Merge remote-tracking branch 'space-station-14/master' into 17-04-2024-upstream
# Conflicts: # Content.Client/Movement/Systems/WaddleAnimationSystem.cs # Content.Server/IoC/ServerContentIoC.cs # Resources/Changelog/Changelog.yml # Resources/Prototypes/Maps/europa.yml # Resources/Prototypes/Maps/train.yml # Resources/Textures/_CP14/Clothing/Masks/pluto-mask.rsi/meta.json # Resources/Textures/_CP14/Objects/Weapons/Melee/Dagger/dagger.rsi/meta.json # Resources/Textures/_CP14/Objects/Weapons/Melee/HandheldAxe/handheldAxe.rsi/meta.json # Resources/Textures/_CP14/Objects/Weapons/Melee/Sickle/sickle.rsi/meta.json # Resources/Textures/_CP14/Objects/Weapons/Melee/TwoHandedSword/zweichhender.rsi/meta.json
This commit is contained in:
@@ -58,7 +58,7 @@ public class SpawnEquipDeleteBenchmark
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ using Content.Shared.Module;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Replay;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
|
||||
|
||||
namespace Content.Client.IoC
|
||||
@@ -29,26 +30,29 @@ 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,13 +64,19 @@ namespace Content.Client.Lobby
|
||||
|
||||
_characterSetup.CloseButton.OnPressed += _ =>
|
||||
{
|
||||
// Reset sliders etc.
|
||||
_characterSetup?.UpdateControls();
|
||||
|
||||
var controller = _userInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.SetClothes(true);
|
||||
controller.UpdateProfile();
|
||||
_lobby.SwitchState(LobbyGui.LobbyGuiState.Default);
|
||||
};
|
||||
|
||||
_characterSetup.SaveButton.OnPressed += _ =>
|
||||
{
|
||||
_characterSetup.Save();
|
||||
_lobby.CharacterPreview.UpdateUI();
|
||||
_userInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
|
||||
};
|
||||
|
||||
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
|
||||
@@ -84,10 +90,6 @@ namespace Content.Client.Lobby
|
||||
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
|
||||
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
|
||||
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
|
||||
|
||||
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
|
||||
|
||||
_lobby.CharacterPreview.UpdateUI();
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
@@ -109,13 +111,6 @@ namespace Content.Client.Lobby
|
||||
|
||||
_characterSetup?.Dispose();
|
||||
_characterSetup = null;
|
||||
|
||||
_preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
|
||||
}
|
||||
|
||||
private void PreferencesDataLoaded()
|
||||
{
|
||||
_lobby?.CharacterPreview.UpdateUI();
|
||||
}
|
||||
|
||||
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
|
||||
|
||||
286
Content.Client/Lobby/LobbyUIController.cs
Normal file
286
Content.Client/Lobby/LobbyUIController.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.Preferences.UI;
|
||||
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;
|
||||
|
||||
private bool _showClothes = true;
|
||||
|
||||
/*
|
||||
* 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 job prototype selected.
|
||||
/// </summary>
|
||||
private JobPrototype? _dummyJob;
|
||||
|
||||
// TODO: Load the species directly and don't update entity ever.
|
||||
public event Action<EntityUid>? PreviewDummyUpdated;
|
||||
|
||||
private HumanoidCharacterProfile? _profile;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
|
||||
}
|
||||
|
||||
private void PreferencesDataLoaded()
|
||||
{
|
||||
UpdateProfile();
|
||||
}
|
||||
|
||||
public void OnStateEntered(LobbyState state)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnStateExited(LobbyState state)
|
||||
{
|
||||
EntityManager.DeleteEntity(_previewDummy);
|
||||
_previewDummy = null;
|
||||
}
|
||||
|
||||
public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel)
|
||||
{
|
||||
_previewPanel = panel;
|
||||
ReloadProfile();
|
||||
}
|
||||
|
||||
public void SetClothes(bool value)
|
||||
{
|
||||
if (_showClothes == value)
|
||||
return;
|
||||
|
||||
_showClothes = value;
|
||||
ReloadCharacterUI();
|
||||
}
|
||||
|
||||
public void SetDummyJob(JobPrototype? job)
|
||||
{
|
||||
_dummyJob = job;
|
||||
ReloadCharacterUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the character only with the specified profile change.
|
||||
/// </summary>
|
||||
public void ReloadProfile()
|
||||
{
|
||||
// Test moment
|
||||
if (_profile == null || _stateManager.CurrentState is not LobbyState)
|
||||
return;
|
||||
|
||||
// Ignore job clothes and the likes so we don't spam entities out every frame of color changes.
|
||||
var previewDummy = EnsurePreviewDummy(_profile);
|
||||
_humanoid.LoadProfile(previewDummy, _profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the currently selected character's preview.
|
||||
/// </summary>
|
||||
public void ReloadCharacterUI()
|
||||
{
|
||||
// Test moment
|
||||
if (_profile == null || _stateManager.CurrentState is not LobbyState)
|
||||
return;
|
||||
|
||||
EntityManager.DeleteEntity(_previewDummy);
|
||||
_previewDummy = null;
|
||||
_previewDummy = EnsurePreviewDummy(_profile);
|
||||
_previewPanel?.SetSprite(_previewDummy.Value);
|
||||
_previewPanel?.SetSummaryText(_profile.Summary);
|
||||
_humanoid.LoadProfile(_previewDummy.Value, _profile);
|
||||
|
||||
if (_showClothes)
|
||||
GiveDummyJobClothesLoadout(_previewDummy.Value, _profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates character profile to the default.
|
||||
/// </summary>
|
||||
public void UpdateProfile()
|
||||
{
|
||||
if (!_preferencesManager.ServerDataLoaded)
|
||||
{
|
||||
_profile = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter)
|
||||
{
|
||||
_profile = selectedCharacter;
|
||||
_previewPanel?.SetLoaded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previewPanel?.SetSummaryText(string.Empty);
|
||||
_previewPanel?.SetLoaded(false);
|
||||
}
|
||||
|
||||
ReloadCharacterUI();
|
||||
}
|
||||
|
||||
public void UpdateProfile(HumanoidCharacterProfile? profile)
|
||||
{
|
||||
if (_profile?.Equals(profile) == true)
|
||||
return;
|
||||
|
||||
if (_stateManager.CurrentState is not LobbyState)
|
||||
return;
|
||||
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile)
|
||||
{
|
||||
if (_previewDummy != null)
|
||||
return _previewDummy.Value;
|
||||
|
||||
_previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(profile.Species).DollPrototype, MapCoordinates.Nullspace);
|
||||
PreviewDummyUpdated?.Invoke(_previewDummy.Value);
|
||||
return _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;
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml
Normal file
22
Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
45
Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
Normal file
45
Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,9 @@
|
||||
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
|
||||
{
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Buckle;
|
||||
using Content.Client.Gravity;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
@@ -14,6 +20,9 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly GravitySystem _gravity = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -21,6 +30,9 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
}
|
||||
|
||||
private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
|
||||
@@ -34,8 +46,6 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
|
||||
if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
|
||||
{
|
||||
component.IsCurrentlyWaddling = false;
|
||||
|
||||
var stopped = new StoppedWaddlingEvent(entity);
|
||||
|
||||
RaiseLocalEvent(entity, ref stopped);
|
||||
@@ -47,8 +57,6 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
|
||||
return;
|
||||
|
||||
component.IsCurrentlyWaddling = true;
|
||||
|
||||
var started = new StartedWaddlingEvent(entity);
|
||||
|
||||
RaiseLocalEvent(entity, ref started);
|
||||
@@ -57,19 +65,25 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
|
||||
{
|
||||
if (_animation.HasRunningAnimation(uid, component.KeyName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<InputMoverComponent>(uid, out var mover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gravity.IsWeightless(uid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!_actionBlocker.CanMove(uid, mover))
|
||||
return;
|
||||
|
||||
// Do nothing if buckled in
|
||||
if (_buckle.IsBuckled(uid))
|
||||
return;
|
||||
|
||||
// Do nothing if crit or dead (for obvious reasons)
|
||||
if (_mobState.IsIncapacitated(uid))
|
||||
return;
|
||||
|
||||
var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
|
||||
var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
|
||||
@@ -114,6 +128,36 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
|
||||
private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
var started = new StartedWaddlingEvent(uid);
|
||||
|
||||
RaiseLocalEvent(uid, ref started);
|
||||
}
|
||||
|
||||
private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
|
||||
{
|
||||
if (!component.IsCurrentlyWaddling)
|
||||
return;
|
||||
|
||||
_animation.Stop(uid, component.KeyName);
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
@@ -123,13 +167,7 @@ public sealed class WaddleAnimationSystem : EntitySystem
|
||||
|
||||
sprite.Offset = new Vector2();
|
||||
sprite.Rotation = Angle.FromDegrees(0);
|
||||
|
||||
component.IsCurrentlyWaddling = false;
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
var started = new StartedWaddlingEvent(uid);
|
||||
|
||||
RaiseLocalEvent(uid, ref started);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ using Robust.Client;
|
||||
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!;
|
||||
@@ -133,5 +134,13 @@ public sealed class JobRequirementsManager
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session)
|
||||
{
|
||||
if (session != _playerManager.LocalSession)
|
||||
{
|
||||
return new Dictionary<string, TimeSpan>();
|
||||
}
|
||||
|
||||
return _roles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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;
|
||||
@@ -20,8 +18,7 @@ namespace Content.Client.Preferences
|
||||
{
|
||||
[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;
|
||||
|
||||
@@ -64,7 +61,8 @@ namespace Content.Client.Preferences
|
||||
|
||||
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
|
||||
|
||||
41
Content.Client/Preferences/UI/AntagPreferenceSelector.cs
Normal file
41
Content.Client/Preferences/UI/AntagPreferenceSelector.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<BoxContainer Name="CharEditor" />
|
||||
<BoxContainer Name="CharEditor" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -3,27 +3,23 @@ using System.Numerics;
|
||||
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;
|
||||
@@ -36,7 +32,6 @@ namespace Content.Client.Preferences.UI
|
||||
private readonly IClientPreferencesManager _preferencesManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
private readonly Button _createNewCharacterButton;
|
||||
private readonly HumanoidProfileEditor _humanoidProfileEditor;
|
||||
|
||||
@@ -51,7 +46,6 @@ namespace Content.Client.Preferences.UI
|
||||
_entityManager = entityManager;
|
||||
_prototypeManager = prototypeManager;
|
||||
_preferencesManager = preferencesManager;
|
||||
_configurationManager = configurationManager;
|
||||
|
||||
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
@@ -74,7 +68,7 @@ namespace Content.Client.Preferences.UI
|
||||
args.Event.Handle();
|
||||
};
|
||||
|
||||
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
|
||||
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
|
||||
_humanoidProfileEditor.OnProfileChanged += ProfileChanged;
|
||||
CharEditor.AddChild(_humanoidProfileEditor);
|
||||
|
||||
@@ -103,6 +97,12 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void UpdateControls()
|
||||
{
|
||||
// Reset sliders etc. upon going going back to GUI.
|
||||
_humanoidProfileEditor.LoadServerData();
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
var numberOfFullSlots = 0;
|
||||
@@ -120,11 +120,6 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
|
||||
{
|
||||
if (character is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
numberOfFullSlots++;
|
||||
var characterPickerButton = new CharacterPickerButton(_entityManager,
|
||||
_preferencesManager,
|
||||
@@ -148,8 +143,12 @@ namespace Content.Client.Preferences.UI
|
||||
_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;
|
||||
@@ -180,7 +179,15 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
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;
|
||||
|
||||
11
Content.Client/Preferences/UI/HighlightedContainer.xaml
Normal file
11
Content.Client/Preferences/UI/HighlightedContainer.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<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>
|
||||
14
Content.Client/Preferences/UI/HighlightedContainer.xaml.cs
Normal file
14
Content.Client/Preferences/UI/HighlightedContainer.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ namespace Content.Client.Preferences.UI
|
||||
{
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
|
||||
private void RandomizeEverything()
|
||||
{
|
||||
Profile = HumanoidCharacterProfile.Random();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<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-->
|
||||
@@ -58,7 +58,9 @@
|
||||
<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 -->
|
||||
@@ -85,18 +87,6 @@
|
||||
<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'}" />
|
||||
@@ -151,7 +141,7 @@
|
||||
</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" />
|
||||
@@ -159,5 +149,4 @@
|
||||
<Button Name="CSpriteRotateRight" Text="▶" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -2,69 +2,48 @@ using System.Linq;
|
||||
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;
|
||||
@@ -72,8 +51,6 @@ namespace Content.Client.Preferences.UI
|
||||
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;
|
||||
@@ -88,44 +65,39 @@ namespace Content.Client.Preferences.UI
|
||||
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); };
|
||||
@@ -139,8 +111,6 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
|
||||
|
||||
ShowClothes.OnPressed += ToggleClothes;
|
||||
|
||||
#region Sex
|
||||
|
||||
_sexButton.OnItemSelected += args =>
|
||||
@@ -220,7 +190,7 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithHairStyleName(newStyle.id));
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_hairPicker.OnColorChanged += newColor =>
|
||||
@@ -230,7 +200,7 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
|
||||
UpdateCMarkingsHair();
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_facialHairPicker.OnMarkingSelect += newStyle =>
|
||||
@@ -239,7 +209,7 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_facialHairPicker.OnColorChanged += newColor =>
|
||||
@@ -249,7 +219,7 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
|
||||
UpdateCMarkingsFacialHair();
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_hairPicker.OnSlotRemove += _ =>
|
||||
@@ -261,7 +231,7 @@ namespace Content.Client.Preferences.UI
|
||||
);
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_facialHairPicker.OnSlotRemove += _ =>
|
||||
@@ -273,7 +243,7 @@ namespace Content.Client.Preferences.UI
|
||||
);
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsFacialHair();
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_hairPicker.OnSlotAdd += delegate()
|
||||
@@ -293,7 +263,7 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_facialHairPicker.OnSlotAdd += delegate()
|
||||
@@ -313,38 +283,11 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsFacialHair();
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
#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>())
|
||||
@@ -369,7 +312,7 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithEyeColor(newColor));
|
||||
CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
#endregion Eyes
|
||||
@@ -393,46 +336,22 @@ namespace Content.Client.Preferences.UI
|
||||
_preferenceUnavailableButton.SelectId(args.Id);
|
||||
|
||||
Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_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();
|
||||
@@ -450,7 +369,7 @@ namespace Content.Client.Preferences.UI
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
Profile = Profile?.WithTraitPreference(trait.ID, preference);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -483,7 +402,7 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
#region FlavorText
|
||||
|
||||
if (_configurationManager.GetCVar(CCVars.FlavorText))
|
||||
if (configurationManager.GetCVar(CCVars.FlavorText))
|
||||
{
|
||||
var flavorText = new FlavorText.FlavorText();
|
||||
_tabContainer.AddChild(flavorText);
|
||||
@@ -500,22 +419,14 @@ namespace Content.Client.Preferences.UI
|
||||
_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
|
||||
@@ -525,6 +436,13 @@ namespace Content.Client.Preferences.UI
|
||||
LoadServerData();
|
||||
}
|
||||
|
||||
ShowClothes.OnToggled += args =>
|
||||
{
|
||||
var lobby = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
lobby.SetClothes(args.Pressed);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
preferencesManager.OnServerDataLoaded += LoadServerData;
|
||||
|
||||
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
|
||||
@@ -532,28 +450,69 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
|
||||
IsDirty = false;
|
||||
controller.UpdateProfile();
|
||||
}
|
||||
|
||||
private void SetDirty()
|
||||
{
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadCharacterUI();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
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);
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, preference);
|
||||
SetDirty();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateRoleRequirements()
|
||||
@@ -614,10 +573,19 @@ namespace Content.Client.Preferences.UI
|
||||
.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;
|
||||
|
||||
// Clone so we don't modify the underlying loadout.
|
||||
Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
|
||||
loadout = loadout?.Clone();
|
||||
var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager)
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
|
||||
if (!_requirements.IsAllowed(job, out var reason))
|
||||
{
|
||||
@@ -627,10 +595,15 @@ namespace Content.Client.Preferences.UI
|
||||
category.AddChild(selector);
|
||||
_jobPriorities.Add(selector);
|
||||
|
||||
selector.LoadoutUpdated += args =>
|
||||
{
|
||||
Profile = Profile?.WithLoadout(args);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
selector.PriorityChanged += priority =>
|
||||
{
|
||||
Profile = Profile?.WithJobPriority(job.ID, priority);
|
||||
IsDirty = true;
|
||||
|
||||
foreach (var jobSelector in _jobPriorities)
|
||||
{
|
||||
@@ -646,6 +619,8 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
|
||||
}
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -663,7 +638,7 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
|
||||
Profile = Profile.WithFlavorText(content);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void OnMarkingChange(MarkingSet markings)
|
||||
@@ -672,20 +647,12 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
|
||||
_needUpdatePreview = true;
|
||||
IsDirty = true;
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadProfile();
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -737,6 +704,9 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
|
||||
IsDirty = true;
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadProfile();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -745,39 +715,28 @@ namespace Content.Client.Preferences.UI
|
||||
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()
|
||||
public void LoadServerData()
|
||||
{
|
||||
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
|
||||
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
|
||||
|
||||
UpdateAntagRequirements();
|
||||
UpdateRoleRequirements();
|
||||
UpdateControls();
|
||||
_needUpdatePreview = true;
|
||||
ShowClothes.Pressed = true;
|
||||
}
|
||||
|
||||
private void SetAge(int newAge)
|
||||
{
|
||||
Profile = Profile?.WithAge(newAge);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetSex(Sex newSex)
|
||||
@@ -798,13 +757,13 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
UpdateGenderControls();
|
||||
CMarkings.SetSex(newSex);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetGender(Gender newGender)
|
||||
{
|
||||
Profile = Profile?.WithGender(newGender);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetSpecies(string newSpecies)
|
||||
@@ -813,46 +772,34 @@ namespace Content.Client.Preferences.UI
|
||||
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;
|
||||
SetDirty();
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
private void SetName(string newName)
|
||||
{
|
||||
Profile = Profile?.WithName(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;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
||||
{
|
||||
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
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);
|
||||
// Reset profile to default.
|
||||
UserInterfaceManager.GetUIController<LobbyUIController>().UpdateProfile();
|
||||
}
|
||||
|
||||
private bool IsDirty
|
||||
@@ -861,7 +808,6 @@ namespace Content.Client.Preferences.UI
|
||||
set
|
||||
{
|
||||
_isDirty = value;
|
||||
_needUpdatePreview = true;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
}
|
||||
@@ -981,7 +927,7 @@ namespace Content.Client.Preferences.UI
|
||||
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
return;
|
||||
|
||||
var style = speciesProto.GuideBookIcon;
|
||||
const string style = "SpeciesInfoDefault";
|
||||
SpeciesInfoButton.StyleClasses.Add(style);
|
||||
}
|
||||
|
||||
@@ -1017,26 +963,6 @@ namespace Content.Client.Preferences.UI
|
||||
_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)
|
||||
@@ -1166,13 +1092,13 @@ namespace Content.Client.Preferences.UI
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
var humanoid = _entMan.System<HumanoidAppearanceSystem>();
|
||||
humanoid.LoadProfile(_previewDummy!.Value, Profile);
|
||||
UserInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
|
||||
SetPreviewRotation(_previewRotation);
|
||||
}
|
||||
|
||||
if (ShowClothes.Pressed)
|
||||
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
|
||||
|
||||
_previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
|
||||
private void SetPreviewRotation(Direction direction)
|
||||
{
|
||||
_previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
|
||||
}
|
||||
|
||||
public void UpdateControls()
|
||||
@@ -1184,17 +1110,15 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateGenderControls();
|
||||
UpdateSkinColor();
|
||||
UpdateSpecies();
|
||||
UpdateClothingControls();
|
||||
UpdateBackpackControls();
|
||||
UpdateSpawnPriorityControls();
|
||||
UpdateAgeEdit();
|
||||
UpdateEyePickers();
|
||||
UpdateSaveButton();
|
||||
UpdateLoadouts();
|
||||
UpdateJobPriorities();
|
||||
UpdateAntagPreferences();
|
||||
UpdateTraitPreferences();
|
||||
UpdateMarkings();
|
||||
RebuildSpriteView();
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
UpdateCMarkingsFacialHair();
|
||||
@@ -1202,17 +1126,6 @@ namespace Content.Client.Preferences.UI
|
||||
_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)
|
||||
@@ -1225,143 +1138,11 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
foreach (var prioritySelector in _jobPriorities)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1386,41 +1167,6 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
46
Content.Client/Preferences/UI/JobPrioritySelector.cs
Normal file
46
Content.Client/Preferences/UI/JobPrioritySelector.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
15
Content.Client/Preferences/UI/LoadoutContainer.xaml
Normal file
15
Content.Client/Preferences/UI/LoadoutContainer.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
74
Content.Client/Preferences/UI/LoadoutContainer.xaml.cs
Normal file
74
Content.Client/Preferences/UI/LoadoutContainer.xaml.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
10
Content.Client/Preferences/UI/LoadoutGroupContainer.xaml
Normal file
10
Content.Client/Preferences/UI/LoadoutGroupContainer.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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>
|
||||
93
Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs
Normal file
93
Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Content.Client/Preferences/UI/LoadoutWindow.xaml
Normal file
10
Content.Client/Preferences/UI/LoadoutWindow.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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>
|
||||
60
Content.Client/Preferences/UI/LoadoutWindow.xaml.cs
Normal file
60
Content.Client/Preferences/UI/LoadoutWindow.xaml.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Content.Client/Preferences/UI/RequirementsSelector.cs
Normal file
222
Content.Client/Preferences/UI/RequirementsSelector.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
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.ReloadProfile();
|
||||
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.ReloadProfile();
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public sealed partial class MindTests
|
||||
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**
|
||||
@@ -28,6 +28,12 @@ public sealed partial class MindTests
|
||||
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.
|
||||
@@ -36,7 +42,7 @@ public sealed partial class MindTests
|
||||
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
|
||||
@@ -45,9 +51,9 @@ public sealed partial class MindTests
|
||||
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();
|
||||
|
||||
44
Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
Normal file
44
Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ using Content.Server.Database;
|
||||
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;
|
||||
@@ -53,8 +55,6 @@ namespace Content.IntegrationTests.Tests.Preferences
|
||||
Color.Beige,
|
||||
new ()
|
||||
),
|
||||
ClothingPreference.Jumpskirt,
|
||||
BackpackPreference.Backpack,
|
||||
SpawnPriorityPreference.None,
|
||||
new Dictionary<string, JobPriority>
|
||||
{
|
||||
@@ -62,7 +62,8 @@ namespace Content.IntegrationTests.Tests.Preferences
|
||||
},
|
||||
PreferenceUnavailableMode.StayInLobby,
|
||||
new List<string> (),
|
||||
new List<string>()
|
||||
new List<string>(),
|
||||
new Dictionary<string, RoleLoadout>()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
1838
Content.Server.Database/Migrations/Postgres/20240301130641_ClothingRemoval.Designer.cs
generated
Normal file
1838
Content.Server.Database/Migrations/Postgres/20240301130641_ClothingRemoval.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
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: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
1884
Content.Server.Database/Migrations/Postgres/20240403072242_Loadouts.Designer.cs
generated
Normal file
1884
Content.Server.Database/Migrations/Postgres/20240403072242_Loadouts.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -735,21 +735,11 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.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")
|
||||
@@ -832,6 +822,84 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
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")
|
||||
@@ -1519,6 +1587,42 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
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")
|
||||
@@ -1731,9 +1835,21 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
|
||||
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");
|
||||
|
||||
1765
Content.Server.Database/Migrations/Sqlite/20240301130602_ClothingRemoval.Designer.cs
generated
Normal file
1765
Content.Server.Database/Migrations/Sqlite/20240301130602_ClothingRemoval.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
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: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
1809
Content.Server.Database/Migrations/Sqlite/20240403072258_Loadouts.Designer.cs
generated
Normal file
1809
Content.Server.Database/Migrations/Sqlite/20240403072258_Loadouts.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -688,21 +688,11 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.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")
|
||||
@@ -785,6 +775,78 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
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")
|
||||
@@ -1450,6 +1512,42 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
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")
|
||||
@@ -1662,9 +1760,21 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
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");
|
||||
|
||||
@@ -56,8 +56,26 @@ namespace Content.Server.Database
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<Trait>()
|
||||
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
|
||||
.IsUnique();
|
||||
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<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);
|
||||
@@ -337,13 +355,13 @@ namespace Content.Server.Database
|
||||
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; }
|
||||
@@ -387,6 +405,79 @@ namespace Content.Server.Database
|
||||
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.
|
||||
@@ -875,8 +966,35 @@ namespace Content.Server.Database
|
||||
public byte[] Data { get; set; } = default!;
|
||||
}
|
||||
|
||||
// Note: this interface isn't used by the game, but it *is* used by SS14.Admin.
|
||||
// Don't remove! Or face the consequences!
|
||||
public interface IAdminRemarksCommon
|
||||
{
|
||||
public int Id { get; }
|
||||
|
||||
public int? RoundId { get; }
|
||||
public Round? Round { get; }
|
||||
|
||||
public Guid? PlayerUserId { get; }
|
||||
public Player? Player { get; }
|
||||
public TimeSpan PlaytimeAtNote { get; }
|
||||
|
||||
public string Message { get; }
|
||||
|
||||
public Player? CreatedBy { get; }
|
||||
|
||||
public DateTime CreatedAt { get; }
|
||||
|
||||
public Player? LastEditedBy { get; }
|
||||
|
||||
public DateTime? LastEditedAt { get; }
|
||||
public DateTime? ExpirationTime { get; }
|
||||
|
||||
public bool Deleted { get; }
|
||||
}
|
||||
|
||||
[Index(nameof(PlayerUserId))]
|
||||
public class AdminNote
|
||||
public class AdminNote : IAdminRemarksCommon
|
||||
{
|
||||
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
@@ -910,7 +1028,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Index(nameof(PlayerUserId))]
|
||||
public class AdminWatchlist
|
||||
public class AdminWatchlist : IAdminRemarksCommon
|
||||
{
|
||||
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
@@ -941,7 +1059,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Index(nameof(PlayerUserId))]
|
||||
public class AdminMessage
|
||||
public class AdminMessage : IAdminRemarksCommon
|
||||
{
|
||||
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Content.Server.Administration.Commands
|
||||
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;
|
||||
|
||||
@@ -149,12 +149,12 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
||||
var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
|
||||
AddComp(uid, relation);
|
||||
|
||||
MetaData.SetEntityName(uid, $"solution - {name}");
|
||||
ContainerSystem.Insert(uid, container, force: true);
|
||||
|
||||
return (uid, solution, relation);
|
||||
}
|
||||
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
|
||||
|
||||
@@ -13,6 +13,8 @@ using Content.Shared.Database;
|
||||
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;
|
||||
@@ -40,6 +42,10 @@ namespace Content.Server.Database
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
|
||||
.Include(p => p.Profiles)
|
||||
.ThenInclude(h => h.Loadouts)
|
||||
.ThenInclude(l => l.Groups)
|
||||
.ThenInclude(group => group.Loadouts)
|
||||
.AsSingleQuery()
|
||||
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
|
||||
|
||||
@@ -88,6 +94,9 @@ namespace Content.Server.Database
|
||||
.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);
|
||||
|
||||
@@ -179,14 +188,6 @@ namespace Content.Server.Database
|
||||
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;
|
||||
@@ -209,6 +210,27 @@ namespace Content.Server.Database
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -226,13 +248,12 @@ namespace Content.Server.Database
|
||||
Color.FromHex(profile.SkinColor),
|
||||
markings
|
||||
),
|
||||
clothing,
|
||||
backpack,
|
||||
spawnPriority,
|
||||
jobs,
|
||||
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
|
||||
antags.ToList(),
|
||||
traits.ToList()
|
||||
traits.ToList(),
|
||||
loadouts
|
||||
);
|
||||
}
|
||||
|
||||
@@ -259,8 +280,6 @@ namespace Content.Server.Database
|
||||
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;
|
||||
@@ -285,6 +304,36 @@ namespace Content.Server.Database
|
||||
.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
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors;
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class BurnBodyBehavior : IThresholdBehavior
|
||||
{
|
||||
|
||||
public void Execute(EntityUid bodyId, DestructibleSystem system, EntityUid? cause = null)
|
||||
{
|
||||
var transformSystem = system.EntityManager.System<TransformSystem>();
|
||||
var inventorySystem = system.EntityManager.System<InventorySystem>();
|
||||
var sharedPopupSystem = system.EntityManager.System<SharedPopupSystem>();
|
||||
|
||||
if (!system.EntityManager.TryGetComponent<InventoryComponent>(bodyId, out var comp))
|
||||
return;
|
||||
|
||||
foreach (var item in inventorySystem.GetHandOrInventoryEntities(bodyId))
|
||||
{
|
||||
transformSystem.DropNextTo(item, bodyId);
|
||||
}
|
||||
|
||||
sharedPopupSystem.PopupCoordinates(Loc.GetString("bodyburn-text-others", ("name", bodyId)), transformSystem.GetMoverCoordinates(bodyId), PopupType.LargeCaution);
|
||||
|
||||
system.EntityManager.QueueDeleteEntity(bodyId);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,10 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
public float Offset { get; set; } = 0.5f;
|
||||
|
||||
[DataField("transferForensics")]
|
||||
public bool DoTransferForensics = false;
|
||||
public bool DoTransferForensics;
|
||||
|
||||
[DataField]
|
||||
public bool SpawnInContainer;
|
||||
|
||||
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
|
||||
{
|
||||
@@ -49,7 +52,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.ComponentFactory))
|
||||
{
|
||||
var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
var spawned = SpawnInContainer
|
||||
? system.EntityManager.SpawnNextToOrDrop(entityId, owner)
|
||||
: system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
system.StackSystem.SetCount(spawned, count);
|
||||
|
||||
TransferForensics(spawned, system, owner);
|
||||
@@ -58,7 +63,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
var spawned = SpawnInContainer
|
||||
? system.EntityManager.SpawnNextToOrDrop(entityId, owner)
|
||||
: system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||
|
||||
TransferForensics(spawned, system, owner);
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
_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");
|
||||
|
||||
@@ -249,7 +249,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
|
||||
|
||||
_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);
|
||||
|
||||
@@ -62,6 +62,7 @@ public class IdentitySystem : SharedIdentitySystem
|
||||
{
|
||||
var ident = Spawn(null, Transform(uid).Coordinates);
|
||||
|
||||
_metaData.SetEntityName(ident, "identity");
|
||||
QueueIdentityUpdate(uid);
|
||||
_container.Insert(ident, component.IdentityEntitySlot);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Server.Worldgen.Tools;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
|
||||
namespace Content.Server.IoC
|
||||
{
|
||||
@@ -58,6 +59,7 @@ namespace Content.Server.IoC
|
||||
IoCManager.Register<PoissonDiskSampler>();
|
||||
IoCManager.Register<DiscordWebhook>();
|
||||
IoCManager.Register<ServerDbEntryManager>();
|
||||
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
||||
IoCManager.Register<ServerApi>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,16 +357,17 @@ public sealed class MindSystem : SharedMindSystem
|
||||
mind.UserId = userId;
|
||||
mind.OriginalOwnerUserId ??= userId;
|
||||
|
||||
// The UserId may not have a current session, but user data may still exist for disconnected players.
|
||||
// So we cannot combine this with the TryGetSessionById() check below.
|
||||
if (_players.GetPlayerData(userId.Value).ContentData() is { } data)
|
||||
data.Mind = mindId;
|
||||
|
||||
if (_players.TryGetSessionById(userId.Value, out var ret))
|
||||
{
|
||||
mind.Session = ret;
|
||||
_pvsOverride.AddSessionOverride(netMind, ret);
|
||||
_players.SetAttachedEntity(ret, mind.CurrentEntity);
|
||||
}
|
||||
|
||||
// session may be null, but user data may still exist for disconnected players.
|
||||
if (_players.GetPlayerData(userId.Value).ContentData() is { } data)
|
||||
data.Mind = mindId;
|
||||
}
|
||||
|
||||
public void ControlMob(EntityUid user, EntityUid target)
|
||||
|
||||
@@ -54,7 +54,7 @@ public delegate void CalcPlayTimeTrackersCallback(ICommonSession player, HashSet
|
||||
/// 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!;
|
||||
@@ -201,6 +201,11 @@ public sealed class PlayTimeTrackingManager
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session)
|
||||
{
|
||||
return GetTrackerTimes(session);
|
||||
}
|
||||
|
||||
private void SendPlayTimes(ICommonSession pSession)
|
||||
{
|
||||
var roles = GetTrackerTimes(pSession);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.CCVar;
|
||||
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;
|
||||
@@ -25,6 +26,7 @@ namespace Content.Server.Preferences.Managers
|
||||
[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.
|
||||
@@ -98,8 +100,10 @@ namespace Content.Server.Preferences.Managers
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -260,17 +264,20 @@ namespace Content.Server.Preferences.Managers
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class SpawnPointSystem : EntitySystem
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.DetailExaminable;
|
||||
using Content.Server.Humanoid;
|
||||
@@ -10,10 +11,12 @@ using Content.Server.Station.Components;
|
||||
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;
|
||||
@@ -86,7 +89,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
|
||||
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);
|
||||
@@ -101,9 +104,11 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +139,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
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)
|
||||
@@ -176,11 +181,49 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
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? roleProto))
|
||||
{
|
||||
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 => roleProto.Groups.FindIndex(e => e == x.Key)))
|
||||
{
|
||||
foreach (var items in group.Value)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(items.Prototype, out var loadoutProto))
|
||||
{
|
||||
Log.Error($"Unable to find loadout prototype for {items.Prototype}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(loadoutProto.Equipment, out var startingGear))
|
||||
{
|
||||
Log.Error($"Unable to find starting gear {loadoutProto.Equipment} for loadout {loadoutProto}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle any extra data here.
|
||||
EquipStartingGear(entity.Value, startingGear);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
_humanoidSystem.LoadProfile(entity.Value, profile);
|
||||
|
||||
@@ -57,6 +57,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
|
||||
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
|
||||
[Dependency] protected readonly SharedHandsSystem Hands = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
|
||||
[Dependency] protected readonly MetaDataSystem MetaData = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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;
|
||||
@@ -24,12 +26,94 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,14 +66,14 @@ public sealed partial class SpeciesPrototype : IPrototype
|
||||
/// <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.
|
||||
@@ -120,12 +120,6 @@ public sealed partial class SpeciesPrototype : IPrototype
|
||||
/// </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
|
||||
|
||||
@@ -26,6 +26,7 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SharedPlayerSystem _player = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
protected readonly Dictionary<NetUserId, EntityUid> UserMinds = new();
|
||||
@@ -107,6 +108,7 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
TryComp(mindIdValue, out mind))
|
||||
{
|
||||
DebugTools.Assert(mind.UserId == user);
|
||||
|
||||
mindId = mindIdValue;
|
||||
return true;
|
||||
}
|
||||
@@ -422,29 +424,26 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
return TryComp(mindId, out mind);
|
||||
}
|
||||
|
||||
public bool TryGetMind(
|
||||
ContentPlayerData contentPlayer,
|
||||
out EntityUid mindId,
|
||||
[NotNullWhen(true)] out MindComponent? mind)
|
||||
{
|
||||
mindId = contentPlayer.Mind ?? default;
|
||||
return TryComp(mindId, out mind);
|
||||
}
|
||||
|
||||
// TODO MIND make this return a nullable EntityUid or Entity<MindComponent>
|
||||
public bool TryGetMind(
|
||||
ICommonSession? player,
|
||||
out EntityUid mindId,
|
||||
[NotNullWhen(true)] out MindComponent? mind)
|
||||
{
|
||||
mindId = default;
|
||||
mind = null;
|
||||
if (_player.ContentData(player) is not { } data)
|
||||
if (player == null)
|
||||
{
|
||||
mindId = default;
|
||||
mind = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryGetMind(data, out mindId, out mind))
|
||||
if (TryGetMind(player.UserId, out var mindUid, out mind))
|
||||
{
|
||||
mindId = mindUid.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
DebugTools.AssertNull(data.Mind);
|
||||
mindId = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Content.Shared.Preferences
|
||||
{
|
||||
/// <summary>
|
||||
/// The backpack preference for a profile. Stored in database!
|
||||
/// </summary>
|
||||
public enum BackpackPreference
|
||||
{
|
||||
Backpack,
|
||||
Satchel,
|
||||
Duffelbag
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Content.Shared.Preferences
|
||||
{
|
||||
/// <summary>
|
||||
/// The clothing preference for a profile. Stored in database!
|
||||
/// </summary>
|
||||
public enum ClothingPreference
|
||||
{
|
||||
Jumpsuit,
|
||||
Jumpskirt
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
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;
|
||||
@@ -31,6 +33,11 @@ namespace Content.Shared.Preferences
|
||||
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,
|
||||
@@ -39,13 +46,12 @@ namespace Content.Shared.Preferences
|
||||
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;
|
||||
@@ -54,13 +60,12 @@ namespace Content.Shared.Preferences
|
||||
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>
|
||||
@@ -68,15 +73,16 @@ namespace Content.Shared.Preferences
|
||||
HumanoidCharacterProfile other,
|
||||
Dictionary<string, JobPriority> jobPriorities,
|
||||
List<string> antagPreferences,
|
||||
List<string> traitPreferences)
|
||||
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack, other.SpawnPriority,
|
||||
jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences)
|
||||
List<string> traitPreferences,
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -88,15 +94,14 @@ namespace Content.Shared.Preferences
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -113,8 +118,6 @@ namespace Content.Shared.Preferences
|
||||
Sex.Male,
|
||||
Gender.Male,
|
||||
new HumanoidCharacterAppearance(),
|
||||
ClothingPreference.Jumpsuit,
|
||||
BackpackPreference.Backpack,
|
||||
SpawnPriorityPreference.None,
|
||||
new Dictionary<string, JobPriority>
|
||||
{
|
||||
@@ -122,7 +125,8 @@ namespace Content.Shared.Preferences
|
||||
},
|
||||
PreferenceUnavailableMode.SpawnAsOverflow,
|
||||
new List<string>(),
|
||||
new List<string>())
|
||||
new List<string>(),
|
||||
new Dictionary<string, RoleLoadout>())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -141,8 +145,6 @@ namespace Content.Shared.Preferences
|
||||
Sex.Male,
|
||||
Gender.Male,
|
||||
HumanoidCharacterAppearance.DefaultWithSpecies(species),
|
||||
ClothingPreference.Jumpsuit,
|
||||
BackpackPreference.Backpack,
|
||||
SpawnPriorityPreference.None,
|
||||
new Dictionary<string, JobPriority>
|
||||
{
|
||||
@@ -150,7 +152,8 @@ namespace Content.Shared.Preferences
|
||||
},
|
||||
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.
|
||||
@@ -195,11 +198,11 @@ namespace Content.Shared.Preferences
|
||||
|
||||
var name = GetName(species, gender);
|
||||
|
||||
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, SpawnPriorityPreference.None,
|
||||
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), 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; }
|
||||
@@ -219,8 +222,6 @@ namespace Content.Shared.Preferences
|
||||
|
||||
[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;
|
||||
@@ -263,21 +264,14 @@ namespace Content.Shared.Preferences
|
||||
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)
|
||||
@@ -291,7 +285,7 @@ namespace Content.Shared.Preferences
|
||||
{
|
||||
dictionary[jobId] = priority;
|
||||
}
|
||||
return new(this, dictionary, _antagPreferences, _traitPreferences);
|
||||
return new(this, dictionary, _antagPreferences, _traitPreferences, _loadouts);
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
|
||||
@@ -301,7 +295,7 @@ namespace Content.Shared.Preferences
|
||||
|
||||
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
|
||||
{
|
||||
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences);
|
||||
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences, _loadouts);
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
|
||||
@@ -321,7 +315,8 @@ namespace Content.Shared.Preferences
|
||||
list.Remove(antagId);
|
||||
}
|
||||
}
|
||||
return new(this, _jobPriorities, list, _traitPreferences);
|
||||
|
||||
return new(this, _jobPriorities, list, _traitPreferences, _loadouts);
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
|
||||
@@ -343,7 +338,7 @@ namespace Content.Shared.Preferences
|
||||
list.Remove(traitId);
|
||||
}
|
||||
}
|
||||
return new(this, _jobPriorities, _antagPreferences, list);
|
||||
return new(this, _jobPriorities, _antagPreferences, list, _loadouts);
|
||||
}
|
||||
|
||||
public string Summary =>
|
||||
@@ -362,17 +357,19 @@ namespace Content.Shared.Preferences
|
||||
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;
|
||||
@@ -455,21 +452,6 @@ namespace Content.Shared.Preferences
|
||||
_ => 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,
|
||||
@@ -502,8 +484,6 @@ namespace Content.Shared.Preferences
|
||||
Sex = sex;
|
||||
Gender = gender;
|
||||
Appearance = appearance;
|
||||
Clothing = clothing;
|
||||
Backpack = backpack;
|
||||
SpawnPriority = spawnPriority;
|
||||
|
||||
_jobPriorities.Clear();
|
||||
@@ -520,12 +500,31 @@ namespace Content.Shared.Preferences
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
@@ -551,16 +550,49 @@ namespace Content.Shared.Preferences
|
||||
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 HumanoidCharacterProfile WithLoadout(RoleLoadout loadout)
|
||||
{
|
||||
// Deep copies so we don't modify the DB profile.
|
||||
var copied = new Dictionary<string, RoleLoadout>();
|
||||
|
||||
foreach (var proto in _loadouts)
|
||||
{
|
||||
if (proto.Key == loadout.Role)
|
||||
continue;
|
||||
|
||||
copied[proto.Key] = proto.Value.Clone();
|
||||
}
|
||||
|
||||
copied[loadout.Role] = loadout.Clone();
|
||||
return new(this, _jobPriorities, _antagPreferences, _traitPreferences, copied);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Preferences
|
||||
@@ -15,11 +16,11 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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>());
|
||||
}
|
||||
}
|
||||
20
Content.Shared/Preferences/Loadouts/Effects/LoadoutEffect.cs
Normal file
20
Content.Shared/Preferences/Loadouts/Effects/LoadoutEffect.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
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) {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
13
Content.Shared/Preferences/Loadouts/Loadout.cs
Normal file
13
Content.Shared/Preferences/Loadouts/Loadout.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
||||
34
Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs
Normal file
34
Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
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();
|
||||
}
|
||||
25
Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs
Normal file
25
Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
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();
|
||||
}
|
||||
260
Content.Shared/Preferences/Loadouts/RoleLoadout.cs
Normal file
260
Content.Shared/Preferences/Loadouts/RoleLoadout.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
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;
|
||||
}
|
||||
|
||||
public RoleLoadout Clone()
|
||||
{
|
||||
var weh = new RoleLoadout(Role);
|
||||
|
||||
foreach (var selected in SelectedLoadouts)
|
||||
{
|
||||
weh.SelectedLoadouts.Add(selected.Key, new List<Loadout>(selected.Value));
|
||||
}
|
||||
|
||||
return weh;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
29
Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs
Normal file
29
Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ namespace Content.Shared.Roles
|
||||
/// </summary>
|
||||
public static bool TryRequirementMet(
|
||||
JobRequirement requirement,
|
||||
Dictionary<string, TimeSpan> playTimes,
|
||||
IReadOnlyDictionary<string, TimeSpan> playTimes,
|
||||
[NotNullWhen(false)] out FormattedMessage? reason,
|
||||
IEntityManager entManager,
|
||||
IPrototypeManager prototypes)
|
||||
@@ -162,7 +162,7 @@ namespace Content.Shared.Roles
|
||||
return true;
|
||||
|
||||
reason = FormattedMessage.FromMarkup(Loc.GetString(
|
||||
"role-timer-overall-insufficient",
|
||||
"role-timer-overall-insufficient",
|
||||
("time", Math.Ceiling(overallDiff))));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,37 +9,21 @@ namespace Content.Shared.Roles
|
||||
[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;
|
||||
|
||||
[DataField]
|
||||
public List<EntProtoId> Inhand = new(0);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts entities into the specified slot's storage (if it does have storage).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ using Content.Shared.Hands.EntitySystems;
|
||||
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;
|
||||
|
||||
@@ -10,40 +13,69 @@ public abstract class SharedStationSpawningSystem : EntitySystem
|
||||
{
|
||||
[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;
|
||||
|
||||
var inhand = startingGear.Inhand;
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
|
||||
foreach (var prototype in inhand)
|
||||
if (TryComp(entity, out HandsComponent? handsComponent))
|
||||
{
|
||||
var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
|
||||
|
||||
if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
|
||||
var inhand = startingGear.Inhand;
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
|
||||
foreach (var prototype in inhand)
|
||||
{
|
||||
_handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
|
||||
var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
|
||||
|
||||
if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
|
||||
{
|
||||
_handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startingGear.Storage.Count > 0)
|
||||
{
|
||||
var coords = _xformSystem.GetMapCoordinates(entity);
|
||||
var ents = new ValueList<EntityUid>();
|
||||
TryComp(entity, out InventoryComponent? inventoryComp);
|
||||
|
||||
foreach (var (slot, entProtos) in startingGear.Storage)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class StepTriggerSystem : EntitySystem
|
||||
|
||||
if (component.Blacklist != null && TryComp<MapGridComponent>(transform.GridUid, out var grid))
|
||||
{
|
||||
var positon = _map.LocalToTile(uid, grid, transform.Coordinates);
|
||||
var positon = _map.LocalToTile(transform.GridUid.Value, grid, transform.Coordinates);
|
||||
var anch = _map.GetAnchoredEntitiesEnumerator(uid, grid, positon);
|
||||
|
||||
while (anch.MoveNext(out var ent))
|
||||
|
||||
Binary file not shown.
@@ -1,88 +1,4 @@
|
||||
Entries:
|
||||
- author: Varen
|
||||
changes:
|
||||
- message: Cutting wires will now properly electrocute if enough power is available.
|
||||
type: Fix
|
||||
id: 5855
|
||||
time: '2024-02-01T11:54:25.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24554
|
||||
- author: SlamBamActionman
|
||||
changes:
|
||||
- message: Paper is now edible, even for non-moths.
|
||||
type: Tweak
|
||||
id: 5856
|
||||
time: '2024-02-01T12:40:55.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24755
|
||||
- author: mac6na6na
|
||||
changes:
|
||||
- message: Tiered part crates will no longer appear on salvage expeditions.
|
||||
type: Remove
|
||||
id: 5857
|
||||
time: '2024-02-01T12:41:03.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24810
|
||||
- author: mirrorcult
|
||||
changes:
|
||||
- message: Crayon/lathe/vendor/round end/gas analyzer windows now open closer to
|
||||
the sides of the screen
|
||||
type: Tweak
|
||||
id: 5858
|
||||
time: '2024-02-01T12:49:49.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24767
|
||||
- author: Tayrtahn
|
||||
changes:
|
||||
- message: Plushies, whoopie cushions, and a few other noisemaking objects can be
|
||||
used as modular grenade payloads.
|
||||
type: Add
|
||||
id: 5859
|
||||
time: '2024-02-01T12:59:42.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24306
|
||||
- author: Repo
|
||||
changes:
|
||||
- message: AFK admins will trigger the SOS in aHelp relay.
|
||||
type: Tweak
|
||||
id: 5860
|
||||
time: '2024-02-01T13:03:50.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24482
|
||||
- author: Nopey
|
||||
changes:
|
||||
- message: Many belts and the plant bag no longer produce an erroneous "Can't insert"
|
||||
and "This doesn't go in there!" messages while inserting certain items such
|
||||
as Produce (plant bag) or Smoke Grenades (security belt).
|
||||
type: Fix
|
||||
- message: Ammunition can once again be stored in assault belts, as was the case
|
||||
before June 2022.
|
||||
type: Fix
|
||||
id: 5861
|
||||
time: '2024-02-01T13:33:57.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24063
|
||||
- author: Blazeror
|
||||
changes:
|
||||
- message: Chemical analysis goggles can now inspect solutions inside spray bottles.
|
||||
type: Fix
|
||||
id: 5862
|
||||
time: '2024-02-01T22:19:01.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24838
|
||||
- author: themias
|
||||
changes:
|
||||
- message: Fixed dylovene overdoses not applying brute damage
|
||||
type: Fix
|
||||
id: 5863
|
||||
time: '2024-02-01T22:21:04.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24826
|
||||
- author: themias
|
||||
changes:
|
||||
- message: Added sound effect for Diona salutes
|
||||
type: Add
|
||||
id: 5864
|
||||
time: '2024-02-01T22:34:09.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24836
|
||||
- author: Dutch-VanDerLinde
|
||||
changes:
|
||||
- message: Detectives are now listed under civilian instead of security.
|
||||
type: Tweak
|
||||
id: 5865
|
||||
time: '2024-02-01T22:49:54.0000000+00:00'
|
||||
url: https://api.github.com/repos/space-wizards/space-station-14/pulls/24739
|
||||
- author: VasilisThePikachu
|
||||
changes:
|
||||
- message: The fridge's explosion resistance has been nerfed significantly, It's
|
||||
@@ -3839,3 +3755,82 @@
|
||||
id: 6354
|
||||
time: '2024-04-14T12:12:54.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26338
|
||||
- author: beck-thompson
|
||||
changes:
|
||||
- message: Cybersun pen now makes a slashing noise when doing damage.
|
||||
type: Fix
|
||||
id: 6355
|
||||
time: '2024-04-14T20:04:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26951
|
||||
- author: TokenStyle
|
||||
changes:
|
||||
- message: Lockers cannot be deconstructed with a screwdriver when locked now.
|
||||
type: Fix
|
||||
id: 6356
|
||||
time: '2024-04-14T22:26:47.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26961
|
||||
- author: pissdemon
|
||||
changes:
|
||||
- message: Catwalks over lava are slightly less likely to catch you on fire again.
|
||||
type: Fix
|
||||
id: 6357
|
||||
time: '2024-04-15T01:27:58.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26968
|
||||
- author: Velcroboy
|
||||
changes:
|
||||
- message: Fixed sec/lawyer and vault airlocks
|
||||
type: Fix
|
||||
id: 6358
|
||||
time: '2024-04-15T22:22:16.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/26980
|
||||
- author: tosatur
|
||||
changes:
|
||||
- message: Made clown snoring quieter
|
||||
type: Tweak
|
||||
id: 6359
|
||||
time: '2024-04-16T18:48:38.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27012
|
||||
- author: DEATHB4DEFEAT, Dutch-VanDerLinde, metalgearsloth and musicmanvr
|
||||
changes:
|
||||
- message: Added loadouts
|
||||
type: Add
|
||||
id: 6360
|
||||
time: '2024-04-16T19:57:41.736477+00:00'
|
||||
url: null
|
||||
- author: Dutch-VanDerLinde
|
||||
changes:
|
||||
- message: Senior role ID cards now function properly.
|
||||
type: Fix
|
||||
id: 6361
|
||||
time: '2024-04-16T20:17:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27017
|
||||
- author: Dutch-VanDerLinde
|
||||
changes:
|
||||
- message: Most job loadouts now have winter clothing available.
|
||||
type: Tweak
|
||||
id: 6362
|
||||
time: '2024-04-17T02:49:53.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27022
|
||||
- author: metalgearsloth
|
||||
changes:
|
||||
- message: 'Fix the following in lobby: ShowClothes button not working, Skin Color
|
||||
not updating, Skin Color slider not updating upon closing and re-opening character.'
|
||||
type: Fix
|
||||
id: 6363
|
||||
time: '2024-04-17T02:54:54.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27033
|
||||
- author: MACMAN2003
|
||||
changes:
|
||||
- message: Nuclear operatives now only need 20 players to be readied up again instead
|
||||
of 35.
|
||||
type: Tweak
|
||||
id: 6364
|
||||
time: '2024-04-17T03:19:30.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27036
|
||||
- author: Bellwether
|
||||
changes:
|
||||
- message: The nun hood now appears in the Chaplain's loadout.
|
||||
type: Tweak
|
||||
id: 6365
|
||||
time: '2024-04-17T03:36:44.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27025
|
||||
|
||||
1
Resources/Locale/en-US/burning/bodyburn.ftl
Normal file
1
Resources/Locale/en-US/burning/bodyburn.ftl
Normal file
@@ -0,0 +1 @@
|
||||
bodyburn-text-others = {$name}'s body burns to ash!
|
||||
2
Resources/Locale/en-US/job/loadouts.ftl
Normal file
2
Resources/Locale/en-US/job/loadouts.ftl
Normal file
@@ -0,0 +1,2 @@
|
||||
loadout-window = Loadout
|
||||
loadout-none = None
|
||||
184
Resources/Locale/en-US/preferences/loadout-groups.ftl
Normal file
184
Resources/Locale/en-US/preferences/loadout-groups.ftl
Normal file
@@ -0,0 +1,184 @@
|
||||
# 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-captain-outerclothing = Captain outer clothing
|
||||
|
||||
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
|
||||
loadout-group-hop-outerclothing = Head of Personnel outer clothing
|
||||
|
||||
# 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-passenger-outerclothing = Passenger outer clothing
|
||||
loadout-group-passenger-shoes = Passenger shoes
|
||||
|
||||
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-janitor-outerclothing = Janitor outer clothing
|
||||
|
||||
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-outerclothing = Clown outer clothing
|
||||
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-mime-outerclothing = Mime outer clothing
|
||||
|
||||
loadout-group-musician-backpack = Musician backpack
|
||||
loadout-group-musician-outerclothing = Musician outer clothing
|
||||
|
||||
# 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-quartermaster-outerclothing = Quartermaster outer clothing
|
||||
loadout-group-quartermaster-shoes = Quartermaster shoes
|
||||
|
||||
loadout-group-cargo-technician-head = Cargo Technician head
|
||||
loadout-group-cargo-technician-jumpsuit = Cargo Technician jumpsuit
|
||||
loadout-group-cargo-technician-backpack = Cargo Technician backpack
|
||||
loadout-group-cargo-technician-outerclothing = Cargo Technician outer clothing
|
||||
loadout-group-cargo-technician-shoes = Cargo Technician shoes
|
||||
|
||||
loadout-group-salvage-specialist-backpack = Salvage Specialist backpack
|
||||
loadout-group-salvage-specialist-outerclothing = Salvage Specialist outer clothing
|
||||
loadout-group-salvage-specialist-shoes = Salvage Specialist shoes
|
||||
|
||||
# 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-outerclothing = Chief Engineer outer clothing
|
||||
loadout-group-chief-engineer-neck = Chief Engineer neck
|
||||
loadout-group-chief-engineer-shoes = Chief Engineer shoes
|
||||
|
||||
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-shoes = Station Engineer shoes
|
||||
loadout-group-station-engineer-id = Station Engineer ID
|
||||
|
||||
loadout-group-atmospheric-technician-jumpsuit = Atmospheric Technician jumpsuit
|
||||
loadout-group-atmospheric-technician-backpack = Atmospheric Technician backpack
|
||||
loadout-group-atmospheric-technician-outerclothing = Atmospheric Technician outer clothing
|
||||
loadout-group-atmospheric-technician-shoes = Atmospheric Technician shoes
|
||||
|
||||
# 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-research-director-shoes = Research Director shoes
|
||||
|
||||
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-shoes = Scientist shoes
|
||||
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-outerclothing = Security outer clothing
|
||||
loadout-group-security-shoes = Security shoes
|
||||
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-shoes = Chief Medical Officer shoes
|
||||
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-shoes = Medical Doctor shoes
|
||||
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-shoes = Paramedic shoes
|
||||
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
|
||||
7
Resources/Locale/en-US/preferences/loadouts.ftl
Normal file
7
Resources/Locale/en-US/preferences/loadouts.ftl
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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
|
||||
@@ -19,8 +19,6 @@ humanoid-profile-editor-pronouns-neuter-text = It / It
|
||||
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
|
||||
|
||||
@@ -104,6 +104,17 @@
|
||||
- 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
|
||||
|
||||
@@ -85,6 +85,10 @@
|
||||
0: Alive
|
||||
450: Critical
|
||||
500: Dead
|
||||
- type: SlowOnDamage
|
||||
speedModifierThresholds:
|
||||
250: 0.7
|
||||
400: 0.5
|
||||
- type: Metabolizer
|
||||
solutionOnBody: false
|
||||
updateInterval: 0.25
|
||||
|
||||
@@ -60,6 +60,21 @@
|
||||
damage: 400
|
||||
behaviors:
|
||||
- !type:GibBehavior { }
|
||||
- trigger:
|
||||
!type:DamageTypeTrigger
|
||||
damageType: Heat
|
||||
damage: 1500
|
||||
behaviors:
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawnInContainer: true
|
||||
spawn:
|
||||
Ash:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:BurnBodyBehavior { }
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MeatLaserImpact
|
||||
- type: RadiationReceiver
|
||||
- type: Stamina
|
||||
- type: MobState
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
suffix: Security/Lawyer, Locked
|
||||
components:
|
||||
- type: AccessReader
|
||||
access: [["Security", "Lawyer"]]
|
||||
access: [["Security"], ["Lawyer"]]
|
||||
|
||||
- type: entity
|
||||
parent: DoorElectronics
|
||||
@@ -261,7 +261,7 @@
|
||||
suffix: Vault, Locked
|
||||
components:
|
||||
- type: AccessReader
|
||||
access: [["Security", "Command"]]
|
||||
access: [["Security"], ["Command"]]
|
||||
|
||||
- type: entity
|
||||
parent: DoorElectronics
|
||||
|
||||
@@ -1310,9 +1310,7 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Fun/rubber_hammer.rsi
|
||||
layers:
|
||||
- state: icon
|
||||
shader: unshaded
|
||||
state: icon
|
||||
- type: WeaponRandom
|
||||
damageBonus:
|
||||
types:
|
||||
|
||||
@@ -802,7 +802,7 @@
|
||||
- type: Unremoveable
|
||||
|
||||
- type: entity
|
||||
parent: IDCardStandard
|
||||
parent: EngineeringIDCard
|
||||
id: SeniorEngineerIDCard
|
||||
name: senior engineer ID card
|
||||
components:
|
||||
@@ -812,7 +812,7 @@
|
||||
- state: idseniorengineer
|
||||
|
||||
- type: entity
|
||||
parent: IDCardStandard
|
||||
parent: ResearchIDCard
|
||||
id: SeniorResearcherIDCard
|
||||
name: senior researcher ID card
|
||||
components:
|
||||
@@ -822,7 +822,7 @@
|
||||
- state: idseniorresearcher
|
||||
|
||||
- type: entity
|
||||
parent: IDCardStandard
|
||||
parent: MedicalIDCard
|
||||
id: SeniorPhysicianIDCard
|
||||
name: senior physician ID card
|
||||
components:
|
||||
@@ -832,7 +832,7 @@
|
||||
- state: idseniorphysician
|
||||
|
||||
- type: entity
|
||||
parent: IDCardStandard
|
||||
parent: SecurityIDCard
|
||||
id: SeniorOfficerIDCard
|
||||
name: senior officer ID card
|
||||
components:
|
||||
|
||||
@@ -351,6 +351,8 @@
|
||||
damage:
|
||||
types:
|
||||
Piercing: 15
|
||||
soundHit:
|
||||
path: /Audio/Weapons/bladeslice.ogg
|
||||
- type: Tool
|
||||
qualities:
|
||||
- Screwing
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
Blunt: 1
|
||||
Heat: 2
|
||||
- type: IgniteOnCollide
|
||||
fireStacks: 3
|
||||
fireStacks: 1
|
||||
count: 10
|
||||
- type: TimedDespawn
|
||||
lifetime: 0.25
|
||||
|
||||
@@ -1041,6 +1041,15 @@
|
||||
containers:
|
||||
board: [ DoorElectronicsResearchDirector ]
|
||||
|
||||
- type: entity
|
||||
parent: AirlockMaintCommandLocked
|
||||
id: AirlockMaintQuartermasterLocked
|
||||
suffix: Quartermaster, Locked
|
||||
components:
|
||||
- type: ContainerFill
|
||||
containers:
|
||||
board: [ DoorElectronicsQuartermaster ]
|
||||
|
||||
- type: entity
|
||||
parent: AirlockMaint
|
||||
id: AirlockMaintArmoryLocked
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
sprite: Structures/Walls/meat.rsi
|
||||
- type: Construction
|
||||
graph: Girder
|
||||
node: bananiumWall
|
||||
node: meatWall
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: GameRule
|
||||
minPlayers: 35
|
||||
minPlayers: 20
|
||||
- type: NukeopsRule
|
||||
faction: Syndicate
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# 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
|
||||
|
||||
# OuterClothing
|
||||
- type: loadout
|
||||
id: CargoTechnicianWintercoat
|
||||
equipment: CargoTechnicianWintercoat
|
||||
|
||||
- type: startingGear
|
||||
id: CargoTechnicianWintercoat
|
||||
equipment:
|
||||
outerClothing: ClothingOuterWinterCargo
|
||||
|
||||
# Shoes
|
||||
- type: loadout
|
||||
id: CargoWinterBoots
|
||||
equipment: CargoWinterBoots
|
||||
|
||||
- type: startingGear
|
||||
id: CargoWinterBoots
|
||||
equipment:
|
||||
shoes: ClothingShoesBootsWinterCargo
|
||||
121
Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml
Normal file
121
Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml
Normal file
@@ -0,0 +1,121 @@
|
||||
# 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
|
||||
|
||||
# OuterClothing
|
||||
- type: loadout
|
||||
id: QuartermasterWintercoat
|
||||
equipment: QuartermasterWintercoat
|
||||
|
||||
- type: startingGear
|
||||
id: QuartermasterWintercoat
|
||||
equipment:
|
||||
outerClothing: ClothingOuterWinterQM
|
||||
@@ -0,0 +1,47 @@
|
||||
# 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
|
||||
|
||||
# OuterClothing
|
||||
- type: loadout
|
||||
id: SalvageSpecialistWintercoat
|
||||
equipment: SalvageSpecialistWintercoat
|
||||
|
||||
- type: startingGear
|
||||
id: SalvageSpecialistWintercoat
|
||||
equipment:
|
||||
outerClothing: ClothingOuterWinterMiner
|
||||
|
||||
# Shoes
|
||||
- type: loadout
|
||||
id: SalvageBoots
|
||||
equipment: SalvageBoots
|
||||
|
||||
- type: startingGear
|
||||
id: SalvageBoots
|
||||
equipment:
|
||||
shoes: ClothingShoesBootsSalvage
|
||||
74
Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml
Normal file
74
Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
# 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
|
||||
|
||||
- type: loadout
|
||||
id: BartenderWintercoat
|
||||
equipment: BartenderWintercoat
|
||||
|
||||
- type: startingGear
|
||||
id: BartenderWintercoat
|
||||
equipment:
|
||||
outerClothing: ClothingOuterWinterBar
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user