for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
- _spawnSys.EquipStartingGear(_entity, _gear, null);
+ _spawnSys.EquipStartingGear(_entity, _gear);
server.EntMan.DeleteEntity(_entity);
}
});
using Content.Client.Guidebook;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
+using Content.Shared.Players.PlayTimeTracking;
namespace Content.Client.IoC
{
public static void Register()
{
- IoCManager.Register<IParallaxManager, ParallaxManager>();
- IoCManager.Register<IChatManager, ChatManager>();
- IoCManager.Register<IClientPreferencesManager, ClientPreferencesManager>();
- IoCManager.Register<IStylesheetManager, StylesheetManager>();
- IoCManager.Register<IScreenshotHook, ScreenshotHook>();
- IoCManager.Register<FullscreenHook, FullscreenHook>();
- IoCManager.Register<IClickMapManager, ClickMapManager>();
- IoCManager.Register<IClientAdminManager, ClientAdminManager>();
- IoCManager.Register<ISharedAdminManager, ClientAdminManager>();
- IoCManager.Register<EuiManager, EuiManager>();
- IoCManager.Register<IVoteManager, VoteManager>();
- IoCManager.Register<ChangelogManager, ChangelogManager>();
- IoCManager.Register<RulesManager, RulesManager>();
- IoCManager.Register<ViewportManager, ViewportManager>();
- IoCManager.Register<ISharedAdminLogManager, SharedAdminLogManager>();
- IoCManager.Register<GhostKickManager>();
- IoCManager.Register<ExtendedDisconnectInformationManager>();
- IoCManager.Register<JobRequirementsManager>();
- IoCManager.Register<DocumentParsingManager>();
- IoCManager.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
+ var collection = IoCManager.Instance!;
+
+ collection.Register<IParallaxManager, ParallaxManager>();
+ collection.Register<IChatManager, ChatManager>();
+ collection.Register<IClientPreferencesManager, ClientPreferencesManager>();
+ collection.Register<IStylesheetManager, StylesheetManager>();
+ collection.Register<IScreenshotHook, ScreenshotHook>();
+ collection.Register<FullscreenHook, FullscreenHook>();
+ collection.Register<IClickMapManager, ClickMapManager>();
+ collection.Register<IClientAdminManager, ClientAdminManager>();
+ collection.Register<ISharedAdminManager, ClientAdminManager>();
+ collection.Register<EuiManager, EuiManager>();
+ collection.Register<IVoteManager, VoteManager>();
+ collection.Register<ChangelogManager, ChangelogManager>();
+ collection.Register<RulesManager, RulesManager>();
+ collection.Register<ViewportManager, ViewportManager>();
+ collection.Register<ISharedAdminLogManager, SharedAdminLogManager>();
+ collection.Register<GhostKickManager>();
+ collection.Register<ExtendedDisconnectInformationManager>();
+ collection.Register<JobRequirementsManager>();
+ collection.Register<DocumentParsingManager>();
+ collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
+ collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
}
}
}
_characterSetup.SaveButton.OnPressed += _ =>
{
_characterSetup.Save();
- _lobby.CharacterPreview.UpdateUI();
+ _userInterfaceManager.GetUIController<LobbyUIController>().UpdateCharacterUI();
};
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
-
- _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
-
- _lobby.CharacterPreview.UpdateUI();
}
protected override void Shutdown()
_characterSetup?.Dispose();
_characterSetup = null;
-
- _preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
- }
-
- private void PreferencesDataLoaded()
- {
- _lobby?.CharacterPreview.UpdateUI();
}
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
--- /dev/null
+using System.Linq;
+using Content.Client.Humanoid;
+using Content.Client.Inventory;
+using Content.Client.Lobby.UI;
+using Content.Client.Preferences;
+using Content.Client.Station;
+using Content.Shared.Clothing;
+using Content.Shared.GameTicking;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Robust.Client.State;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby;
+
+public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
+{
+ [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
+ [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
+ [UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
+
+ private LobbyCharacterPreviewPanel? _previewPanel;
+
+ /*
+ * Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor
+ * that is shared too.
+ */
+
+ /// <summary>
+ /// Preview dummy for role gear.
+ /// </summary>
+ private EntityUid? _previewDummy;
+
+ /// <summary>
+ /// If we currently have a loadout selected.
+ /// </summary>
+ private JobPrototype? _dummyJob;
+
+ // TODO: Load the species directly and don't update entity ever.
+ public event Action<EntityUid>? PreviewDummyUpdated;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
+ }
+
+ private void PreferencesDataLoaded()
+ {
+ UpdateCharacterUI();
+ }
+
+ public void OnStateEntered(LobbyState state)
+ {
+ }
+
+ public void OnStateExited(LobbyState state)
+ {
+ EntityManager.DeleteEntity(_previewDummy);
+ _previewDummy = null;
+ }
+
+ public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel)
+ {
+ _previewPanel = panel;
+ UpdateCharacterUI();
+ }
+
+ public void SetDummyJob(JobPrototype? job)
+ {
+ _dummyJob = job;
+ UpdateCharacterUI();
+ }
+
+ public void UpdateCharacterUI()
+ {
+ // Test moment
+ if (_stateManager.CurrentState is not LobbyState)
+ return;
+
+ if (!_preferencesManager.ServerDataLoaded)
+ {
+ _previewPanel?.SetLoaded(false);
+ return;
+ }
+
+ _previewPanel?.SetLoaded(true);
+
+ if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
+ {
+ _previewPanel?.SetSummaryText(string.Empty);
+ }
+ else
+ {
+ EntityManager.DeleteEntity(_previewDummy);
+ _previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
+ _previewPanel?.SetSprite(_previewDummy.Value);
+ _previewPanel?.SetSummaryText(selectedCharacter.Summary);
+ _humanoid.LoadProfile(_previewDummy.Value, selectedCharacter);
+
+ GiveDummyJobClothesLoadout(_previewDummy.Value, selectedCharacter);
+ PreviewDummyUpdated?.Invoke(_previewDummy.Value);
+ }
+ }
+
+ /// <summary>
+ /// Applies the highest priority job's clothes to the dummy.
+ /// </summary>
+ public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile)
+ {
+ var job = _dummyJob ?? GetPreferredJob(profile);
+ GiveDummyJobClothes(dummy, profile, job);
+
+ if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager);
+ GiveDummyLoadout(dummy, loadout);
+ }
+ }
+
+ /// <summary>
+ /// Gets the highest priority job for the profile.
+ /// </summary>
+ public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
+ {
+ var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
+ return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
+ }
+
+ public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
+ {
+ if (roleLoadout == null)
+ return;
+
+ foreach (var group in roleLoadout.SelectedLoadouts.Values)
+ {
+ foreach (var loadout in group)
+ {
+ if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ continue;
+
+ _spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Applies the specified job's clothes to the dummy.
+ /// </summary>
+ public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job)
+ {
+ if (!_inventory.TryGetSlots(dummy, out var slots))
+ return;
+
+ // Apply loadout
+ if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout))
+ {
+ foreach (var loadouts in jobLoadout.SelectedLoadouts.Values)
+ {
+ foreach (var loadout in loadouts)
+ {
+ if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ continue;
+
+ // TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
+ var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
+
+ foreach (var slot in slots)
+ {
+ var itemType = loadoutGear.GetGear(slot.Name);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ {
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ if (itemType != string.Empty)
+ {
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+ }
+ }
+ }
+
+ if (job.StartingGear == null)
+ return;
+
+ var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
+
+ foreach (var slot in slots)
+ {
+ var itemType = gear.GetGear(slot.Name);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ {
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ if (itemType != string.Empty)
+ {
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+ }
+
+ public EntityUid? GetPreviewDummy()
+ {
+ return _previewDummy;
+ }
+}
+++ /dev/null
-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.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;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Lobby.UI
-{
- public sealed class LobbyCharacterPreviewPanel : Control
- {
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
-
- private EntityUid? _previewDummy;
- private readonly Label _summaryLabel;
- private readonly BoxContainer _loaded;
- private readonly BoxContainer _viewBox;
- private readonly Label _unloaded;
-
- public LobbyCharacterPreviewPanel()
- {
- IoCManager.InjectDependencies(this);
- var header = new NanoHeading
- {
- Text = Loc.GetString("lobby-character-preview-panel-header")
- };
-
- CharacterSetupButton = new Button
- {
- Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"),
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(0, 5, 0, 0),
- };
-
- _summaryLabel = new Label
- {
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(3, 3),
- };
-
- var vBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
- _unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") };
-
- _loaded = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Visible = false
- };
- _viewBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = HAlignment.Center,
- };
- var _vSpacer = new VSpacer();
-
- _loaded.AddChild(_summaryLabel);
- _loaded.AddChild(_viewBox);
- _loaded.AddChild(_vSpacer);
- _loaded.AddChild(CharacterSetupButton);
-
- vBox.AddChild(header);
- vBox.AddChild(_loaded);
- vBox.AddChild(_unloaded);
- AddChild(vBox);
-
- UpdateUI();
- }
-
- public Button CharacterSetupButton { get; }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing) return;
- if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value);
- _previewDummy = default;
- }
-
- public void UpdateUI()
- {
- if (!_preferencesManager.ServerDataLoaded)
- {
- _loaded.Visible = false;
- _unloaded.Visible = true;
- }
- else
- {
- _loaded.Visible = true;
- _unloaded.Visible = false;
- if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
- {
- _summaryLabel.Text = string.Empty;
- }
- else
- {
- _previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
- _viewBox.DisposeAllChildren();
- var spriteView = new SpriteView
- {
- OverrideDirection = Direction.South,
- Scale = new Vector2(4f, 4f),
- MaxSize = new Vector2(112, 112),
- Stretch = SpriteView.StretchMode.Fill,
- };
- spriteView.SetEntity(_previewDummy.Value);
- _viewBox.AddChild(spriteView);
- _summaryLabel.Text = selectedCharacter.Summary;
- _entityManager.System<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
- GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
- }
- }
- }
-
- public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile)
- {
- var protoMan = IoCManager.Resolve<IPrototypeManager>();
- var entMan = IoCManager.Resolve<IEntityManager>();
- var invSystem = EntitySystem.Get<ClientInventorySystem>();
-
- var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
-
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
- var job = protoMan.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
-
- if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots))
- {
- var gear = protoMan.Index<StartingGearPrototype>(job.StartingGear);
-
- foreach (var slot in slots)
- {
- var itemType = gear.GetGear(slot.Name, profile);
-
- if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
- {
- entMan.DeleteEntity(unequippedItem.Value);
- }
-
- if (itemType != string.Empty)
- {
- var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace);
- invSystem.TryEquip(dummy, item, slot.Name, true, true);
- }
- }
- }
- }
- }
-}
--- /dev/null
+<Control
+ xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
+ <BoxContainer Name="VBox" Orientation="Vertical">
+ <controls:NanoHeading Name="Header" Text="{Loc 'lobby-character-preview-panel-header'}">
+
+ </controls:NanoHeading>
+ <BoxContainer Name="Loaded" Orientation="Vertical"
+ Visible="False">
+ <Label Name="Summary" HorizontalAlignment="Center" Margin="3 3"/>
+ <BoxContainer Name="ViewBox" Orientation="Horizontal" HorizontalAlignment="Center">
+
+ </BoxContainer>
+ <controls:VSpacer/>
+ <Button Name="CharacterSetup" Text="{Loc 'lobby-character-preview-panel-character-setup-button'}"
+ HorizontalAlignment="Center"
+ Margin="0 5 0 0"/>
+ </BoxContainer>
+ <Label Name="Unloaded" Text="{Loc 'lobby-character-preview-panel-unloaded-preferences-label'}"/>
+ </BoxContainer>
+</Control>
--- /dev/null
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LobbyCharacterPreviewPanel : Control
+{
+ public Button CharacterSetupButton => CharacterSetup;
+
+ public LobbyCharacterPreviewPanel()
+ {
+ RobustXamlLoader.Load(this);
+ UserInterfaceManager.GetUIController<LobbyUIController>().SetPreviewPanel(this);
+ }
+
+ public void SetLoaded(bool value)
+ {
+ Loaded.Visible = value;
+ Unloaded.Visible = !value;
+ }
+
+ public void SetSummaryText(string value)
+ {
+ Summary.Text = string.Empty;
+ }
+
+ public void SetSprite(EntityUid uid)
+ {
+ ViewBox.DisposeAllChildren();
+ var spriteView = new SpriteView
+ {
+ OverrideDirection = Direction.South,
+ Scale = new Vector2(4f, 4f),
+ MaxSize = new Vector2(112, 112),
+ Stretch = SpriteView.StretchMode.Fill,
+ };
+ spriteView.SetEntity(uid);
+ ViewBox.AddChild(spriteView);
+ }
+}
-using Content.Client.Chat.UI;
-using Content.Client.Info;
using Content.Client.Message;
-using Content.Client.Preferences;
-using Content.Client.Preferences.UI;
-using Content.Client.UserInterface.Screens;
-using Content.Client.UserInterface.Systems.Chat.Widgets;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
-using Robust.Client.Graphics;
-using Robust.Client.State;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Lobby.UI
{
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Players.PlayTimeTracking;
-public sealed class JobRequirementsManager
+public sealed class JobRequirementsManager : ISharedPlaytimeManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientNetManager _net = default!;
}
}
+ public IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session)
+ {
+ if (session != _playerManager.LocalSession)
+ {
+ return new Dictionary<string, TimeSpan>();
+ }
+ return _roles;
+ }
}
-using System;
-using System.Collections.Generic;
using System.Linq;
using Content.Shared.Preferences;
using Robust.Client;
+using Robust.Client.Player;
using Robust.Shared.Configuration;
-using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IPrototypeManager _prototypes = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? OnServerDataLoaded;
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
- profile.EnsureValid(_cfg, _prototypes);
+ var collection = IoCManager.Instance!;
+ profile.EnsureValid(_playerManager.LocalSession!, collection);
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter
--- /dev/null
+using Content.Client.Players.PlayTimeTracking;
+using Content.Shared.Roles;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Preferences.UI;
+
+public sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
+{
+ // 0 is yes and 1 is no
+ public bool Preference
+ {
+ get => Options.SelectedValue == 0;
+ set => Options.Select((value && !Disabled) ? 0 : 1);
+ }
+
+ public event Action<bool>? PreferenceChanged;
+
+ public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup)
+ : base(proto, btnGroup)
+ {
+ Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-antag-preference-yes-button", 0),
+ ("humanoid-profile-editor-antag-preference-no-button", 1)
+ };
+ var title = Loc.GetString(proto.Name);
+ var description = Loc.GetString(proto.Objective);
+ // Not supported yet get fucked.
+ Setup(null, items, title, 250, description);
+
+ // immediately lock requirements if they arent met.
+ // another function checks Disabled after creating the selector so this has to be done now
+ var requirements = IoCManager.Resolve<JobRequirementsManager>();
+ if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
+ {
+ LockRequirements(reason);
+ }
+ }
+}
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" ContentMarginTopOverride="2" />
</PanelContainer.PanelOverride>
</PanelContainer>
- <BoxContainer Name="CharEditor" />
+ <BoxContainer Name="CharEditor" HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</Control>
using Content.Client.Humanoid;
using Content.Client.Info;
using Content.Client.Info.PlaytimeStats;
-using Content.Client.Lobby.UI;
+using Content.Client.Lobby;
using Content.Client.Resources;
using Content.Client.Stylesheets;
+using Content.Shared.Clothing;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
using Robust.Shared.Map;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
private readonly IClientPreferencesManager _preferencesManager;
private readonly IEntityManager _entityManager;
private readonly IPrototypeManager _prototypeManager;
- private readonly IConfigurationManager _configurationManager;
private readonly Button _createNewCharacterButton;
private readonly HumanoidProfileEditor _humanoidProfileEditor;
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
args.Event.Handle();
};
- _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
+ _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
_humanoidProfileEditor.OnProfileChanged += ProfileChanged;
CharEditor.AddChild(_humanoidProfileEditor);
private void UpdateUI()
{
+ UserInterfaceManager.GetUIController<LobbyUIController>().UpdateCharacterUI();
var numberOfFullSlots = 0;
var characterButtonsGroup = new ButtonGroup();
Characters.RemoveAllChildren();
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
{
- if (character is null)
- {
- continue;
- }
-
numberOfFullSlots++;
var characterPickerButton = new CharacterPickerButton(_entityManager,
_preferencesManager,
_createNewCharacterButton.Disabled =
numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
Characters.AddChild(_createNewCharacterButton);
+ // TODO: Move this shit to the Lobby UI controller
}
+ /// <summary>
+ /// Shows individual characters on the side of the character GUI.
+ /// </summary>
private sealed class CharacterPickerButton : ContainerButton
{
private EntityUid _previewDummy;
if (humanoid != null)
{
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ var job = controller.GetPreferredJob(humanoid);
+ controller.GiveDummyJobClothes(_previewDummy, humanoid, job);
+
+ if (prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), entityManager, prototypeManager);
+ controller.GiveDummyLoadout(_previewDummy, loadout);
+ }
}
var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
--- /dev/null
+<PanelContainer
+ xmlns="https://spacestation14.io"
+ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
+ <PanelContainer.PanelOverride>
+ <graphics:StyleBoxFlat BackgroundColor="#2F2F35"
+ ContentMarginTopOverride="10"
+ ContentMarginBottomOverride="10"
+ ContentMarginLeftOverride="10"
+ ContentMarginRightOverride="10"/>
+ </PanelContainer.PanelOverride>
+</PanelContainer>
--- /dev/null
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class HighlightedContainer : PanelContainer
+{
+ public HighlightedContainer()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
{
public sealed partial class HumanoidProfileEditor
{
- private readonly IPrototypeManager _prototypeManager;
-
private void RandomizeEverything()
{
Profile = HumanoidCharacterProfile.Random();
-<Control xmlns="https://spacestation14.io"
+<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
xmlns:humanoid="clr-namespace:Content.Client.Humanoid"
- xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
- <BoxContainer Orientation="Horizontal">
+ xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ HorizontalExpand="True">
<!-- Left side -->
- <BoxContainer Orientation="Vertical" Margin="10 10 10 10">
+ <BoxContainer Orientation="Vertical" Margin="10 10 10 10" HorizontalExpand="True">
<!-- Middle container -->
<BoxContainer Orientation="Horizontal" SeparationOverride="10">
<!-- Name box-->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-species-label'}" />
<Control HorizontalExpand="True"/>
- <TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3" VerticalAlignment="Center"></TextureButton>
+ <TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3"
+ VerticalAlignment="Center"
+ ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/>
<OptionButton Name="CSpeciesButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Age -->
<Control HorizontalExpand="True"/>
<Button Name="ShowClothes" Pressed="True" ToggleMode="True" Text="{Loc 'humanoid-profile-editor-clothing-show'}" HorizontalAlignment="Right" />
</BoxContainer>
- <!-- Clothing -->
- <BoxContainer HorizontalExpand="True">
- <Label Text="{Loc 'humanoid-profile-editor-clothing-label'}" />
- <Control HorizontalExpand="True"/>
- <OptionButton Name="CClothingButton" HorizontalAlignment="Right" />
- </BoxContainer>
- <!-- Backpack -->
- <BoxContainer HorizontalExpand="True">
- <Label Text="{Loc 'humanoid-profile-editor-backpack-label'}" />
- <Control HorizontalExpand="True"/>
- <OptionButton Name="CBackpackButton" HorizontalAlignment="Right" />
- </BoxContainer>
<!-- Spawn Priority -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-spawn-priority-label'}" />
</TabContainer>
</BoxContainer>
<!-- Right side -->
- <BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Center">
+ <BoxContainer Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Center">
<SpriteView Name="CSpriteView" Scale="8 8" SizeFlagsStretchRatio="1" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 5">
<Button Name="CSpriteRotateLeft" Text="◀" StyleClasses="OpenRight" />
<Button Name="CSpriteRotateRight" Text="▶" StyleClasses="OpenLeft" />
</BoxContainer>
</BoxContainer>
- </BoxContainer>
-</Control>
+</BoxContainer>
using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
-using Content.Client.Lobby.UI;
+using Content.Client.Lobby;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
+using Content.Shared.Clothing;
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.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
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;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
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;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Preferences.UI
{
- public sealed class HighlightedContainer : PanelContainer
- {
- public HighlightedContainer()
- {
- PanelOverride = new StyleBoxFlat()
- {
- BackgroundColor = new Color(47, 47, 53),
- ContentMarginTopOverride = 10,
- ContentMarginBottomOverride = 10,
- ContentMarginLeftOverride = 10,
- ContentMarginRightOverride = 10
- };
- }
- }
-
[GenerateTypedNameReferences]
- public sealed partial class HumanoidProfileEditor : Control
+ public sealed partial class HumanoidProfileEditor : BoxContainer
{
private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entMan;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IPrototypeManager _prototypeManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
- private TextEdit _flavorTextEdit = null!;
+ private TextEdit? _flavorTextEdit;
private Button _nameRandomButton => CNameRandomize;
private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel;
private OptionButton _sexButton => CSexButton;
private OptionButton _genderButton => CPronounsButton;
private Slider _skinColor => CSkin;
- private OptionButton _clothingButton => CClothingButton;
- private OptionButton _backpackButton => CBackpackButton;
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
private readonly Dictionary<string, BoxContainer> _jobCategories;
// Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
private readonly List<SpeciesPrototype> _speciesList;
- private readonly List<AntagPreferenceSelector> _antagPreferences;
+ private readonly List<AntagPreferenceSelector> _antagPreferences = new();
private readonly List<TraitPreferenceSelector> _traitPreferences;
private SpriteView _previewSpriteView => CSpriteView;
private Button _previewRotateLeftButton => CSpriteRotateLeft;
private Button _previewRotateRightButton => CSpriteRotateRight;
private Direction _previewRotation = Direction.North;
- private EntityUid? _previewDummy;
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
private ColorSelectorSliders _rgbSkinColorSelector;
private bool _isDirty;
- private bool _needUpdatePreview;
public int CharacterSlot;
public HumanoidCharacterProfile? Profile;
- private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
- public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
- IEntityManager entityManager, IConfigurationManager configurationManager)
+ [ValidatePrototypeId<GuideEntryPrototype>]
+ private const string DefaultSpeciesGuidebook = "Species";
+
+ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
{
RobustXamlLoader.Load(this);
_prototypeManager = prototypeManager;
- _entMan = entityManager;
_preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
_markingManager = IoCManager.Resolve<MarkingManager>();
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ controller.PreviewDummyUpdated += OnDummyUpdate;
- SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
+ _previewSpriteView.SetEntity(controller.GetPreviewDummy());
#region Left
- #region Randomize
-
- #endregion Randomize
-
#region Name
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
- ShowClothes.OnPressed += ToggleClothes;
-
#region Sex
_sexButton.OnItemSelected += args =>
#endregion Hair
- #region Clothing
-
- _clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpsuit"), (int) ClothingPreference.Jumpsuit);
- _clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpskirt"), (int) ClothingPreference.Jumpskirt);
-
- _clothingButton.OnItemSelected += args =>
- {
- _clothingButton.SelectId(args.Id);
- SetClothing((ClothingPreference) args.Id);
- };
-
- #endregion Clothing
-
- #region Backpack
-
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-backpack"), (int) BackpackPreference.Backpack);
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-satchel"), (int) BackpackPreference.Satchel);
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-duffelbag"), (int) BackpackPreference.Duffelbag);
-
- _backpackButton.OnItemSelected += args =>
- {
- _backpackButton.SelectId(args.Id);
- SetBackpack((BackpackPreference) args.Id);
- };
-
- #endregion Backpack
-
#region SpawnPriority
foreach (var value in Enum.GetValues<SpawnPriorityPreference>())
_jobPriorities = new List<JobPrioritySelector>();
_jobCategories = new Dictionary<string, BoxContainer>();
_requirements = IoCManager.Resolve<JobRequirementsManager>();
+ // TODO: Move this to the LobbyUIController instead of being spaghetti everywhere.
+ _requirements.Updated += UpdateAntagRequirements;
_requirements.Updated += UpdateRoleRequirements;
+ UpdateAntagRequirements();
UpdateRoleRequirements();
#endregion Jobs
- #region Antags
-
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
- _antagPreferences = new List<AntagPreferenceSelector>();
-
- foreach (var antag in prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
- {
- if (!antag.SetPreference)
- continue;
-
- var selector = new AntagPreferenceSelector(antag);
- _antagList.AddChild(selector);
- _antagPreferences.Add(selector);
- if (selector.Disabled)
- {
- Profile = Profile?.WithAntagPreference(antag.ID, false);
- IsDirty = true;
- }
-
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithAntagPreference(antag.ID, preference);
- IsDirty = true;
- };
- }
-
- #endregion Antags
-
#region Traits
var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
#region FlavorText
- if (_configurationManager.GetCVar(CCVars.FlavorText))
+ if (configurationManager.GetCVar(CCVars.FlavorText))
{
var flavorText = new FlavorText.FlavorText();
_tabContainer.AddChild(flavorText);
_previewRotateLeftButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCw();
- _needUpdatePreview = true;
+ SetPreviewRotation(_previewRotation);
};
_previewRotateRightButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCcw();
- _needUpdatePreview = true;
+ SetPreviewRotation(_previewRotation);
};
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
#endregion Dummy
#endregion Left
{
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var page = "Species";
+ var page = DefaultSpeciesGuidebook;
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
page = species;
- if (_prototypeManager.TryIndex<GuideEntryPrototype>("Species", out var guideRoot))
+ if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
{
var dict = new Dictionary<string, GuideEntry>();
- dict.Add("Species", guideRoot);
+ dict.Add(DefaultSpeciesGuidebook, guideRoot);
//TODO: Don't close the guidebook if its already open, just go to the correct page
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
}
}
- private void ToggleClothes(BaseButton.ButtonEventArgs obj)
+ private void OnDummyUpdate(EntityUid value)
{
- RebuildSpriteView();
+ _previewSpriteView.SetEntity(value);
+ }
+
+ private void UpdateAntagRequirements()
+ {
+ _antagList.DisposeAllChildren();
+ _antagPreferences.Clear();
+ var btnGroup = new ButtonGroup();
+
+ foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
+ {
+ if (!antag.SetPreference)
+ continue;
+
+ var selector = new AntagPreferenceSelector(antag, btnGroup)
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
+ _antagList.AddChild(selector);
+ _antagPreferences.Add(selector);
+ if (selector.Disabled)
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, false);
+ IsDirty = true;
+ }
+
+ selector.PreferenceChanged += preference =>
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, preference);
+ IsDirty = true;
+ };
+ }
+
}
private void UpdateRoleRequirements()
.Where(job => job.SetPreference)
.ToArray();
Array.Sort(jobs, JobUIComparer.Instance);
+ var jobLoadoutGroup = new ButtonGroup();
foreach (var job in jobs)
{
- var selector = new JobPrioritySelector(job, _prototypeManager);
+ RoleLoadout? loadout = null;
+ Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
+ var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager)
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
if (!_requirements.IsAllowed(job, out var reason))
{
category.AddChild(selector);
_jobPriorities.Add(selector);
+ selector.LoadoutUpdated += args =>
+ {
+ Profile?.SetLoadout(args);
+ UserInterfaceManager.GetUIController<LobbyUIController>().UpdateCharacterUI();
+ IsDirty = true;
+ };
+
selector.PriorityChanged += priority =>
{
Profile = Profile?.WithJobPriority(job.ID, priority);
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
- _needUpdatePreview = true;
+ UpdatePreview();
IsDirty = true;
}
- private void OnMarkingColorChange(List<Marking> markings)
- {
- if (Profile is null)
- return;
-
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
- IsDirty = true;
- }
-
-
private void OnSkinColorOnValueChanged()
{
if (Profile is null) return;
if (!disposing)
return;
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy.Value);
-
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ controller.PreviewDummyUpdated -= OnDummyUpdate;
+ _requirements.Updated -= UpdateAntagRequirements;
_requirements.Updated -= UpdateRoleRequirements;
_preferencesManager.OnServerDataLoaded -= LoadServerData;
}
- private void RebuildSpriteView()
- {
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
- _needUpdatePreview = true;
- }
-
private void LoadServerData()
{
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
+ UpdateAntagRequirements();
+ UpdateRoleRequirements();
UpdateControls();
- _needUpdatePreview = true;
}
private void SetAge(int newAge)
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
UpdateSexControls(); // update sex for new species
- RebuildSpriteView(); // they might have different inv so we need a new dummy
UpdateSpeciesGuidebookIcon();
IsDirty = true;
- _needUpdatePreview = true;
+ UpdatePreview();
}
private void SetName(string newName)
IsDirty = true;
}
- private void SetClothing(ClothingPreference newClothing)
- {
- Profile = Profile?.WithClothingPreference(newClothing);
- IsDirty = true;
- }
-
- private void SetBackpack(BackpackPreference newBackpack)
- {
- Profile = Profile?.WithBackpackPreference(newBackpack);
- IsDirty = true;
- }
-
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
{
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
{
IsDirty = false;
- if (Profile != null)
- {
- _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
- OnProfileChanged?.Invoke(Profile, CharacterSlot);
- _needUpdatePreview = true;
- }
+ if (Profile == null)
+ return;
+
+ _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
+ OnProfileChanged?.Invoke(Profile, CharacterSlot);
}
private bool IsDirty
set
{
_isDirty = value;
- _needUpdatePreview = true;
UpdateSaveButton();
}
}
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(species))
return;
- var style = speciesProto.GuideBookIcon;
+ const string style = "SpeciesInfoDefault";
SpeciesInfoButton.StyleClasses.Add(style);
}
_genderButton.SelectId((int) Profile.Gender);
}
- private void UpdateClothingControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _clothingButton.SelectId((int) Profile.Clothing);
- }
-
- private void UpdateBackpackControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _backpackButton.SelectId((int) Profile.Backpack);
- }
-
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
if (Profile is null)
return;
- var humanoid = _entMan.System<HumanoidAppearanceSystem>();
- humanoid.LoadProfile(_previewDummy!.Value, Profile);
-
- if (ShowClothes.Pressed)
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
+ UserInterfaceManager.GetUIController<LobbyUIController>().UpdateCharacterUI();
+ SetPreviewRotation(_previewRotation);
+ }
- _previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
+ private void SetPreviewRotation(Direction direction)
+ {
+ _previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
}
public void UpdateControls()
UpdateGenderControls();
UpdateSkinColor();
UpdateSpecies();
- UpdateClothingControls();
- UpdateBackpackControls();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();
+ UpdateLoadouts();
UpdateJobPriorities();
UpdateAntagPreferences();
UpdateTraitPreferences();
UpdateMarkings();
- RebuildSpriteView();
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- if (_needUpdatePreview)
- {
- UpdatePreview();
- _needUpdatePreview = false;
- }
- }
-
private void UpdateJobPriorities()
{
foreach (var prioritySelector in _jobPriorities)
}
}
- private abstract class RequirementsSelector<T> : Control
+ private void UpdateLoadouts()
{
- public T Proto { get; }
- public bool Disabled => _lockStripe.Visible;
-
- protected readonly RadioOptions<int> Options;
- private StripeBack _lockStripe;
- private Label _requirementsLabel;
-
- protected RequirementsSelector(T proto)
- {
- Proto = proto;
-
- Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
- {
- FirstButtonStyle = StyleBase.ButtonOpenRight,
- ButtonStyle = StyleBase.ButtonOpenBoth,
- LastButtonStyle = StyleBase.ButtonOpenLeft
- };
- //Override default radio option button width
- Options.GenerateItem = GenerateButton;
-
- Options.OnItemSelected += args => Options.Select(args.Id);
-
- _requirementsLabel = new Label()
- {
- Text = Loc.GetString("role-timer-locked"),
- Visible = true,
- HorizontalAlignment = HAlignment.Center,
- StyleClasses = {StyleBase.StyleClassLabelSubText},
- };
-
- _lockStripe = new StripeBack()
- {
- Visible = false,
- HorizontalExpand = true,
- MouseFilter = MouseFilterMode.Stop,
- Children =
- {
- _requirementsLabel
- }
- };
-
- // Setup must be called after
- }
-
- /// <summary>
- /// Actually adds the controls, must be called in the inheriting class' constructor.
- /// </summary>
- protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
- {
- foreach (var (text, value) in items)
- {
- Options.AddItem(Loc.GetString(text), value);
- }
-
- var titleLabel = new Label()
- {
- Margin = new Thickness(5f, 0, 5f, 0),
- Text = title,
- MinSize = new Vector2(titleSize, 0),
- MouseFilter = MouseFilterMode.Stop,
- ToolTip = description
- };
-
- var container = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- };
-
- if (icon != null)
- container.AddChild(icon);
- container.AddChild(titleLabel);
- container.AddChild(Options);
- container.AddChild(_lockStripe);
-
- AddChild(container);
- }
-
- public void LockRequirements(FormattedMessage requirements)
- {
- var tooltip = new Tooltip();
- tooltip.SetMessage(requirements);
- _lockStripe.TooltipSupplier = _ => tooltip;
- _lockStripe.Visible = true;
- Options.Visible = false;
- }
-
- // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
- public void UnlockRequirements()
- {
- _lockStripe.Visible = false;
- Options.Visible = true;
- }
-
- private Button GenerateButton(string text, int value)
- {
- return new Button
- {
- Text = text,
- MinWidth = 90
- };
- }
- }
-
- private sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
- {
- public JobPriority Priority
- {
- get => (JobPriority) Options.SelectedValue;
- set => Options.SelectByValue((int) value);
- }
-
- public event Action<JobPriority>? PriorityChanged;
-
- public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan)
- : base(proto)
+ foreach (var prioritySelector in _jobPriorities)
{
- Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
-
- var items = new[]
- {
- ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
- ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
- ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
- ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
- };
-
- var icon = new TextureRect
- {
- TextureScale = new Vector2(2, 2),
- VerticalAlignment = VAlignment.Center
- };
- var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
- icon.Texture = jobIcon.Icon.Frame0();
-
- Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
+ prioritySelector.CloseLoadout();
}
}
}
}
- private sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
- {
- // 0 is yes and 1 is no
- public bool Preference
- {
- get => Options.SelectedValue == 0;
- set => Options.Select((value && !Disabled) ? 0 : 1);
- }
-
- public event Action<bool>? PreferenceChanged;
-
- public AntagPreferenceSelector(AntagPrototype proto)
- : base(proto)
- {
- Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
-
- var items = new[]
- {
- ("humanoid-profile-editor-antag-preference-yes-button", 0),
- ("humanoid-profile-editor-antag-preference-no-button", 1)
- };
- var title = Loc.GetString(proto.Name);
- var description = Loc.GetString(proto.Objective);
- Setup(items, title, 250, description);
-
- // immediately lock requirements if they arent met.
- // another function checks Disabled after creating the selector so this has to be done now
- var requirements = IoCManager.Resolve<JobRequirementsManager>();
- if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
- {
- LockRequirements(reason);
- }
- }
- }
-
private sealed class TraitPreferenceSelector : Control
{
public TraitPrototype Trait { get; }
--- /dev/null
+using System.Numerics;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Content.Shared.StatusIcon;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.Utility;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+public sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
+{
+ public JobPriority Priority
+ {
+ get => (JobPriority) Options.SelectedValue;
+ set => Options.SelectByValue((int) value);
+ }
+
+ public event Action<JobPriority>? PriorityChanged;
+
+ public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan)
+ : base(proto, btnGroup)
+ {
+ Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
+ ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
+ ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
+ ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
+ };
+
+ var icon = new TextureRect
+ {
+ TextureScale = new Vector2(2, 2),
+ VerticalAlignment = VAlignment.Center
+ };
+ var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
+ icon.Texture = jobIcon.Icon.Frame0();
+
+ Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
+ }
+}
--- /dev/null
+<BoxContainer Name="Container" xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+ Orientation="Horizontal"
+ HorizontalExpand="True"
+ MouseFilter="Ignore"
+ Margin="0 0 0 5">
+ <Button Name="SelectButton" ToggleMode="True" Margin="0 0 5 0" HorizontalExpand="True"/>
+ <PanelContainer SetSize="64 64" HorizontalAlignment="Right">
+ <PanelContainer.PanelOverride>
+ <graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
+ </PanelContainer.PanelOverride>
+ <SpriteView Name="Sprite" Scale="4 4" MouseFilter="Stop"/>
+ </PanelContainer>
+</BoxContainer>
--- /dev/null
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutContainer : BoxContainer
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
+ private readonly EntityUid? _entity;
+
+ public Button Select => SelectButton;
+
+ public LoadoutContainer(ProtoId<LoadoutPrototype> proto, bool disabled, FormattedMessage? reason)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ SelectButton.Disabled = disabled;
+
+ if (disabled && reason != null)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(reason);
+ SelectButton.TooltipSupplier = _ => tooltip;
+ }
+
+ if (_protoManager.TryIndex(proto, out var loadProto))
+ {
+ var ent = _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
+
+ if (ent != null)
+ {
+ _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
+ Sprite.SetEntity(_entity);
+
+ var spriteTooltip = new Tooltip();
+ spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
+ Sprite.TooltipSupplier = _ => spriteTooltip;
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ _entManager.DeleteEntity(_entity);
+ }
+
+ public bool Pressed
+ {
+ get => SelectButton.Pressed;
+ set => SelectButton.Pressed = value;
+ }
+
+ public string? Text
+ {
+ get => SelectButton.Text;
+ set => SelectButton.Text = value;
+ }
+}
--- /dev/null
+<BoxContainer xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ Orientation="Vertical">
+ <PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
+ <BoxContainer Name="LoadoutsContainer" Orientation="Vertical"/>
+ </PanelContainer>
+ <!-- Buffer space so we have 10 margin between controls but also 10 to the borders -->
+ <Label Text="{Loc 'loadout-restrictions'}" Margin="5 0 5 5"/>
+ <BoxContainer Name="RestrictionsContainer" Orientation="Vertical" HorizontalExpand="True" />
+</BoxContainer>
--- /dev/null
+using System.Linq;
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutGroupContainer : BoxContainer
+{
+ private readonly LoadoutGroupPrototype _groupProto;
+
+ public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
+ public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
+
+ public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
+ {
+ RobustXamlLoader.Load(this);
+ _groupProto = groupProto;
+
+ RefreshLoadouts(loadout, session, collection);
+ }
+
+ /// <summary>
+ /// Updates button availabilities and buttons.
+ /// </summary>
+ public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ {
+ var protoMan = collection.Resolve<IPrototypeManager>();
+ var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
+ RestrictionsContainer.DisposeAllChildren();
+
+ if (_groupProto.MinLimit > 0)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-min-limit", ("count", _groupProto.MinLimit)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ if (_groupProto.MaxLimit > 0)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-max-limit", ("count", _groupProto.MaxLimit)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ if (protoMan.TryIndex(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-points-limit", ("count", loadout.Points.Value), ("max", roleProto.Points.Value)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ LoadoutsContainer.DisposeAllChildren();
+ // Didn't use options because this is more robust in future.
+
+ var selected = loadout.SelectedLoadouts[_groupProto.ID];
+
+ foreach (var loadoutProto in _groupProto.Loadouts)
+ {
+ if (!protoMan.TryIndex(loadoutProto, out var loadProto))
+ continue;
+
+ var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
+ var pressed = matchingLoadout != null;
+
+ var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason);
+ var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
+ loadoutContainer.Select.Pressed = pressed;
+ loadoutContainer.Text = loadoutSystem.GetName(loadProto);
+
+ loadoutContainer.Select.OnPressed += args =>
+ {
+ if (args.Button.Pressed)
+ OnLoadoutPressed?.Invoke(loadoutProto);
+ else
+ OnLoadoutUnpressed?.Invoke(loadoutProto);
+ };
+
+ LoadoutsContainer.AddChild(loadoutContainer);
+ }
+ }
+}
--- /dev/null
+<controls:FancyWindow xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ SetSize="800 800"
+ MinSize="800 64">
+ <VerticalTabContainer Name="LoadoutGroupsContainer"
+ VerticalExpand="True"
+ HorizontalExpand="True">
+ </VerticalTabContainer>
+</controls:FancyWindow>
--- /dev/null
+using Content.Client.Lobby;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutWindow : FancyWindow
+{
+ public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
+ public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
+
+ private List<LoadoutGroupContainer> _groups = new();
+
+ public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
+ {
+ RobustXamlLoader.Load(this);
+ var protoManager = collection.Resolve<IPrototypeManager>();
+
+ foreach (var group in proto.Groups)
+ {
+ if (!protoManager.TryIndex(group, out var groupProto))
+ continue;
+
+ var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection);
+ LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
+ _groups.Add(container);
+
+ container.OnLoadoutPressed += args =>
+ {
+ OnLoadoutPressed?.Invoke(group, args);
+ };
+
+ container.OnLoadoutUnpressed += args =>
+ {
+ OnLoadoutUnpressed?.Invoke(group, args);
+ };
+ }
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ controller.SetDummyJob(null);
+ }
+
+ public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ {
+ foreach (var group in _groups)
+ {
+ group.RefreshLoadouts(loadout, session, collection);
+ }
+ }
+}
--- /dev/null
+using System.Numerics;
+using Content.Client.Lobby;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Robust.Client.Player;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototype
+{
+ private ButtonGroup _loadoutGroup;
+
+ public T Proto { get; }
+ public bool Disabled => _lockStripe.Visible;
+
+ protected readonly RadioOptions<int> Options;
+ private readonly StripeBack _lockStripe;
+ private LoadoutWindow? _loadoutWindow;
+
+ private RoleLoadout? _loadout;
+
+ /// <summary>
+ /// Raised if a loadout has been updated.
+ /// </summary>
+ public event Action<RoleLoadout>? LoadoutUpdated;
+
+ protected RequirementsSelector(T proto, ButtonGroup loadoutGroup)
+ {
+ _loadoutGroup = loadoutGroup;
+ Proto = proto;
+
+ Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
+ {
+ FirstButtonStyle = StyleBase.ButtonOpenRight,
+ ButtonStyle = StyleBase.ButtonOpenBoth,
+ LastButtonStyle = StyleBase.ButtonOpenLeft,
+ HorizontalExpand = true,
+ };
+ //Override default radio option button width
+ Options.GenerateItem = GenerateButton;
+
+ Options.OnItemSelected += args => Options.Select(args.Id);
+
+ var requirementsLabel = new Label()
+ {
+ Text = Loc.GetString("role-timer-locked"),
+ Visible = true,
+ HorizontalAlignment = HAlignment.Center,
+ StyleClasses = {StyleBase.StyleClassLabelSubText},
+ };
+
+ _lockStripe = new StripeBack()
+ {
+ Visible = false,
+ HorizontalExpand = true,
+ HasMargins = false,
+ MouseFilter = MouseFilterMode.Stop,
+ Children =
+ {
+ requirementsLabel
+ }
+ };
+
+ // Setup must be called after
+ }
+
+ /// <summary>
+ /// Actually adds the controls, must be called in the inheriting class' constructor.
+ /// </summary>
+ protected void Setup(RoleLoadout? loadout, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
+ {
+ _loadout = loadout;
+
+ foreach (var (text, value) in items)
+ {
+ Options.AddItem(Loc.GetString(text), value);
+ }
+
+ var titleLabel = new Label()
+ {
+ Margin = new Thickness(5f, 0, 5f, 0),
+ Text = title,
+ MinSize = new Vector2(titleSize, 0),
+ MouseFilter = MouseFilterMode.Stop,
+ ToolTip = description
+ };
+
+ if (icon != null)
+ AddChild(icon);
+
+ AddChild(titleLabel);
+ AddChild(Options);
+ AddChild(_lockStripe);
+
+ var loadoutWindowBtn = new Button()
+ {
+ Text = Loc.GetString("loadout-window"),
+ HorizontalAlignment = HAlignment.Right,
+ Group = _loadoutGroup,
+ Margin = new Thickness(3f, 0f, 0f, 0f),
+ };
+
+ var collection = IoCManager.Instance!;
+ var protoManager = collection.Resolve<IPrototypeManager>();
+
+ // If no loadout found then disabled button
+ if (!protoManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(Proto.ID)))
+ {
+ loadoutWindowBtn.Disabled = true;
+ }
+ // else
+ else
+ {
+ var session = collection.Resolve<IPlayerManager>().LocalSession!;
+ // TODO: Most of lobby state should be a uicontroller
+ // trying to handle all this shit is a big-ass mess.
+ // Every time I touch it I try to make it slightly better but it needs a howitzer dropped on it.
+ loadoutWindowBtn.OnPressed += args =>
+ {
+ if (args.Button.Pressed)
+ {
+ // We only create a loadout when necessary to avoid unnecessary DB entries.
+ _loadout ??= new RoleLoadout(LoadoutSystem.GetJobPrototype(Proto.ID));
+ _loadout.SetDefault(protoManager);
+
+ _loadoutWindow = new LoadoutWindow(_loadout, protoManager.Index(_loadout.Role), session, collection)
+ {
+ Title = Loc.GetString(Proto.ID + "-loadout"),
+ };
+
+ _loadoutWindow.RefreshLoadouts(_loadout, session, collection);
+
+ // If it's a job preview then refresh it.
+ if (Proto is JobPrototype jobProto)
+ {
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ controller.SetDummyJob(jobProto);
+ }
+
+ _loadoutWindow.OnLoadoutUnpressed += (selectedGroup, selectedLoadout) =>
+ {
+ if (!_loadout.RemoveLoadout(selectedGroup, selectedLoadout, protoManager))
+ return;
+
+ _loadout.EnsureValid(session, collection);
+ _loadoutWindow.RefreshLoadouts(_loadout, session, collection);
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ controller.UpdateCharacterUI();
+ LoadoutUpdated?.Invoke(_loadout);
+ };
+
+ _loadoutWindow.OnLoadoutPressed += (selectedGroup, selectedLoadout) =>
+ {
+ if (!_loadout.AddLoadout(selectedGroup, selectedLoadout, protoManager))
+ return;
+
+ _loadout.EnsureValid(session, collection);
+ _loadoutWindow.RefreshLoadouts(_loadout, session, collection);
+ var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+ controller.UpdateCharacterUI();
+ LoadoutUpdated?.Invoke(_loadout);
+ };
+
+ _loadoutWindow.OpenCenteredLeft();
+ _loadoutWindow.OnClose += () =>
+ {
+ loadoutWindowBtn.Pressed = false;
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ };
+ }
+ else
+ {
+ CloseLoadout();
+ }
+ };
+ }
+
+ AddChild(loadoutWindowBtn);
+ }
+
+ public void CloseLoadout()
+ {
+ _loadoutWindow?.Close();
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ }
+
+ public void LockRequirements(FormattedMessage requirements)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(requirements);
+ _lockStripe.TooltipSupplier = _ => tooltip;
+ _lockStripe.Visible = true;
+ Options.Visible = false;
+ }
+
+ // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
+ public void UnlockRequirements()
+ {
+ _lockStripe.Visible = false;
+ Options.Visible = true;
+ }
+
+ private Button GenerateButton(string text, int value)
+ {
+ return new Button
+ {
+ Text = text,
+ MinWidth = 90,
+ HorizontalExpand = true,
+ };
+ }
+}
await using var pair = await PoolManager.GetServerClient(settings);
// Client is connected with a valid entity & mind
- Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
+ Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
// Delete **everything**
await pair.RunTicksSync(5);
Assert.That(pair.Server.EntMan.EntityCount, Is.EqualTo(0));
+
+ foreach (var ent in pair.Client.EntMan.GetEntities())
+ {
+ Console.WriteLine(pair.Client.EntMan.ToPrettyString(ent));
+ }
+
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
// Create a new map.
await pair.RunTicksSync(5);
// Client is not attached to anything
- Assert.That(pair.Client.Player?.ControlledEntity, Is.Null);
+ Assert.That(pair.Client.AttachedEntity, Is.Null);
Assert.That(pair.PlayerData?.Mind, Is.Null);
// Attempt to ghost
await pair.RunTicksSync(10);
// Client should be attached to a ghost placed on the new map.
- Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
+ Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
- var xform = pair.Client.Transform(pair.Client.Player!.ControlledEntity!.Value);
+ var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value);
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
await pair.CleanReturnAsync();
--- /dev/null
+using Content.Server.Station.Systems;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests.Preferences;
+
+[TestFixture]
+[Ignore("HumanoidAppearance crashes upon loading default profiles.")]
+public sealed class LoadoutTests
+{
+ /// <summary>
+ /// Checks that an empty loadout still spawns with default gear and not naked.
+ /// </summary>
+ [Test]
+ public async Task TestEmptyLoadout()
+ {
+ var pair = await PoolManager.GetServerClient(new PoolSettings()
+ {
+ Dirty = true,
+ });
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+
+ // Check that an empty role loadout spawns gear
+ var stationSystem = entManager.System<StationSpawningSystem>();
+ var testMap = await pair.CreateTestMap();
+
+ // That's right I can't even spawn a dummy profile without station spawning / humanoidappearance code crashing.
+ var profile = new HumanoidCharacterProfile();
+
+ profile.SetLoadout(new RoleLoadout("TestRoleLoadout"));
+
+ stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
+ {
+ // Sue me, there's so much involved in setting up jobs
+ Prototype = "CargoTechnician"
+ }, profile, station: null);
+
+ await pair.CleanReturnAsync();
+ }
+}
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Configuration;
Color.Beige,
new ()
),
- ClothingPreference.Jumpskirt,
- BackpackPreference.Backpack,
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
},
PreferenceUnavailableMode.StayInLobby,
new List<string> (),
- new List<string>()
+ new List<string>(),
+ new Dictionary<string, RoleLoadout>()
);
}
--- /dev/null
+// <auto-generated />
+using System;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20240301130641_ClothingRemoval")]
+ partial class ClothingRemoval
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<short>("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property<JsonDocument>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("antag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("assigned_user_id_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<IPAddress>("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("smallint")
+ .HasColumnName("denied");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("job_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("integer")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("play_time_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("interval")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_read_rules");
+
+ b.Property<IPAddress>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("last_seen_address");
+
+ b.Property<byte[]>("LastSeenHWId")
+ .HasColumnType("bytea")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", null, t =>
+ {
+ t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("integer")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("Age")
+ .HasColumnType("integer")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_name");
+
+ b.Property<JsonDocument>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("integer")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("integer")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("integer")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property<string>("LoadoutName")
+ .HasColumnType("text")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ServerId")
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("StartDate")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_ban_round_id");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("integer")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_hit_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("integer")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_role_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_role_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_role_ban_round_id");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("role_unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("trait_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("uploaded_resource_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("integer")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("integer")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerRoleBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerRoleBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_role_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminServerRoleBansCreated");
+
+ b.Navigation("AdminServerRoleBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ /// <inheritdoc />
+ public partial class ClothingRemoval : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "backpack",
+ table: "profile");
+
+ migrationBuilder.DropColumn(
+ name: "clothing",
+ table: "profile");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn<string>(
+ name: "backpack",
+ table: "profile",
+ type: "text",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn<string>(
+ name: "clothing",
+ table: "profile",
+ type: "text",
+ nullable: false,
+ defaultValue: "");
+ }
+ }
+}
--- /dev/null
+// <auto-generated />
+using System;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20240403072242_Loadouts")]
+ partial class Loadouts
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<short>("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property<JsonDocument>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<bool>("Dismissed")
+ .HasColumnType("boolean")
+ .HasColumnName("dismissed");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("antag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("assigned_user_id_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<IPAddress>("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("smallint")
+ .HasColumnName("denied");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("job_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("integer")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("play_time_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("interval")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_read_rules");
+
+ b.Property<IPAddress>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("last_seen_address");
+
+ b.Property<byte[]>("LastSeenHWId")
+ .HasColumnType("bytea")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", null, t =>
+ {
+ t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("integer")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("Age")
+ .HasColumnType("integer")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_name");
+
+ b.Property<JsonDocument>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("integer")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("integer")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("integer")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ServerId")
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("StartDate")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_ban_round_id");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("integer")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_hit_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("integer")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_role_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_role_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_role_ban_round_id");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("role_unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("trait_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("uploaded_resource_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("integer")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("integer")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerRoleBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerRoleBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_role_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminServerRoleBansCreated");
+
+ b.Navigation("AdminServerRoleBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+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: "profile_role_loadout",
+ columns: table => new
+ {
+ profile_role_loadout_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ profile_id = table.Column<int>(type: "integer", nullable: false),
+ role_name = table.Column<string>(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_profile_role_loadout", x => x.profile_role_loadout_id);
+ table.ForeignKey(
+ name: "FK_profile_role_loadout_profile_profile_id",
+ column: x => x.profile_id,
+ principalTable: "profile",
+ principalColumn: "profile_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "profile_loadout_group",
+ columns: table => new
+ {
+ profile_loadout_group_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ profile_role_loadout_id = table.Column<int>(type: "integer", nullable: false),
+ group_name = table.Column<string>(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_profile_loadout_group", x => x.profile_loadout_group_id);
+ table.ForeignKey(
+ name: "FK_profile_loadout_group_profile_role_loadout_profile_role_loa~",
+ column: x => x.profile_role_loadout_id,
+ principalTable: "profile_role_loadout",
+ principalColumn: "profile_role_loadout_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "profile_loadout",
+ columns: table => new
+ {
+ profile_loadout_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ profile_loadout_group_id = table.Column<int>(type: "integer", nullable: false),
+ loadout_name = table.Column<string>(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_profile_loadout", x => x.profile_loadout_id);
+ table.ForeignKey(
+ name: "FK_profile_loadout_profile_loadout_group_profile_loadout_group~",
+ column: x => x.profile_loadout_group_id,
+ principalTable: "profile_loadout_group",
+ principalColumn: "profile_loadout_group_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_profile_loadout_profile_loadout_group_id",
+ table: "profile_loadout",
+ column: "profile_loadout_group_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_profile_loadout_group_profile_role_loadout_id",
+ table: "profile_loadout_group",
+ column: "profile_role_loadout_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_profile_role_loadout_profile_id",
+ table: "profile_role_loadout",
+ column: "profile_id");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "profile_loadout");
+
+ migrationBuilder.DropTable(
+ name: "profile_loadout_group");
+
+ migrationBuilder.DropTable(
+ name: "profile_role_loadout");
+ }
+ }
+}
.HasColumnType("integer")
.HasColumnName("age");
- b.Property<string>("Backpack")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("backpack");
-
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("char_name");
- b.Property<string>("Clothing")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("clothing");
-
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("text")
b.ToTable("profile", (string)null);
});
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Property<int>("Id")
b.Navigation("Preference");
});
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
b.Navigation("Jobs");
+ b.Navigation("Loadouts");
+
b.Navigation("Traits");
});
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Navigation("AdminLogs");
--- /dev/null
+// <auto-generated />
+using System;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Sqlite
+{
+ [DbContext(typeof(SqliteServerDbContext))]
+ [Migration("20240301130602_ClothingRemoval")]
+ partial class ClothingRemoval
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_flag_id");
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("INTEGER")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<sbyte>("Impact")
+ .HasColumnType("INTEGER")
+ .HasColumnName("impact");
+
+ b.Property<string>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_messages_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("INTEGER")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_notes_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("INTEGER")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_flag_id");
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_watchlists_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("antag_id");
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("assigned_user_id_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_log_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("INTEGER")
+ .HasColumnName("denied");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("job_id");
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("play_time_id");
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("TEXT")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("player_id");
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_read_rules");
+
+ b.Property<string>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_address");
+
+ b.Property<byte[]>("LastSeenHWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<int>("Age")
+ .HasColumnType("INTEGER")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_name");
+
+ b.Property<byte[]>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("INTEGER")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("group_name");
+
+ b.Property<string>("LoadoutName")
+ .HasColumnType("TEXT")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("ServerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("StartDate")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_id");
+
+ b.Property<string>("Address")
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("INTEGER")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_ban_round_id");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_hit_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_role_ban_id");
+
+ b.Property<string>("Address")
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_id");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_role_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_role_ban_round_id");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("role_unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("trait_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("uploaded_resource_log_id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerRoleBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerRoleBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_role_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminServerRoleBansCreated");
+
+ b.Navigation("AdminServerRoleBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Sqlite
+{
+ /// <inheritdoc />
+ public partial class ClothingRemoval : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "backpack",
+ table: "profile");
+
+ migrationBuilder.DropColumn(
+ name: "clothing",
+ table: "profile");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn<string>(
+ name: "backpack",
+ table: "profile",
+ type: "TEXT",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn<string>(
+ name: "clothing",
+ table: "profile",
+ type: "TEXT",
+ nullable: false,
+ defaultValue: "");
+ }
+ }
+}
--- /dev/null
+// <auto-generated />
+using System;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Sqlite
+{
+ [DbContext(typeof(SqliteServerDbContext))]
+ [Migration("20240403072258_Loadouts")]
+ partial class Loadouts
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_flag_id");
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("INTEGER")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<sbyte>("Impact")
+ .HasColumnType("INTEGER")
+ .HasColumnName("impact");
+
+ b.Property<string>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_messages_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<bool>("Dismissed")
+ .HasColumnType("INTEGER")
+ .HasColumnName("dismissed");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("INTEGER")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_notes_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("INTEGER")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_flag_id");
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_watchlists_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("antag_id");
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("assigned_user_id_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_log_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("INTEGER")
+ .HasColumnName("denied");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("job_id");
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("play_time_id");
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("TEXT")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("player_id");
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_read_rules");
+
+ b.Property<string>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_address");
+
+ b.Property<byte[]>("LastSeenHWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<int>("Age")
+ .HasColumnType("INTEGER")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_name");
+
+ b.Property<byte[]>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("INTEGER")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_id");
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("ServerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("StartDate")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_id");
+
+ b.Property<string>("Address")
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("INTEGER")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_ban_round_id");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_hit_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_role_ban_id");
+
+ b.Property<string>("Address")
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_id");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_role_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_role_ban_round_id");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("role_unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("trait_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("uploaded_resource_log_id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerRoleBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerRoleBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_role_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminServerRoleBansCreated");
+
+ b.Navigation("AdminServerRoleBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+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: "profile_role_loadout",
+ columns: table => new
+ {
+ profile_role_loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ profile_id = table.Column<int>(type: "INTEGER", nullable: false),
+ role_name = table.Column<string>(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_profile_role_loadout", x => x.profile_role_loadout_id);
+ table.ForeignKey(
+ name: "FK_profile_role_loadout_profile_profile_id",
+ column: x => x.profile_id,
+ principalTable: "profile",
+ principalColumn: "profile_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "profile_loadout_group",
+ columns: table => new
+ {
+ profile_loadout_group_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ profile_role_loadout_id = table.Column<int>(type: "INTEGER", nullable: false),
+ group_name = table.Column<string>(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_profile_loadout_group", x => x.profile_loadout_group_id);
+ table.ForeignKey(
+ name: "FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id",
+ column: x => x.profile_role_loadout_id,
+ principalTable: "profile_role_loadout",
+ principalColumn: "profile_role_loadout_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "profile_loadout",
+ columns: table => new
+ {
+ profile_loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ profile_loadout_group_id = table.Column<int>(type: "INTEGER", nullable: false),
+ loadout_name = table.Column<string>(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_profile_loadout", x => x.profile_loadout_id);
+ table.ForeignKey(
+ name: "FK_profile_loadout_profile_loadout_group_profile_loadout_group_id",
+ column: x => x.profile_loadout_group_id,
+ principalTable: "profile_loadout_group",
+ principalColumn: "profile_loadout_group_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_profile_loadout_profile_loadout_group_id",
+ table: "profile_loadout",
+ column: "profile_loadout_group_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_profile_loadout_group_profile_role_loadout_id",
+ table: "profile_loadout_group",
+ column: "profile_role_loadout_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_profile_role_loadout_profile_id",
+ table: "profile_role_loadout",
+ column: "profile_id");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "profile_loadout");
+
+ migrationBuilder.DropTable(
+ name: "profile_loadout_group");
+
+ migrationBuilder.DropTable(
+ name: "profile_role_loadout");
+ }
+ }
+}
.HasColumnType("INTEGER")
.HasColumnName("age");
- b.Property<string>("Backpack")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasColumnName("backpack");
-
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("char_name");
- b.Property<string>("Clothing")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasColumnName("clothing");
-
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT")
b.ToTable("profile", (string)null);
});
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_id");
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Property<int>("Id")
b.Navigation("Preference");
});
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
b.Navigation("Jobs");
+ b.Navigation("Loadouts");
+
b.Navigation("Traits");
});
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Navigation("AdminLogs");
.IsUnique();
modelBuilder.Entity<Trait>()
- .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
- .IsUnique();
+ .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
+ .IsUnique();
+
+ modelBuilder.Entity<ProfileRoleLoadout>()
+ .HasOne(e => e.Profile)
+ .WithMany(e => e.Loadouts)
+ .HasForeignKey(e => e.ProfileId)
+ .IsRequired();
+
+ modelBuilder.Entity<ProfileLoadoutGroup>()
+ .HasOne(e => e.ProfileRoleLoadout)
+ .WithMany(e => e.Groups)
+ .HasForeignKey(e => e.ProfileRoleLoadoutId)
+ .IsRequired();
+
+ modelBuilder.Entity<ProfileLoadout>()
+ .HasOne(e => e.ProfileLoadoutGroup)
+ .WithMany(e => e.Loadouts)
+ .HasForeignKey(e => e.ProfileLoadoutGroupId)
+ .IsRequired();
modelBuilder.Entity<Job>()
.HasIndex(j => j.ProfileId);
public string FacialHairColor { get; set; } = null!;
public string EyeColor { get; set; } = null!;
public string SkinColor { get; set; } = null!;
- public string Clothing { get; set; } = null!;
- public string Backpack { get; set; } = null!;
public int SpawnPriority { get; set; } = 0;
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();
public List<Trait> Traits { get; } = new();
+ public List<ProfileRoleLoadout> Loadouts { get; } = new();
+
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
public int PreferenceId { get; set; }
public string TraitName { get; set; } = null!;
}
+ #region Loadouts
+
+ /// <summary>
+ /// Corresponds to a single role's loadout inside the DB.
+ /// </summary>
+ public class ProfileRoleLoadout
+ {
+ public int Id { get; set; }
+
+ public int ProfileId { get; set; }
+
+ public Profile Profile { get; set; } = null!;
+
+ /// <summary>
+ /// The corresponding role prototype on the profile.
+ /// </summary>
+ public string RoleName { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
+ /// </summary>
+ public List<ProfileLoadoutGroup> Groups { get; set; } = new();
+ }
+
+ /// <summary>
+ /// Corresponds to a loadout group prototype with the specified loadouts attached.
+ /// </summary>
+ public class ProfileLoadoutGroup
+ {
+ public int Id { get; set; }
+
+ public int ProfileRoleLoadoutId { get; set; }
+
+ /// <summary>
+ /// The corresponding RoleLoadout that owns this.
+ /// </summary>
+ public ProfileRoleLoadout ProfileRoleLoadout { get; set; } = null!;
+
+ /// <summary>
+ /// The corresponding group prototype.
+ /// </summary>
+ public string GroupName { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Selected loadout prototype. Null if none is set.
+ /// May get validated at runtime and updated to to the default.
+ /// </summary>
+ public List<ProfileLoadout> Loadouts { get; set; } = new();
+ }
+
+ /// <summary>
+ /// Corresponds to a selected loadout.
+ /// </summary>
+ public class ProfileLoadout
+ {
+ public int Id { get; set; }
+
+ public int ProfileLoadoutGroupId { get; set; }
+
+ public ProfileLoadoutGroup ProfileLoadoutGroup { get; set; } = null!;
+
+ /// <summary>
+ /// Corresponding loadout prototype.
+ /// </summary>
+ public string LoadoutName { get; set; } = string.Empty;
+
+ /*
+ * Insert extra data here like custom descriptions or colors or whatever.
+ */
+ }
+
+ #endregion
+
public enum DbPreferenceUnavailableMode
{
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
foreach (var slot in slots)
{
invSystem.TryUnequip(target, slot.Name, true, true, false, inventoryComponent);
- var gearStr = startingGear.GetGear(slot.Name, profile);
+ var gearStr = startingGear.GetGear(slot.Name);
if (gearStr == string.Empty)
{
continue;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Enums;
using Robust.Shared.Network;
.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)
+ .ThenInclude(l => l.Groups)
+ .ThenInclude(group => group.Loadouts)
.AsSingleQuery()
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
.Include(p => p.Jobs)
.Include(p => p.Antags)
.Include(p => p.Traits)
+ .Include(p => p.Loadouts)
+ .ThenInclude(l => l.Groups)
+ .ThenInclude(group => group.Loadouts)
.AsSplitQuery()
.SingleOrDefault(h => h.Slot == slot);
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
sex = sexVal;
- var clothing = ClothingPreference.Jumpsuit;
- if (Enum.TryParse<ClothingPreference>(profile.Clothing, true, out var clothingVal))
- clothing = clothingVal;
-
- var backpack = BackpackPreference.Backpack;
- if (Enum.TryParse<BackpackPreference>(profile.Backpack, true, out var backpackVal))
- backpack = backpackVal;
-
var spawnPriority = (SpawnPriorityPreference) profile.SpawnPriority;
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
}
}
+ var loadouts = new Dictionary<string, RoleLoadout>();
+
+ foreach (var role in profile.Loadouts)
+ {
+ var loadout = new RoleLoadout(role.RoleName);
+
+ foreach (var group in role.Groups)
+ {
+ var groupLoadouts = loadout.SelectedLoadouts.GetOrNew(group.GroupName);
+ foreach (var profLoadout in group.Loadouts)
+ {
+ groupLoadouts.Add(new Loadout()
+ {
+ Prototype = profLoadout.LoadoutName,
+ });
+ }
+ }
+
+ loadouts[role.RoleName] = loadout;
+ }
+
return new HumanoidCharacterProfile(
profile.CharacterName,
profile.FlavorText,
Color.FromHex(profile.SkinColor),
markings
),
- clothing,
- backpack,
spawnPriority,
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
antags.ToList(),
- traits.ToList()
+ traits.ToList(),
+ loadouts
);
}
profile.FacialHairColor = appearance.FacialHairColor.ToHex();
profile.EyeColor = appearance.EyeColor.ToHex();
profile.SkinColor = appearance.SkinColor.ToHex();
- profile.Clothing = humanoid.Clothing.ToString();
- profile.Backpack = humanoid.Backpack.ToString();
profile.SpawnPriority = (int) humanoid.SpawnPriority;
profile.Markings = markings;
profile.Slot = slot;
.Select(t => new Trait {TraitName = t})
);
+ profile.Loadouts.Clear();
+
+ foreach (var (role, loadouts) in humanoid.Loadouts)
+ {
+ var dz = new ProfileRoleLoadout()
+ {
+ RoleName = role,
+ };
+
+ foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts)
+ {
+ var profileGroup = new ProfileLoadoutGroup()
+ {
+ GroupName = group,
+ };
+
+ foreach (var loadout in groupLoadouts)
+ {
+ profileGroup.Loadouts.Add(new ProfileLoadout()
+ {
+ LoadoutName = loadout.Prototype,
+ });
+ }
+
+ dz.Groups.Add(profileGroup);
+ }
+
+ profile.Loadouts.Add(dz);
+ }
+
return profile;
}
#endregion
_humanoid.LoadProfile(mob, profile);
var gear = _prototypeManager.Index(spawnDetails.GearProto);
- _stationSpawning.EquipStartingGear(mob, gear, profile);
+ _stationSpawning.EquipStartingGear(mob, gear);
_npcFaction.RemoveFaction(mob, "NanoTrasen", false);
_npcFaction.AddFaction(mob, "Syndicate");
_mindSystem.TransferTo(newMind, mob);
var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
- _stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile);
+ _stationSpawningSystem.EquipStartingGear(mob, pirateGear);
_npcFaction.RemoveFaction(mob, EnemyFactionId, false);
_npcFaction.AddFaction(mob, PirateFactionId);
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Kitchen;
+using Content.Shared.Players.PlayTimeTracking;
namespace Content.Server.IoC
{
IoCManager.Register<PoissonDiskSampler>();
IoCManager.Register<DiscordWebhook>();
IoCManager.Register<ServerDbEntryManager>();
+ IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
IoCManager.Register<ServerApi>();
}
}
/// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick).
/// </para>
/// </remarks>
-public sealed class PlayTimeTrackingManager
+public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager
{
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IServerNetManager _net = default!;
}
}
+ public IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session)
+ {
+ return GetTrackerTimes(session);
+ }
+
private void SendPlayTimes(ICommonSession pSession)
{
var roles = GetTrackerTimes(pSession);
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Roles;
+using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IServerDbManager _db = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _protos = default!;
// Cache player prefs on the server so we don't need as much async hell related to them.
}
var curPrefs = prefsData.Prefs!;
+ var session = _playerManager.GetSessionById(userId);
+ var collection = IoCManager.Instance!;
- profile.EnsureValid(_cfg, _protos);
+ profile.EnsureValid(session, collection);
var profiles = new Dictionary<int, ICharacterProfile>(curPrefs.Characters)
{
return await _db.InitPrefsAsync(userId, HumanoidCharacterProfile.Random());
}
- return SanitizePreferences(prefs);
+ var session = _playerManager.GetSessionById(userId);
+ var collection = IoCManager.Instance!;
+
+ return SanitizePreferences(session, prefs, collection);
}
- private PlayerPreferences SanitizePreferences(PlayerPreferences prefs)
+ private PlayerPreferences SanitizePreferences(ICommonSession session, PlayerPreferences prefs, IDependencyCollection collection)
{
// Clean up preferences in case of changes to the game,
// such as removed jobs still being selected.
return new PlayerPreferences(prefs.Characters.Select(p =>
{
- return new KeyValuePair<int, ICharacterProfile>(p.Key, p.Value.Validated(_cfg, _protos));
+ return new KeyValuePair<int, ICharacterProfile>(p.Key, p.Value.Validated(session, collection));
}), prefs.SelectedCharacterIndex, prefs.AdminOOCColor);
}
// TODO: Refactor gameticker spawning code so we don't have to do this!
var points2 = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
- if (points2.MoveNext(out var uid, out var spawnPoint, out var xform))
+ if (points2.MoveNext(out var spawnPoint, out var xform))
{
possiblePositions.Add(xform.Coordinates);
}
+using System.Linq;
using Content.Server.Access.Systems;
using Content.Server.DetailExaminable;
using Content.Server.Humanoid;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
+using Content.Shared.Clothing;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.PDA;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Roles;
if (station != null && profile != null)
{
- /// Try to call the character's preferred spawner first.
+ // Try to call the character's preferred spawner first.
if (_spawnerCallbacks.TryGetValue(profile.SpawnPriority, out var preferredSpawner))
{
preferredSpawner(ev);
}
else
{
- /// Call all of them in the typical order.
+ // Call all of them in the typical order.
foreach (var typicalSpawner in _spawnerCallbacks.Values)
+ {
typicalSpawner(ev);
+ }
}
}
EntityUid? station,
EntityUid? entity = null)
{
- _prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype);
+ _prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype);
// If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff.
if (prototype?.JobEntity != null)
if (prototype?.StartingGear != null)
{
var startingGear = _prototypeManager.Index<StartingGearPrototype>(prototype.StartingGear);
- EquipStartingGear(entity.Value, startingGear, profile);
+ EquipStartingGear(entity.Value, startingGear);
if (profile != null)
EquipIdCard(entity.Value, profile.Name, prototype, station);
}
+ // Run loadouts after so stuff like storage loadouts can get
+ var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID);
+
+ if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? loadoutProto))
+ {
+ RoleLoadout? loadout = null;
+ profile?.Loadouts.TryGetValue(jobLoadout, out loadout);
+
+ // Set to default if not present
+ if (loadout == null)
+ {
+ loadout = new RoleLoadout(jobLoadout);
+ loadout.SetDefault(_prototypeManager);
+ }
+
+ // Order loadout selections by the order they appear on the prototype.
+ foreach (var group in loadout.SelectedLoadouts.OrderBy(x => loadoutProto.Groups.FindIndex(e => e == x.Key)))
+ {
+ foreach (var items in group.Value)
+ {
+ // Handle any extra data here.
+ var startingGear = _prototypeManager.Index<StartingGearPrototype>(items.Prototype);
+ EquipStartingGear(entity.Value, startingGear);
+ }
+ }
+ }
+
if (profile != null)
{
_humanoidSystem.LoadProfile(entity.Value, profile);
+using System.Linq;
using Content.Shared.Clothing.Components;
+using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Station;
using Robust.Shared.Prototypes;
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit);
}
+ public static string GetJobPrototype(string? loadout)
+ {
+ if (string.IsNullOrEmpty(loadout))
+ return string.Empty;
+
+ return "Job" + loadout;
+ }
+
+ /// <summary>
+ /// Tries to get the first entity prototype for operations such as sprite drawing.
+ /// </summary>
+ public EntProtoId? GetFirstOrNull(LoadoutPrototype loadout)
+ {
+ if (!_protoMan.TryIndex(loadout.Equipment, out var gear))
+ return null;
+
+ var count = gear.Equipment.Count + gear.Inhand.Count + gear.Storage.Values.Sum(x => x.Count);
+
+ if (count == 1)
+ {
+ if (gear.Equipment.Count == 1 && _protoMan.TryIndex<EntityPrototype>(gear.Equipment.Values.First(), out var proto))
+ {
+ return proto.ID;
+ }
+
+ if (gear.Inhand.Count == 1 && _protoMan.TryIndex<EntityPrototype>(gear.Inhand[0], out proto))
+ {
+ return proto.ID;
+ }
+
+ // Storage moment
+ foreach (var ents in gear.Storage.Values)
+ {
+ foreach (var ent in ents)
+ {
+ return ent;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Tries to get the name of a loadout.
+ /// </summary>
+ public string GetName(LoadoutPrototype loadout)
+ {
+ if (!_protoMan.TryIndex(loadout.Equipment, out var gear))
+ return Loc.GetString("loadout-unknown");
+
+ var count = gear.Equipment.Count + gear.Storage.Values.Sum(o => o.Count) + gear.Inhand.Count;
+
+ if (count == 1)
+ {
+ if (gear.Equipment.Count == 1 && _protoMan.TryIndex<EntityPrototype>(gear.Equipment.Values.First(), out var proto))
+ {
+ return proto.Name;
+ }
+
+ if (gear.Inhand.Count == 1 && _protoMan.TryIndex<EntityPrototype>(gear.Inhand[0], out proto))
+ {
+ return proto.Name;
+ }
+
+ foreach (var values in gear.Storage.Values)
+ {
+ if (values.Count != 1)
+ continue;
+
+ if (_protoMan.TryIndex<EntityPrototype>(values[0], out proto))
+ {
+ return proto.Name;
+ }
+
+ break;
+ }
+ }
+
+ return Loc.GetString($"loadout-{loadout.ID}");
+ }
+
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);
+ _station.EquipStartingGear(uid, proto);
}
}
/// <summary>
/// Humanoid species variant used by this entity.
/// </summary>
- [DataField(required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string Prototype { get; private set; } = default!;
+ [DataField(required: true)]
+ public EntProtoId Prototype { get; private set; } = default!;
/// <summary>
/// Prototype used by the species for the dress-up doll in various menus.
/// </summary>
- [DataField(required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string DollPrototype { get; private set; } = default!;
+ [DataField(required: true)]
+ public EntProtoId DollPrototype { get; private set; } = default!;
/// <summary>
/// Method of skin coloration used by the species.
/// </summary>
[DataField]
public int MaxAge = 120;
-
- /// <summary>
- /// The Style used for the guidebook info link in the character profile editor
- /// </summary>
- [DataField]
- public string GuideBookIcon = "SpeciesInfoDefault";
}
public enum SpeciesNaming : byte
--- /dev/null
+using Robust.Shared.Player;
+
+namespace Content.Shared.Players.PlayTimeTracking;
+
+public interface ISharedPlaytimeManager
+{
+ /// <summary>
+ /// Gets the playtimes for the session or an empty dictionary if none found.
+ /// </summary>
+ IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session);
+}
+
+++ /dev/null
-namespace Content.Shared.Preferences
-{
- /// <summary>
- /// The backpack preference for a profile. Stored in database!
- /// </summary>
- public enum BackpackPreference
- {
- Backpack,
- Satchel,
- Duffelbag
- }
-}
+++ /dev/null
-namespace Content.Shared.Preferences
-{
- /// <summary>
- /// The clothing preference for a profile. Stored in database!
- /// </summary>
- public enum ClothingPreference
- {
- Jumpsuit,
- Jumpskirt
- }
-}
using System.Linq;
-using System.Globalization;
using System.Text.RegularExpressions;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Random.Helpers;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Content.Shared.Traits;
+using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
private readonly List<string> _antagPreferences;
private readonly List<string> _traitPreferences;
+ public IReadOnlyDictionary<string, RoleLoadout> Loadouts => _loadouts;
+
+ private Dictionary<string, RoleLoadout> _loadouts;
+
+ // What in the lord is happening here.
private HumanoidCharacterProfile(
string name,
string flavortext,
Sex sex,
Gender gender,
HumanoidCharacterAppearance appearance,
- ClothingPreference clothing,
- BackpackPreference backpack,
SpawnPriorityPreference spawnPriority,
Dictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
List<string> antagPreferences,
- List<string> traitPreferences)
+ List<string> traitPreferences,
+ Dictionary<string, RoleLoadout> loadouts)
{
Name = name;
FlavorText = flavortext;
Sex = sex;
Gender = gender;
Appearance = appearance;
- Clothing = clothing;
- Backpack = backpack;
SpawnPriority = spawnPriority;
_jobPriorities = jobPriorities;
PreferenceUnavailable = preferenceUnavailable;
_antagPreferences = antagPreferences;
_traitPreferences = traitPreferences;
+ _loadouts = loadouts;
}
/// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
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,
+ Dictionary<string, RoleLoadout> loadouts)
+ : this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.SpawnPriority,
+ jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences, loadouts)
{
}
/// <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 Dictionary<string, RoleLoadout>(other.Loadouts))
{
}
Sex sex,
Gender gender,
HumanoidCharacterAppearance appearance,
- ClothingPreference clothing,
- BackpackPreference backpack,
SpawnPriorityPreference spawnPriority,
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,
+ Dictionary<string, RoleLoadout> loadouts)
+ : this(name, flavortext, species, age, sex, gender, appearance, spawnPriority, new Dictionary<string, JobPriority>(jobPriorities),
+ preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences), new Dictionary<string, RoleLoadout>(loadouts))
{
}
Sex.Male,
Gender.Male,
new HumanoidCharacterAppearance(),
- ClothingPreference.Jumpsuit,
- BackpackPreference.Backpack,
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
- new List<string>())
+ new List<string>(),
+ new Dictionary<string, RoleLoadout>())
{
}
Sex.Male,
Gender.Male,
HumanoidCharacterAppearance.DefaultWithSpecies(species),
- ClothingPreference.Jumpsuit,
- BackpackPreference.Backpack,
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
- new List<string>());
+ new List<string>(),
+ new Dictionary<string, RoleLoadout>());
}
// TODO: This should eventually not be a visual change only.
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), 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 Dictionary<string, RoleLoadout>());
}
public string Name { get; private set; }
[DataField("appearance")]
public HumanoidCharacterAppearance Appearance { get; private set; }
- public ClothingPreference Clothing { get; private set; }
- public BackpackPreference Backpack { get; private set; }
public SpawnPriorityPreference SpawnPriority { get; private set; }
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
return new(this) { Appearance = appearance };
}
- public HumanoidCharacterProfile WithClothingPreference(ClothingPreference clothing)
- {
- return new(this) { Clothing = clothing };
- }
- public HumanoidCharacterProfile WithBackpackPreference(BackpackPreference backpack)
- {
- return new(this) { Backpack = backpack };
- }
public HumanoidCharacterProfile WithSpawnPriorityPreference(SpawnPriorityPreference spawnPriority)
{
return new(this) { SpawnPriority = spawnPriority };
}
+
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, _loadouts);
}
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
{
dictionary[jobId] = priority;
}
- return new(this, dictionary, _antagPreferences, _traitPreferences);
+ return new(this, dictionary, _antagPreferences, _traitPreferences, _loadouts);
}
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
{
- return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences);
+ return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences, _loadouts);
}
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
list.Remove(antagId);
}
}
- return new(this, _jobPriorities, list, _traitPreferences);
+ return new(this, _jobPriorities, list, _traitPreferences, _loadouts);
}
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
list.Remove(traitId);
}
}
- return new(this, _jobPriorities, _antagPreferences, list);
+ return new(this, _jobPriorities, _antagPreferences, list, _loadouts);
}
public string Summary =>
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 (!Loadouts.SequenceEqual(other.Loadouts)) return false;
return Appearance.MemberwiseEquals(other.Appearance);
}
- public void EnsureValid(IConfigurationManager configManager, IPrototypeManager prototypeManager)
+ public void EnsureValid(ICommonSession session, IDependencyCollection collection)
{
+ var configManager = collection.Resolve<IConfigurationManager>();
+ var prototypeManager = collection.Resolve<IPrototypeManager>();
+
if (!prototypeManager.TryIndex<SpeciesPrototype>(Species, out var speciesPrototype) || speciesPrototype.RoundStart == false)
{
Species = SharedHumanoidAppearanceSystem.DefaultSpecies;
_ => PreferenceUnavailableMode.StayInLobby // Invalid enum values.
};
- var clothing = Clothing switch
- {
- ClothingPreference.Jumpsuit => ClothingPreference.Jumpsuit,
- ClothingPreference.Jumpskirt => ClothingPreference.Jumpskirt,
- _ => ClothingPreference.Jumpsuit // Invalid enum values.
- };
-
- var backpack = Backpack switch
- {
- BackpackPreference.Backpack => BackpackPreference.Backpack,
- BackpackPreference.Satchel => BackpackPreference.Satchel,
- BackpackPreference.Duffelbag => BackpackPreference.Duffelbag,
- _ => BackpackPreference.Backpack // Invalid enum values.
- };
-
var spawnPriority = SpawnPriority switch
{
SpawnPriorityPreference.None => SpawnPriorityPreference.None,
Sex = sex;
Gender = gender;
Appearance = appearance;
- Clothing = clothing;
- Backpack = backpack;
SpawnPriority = spawnPriority;
_jobPriorities.Clear();
_traitPreferences.Clear();
_traitPreferences.AddRange(traits);
+
+ // Checks prototypes exist for all loadouts and dump / set to default if not.
+ var toRemove = new ValueList<string>();
+
+ foreach (var (roleName, loadouts) in _loadouts)
+ {
+ if (!prototypeManager.HasIndex<RoleLoadoutPrototype>(roleName))
+ {
+ toRemove.Add(roleName);
+ continue;
+ }
+
+ loadouts.EnsureValid(session, collection);
+ }
+
+ foreach (var value in toRemove)
+ {
+ _loadouts.Remove(value);
+ }
}
- public ICharacterProfile Validated(IConfigurationManager configManager, IPrototypeManager prototypeManager)
+ public ICharacterProfile Validated(ICommonSession session, IDependencyCollection collection)
{
var profile = new HumanoidCharacterProfile(this);
- profile.EnsureValid(configManager, prototypeManager);
+ profile.EnsureValid(session, collection);
return profile;
}
Age,
Sex,
Gender,
- Appearance,
- Clothing,
- Backpack
+ Appearance
),
SpawnPriority,
PreferenceUnavailable,
_jobPriorities,
_antagPreferences,
- _traitPreferences
+ _traitPreferences,
+ _loadouts
);
}
+
+ public void SetLoadout(RoleLoadout loadout)
+ {
+ _loadouts[loadout.Role.Id] = loadout;
+ }
+
+ public RoleLoadout GetLoadoutOrDefault(string id, IEntityManager entManager, IPrototypeManager protoManager)
+ {
+ if (!_loadouts.TryGetValue(id, out var loadout))
+ {
+ loadout = new RoleLoadout(id);
+ loadout.SetDefault(protoManager, force: true);
+ }
+
+ loadout.SetDefault(protoManager);
+ return loadout;
+ }
}
}
using Content.Shared.Humanoid;
using Robust.Shared.Configuration;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Shared.Preferences
/// <summary>
/// Makes this profile valid so there's no bad data like negative ages.
/// </summary>
- void EnsureValid(IConfigurationManager configManager, IPrototypeManager prototypeManager);
+ void EnsureValid(ICommonSession session, IDependencyCollection collection);
/// <summary>
/// Gets a copy of this profile that has <see cref="EnsureValid"/> applied, i.e. no invalid data.
/// </summary>
- ICharacterProfile Validated(IConfigurationManager configManager, IPrototypeManager prototypeManager);
+ ICharacterProfile Validated(ICommonSession session, IDependencyCollection collection);
}
}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Preferences.Loadouts.Effects;
+
+/// <summary>
+/// Uses a <see cref="LoadoutEffectGroupPrototype"/> prototype as a singular effect that can be re-used.
+/// </summary>
+public sealed partial class GroupLoadoutEffect : LoadoutEffect
+{
+ [DataField(required: true)]
+ public ProtoId<LoadoutEffectGroupPrototype> Proto;
+
+ public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ var effectsProto = collection.Resolve<IPrototypeManager>().Index(Proto);
+
+ foreach (var effect in effectsProto.Effects)
+ {
+ if (!effect.Validate(loadout, session, collection, out reason))
+ return false;
+ }
+
+ reason = null;
+ return true;
+ }
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Roles;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Preferences.Loadouts.Effects;
+
+/// <summary>
+/// Checks for a job requirement to be met such as playtime.
+/// </summary>
+public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect
+{
+ [DataField(required: true)]
+ public JobRequirement Requirement = default!;
+
+ public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ var manager = collection.Resolve<ISharedPlaytimeManager>();
+ var playtimes = manager.GetPlayTimes(session);
+ return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason,
+ collection.Resolve<IEntityManager>(),
+ collection.Resolve<IPrototypeManager>());
+ }
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Robust.Shared.Player;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Preferences.Loadouts.Effects;
+
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class LoadoutEffect
+{
+ /// <summary>
+ /// Tries to validate the effect.
+ /// </summary>
+ public abstract bool Validate(
+ RoleLoadout loadout,
+ ICommonSession session,
+ IDependencyCollection collection,
+ [NotNullWhen(false)] out FormattedMessage? reason);
+
+ public virtual void Apply(RoleLoadout loadout) {}
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Preferences.Loadouts.Effects;
+
+/// <summary>
+/// Stores a group of loadout effects in a prototype for re-use.
+/// </summary>
+[Prototype]
+public sealed class LoadoutEffectGroupPrototype : IPrototype
+{
+ [IdDataField]
+ public string ID { get; } = string.Empty;
+
+ [DataField(required: true)]
+ public List<LoadoutEffect> Effects = new();
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Preferences.Loadouts.Effects;
+
+public sealed partial class PointsCostLoadoutEffect : LoadoutEffect
+{
+ [DataField(required: true)]
+ public int Cost = 1;
+
+ public override bool Validate(
+ RoleLoadout loadout,
+ ICommonSession session,
+ IDependencyCollection collection,
+ [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ reason = null;
+ var protoManager = collection.Resolve<IPrototypeManager>();
+
+ if (!protoManager.TryIndex(loadout.Role, out var roleProto) || roleProto.Points == null)
+ {
+ return true;
+ }
+
+ if (loadout.Points <= Cost)
+ {
+ reason = FormattedMessage.FromUnformatted("loadout-group-points-insufficient");
+ return false;
+ }
+
+ return true;
+ }
+
+ public override void Apply(RoleLoadout loadout)
+ {
+ loadout.Points -= Cost;
+ }
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Preferences.Loadouts;
+
+/// <summary>
+/// Specifies the selected prototype and custom data for a loadout.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class Loadout
+{
+ public ProtoId<LoadoutPrototype> Prototype;
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Preferences.Loadouts;
+
+/// <summary>
+/// Corresponds to a set of loadouts for a particular slot.
+/// </summary>
+[Prototype("loadoutGroup")]
+public sealed class LoadoutGroupPrototype : IPrototype
+{
+ [IdDataField]
+ public string ID { get; } = string.Empty;
+
+ /// <summary>
+ /// User-friendly name for the group.
+ /// </summary>
+ [DataField(required: true)]
+ public LocId Name;
+
+ /// <summary>
+ /// Minimum number of loadouts that need to be specified for this category.
+ /// </summary>
+ [DataField]
+ public int MinLimit = 1;
+
+ /// <summary>
+ /// Maximum limit for the category.
+ /// </summary>
+ [DataField]
+ public int MaxLimit = 1;
+
+ [DataField(required: true)]
+ public List<ProtoId<LoadoutPrototype>> Loadouts = new();
+}
--- /dev/null
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Preferences.Loadouts;
+
+/// <summary>
+/// Individual loadout item to be applied.
+/// </summary>
+[Prototype]
+public sealed class LoadoutPrototype : IPrototype
+{
+ [IdDataField]
+ public string ID { get; } = string.Empty;
+
+ [DataField(required: true)]
+ public ProtoId<StartingGearPrototype> Equipment;
+
+ /// <summary>
+ /// Effects to be applied when the loadout is applied.
+ /// These can also return true or false for validation purposes.
+ /// </summary>
+ [DataField]
+ public List<LoadoutEffect> Effects = new();
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Random;
+using Robust.Shared.Collections;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Preferences.Loadouts;
+
+/// <summary>
+/// Contains all of the selected data for a role's loadout.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class RoleLoadout
+{
+ public readonly ProtoId<RoleLoadoutPrototype> Role;
+
+ public Dictionary<ProtoId<LoadoutGroupPrototype>, List<Loadout>> SelectedLoadouts = new();
+
+ /*
+ * Loadout-specific data used for validation.
+ */
+
+ public int? Points;
+
+ public RoleLoadout(ProtoId<RoleLoadoutPrototype> role)
+ {
+ Role = role;
+ }
+
+ /// <summary>
+ /// Ensures all prototypes exist and effects can be applied.
+ /// </summary>
+ public void EnsureValid(ICommonSession session, IDependencyCollection collection)
+ {
+ var groupRemove = new ValueList<string>();
+ var protoManager = collection.Resolve<IPrototypeManager>();
+
+ if (!protoManager.TryIndex(Role, out var roleProto))
+ {
+ SelectedLoadouts.Clear();
+ return;
+ }
+
+ // Reset points to recalculate.
+ Points = roleProto.Points;
+
+ foreach (var (group, groupLoadouts) in SelectedLoadouts)
+ {
+ // Dump if Group doesn't exist
+ if (!protoManager.TryIndex(group, out var groupProto))
+ {
+ groupRemove.Add(group);
+ continue;
+ }
+
+ var loadouts = groupLoadouts[..Math.Min(groupLoadouts.Count, groupProto.MaxLimit)];
+
+ // Validate first
+ for (var i = loadouts.Count - 1; i >= 0; i--)
+ {
+ var loadout = loadouts[i];
+
+ if (!protoManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ {
+ loadouts.RemoveAt(i);
+ continue;
+ }
+
+ // Validate the loadout can be applied (e.g. points).
+ if (!IsValid(session, loadout.Prototype, collection, out _))
+ {
+ loadouts.RemoveAt(i);
+ continue;
+ }
+
+ Apply(loadoutProto);
+ }
+
+ // Apply defaults if required
+ // Technically it's possible for someone to game themselves into loadouts they shouldn't have
+ // If you put invalid ones first but that's your fault for not using sensible defaults
+ if (loadouts.Count < groupProto.MinLimit)
+ {
+ for (var i = 0; i < Math.Min(groupProto.MinLimit, groupProto.Loadouts.Count); i++)
+ {
+ if (!protoManager.TryIndex(groupProto.Loadouts[i], out var loadoutProto))
+ continue;
+
+ var defaultLoadout = new Loadout()
+ {
+ Prototype = loadoutProto.ID,
+ };
+
+ if (loadouts.Contains(defaultLoadout))
+ continue;
+
+ // Still need to apply the effects even if validation is ignored.
+ loadouts.Add(defaultLoadout);
+ Apply(loadoutProto);
+ }
+ }
+
+ SelectedLoadouts[group] = loadouts;
+ }
+
+ foreach (var value in groupRemove)
+ {
+ SelectedLoadouts.Remove(value);
+ }
+ }
+
+ private void Apply(LoadoutPrototype loadoutProto)
+ {
+ foreach (var effect in loadoutProto.Effects)
+ {
+ effect.Apply(this);
+ }
+ }
+
+ /// <summary>
+ /// Resets the selected loadouts to default if no data is present.
+ /// </summary>
+ public void SetDefault(IPrototypeManager protoManager, bool force = false)
+ {
+ if (force)
+ SelectedLoadouts.Clear();
+
+ var roleProto = protoManager.Index(Role);
+
+ for (var i = roleProto.Groups.Count - 1; i >= 0; i--)
+ {
+ var group = roleProto.Groups[i];
+
+ if (!protoManager.TryIndex(group, out var groupProto))
+ continue;
+
+ if (SelectedLoadouts.ContainsKey(group))
+ continue;
+
+ SelectedLoadouts[group] = new List<Loadout>();
+
+ if (groupProto.MinLimit > 0)
+ {
+ // Apply any loadouts we can.
+ for (var j = 0; j < Math.Min(groupProto.MinLimit, groupProto.Loadouts.Count); j++)
+ {
+ AddLoadout(group, groupProto.Loadouts[j], protoManager);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns whether a loadout is valid or not.
+ /// </summary>
+ public bool IsValid(ICommonSession session, ProtoId<LoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ reason = null;
+
+ var protoManager = collection.Resolve<IPrototypeManager>();
+
+ if (!protoManager.TryIndex(loadout, out var loadoutProto))
+ {
+ // Uhh
+ reason = FormattedMessage.FromMarkup("");
+ return false;
+ }
+
+ if (!protoManager.TryIndex(Role, out var roleProto))
+ {
+ reason = FormattedMessage.FromUnformatted("loadouts-prototype-missing");
+ return false;
+ }
+
+ var valid = true;
+
+ foreach (var effect in loadoutProto.Effects)
+ {
+ valid = valid && effect.Validate(this, session, collection, out reason);
+ }
+
+ return valid;
+ }
+
+ /// <summary>
+ /// Applies the specified loadout to this group.
+ /// </summary>
+ public bool AddLoadout(ProtoId<LoadoutGroupPrototype> selectedGroup, ProtoId<LoadoutPrototype> selectedLoadout, IPrototypeManager protoManager)
+ {
+ var groupLoadouts = SelectedLoadouts[selectedGroup];
+
+ // Need to unselect existing ones if we're at or above limit
+ var limit = Math.Max(0, groupLoadouts.Count + 1 - protoManager.Index(selectedGroup).MaxLimit);
+
+ for (var i = 0; i < groupLoadouts.Count; i++)
+ {
+ var loadout = groupLoadouts[i];
+
+ if (loadout.Prototype != selectedLoadout)
+ {
+ // Remove any other loadouts that might push it above the limit.
+ if (limit > 0)
+ {
+ limit--;
+ groupLoadouts.RemoveAt(i);
+ i--;
+ }
+
+ continue;
+ }
+
+ DebugTools.Assert(false);
+ return false;
+ }
+
+ groupLoadouts.Add(new Loadout()
+ {
+ Prototype = selectedLoadout,
+ });
+
+ return true;
+ }
+
+ /// <summary>
+ /// Removed the specified loadout from this group.
+ /// </summary>
+ public bool RemoveLoadout(ProtoId<LoadoutGroupPrototype> selectedGroup, ProtoId<LoadoutPrototype> selectedLoadout, IPrototypeManager protoManager)
+ {
+ // Although this may bring us below minimum we'll let EnsureValid handle it.
+
+ var groupLoadouts = SelectedLoadouts[selectedGroup];
+
+ for (var i = 0; i < groupLoadouts.Count; i++)
+ {
+ var loadout = groupLoadouts[i];
+
+ if (loadout.Prototype != selectedLoadout)
+ continue;
+
+ groupLoadouts.RemoveAt(i);
+ return true;
+ }
+
+ return false;
+ }
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Preferences.Loadouts;
+
+/// <summary>
+/// Corresponds to a Job / Antag prototype and specifies loadouts
+/// </summary>
+[Prototype]
+public sealed class RoleLoadoutPrototype : IPrototype
+{
+ /*
+ * Separate to JobPrototype / AntagPrototype as they are turning into messy god classes.
+ */
+
+ [IdDataField]
+ public string ID { get; } = string.Empty;
+
+ /// <summary>
+ /// Groups that comprise this role loadout.
+ /// </summary>
+ [DataField(required: true)]
+ public List<ProtoId<LoadoutGroupPrototype>> Groups = new();
+
+ /// <summary>
+ /// How many points are allotted for this role loadout prototype.
+ /// </summary>
+ [DataField]
+ public int? Points;
+}
/// </summary>
public static bool TryRequirementMet(
JobRequirement requirement,
- Dictionary<string, TimeSpan> playTimes,
+ IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason,
IEntityManager entManager,
IPrototypeManager prototypes)
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
- "role-timer-overall-insufficient",
+ "role-timer-overall-insufficient",
("time", Math.Ceiling(overallDiff))));
return false;
}
[DataField]
public Dictionary<string, EntProtoId> Equipment = new();
- /// <summary>
- /// if empty, there is no skirt override - instead the uniform provided in equipment is added.
- /// </summary>
- [DataField]
- public EntProtoId? InnerClothingSkirt;
-
- [DataField]
- public EntProtoId? Satchel;
-
[DataField]
- public EntProtoId? Duffelbag;
+ public List<EntProtoId> Inhand = new(0);
+ /// <summary>
+ /// Inserts entities into the specified slot's storage (if it does have storage).
+ /// </summary>
[DataField]
- public List<EntProtoId> Inhand = new(0);
+ public Dictionary<string, List<EntProtoId>> Storage = new();
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = string.Empty;
- public string GetGear(string slot, HumanoidCharacterProfile? profile)
+ public string GetGear(string slot)
{
- if (profile != null)
- {
- if (slot == "jumpsuit" && profile.Clothing == ClothingPreference.Jumpskirt && !string.IsNullOrEmpty(InnerClothingSkirt))
- return InnerClothingSkirt;
- if (slot == "back" && profile.Backpack == BackpackPreference.Satchel && !string.IsNullOrEmpty(Satchel))
- return Satchel;
- if (slot == "back" && profile.Backpack == BackpackPreference.Duffelbag && !string.IsNullOrEmpty(Duffelbag))
- return Duffelbag;
- }
-
return Equipment.TryGetValue(slot, out var equipment) ? equipment : string.Empty;
}
}
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
+using Content.Shared.Storage;
+using Content.Shared.Storage.EntitySystems;
+using Robust.Shared.Collections;
namespace Content.Shared.Station;
{
[Dependency] protected readonly InventorySystem InventorySystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly SharedStorageSystem _storage = default!;
+ [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
/// <summary>
/// Equips starting gear onto the given entity.
/// </summary>
/// <param name="entity">Entity to load out.</param>
/// <param name="startingGear">Starting gear to use.</param>
- /// <param name="profile">Character profile to use, if any.</param>
- public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile)
+ public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear)
{
if (InventorySystem.TryGetSlots(entity, out var slotDefinitions))
{
foreach (var slot in slotDefinitions)
{
- var equipmentStr = startingGear.GetGear(slot.Name, profile);
+ var equipmentStr = startingGear.GetGear(slot.Name);
if (!string.IsNullOrEmpty(equipmentStr))
{
var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, EntityManager.GetComponent<TransformComponent>(entity).Coordinates);
- InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, true, force:true);
+ InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, silent: true, force:true);
}
}
}
- if (!TryComp(entity, out HandsComponent? handsComponent))
- return;
+ if (TryComp(entity, out HandsComponent? handsComponent))
+ {
+ var inhand = startingGear.Inhand;
+ var coords = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
+ foreach (var prototype in inhand)
+ {
+ var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
+
+ if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
+ {
+ _handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
+ }
+ }
+ }
- var inhand = startingGear.Inhand;
- var coords = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
- foreach (var prototype in inhand)
+ if (startingGear.Storage.Count > 0)
{
- var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
+ var coords = _xformSystem.GetMapCoordinates(entity);
+ var ents = new ValueList<EntityUid>();
+ TryComp(entity, out InventoryComponent? inventoryComp);
- if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
+ foreach (var (slot, entProtos) in startingGear.Storage)
{
- _handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
+ if (entProtos.Count == 0)
+ continue;
+
+ foreach (var ent in entProtos)
+ {
+ ents.Add(Spawn(ent, coords));
+ }
+
+ if (inventoryComp != null &&
+ InventorySystem.TryGetSlotEntity(entity, slot, out var slotEnt, inventoryComponent: inventoryComp) &&
+ TryComp(slotEnt, out StorageComponent? storage))
+ {
+ foreach (var ent in ents)
+ {
+ _storage.Insert(slotEnt.Value, ent, out _, storageComp: storage, playSound: false);
+ }
+ }
}
}
}
--- /dev/null
+loadout-window = Loadout
+loadout-none = None
--- /dev/null
+# Miscellaneous
+loadout-group-trinkets = Trinkets
+
+# Command
+loadout-group-captain-head = Captain head
+loadout-group-captain-jumpsuit = Captain jumpsuit
+loadout-group-captain-neck = Captain neck
+loadout-group-captain-backpack = Captain backpack
+
+loadout-group-hop-head = Head of Personnel head
+loadout-group-hop-jumpsuit = Head of Personnel jumpsuit
+loadout-group-hop-neck = Head of Personnel neck
+loadout-group-hop-backpack = Head of Personnel backpack
+
+
+# Civilian
+loadout-group-passenger-jumpsuit = Passenger jumpsuit
+loadout-group-passenger-mask = Passenger mask
+loadout-group-passenger-gloves = Passenger gloves
+loadout-group-passenger-backpack = Passenger backpack
+
+loadout-group-bartender-head = Bartender head
+loadout-group-bartender-jumpsuit = Bartender jumpsuit
+loadout-group-bartender-outerclothing = Bartender outer clothing
+
+loadout-group-chef-head = Chef head
+loadout-group-chef-mask = Chef mask
+loadout-group-chef-jumpsuit = Chef jumpsuit
+loadout-group-chef-outerclothing = Chef outer clothing
+
+loadout-group-librarian-jumpsuit = Librarian jumpsuit
+
+loadout-group-lawyer-jumpsuit = Lawyer jumpsuit
+loadout-group-lawyer-neck = Lawyer neck
+
+loadout-group-chaplain-head = Chaplain head
+loadout-group-chaplain-mask = Chaplain mask
+loadout-group-chaplain-jumpsuit = Chaplain jumpsuit
+loadout-group-chaplain-backpack = Chaplain backpack
+loadout-group-chaplain-outerclothing = Chaplain outer clothing
+loadout-group-chaplain-neck = Chaplain neck
+
+loadout-group-janitor-head = Janitor head
+loadout-group-janitor-jumpsuit = Janitor jumpsuit
+
+loadout-group-botanist-head = Botanist head
+loadout-group-botanist-jumpsuit = Botanist jumpsuit
+loadout-group-botanist-backpack = Botanist backpack
+loadout-group-botanist-outerclothing = Botanist outer clothing
+
+loadout-group-clown-head = Clown head
+loadout-group-clown-jumpsuit = Clown jumpsuit
+loadout-group-clown-backpack = Clown backpack
+loadout-group-clown-shoes = Clown shoes
+
+loadout-group-mime-head = Mime head
+loadout-group-mime-mask = Mime mask
+loadout-group-mime-jumpsuit = Mime jumpsuit
+loadout-group-mime-backpack = Mime backpack
+
+loadout-group-musician-backpack = Musician backpack
+
+# Cargo
+loadout-group-quartermaster-head = Quartermaster head
+loadout-group-quartermaster-jumpsuit = Quartermaster jumpsuit
+loadout-group-quartermaster-backpack = Quartermaster backpack
+loadout-group-quartermaster-neck = Quartermaster neck
+
+loadout-group-cargo-technician-head = Technician head
+loadout-group-cargo-technician-jumpsuit = Technician jumpsuit
+loadout-group-cargo-technician-backpack = Technician backpack
+
+loadout-group-salvage-specialist-backpack = Salvage Specialist backpack
+
+# Engineering
+loadout-group-chief-engineer-head = Chief Engineer head
+loadout-group-chief-engineer-jumpsuit = Chief Engineer jumpsuit
+loadout-group-chief-engineer-backpack = Chief Engineer backpack
+loadout-group-chief-engineer-neck = Chief Engineer neck
+
+loadout-group-technical-assistant-jumpsuit = Technical Assistant jumpsuit
+
+loadout-group-station-engineer-head = Station Engineer head
+loadout-group-station-engineer-jumpsuit = Station Engineer jumpsuit
+loadout-group-station-engineer-backpack = Station Engineer backpack
+loadout-group-station-engineer-outerclothing = Station Engineer outer clothing
+loadout-group-station-engineer-id = Station Engineer ID
+
+loadout-group-atmospheric-technician-jumpsuit = Atmospheric Technician jumpsuit
+loadout-group-atmospheric-technician-backpack = Atmospheric Technician backpack
+
+# Science
+loadout-group-research-director-head = Research Director head
+loadout-group-research-director-neck = Research Director neck
+loadout-group-research-director-jumpsuit = Research Director jumpsuit
+loadout-group-research-director-backpack = Research Director backpack
+loadout-group-research-director-outerclothing = Research Director outer clothing
+
+loadout-group-scientist-head = Scientist head
+loadout-group-scientist-neck = Scientist neck
+loadout-group-scientist-jumpsuit = Scientist jumpsuit
+loadout-group-scientist-backpack = Scientist backpack
+loadout-group-scientist-outerclothing = Scientist outer clothing
+loadout-group-scientist-id = Scientist ID
+
+loadout-group-research-assistant-jumpsuit = Research Assistant jumpsuit
+
+# Security
+loadout-group-head-of-security-head = Head of Security head
+loadout-group-head-of-security-jumpsuit = Head of Security jumpsuit
+loadout-group-head-of-security-neck = Head of Security neck
+loadout-group-head-of-security-outerclothing = Head of Security outer clothing
+
+loadout-group-warden-head = Warden head
+loadout-group-warden-jumpsuit = Warden jumpsuit
+loadout-group-warden-outerclothing = Warden outer clothing
+
+loadout-group-security-head = Security head
+loadout-group-security-jumpsuit = Security jumpsuit
+loadout-group-security-backpack = Security backpack
+loadout-group-security-id = Security ID
+
+loadout-group-detective-head = Detective head
+loadout-group-detective-neck = Detective neck
+loadout-group-detective-jumpsuit = Detective jumpsuit
+loadout-group-detective-backpack = Detective backpack
+loadout-group-detective-outerclothing = Detective outer clothing
+
+loadout-group-security-cadet-jumpsuit = Security cadet jumpsuit
+
+# Medical
+loadout-group-chief-medical-officer-head = Chief Medical Officer head
+loadout-group-chief-medical-officer-jumpsuit = Chief Medical Officer jumpsuit
+loadout-group-chief-medical-officer-outerclothing = Chief Medical Officer outer clothing
+loadout-group-chief-medical-officer-backpack = Chief Medical Officer backpack
+loadout-group-chief-medical-officer-neck = Chief Medical Officer neck
+
+loadout-group-medical-doctor-head = Medical Doctor head
+loadout-group-medical-doctor-jumpsuit = Medical Doctor jumpsuit
+loadout-group-medical-doctor-outerclothing = Medical Doctor outer clothing
+loadout-group-medical-doctor-backpack = Medical Doctor backpack
+loadout-group-medical-doctor-id = Medical Doctor ID
+
+loadout-group-medical-intern-jumpsuit = Medical intern jumpsuit
+
+loadout-group-chemist-jumpsuit = Chemist jumpsuit
+loadout-group-chemist-outerclothing = Chemist outer clothing
+loadout-group-chemist-backpack = Chemist backpack
+
+loadout-group-paramedic-head = Paramedic head
+loadout-group-paramedic-jumpsuit = Paramedic jumpsuit
+loadout-group-paramedic-outerclothing = Paramedic outer clothing
+loadout-group-paramedic-backpack = Paramedic backpack
+
+# Wildcards
+loadout-group-reporter-jumpsuit = Reporter jumpsuit
+
+loadout-group-boxer-jumpsuit = Boxer jumpsuit
+loadout-group-boxer-gloves = Boxer gloves
--- /dev/null
+# Restrictions
+loadout-restrictions = Restrictions
+loadouts-min-limit = Min count: {$count}
+loadouts-max-limit = Max count: {$count}
+loadouts-points-limit = Points: {$count} / {$max}
+
+loadouts-points-restriction = Insufficient points
humanoid-profile-editor-import-button = Import
humanoid-profile-editor-export-button = Export
humanoid-profile-editor-save-button = Save
-humanoid-profile-editor-clothing-label = Clothing:
-humanoid-profile-editor-backpack-label = Backpack:
humanoid-profile-editor-spawn-priority-label = Spawn priority:
humanoid-profile-editor-eyes-label = Eye color:
humanoid-profile-editor-jobs-tab = Jobs
- id: Flash
#- id: TelescopicBaton
+- type: entity
+ noSpawn: true
+ parent: ClothingBackpackIan
+ id: ClothingBackpackHOPIanFilled
+ components:
+ - type: StorageFill
+ contents:
+ - id: BoxSurvival
+ - id: Flash
+ #- id: TelescopicBaton
+
- type: entity
noSpawn: true
parent: ClothingBackpackMedical
--- /dev/null
+# Head
+- type: loadout
+ id: CargoTechnicianHead
+ equipment: CargoTechnicianHead
+
+- type: startingGear
+ id: CargoTechnicianHead
+ equipment:
+ head: ClothingHeadHatCargosoft
+
+# Jumpsuit
+- type: loadout
+ id: CargoTechnicianJumpsuit
+ equipment: CargoTechnicianJumpsuit
+
+- type: startingGear
+ id: CargoTechnicianJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitCargo
+
+- type: loadout
+ id: CargoTechnicianJumpskirt
+ equipment: CargoTechnicianJumpskirt
+
+- type: startingGear
+ id: CargoTechnicianJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtCargo
+
+# Back
+- type: loadout
+ id: CargoTechnicianBackpack
+ equipment: CargoTechnicianBackpack
+
+- type: startingGear
+ id: CargoTechnicianBackpack
+ equipment:
+ back: ClothingBackpackCargoFilled
+
+- type: loadout
+ id: CargoTechnicianSatchel
+ equipment: CargoTechnicianSatchel
+
+- type: startingGear
+ id: CargoTechnicianSatchel
+ equipment:
+ back: ClothingBackpackSatchelCargoFilled
+
+- type: loadout
+ id: CargoTechnicianDuffel
+ equipment: CargoTechnicianDuffel
+
+- type: startingGear
+ id: CargoTechnicianDuffel
+ equipment:
+ back: ClothingBackpackDuffelCargoFilled
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: QuartermasterJumpsuit
+ equipment: QuartermasterJumpsuit
+
+- type: startingGear
+ id: QuartermasterJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitQM
+
+- type: loadout
+ id: QuartermasterJumpskirt
+ equipment: QuartermasterJumpskirt
+
+- type: startingGear
+ id: QuartermasterJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtQM
+
+- type: loadout
+ id: QuartermasterTurtleneck
+ equipment: QuartermasterTurtleneck
+
+- type: startingGear
+ id: QuartermasterTurtleneck
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitQMTurtleneck
+
+- type: loadout
+ id: QuartermasterTurtleneckSkirt
+ equipment: QuartermasterTurtleneckSkirt
+
+- type: startingGear
+ id: QuartermasterTurtleneckSkirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtQMTurtleneck
+
+- type: loadout
+ id: QuartermasterFormalSuit
+ equipment: QuartermasterFormalSuit
+
+- type: startingGear
+ id: QuartermasterFormalSuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitQMFormal
+
+# Head
+- type: loadout
+ id: QuartermasterHead
+ equipment: QuartermasterHead
+
+- type: startingGear
+ id: QuartermasterHead
+ equipment:
+ head: ClothingHeadHatQMsoft
+
+- type: loadout
+ id: QuartermasterBeret
+ equipment: QuartermasterBeret
+
+- type: startingGear
+ id: QuartermasterBeret
+ equipment:
+ head: ClothingHeadHatBeretQM
+
+# Neck
+- type: loadout
+ id: QuartermasterCloak
+ equipment: QuartermasterCloak
+
+- type: startingGear
+ id: QuartermasterCloak
+ equipment:
+ neck: ClothingNeckCloakQm
+
+- type: loadout
+ id: QuartermasterMantle
+ equipment: QuartermasterMantle
+
+- type: startingGear
+ id: QuartermasterMantle
+ equipment:
+ neck: ClothingNeckMantleQM
+
+# Back
+- type: loadout
+ id: QuartermasterBackpack
+ equipment: QuartermasterBackpack
+
+- type: startingGear
+ id: QuartermasterBackpack
+ equipment:
+ back: ClothingBackpackQuartermasterFilled
+
+- type: loadout
+ id: QuartermasterSatchel
+ equipment: QuartermasterSatchel
+
+- type: startingGear
+ id: QuartermasterSatchel
+ equipment:
+ back: ClothingBackpackSatchelQuartermasterFilled
+
+- type: loadout
+ id: QuartermasterDuffel
+ equipment: QuartermasterDuffel
+
+- type: startingGear
+ id: QuartermasterDuffel
+ equipment:
+ back: ClothingBackpackDuffelQuartermasterFilled
\ No newline at end of file
--- /dev/null
+# Back
+- type: loadout
+ id: SalvageSpecialistBackpack
+ equipment: SalvageSpecialistBackpack
+
+- type: startingGear
+ id: SalvageSpecialistBackpack
+ equipment:
+ back: ClothingBackpackSalvageFilled
+
+- type: loadout
+ id: SalvageSpecialistSatchel
+ equipment: SalvageSpecialistSatchel
+
+- type: startingGear
+ id: SalvageSpecialistSatchel
+ equipment:
+ back: ClothingBackpackSatchelSalvageFilled
+
+- type: loadout
+ id: SalvageSpecialistDuffel
+ equipment: SalvageSpecialistDuffel
+
+- type: startingGear
+ id: SalvageSpecialistDuffel
+ equipment:
+ back: ClothingBackpackDuffelSalvageFilled
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: BartenderHead
+ equipment: BartenderHead
+
+- type: startingGear
+ id: BartenderHead
+ equipment:
+ head: ClothingHeadHatTophat
+
+- type: loadout
+ id: BartenderBowler
+ equipment: BartenderBowler
+
+- type: startingGear
+ id: BartenderBowler
+ equipment:
+ head: ClothingHeadHatBowlerHat
+
+# Jumpsuit
+- type: loadout
+ id: BartenderJumpsuit
+ equipment: BartenderJumpsuit
+
+- type: startingGear
+ id: BartenderJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitBartender
+
+- type: loadout
+ id: BartenderJumpskirt
+ equipment: BartenderJumpskirt
+
+- type: startingGear
+ id: BartenderJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtBartender
+
+- type: loadout
+ id: BartenderJumpsuitPurple
+ equipment: BartenderJumpsuitPurple
+
+- type: startingGear
+ id: BartenderJumpsuitPurple
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitBartenderPurple
+
+# Outer clothing
+- type: loadout
+ id: BartenderApron
+ equipment: BartenderApron
+
+- type: startingGear
+ id: BartenderApron
+ equipment:
+ outerClothing: ClothingOuterApronBar
+
+- type: loadout
+ id: BartenderVest
+ equipment: BartenderVest
+
+- type: startingGear
+ id: BartenderVest
+ equipment:
+ outerClothing: ClothingOuterVest
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: BotanistHead
+ equipment: BotanistHead
+
+- type: startingGear
+ id: BotanistHead
+ equipment:
+ head: ClothingHeadHatTrucker
+
+- type: loadout
+ id: BotanistBandana
+ equipment: BotanistBandana
+
+- type: startingGear
+ id: BotanistBandana
+ equipment:
+ head: ClothingHeadBandBotany
+
+# Jumpsuit
+- type: loadout
+ id: BotanistJumpsuit
+ equipment: BotanistJumpsuit
+
+- type: startingGear
+ id: BotanistJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitHydroponics
+
+- type: loadout
+ id: BotanistJumpskirt
+ equipment: BotanistJumpskirt
+
+- type: startingGear
+ id: BotanistJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtHydroponics
+
+- type: loadout
+ id: BotanistOveralls
+ equipment: BotanistOveralls
+
+- type: startingGear
+ id: BotanistOveralls
+ equipment:
+ jumpsuit: ClothingUniformOveralls
+
+# Back
+- type: loadout
+ id: BotanistBackpack
+ equipment: BotanistBackpack
+
+- type: startingGear
+ id: BotanistBackpack
+ equipment:
+ back: ClothingBackpackHydroponicsFilled
+
+- type: loadout
+ id: BotanistSatchel
+ equipment: BotanistSatchel
+
+- type: startingGear
+ id: BotanistSatchel
+ equipment:
+ back: ClothingBackpackSatchelHydroponicsFilled
+
+- type: loadout
+ id: BotanistDuffel
+ equipment: BotanistDuffel
+
+- type: startingGear
+ id: BotanistDuffel
+ equipment:
+ back: ClothingBackpackDuffelHydroponicsFilled
+
+# Outer clothing
+- type: loadout
+ id: BotanistApron
+ equipment: BotanistApron
+
+- type: startingGear
+ id: BotanistApron
+ equipment:
+ outerClothing: ClothingOuterApronBotanist
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: ChaplainHead
+ equipment: ChaplainHead
+
+- type: startingGear
+ id: ChaplainHead
+ equipment:
+ head: ClothingHeadHatFez
+
+- type: loadout
+ id: ChaplainPlagueHat
+ equipment: ChaplainPlagueHat
+
+- type: startingGear
+ id: ChaplainPlagueHat
+ equipment:
+ head: ClothingHeadHatPlaguedoctor
+
+- type: loadout
+ id: ChaplainWitchHat
+ equipment: ChaplainWitchHat
+
+- type: startingGear
+ id: ChaplainWitchHat
+ equipment:
+ head: ClothingHeadHatWitch
+
+- type: loadout
+ id: ChaplainWitchHatAlt
+ equipment: ChaplainWitchHatAlt
+
+- type: startingGear
+ id: ChaplainWitchHatAlt
+ equipment:
+ head: ClothingHeadHatWitch1
+
+# Mask
+- type: loadout
+ id: ChaplainMask
+ equipment: ChaplainMask
+
+- type: startingGear
+ id: ChaplainMask
+ equipment:
+ mask: ClothingMaskPlague
+
+# Jumpsuit
+- type: loadout
+ id: ChaplainJumpsuit
+ equipment: ChaplainJumpsuit
+
+- type: startingGear
+ id: ChaplainJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitChaplain
+
+- type: loadout
+ id: ChaplainJumpskirt
+ equipment: ChaplainJumpskirt
+
+- type: startingGear
+ id: ChaplainJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtChaplain
+
+- type: loadout
+ id: ChaplainRobesDark
+ equipment: ChaplainRobesDark
+
+- type: startingGear
+ id: ChaplainRobesDark
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitMonasticRobeDark
+
+- type: loadout
+ id: ChaplainRobesLight
+ equipment: ChaplainRobesLight
+
+- type: startingGear
+ id: ChaplainRobesLight
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitMonasticRobeLight
+
+# Back
+- type: loadout
+ id: ChaplainBackpack
+ equipment: ChaplainBackpack
+
+- type: startingGear
+ id: ChaplainBackpack
+ equipment:
+ back: ClothingBackpackChaplainFilled
+
+- type: loadout
+ id: ChaplainSatchel
+ equipment: ChaplainSatchel
+
+- type: startingGear
+ id: ChaplainSatchel
+ equipment:
+ back: ClothingBackpackSatchelChaplainFilled
+
+- type: loadout
+ id: ChaplainDuffel
+ equipment: ChaplainDuffel
+
+- type: startingGear
+ id: ChaplainDuffel
+ equipment:
+ back: ClothingBackpackDuffelChaplainFilled
+
+# Neck
+- type: loadout
+ id: ChaplainNeck
+ equipment: ChaplainNeck
+
+- type: startingGear
+ id: ChaplainNeck
+ equipment:
+ neck: ClothingNeckStoleChaplain
+
+# Outer clothing
+- type: loadout
+ id: ChaplainPlagueSuit
+ equipment: ChaplainPlagueSuit
+
+- type: startingGear
+ id: ChaplainPlagueSuit
+ equipment:
+ outerClothing: ClothingOuterPlagueSuit
+
+- type: loadout
+ id: ChaplainNunRobe
+ equipment: ChaplainNunRobe
+
+- type: startingGear
+ id: ChaplainNunRobe
+ equipment:
+ outerClothing: ClothingOuterNunRobe
+
+- type: loadout
+ id: ChaplainBlackHoodie
+ equipment: ChaplainBlackHoodie
+
+- type: startingGear
+ id: ChaplainBlackHoodie
+ equipment:
+ outerClothing: ClothingOuterHoodieBlack
+
+- type: loadout
+ id: ChaplainHoodie
+ equipment: ChaplainHoodie
+
+- type: startingGear
+ id: ChaplainHoodie
+ equipment:
+ outerClothing: ClothingOuterHoodieChaplain
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: ChefHead
+ equipment: ChefHead
+
+- type: startingGear
+ id: ChefHead
+ equipment:
+ head: ClothingHeadHatChef
+
+# Mask
+- type: loadout
+ id: ChefMask
+ equipment: ChefMask
+
+- type: startingGear
+ id: ChefMask
+ equipment:
+ mask: ClothingMaskItalianMoustache
+
+# Jumpsuit
+- type: loadout
+ id: ChefJumpsuit
+ equipment: ChefJumpsuit
+
+- type: startingGear
+ id: ChefJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitChef
+
+- type: loadout
+ id: ChefJumpskirt
+ equipment: ChefJumpskirt
+
+- type: startingGear
+ id: ChefJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtChef
+
+# Outer clothing
+- type: loadout
+ id: ChefApron
+ equipment: ChefApron
+
+- type: startingGear
+ id: ChefApron
+ equipment:
+ outerClothing: ClothingOuterApronChef
+
+- type: loadout
+ id: ChefJacket
+ equipment: ChefJacket
+
+- type: startingGear
+ id: ChefJacket
+ equipment:
+ outerClothing: ClothingOuterJacketChef
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: JesterHat
+ equipment: JesterHat
+
+- type: startingGear
+ id: JesterHat
+ equipment:
+ head: ClothingHeadHatJesterAlt
+
+# Jumpsuit
+- type: loadout
+ id: ClownSuit
+ equipment: ClownSuit
+
+- type: startingGear
+ id: ClownSuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitClown
+
+- type: loadout
+ id: JesterSuit
+ equipment: JesterSuit
+
+- type: startingGear
+ id: JesterSuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitJesterAlt
+
+# Back
+- type: loadout
+ id: ClownBackpack
+ equipment: ClownBackpack
+
+- type: startingGear
+ id: ClownBackpack
+ equipment:
+ back: ClothingBackpackClownFilled
+
+- type: loadout
+ id: ClownSatchel
+ equipment: ClownSatchel
+
+- type: startingGear
+ id: ClownSatchel
+ equipment:
+ back: ClothingBackpackSatchelClownFilled
+
+- type: loadout
+ id: ClownDuffel
+ equipment: ClownDuffel
+
+- type: startingGear
+ id: ClownDuffel
+ equipment:
+ back: ClothingBackpackDuffelClownFilled
+
+# Shoes
+- type: loadout
+ id: ClownShoes
+ equipment: ClownShoes
+
+- type: startingGear
+ id: ClownShoes
+ equipment:
+ shoes: ClothingShoesClown
+
+- type: loadout
+ id: JesterShoes
+ equipment: JesterShoes
+
+- type: startingGear
+ id: JesterShoes
+ equipment:
+ shoes: ClothingShoesJester
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: JanitorHead
+ equipment: JanitorHead
+
+- type: startingGear
+ id: JanitorHead
+ equipment:
+ head: ClothingHeadHatPurplesoft
+
+# Jumpsuit
+- type: loadout
+ id: JanitorJumpsuit
+ equipment: JanitorJumpsuit
+
+- type: startingGear
+ id: JanitorJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitJanitor
+
+- type: loadout
+ id: JanitorJumpskirt
+ equipment: JanitorJumpskirt
+
+- type: startingGear
+ id: JanitorJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtJanitor
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: LawyerJumpsuit
+ equipment: LawyerJumpsuit
+
+- type: startingGear
+ id: LawyerJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitLawyerBlack
+
+- type: loadout
+ id: LawyerJumpskirt
+ equipment: LawyerJumpskirt
+
+- type: startingGear
+ id: LawyerJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtLawyerBlack
+
+- type: loadout
+ id: LawyerJumpsuitBlue
+ equipment: LawyerJumpsuitBlue
+
+- type: startingGear
+ id: LawyerJumpsuitBlue
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitLawyerBlue
+
+- type: loadout
+ id: LawyerJumpskirtBlue
+ equipment: LawyerJumpskirtBlue
+
+- type: startingGear
+ id: LawyerJumpskirtBlue
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtLawyerBlue
+
+- type: loadout
+ id: LawyerJumpsuitPurple
+ equipment: LawyerJumpsuitPurple
+
+- type: startingGear
+ id: LawyerJumpsuitPurple
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitLawyerPurple
+
+- type: loadout
+ id: LawyerJumpskirtPurple
+ equipment: LawyerJumpskirtPurple
+
+- type: startingGear
+ id: LawyerJumpskirtPurple
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtLawyerPurple
+
+- type: loadout
+ id: LawyerJumpsuitRed
+ equipment: LawyerJumpsuitRed
+
+- type: startingGear
+ id: LawyerJumpsuitRed
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitLawyerRed
+
+- type: loadout
+ id: LawyerJumpskirtRed
+ equipment: LawyerJumpskirtRed
+
+- type: startingGear
+ id: LawyerJumpskirtRed
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtLawyerRed
+
+- type: loadout
+ id: LawyerJumpsuitGood
+ equipment: LawyerJumpsuitGood
+
+- type: startingGear
+ id: LawyerJumpsuitGood
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitLawyerGood
+
+- type: loadout
+ id: LawyerJumpskirtGood
+ equipment: LawyerJumpskirtGood
+
+- type: startingGear
+ id: LawyerJumpskirtGood
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtLawyerGood
+
+# Neck
+- type: loadout
+ id: LawyerNeck
+ equipment: LawyerNeck
+
+- type: startingGear
+ id: LawyerNeck
+ equipment:
+ neck: ClothingNeckLawyerbadge
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: LibrarianJumpsuit
+ equipment: LibrarianJumpsuit
+
+- type: startingGear
+ id: LibrarianJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitLibrarian
+
+- type: loadout
+ id: LibrarianJumpskirt
+ equipment: LibrarianJumpskirt
+
+- type: startingGear
+ id: LibrarianJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtLibrarian
+
+- type: loadout
+ id: CuratorJumpsuit
+ equipment: CuratorJumpsuit
+
+- type: startingGear
+ id: CuratorJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitCurator
+
+- type: loadout
+ id: CuratorJumpskirt
+ equipment: CuratorJumpskirt
+
+- type: startingGear
+ id: CuratorJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtCurator
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: MimeHead
+ equipment: MimeHead
+
+- type: startingGear
+ id: MimeHead
+ equipment:
+ head: ClothingHeadHatBeret
+
+- type: loadout
+ id: MimeFrenchBeret
+ equipment: MimeFrenchBeret
+
+- type: startingGear
+ id: MimeFrenchBeret
+ equipment:
+ head: ClothingHeadHatBeretFrench
+
+- type: loadout
+ id: MimeCap
+ equipment: MimeCap
+
+- type: startingGear
+ id: MimeCap
+ equipment:
+ head: ClothingHeadHatMimesoft
+
+# Mask
+- type: loadout
+ id: MimeMask
+ equipment: MimeMask
+
+- type: startingGear
+ id: MimeMask
+ equipment:
+ mask: ClothingMaskMime
+
+- type: loadout
+ id: MimeMaskSad
+ equipment: MimeMaskSad
+
+- type: startingGear
+ id: MimeMaskSad
+ equipment:
+ mask: ClothingMaskSadMime
+
+- type: loadout
+ id: MimeMaskScared
+ equipment: MimeMaskScared
+
+- type: startingGear
+ id: MimeMaskScared
+ equipment:
+ mask: ClothingMaskScaredMime
+
+# Jumpsuit
+- type: loadout
+ id: MimeJumpsuit
+ equipment: MimeJumpsuit
+
+- type: startingGear
+ id: MimeJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitMime
+
+- type: loadout
+ id: MimeJumpskirt
+ equipment: MimeJumpskirt
+
+- type: startingGear
+ id: MimeJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtMime
+
+# Back
+- type: loadout
+ id: MimeBackpack
+ equipment: MimeBackpack
+
+- type: startingGear
+ id: MimeBackpack
+ equipment:
+ back: ClothingBackpackMimeFilled
+
+- type: loadout
+ id: MimeSatchel
+ equipment: MimeSatchel
+
+- type: startingGear
+ id: MimeSatchel
+ equipment:
+ back: ClothingBackpackSatchelMimeFilled
+
+- type: loadout
+ id: MimeDuffel
+ equipment: MimeDuffel
+
+- type: startingGear
+ id: MimeDuffel
+ equipment:
+ back: ClothingBackpackDuffelMimeFilled
\ No newline at end of file
--- /dev/null
+# Back
+- type: loadout
+ id: MusicianBackpack
+ equipment: MusicianBackpack
+
+- type: startingGear
+ id: MusicianBackpack
+ equipment:
+ back: ClothingBackpackMusicianFilled
+
+- type: loadout
+ id: MusicianSatchel
+ equipment: MusicianSatchel
+
+- type: startingGear
+ id: MusicianSatchel
+ equipment:
+ back: ClothingBackpackSatchelMusicianFilled
+
+- type: loadout
+ id: MusicianDuffel
+ equipment: MusicianDuffel
+
+- type: startingGear
+ id: MusicianDuffel
+ equipment:
+ back: ClothingBackpackDuffelMusicianFilled
\ No newline at end of file
--- /dev/null
+# Greytide Time
+- type: loadoutEffectGroup
+ id: GreyTider
+ effects:
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobPassenger
+ time: 36000 #10 hrs, silly reward for people who play passenger a lot
+
+# Face
+- type: loadout
+ id: PassengerFace
+ equipment: GasMask
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: GreyTider
+
+- type: startingGear
+ id: GasMask
+ equipment:
+ mask: ClothingMaskGas
+
+# Jumpsuit
+- type: loadout
+ id: GreyJumpsuit
+ equipment: GreyJumpsuit
+
+- type: startingGear
+ id: GreyJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitColorGrey
+
+- type: loadout
+ id: GreyJumpskirt
+ equipment: GreyJumpskirt
+
+- type: startingGear
+ id: GreyJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtColorGrey
+
+# Back
+- type: loadout
+ id: CommonBackpack
+ equipment: CommonBackpack
+
+- type: startingGear
+ id: CommonBackpack
+ equipment:
+ back: ClothingBackpackFilled
+
+- type: loadout
+ id: CommonSatchel
+ equipment: CommonSatchel
+
+- type: startingGear
+ id: CommonSatchel
+ equipment:
+ back: ClothingBackpackSatchelFilled
+
+- type: loadout
+ id: CommonDuffel
+ equipment: CommonDuffel
+
+- type: startingGear
+ id: CommonDuffel
+ equipment:
+ back: ClothingBackpackDuffelFilled
+
+# Gloves
+- type: loadout
+ id: PassengerGloves
+ equipment: FingerlessInsulatedGloves
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: GreyTider
+
+- type: startingGear
+ id: FingerlessInsulatedGloves
+ equipment:
+ gloves: ClothingHandsGlovesFingerlessInsulated
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: CaptainJumpsuit
+ equipment: CaptainJumpsuit
+
+- type: startingGear
+ id: CaptainJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitCaptain
+
+- type: loadout
+ id: CaptainJumpskirt
+ equipment: CaptainJumpskirt
+
+- type: startingGear
+ id: CaptainJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtCaptain
+
+- type: loadout
+ id: CaptainFormalSuit
+ equipment: CaptainFormalSuit
+
+- type: startingGear
+ id: CaptainFormalSuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitCapFormal
+
+- type: loadout
+ id: CaptainFormalSkirt
+ equipment: CaptainFormalSkirt
+
+- type: startingGear
+ id: CaptainFormalSkirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtCapFormalDress
+
+# Head
+- type: loadout
+ id: CaptainHead
+ equipment: CaptainHead
+
+- type: startingGear
+ id: CaptainHead
+ equipment:
+ head: ClothingHeadHatCaptain
+
+- type: loadout
+ id: CaptainCap
+ equipment: CaptainCap
+
+- type: startingGear
+ id: CaptainCap
+ equipment:
+ head: ClothingHeadHatCapcap
+
+# Neck
+- type: loadout
+ id: CaptainCloak
+ equipment: CaptainCloak
+
+- type: startingGear
+ id: CaptainCloak
+ equipment:
+ neck: ClothingNeckCloakCap
+
+- type: loadout
+ id: CaptainCloakFormal
+ equipment: CaptainCloakFormal
+
+- type: startingGear
+ id: CaptainCloakFormal
+ equipment:
+ neck: ClothingNeckCloakCapFormal
+
+- type: loadout
+ id: CaptainMantle
+ equipment: CaptainMantle
+
+- type: startingGear
+ id: CaptainMantle
+ equipment:
+ neck: ClothingNeckMantleCap
+
+# Back
+- type: loadout
+ id: CaptainBackpack
+ equipment: CaptainBackpack
+
+- type: startingGear
+ id: CaptainBackpack
+ equipment:
+ back: ClothingBackpackCaptainFilled
+
+- type: loadout
+ id: CaptainSatchel
+ equipment: CaptainSatchel
+
+- type: startingGear
+ id: CaptainSatchel
+ equipment:
+ back: ClothingBackpackSatchelCaptainFilled
+
+- type: loadout
+ id: CaptainDuffel
+ equipment: CaptainDuffel
+
+- type: startingGear
+ id: CaptainDuffel
+ equipment:
+ back: ClothingBackpackDuffelCaptainFilled
\ No newline at end of file
--- /dev/null
+# Professional HoP Time
+- type: loadoutEffectGroup
+ id: ProfessionalHoP
+ effects:
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobHeadOfPersonnel
+ time: 54000 #15 hrs, special reward for HoP mains
+
+# Jumpsuit
+- type: loadout
+ id: HoPJumpsuit
+ equipment: HoPJumpsuit
+
+- type: startingGear
+ id: HoPJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitHoP
+
+- type: loadout
+ id: HoPJumpskirt
+ equipment: HoPJumpskirt
+
+- type: startingGear
+ id: HoPJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtHoP
+
+# Head
+- type: loadout
+ id: HoPHead
+ equipment: HoPHead
+
+- type: startingGear
+ id: HoPHead
+ equipment:
+ head: ClothingHeadHatHopcap
+
+# Neck
+- type: loadout
+ id: HoPCloak
+ equipment: HoPCloak
+
+- type: startingGear
+ id: HoPCloak
+ equipment:
+ neck: ClothingNeckCloakHop
+
+- type: loadout
+ id: HoPMantle
+ equipment: HoPMantle
+
+- type: startingGear
+ id: HoPMantle
+ equipment:
+ neck: ClothingNeckMantleHOP
+
+# Back
+- type: loadout
+ id: HoPBackpack
+ equipment: HoPBackpack
+
+- type: startingGear
+ id: HoPBackpack
+ equipment:
+ back: ClothingBackpackHOPFilled
+
+- type: loadout
+ id: HoPSatchel
+ equipment: HoPSatchel
+
+- type: startingGear
+ id: HoPSatchel
+ equipment:
+ back: ClothingBackpackSatchelHOPFilled
+
+- type: loadout
+ id: HoPDuffel
+ equipment: HoPDuffel
+
+- type: startingGear
+ id: HoPDuffel
+ equipment:
+ back: ClothingBackpackDuffelHOPFilled
+
+- type: loadout
+ id: HoPBackpackIan
+ equipment: HoPBackpackIan
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: ProfessionalHoP
+
+- type: startingGear
+ id: HoPBackpackIan
+ equipment:
+ back: ClothingBackpackHOPIanFilled
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: AtmosphericTechnicianJumpsuit
+ equipment: AtmosphericTechnicianJumpsuit
+
+- type: startingGear
+ id: AtmosphericTechnicianJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitAtmos
+
+- type: loadout
+ id: AtmosphericTechnicianJumpskirt
+ equipment: AtmosphericTechnicianJumpskirt
+
+- type: startingGear
+ id: AtmosphericTechnicianJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtAtmos
+
+- type: loadout
+ id: AtmosphericTechnicianJumpsuitCasual
+ equipment: AtmosphericTechnicianJumpsuitCasual
+
+- type: startingGear
+ id: AtmosphericTechnicianJumpsuitCasual
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitAtmosCasual
+
+# Back
+- type: loadout
+ id: AtmosphericTechnicianBackpack
+ equipment: AtmosphericTechnicianBackpack
+
+- type: startingGear
+ id: AtmosphericTechnicianBackpack
+ equipment:
+ back: ClothingBackpackAtmosphericsFilled
+
+- type: loadout
+ id: AtmosphericTechnicianSatchel
+ equipment: AtmosphericTechnicianSatchel
+
+- type: startingGear
+ id: AtmosphericTechnicianSatchel
+ equipment:
+ back: ClothingBackpackSatchelEngineeringFilled
+
+- type: loadout
+ id: AtmosphericTechnicianDuffel
+ equipment: AtmosphericTechnicianDuffel
+
+- type: startingGear
+ id: AtmosphericTechnicianDuffel
+ equipment:
+ back: ClothingBackpackDuffelAtmosphericsFilled
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: ChiefEngineerJumpsuit
+ equipment: ChiefEngineerJumpsuit
+
+- type: startingGear
+ id: ChiefEngineerJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitChiefEngineer
+
+- type: loadout
+ id: ChiefEngineerJumpskirt
+ equipment: ChiefEngineerJumpskirt
+
+- type: startingGear
+ id: ChiefEngineerJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtChiefEngineer
+
+- type: loadout
+ id: ChiefEngineerTurtleneck
+ equipment: ChiefEngineerTurtleneck
+
+- type: startingGear
+ id: ChiefEngineerTurtleneck
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitChiefEngineerTurtle
+
+- type: loadout
+ id: ChiefEngineerTurtleneckSkirt
+ equipment: ChiefEngineerTurtleneckSkirt
+
+- type: startingGear
+ id: ChiefEngineerTurtleneckSkirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtChiefEngineerTurtle
+
+# Head
+- type: loadout
+ id: ChiefEngineerHead
+ equipment: ChiefEngineerHead
+
+- type: startingGear
+ id: ChiefEngineerHead
+ equipment:
+ head: ClothingHeadHatHardhatWhite
+
+- type: loadout
+ id: ChiefEngineerBeret
+ equipment: EngineeringBeret
+
+# Neck
+- type: loadout
+ id: ChiefEngineerCloak
+ equipment: ChiefEngineerCloak
+
+- type: startingGear
+ id: ChiefEngineerCloak
+ equipment:
+ neck: ClothingNeckCloakCe
+
+- type: loadout
+ id: ChiefEngineerMantle
+ equipment: ChiefEngineerMantle
+
+- type: startingGear
+ id: ChiefEngineerMantle
+ equipment:
+ neck: ClothingNeckMantleCE
+
+# Back
+- type: loadout
+ id: ChiefEngineerBackpack
+ equipment: ChiefEngineerBackpack
+
+- type: startingGear
+ id: ChiefEngineerBackpack
+ equipment:
+ back: ClothingBackpackChiefEngineerFilled
+
+- type: loadout
+ id: ChiefEngineerSatchel
+ equipment: ChiefEngineerSatchel
+
+- type: startingGear
+ id: ChiefEngineerSatchel
+ equipment:
+ back: ClothingBackpackSatchelChiefEngineerFilled
+
+- type: loadout
+ id: ChiefEngineerDuffel
+ equipment: ChiefEngineerDuffel
+
+- type: startingGear
+ id: ChiefEngineerDuffel
+ equipment:
+ back: ClothingBackpackDuffelChiefEngineerFilled
\ No newline at end of file
--- /dev/null
+# Senior times
+- type: loadoutEffectGroup
+ id: SeniorEngineering
+ effects:
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobAtmosphericTechnician
+ time: 21600 #6 hrs
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobStationEngineer
+ time: 21600 #6 hrs
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:DepartmentTimeRequirement
+ department: Engineering
+ time: 216000 # 60 hrs
+
+# Head
+- type: loadout
+ id: StationEngineerHead
+ equipment: StationEngineerHead
+
+- type: startingGear
+ id: StationEngineerHead
+ equipment:
+ head: ClothingHeadHatHardhatYellow
+
+- type: loadout
+ id: SeniorEngineerBeret
+ equipment: EngineeringBeret
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: EngineeringBeret
+ equipment:
+ head: ClothingHeadHatBeretEngineering
+
+# Jumpsuit
+- type: loadout
+ id: StationEngineerJumpsuit
+ equipment: StationEngineerJumpsuit
+
+- type: startingGear
+ id: StationEngineerJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitEngineering
+
+- type: loadout
+ id: StationEngineerJumpskirt
+ equipment: StationEngineerJumpskirt
+
+- type: startingGear
+ id: StationEngineerJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtEngineering
+
+- type: loadout
+ id: StationEngineerHazardsuit
+ equipment: StationEngineerHazardsuit
+
+- type: startingGear
+ id: StationEngineerHazardsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitEngineeringHazard
+
+- type: loadout
+ id: SeniorEngineerJumpsuit
+ equipment: SeniorEngineerJumpsuit
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: SeniorEngineerJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitSeniorEngineer
+
+- type: loadout
+ id: SeniorEngineerJumpskirt
+ equipment: SeniorEngineerJumpskirt
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: SeniorEngineerJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtSeniorEngineer
+
+# Back
+- type: loadout
+ id: StationEngineerBackpack
+ equipment: StationEngineerBackpack
+
+- type: startingGear
+ id: StationEngineerBackpack
+ equipment:
+ back: ClothingBackpackEngineeringFilled
+
+- type: loadout
+ id: StationEngineerSatchel
+ equipment: StationEngineerSatchel
+
+- type: startingGear
+ id: StationEngineerSatchel
+ equipment:
+ back: ClothingBackpackSatchelEngineeringFilled
+
+- type: loadout
+ id: StationEngineerDuffel
+ equipment: StationEngineerDuffel
+
+- type: startingGear
+ id: StationEngineerDuffel
+ equipment:
+ back: ClothingBackpackDuffelEngineeringFilled
+
+- type: loadout
+ id: SeniorEngineerBackpack
+ equipment: SeniorEngineerBackpack
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: SeniorEngineerBackpack
+ equipment:
+ back: ClothingBackpackEngineeringFilled
+
+- type: loadout
+ id: SeniorEngineerSatchel
+ equipment: SeniorEngineerSatchel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: SeniorEngineerSatchel
+ equipment:
+ back: ClothingBackpackSatchelEngineeringFilled
+
+- type: loadout
+ id: SeniorEngineerDuffel
+ equipment: SeniorEngineerDuffel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: SeniorEngineerDuffel
+ equipment:
+ back: ClothingBackpackDuffelEngineeringFilled
+
+# OuterClothing
+- type: loadout
+ id: StationEngineerOuterVest
+ equipment: StationEngineerOuterVest
+
+- type: startingGear
+ id: StationEngineerOuterVest
+ equipment:
+ outerClothing: ClothingOuterVestHazard
+
+# ID
+- type: loadout
+ id: StationEngineerPDA
+ equipment: StationEngineerPDA
+
+- type: startingGear
+ id: StationEngineerPDA
+ equipment:
+ id: EngineerPDA
+
+- type: loadout
+ id: SeniorEngineerPDA
+ equipment: SeniorEngineerPDA
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorEngineering
+
+- type: startingGear
+ id: SeniorEngineerPDA
+ equipment:
+ id: SeniorEngineerPDA
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: YellowJumpsuit
+ equipment: YellowJumpsuit
+
+- type: startingGear
+ id: YellowJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitColorYellow
+
+- type: loadout
+ id: YellowJumpskirt
+ equipment: YellowJumpskirt
+
+- type: startingGear
+ id: YellowJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtColorYellow
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: ChemistJumpsuit
+ equipment: ChemistJumpsuit
+
+- type: startingGear
+ id: ChemistJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitChemistry
+
+- type: loadout
+ id: ChemistJumpskirt
+ equipment: ChemistJumpskirt
+
+- type: startingGear
+ id: ChemistJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtChemistry
+
+# Back
+- type: loadout
+ id: ChemistBackpack
+ equipment: ChemistBackpack
+
+- type: startingGear
+ id: ChemistBackpack
+ equipment:
+ back: ClothingBackpackChemistryFilled
+
+- type: loadout
+ id: ChemistSatchel
+ equipment: ChemistSatchel
+
+- type: startingGear
+ id: ChemistSatchel
+ equipment:
+ back: ClothingBackpackSatchelChemistryFilled
+
+- type: loadout
+ id: ChemistDuffel
+ equipment: ChemistDuffel
+
+- type: startingGear
+ id: ChemistDuffel
+ equipment:
+ back: ClothingBackpackDuffelChemistryFilled
+
+# Outer clothing
+- type: loadout
+ id: ChemistLabCoat
+ equipment: ChemistLabCoat
+
+- type: startingGear
+ id: ChemistLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatLabChem
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: ChiefMedicalOfficerJumpsuit
+ equipment: ChiefMedicalOfficerJumpsuit
+
+- type: startingGear
+ id: ChiefMedicalOfficerJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitCMO
+
+- type: loadout
+ id: ChiefMedicalOfficerJumpskirt
+ equipment: ChiefMedicalOfficerJumpskirt
+
+- type: startingGear
+ id: ChiefMedicalOfficerJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtCMO
+
+# Head
+- type: loadout
+ id: ChiefMedicalOfficerBeret
+ equipment: ChiefMedicalOfficerBeret
+
+- type: startingGear
+ id: ChiefMedicalOfficerBeret
+ equipment:
+ head: ClothingHeadHatBeretCmo
+
+# Neck
+- type: loadout
+ id: ChiefMedicalOfficerCloak
+ equipment: ChiefMedicalOfficerCloak
+
+- type: startingGear
+ id: ChiefMedicalOfficerCloak
+ equipment:
+ neck: ClothingCloakCmo
+
+- type: loadout
+ id: ChiefMedicalOfficerMantle
+ equipment: ChiefMedicalOfficerMantle
+
+- type: startingGear
+ id: ChiefMedicalOfficerMantle
+ equipment:
+ neck: ClothingNeckMantleCMO
+
+# Back
+- type: loadout
+ id: ChiefMedicalOfficerBackpack
+ equipment: ChiefMedicalOfficerBackpack
+
+- type: startingGear
+ id: ChiefMedicalOfficerBackpack
+ equipment:
+ back: ClothingBackpackCMOFilled
+
+- type: loadout
+ id: ChiefMedicalOfficerSatchel
+ equipment: ChiefMedicalOfficerSatchel
+
+- type: startingGear
+ id: ChiefMedicalOfficerSatchel
+ equipment:
+ back: ClothingBackpackSatchelCMOFilled
+
+- type: loadout
+ id: ChiefMedicalOfficerDuffel
+ equipment: ChiefMedicalOfficerDuffel
+
+- type: startingGear
+ id: ChiefMedicalOfficerDuffel
+ equipment:
+ back: ClothingBackpackDuffelCMOFilled
+
+# Outer clothing
+- type: loadout
+ id: ChiefMedicalOfficerLabCoat
+ equipment: ChiefMedicalOfficerLabCoat
+
+- type: startingGear
+ id: ChiefMedicalOfficerLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatLabCmo
\ No newline at end of file
--- /dev/null
+# Senior Time
+- type: loadoutEffectGroup
+ id: SeniorPhysician
+ effects:
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobChemist
+ time: 21600 #6 hrs
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobMedicalDoctor
+ time: 21600 #6 hrs
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:DepartmentTimeRequirement
+ department: Medical
+ time: 216000 # 60 hrs
+
+# Head
+
+- type: loadout
+ id: SeniorPhysicianBeret
+ equipment: SeniorPhysicianBeret
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianBeret
+ equipment:
+ head: ClothingHeadHatBeretSeniorPhysician
+
+- type: loadout
+ id: MedicalBeret
+ equipment: MedicalBeret
+
+- type: startingGear
+ id: MedicalBeret
+ equipment:
+ head: ClothingHeadHatBeretMedic
+
+- type: loadout
+ id: BlueSurgeryCap
+ equipment: BlueSurgeryCap
+
+- type: startingGear
+ id: BlueSurgeryCap
+ equipment:
+ head: ClothingHeadHatSurgcapBlue
+
+- type: loadout
+ id: GreenSurgeryCap
+ equipment: GreenSurgeryCap
+
+- type: startingGear
+ id: GreenSurgeryCap
+ equipment:
+ head: ClothingHeadHatSurgcapGreen
+
+- type: loadout
+ id: PurpleSurgeryCap
+ equipment: PurpleSurgeryCap
+
+- type: startingGear
+ id: PurpleSurgeryCap
+ equipment:
+ head: ClothingHeadHatSurgcapPurple
+
+# Jumpsuit
+- type: loadout
+ id: MedicalDoctorJumpsuit
+ equipment: MedicalDoctorJumpsuit
+
+- type: startingGear
+ id: MedicalDoctorJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitMedicalDoctor
+
+- type: loadout
+ id: MedicalDoctorJumpskirt
+ equipment: MedicalDoctorJumpskirt
+
+- type: startingGear
+ id: MedicalDoctorJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtMedicalDoctor
+
+- type: loadout
+ id: SeniorPhysicianJumpsuit
+ equipment: SeniorPhysicianJumpsuit
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitSeniorPhysician
+
+- type: loadout
+ id: SeniorPhysicianJumpskirt
+ equipment: SeniorPhysicianJumpskirt
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtSeniorPhysician
+
+- type: loadout
+ id: MedicalBlueScrubs
+ equipment: MedicalBlueScrubs
+
+- type: startingGear
+ id: MedicalBlueScrubs
+ equipment:
+ jumpsuit: UniformScrubsColorBlue
+
+- type: loadout
+ id: MedicalGreenScrubs
+ equipment: MedicalGreenScrubs
+
+- type: startingGear
+ id: MedicalGreenScrubs
+ equipment:
+ jumpsuit: UniformScrubsColorGreen
+
+- type: loadout
+ id: MedicalPurpleScrubs
+ equipment: MedicalPurpleScrubs
+
+- type: startingGear
+ id: MedicalPurpleScrubs
+ equipment:
+ jumpsuit: UniformScrubsColorPurple
+
+# Back
+- type: loadout
+ id: MedicalDoctorBackpack
+ equipment: MedicalDoctorBackpack
+
+- type: startingGear
+ id: MedicalDoctorBackpack
+ equipment:
+ back: ClothingBackpackMedicalFilled
+
+- type: loadout
+ id: MedicalDoctorSatchel
+ equipment: MedicalDoctorSatchel
+
+- type: startingGear
+ id: MedicalDoctorSatchel
+ equipment:
+ back: ClothingBackpackSatchelMedicalFilled
+
+- type: loadout
+ id: MedicalDoctorDuffel
+ equipment: MedicalDoctorDuffel
+
+- type: startingGear
+ id: MedicalDoctorDuffel
+ equipment:
+ back: ClothingBackpackDuffelMedicalFilled
+
+- type: loadout
+ id: SeniorPhysicianBackpack
+ equipment: SeniorPhysicianBackpack
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianBackpack
+ equipment:
+ back: ClothingBackpackDuffelMedicalFilled
+
+- type: loadout
+ id: SeniorPhysicianSatchel
+ equipment: SeniorPhysicianSatchel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianSatchel
+ equipment:
+ back: ClothingBackpackSatchelMedicalFilled
+
+- type: loadout
+ id: SeniorPhysicianDuffel
+ equipment: SeniorPhysicianDuffel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianDuffel
+ equipment:
+ back: ClothingBackpackDuffelMedicalFilled
+
+# OuterClothing
+
+- type: loadout
+ id: SeniorPhysicianLabCoat
+ equipment: SeniorPhysicianLabCoat
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatLabSeniorPhysician
+
+# ID
+- type: loadout
+ id: MedicalDoctorPDA
+ equipment: MedicalDoctorPDA
+
+- type: startingGear
+ id: MedicalDoctorPDA
+ equipment:
+ id: MedicalPDA
+
+- type: loadout
+ id: SeniorPhysicianPDA
+ equipment: SeniorPhysicianPDA
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorPhysician
+
+- type: startingGear
+ id: SeniorPhysicianPDA
+ equipment:
+ id: SeniorPhysicianPDA
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: WhiteJumpsuit
+ equipment: WhiteJumpsuit
+
+- type: startingGear
+ id: WhiteJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitColorWhite
+
+- type: loadout
+ id: WhiteJumpskirt
+ equipment: WhiteJumpskirt
+
+- type: startingGear
+ id: WhiteJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtColorWhite
\ No newline at end of file
--- /dev/null
+# Head
+- type: loadout
+ id: ParamedicHead
+ equipment: ParamedicHead
+
+- type: startingGear
+ id: ParamedicHead
+ equipment:
+ head: ClothingHeadHatParamedicsoft
+
+# Jumpsuit
+- type: loadout
+ id: ParamedicJumpsuit
+ equipment: ParamedicJumpsuit
+
+- type: startingGear
+ id: ParamedicJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitParamedic
+
+- type: loadout
+ id: ParamedicJumpskirt
+ equipment: ParamedicJumpskirt
+
+- type: startingGear
+ id: ParamedicJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtParamedic
+
+# Back
+- type: loadout
+ id: ParamedicBackpack
+ equipment: ParamedicBackpack
+
+- type: startingGear
+ id: ParamedicBackpack
+ equipment:
+ back: ClothingBackpackParamedicFilled
+
+- type: loadout
+ id: ParamedicSatchel
+ equipment: ParamedicSatchel
+
+- type: startingGear
+ id: ParamedicSatchel
+ equipment:
+ back: ClothingBackpackSatchelParamedicFilled
+
+- type: loadout
+ id: ParamedicDuffel
+ equipment: ParamedicDuffel
+
+- type: startingGear
+ id: ParamedicDuffel
+ equipment:
+ back: ClothingBackpackDuffelParamedicFilled
+
+# Outer clothing
+- type: loadout
+ id: ParamedicWindbreaker
+ equipment: ParamedicWindbreaker
+
+- type: startingGear
+ id: ParamedicWindbreaker
+ equipment:
+ outerClothing: ClothingOuterCoatParamedicWB
\ No newline at end of file
--- /dev/null
+# Head
+
+- type: loadout
+ id: ResearchDirectorBeret
+ equipment: ScientificBeret
+
+# Neck
+
+- type: loadout
+ id: ResearchDirectorMantle
+ equipment: ResearchDirectorMantle
+
+- type: startingGear
+ id: ResearchDirectorMantle
+ equipment:
+ neck: ClothingNeckMantleRD
+
+- type: loadout
+ id: ResearchDirectorCloak
+ equipment: ResearchDirectorCloak
+
+- type: startingGear
+ id: ResearchDirectorCloak
+ equipment:
+ neck: ClothingNeckCloakRd
+
+# Jumpsuit
+- type: loadout
+ id: ResearchDirectorJumpsuit
+ equipment: ResearchDirectorJumpsuit
+
+- type: startingGear
+ id: ResearchDirectorJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitResearchDirector
+
+- type: loadout
+ id: ResearchDirectorJumpskirt
+ equipment: ResearchDirectorJumpskirt
+
+- type: startingGear
+ id: ResearchDirectorJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtResearchDirector
+
+# Back
+- type: loadout
+ id: ResearchDirectorBackpack
+ equipment: ResearchDirectorBackpack
+
+- type: startingGear
+ id: ResearchDirectorBackpack
+ equipment:
+ back: ClothingBackpackResearchDirectorFilled
+
+- type: loadout
+ id: ResearchDirectorSatchel
+ equipment: ResearchDirectorSatchel
+
+- type: startingGear
+ id: ResearchDirectorSatchel
+ equipment:
+ back: ClothingBackpackSatchelResearchDirectorFilled
+
+- type: loadout
+ id: ResearchDirectorDuffel
+ equipment: ResearchDirectorDuffel
+
+- type: startingGear
+ id: ResearchDirectorDuffel
+ equipment:
+ back: ClothingBackpackDuffelResearchDirectorFilled
+
+# OuterClothing
+- type: loadout
+ id: ResearchDirectorLabCoat
+ equipment: ResearchDirectorLabCoat
+
+- type: startingGear
+ id: ResearchDirectorLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatRD
\ No newline at end of file
--- /dev/null
+# Senior Time
+- type: loadoutEffectGroup
+ id: SeniorResearcher
+ effects:
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:DepartmentTimeRequirement
+ department: Science
+ time: 216000 #60 hrs
+
+# Head
+
+- type: loadout
+ id: ScientificBeret
+ equipment: ScientificBeret
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: ScientificBeret
+ equipment:
+ head: ClothingHeadHatBeretRND
+
+# Neck
+
+- type: loadout
+ id: ScientistTie
+ equipment: ScientistTie
+
+- type: startingGear
+ id: ScientistTie
+ equipment:
+ neck: ClothingNeckTieSci
+
+# Jumpsuit
+- type: loadout
+ id: ScientistJumpsuit
+ equipment: ScientistJumpsuit
+
+- type: startingGear
+ id: ScientistJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitScientist
+
+- type: loadout
+ id: ScientistJumpskirt
+ equipment: ScientistJumpskirt
+
+- type: startingGear
+ id: ScientistJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtScientist
+
+- type: loadout
+ id: SeniorResearcherJumpsuit
+ equipment: SeniorResearcherJumpsuit
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitSeniorResearcher
+
+- type: loadout
+ id: SeniorResearcherJumpskirt
+ equipment: SeniorResearcherJumpskirt
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtSeniorResearcher
+
+# Back
+- type: loadout
+ id: ScientistBackpack
+ equipment: ScientistBackpack
+
+- type: startingGear
+ id: ScientistBackpack
+ equipment:
+ back: ClothingBackpackScienceFilled
+
+- type: loadout
+ id: ScientistSatchel
+ equipment: ScientistSatchel
+
+- type: startingGear
+ id: ScientistSatchel
+ equipment:
+ back: ClothingBackpackSatchelScienceFilled
+
+- type: loadout
+ id: ScientistDuffel
+ equipment: ScientistDuffel
+
+- type: startingGear
+ id: ScientistDuffel
+ equipment:
+ back: ClothingBackpackDuffelScienceFilled
+
+- type: loadout
+ id: SeniorResearcherBackpack
+ equipment: SeniorResearcherBackpack
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherBackpack
+ equipment:
+ back: ClothingBackpackScienceFilled
+
+- type: loadout
+ id: SeniorResearcherSatchel
+ equipment: SeniorResearcherSatchel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherSatchel
+ equipment:
+ back: ClothingBackpackSatchelScienceFilled
+
+- type: loadout
+ id: SeniorResearcherDuffel
+ equipment: SeniorResearcherDuffel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherDuffel
+ equipment:
+ back: ClothingBackpackDuffelScienceFilled
+
+# OuterClothing
+- type: loadout
+ id: RegularLabCoat
+ equipment: RegularLabCoat
+
+- type: startingGear
+ id: RegularLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatLab
+
+- type: loadout
+ id: ScienceLabCoat
+ equipment: ScienceLabCoat
+
+- type: startingGear
+ id: ScienceLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatRnd
+
+- type: loadout
+ id: SeniorResearcherLabCoat
+ equipment: SeniorResearcherLabCoat
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherLabCoat
+ equipment:
+ outerClothing: ClothingOuterCoatLabSeniorResearcher
+
+# ID
+- type: loadout
+ id: ScientistPDA
+ equipment: ScientistPDA
+
+- type: startingGear
+ id: ScientistPDA
+ equipment:
+ id: SciencePDA
+
+- type: loadout
+ id: SeniorResearcherPDA
+ equipment: SeniorResearcherPDA
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorResearcher
+
+- type: startingGear
+ id: SeniorResearcherPDA
+ equipment:
+ id: SeniorResearcherPDA
--- /dev/null
+# Head
+- type: loadout
+ id: DetectiveFedora
+ equipment: DetectiveFedora
+
+- type: startingGear
+ id: DetectiveFedora
+ equipment:
+ head: ClothingHeadHatFedoraBrown
+
+- type: loadout
+ id: DetectiveFedoraGrey
+ equipment: DetectiveFedoraGrey
+
+- type: startingGear
+ id: DetectiveFedoraGrey
+ equipment:
+ head: ClothingHeadHatFedoraGrey
+
+# Neck
+- type: loadout
+ id: DetectiveTie
+ equipment: DetectiveTie
+
+- type: startingGear
+ id: DetectiveTie
+ equipment:
+ neck: ClothingNeckTieDet
+
+# Jumpsuit
+- type: loadout
+ id: DetectiveJumpsuit
+ equipment: DetectiveJumpsuit
+
+- type: startingGear
+ id: DetectiveJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitDetective
+
+- type: loadout
+ id: DetectiveJumpskirt
+ equipment: DetectiveJumpskirt
+
+- type: startingGear
+ id: DetectiveJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtDetective
+
+- type: loadout
+ id: NoirJumpsuit
+ equipment: NoirJumpsuit
+
+- type: startingGear
+ id: NoirJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitDetectiveGrey
+
+- type: loadout
+ id: NoirJumpskirt
+ equipment: NoirJumpskirt
+
+- type: startingGear
+ id: NoirJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtDetectiveGrey
+
+# Back
+- type: loadout
+ id: DetectiveBackpack
+ equipment: DetectiveBackpack
+
+- type: startingGear
+ id: DetectiveBackpack
+ equipment:
+ back: ClothingBackpackSecurityFilledDetective
+
+- type: loadout
+ id: DetectiveSatchel
+ equipment: DetectiveSatchel
+
+- type: startingGear
+ id: DetectiveSatchel
+ equipment:
+ back: ClothingBackpackSatchelSecurityFilledDetective
+
+- type: loadout
+ id: DetectiveDuffel
+ equipment: DetectiveDuffel
+
+- type: startingGear
+ id: DetectiveDuffel
+ equipment:
+ back: ClothingBackpackDuffelSecurityFilledDetective
+
+# OuterClothing
+- type: loadout
+ id: DetectiveArmorVest
+ equipment: DetectiveArmorVest
+
+- type: startingGear
+ id: DetectiveArmorVest
+ equipment:
+ outerClothing: ClothingOuterVestDetective
+
+- type: loadout
+ id: DetectiveCoat
+ equipment: DetectiveCoat
+
+- type: startingGear
+ id: DetectiveCoat
+ equipment:
+ outerClothing: ClothingOuterCoatDetective
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: HeadofSecurityJumpsuit
+ equipment: HeadofSecurityJumpsuit
+
+- type: startingGear
+ id: HeadofSecurityJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitHoS
+
+- type: loadout
+ id: HeadofSecurityJumpskirt
+ equipment: HeadofSecurityJumpskirt
+
+- type: startingGear
+ id: HeadofSecurityJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtHoS
+
+- type: loadout
+ id: HeadofSecurityTurtleneck
+ equipment: HeadofSecurityTurtleneck
+
+- type: startingGear
+ id: HeadofSecurityTurtleneck
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitHoSAlt
+
+- type: loadout
+ id: HeadofSecurityTurtleneckSkirt
+ equipment: HeadofSecurityTurtleneckSkirt
+
+- type: startingGear
+ id: HeadofSecurityTurtleneckSkirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtHoSAlt
+
+- type: loadout
+ id: HeadofSecurityFormalSuit
+ equipment: HeadofSecurityFormalSuit
+
+- type: startingGear
+ id: HeadofSecurityFormalSuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitHosFormal
+
+- type: loadout
+ id: HeadofSecurityFormalSkirt
+ equipment: HeadofSecurityFormalSkirt
+
+- type: startingGear
+ id: HeadofSecurityFormalSkirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtHosFormal
+
+# Head
+- type: loadout
+ id: HeadofSecurityHead
+ equipment: HeadofSecurityHead
+
+- type: startingGear
+ id: HeadofSecurityHead
+ equipment:
+ head: ClothingHeadHatHoshat
+
+- type: loadout
+ id: HeadofSecurityBeret
+ equipment: HeadofSecurityBeret
+
+- type: startingGear
+ id: HeadofSecurityBeret
+ equipment:
+ head: ClothingHeadHatBeretHoS
+
+# Neck
+- type: loadout
+ id: HeadofSecurityCloak
+ equipment: HeadofSecurityCloak
+
+- type: startingGear
+ id: HeadofSecurityCloak
+ equipment:
+ neck: ClothingNeckCloakHos
+
+- type: loadout
+ id: HeadofSecurityMantle
+ equipment: HeadofSecurityMantle
+
+- type: startingGear
+ id: HeadofSecurityMantle
+ equipment:
+ neck: ClothingNeckMantleHOS
+
+# OuterClothing
+- type: loadout
+ id: HeadofSecurityCoat
+ equipment: HeadofSecurityCoat
+
+- type: startingGear
+ id: HeadofSecurityCoat
+ equipment:
+ outerClothing: ClothingOuterCoatHoSTrench
+
+- type: loadout
+ id: HeadofSecurityWinterCoat
+ equipment: HeadofSecurityWinterCoat
+
+- type: startingGear
+ id: HeadofSecurityWinterCoat
+ equipment:
+ outerClothing: ClothingOuterWinterHoS
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: RedJumpsuit
+ equipment: RedJumpsuit
+
+- type: startingGear
+ id: RedJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitColorRed
+
+- type: loadout
+ id: RedJumpskirt
+ equipment: RedJumpskirt
+
+- type: startingGear
+ id: RedJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtColorRed
\ No newline at end of file
--- /dev/null
+# Senior Time
+- type: loadoutEffectGroup
+ id: SeniorOfficer
+ effects:
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:RoleTimeRequirement
+ role: JobWarden
+ time: 21600 #6 hrs
+ - !type:JobRequirementLoadoutEffect
+ requirement:
+ !type:DepartmentTimeRequirement
+ department: Security
+ time: 216000 # 60 hrs
+
+# Head
+- type: loadout
+ id: SecurityHelmet
+ equipment: SecurityHelmet
+
+- type: startingGear
+ id: SecurityHelmet
+ equipment:
+ head: ClothingHeadHelmetBasic
+
+- type: loadout
+ id: SecurityHat
+ equipment: SecurityHat
+
+- type: startingGear
+ id: SecurityHat
+ equipment:
+ head: ClothingHeadHatSecsoft
+
+- type: loadout
+ id: SecurityBeret
+ equipment: SecurityBeret
+
+- type: startingGear
+ id: SecurityBeret
+ equipment:
+ head: ClothingHeadHatBeretSecurity
+
+# Jumpsuit
+- type: loadout
+ id: SecurityJumpsuit
+ equipment: SecurityJumpsuit
+
+- type: startingGear
+ id: SecurityJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitSec
+
+- type: loadout
+ id: SecurityJumpskirt
+ equipment: SecurityJumpskirt
+
+- type: startingGear
+ id: SecurityJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtSec
+
+- type: loadout
+ id: SeniorOfficerJumpsuit
+ equipment: SeniorOfficerJumpsuit
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorOfficer
+
+- type: startingGear
+ id: SeniorOfficerJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitSeniorOfficer
+
+- type: loadout
+ id: SeniorOfficerJumpskirt
+ equipment: SeniorOfficerJumpskirt
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorOfficer
+
+- type: startingGear
+ id: SeniorOfficerJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtSeniorOfficer
+
+# Back
+- type: loadout
+ id: SecurityBackpack
+ equipment: SecurityBackpack
+
+- type: startingGear
+ id: SecurityBackpack
+ equipment:
+ back: ClothingBackpackSecurityFilled
+
+- type: loadout
+ id: SecuritySatchel
+ equipment: SecuritySatchel
+
+- type: startingGear
+ id: SecuritySatchel
+ equipment:
+ back: ClothingBackpackSatchelSecurityFilled
+
+- type: loadout
+ id: SecurityDuffel
+ equipment: SecurityDuffel
+
+- type: startingGear
+ id: SecurityDuffel
+ equipment:
+ back: ClothingBackpackDuffelSecurityFilled
+
+- type: loadout
+ id: SeniorOfficerBackpack
+ equipment: SeniorOfficerBackpack
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorOfficer
+
+- type: startingGear
+ id: SeniorOfficerBackpack
+ equipment:
+ back: ClothingBackpackSecurityFilled
+
+- type: loadout
+ id: SeniorOfficerSatchel
+ equipment: SeniorOfficerSatchel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorOfficer
+
+- type: startingGear
+ id: SeniorOfficerSatchel
+ equipment:
+ back: ClothingBackpackSatchelSecurityFilled
+
+- type: loadout
+ id: SeniorOfficerDuffel
+ equipment: SeniorOfficerDuffel
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorOfficer
+
+- type: startingGear
+ id: SeniorOfficerDuffel
+ equipment:
+ back: ClothingBackpackDuffelSecurityFilled
+
+# PDA
+- type: loadout
+ id: SecurityPDA
+ equipment: SecurityPDA
+
+- type: startingGear
+ id: SecurityPDA
+ equipment:
+ id: SecurityPDA
+
+- type: loadout
+ id: SeniorOfficerPDA
+ equipment: SeniorOfficerPDA
+ effects:
+ - !type:GroupLoadoutEffect
+ proto: SeniorOfficer
+
+- type: startingGear
+ id: SeniorOfficerPDA
+ equipment:
+ id: SeniorOfficerPDA
--- /dev/null
+# Head
+- type: loadout
+ id: WardenHead
+ equipment: WardenHead
+
+- type: startingGear
+ id: WardenHead
+ equipment:
+ head: ClothingHeadHatWarden
+
+- type: loadout
+ id: WardenBeret
+ equipment: WardenBeret
+
+- type: startingGear
+ id: WardenBeret
+ equipment:
+ head: ClothingHeadHatBeretWarden
+
+# Jumpsuit
+- type: loadout
+ id: WardenJumpsuit
+ equipment: WardenJumpsuit
+
+- type: startingGear
+ id: WardenJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitWarden
+
+- type: loadout
+ id: WardenJumpskirt
+ equipment: WardenJumpskirt
+
+- type: startingGear
+ id: WardenJumpskirt
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtWarden
+
+# OuterClothing
+- type: loadout
+ id: WardenCoat
+ equipment: WardenCoat
+
+- type: startingGear
+ id: WardenCoat
+ equipment:
+ outerClothing: ClothingOuterCoatWarden
+
+- type: loadout
+ id: WardenArmoredWinterCoat
+ equipment: WardenArmoredWinterCoat
+
+- type: startingGear
+ id: WardenArmoredWinterCoat
+ equipment:
+ outerClothing: ClothingOuterWinterWarden
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: BoxerShorts
+ equipment: BoxerShorts
+
+- type: startingGear
+ id: BoxerShorts
+ equipment:
+ jumpsuit: UniformShortsRed
+
+- type: loadout
+ id: BoxerShortsWithTop
+ equipment: BoxerShortsWithTop
+
+- type: startingGear
+ id: BoxerShortsWithTop
+ equipment:
+ jumpsuit: UniformShortsRedWithTop
+
+# Gloves
+- type: loadout
+ id: RedBoxingGloves
+ equipment: RedBoxingGloves
+
+- type: startingGear
+ id: RedBoxingGloves
+ equipment:
+ gloves: ClothingHandsGlovesBoxingRed
+
+- type: loadout
+ id: BlueBoxingGloves
+ equipment: BlueBoxingGloves
+
+- type: startingGear
+ id: BlueBoxingGloves
+ equipment:
+ gloves: ClothingHandsGlovesBoxingBlue
+
+- type: loadout
+ id: GreenBoxingGloves
+ equipment: GreenBoxingGloves
+
+- type: startingGear
+ id: GreenBoxingGloves
+ equipment:
+ gloves: ClothingHandsGlovesBoxingGreen
+
+- type: loadout
+ id: YellowBoxingGloves
+ equipment: YellowBoxingGloves
+
+- type: startingGear
+ id: YellowBoxingGloves
+ equipment:
+ gloves: ClothingHandsGlovesBoxingYellow
\ No newline at end of file
--- /dev/null
+# Jumpsuit
+- type: loadout
+ id: ReporterJumpsuit
+ equipment: ReporterJumpsuit
+
+- type: startingGear
+ id: ReporterJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitReporter
+
+- type: loadout
+ id: JournalistJumpsuit
+ equipment: JournalistJumpsuit
+
+- type: startingGear
+ id: JournalistJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitJournalist
\ No newline at end of file
--- /dev/null
+- type: loadout
+ id: PlushieLizard
+ equipment: PlushieLizard
+
+- type: startingGear
+ id: PlushieLizard
+ storage:
+ back:
+ - PlushieLizard
+
+- type: loadout
+ id: ClothingNeckLGBTPin
+ equipment: ClothingNeckLGBTPin
+
+- type: startingGear
+ id: ClothingNeckLGBTPin
+ storage:
+ back:
+ - ClothingNeckLGBTPin
+
+- type: loadout
+ id: ClothingNeckAromanticPin
+ equipment: ClothingNeckAromanticPin
+
+- type: startingGear
+ id: ClothingNeckAromanticPin
+ storage:
+ back:
+ - ClothingNeckAromanticPin
+
+- type: loadout
+ id: ClothingNeckAsexualPin
+ equipment: ClothingNeckAsexualPin
+
+- type: startingGear
+ id: ClothingNeckAsexualPin
+ storage:
+ back:
+ - ClothingNeckAsexualPin
+
+- type: loadout
+ id: ClothingNeckBisexualPin
+ equipment: ClothingNeckBisexualPin
+
+- type: startingGear
+ id: ClothingNeckBisexualPin
+ storage:
+ back:
+ - ClothingNeckBisexualPin
+
+- type: loadout
+ id: ClothingNeckIntersexPin
+ equipment: ClothingNeckIntersexPin
+
+- type: startingGear
+ id: ClothingNeckIntersexPin
+ storage:
+ back:
+ - ClothingNeckIntersexPin
+
+- type: loadout
+ id: ClothingNeckLesbianPin
+ equipment: ClothingNeckLesbianPin
+
+- type: startingGear
+ id: ClothingNeckLesbianPin
+ storage:
+ back:
+ - ClothingNeckLesbianPin
+
+- type: loadout
+ id: ClothingNeckNonBinaryPin
+ equipment: ClothingNeckNonBinaryPin
+
+- type: startingGear
+ id: ClothingNeckNonBinaryPin
+ storage:
+ back:
+ - ClothingNeckNonBinaryPin
+
+- type: loadout
+ id: ClothingNeckPansexualPin
+ equipment: ClothingNeckPansexualPin
+
+- type: startingGear
+ id: ClothingNeckPansexualPin
+ storage:
+ back:
+ - ClothingNeckPansexualPin
+
+- type: loadout
+ id: ClothingNeckTransPin
+ equipment: ClothingNeckTransPin
+
+- type: startingGear
+ id: ClothingNeckTransPin
+ storage:
+ back:
+ - ClothingNeckTransPin
--- /dev/null
+# Miscellaneous
+- type: loadoutGroup
+ id: Trinkets
+ name: loadout-group-trinkets
+ minLimit: 0
+ maxLimit: 3
+ loadouts:
+ - PlushieLizard
+ - ClothingNeckLGBTPin
+ - ClothingNeckAromanticPin
+ - ClothingNeckAsexualPin
+ - ClothingNeckBisexualPin
+ - ClothingNeckIntersexPin
+ - ClothingNeckLesbianPin
+ - ClothingNeckNonBinaryPin
+ - ClothingNeckPansexualPin
+ - ClothingNeckTransPin
+
+# Command
+- type: loadoutGroup
+ id: CaptainHead
+ name: loadout-group-captain-head
+ minLimit: 0
+ loadouts:
+ - CaptainHead
+ - CaptainCap
+
+- type: loadoutGroup
+ id: CaptainJumpsuit
+ name: loadout-group-captain-jumpsuit
+ loadouts:
+ - CaptainJumpsuit
+ - CaptainJumpskirt
+ - CaptainFormalSuit
+ - CaptainFormalSkirt
+
+- type: loadoutGroup
+ id: CaptainNeck
+ name: loadout-group-captain-neck
+ minLimit: 0
+ loadouts:
+ - CaptainCloak
+ - CaptainCloakFormal
+ - CaptainMantle
+
+- type: loadoutGroup
+ id: CaptainBackpack
+ name: loadout-group-captain-backpack
+ loadouts:
+ - CaptainBackpack
+ - CaptainSatchel
+ - CaptainDuffel
+
+- type: loadoutGroup
+ id: HoPHead
+ name: loadout-group-hop-head
+ minLimit: 0
+ loadouts:
+ - HoPHead
+
+- type: loadoutGroup
+ id: HoPJumpsuit
+ name: loadout-group-hop-jumpsuit
+ loadouts:
+ - HoPJumpsuit
+ - HoPJumpskirt
+
+- type: loadoutGroup
+ id: HoPNeck
+ name: loadout-group-hop-neck
+ minLimit: 0
+ loadouts:
+ - HoPCloak
+ - HoPMantle
+
+- type: loadoutGroup
+ id: HoPBackpack
+ name: loadout-group-hop-backpack
+ loadouts:
+ - HoPBackpack
+ - HoPSatchel
+ - HoPDuffel
+ - HoPBackpackIan
+
+# Civilian
+- type: loadoutGroup
+ id: PassengerJumpsuit
+ name: loadout-group-passenger-jumpsuit
+ loadouts:
+ - GreyJumpsuit
+ - GreyJumpskirt
+
+- type: loadoutGroup
+ id: PassengerFace
+ name: loadout-group-passenger-mask
+ minLimit: 0
+ loadouts:
+ - PassengerFace
+
+- type: loadoutGroup
+ id: PassengerGloves
+ name: loadout-group-passenger-gloves
+ minLimit: 0
+ loadouts:
+ - PassengerGloves
+
+- type: loadoutGroup
+ id: CommonBackpack
+ name: loadout-group-passenger-backpack
+ loadouts:
+ - CommonBackpack
+ - CommonSatchel
+ - CommonDuffel
+
+- type: loadoutGroup
+ id: BartenderHead
+ name: loadout-group-bartender-head
+ minLimit: 0
+ loadouts:
+ - BartenderHead
+ - BartenderBowler
+
+- type: loadoutGroup
+ id: BartenderJumpsuit
+ name: loadout-group-bartender-jumpsuit
+ loadouts:
+ - BartenderJumpsuit
+ - BartenderJumpskirt
+ - BartenderJumpsuitPurple
+
+- type: loadoutGroup
+ id: BartenderOuterClothing
+ name: loadout-group-bartender-outerclothing
+ minLimit: 0
+ loadouts:
+ - BartenderVest
+ - BartenderApron
+
+- type: loadoutGroup
+ id: ChefHead
+ name: loadout-group-chef-head
+ minLimit: 0
+ loadouts:
+ - ChefHead
+
+- type: loadoutGroup
+ id: ChefMask
+ name: loadout-group-chef-mask
+ minLimit: 0
+ loadouts:
+ - ChefMask
+
+- type: loadoutGroup
+ id: ChefJumpsuit
+ name: loadout-group-chef-jumpsuit
+ loadouts:
+ - ChefJumpsuit
+ - ChefJumpskirt
+
+- type: loadoutGroup
+ id: ChefOuterClothing
+ name: loadout-group-chef-outerclothing
+ minLimit: 0
+ loadouts:
+ - ChefApron
+ - ChefJacket
+
+- type: loadoutGroup
+ id: LibrarianJumpsuit
+ name: loadout-group-librarian-jumpsuit
+ loadouts:
+ - LibrarianJumpsuit
+ - LibrarianJumpskirt
+ - CuratorJumpsuit
+ - CuratorJumpskirt
+
+- type: loadoutGroup
+ id: LawyerJumpsuit
+ name: loadout-group-lawyer-jumpsuit
+ loadouts:
+ - LawyerJumpsuit
+ - LawyerJumpskirt
+ - LawyerJumpsuitBlue
+ - LawyerJumpskirtBlue
+ - LawyerJumpsuitPurple
+ - LawyerJumpskirtPurple
+ - LawyerJumpsuitRed
+ - LawyerJumpskirtRed
+ - LawyerJumpsuitGood
+ - LawyerJumpskirtGood
+
+- type: loadoutGroup
+ id: LawyerNeck
+ name: loadout-group-lawyer-neck
+ minLimit: 0
+ loadouts:
+ - LawyerNeck
+
+- type: loadoutGroup
+ id: ChaplainHead
+ name: loadout-group-chaplain-head
+ minLimit: 0
+ loadouts:
+ - ChaplainHead
+ - ChaplainPlagueHat
+ - ChaplainWitchHat
+ - ChaplainWitchHatAlt
+
+- type: loadoutGroup
+ id: ChaplainMask
+ name: loadout-group-chaplain-mask
+ minLimit: 0
+ loadouts:
+ - ChaplainMask
+
+- type: loadoutGroup
+ id: ChaplainJumpsuit
+ name: loadout-group-chaplain-jumpsuit
+ loadouts:
+ - ChaplainJumpsuit
+ - ChaplainJumpskirt
+ - ChaplainRobesLight
+ - ChaplainRobesDark
+
+- type: loadoutGroup
+ id: ChaplainBackpack
+ name: loadout-group-chaplain-backpack
+ loadouts:
+ - ChaplainBackpack
+ - ChaplainSatchel
+ - ChaplainDuffel
+
+- type: loadoutGroup
+ id: ChaplainOuterClothing
+ name: loadout-group-chaplain-outerclothing
+ minLimit: 0
+ loadouts:
+ - ChaplainPlagueSuit
+ - ChaplainNunRobe
+ - ChaplainBlackHoodie
+ - ChaplainHoodie
+
+- type: loadoutGroup
+ id: ChaplainNeck
+ name: loadout-group-chaplain-neck
+ minLimit: 0
+ loadouts:
+ - ChaplainNeck
+
+- type: loadoutGroup
+ id: JanitorHead
+ name: loadout-group-janitor-head
+ minLimit: 0
+ loadouts:
+ - JanitorHead
+
+- type: loadoutGroup
+ id: JanitorJumpsuit
+ name: loadout-group-janitor-jumpsuit
+ loadouts:
+ - JanitorJumpsuit
+ - JanitorJumpskirt
+
+- type: loadoutGroup
+ id: BotanistHead
+ name: loadout-group-botanist-head
+ minLimit: 0
+ loadouts:
+ - BotanistHead
+ - BotanistBandana
+
+- type: loadoutGroup
+ id: BotanistJumpsuit
+ name: loadout-group-botanist-jumpsuit
+ loadouts:
+ - BotanistJumpsuit
+ - BotanistJumpskirt
+ - BotanistOveralls
+
+- type: loadoutGroup
+ id: BotanistBackpack
+ name: loadout-group-botanist-backpack
+ loadouts:
+ - BotanistBackpack
+ - BotanistSatchel
+ - BotanistDuffel
+
+- type: loadoutGroup
+ id: BotanistOuterClothing
+ name: loadout-group-botanist-outerclothing
+ minLimit: 0
+ loadouts:
+ - BotanistApron
+
+- type: loadoutGroup
+ id: ClownHead
+ name: loadout-group-clown-head
+ minLimit: 0
+ loadouts:
+ - JesterHat
+
+- type: loadoutGroup
+ id: ClownJumpsuit
+ name: loadout-group-clown-jumpsuit
+ loadouts:
+ - ClownSuit
+ - JesterSuit
+
+- type: loadoutGroup
+ id: ClownBackpack
+ name: loadout-group-clown-backpack
+ loadouts:
+ - ClownBackpack
+ - ClownSatchel
+ - ClownDuffel
+
+- type: loadoutGroup
+ id: ClownShoes
+ name: loadout-group-clown-shoes
+ loadouts:
+ - ClownShoes
+ - JesterShoes
+
+- type: loadoutGroup
+ id: MimeHead
+ name: loadout-group-mime-head
+ minLimit: 0
+ loadouts:
+ - MimeHead
+ - MimeFrenchBeret
+ - MimeCap
+
+- type: loadoutGroup
+ id: MimeMask
+ name: loadout-group-mime-mask
+ minLimit: 0
+ loadouts:
+ - MimeMask
+ - MimeMaskSad
+ - MimeMaskScared
+
+- type: loadoutGroup
+ id: MimeJumpsuit
+ name: loadout-group-mime-jumpsuit
+ loadouts:
+ - MimeJumpsuit
+ - MimeJumpskirt
+
+- type: loadoutGroup
+ id: MimeBackpack
+ name: loadout-group-mime-backpack
+ loadouts:
+ - MimeBackpack
+ - MimeSatchel
+ - MimeDuffel
+
+- type: loadoutGroup
+ id: MusicianBackpack
+ name: loadout-group-musician-backpack
+ loadouts:
+ - MusicianBackpack
+ - MusicianSatchel
+ - MusicianDuffel
+
+# Cargo
+- type: loadoutGroup
+ id: QuartermasterHead
+ name: loadout-group-quartermaster-head
+ minLimit: 0
+ loadouts:
+ - QuartermasterHead
+ - QuartermasterBeret
+
+- type: loadoutGroup
+ id: QuartermasterJumpsuit
+ name: loadout-group-quartermaster-jumpsuit
+ loadouts:
+ - QuartermasterJumpsuit
+ - QuartermasterJumpskirt
+ - QuartermasterTurtleneck
+ - QuartermasterTurtleneckSkirt
+ - QuartermasterFormalSuit
+
+- type: loadoutGroup
+ id: QuartermasterBackpack
+ name: loadout-group-quartermaster-backpack
+ loadouts:
+ - QuartermasterBackpack
+ - QuartermasterSatchel
+ - QuartermasterDuffel
+
+- type: loadoutGroup
+ id: QuartermasterNeck
+ name: loadout-group-quartermaster-neck
+ minLimit: 0
+ loadouts:
+ - QuartermasterCloak
+ - QuartermasterMantle
+
+- type: loadoutGroup
+ id: CargoTechnicianHead
+ name: loadout-group-cargo-technician-head
+ minLimit: 0
+ loadouts:
+ - CargoTechnicianHead
+
+- type: loadoutGroup
+ id: CargoTechnicianJumpsuit
+ name: loadout-group-cargo-technician-jumpsuit
+ loadouts:
+ - CargoTechnicianJumpsuit
+ - CargoTechnicianJumpskirt
+
+- type: loadoutGroup
+ id: CargoTechnicianBackpack
+ name: loadout-group-cargo-technician-backpack
+ loadouts:
+ - CargoTechnicianBackpack
+ - CargoTechnicianSatchel
+ - CargoTechnicianDuffel
+
+- type: loadoutGroup
+ id: SalvageSpecialistBackpack
+ name: loadout-group-salvage-specialist-backpack
+ loadouts:
+ - SalvageSpecialistBackpack
+ - SalvageSpecialistSatchel
+ - SalvageSpecialistDuffel
+
+# Engineering
+- type: loadoutGroup
+ id: ChiefEngineerHead
+ name: loadout-group-chief-engineer-head
+ minLimit: 0
+ loadouts:
+ - ChiefEngineerHead
+ - ChiefEngineerBeret
+
+- type: loadoutGroup
+ id: ChiefEngineerJumpsuit
+ name: loadout-group-chief-engineer-jumpsuit
+ loadouts:
+ - ChiefEngineerJumpsuit
+ - ChiefEngineerJumpskirt
+ - ChiefEngineerTurtleneck
+ - ChiefEngineerTurtleneckSkirt
+
+- type: loadoutGroup
+ id: ChiefEngineerBackpack
+ name: loadout-group-chief-engineer-backpack
+ loadouts:
+ - ChiefEngineerBackpack
+ - ChiefEngineerSatchel
+ - ChiefEngineerDuffel
+
+- type: loadoutGroup
+ id: ChiefEngineerNeck
+ name: loadout-group-chief-engineer-neck
+ minLimit: 0
+ loadouts:
+ - ChiefEngineerCloak
+ - ChiefEngineerMantle
+
+- type: loadoutGroup
+ id: TechnicalAssistantJumpsuit
+ name: loadout-group-technical-assistant-jumpsuit
+ loadouts:
+ - YellowJumpsuit
+ - YellowJumpskirt
+
+- type: loadoutGroup
+ id: StationEngineerHead
+ name: loadout-group-station-engineer-head
+ loadouts:
+ - StationEngineerHead
+ - SeniorEngineerBeret
+
+- type: loadoutGroup
+ id: StationEngineerJumpsuit
+ name: loadout-group-station-engineer-jumpsuit
+ loadouts:
+ - StationEngineerJumpsuit
+ - StationEngineerJumpskirt
+ - StationEngineerHazardsuit
+ - SeniorEngineerJumpsuit
+ - SeniorEngineerJumpskirt
+
+- type: loadoutGroup
+ id: StationEngineerBackpack
+ name: loadout-group-station-engineer-backpack
+ loadouts:
+ - StationEngineerBackpack
+ - StationEngineerSatchel
+ - StationEngineerDuffel
+ - SeniorEngineerBackpack
+ - SeniorEngineerSatchel
+ - SeniorEngineerDuffel
+
+- type: loadoutGroup
+ id: StationEngineerOuterClothing
+ name: loadout-group-station-engineer-outerclothing
+ minLimit: 0
+ loadouts:
+ - StationEngineerOuterVest
+
+- type: loadoutGroup
+ id: StationEngineerID
+ name: loadout-group-station-engineer-id
+ loadouts:
+ - StationEngineerPDA
+ - SeniorEngineerPDA
+
+- type: loadoutGroup
+ id: AtmosphericTechnicianJumpsuit
+ name: loadout-group-atmospheric-technician-jumpsuit
+ loadouts:
+ - AtmosphericTechnicianJumpsuit
+ - AtmosphericTechnicianJumpskirt
+ - AtmosphericTechnicianJumpsuitCasual
+
+- type: loadoutGroup
+ id: AtmosphericTechnicianBackpack
+ name: loadout-group-atmospheric-technician-backpack
+ loadouts:
+ - AtmosphericTechnicianBackpack
+ - AtmosphericTechnicianSatchel
+ - AtmosphericTechnicianDuffel
+
+# Science
+- type: loadoutGroup
+ id: ResearchDirectorHead
+ name: loadout-group-research-director-head
+ minLimit: 0
+ loadouts:
+ - ResearchDirectorBeret
+
+- type: loadoutGroup
+ id: ResearchDirectorNeck
+ name: loadout-group-research-director-neck
+ minLimit: 0
+ loadouts:
+ - ResearchDirectorMantle
+ - ResearchDirectorCloak
+
+- type: loadoutGroup
+ id: ResearchDirectorJumpsuit
+ name: loadout-group-research-director-jumpsuit
+ loadouts:
+ - ResearchDirectorJumpsuit
+ - ResearchDirectorJumpskirt
+
+- type: loadoutGroup
+ id: ResearchDirectorBackpack
+ name: loadout-group-research-director-backpack
+ loadouts:
+ - ResearchDirectorBackpack
+ - ResearchDirectorSatchel
+ - ResearchDirectorDuffel
+
+- type: loadoutGroup
+ id: ResearchDirectorOuterClothing
+ name: loadout-group-research-director-outerclothing
+ minLimit: 0
+ loadouts:
+ - ResearchDirectorLabCoat
+
+- type: loadoutGroup
+ id: ScientistHead
+ name: loadout-group-scientist-head
+ minLimit: 0
+ loadouts:
+ - ScientificBeret
+
+- type: loadoutGroup
+ id: ScientistNeck
+ name: loadout-group-scientist-neck
+ minLimit: 0
+ loadouts:
+ - ScientistTie
+
+- type: loadoutGroup
+ id: ScientistJumpsuit
+ name: loadout-group-scientist-jumpsuit
+ loadouts:
+ - ScientistJumpsuit
+ - ScientistJumpskirt
+ - SeniorResearcherJumpsuit
+ - SeniorResearcherJumpskirt
+
+- type: loadoutGroup
+ id: ScientistBackpack
+ name: loadout-group-scientist-backpack
+ loadouts:
+ - ScientistBackpack
+ - ScientistSatchel
+ - ScientistDuffel
+ - SeniorResearcherBackpack
+ - SeniorResearcherSatchel
+ - SeniorResearcherDuffel
+
+- type: loadoutGroup
+ id: ScientistOuterClothing
+ name: loadout-group-scientist-outerclothing
+ minLimit: 0
+ loadouts:
+ - RegularLabCoat
+ - ScienceLabCoat
+ - SeniorResearcherLabCoat
+
+- type: loadoutGroup
+ id: ScientistPDA
+ name: loadout-group-scientist-id
+ loadouts:
+ - ScientistPDA
+ - SeniorResearcherPDA
+
+- type: loadoutGroup
+ id: ResearchAssistantJumpsuit
+ name: loadout-group-research-assistant-jumpsuit
+ loadouts:
+ - WhiteJumpsuit
+ - WhiteJumpskirt
+
+# Security
+- type: loadoutGroup
+ id: HeadofSecurityHead
+ name: loadout-group-head-of-security-head
+ minLimit: 0
+ loadouts:
+ - HeadofSecurityHead
+ - HeadofSecurityBeret
+
+- type: loadoutGroup
+ id: HeadofSecurityJumpsuit
+ name: loadout-group-head-of-security-jumpsuit
+ loadouts:
+ - HeadofSecurityJumpsuit
+ - HeadofSecurityJumpskirt
+ - HeadofSecurityTurtleneck
+ - HeadofSecurityTurtleneckSkirt
+ - HeadofSecurityFormalSuit
+ - HeadofSecurityFormalSkirt
+
+- type: loadoutGroup
+ id: HeadofSecurityNeck
+ name: loadout-group-head-of-security-neck
+ minLimit: 0
+ loadouts:
+ - HeadofSecurityCloak
+ - HeadofSecurityMantle
+
+- type: loadoutGroup
+ id: HeadofSecurityOuterClothing
+ name: loadout-group-head-of-security-outerclothing
+ minLimit: 0
+ loadouts:
+ - HeadofSecurityCoat
+ - HeadofSecurityWinterCoat
+
+- type: loadoutGroup
+ id: WardenHead
+ name: loadout-group-warden-head
+ minLimit: 0
+ loadouts:
+ - WardenHead
+ - WardenBeret
+
+- type: loadoutGroup
+ id: WardenJumpsuit
+ name: loadout-group-warden-jumpsuit
+ loadouts:
+ - WardenJumpsuit
+ - WardenJumpskirt
+
+- type: loadoutGroup
+ id: WardenOuterClothing
+ name: loadout-group-warden-outerclothing
+ minLimit: 0
+ loadouts:
+ - WardenCoat
+ - WardenArmoredWinterCoat
+
+- type: loadoutGroup
+ id: SecurityHead
+ name: loadout-group-security-head
+ minLimit: 0
+ loadouts:
+ - SecurityHelmet
+ - SecurityBeret
+ - SecurityHat
+
+- type: loadoutGroup
+ id: SecurityJumpsuit
+ name: loadout-group-security-jumpsuit
+ loadouts:
+ - SecurityJumpsuit
+ - SecurityJumpskirt
+ - SeniorOfficerJumpsuit
+ - SeniorOfficerJumpskirt
+
+- type: loadoutGroup
+ id: SecurityBackpack
+ name: loadout-group-security-backpack
+ loadouts:
+ - SecurityBackpack
+ - SecuritySatchel
+ - SecurityDuffel
+ - SeniorOfficerBackpack
+ - SeniorOfficerSatchel
+ - SeniorOfficerDuffel
+
+- type: loadoutGroup
+ id: SecurityPDA
+ name: loadout-group-security-id
+ loadouts:
+ - SecurityPDA
+ - SeniorOfficerPDA
+
+- type: loadoutGroup
+ id: DetectiveHead
+ name: loadout-group-detective-head
+ minLimit: 0
+ loadouts:
+ - DetectiveFedora
+ - DetectiveFedoraGrey
+
+- type: loadoutGroup
+ id: DetectiveNeck
+ name: loadout-group-detective-neck
+ minLimit: 0
+ loadouts:
+ - DetectiveTie
+
+- type: loadoutGroup
+ id: DetectiveJumpsuit
+ name: loadout-group-detective-jumpsuit
+ loadouts:
+ - DetectiveJumpsuit
+ - DetectiveJumpskirt
+ - NoirJumpsuit
+ - NoirJumpskirt
+
+- type: loadoutGroup
+ id: DetectiveBackpack
+ name: loadout-group-detective-backpack
+ loadouts:
+ - DetectiveBackpack
+ - DetectiveSatchel
+ - DetectiveDuffel
+
+- type: loadoutGroup
+ id: DetectiveOuterClothing
+ name: loadout-group-detective-outerclothing
+ minLimit: 0
+ loadouts:
+ - DetectiveArmorVest
+ - DetectiveCoat
+
+- type: loadoutGroup
+ id: SecurityCadetJumpsuit
+ name: loadout-group-security-cadet-jumpsuit
+ loadouts:
+ - RedJumpsuit
+ - RedJumpskirt
+
+# Medical
+- type: loadoutGroup
+ id: ChiefMedicalOfficerHead
+ name: loadout-group-chief-medical-officer-head
+ minLimit: 0
+ loadouts:
+ - ChiefMedicalOfficerBeret
+
+- type: loadoutGroup
+ id: ChiefMedicalOfficerJumpsuit
+ name: loadout-group-chief-medical-officer-jumpsuit
+ loadouts:
+ - ChiefMedicalOfficerJumpsuit
+ - ChiefMedicalOfficerJumpskirt
+
+- type: loadoutGroup
+ id: ChiefMedicalOfficerOuterClothing
+ name: loadout-group-chief-medical-officer-outerclothing
+ minLimit: 0
+ loadouts:
+ - ChiefMedicalOfficerLabCoat
+
+- type: loadoutGroup
+ id: ChiefMedicalOfficerBackpack
+ name: loadout-group-chief-medical-officer-backpack
+ loadouts:
+ - ChiefMedicalOfficerBackpack
+ - ChiefMedicalOfficerSatchel
+ - ChiefMedicalOfficerDuffel
+
+- type: loadoutGroup
+ id: ChiefMedicalOfficerNeck
+ name: loadout-group-chief-medical-officer-neck
+ minLimit: 0
+ loadouts:
+ - ChiefMedicalOfficerCloak
+ - ChiefMedicalOfficerMantle
+
+- type: loadoutGroup
+ id: MedicalDoctorHead
+ name: loadout-group-medical-doctor-head
+ minLimit: 0
+ loadouts:
+ - MedicalBeret
+ - SeniorPhysicianBeret
+ - BlueSurgeryCap
+ - GreenSurgeryCap
+ - PurpleSurgeryCap
+
+- type: loadoutGroup
+ id: MedicalDoctorJumpsuit
+ name: loadout-group-medical-doctor-jumpsuit
+ loadouts:
+ - MedicalDoctorJumpsuit
+ - MedicalDoctorJumpskirt
+ - SeniorPhysicianJumpsuit
+ - SeniorPhysicianJumpskirt
+ - MedicalBlueScrubs
+ - MedicalGreenScrubs
+ - MedicalPurpleScrubs
+
+- type: loadoutGroup
+ id: MedicalDoctorOuterClothing
+ name: loadout-group-medical-doctor-outerclothing
+ minLimit: 0
+ loadouts:
+ - RegularLabCoat
+ - SeniorPhysicianLabCoat
+
+- type: loadoutGroup
+ id: MedicalBackpack
+ name: loadout-group-medical-doctor-backpack
+ loadouts:
+ - MedicalDoctorBackpack
+ - MedicalDoctorSatchel
+ - MedicalDoctorDuffel
+ - SeniorPhysicianBackpack
+ - SeniorPhysicianSatchel
+ - SeniorPhysicianDuffel
+
+- type: loadoutGroup
+ id: MedicalDoctorPDA
+ name: loadout-group-medical-doctor-id
+ loadouts:
+ - MedicalDoctorPDA
+ - SeniorPhysicianPDA
+
+- type: loadoutGroup
+ id: MedicalInternJumpsuit
+ name: loadout-group-medical-intern-jumpsuit
+ loadouts:
+ - WhiteJumpsuit
+ - WhiteJumpskirt
+
+- type: loadoutGroup
+ id: ChemistJumpsuit
+ name: loadout-group-chemist-jumpsuit
+ loadouts:
+ - ChemistJumpsuit
+ - ChemistJumpskirt
+
+- type: loadoutGroup
+ id: ChemistOuterClothing
+ name: loadout-group-chemist-outerclothing
+ minLimit: 0
+ loadouts:
+ - RegularLabCoat
+ - ChemistLabCoat
+
+- type: loadoutGroup
+ id: ChemistBackpack
+ name: loadout-group-chemist-backpack
+ loadouts:
+ - ChemistBackpack
+ - ChemistSatchel
+ - ChemistDuffel
+
+- type: loadoutGroup
+ id: ParamedicHead
+ name: loadout-group-paramedic-head
+ minLimit: 0
+ loadouts:
+ - ParamedicHead
+
+- type: loadoutGroup
+ id: ParamedicJumpsuit
+ name: loadout-group-paramedic-jumpsuit
+ loadouts:
+ - ParamedicJumpsuit
+ - ParamedicJumpskirt
+
+- type: loadoutGroup
+ id: ParamedicOuterClothing
+ name: loadout-group-paramedic-outerclothing
+ minLimit: 0
+ loadouts:
+ - ParamedicWindbreaker
+
+- type: loadoutGroup
+ id: ParamedicBackpack
+ name: loadout-group-paramedic-backpack
+ loadouts:
+ - ParamedicBackpack
+ - ParamedicSatchel
+ - ParamedicDuffel
+
+# Wildcards
+- type: loadoutGroup
+ id: ReporterJumpsuit
+ name: loadout-group-reporter-jumpsuit
+ loadouts:
+ - ReporterJumpsuit
+ - JournalistJumpsuit
+
+- type: loadoutGroup
+ id: BoxerJumpsuit
+ name: loadout-group-boxer-jumpsuit
+ loadouts:
+ - BoxerShorts
+ - BoxerShortsWithTop
+
+- type: loadoutGroup
+ id: BoxerGloves
+ name: loadout-group-boxer-gloves
+ loadouts:
+ - RedBoxingGloves
+ - BlueBoxingGloves
+ - GreenBoxingGloves
+ - YellowBoxingGloves
--- /dev/null
+# Command
+- type: roleLoadout
+ id: JobCaptain
+ groups:
+ - CaptainHead
+ - CaptainNeck
+ - CaptainJumpsuit
+ - CaptainBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobHeadOfPersonnel
+ groups:
+ - HoPHead
+ - HoPNeck
+ - HoPJumpsuit
+ - HoPBackpack
+ - Trinkets
+
+# Civilian
+- type: roleLoadout
+ id: JobPassenger
+ groups:
+ - PassengerJumpsuit
+ - CommonBackpack
+ - PassengerFace
+ - PassengerGloves
+ - Trinkets
+
+- type: roleLoadout
+ id: JobBartender
+ groups:
+ - BartenderHead
+ - BartenderJumpsuit
+ - CommonBackpack
+ - BartenderOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobServiceWorker
+ groups:
+ - BartenderJumpsuit
+ - CommonBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobChef
+ groups:
+ - ChefHead
+ - ChefMask
+ - ChefJumpsuit
+ - CommonBackpack
+ - ChefOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobLibrarian
+ groups:
+ - LibrarianJumpsuit
+ - CommonBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobLawyer
+ groups:
+ - LawyerNeck
+ - LawyerJumpsuit
+ - CommonBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobChaplain
+ groups:
+ - ChaplainHead
+ - ChaplainMask
+ - ChaplainNeck
+ - ChaplainJumpsuit
+ - ChaplainBackpack
+ - ChaplainOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobJanitor
+ groups:
+ - JanitorHead
+ - JanitorJumpsuit
+ - CommonBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobBotanist
+ groups:
+ - BotanistHead
+ - BotanistJumpsuit
+ - BotanistBackpack
+ - BotanistOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobClown
+ groups:
+ - ClownHead
+ - ClownJumpsuit
+ - ClownBackpack
+ - ClownShoes
+ - Trinkets
+
+- type: roleLoadout
+ id: JobMime
+ groups:
+ - MimeHead
+ - MimeMask
+ - MimeJumpsuit
+ - MimeBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobMusician
+ groups:
+ - MusicianBackpack
+ - Trinkets
+
+# Cargo
+- type: roleLoadout
+ id: JobQuartermaster
+ groups:
+ - QuartermasterHead
+ - QuartermasterNeck
+ - QuartermasterJumpsuit
+ - QuartermasterBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobCargoTechnician
+ groups:
+ - CargoTechnicianHead
+ - CargoTechnicianJumpsuit
+ - CargoTechnicianBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobSalvageSpecialist
+ groups:
+ - SalvageSpecialistBackpack
+ - Trinkets
+
+# Engineering
+- type: roleLoadout
+ id: JobChiefEngineer
+ groups:
+ - ChiefEngineerHead
+ - ChiefEngineerJumpsuit
+ - ChiefEngineerBackpack
+ - ChiefEngineerNeck
+ - Trinkets
+
+- type: roleLoadout
+ id: JobTechnicalAssistant
+ groups:
+ - TechnicalAssistantJumpsuit
+ - StationEngineerBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobStationEngineer
+ groups:
+ - StationEngineerHead
+ - StationEngineerJumpsuit
+ - StationEngineerBackpack
+ - StationEngineerOuterClothing
+ - StationEngineerID
+ - Trinkets
+
+- type: roleLoadout
+ id: JobAtmosphericTechnician
+ groups:
+ - AtmosphericTechnicianJumpsuit
+ - AtmosphericTechnicianBackpack
+ - Trinkets
+
+# Science
+- type: roleLoadout
+ id: JobResearchDirector
+ groups:
+ - ResearchDirectorHead
+ - ResearchDirectorNeck
+ - ResearchDirectorJumpsuit
+ - ResearchDirectorBackpack
+ - ResearchDirectorOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobScientist
+ groups:
+ - ScientistHead
+ - ScientistNeck
+ - ScientistJumpsuit
+ - ScientistBackpack
+ - ScientistOuterClothing
+ - ScientistPDA
+ - Trinkets
+
+- type: roleLoadout
+ id: JobResearchAssistant
+ groups:
+ - ResearchAssistantJumpsuit
+ - ScientistBackpack
+ - Trinkets
+
+# Security
+- type: roleLoadout
+ id: JobHeadOfSecurity
+ groups:
+ - HeadofSecurityHead
+ - HeadofSecurityNeck
+ - HeadofSecurityJumpsuit
+ - SecurityBackpack
+ - HeadofSecurityOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobWarden
+ groups:
+ - WardenHead
+ - WardenJumpsuit
+ - SecurityBackpack
+ - WardenOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobSecurityOfficer
+ groups:
+ - SecurityHead
+ - SecurityJumpsuit
+ - SecurityBackpack
+ - SecurityPDA
+ - Trinkets
+
+- type: roleLoadout
+ id: JobDetective
+ groups:
+ - DetectiveHead
+ - DetectiveNeck
+ - DetectiveJumpsuit
+ - DetectiveBackpack
+ - DetectiveOuterClothing
+ - Trinkets
+
+- type: roleLoadout
+ id: JobSecurityCadet
+ groups:
+ - SecurityCadetJumpsuit
+ - SecurityBackpack
+ - Trinkets
+
+# Medical
+- type: roleLoadout
+ id: JobChiefMedicalOfficer
+ groups:
+ - ChiefMedicalOfficerHead
+ - ChiefMedicalOfficerJumpsuit
+ - ChiefMedicalOfficerOuterClothing
+ - ChiefMedicalOfficerBackpack
+ - ChiefMedicalOfficerNeck
+ - Trinkets
+
+- type: roleLoadout
+ id: JobMedicalDoctor
+ groups:
+ - MedicalDoctorHead
+ - MedicalDoctorJumpsuit
+ - MedicalBackpack
+ - MedicalDoctorOuterClothing
+ - MedicalDoctorPDA
+ - Trinkets
+
+- type: roleLoadout
+ id: JobMedicalIntern
+ groups:
+ - MedicalInternJumpsuit
+ - MedicalBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobChemist
+ groups:
+ - ChemistJumpsuit
+ - ChemistOuterClothing
+ - ChemistBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobParamedic
+ groups:
+ - ParamedicHead
+ - ParamedicJumpsuit
+ - ParamedicOuterClothing
+ - ParamedicBackpack
+ - Trinkets
+
+# Wildcards
+- type: roleLoadout
+ id: JobZookeeper
+ groups:
+ - CommonBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobReporter
+ groups:
+ - ReporterJumpsuit
+ - CommonBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobPsychologist
+ groups:
+ - MedicalBackpack
+ - Trinkets
+
+- type: roleLoadout
+ id: JobBoxer
+ groups:
+ - BoxerJumpsuit
+ - BoxerGloves
+ - CommonBackpack
+ - Trinkets
id: PiratePDA
belt: ClothingBeltUtility
pocket1: AppraisalTool
- innerClothingSkirt: ClothingUniformJumpsuitPirate
- satchel: ClothingBackpackPirateFilled
- duffelbag: ClothingBackpackPirateFilled
- type: startingGear
id: PirateCaptainGear
pocket1: AppraisalTool
pocket2: EnergyCutlass
outerClothing: ClothingOuterCoatPirate
- innerClothingSkirt: ClothingUniformJumpskirtColorLightBrown
- satchel: ClothingBackpackPirateFilled
- duffelbag: ClothingBackpackPirateFilled
- type: startingGear
id: PirateFirstmateGear
belt: ClothingBeltUtility
pocket1: AppraisalTool
outerClothing: ClothingOuterCoatGentle
- innerClothingSkirt: ClothingUniformJumpsuitPirate
- satchel: ClothingBackpackPirateFilled
- duffelbag: ClothingBackpackPirateFilled
- type: startingGear
id: CargoTechGear
equipment:
- head: ClothingHeadHatCargosoft
- jumpsuit: ClothingUniformJumpsuitCargo
- back: ClothingBackpackCargoFilled
shoes: ClothingShoesColorBlack
id: CargoPDA
ears: ClothingHeadsetCargo
- pocket1: AppraisalTool
- innerClothingSkirt: ClothingUniformJumpskirtCargo
- satchel: ClothingBackpackSatchelCargoFilled
- duffelbag: ClothingBackpackDuffelCargoFilled
+ pocket1: AppraisalTool
\ No newline at end of file
- type: startingGear
id: QuartermasterGear
equipment:
- head: ClothingHeadHatQMsoft
- jumpsuit: ClothingUniformJumpsuitQM
- back: ClothingBackpackQuartermasterFilled
shoes: ClothingShoesColorBrown
id: QuartermasterPDA
ears: ClothingHeadsetQM
belt: BoxFolderClipboard
- pocket1: AppraisalTool
- innerClothingSkirt: ClothingUniformJumpskirtQM
- satchel: ClothingBackpackSatchelQuartermasterFilled
- duffelbag: ClothingBackpackDuffelQuartermasterFilled
+ pocket1: AppraisalTool
\ No newline at end of file
id: SalvageSpecialistGear
equipment:
jumpsuit: ClothingUniformJumpsuitSalvageSpecialist
- back: ClothingBackpackSalvageFilled
shoes: ClothingShoesBootsSalvage
id: SalvagePDA
- ears: ClothingHeadsetCargo
- satchel: ClothingBackpackSatchelSalvageFilled
- duffelbag: ClothingBackpackDuffelSalvageFilled
+ ears: ClothingHeadsetCargo
\ No newline at end of file
- type: startingGear
id: PassengerGear
equipment:
- jumpsuit: ClothingUniformJumpsuitColorGrey
- back: ClothingBackpackFilled
shoes: ClothingShoesColorBlack
id: PassengerPDA
ears: ClothingHeadsetGrey
- innerClothingSkirt: ClothingUniformJumpskirtColorGrey
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: BartenderGear
equipment:
- head: ClothingHeadHatTophat
- jumpsuit: ClothingUniformJumpsuitBartender
- outerClothing: ClothingOuterVest
- back: ClothingBackpackFilled
shoes: ClothingShoesColorBlack
id: BartenderPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtBartender
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: BotanistGear
equipment:
- jumpsuit: ClothingUniformJumpsuitHydroponics
- back: ClothingBackpackHydroponicsFilled
shoes: ClothingShoesColorBrown
id: BotanistPDA
ears: ClothingHeadsetService
- outerClothing: ClothingOuterApronBotanist
belt: ClothingBeltPlantFilled
- innerClothingSkirt: ClothingUniformJumpskirtHydroponics
- satchel: ClothingBackpackSatchelHydroponicsFilled
- duffelbag: ClothingBackpackDuffelHydroponicsFilled
- type: startingGear
id: ChaplainGear
equipment:
- jumpsuit: ClothingUniformJumpsuitChaplain
- back: ClothingBackpackChaplainFilled
shoes: ClothingShoesColorBlack
id: ChaplainPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtChaplain
- satchel: ClothingBackpackSatchelChaplainFilled
- duffelbag: ClothingBackpackDuffelChaplainFilled
- type: startingGear
id: ChefGear
equipment:
- jumpsuit: ClothingUniformJumpsuitChef
- head: ClothingHeadHatChef
- back: ClothingBackpackFilled
- mask: ClothingMaskItalianMoustache
shoes: ClothingShoesColorBlack
id: ChefPDA
ears: ClothingHeadsetService
- outerClothing: ClothingOuterApronChef
belt: ClothingBeltChefFilled
- innerClothingSkirt: ClothingUniformJumpskirtChef
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: ClownGear
equipment:
- jumpsuit: ClothingUniformJumpsuitClown
- back: ClothingBackpackClownFilled
- shoes: ClothingShoesClown
mask: ClothingMaskClown
pocket1: BikeHorn
pocket2: ClownRecorder
id: ClownPDA
- ears: ClothingHeadsetService
- satchel: ClothingBackpackSatchelClownFilled
- duffelbag: ClothingBackpackDuffelClownFilled
+ ears: ClothingHeadsetService
\ No newline at end of file
- type: startingGear
id: JanitorGear
equipment:
- jumpsuit: ClothingUniformJumpsuitJanitor
- back: ClothingBackpackFilled
shoes: ClothingShoesGaloshes
- head: ClothingHeadHatPurplesoft
id: JanitorPDA
gloves: ClothingHandsGlovesJanitor
ears: ClothingHeadsetService
belt: ClothingBeltJanitorFilled
- innerClothingSkirt: ClothingUniformJumpskirtJanitor
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: JanitorMaidGear
equipment:
jumpsuit: ClothingUniformJumpskirtJanimaid
- back: ClothingBackpackFilled
id: JanitorPDA
gloves: ClothingHandsGlovesJanitor
head: ClothingHeadHatCatEars
ears: ClothingHeadsetService
belt: ClothingBeltJanitorFilled
- innerClothingSkirt: ClothingUniformJumpskirtJanimaid
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: LawyerGear
equipment:
- jumpsuit: ClothingUniformJumpsuitLawyerBlack # TODO change jumpsuit to randomiser of the 4 variants # ignoring this since loadouts are gonna be out soon
- back: ClothingBackpackLawyerFilled
shoes: ClothingShoesBootsLaceup
id: LawyerPDA
ears: ClothingHeadsetSecurity
# TODO add copy of space law
inhand:
- BriefcaseBrownFilled
- innerClothingSkirt: ClothingUniformJumpskirtLawyerBlack
- satchel: ClothingBackpackSatchelLawyerFilled
- duffelbag: ClothingBackpackDuffelLawyerFilled
- type: startingGear
id: LibrarianGear
equipment:
- jumpsuit: ClothingUniformJumpsuitLibrarian
- back: ClothingBackpackLibrarianFilled
shoes: ClothingShoesBootsLaceup
id: LibrarianPDA
ears: ClothingHeadsetService
pocket1: d10Dice
pocket2: HandLabeler # for making named bestsellers
- innerClothingSkirt: ClothingUniformJumpskirtLibrarian
- satchel: ClothingBackpackSatchelLibrarianFilled
- duffelbag: ClothingBackpackDuffelLibrarianFilled
- type: startingGear
id: MimeGear
equipment:
- jumpsuit: ClothingUniformJumpsuitMime
- back: ClothingBackpackMimeFilled
- head: ClothingHeadHatBeret
belt: ClothingBeltSuspenders
- gloves: ClothingHandsGlovesLatex
+ gloves: ClothingHandsGlovesColorWhite
shoes: ClothingShoesColorWhite
pocket1: CrayonMime
pocket2: Paper
- mask: ClothingMaskMime
id: MimePDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtMime
- satchel: ClothingBackpackSatchelMimeFilled
- duffelbag: ClothingBackpackDuffelMimeFilled
- type: entity
id: ActionMimeInvisibleWall
id: MusicianGear
equipment:
jumpsuit: ClothingUniformJumpsuitMusician
- back: ClothingBackpackMusicianFilled
eyes: ClothingEyesGlassesSunglasses
shoes: ClothingShoesBootsLaceup
id: MusicianPDA
- ears: ClothingHeadsetService
- satchel: ClothingBackpackSatchelMusicianFilled
- duffelbag: ClothingBackpackDuffelMusicianFilled
+ ears: ClothingHeadsetService
\ No newline at end of file
- type: startingGear
id: ServiceWorkerGear
equipment:
- jumpsuit: ClothingUniformJumpsuitBartender
- back: ClothingBackpackFilled
shoes: ClothingShoesColorBlack
id: ServiceWorkerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtBartender
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: CaptainGear
equipment:
- jumpsuit: ClothingUniformJumpsuitCaptain
- back: ClothingBackpackCaptainFilled
shoes: ClothingShoesBootsLaceup
- head: ClothingHeadHatCaptain
eyes: ClothingEyesGlassesSunglasses
gloves: ClothingHandsGlovesCaptain
outerClothing: ClothingOuterArmorCaptainCarapace
id: CaptainPDA
ears: ClothingHeadsetAltCommand
- innerClothingSkirt: ClothingUniformJumpskirtCaptain
- satchel: ClothingBackpackSatchelCaptainFilled
- duffelbag: ClothingBackpackDuffelCaptainFilled
- type: startingGear
id: HoPGear
equipment:
- jumpsuit: ClothingUniformJumpsuitHoP
- back: ClothingBackpackHOPFilled
shoes: ClothingShoesColorBrown
- head: ClothingHeadHatHopcap
id: HoPPDA
gloves: ClothingHandsGlovesHop
ears: ClothingHeadsetAltCommand
belt: BoxFolderClipboard
- innerClothingSkirt: ClothingUniformJumpskirtHoP
- satchel: ClothingBackpackSatchelHOPFilled
- duffelbag: ClothingBackpackDuffelHOPFilled
- type: startingGear
id: AtmosphericTechnicianGear
equipment:
- jumpsuit: ClothingUniformJumpsuitAtmos
- back: ClothingBackpackAtmosphericsFilled
shoes: ClothingShoesColorWhite
eyes: ClothingEyesGlassesMeson
id: AtmosPDA
belt: ClothingBeltUtilityEngineering
ears: ClothingHeadsetEngineering
- innerClothingSkirt: ClothingUniformJumpskirtAtmos
- satchel: ClothingBackpackSatchelAtmosphericsFilled
- duffelbag: ClothingBackpackDuffelAtmosphericsFilled
- type: startingGear
id: ChiefEngineerGear
equipment:
- head: ClothingHeadHatHardhatWhite
- jumpsuit: ClothingUniformJumpsuitChiefEngineer
- back: ClothingBackpackChiefEngineerFilled
shoes: ClothingShoesColorBrown
id: CEPDA
eyes: ClothingEyesGlassesMeson
ears: ClothingHeadsetCE
belt: ClothingBeltUtilityEngineering
- innerClothingSkirt: ClothingUniformJumpskirtChiefEngineer
- satchel: ClothingBackpackSatchelChiefEngineerFilled
- duffelbag: ClothingBackpackDuffelChiefEngineerFilled
- type: startingGear
id: StationEngineerGear
equipment:
- head: ClothingHeadHatHardhatYellow
- jumpsuit: ClothingUniformJumpsuitEngineering
- back: ClothingBackpackEngineeringFilled
shoes: ClothingShoesBootsWork
- outerClothing: ClothingOuterVestHazard
- id: EngineerPDA
eyes: ClothingEyesGlassesMeson
belt: ClothingBeltUtilityEngineering
ears: ClothingHeadsetEngineering
- innerClothingSkirt: ClothingUniformJumpskirtEngineering
- satchel: ClothingBackpackSatchelEngineeringFilled
- duffelbag: ClothingBackpackDuffelEngineeringFilled
- type: startingGear
id: TechnicalAssistantGear
equipment:
- jumpsuit: ClothingUniformJumpsuitColorYellow
- back: ClothingBackpackEngineeringFilled
shoes: ClothingShoesBootsWork
id: TechnicalAssistantPDA
belt: ClothingBeltUtilityEngineering
ears: ClothingHeadsetEngineering
- pocket2: BookEngineersHandbook
- innerClothingSkirt: ClothingUniformJumpskirtColorYellow
- satchel: ClothingBackpackSatchelEngineeringFilled
- duffelbag: ClothingBackpackDuffelEngineeringFilled
+ pocket2: BookEngineersHandbook
\ No newline at end of file
shoes: ClothingShoesCult
id: PassengerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtColorBlack
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: CultistGear
shoes: ClothingShoesColorRed
id: PassengerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtColorBlack
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
shoes: ClothingShoesBootsJack
id: PassengerPDA
ears: ClothingHeadsetGrey
- innerClothingSkirt: ClothingUniformJumpskirtColorBlack
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
#Space Ninja Outfit
- type: startingGear
pocket1: EnergySword
pocket2: EnergyShield
belt: ClothingBeltMilitaryWebbingMedFilled
- innerClothingSkirt: ClothingUniformJumpskirtColorBlack
# Syndicate Operative Outfit - Monkey
- type: startingGear
head: ClothingHeadHatOutlawHat
jumpsuit: ClothingUniformJumpsuitOperative
mask: CigaretteSyndicate
- innerClothingSkirt: ClothingUniformJumpsuitOperative
# Syndicate Operative Outfit - Barratry
- type: startingGear
back: ClothingBackpackDuffelSyndicateOperative
shoes: ClothingShoesBootsCombatFilled
gloves: ClothingHandsGlovesColorBlack
- innerClothingSkirt: ClothingUniformJumpsuitOperative
- satchel: ClothingBackpackDuffelSyndicateOperative
- duffelbag: ClothingBackpackDuffelSyndicateOperative
#Syndicate Operative Outfit - Basic
- type: startingGear
shoes: ClothingShoesBootsCombatFilled
pocket1: BaseUplinkRadio40TC
id: SyndiPDA
- innerClothingSkirt: ClothingUniformJumpsuitOperative
- satchel: ClothingBackpackDuffelSyndicateOperative
- duffelbag: ClothingBackpackDuffelSyndicateOperative
#Syndicate Operative Outfit - Full Kit
- type: startingGear
pocket1: DoubleEmergencyOxygenTankFilled
pocket2: BaseUplinkRadio40TC
belt: ClothingBeltMilitaryWebbing
- innerClothingSkirt: ClothingUniformJumpskirtOperative
- satchel: ClothingBackpackDuffelSyndicateOperative
- duffelbag: ClothingBackpackDuffelSyndicateOperative
#Nuclear Operative Commander Gear
- type: startingGear
belt: ClothingBeltMilitaryWebbing
inhand:
- NukeOpsDeclarationOfWar
- innerClothingSkirt: ClothingUniformJumpskirtOperative
- satchel: ClothingBackpackDuffelSyndicateOperative
- duffelbag: ClothingBackpackDuffelSyndicateOperative
#Nuclear Operative Medic Gear
- type: startingGear
pocket1: DoubleEmergencyOxygenTankFilled
pocket2: BaseUplinkRadio40TC
belt: ClothingBeltMilitaryWebbingMedFilled
- innerClothingSkirt: ClothingUniformJumpskirtOperative
- satchel: ClothingBackpackDuffelSyndicateOperativeMedic
- duffelbag: ClothingBackpackDuffelSyndicateOperativeMedic
#Syndicate Lone Operative Outfit - Full Kit
- type: startingGear
pocket1: DoubleEmergencyOxygenTankFilled
pocket2: BaseUplinkRadio60TC
belt: ClothingBeltMilitaryWebbing
- innerClothingSkirt: ClothingUniformJumpskirtOperative
- satchel: ClothingBackpackDuffelSyndicateOperative
- duffelbag: ClothingBackpackDuffelSyndicateOperative
# Syndicate Footsoldier Gear
- type: startingGear
back: ClothingBackpackFilled
shoes: ClothingShoesBootsCombat
id: SyndiPDA
- innerClothingSkirt: ClothingUniformJumpsuitOperative
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelSyndicateOperative
# Syndicate Footsoldier Gear - No Headset
- type: startingGear
back: ClothingBackpackFilled
shoes: ClothingShoesBootsCombat
id: SyndiPDA
- innerClothingSkirt: ClothingUniformJumpsuitOperative
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelSyndicateOperative
# Nanotrasen Paramilitary Unit Gear
- type: startingGear
outerClothing: ClothingOuterArmorBasicSlim
ears: ClothingHeadsetSecurity
gloves: ClothingHandsGlovesCombat
- innerClothingSkirt: ClothingUniformJumpskirtSec
- satchel: ClothingBackpackSatchelSecurityFilled
- duffelbag: ClothingBackpackDuffelSecurityFilled
#CBURN Unit Gear - Full Kit
- type: startingGear
pocket2: WeaponLaserGun
suitstorage: OxygenTankFilled
belt: ClothingBeltBandolier
- innerClothingSkirt: ClothingUniformJumpsuitColorBrown
- satchel: ClothingBackpackDuffelCBURNFilled
- duffelbag: ClothingBackpackDuffelCBURNFilled
- type: startingGear
id: BoxingKangarooGear
equipment:
jumpsuit: ClothingUniformJumpsuitColorGrey
shoes: ClothingShoesColorBlack
- innerClothingSkirt: ClothingUniformJumpskirtColorGrey
# DeathMatch Gear
shoes: ClothingShoesBootsJack
ears: ClothingHeadsetGrey
gloves: ClothingHandsGlovesFingerless
- innerClothingSkirt: ClothingUniformJumpskirtColorWhite
inhand:
- WeaponMeleeToolboxRobust
ears: ClothingHeadsetBrigmedic
mask: ClothingMaskBreathMedicalSecurity
belt: ClothingBeltMedicalFilled
- innerClothingSkirt: ClothingUniformJumpskirtBrigmedic
- satchel: ClothingBackpackSatchelBrigmedicFilled
- duffelbag: ClothingBackpackDuffelBrigmedicFilled
# Aghost
- type: startingGear
shoes: ClothingShoesWizard
id: PassengerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtColorDarkBlue
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: WizardRedGear
shoes: ClothingShoesWizard
id: PassengerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtColorRed
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: WizardVioletGear
shoes: ClothingShoesWizard
id: PassengerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtColorPurple
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: WizardHardsuitGear
shoes: ClothingShoesWizard
id: PassengerPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpskirtColorPurple
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: ChemistGear
equipment:
- jumpsuit: ClothingUniformJumpsuitChemistry
back: ClothingBackpackChemistryFilled
shoes: ClothingShoesColorWhite
- outerClothing: ClothingOuterCoatLabChem
id: ChemistryPDA
ears: ClothingHeadsetMedical
belt: ChemBag
pocket1: HandLabeler
- # the purple glasses?
- innerClothingSkirt: ClothingUniformJumpskirtChemistry
- satchel: ClothingBackpackSatchelChemistryFilled
- duffelbag: ClothingBackpackDuffelChemistryFilled
+ # the purple glasses?
\ No newline at end of file
- type: startingGear
id: CMOGear
equipment:
- jumpsuit: ClothingUniformJumpsuitCMO
- back: ClothingBackpackCMOFilled
shoes: ClothingShoesColorBrown
- outerClothing: ClothingOuterCoatLabCmo
id: CMOPDA
ears: ClothingHeadsetCMO
belt: ClothingBeltMedicalFilled
- innerClothingSkirt: ClothingUniformJumpskirtCMO
- satchel: ClothingBackpackSatchelCMOFilled
- duffelbag: ClothingBackpackDuffelCMOFilled
- type: startingGear
id: DoctorGear
equipment:
- jumpsuit: ClothingUniformJumpsuitMedicalDoctor
- back: ClothingBackpackMedicalFilled
shoes: ClothingShoesColorWhite
- outerClothing: ClothingOuterCoatLab
- id: MedicalPDA
ears: ClothingHeadsetMedical
belt: ClothingBeltMedicalFilled
- innerClothingSkirt: ClothingUniformJumpskirtMedicalDoctor
- satchel: ClothingBackpackSatchelMedicalFilled
- duffelbag: ClothingBackpackDuffelMedicalFilled
- type: startingGear
id: MedicalInternGear
equipment:
- jumpsuit: ClothingUniformJumpsuitColorWhite
- back: ClothingBackpackMedicalFilled
shoes: ClothingShoesColorWhite
id: MedicalInternPDA
ears: ClothingHeadsetMedical
belt: ClothingBeltMedicalFilled
- pocket2: BookMedicalReferenceBook
- innerClothingSkirt: ClothingUniformJumpskirtColorWhite
- satchel: ClothingBackpackSatchelMedicalFilled
- duffelbag: ClothingBackpackDuffelMedicalFilled
+ pocket2: BookMedicalReferenceBook
\ No newline at end of file
- type: startingGear
id: ParamedicGear
equipment:
- jumpsuit: ClothingUniformJumpsuitParamedic
- back: ClothingBackpackParamedicFilled
shoes: ClothingShoesColorBlue
id: ParamedicPDA
ears: ClothingHeadsetMedical
belt: ClothingBeltMedicalEMTFilled
- innerClothingSkirt: ClothingUniformJumpskirtParamedic
- satchel: ClothingBackpackSatchelParamedicFilled
- duffelbag: ClothingBackpackDuffelParamedicFilled
- type: startingGear
id: ResearchAssistantGear
equipment:
- jumpsuit: ClothingUniformJumpsuitColorWhite
back: ClothingBackpackScienceFilled
shoes: ClothingShoesColorWhite
id: ResearchAssistantPDA
ears: ClothingHeadsetScience
pocket2: BookScientistsGuidebook
- innerClothingSkirt: ClothingUniformJumpskirtColorWhite
- satchel: ClothingBackpackSatchelScienceFilled
- duffelbag: ClothingBackpackDuffelScienceFilled
- type: startingGear
id: ResearchDirectorGear
equipment:
- jumpsuit: ClothingUniformJumpsuitResearchDirector
- back: ClothingBackpackResearchDirectorFilled
shoes: ClothingShoesColorBrown
- outerClothing: ClothingOuterCoatRD
id: RnDPDA
- ears: ClothingHeadsetRD
- innerClothingSkirt: ClothingUniformJumpskirtResearchDirector
- satchel: ClothingBackpackSatchelResearchDirectorFilled
- duffelbag: ClothingBackpackDuffelResearchDirectorFilled
+ ears: ClothingHeadsetRD
\ No newline at end of file
- type: startingGear
id: ScientistGear
equipment:
- jumpsuit: ClothingUniformJumpsuitScientist
- back: ClothingBackpackScienceFilled
shoes: ClothingShoesColorWhite
- outerClothing: ClothingOuterCoatRnd
- id: SciencePDA
ears: ClothingHeadsetScience
- innerClothingSkirt: ClothingUniformJumpskirtScientist
- satchel: ClothingBackpackSatchelScienceFilled
- duffelbag: ClothingBackpackDuffelScienceFilled
+
- type: startingGear
id: DetectiveGear
equipment:
- jumpsuit: ClothingUniformJumpsuitDetective
- back: ClothingBackpackSecurityFilledDetective
shoes: ClothingShoesBootsCombatFilled
eyes: ClothingEyesGlassesSecurity
- head: ClothingHeadHatFedoraBrown
- outerClothing: ClothingOuterVestDetective
id: DetectivePDA
ears: ClothingHeadsetSecurity
belt: ClothingBeltHolsterFilled
- innerClothingSkirt: ClothingUniformJumpskirtDetective
- satchel: ClothingBackpackSatchelSecurityFilledDetective
- duffelbag: ClothingBackpackDuffelSecurityFilledDetective
- type: startingGear
id: HoSGear
equipment:
- jumpsuit: ClothingUniformJumpsuitHoS
- back: ClothingBackpackHOSFilled
shoes: ClothingShoesBootsCombatFilled
- outerClothing: ClothingOuterCoatHoSTrench
eyes: ClothingEyesGlassesSecurity
- head: ClothingHeadHatBeretHoS
id: HoSPDA
gloves: ClothingHandsGlovesCombat
ears: ClothingHeadsetAltSecurity
belt: ClothingBeltSecurityFilled
- pocket1: WeaponPistolMk58
- innerClothingSkirt: ClothingUniformJumpskirtHoS
- satchel: ClothingBackpackSatchelHOSFilled
- duffelbag: ClothingBackpackDuffelHOSFilled
+ pocket1: WeaponPistolMk58
\ No newline at end of file
- type: startingGear
id: SecurityCadetGear
equipment:
- jumpsuit: ClothingUniformJumpsuitColorRed
- back: ClothingBackpackSecurityFilled
shoes: ClothingShoesBootsCombatFilled
outerClothing: ClothingOuterArmorBasic
id: SecurityCadetPDA
ears: ClothingHeadsetSecurity
belt: ClothingBeltSecurityFilled
pocket1: WeaponPistolMk58
- pocket2: BookSecurity
- innerClothingSkirt: ClothingUniformJumpskirtColorRed
- satchel: ClothingBackpackSatchelSecurityFilled
- duffelbag: ClothingBackpackDuffelSecurityFilled
+ pocket2: BookSecurity
\ No newline at end of file
- type: startingGear
id: SecurityOfficerGear
equipment:
- jumpsuit: ClothingUniformJumpsuitSec
- back: ClothingBackpackSecurityFilled
shoes: ClothingShoesBootsCombatFilled
eyes: ClothingEyesGlassesSecurity
- head: ClothingHeadHelmetBasic
outerClothing: ClothingOuterArmorBasic
- id: SecurityPDA
ears: ClothingHeadsetSecurity
belt: ClothingBeltSecurityFilled
pocket1: WeaponPistolMk58
- innerClothingSkirt: ClothingUniformJumpskirtSec
- satchel: ClothingBackpackSatchelSecurityFilled
- duffelbag: ClothingBackpackDuffelSecurityFilled
- type: startingGear
id: WardenGear
equipment:
- head: ClothingHeadHatWarden
- jumpsuit: ClothingUniformJumpsuitWarden
- back: ClothingBackpackSecurityFilled
shoes: ClothingShoesBootsCombatFilled
eyes: ClothingEyesGlassesSecurity
- outerClothing: ClothingOuterCoatWarden
id: WardenPDA
ears: ClothingHeadsetSecurity
belt: ClothingBeltSecurityFilled
- pocket1: WeaponPistolMk58
- innerClothingSkirt: ClothingUniformJumpskirtWarden
- satchel: ClothingBackpackSatchelSecurityFilled
- duffelbag: ClothingBackpackDuffelSecurityFilled
+ pocket1: WeaponPistolMk58
\ No newline at end of file
+++ /dev/null
-#These are startingGear definitions for the currently yet=to-be-added Ship VS. Ship gamemode.
-#These are not gamemode-ready and are just here so that people know what their general outfit is supposed to look like.
-#For the love of god, please move these out of this file when the gamemode is actually added. They are currently all here for convienence's sake.
-
-#CREW
-#Recruit
-- type: startingGear
- id: RecruitNTGear
- equipment:
- jumpsuit: ClothingUniformJumpsuitRecruitNT
- back: ClothingBackpackFilled
- shoes: ClothingShoesColorBlack
- gloves: ClothingHandsGlovesColorBlack
- id: PassengerPDA
- ears: ClothingHeadsetGrey
- innerClothingSkirt: ClothingUniformJumpsuitRecruitNT #Wearing a jumpskirt into combat is a little unfitting and silly, so there is no jumpskirt counterpart for any of the Ship VS. Ship suits.
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
-
-#Repairman
-- type: startingGear
- id: RepairmanNTGear
- equipment:
- head: ClothingHeadHatHardhatYellow
- jumpsuit: ClothingUniformJumpsuitRepairmanNT
- back: ClothingBackpackEngineeringFilled
- shoes: ClothingShoesBootsWork
- gloves: ClothingHandsGlovesColorYellow #Should maybe still be in lockers - this is just so people know that they're there and a part of the outfit.
- id: EngineerPDA
- eyes: ClothingEyesGlassesMeson
- belt: ClothingBeltUtilityEngineering
- ears: ClothingHeadsetAltCommand #Should use the "alt" engineering headset sprite.
- innerClothingSkirt: ClothingUniformJumpsuitRepairmanNT
- satchel: ClothingBackpackSatchelEngineeringFilled
- duffelbag: ClothingBackpackDuffelEngineeringFilled
-
-#Paramedic
-- type: startingGear
- id: ParamedicNTGear
- equipment:
- jumpsuit: ClothingUniformJumpsuitParamedicNT
- back: ClothingBackpackFilled #The medical backpack sprite looks way worse so this will do for now.
- shoes: ClothingShoesColorBlue
- id: MedicalPDA
- ears: ClothingHeadsetMedical
- eyes: ClothingEyesHudMedical
- gloves: ClothingHandsGlovesLatex
- belt: ClothingBeltMedicalFilled
- innerClothingSkirt: ClothingUniformJumpskirtMedicalDoctor
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
-
-#HEADS OF STAFF
-#Chief Engineer
-- type: startingGear
- id: ChiefEngineerNTGear
- equipment:
- head: ClothingHeadHatHardhatArmored
- jumpsuit: ClothingUniformJumpsuitChiefEngineerNT
- back: ClothingBackpackFilled #Again, the regular sprite here looks way worse than the regular backpack.
- shoes: ClothingShoesBootsJack
- gloves: ClothingHandsGlovesCombat
- id: CEPDA
- eyes: ClothingEyesGlassesMeson
- ears: ClothingHeadsetAltCommand #Same as repairman - make this use the alt headset sprite.
- belt: ClothingBeltUtilityEngineering
- innerClothingSkirt: ClothingUniformJumpsuitChiefEngineerNT
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
+++ /dev/null
-#These are startingGear definitions for the currently yet=to-be-added Ship VS. Ship gamemode.
-#Refer to Nanotrasen.yml for additional comments.
-
-#CREW
-#Recruit
-- type: startingGear
- id: RecruitSyndieGear
- equipment:
- jumpsuit: ClothingUniformJumpsuitRecruitSyndie
- back: ClothingBackpackFilled
- shoes: ClothingShoesColorBlack
- gloves: ClothingHandsGlovesColorBlack
- id: PassengerPDA
- ears: ClothingHeadsetGrey
- innerClothingSkirt: ClothingUniformJumpsuitRecruitSyndie #Wearing a jumpskirt into combat is a little unfitting and silly, so there is no jumpskirt counterpart for any of the Ship VS. Ship suits.
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
-
-#Repairman
-- type: startingGear
- id: RepairmanSyndieGear
- equipment:
- head: ClothingHeadHatHardhatYellow
- jumpsuit: ClothingUniformJumpsuitRepairmanSyndie
- back: ClothingBackpackFilled #The regular industrial backpack looks really weird here, so I've opted for this instead for now. If a new one is never made, then make sure to make a prototype that has this with extended internals!
- shoes: ClothingShoesBootsWork
- gloves: ClothingHandsGlovesColorYellow #Should maybe still be in lockers - this is just so people know that they're there and a part of the outfit.
- id: EngineerPDA
- eyes: ClothingEyesGlassesMeson
- belt: ClothingBeltUtilityEngineering
- ears: ClothingHeadsetAltCommand #Should use the "alt" engineering headset sprite.
- innerClothingSkirt: ClothingUniformJumpsuitRepairmanSyndie
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
-
-#Paramedic
-- type: startingGear
- id: ParamedicSyndieGear
- equipment:
- jumpsuit: ClothingUniformJumpsuitParamedicSyndie
- back: ClothingBackpackFilled #The default job backpack again looks way worse. Same case as the NT Paramedc and Syndicate repairman.
- shoes: ClothingShoesColorRed
- id: MedicalPDA
- ears: ClothingHeadsetMedical
- eyes: ClothingEyesHudMedical
- gloves: ClothingHandsGlovesLatex
- belt: ClothingBeltMedicalFilled
- innerClothingSkirt: ClothingUniformJumpsuitParamedicSyndie
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
-
-#HEADS OF STAFF
-#Chief Engineer
-- type: startingGear
- id: ChiefEngineerSyndieGear
- equipment:
- head: ClothingHeadHatHardhatArmored
- jumpsuit: ClothingUniformJumpsuitChiefEngineerSyndie
- back: ClothingBackpackFilled #In a running theme, the default station job backpack still continues to look strange in comparison to the regular one. It's not as bad as on the syndicate engineer here, though.
- shoes: ClothingShoesBootsJack
- gloves: ClothingHandsGlovesCombat
- id: CEPDA
- eyes: ClothingEyesGlassesMeson
- ears: ClothingHeadsetAltCommand
- belt: ClothingBeltUtilityEngineering
- innerClothingSkirt: ClothingUniformJumpsuitChiefEngineerSyndie
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
- type: startingGear
id: BoxerGear
equipment:
- jumpsuit: UniformShortsRed
- back: ClothingBackpackFilled
id: BoxerPDA
ears: ClothingHeadsetService
- gloves: ClothingHandsGlovesBoxingRed
shoes: ClothingShoesColorRed
belt: ClothingBeltChampion
- innerClothingSkirt: UniformShortsRedWithTop
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
id: PsychologistGear
equipment:
jumpsuit: ClothingUniformJumpsuitPsychologist
- back: ClothingBackpackMedicalFilled
shoes: ClothingShoesLeather
id: PsychologistPDA
ears: ClothingHeadsetMedical
- innerClothingSkirt: ClothingUniformJumpsuitPsychologist
- satchel: ClothingBackpackSatchelMedicalFilled
- duffelbag: ClothingBackpackDuffelMedicalFilled
- type: startingGear
id: ReporterGear
equipment:
- jumpsuit: ClothingUniformJumpsuitReporter
- back: ClothingBackpackFilled
shoes: ClothingShoesColorWhite
id: ReporterPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpsuitJournalist
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled
id: ZookeeperGear
equipment:
jumpsuit: ClothingUniformJumpsuitSafari
- back: ClothingBackpackFilled
head: ClothingHeadSafari
shoes: ClothingShoesColorWhite
id: ZookeeperPDA
ears: ClothingHeadsetService
- innerClothingSkirt: ClothingUniformJumpsuitSafari
- satchel: ClothingBackpackSatchelFilled
- duffelbag: ClothingBackpackDuffelFilled