Merge remote-tracking branch 'upstream/master' into secrespritefinally
This commit is contained in:
commit
4e58a7b108
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.100
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.100
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.100
|
||||
|
||||
- name: Get Engine Tag
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ jobs:
|
|||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.100
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.100
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
<ItemGroup>
|
||||
<Folder Include="Spawners\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Lobby\UI\LobbyCharacterPreviewPanel.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.Shipyard;
|
||||
|
||||
namespace Content.Client.Shipyard;
|
||||
|
||||
public sealed class ShipyardConsoleSystem : SharedShipyardConsoleSystem;
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Shipyard;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.DeltaV.Shipyard.UI;
|
||||
|
||||
public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
private readonly AccessReaderSystem _access;
|
||||
|
||||
[ViewVariables]
|
||||
private ShipyardConsoleMenu? _menu;
|
||||
|
||||
public ShipyardConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_access = EntMan.System<AccessReaderSystem>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
_menu.OnPurchased += Purchase;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not ShipyardConsoleState cast)
|
||||
return;
|
||||
|
||||
_menu?.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_menu?.Dispose();
|
||||
}
|
||||
|
||||
private void Purchase(string id)
|
||||
{
|
||||
SendMessage(new ShipyardConsolePurchaseMessage(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="500 360"
|
||||
MinSize="460 280"
|
||||
Title="{Loc 'shipyard-console-menu-title'}">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 0 5 0">
|
||||
<Label Name="BankAccountLabel" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="Categories"
|
||||
Prefix="{Loc 'cargo-console-menu-categories-label'}"
|
||||
HorizontalExpand="True" />
|
||||
<LineEdit Name="SearchBar"
|
||||
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
<BoxContainer Name="Vessels"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<!-- Vessels get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<TextureButton VerticalExpand="True" />
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Shipyard;
|
||||
using Content.Shared.Shipyard.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.DeltaV.Shipyard.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ShipyardConsoleMenu : FancyWindow
|
||||
{
|
||||
private readonly AccessReaderSystem _access;
|
||||
private readonly IPlayerManager _player;
|
||||
|
||||
public event Action<string>? OnPurchased;
|
||||
|
||||
private readonly List<VesselPrototype> _vessels = new();
|
||||
private readonly List<string> _categories = new();
|
||||
|
||||
public Entity<ShipyardConsoleComponent> Console;
|
||||
private string? _category;
|
||||
|
||||
public ShipyardConsoleMenu(EntityUid console, IPrototypeManager proto, IEntityManager entMan, IPlayerManager player, AccessReaderSystem access)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Console = (console, entMan.GetComponent<ShipyardConsoleComponent>(console));
|
||||
_access = access;
|
||||
_player = player;
|
||||
|
||||
// don't include ships that aren't allowed by whitelist, server won't accept them anyway
|
||||
foreach (var vessel in proto.EnumeratePrototypes<VesselPrototype>())
|
||||
{
|
||||
if (vessel.Whitelist?.IsValid(console, entMan) != false)
|
||||
_vessels.Add(vessel);
|
||||
}
|
||||
_vessels.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
// only list categories in said ships
|
||||
foreach (var vessel in _vessels)
|
||||
{
|
||||
foreach (var category in vessel.Categories)
|
||||
{
|
||||
if (!_categories.Contains(category))
|
||||
_categories.Add(category);
|
||||
}
|
||||
}
|
||||
|
||||
_categories.Sort();
|
||||
// inserting here and not adding at the start so it doesn't get affected by sort
|
||||
_categories.Insert(0, Loc.GetString("cargo-console-menu-populate-categories-all-text"));
|
||||
PopulateCategories();
|
||||
|
||||
SearchBar.OnTextChanged += _ => PopulateProducts();
|
||||
Categories.OnItemSelected += args =>
|
||||
{
|
||||
_category = args.Id == 0 ? null : _categories[args.Id];
|
||||
Categories.SelectId(args.Id);
|
||||
PopulateProducts();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the list of products that will actually be shown, using the current filters.
|
||||
/// </summary>
|
||||
private void PopulateProducts()
|
||||
{
|
||||
Vessels.RemoveAllChildren();
|
||||
|
||||
var access = _player.LocalSession?.AttachedEntity is {} player
|
||||
&& _access.IsAllowed(player, Console);
|
||||
|
||||
var search = SearchBar.Text.Trim().ToLowerInvariant();
|
||||
foreach (var vessel in _vessels)
|
||||
{
|
||||
if (search.Length != 0 && !vessel.Name.ToLowerInvariant().Contains(search))
|
||||
continue;
|
||||
if (_category != null && !vessel.Categories.Contains(_category))
|
||||
continue;
|
||||
|
||||
var vesselEntry = new VesselRow(vessel, access);
|
||||
vesselEntry.OnPurchasePressed += () => OnPurchased?.Invoke(vessel.ID);
|
||||
Vessels.AddChild(vesselEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the list categories that will actually be shown, using the current filters.
|
||||
/// </summary>
|
||||
private void PopulateCategories()
|
||||
{
|
||||
Categories.Clear();
|
||||
foreach (var category in _categories)
|
||||
{
|
||||
Categories.AddItem(category);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(ShipyardConsoleState state)
|
||||
{
|
||||
BankAccountLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", state.Balance.ToString()));
|
||||
PopulateProducts();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<Button Name="Purchase" Text="{Loc 'purchase'}" StyleClasses="LabelSubText" />
|
||||
<Label Name="VesselName" HorizontalExpand="True" />
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252A" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<Label Name="Price" MinSize="52 32" Align="Right" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Content.Shared.Shipyard.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.DeltaV.Shipyard.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class VesselRow : PanelContainer
|
||||
{
|
||||
public event Action? OnPurchasePressed;
|
||||
|
||||
public VesselRow(VesselPrototype vessel, bool access)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
VesselName.Text = vessel.Name;
|
||||
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.SetMessage(FormattedMessage.FromMarkup(vessel.Description));
|
||||
Purchase.TooltipSupplier = _ => tooltip;
|
||||
Purchase.Disabled = !access;
|
||||
Purchase.OnPressed += _ => OnPurchasePressed?.Invoke();
|
||||
|
||||
Price.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", vessel.Price));
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 partial class JobRequirementsManager
|
||||
public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
|
||||
{
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
|
|
@ -134,5 +135,13 @@ public sealed partial 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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -140,6 +135,9 @@ namespace Content.Client.Preferences.UI
|
|||
_humanoidProfileEditor.CharacterSlot = characterIndexCopy;
|
||||
_humanoidProfileEditor.UpdateControls();
|
||||
_preferencesManager.SelectCharacter(character);
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(_humanoidProfileEditor.Profile);
|
||||
controller.ReloadCharacterUI();
|
||||
UpdateUI();
|
||||
args.Event.Handle();
|
||||
};
|
||||
|
|
@ -148,8 +146,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 +182,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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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))
|
||||
{
|
||||
|
|
@ -626,12 +594,16 @@ namespace Content.Client.Preferences.UI
|
|||
|
||||
category.AddChild(selector);
|
||||
_jobPriorities.Add(selector);
|
||||
EnsureJobRequirementsValid(); // DeltaV
|
||||
|
||||
selector.LoadoutUpdated += args =>
|
||||
{
|
||||
Profile = Profile?.WithLoadout(args);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
selector.PriorityChanged += priority =>
|
||||
{
|
||||
Profile = Profile?.WithJobPriority(job.ID, priority);
|
||||
IsDirty = true;
|
||||
|
||||
foreach (var jobSelector in _jobPriorities)
|
||||
{
|
||||
|
|
@ -647,6 +619,8 @@ namespace Content.Client.Preferences.UI
|
|||
Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
|
||||
}
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -658,35 +632,13 @@ namespace Content.Client.Preferences.UI
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Make sure that no invalid job priorities get through.
|
||||
/// </summary>
|
||||
private void EnsureJobRequirementsValid()
|
||||
{
|
||||
var changed = false;
|
||||
foreach (var selector in _jobPriorities)
|
||||
{
|
||||
if (_requirements.IsAllowed(selector.Proto, out var _) || selector.Priority == JobPriority.Never)
|
||||
continue;
|
||||
|
||||
selector.Priority = JobPriority.Never;
|
||||
Profile = Profile?.WithJobPriority(selector.Proto.ID, JobPriority.Never);
|
||||
changed = true;
|
||||
}
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
_needUpdatePreview = true;
|
||||
Save();
|
||||
}
|
||||
|
||||
private void OnFlavorTextChange(string content)
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithFlavorText(content);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void OnMarkingChange(MarkingSet markings)
|
||||
|
|
@ -695,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;
|
||||
|
|
@ -782,6 +726,9 @@ namespace Content.Client.Preferences.UI
|
|||
}
|
||||
|
||||
IsDirty = true;
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadProfile();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
|
@ -790,40 +737,27 @@ 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();
|
||||
UpdateControls();
|
||||
EnsureJobRequirementsValid(); // DeltaV
|
||||
_needUpdatePreview = true;
|
||||
ShowClothes.Pressed = true;
|
||||
}
|
||||
|
||||
private void SetAge(int newAge)
|
||||
{
|
||||
Profile = Profile?.WithAge(newAge);
|
||||
IsDirty = true;
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetSex(Sex newSex)
|
||||
|
|
@ -844,13 +778,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)
|
||||
|
|
@ -859,46 +793,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
|
||||
|
|
@ -907,7 +829,6 @@ namespace Content.Client.Preferences.UI
|
|||
set
|
||||
{
|
||||
_isDirty = value;
|
||||
_needUpdatePreview = true;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
}
|
||||
|
|
@ -1039,7 +960,7 @@ namespace Content.Client.Preferences.UI
|
|||
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
return;
|
||||
|
||||
var style = speciesProto.GuideBookIcon;
|
||||
const string style = "SpeciesInfoDefault";
|
||||
SpeciesInfoButton.StyleClasses.Add(style);
|
||||
}
|
||||
|
||||
|
|
@ -1075,26 +996,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)
|
||||
|
|
@ -1224,13 +1125,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()
|
||||
|
|
@ -1242,17 +1143,16 @@ namespace Content.Client.Preferences.UI
|
|||
UpdateGenderControls();
|
||||
UpdateSkinColor();
|
||||
UpdateSpecies();
|
||||
UpdateClothingControls();
|
||||
UpdateBackpackControls();
|
||||
UpdateSpawnPriorityControls();
|
||||
UpdateAgeEdit();
|
||||
UpdateEyePickers();
|
||||
UpdateSaveButton();
|
||||
UpdateLoadouts();
|
||||
UpdateRoleRequirements();
|
||||
UpdateJobPriorities();
|
||||
UpdateAntagPreferences();
|
||||
UpdateTraitPreferences();
|
||||
UpdateMarkings();
|
||||
RebuildSpriteView();
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
UpdateCMarkingsFacialHair();
|
||||
|
|
@ -1260,17 +1160,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)
|
||||
|
|
@ -1283,143 +1172,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1444,41 +1201,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; }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Shipyard;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Shipyard.Prototypes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.DeltaV;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(ShipyardSystem))]
|
||||
public sealed class ShipyardTest
|
||||
{
|
||||
[Test]
|
||||
public async Task NoShipyardArbitrage()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entities = server.ResolveDependency<IEntityManager>();
|
||||
var proto = server.ResolveDependency<IPrototypeManager>();
|
||||
var shipyard = entities.System<ShipyardSystem>();
|
||||
var pricing = entities.System<PricingSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var vessel in proto.EnumeratePrototypes<VesselPrototype>())
|
||||
{
|
||||
var shuttle = shipyard.TryCreateShuttle(vessel.Path.ToString());
|
||||
Assert.That(shuttle, Is.Not.Null, $"Failed to spawn shuttle {vessel.ID}!");
|
||||
|
||||
var value = pricing.AppraiseGrid(shuttle.Value);
|
||||
Assert.That(value, Is.AtMost(vessel.Price), $"Found arbitrage on shuttle {vessel.ID}! Price is {vessel.Price} but value is {value}!");
|
||||
entities.DeleteEntity(shuttle);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AllShuttlesValid()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entities = server.ResolveDependency<IEntityManager>();
|
||||
var proto = server.ResolveDependency<IPrototypeManager>();
|
||||
var shipyard = entities.System<ShipyardSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var vessel in proto.EnumeratePrototypes<VesselPrototype>())
|
||||
{
|
||||
var shuttle = shipyard.TryCreateShuttle(vessel.Path.ToString());
|
||||
Assert.That(shuttle, Is.Not.Null, $"Failed to spawn shuttle {vessel.ID}!");
|
||||
|
||||
var console = FindComponent<ShuttleConsoleComponent>(entities, shuttle.Value);
|
||||
Assert.That(console, Is.True, $"Shuttle {vessel.ID} had no shuttle console!");
|
||||
|
||||
var dock = FindComponent<DockingComponent>(entities, shuttle.Value);
|
||||
Assert.That(dock, Is.True, $"Shuttle {vessel.ID} had no shuttle dock!");
|
||||
|
||||
entities.DeleteEntity(shuttle);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private bool FindComponent<T>(IEntityManager entities, EntityUid shuttle) where T: Component
|
||||
{
|
||||
var query = entities.EntityQueryEnumerator<T, TransformComponent>();
|
||||
while (query.MoveNext(out _, out var xform))
|
||||
{
|
||||
if (xform.ParentUid != shuttle)
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ public sealed class ParadoxAnomalySystem : EntitySystem
|
|||
|
||||
if (job.StartingGear != null && _proto.TryIndex<StartingGearPrototype>(job.StartingGear, out var gear))
|
||||
{
|
||||
_stationSpawning.EquipStartingGear(spawned, gear, profile);
|
||||
_stationSpawning.EquipIdCard(spawned,
|
||||
_stationSpawning.EquipStartingGear(spawned, gear);
|
||||
_stationSpawning.SetPdaAndIdCardData(spawned,
|
||||
profile.Name,
|
||||
job,
|
||||
station);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
namespace Content.Server.Shipyard;
|
||||
|
||||
/// <summary>
|
||||
/// When added to a shuttle, once it FTLs the previous map is deleted.
|
||||
/// After that the component is removed to prevent insane abuse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Could be upstreamed at some point, loneop shuttle could use it.
|
||||
/// </remarks>
|
||||
[RegisterComponent, Access(typeof(MapDeleterShuttleSystem))]
|
||||
public sealed partial class MapDeleterShuttleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Only set by the system to prevent someone in VV deleting maps by mistake or otherwise.
|
||||
/// </summary>
|
||||
public bool Enabled;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Server.Shuttles.Events;
|
||||
|
||||
namespace Content.Server.Shipyard;
|
||||
|
||||
public sealed class MapDeleterShuttleSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MapDeleterShuttleComponent, FTLStartedEvent>(OnFTLStarted);
|
||||
}
|
||||
|
||||
private void OnFTLStarted(Entity<MapDeleterShuttleComponent> ent, ref FTLStartedEvent args)
|
||||
{
|
||||
if (ent.Comp.Enabled)
|
||||
Del(args.FromMapUid);
|
||||
RemComp<MapDeleterShuttleComponent>(ent); // prevent the shuttle becoming a WMD
|
||||
}
|
||||
|
||||
public void Enable(EntityUid shuttle)
|
||||
{
|
||||
EnsureComp<MapDeleterShuttleComponent>(shuttle).Enabled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Shipyard;
|
||||
using Content.Shared.Shipyard.Prototypes;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Random;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.Shipyard;
|
||||
|
||||
public sealed class ShipyardConsoleSystem : SharedShipyardConsoleSystem
|
||||
{
|
||||
[Dependency] private readonly CargoSystem _cargo = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
[Dependency] private readonly RadioSystem _radio = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly ShipyardSystem _shipyard = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Subs.BuiEvents<ShipyardConsoleComponent>(ShipyardConsoleUiKey.Key, subs =>
|
||||
{
|
||||
subs.Event<BoundUIOpenedEvent>(OnOpened);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void TryPurchase(Entity<ShipyardConsoleComponent> ent, EntityUid user, VesselPrototype vessel)
|
||||
{
|
||||
if (GetBankAccount(ent) is not {} bank)
|
||||
return;
|
||||
|
||||
if (bank.Comp.Balance < vessel.Price)
|
||||
{
|
||||
var popup = Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price));
|
||||
Popup.PopupEntity(popup, ent, user);
|
||||
Audio.PlayPvs(ent.Comp.DenySound, ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_shipyard.TrySendShuttle(bank.Owner, vessel.Path.ToString()) is not {} shuttle)
|
||||
{
|
||||
var popup = Loc.GetString("shipyard-console-error");
|
||||
Popup.PopupEntity(popup, ent, user);
|
||||
Audio.PlayPvs(ent.Comp.DenySound, ent);
|
||||
return;
|
||||
}
|
||||
|
||||
_meta.SetEntityName(shuttle, $"{vessel.Name} {_random.Next(1000):000}");
|
||||
|
||||
_cargo.UpdateBankAccount(bank, bank.Comp, -vessel.Price);
|
||||
|
||||
var message = Loc.GetString("shipyard-console-docking", ("vessel", vessel.Name.ToString()));
|
||||
_radio.SendRadioMessage(ent, message, ent.Comp.Channel, ent);
|
||||
Audio.PlayPvs(ent.Comp.ConfirmSound, ent);
|
||||
|
||||
// TODO: make the ui updating more robust, make pr upstream to have UpdateBankAccount support things that arent ordering consoles
|
||||
// TODO: then have shipyard have that component and update the ui when it changes balance
|
||||
UpdateUI(ent, bank.Comp.Balance);
|
||||
}
|
||||
|
||||
private void OnOpened(Entity<ShipyardConsoleComponent> ent, ref BoundUIOpenedEvent args)
|
||||
{
|
||||
UpdateUI(ent);
|
||||
}
|
||||
|
||||
private void UpdateUI(EntityUid uid)
|
||||
{
|
||||
if (GetBankAccount(uid) is {} bank)
|
||||
UpdateUI(uid, bank.Comp.Balance);
|
||||
}
|
||||
|
||||
private void UpdateUI(EntityUid uid, int balance)
|
||||
{
|
||||
if (!_shipyard.Enabled)
|
||||
return;
|
||||
|
||||
var state = new ShipyardConsoleState(balance);
|
||||
_ui.SetUiState(uid, ShipyardConsoleUiKey.Key, state);
|
||||
}
|
||||
|
||||
private Entity<StationBankAccountComponent>? GetBankAccount(EntityUid console)
|
||||
{
|
||||
if (_station.GetOwningStation(console) is not {} station)
|
||||
return null;
|
||||
|
||||
if (!TryComp<StationBankAccountComponent>(station, out var bank))
|
||||
return null;
|
||||
|
||||
return (station, bank);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.DeltaV.CCVars;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.Shipyard;
|
||||
|
||||
/// <summary>
|
||||
/// Handles spawning and ftling ships.
|
||||
/// </summary>
|
||||
public sealed class ShipyardSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly MapDeleterShuttleSystem _mapDeleterShuttle = default!;
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _mapLoader = default!;
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
public string DockTag = "DockShipyard";
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Subs.CVar(_config, DCCVars.Shipyard, value => Enabled = value, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ship from its yaml path in the shipyard.
|
||||
/// </summary>
|
||||
public Entity<ShuttleComponent>? TryCreateShuttle(string path)
|
||||
{
|
||||
if (!Enabled)
|
||||
return null;
|
||||
|
||||
var map = _map.CreateMap(out var mapId);
|
||||
_map.SetPaused(map, false);
|
||||
|
||||
if (!_mapLoader.TryLoad(mapId, path, out var grids))
|
||||
{
|
||||
Log.Error($"Failed to load shuttle {path}");
|
||||
Del(map);
|
||||
return null;
|
||||
}
|
||||
|
||||
// only 1 grid is supported, no tramshuttle
|
||||
if (grids.Count != 1)
|
||||
{
|
||||
var error = grids.Count < 1 ? "less" : "more";
|
||||
Log.Error($"Shuttle {path} had {error} than 1 grid, which is not supported.");
|
||||
Del(map);
|
||||
return null;
|
||||
}
|
||||
|
||||
var uid = grids[0];
|
||||
if (!TryComp<ShuttleComponent>(uid, out var comp))
|
||||
{
|
||||
Log.Error($"Shuttle {path}'s grid was missing ShuttleComponent");
|
||||
Del(map);
|
||||
return null;
|
||||
}
|
||||
|
||||
_mapDeleterShuttle.Enable(uid);
|
||||
return (uid, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a ship to the shipyard and attempts to ftl-dock it to the given station.
|
||||
/// </summary>
|
||||
public Entity<ShuttleComponent>? TrySendShuttle(Entity<StationDataComponent?> station, string path)
|
||||
{
|
||||
if (!Resolve(station, ref station.Comp))
|
||||
return null;
|
||||
|
||||
if (_station.GetLargestGrid(station.Comp) is not {} grid)
|
||||
{
|
||||
Log.Error($"Station {ToPrettyString(station):station} had no largest grid to FTL to");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TryCreateShuttle(path) is not {} shuttle)
|
||||
return null;
|
||||
|
||||
Log.Info($"Shuttle {path} was spawned for {ToPrettyString(station):station}, FTLing to {grid}");
|
||||
_shuttle.FTLToDock(shuttle, shuttle.Comp, grid, priorityTag: DockTag);
|
||||
return shuttle;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -250,7 +250,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);
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,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 partial class PlayTimeTrackingManager
|
||||
public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly IServerNetManager _net = default!;
|
||||
|
|
@ -209,6 +209,11 @@ public sealed partial 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,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;
|
||||
|
|
@ -11,10 +12,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;
|
||||
|
|
@ -89,7 +92,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);
|
||||
|
|
@ -104,9 +107,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +142,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)
|
||||
|
|
@ -179,13 +184,52 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
|||
if (prototype?.StartingGear != null)
|
||||
{
|
||||
var startingGear = _prototypeManager.Index<StartingGearPrototype>(prototype.StartingGear);
|
||||
EquipStartingGear(entity.Value, startingGear, profile);
|
||||
if (profile != null)
|
||||
EquipIdCard(entity.Value, profile.Name, prototype, station);
|
||||
EquipStartingGear(entity.Value, startingGear);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (prototype != null)
|
||||
SetPdaAndIdCardData(entity.Value, profile.Name, prototype, station);
|
||||
|
||||
_humanoidSystem.LoadProfile(entity.Value, profile);
|
||||
_metaSystem.SetEntityName(entity.Value, profile.Name);
|
||||
if (profile.FlavorText != "" && _configurationManager.GetCVar(CCVars.FlavorText))
|
||||
|
|
@ -211,13 +255,13 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equips an ID card and PDA onto the given entity.
|
||||
/// Sets the ID card and PDA name, job, and access data.
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity to load out.</param>
|
||||
/// <param name="characterName">Character name to use for the ID.</param>
|
||||
/// <param name="jobPrototype">Job prototype to use for the PDA and ID.</param>
|
||||
/// <param name="station">The station this player is being spawned on.</param>
|
||||
public void EquipIdCard(EntityUid entity, string characterName, JobPrototype jobPrototype, EntityUid? station)
|
||||
public void SetPdaAndIdCardData(EntityUid entity, string characterName, JobPrototype jobPrototype, EntityUid? station)
|
||||
{
|
||||
if (!InventorySystem.TryGetSlotEntity(entity, "id", out var idUid))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
<ItemGroup>
|
||||
<Folder Include="DeltaV\Abilities\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Preferences\BackpackPreference.cs" />
|
||||
<Compile Remove="Preferences\ClothingPreference.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -21,4 +21,10 @@ public sealed class DCCVars
|
|||
/// </summary>
|
||||
public static readonly CVarDef<bool> NoVisionFilters =
|
||||
CVarDef.Create("accessibility.no_vision_filters", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Shipyard is enabled.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> Shipyard =
|
||||
CVarDef.Create("shuttle.shipyard", true, CVar.SERVERONLY);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Shipyard.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Like <c>TagPrototype</c> but for vessel categories.
|
||||
/// Prevents making typos being silently ignored by the linter.
|
||||
/// </summary>
|
||||
[Prototype("vesselCategory")]
|
||||
public sealed class VesselCategoryPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables, IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Shipyard.Prototypes;
|
||||
|
||||
[Prototype("vessel")]
|
||||
public sealed class VesselPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables, IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Already localized name of the vessel.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Already localized short description of the vessel.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// How much the vessel costs to purchase.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public int Price;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the shuttle yml to load, e.g. `/Maps/Shuttles/yourshittle.yml`
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ResPath Path = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Categories that can be filtered in the UI.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<VesselCategoryPrototype>> Categories = new();
|
||||
|
||||
/// <summary>
|
||||
/// If the console does not match this whitelist, the vessel is hidden and can't be bought.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Shipyard.Prototypes;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Shipyard;
|
||||
|
||||
/// <summary>
|
||||
/// Handles shipyard console interaction.
|
||||
/// <c>ShipyardSystem</c> does the heavy lifting serverside.
|
||||
/// </summary>
|
||||
public abstract class SharedShipyardConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] protected readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Subs.BuiEvents<ShipyardConsoleComponent>(ShipyardConsoleUiKey.Key, subs =>
|
||||
{
|
||||
subs.Event<ShipyardConsolePurchaseMessage>(OnPurchase);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPurchase(Entity<ShipyardConsoleComponent> ent, ref ShipyardConsolePurchaseMessage msg)
|
||||
{
|
||||
var user = msg.Actor;
|
||||
if (!_access.IsAllowed(user, ent.Owner))
|
||||
{
|
||||
Popup.PopupClient(Loc.GetString("comms-console-permission-denied"), ent, user);
|
||||
Audio.PlayPredicted(ent.Comp.DenySound, ent, user);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.TryIndex(msg.Vessel, out var vessel) || vessel.Whitelist?.IsValid(ent) == false)
|
||||
return;
|
||||
|
||||
TryPurchase(ent, user, vessel);
|
||||
}
|
||||
|
||||
protected virtual void TryPurchase(Entity<ShipyardConsoleComponent> ent, EntityUid user, VesselPrototype vessel)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using Content.Shared.Radio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Shipyard;
|
||||
|
||||
/// <summary>
|
||||
/// Component for the shipyard console.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedShipyardConsoleSystem))]
|
||||
public sealed partial class ShipyardConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Sound played when the ship can't be bought for any reason.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier DenySound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when a ship is purchased.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier ConfirmSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Radio channel to send the purchase announcement to.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<RadioChannelPrototype> Channel = "Command";
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Shared.Shipyard.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Shipyard;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ShipyardConsoleUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ShipyardConsoleState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly int Balance;
|
||||
|
||||
public ShipyardConsoleState(int balance)
|
||||
{
|
||||
Balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ask the server to purchase a vessel.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ShipyardConsolePurchaseMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly ProtoId<VesselPrototype> Vessel;
|
||||
|
||||
public ShipyardConsolePurchaseMessage(string vessel)
|
||||
{
|
||||
Vessel = vessel;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -43,6 +45,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,
|
||||
|
|
@ -51,13 +58,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;
|
||||
|
|
@ -66,13 +72,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>
|
||||
|
|
@ -80,15 +85,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))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -100,15 +106,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))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -125,8 +130,6 @@ namespace Content.Shared.Preferences
|
|||
Sex.Male,
|
||||
Gender.Male,
|
||||
new HumanoidCharacterAppearance(),
|
||||
ClothingPreference.Jumpsuit,
|
||||
BackpackPreference.Backpack,
|
||||
SpawnPriorityPreference.None,
|
||||
new Dictionary<string, JobPriority>
|
||||
{
|
||||
|
|
@ -134,7 +137,8 @@ namespace Content.Shared.Preferences
|
|||
},
|
||||
PreferenceUnavailableMode.SpawnAsOverflow,
|
||||
new List<string>(),
|
||||
new List<string>())
|
||||
new List<string>(),
|
||||
new Dictionary<string, RoleLoadout>())
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -153,8 +157,6 @@ namespace Content.Shared.Preferences
|
|||
Sex.Male,
|
||||
Gender.Male,
|
||||
HumanoidCharacterAppearance.DefaultWithSpecies(species),
|
||||
ClothingPreference.Jumpsuit,
|
||||
BackpackPreference.Backpack,
|
||||
SpawnPriorityPreference.None,
|
||||
new Dictionary<string, JobPriority>
|
||||
{
|
||||
|
|
@ -162,7 +164,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.
|
||||
|
|
@ -207,11 +210,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; }
|
||||
|
|
@ -231,8 +234,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;
|
||||
|
|
@ -275,21 +276,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)
|
||||
|
|
@ -303,7 +297,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)
|
||||
|
|
@ -313,7 +307,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)
|
||||
|
|
@ -333,7 +327,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)
|
||||
|
|
@ -355,7 +350,7 @@ namespace Content.Shared.Preferences
|
|||
list.Remove(traitId);
|
||||
}
|
||||
}
|
||||
return new(this, _jobPriorities, _antagPreferences, list);
|
||||
return new(this, _jobPriorities, _antagPreferences, list, _loadouts);
|
||||
}
|
||||
|
||||
public string Summary =>
|
||||
|
|
@ -375,17 +370,19 @@ namespace Content.Shared.Preferences
|
|||
if (Gender != other.Gender) return false;
|
||||
if (Species != other.Species) 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;
|
||||
|
|
@ -466,21 +463,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,
|
||||
|
|
@ -513,8 +495,6 @@ namespace Content.Shared.Preferences
|
|||
Sex = sex;
|
||||
Gender = gender;
|
||||
Appearance = appearance;
|
||||
Clothing = clothing;
|
||||
Backpack = backpack;
|
||||
SpawnPriority = spawnPriority;
|
||||
|
||||
_jobPriorities.Clear();
|
||||
|
|
@ -531,12 +511,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;
|
||||
}
|
||||
|
||||
|
|
@ -562,16 +561,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,30 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Players;
|
||||
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);
|
||||
var isWhitelisted = session.ContentData()?.Whitelisted ?? false; // DeltaV - Whitelist requirement
|
||||
|
||||
return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason,
|
||||
collection.Resolve<IEntityManager>(),
|
||||
collection.Resolve<IPrototypeManager>(),
|
||||
isWhitelisted); // DeltaV
|
||||
}
|
||||
}
|
||||
|
|
@ -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 partial 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 partial 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();
|
||||
}
|
||||
|
|
@ -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 partial 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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 partial 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;
|
||||
}
|
||||
|
|
@ -97,7 +97,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,
|
||||
|
|
@ -164,7 +164,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,38 +10,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)
|
||||
|| slot == "jumpsuit" && profile.Species == "Harpy" && !string.IsNullOrEmpty(InnerClothingSkirt)) //DeltaV adds this line to prevent Harpies from starting with jumpsuits
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
job-description-medical-borg = Half-human, Half-machine. Follow your laws, serve the crew, and assist the medical department.
|
||||
job-description-courier = Deliver mail and other packages from and to logistics. Avoid dogs.
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
job-name-medical-borg = Medical Cyborg
|
||||
job-name-courier = Courier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# This file will contain strings for both DeltaV and Nyanotrasen loadouts, because I'm lazy
|
||||
|
||||
# Logistics
|
||||
loadout-group-courier-head = Courier head
|
||||
loadout-group-courier-jumpsuit = Courier jumpsuit
|
||||
loadout-group-courier-shoes = Courier shoes
|
||||
loadout-group-courier-outerclothing = Courier outer clothing
|
||||
loadout-group-courier-id = Courier ID
|
||||
|
||||
loadout-group-mail-carrier-head = Mail Carrier head
|
||||
loadout-group-mail-carrier-jumpsuit = Mail Carrier jumpsuit
|
||||
loadout-group-mail-carrier-outerclothing = Mail Carrier outer clothing
|
||||
|
||||
# Epistemics
|
||||
loadout-group-mantis-head = Mantis head
|
||||
loadout-group-mantis-jumpsuit = Mantis jumpsuit
|
||||
loadout-group-mantis-backpack = Mantis backpack
|
||||
loadout-group-mantis-outerclothing = Mantis outer clothing
|
||||
loadout-group-mantis-shoes = Mantis shoes
|
||||
loadout-group-mantis-gloves = Mantis gloves
|
||||
|
||||
# Security
|
||||
loadout-group-brig-medic-head = Brigmedic head
|
||||
loadout-group-brig-medic-jumpsuit = Brigmedic jumpsuit
|
||||
loadout-group-brig-medic-back = Brigmedic backpack
|
||||
|
||||
loadout-group-prison-guard-head = Prison Guard head
|
||||
loadout-group-prison-guard-jumpsuit = Prison Guard jumpsuit
|
||||
|
||||
# Wildcards
|
||||
loadout-group-prisoner-jumpsuit = Prisoner jumpsuit
|
||||
|
||||
loadout-group-martial-artist-jumpsuit = Martial Artist jumpsuit
|
||||
loadout-group-martial-belt = Martial Artist belt
|
||||
loadout-group-martial-artist-shoes = Martial Artist shoes
|
||||
|
||||
loadout-group-gladiator-jumpsuit = Gladiator jumpsuit
|
||||
loadout-group-gladiator-outerclothing = Gladiator outer clothing
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
shipyard-console-menu-title = Shipyard Console
|
||||
|
||||
shipyard-console-error = Temporary embargo is in place, try later?
|
||||
shipyard-console-docking = {$vessel} is en route to the station, eta 60 seconds.
|
||||
|
|
@ -45,8 +45,4 @@ job-description-security = Catch criminals and enemies of the station, enforce t
|
|||
job-description-serviceworker = Learn the basics of bartending, cooking, and growing plants.
|
||||
job-description-visitor = Enjoy your visit to the station.
|
||||
job-description-warden = Patrol the security department, ensure that no one is stealing from the armory, and make sure that all prisoners are processed and let out when their time is up.
|
||||
job-description-zookeeper = Put on a joyful display of cute animals and space carps for all the crew to see. Currently unavailable.
|
||||
job-description-senior-engineer = Teach new engineers the basics of the station's engine, repairing, atmospherics and power.
|
||||
job-description-senior-researcher = Teach new scientists the basics of item printing, artifact research and anomalous objects.
|
||||
job-description-senior-physician = Teach new medics the basics of tending to the wounded, chemistry, diagnosing the diseased and disposing of the dead.
|
||||
job-description-senior-officer = Teach new officers the basics of searches, performing arrests, prison times and how to properly shoot a firearm.
|
||||
job-description-zookeeper = Put on a joyful display of cute animals and space carps for all the crew to see. Currently available on Gemini Station.
|
||||
|
|
|
|||
|
|
@ -90,10 +90,6 @@ JobSalvageSpecialist = Salvage Specialist
|
|||
JobScientist = Scientist
|
||||
JobSecurityCadet = Security Cadet
|
||||
JobSecurityOfficer = Security Officer
|
||||
JobSeniorEngineer = Senior Engineer
|
||||
JobSeniorOfficer = Senior Officer
|
||||
JobSeniorPhysician = Senior Physician
|
||||
JobSeniorResearcher = Senior Researcher
|
||||
JobServiceWorker = Service Worker
|
||||
JobStationEngineer = Station Engineer
|
||||
JobTechnicalAssistant = Technical Assistant
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue