mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-16 13:07:39 +03:00
[tweak] UI Tweaks (#1001)
* - tweak: update StyleSheetify * - add: flexbox * - fix: size of flexbox in launchergui * - tweak: Profile editor: start. * - add: categories * - tweak: help me please with this shi... loadouts * - fix: container path think * - tweak: thinks for optimisation * - add: group selection for loadoutpicker * - tweak: change position of preview * - add: reason text * - fix: Кролькины фиксы * - fix: кролькины фиксы ч.2 * - fix: кролькины фиксы ч.3 * - кролькины фиксы - финал * - fix: Ворчливого дедушкины фиксы, удаление старого барахла и пометка wwdp * - tweak: some ui change for LoadoutCategories and LoadoutEntry * - ворчливый дед фиксы ч.2 * - fix: очередные кролькины фиксы * - add: loadout prototype validation * - fix: description read from edit field
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<!-- Markings -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<OptionButton Name="CMarkingCategoryButton" StyleClasses="OpenBoth" />
|
||||
<OptionButton Name="CMarkingCategoryButton" StyleClasses="OpenBoth" Access="Public" /> <!-- WWDP EDIT -->
|
||||
<Label Name="CMarkingPoints" Text="uwu" Margin="8 0" />
|
||||
<LineEdit Name="CMarkingSearch" PlaceHolder="{Loc 'markings-search'}" HorizontalExpand="True" />
|
||||
<Button Name="ResetButton" Text="{Loc 'ui-options-bind-reset'}" StyleClasses="OpenLeft" />
|
||||
|
||||
@@ -110,6 +110,18 @@ public sealed partial class MarkingPicker : Control
|
||||
Populate(CMarkingSearch.Text);
|
||||
}
|
||||
|
||||
// WWDP EDIT START
|
||||
public bool Select(MarkingCategories category)
|
||||
{
|
||||
_selectedMarkingCategory = category;
|
||||
if (!CMarkingCategoryButton.TrySelectId(_markingCategories.IndexOf(_selectedMarkingCategory)))
|
||||
return false;
|
||||
Populate(CMarkingSearch.Text);
|
||||
UpdatePoints();
|
||||
return true;
|
||||
}
|
||||
// WWDP EDIT END
|
||||
|
||||
public MarkingPicker()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -126,6 +138,9 @@ public sealed partial class MarkingPicker : Control
|
||||
Populate(CMarkingSearch.Text);
|
||||
}
|
||||
|
||||
public bool IsCategoryValid(MarkingCategories category) =>
|
||||
!_ignoreCategories.Contains(category) && GetMarkings(category).Count > 0; // WWDP EDIT
|
||||
|
||||
private void SetupCategoryButtons()
|
||||
{
|
||||
CMarkingCategoryButton.Clear();
|
||||
@@ -208,6 +223,7 @@ public sealed partial class MarkingPicker : Control
|
||||
{
|
||||
Texture = marking.Sprites[0].DirFrame0().TextureFor(
|
||||
Enum.TryParse<Direction>(marking.PreviewDirection, out var dir) ? dir : Direction.South),
|
||||
TextureScale = new Vector2(1.5f) // WWDP EDIT
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:parallax="clr-namespace:Content.Client.Parallax"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:controlsWhite="clr-namespace:Content.Client._White.UserInterface.Controls">
|
||||
<parallax:ParallaxControl />
|
||||
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<PanelContainer StyleClasses="MainPanel" />
|
||||
<BoxContainer Orientation="Horizontal" MinSize="300 200" Margin="10">
|
||||
<controlsWhite:FlexBox MinSize="300 200" Margin="10">
|
||||
<BoxContainer Orientation="Vertical" Margin="0,0,5,0">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 5 0 8">
|
||||
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
|
||||
@@ -85,7 +86,7 @@
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controlsWhite:FlexBox>
|
||||
</Control>
|
||||
<!-- Bottom window for tips -->
|
||||
|
||||
|
||||
@@ -56,13 +56,6 @@ namespace Content.Client.Lobby
|
||||
_voteManager.SetPopupContainer(Lobby.VoteContainer);
|
||||
LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
var lobbyNameCvar = _cfg.GetCVar(CCVars.ServerLobbyName);
|
||||
var serverName = _baseClient.GameInfo?.ServerName ?? string.Empty;
|
||||
|
||||
Lobby.ServerName.Text = string.IsNullOrEmpty(lobbyNameCvar)
|
||||
? Loc.GetString("ui-lobby-title", ("serverName", serverName))
|
||||
: lobbyNameCvar;
|
||||
|
||||
var width = _cfg.GetCVar(CCVars.ServerLobbyRightPanelWidth);
|
||||
Lobby.RightSide.SetWidth = width;
|
||||
|
||||
|
||||
@@ -115,9 +115,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
|
||||
if (obj.WasModified<TraitPrototype>())
|
||||
_profileEditor.UpdateTraits(null, true);
|
||||
|
||||
if (obj.WasModified<LoadoutPrototype>())
|
||||
_profileEditor.UpdateLoadouts(null, true);
|
||||
}
|
||||
|
||||
private void PreferencesDataLoaded()
|
||||
|
||||
@@ -35,7 +35,27 @@
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" SeparationOverride="0">
|
||||
<ScrollContainer MinSize="325 0" Margin="5 5 0 0">
|
||||
<BoxContainer Name="Characters" Orientation="Vertical" />
|
||||
<!-- WWDP EDIT START -->
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer VerticalExpand="True" Name="Characters" Orientation="Vertical" />
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<SpriteView Name="CharacterSpriteView" Access="Public" OverrideDirection="South" Scale="7 7" />
|
||||
<BoxContainer HorizontalAlignment="Center">
|
||||
<Button Name="PreviewRotateLeftButton" StyleClasses="OpenRight" >
|
||||
<TextureRect TexturePath="/Textures/Interface/Default/left_arrow.svg.192dpi.png" Stretch="KeepAspectCentered"/>
|
||||
</Button>
|
||||
<Button Name="PreviewRotateRightButton" StyleClasses="OpenLeft">
|
||||
<TextureRect TexturePath="/Textures/Interface/Default/right_arrow.svg.192dpi.png" Stretch="KeepAspectCentered"/>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- WWDP EDIT END -->
|
||||
</ScrollContainer>
|
||||
<PanelContainer MinSize="2 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
|
||||
@@ -63,6 +63,18 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
|
||||
|
||||
//WWDP EDIT START
|
||||
profileEditor.CharacterSpriteView = CharacterSpriteView;
|
||||
CharacterSpriteView.SetEntity(profileEditor.PreviewDummy);
|
||||
PreviewRotateRightButton.OnPressed += args =>
|
||||
CharacterSpriteView.OverrideDirection =
|
||||
(Direction)(((int)(CharacterSpriteView.OverrideDirection ?? Direction.South) + 2) % 8);
|
||||
|
||||
PreviewRotateLeftButton.OnPressed += args =>
|
||||
CharacterSpriteView.OverrideDirection =
|
||||
(Direction)(((int)(CharacterSpriteView.OverrideDirection ?? Direction.South) + 6) % 8);
|
||||
//WWDP EDIT END
|
||||
|
||||
_cfg.OnValueChanged(CCVars.SeeOwnNotes, p => AdminRemarksButton.Visible = p, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Controls"
|
||||
xmlns:loadouts="clr-namespace:Content.Client._White.Loadouts"
|
||||
HorizontalExpand="True">
|
||||
<!-- Left side -->
|
||||
<BoxContainer Orientation="Vertical" Margin="10 10 10 10" HorizontalExpand="True">
|
||||
@@ -180,6 +181,22 @@
|
||||
<Label Name="WeightLabel" />
|
||||
</BoxContainer>
|
||||
</ui:AlternatingBGContainer>
|
||||
<!-- WD EDIT START -->
|
||||
<Control Margin="5"/>
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Vertical">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-markings-tab'}" />
|
||||
<Control Margin="5"/>
|
||||
<humanoid:MarkingPicker Name="Markings" HorizontalExpand="True" HorizontalAlignment="Stretch" Access="Public"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Vertical">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-loadouts-tab'}" />
|
||||
<Control Margin="5"/>
|
||||
<loadouts:LoadoutPicker Name="Loadouts" HorizontalExpand="True" HorizontalAlignment="Stretch" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<Control Margin="5"/>
|
||||
<!-- WD EDIT END -->
|
||||
<!-- Skin -->
|
||||
<BoxContainer Margin="0 4 0 4" HorizontalExpand="True" Orientation="Vertical">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-skin-color-label'}" />
|
||||
@@ -277,52 +294,8 @@
|
||||
|
||||
<ui:NeoTabContainer Name="TraitsTabs" VerticalExpand="True" SeparatorMargin="0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="LoadoutsTab" HorizontalExpand="True" Orientation="Vertical">
|
||||
<!-- Loadouts -->
|
||||
<Label Name="LoadoutPointsLabel" HorizontalAlignment="Stretch" Align="Center" />
|
||||
<ProgressBar Name="LoadoutPointsBar" MaxValue="1" Value="1" MaxHeight="8" Margin="0 5" />
|
||||
|
||||
<BoxContainer HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<Button
|
||||
Name="LoadoutsShowUnusableButton"
|
||||
Text="{Loc 'humanoid-profile-editor-loadouts-show-unusable-button'}"
|
||||
ToolTip="{Loc 'humanoid-profile-editor-loadouts-show-unusable-button-tooltip'}"
|
||||
ToggleMode="True"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight" />
|
||||
<Button
|
||||
Name="LoadoutsRemoveUnusableButton"
|
||||
Text="You shouldn't see this"
|
||||
ToolTip="{Loc 'humanoid-profile-editor-loadouts-remove-unusable-button-tooltip'}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
|
||||
<ui:NeoTabContainer Name="LoadoutsTabs" VerticalExpand="True" SeparatorMargin="0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="MarkingsTab" HorizontalExpand="True" Orientation="Vertical">
|
||||
<ScrollContainer HorizontalExpand="True" HScrollEnabled="True" VerticalExpand="True" VScrollEnabled="True">
|
||||
<!-- Markings -->
|
||||
<humanoid:MarkingPicker Name="Markings" IgnoreCategories="Hair,FacialHair" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</ui:NeoTabContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<!-- Right side -->
|
||||
<!-- Sprite View -->
|
||||
<PanelContainer VerticalExpand="True" VerticalAlignment="Center" StyleClasses="PanelBackground"> <!-- WD EDIT -->
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#2f2f2f" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Center">
|
||||
<SpriteView Name="SpriteViewS" OverrideDirection="South" Scale="4 4" />
|
||||
<SpriteView Name="SpriteViewN" OverrideDirection="North" Scale="4 4" />
|
||||
<SpriteView Name="SpriteViewE" OverrideDirection="East" Scale="4 4" />
|
||||
<SpriteView Name="SpriteViewW" OverrideDirection="West" Scale="4 4" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -106,7 +106,6 @@ namespace Content.Client.Lobby.UI
|
||||
private Dictionary<Button, ConfirmationData> _confirmationData = new();
|
||||
private List<TraitPreferenceSelector> _traitPreferences = new();
|
||||
private int _traitCount;
|
||||
private HashSet<LoadoutPreferenceSelector> _loadoutPreferences = new();
|
||||
|
||||
private bool _customizePronouns;
|
||||
private bool _customizeStationAiName;
|
||||
@@ -145,6 +144,8 @@ namespace Content.Client.Lobby.UI
|
||||
private const string MimeNames = "MimeNames";
|
||||
|
||||
private const string Uncategorized = "Uncategorized";
|
||||
|
||||
public SpriteView? CharacterSpriteView;
|
||||
// WD EDIT END
|
||||
|
||||
public HumanoidProfileEditor(
|
||||
@@ -597,31 +598,8 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
#endregion
|
||||
|
||||
#region Loadouts
|
||||
|
||||
// Set up the loadouts tab
|
||||
LoadoutsTab.Orphan();
|
||||
CTabContainer.AddTab(LoadoutsTab, Loc.GetString("humanoid-profile-editor-loadouts-tab"));
|
||||
_loadoutPreferences = new();
|
||||
|
||||
// Show/Hide the loadouts tab if they ever get enabled/disabled
|
||||
var loadoutsEnabled = cfgManager.GetCVar(CCVars.GameLoadoutsEnabled);
|
||||
CTabContainer.SetTabVisible(4, loadoutsEnabled);
|
||||
ShowLoadouts.Visible = loadoutsEnabled;
|
||||
cfgManager.OnValueChanged(CCVars.GameLoadoutsEnabled, LoadoutsChanged);
|
||||
|
||||
LoadoutsShowUnusableButton.OnToggled += args => UpdateLoadouts(args.Pressed);
|
||||
LoadoutsRemoveUnusableButton.OnPressed += _ => TryRemoveUnusableLoadouts();
|
||||
|
||||
UpdateLoadouts(false);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Markings
|
||||
|
||||
MarkingsTab.Orphan();
|
||||
CTabContainer.AddTab(MarkingsTab, Loc.GetString("humanoid-profile-editor-markings-tab"));
|
||||
|
||||
Markings.OnMarkingAdded += OnMarkingChange;
|
||||
Markings.OnMarkingRemoved += OnMarkingChange;
|
||||
Markings.OnMarkingColorChange += OnMarkingChange;
|
||||
@@ -640,6 +618,7 @@ namespace Content.Client.Lobby.UI
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
|
||||
ReloadPreview();
|
||||
InitializeCharacterMenu(); // WWDP EDIT
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
@@ -944,10 +923,7 @@ namespace Content.Client.Lobby.UI
|
||||
return;
|
||||
|
||||
PreviewDummy = _controller.LoadProfileEntity(Profile, null, ShowClothes.Pressed, ShowLoadouts.Pressed);
|
||||
SpriteViewS.SetEntity(PreviewDummy);
|
||||
SpriteViewN.SetEntity(PreviewDummy);
|
||||
SpriteViewE.SetEntity(PreviewDummy);
|
||||
SpriteViewW.SetEntity(PreviewDummy);
|
||||
CharacterSpriteView?.SetEntity(PreviewDummy); // WWDP EDIT
|
||||
|
||||
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
|
||||
SetDirty();
|
||||
@@ -1009,6 +985,8 @@ namespace Content.Client.Lobby.UI
|
||||
UpdateEyePickers();
|
||||
UpdateSaveButton();
|
||||
UpdateMarkings();
|
||||
UpdateLoadouts(); // WD EDIT
|
||||
CheckpointLoadouts(); // WD EDIT
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
UpdateCMarkingsFacialHair();
|
||||
@@ -1048,7 +1026,6 @@ namespace Content.Client.Lobby.UI
|
||||
}
|
||||
|
||||
TraitsTabs.UpdateTabMerging();
|
||||
LoadoutsTabs.UpdateTabMerging();
|
||||
|
||||
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
|
||||
SetDirty();
|
||||
@@ -1893,10 +1870,7 @@ namespace Content.Client.Lobby.UI
|
||||
else // Whelp, the fixture doesn't exist, guesstimate it instead
|
||||
WeightLabel.Text = Loc.GetString("humanoid-profile-editor-weight-label", ("weight", (int) 71));
|
||||
|
||||
SpriteViewS.InvalidateMeasure();
|
||||
SpriteViewN.InvalidateMeasure();
|
||||
SpriteViewE.InvalidateMeasure();
|
||||
SpriteViewW.InvalidateMeasure();
|
||||
CharacterSpriteView?.InvalidateMeasure();
|
||||
}
|
||||
|
||||
private void UpdateHairPickers()
|
||||
@@ -2157,7 +2131,6 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
foreach (var tab in TraitsTabs.TakenIds)
|
||||
TraitsTabs.RemoveTab(tab);
|
||||
_loadoutPreferences.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -2374,6 +2347,23 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
#region Functions
|
||||
|
||||
private BoxContainer? FindCategory(string id, NeoTabContainer parent)
|
||||
{
|
||||
BoxContainer? match = null;
|
||||
|
||||
if(parent.TryFindTabByAlias(id, out var tabId))
|
||||
match = parent.GetControl<BoxContainer>(tabId);
|
||||
|
||||
|
||||
if (match != null)
|
||||
return match;
|
||||
|
||||
foreach (var subcategory in parent.GetControls<NeoTabContainer>())
|
||||
match ??= FindCategory(id, subcategory);
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
private Dictionary<string, object> CreateTree(List<TraitCategoryPrototype> cats)
|
||||
{
|
||||
var tree = new Dictionary<string, object>();
|
||||
@@ -2421,385 +2411,6 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
#endregion
|
||||
|
||||
#region Loadouts
|
||||
|
||||
#region Updates
|
||||
|
||||
private void UpdateLoadoutPreferences()
|
||||
{
|
||||
var points = _cfgManager.GetCVar(CCVars.GameLoadoutsPoints);
|
||||
LoadoutPointsBar.Value = points;
|
||||
LoadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", points));
|
||||
|
||||
foreach (var preferenceSelector in _loadoutPreferences)
|
||||
{
|
||||
var loadoutId = preferenceSelector.Loadout.ID;
|
||||
var loadoutPreference = Profile?.LoadoutPreferences.FirstOrDefault(l => l.LoadoutName == loadoutId) ?? preferenceSelector.Preference;
|
||||
var preference = new LoadoutPreference(
|
||||
loadoutPreference.LoadoutName,
|
||||
loadoutPreference.CustomName,
|
||||
loadoutPreference.CustomDescription,
|
||||
loadoutPreference.CustomContent, // WD EDIT
|
||||
loadoutPreference.CustomColorTint,
|
||||
loadoutPreference.CustomHeirloom)
|
||||
{ Selected = loadoutPreference.Selected };
|
||||
|
||||
preferenceSelector.Preference = preference;
|
||||
|
||||
if (preference.Selected)
|
||||
{
|
||||
points -= preferenceSelector.Loadout.Cost;
|
||||
LoadoutPointsBar.Value = points;
|
||||
LoadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", LoadoutPointsBar.MaxValue));
|
||||
}
|
||||
}
|
||||
|
||||
// Set the remove unusable button's label to have the correct amount of unusable loadouts
|
||||
LoadoutsRemoveUnusableButton.Text = Loc.GetString("humanoid-profile-editor-loadouts-remove-unusable-button",
|
||||
("count", _loadouts
|
||||
.Where(l => _loadoutPreferences
|
||||
.Where(lps => lps.Preference.Selected).Select(lps => lps.Loadout).Contains(l.Key))
|
||||
.Count(l => !l.Value
|
||||
|| !_loadoutPreferences.First(lps => lps.Loadout == l.Key).Wearable)));
|
||||
AdminUIHelpers.RemoveConfirm(LoadoutsRemoveUnusableButton, _confirmationData);
|
||||
|
||||
IsDirty = true;
|
||||
ReloadProfilePreview();
|
||||
}
|
||||
|
||||
private Dictionary<LoadoutPrototype, bool> _loadouts = new();
|
||||
private Dictionary<string, EntityUid> _dummyLoadouts = new();
|
||||
public void UpdateLoadouts(bool? showUnusable = null, bool reload = false)
|
||||
{
|
||||
showUnusable ??= LoadoutsShowUnusableButton.Pressed;
|
||||
|
||||
// Reset loadout points so you don't get -14 points or something for no reason
|
||||
var points = _cfgManager.GetCVar(CCVars.GameLoadoutsPoints);
|
||||
LoadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", points));
|
||||
LoadoutPointsBar.MaxValue = points;
|
||||
LoadoutPointsBar.Value = points;
|
||||
|
||||
// Reset the whole UI and delete caches
|
||||
if (reload)
|
||||
{
|
||||
foreach (var tab in LoadoutsTabs.TakenIds)
|
||||
LoadoutsTabs.RemoveTab(tab);
|
||||
foreach (var uid in _dummyLoadouts)
|
||||
_entManager.QueueDeleteEntity(uid.Value);
|
||||
_loadoutPreferences.Clear();
|
||||
}
|
||||
|
||||
|
||||
// Get the highest priority job to use for loadout filtering
|
||||
var highJob = _controller.GetPreferredJob(Profile ?? HumanoidCharacterProfile.DefaultWithSpecies());
|
||||
|
||||
_loadouts.Clear();
|
||||
foreach (var loadout in _prototypeManager.EnumeratePrototypes<LoadoutPrototype>())
|
||||
{
|
||||
var usable = _characterRequirementsSystem.CheckRequirementsValid(
|
||||
loadout.Requirements,
|
||||
highJob ?? new JobPrototype(),
|
||||
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
|
||||
_requirements.GetRawPlayTimeTrackers(),
|
||||
_requirements.IsWhitelisted(),
|
||||
loadout,
|
||||
_entManager,
|
||||
_prototypeManager,
|
||||
_cfgManager,
|
||||
out _
|
||||
);
|
||||
_loadouts.Add(loadout, usable);
|
||||
|
||||
var list = _loadoutPreferences.ToList();
|
||||
if (list.FindIndex(lps => lps.Loadout.ID == loadout.ID) is not (not -1 and var i))
|
||||
continue;
|
||||
|
||||
var selector = list[i];
|
||||
UpdateSelector(selector, usable);
|
||||
}
|
||||
|
||||
if (_loadouts.Count == 0)
|
||||
{
|
||||
LoadoutsTabs.AddTab(new Label { Text = Loc.GetString("humanoid-profile-editor-loadouts-no-loadouts") },
|
||||
Loc.GetString("loadout-category-Uncategorized"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LoadoutsTabs.TryFindTabByAlias(Uncategorized, out var id))
|
||||
{
|
||||
var uncategorizedB = new BoxContainer
|
||||
{
|
||||
Name = Uncategorized,
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
// I hate ScrollContainers
|
||||
Children =
|
||||
{
|
||||
new ScrollContainer
|
||||
{
|
||||
HScrollEnabled = false,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
id = LoadoutsTabs.AddTab(uncategorizedB, Loc.GetString("loadout-category-Uncategorized"));
|
||||
LoadoutsTabs.SetTabAlias(id, Uncategorized);
|
||||
}
|
||||
|
||||
var uncategorized = LoadoutsTabs.GetControl<BoxContainer>(id)!;
|
||||
|
||||
// Create a Dictionary/tree of categories and subcategories
|
||||
var cats = CreateTree(_prototypeManager.EnumeratePrototypes<LoadoutCategoryPrototype>()
|
||||
.Where(c => c.Root)
|
||||
.OrderBy(c => Loc.GetString($"loadout-category-{c.ID}"))
|
||||
.ToList());
|
||||
var categories = new Dictionary<string, object>();
|
||||
foreach (var (key, value) in cats)
|
||||
categories.Add(key, value);
|
||||
|
||||
// Create the UI elements for the category tree
|
||||
CreateCategoryUI(categories, LoadoutsTabs);
|
||||
|
||||
// Fill categories with loadouts
|
||||
foreach (var (loadout, usable) in _loadouts
|
||||
.OrderBy(l => l.Key.ID)
|
||||
.ThenBy(l => Loc.GetString($"loadout-name-{l.Key.ID}"))
|
||||
.ThenBy(l => l.Key.Cost))
|
||||
{
|
||||
if (_loadoutPreferences.Select(lps => lps.Loadout.ID).Contains(loadout.ID))
|
||||
{
|
||||
var first = _loadoutPreferences.First(lps => lps.Loadout.ID == loadout.ID);
|
||||
var prof = Profile?.LoadoutPreferences.FirstOrDefault(lp => lp.LoadoutName == loadout.ID);
|
||||
first.Preference = new(loadout.ID, prof?.CustomName, prof?.CustomDescription, prof?.CustomContent, prof?.CustomColorTint, prof?.CustomHeirloom); // WD EDIT
|
||||
UpdateSelector(first, usable);
|
||||
continue;
|
||||
}
|
||||
|
||||
var selector = new LoadoutPreferenceSelector(
|
||||
loadout, highJob ?? new JobPrototype(),
|
||||
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), ref _dummyLoadouts,
|
||||
_entManager, _prototypeManager, _cfgManager, _characterRequirementsSystem, _requirements)
|
||||
{ Preference = new(loadout.ID) };
|
||||
UpdateSelector(selector, usable);
|
||||
AddSelector(selector);
|
||||
|
||||
// Look for an existing category tab
|
||||
var match = FindCategory(loadout.Category, LoadoutsTabs);
|
||||
|
||||
// If there is no category put it in Uncategorized (this shouldn't happen)
|
||||
(match ?? uncategorized).Children.First().Children.First().AddChild(selector);
|
||||
}
|
||||
|
||||
// Hide any empty tabs
|
||||
HideEmptyTabs(_prototypeManager.EnumeratePrototypes<LoadoutCategoryPrototype>().ToList());
|
||||
|
||||
UpdateLoadoutPreferences();
|
||||
return;
|
||||
|
||||
|
||||
void UpdateSelector(LoadoutPreferenceSelector selector, bool usable)
|
||||
{
|
||||
selector.Valid = usable;
|
||||
selector.ShowUnusable = showUnusable.Value;
|
||||
|
||||
foreach (var item in selector.Loadout.Items)
|
||||
{
|
||||
if (_dummyLoadouts.TryGetValue(selector.Loadout.ID + selector.Loadout.Items.IndexOf(item), out var entity)
|
||||
&& _entManager.GetComponent<MetaDataComponent>(entity).EntityPrototype!.ID == item)
|
||||
{
|
||||
if (!_entManager.HasComponent<ClothingComponent>(entity))
|
||||
{
|
||||
selector.Wearable = true;
|
||||
continue;
|
||||
}
|
||||
selector.Wearable = _characterRequirementsSystem.CanEntityWearItem(PreviewDummy, entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
entity = _entManager.SpawnEntity(item, MapCoordinates.Nullspace);
|
||||
_dummyLoadouts[selector.Loadout.ID + selector.Loadout.Items.IndexOf(item)] = entity;
|
||||
|
||||
if (!_entManager.HasComponent<ClothingComponent>(entity))
|
||||
{
|
||||
selector.Wearable = true;
|
||||
continue;
|
||||
}
|
||||
selector.Wearable = _characterRequirementsSystem.CanEntityWearItem(PreviewDummy, entity);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateCategoryUI(Dictionary<string, object> tree, NeoTabContainer parent)
|
||||
{
|
||||
foreach (var (key, value) in tree)
|
||||
{
|
||||
// If the category's container exists already, ignore it
|
||||
if (parent.TryFindTabByAlias(key, out _))
|
||||
continue;
|
||||
|
||||
// If the value is a list of LoadoutPrototypes, create a final tab for them
|
||||
if (value is List<LoadoutPrototype>)
|
||||
{
|
||||
var category = new BoxContainer
|
||||
{
|
||||
Name = key,
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new ScrollContainer
|
||||
{
|
||||
HScrollEnabled = false,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var catId = parent.AddTab(category, Loc.GetString($"loadout-category-{key}"));
|
||||
parent.SetTabAlias(catId, key);
|
||||
}
|
||||
// If the value is a dictionary, create a new tab for it and recursively call this function to fill it
|
||||
else
|
||||
{
|
||||
var category = new NeoTabContainer
|
||||
{
|
||||
Name = key,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
SeparatorMargin = new Thickness(0),
|
||||
};
|
||||
|
||||
var catId = parent.AddTab(category, Loc.GetString($"loadout-category-{key}"));
|
||||
parent.SetTabAlias(catId, key);
|
||||
CreateCategoryUI((Dictionary<string, object>) value, category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddSelector(LoadoutPreferenceSelector selector)
|
||||
{
|
||||
_loadoutPreferences.Add(selector);
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
// Make sure they have enough loadout points
|
||||
var wasSelected = Profile?.LoadoutPreferences
|
||||
.FirstOrDefault(it => it.LoadoutName == selector.Loadout.ID)
|
||||
?.Selected ?? false;
|
||||
var selected = preference.Selected && (wasSelected || CheckPoints(-selector.Loadout.Cost, true));
|
||||
|
||||
// Update Preferences
|
||||
Profile = Profile?.WithLoadoutPreference(
|
||||
selector.Loadout.ID,
|
||||
selected,
|
||||
preference.CustomName,
|
||||
preference.CustomDescription,
|
||||
preference.CustomContent, // WD EDIT
|
||||
preference.CustomColorTint,
|
||||
preference.CustomHeirloom);
|
||||
IsDirty = true;
|
||||
UpdateLoadoutPreferences();
|
||||
SetProfile(Profile, CharacterSlot);
|
||||
};
|
||||
}
|
||||
|
||||
bool CheckPoints(int points, bool preference)
|
||||
{
|
||||
var temp = LoadoutPointsBar.Value + points;
|
||||
return preference ? temp >= 0 : temp < 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
private Dictionary<string, object> CreateTree(List<LoadoutCategoryPrototype> cats)
|
||||
{
|
||||
var tree = new Dictionary<string, object>();
|
||||
foreach (var category in cats)
|
||||
{
|
||||
// If the category is already in the tree, ignore it
|
||||
if (tree.ContainsKey(category.ID))
|
||||
continue;
|
||||
|
||||
// Categories don't have a Parent field, so we need to instead check the SubCategories of every Category
|
||||
var subCategories = category.SubCategories.Where(subCategory => !tree.ContainsKey(subCategory)).ToList();
|
||||
// If there are no subcategories, add a loadout spot to the dictionary
|
||||
if (subCategories.Count == 0)
|
||||
{
|
||||
tree.Add(category.ID, new List<LoadoutPrototype>());
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are subcategories, we need to add them to the dictionary as well
|
||||
var subCategoryTree = CreateTree(subCategories.Select(c => _prototypeManager.Index(c)).ToList());
|
||||
tree.Add(category.ID, subCategoryTree);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private BoxContainer? FindCategory(string id, NeoTabContainer parent)
|
||||
{
|
||||
BoxContainer? match = null;
|
||||
|
||||
if(parent.TryFindTabByAlias(id, out var tabId))
|
||||
match = parent.GetControl<BoxContainer>(tabId);
|
||||
|
||||
|
||||
if (match != null)
|
||||
return match;
|
||||
|
||||
foreach (var subcategory in parent.GetControls<NeoTabContainer>())
|
||||
match ??= FindCategory(id, subcategory);
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
private void HideEmptyTabs(List<LoadoutCategoryPrototype> cats)
|
||||
{
|
||||
// TODO: HIDE LOGIC LATER
|
||||
}
|
||||
|
||||
private void TryRemoveUnusableLoadouts()
|
||||
{
|
||||
// Confirm the user wants to remove unusable loadouts
|
||||
if (!AdminUIHelpers.TryConfirm(LoadoutsRemoveUnusableButton, _confirmationData))
|
||||
return;
|
||||
|
||||
// Remove unusable and unwearable loadouts
|
||||
foreach (var (loadout, _) in
|
||||
_loadouts.Where(l =>
|
||||
!l.Value || !_loadoutPreferences.First(lps => lps.Loadout.ID == l.Key.ID).Wearable).ToList())
|
||||
Profile = Profile?.WithLoadoutPreference(loadout.ID, false);
|
||||
UpdateCharacterRequired();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateCharacterRequired()
|
||||
{
|
||||
@@ -2808,7 +2419,6 @@ namespace Content.Client.Lobby.UI
|
||||
RefreshLifepaths();
|
||||
RefreshJobs();
|
||||
UpdateTraits(TraitsShowUnusableButton.Pressed);
|
||||
UpdateLoadouts(LoadoutsShowUnusableButton.Pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ public sealed partial class LoadoutPreferenceSelector : Control
|
||||
|
||||
public LoadoutPrototype Loadout { get; }
|
||||
|
||||
private LoadoutPreference _preference = null!;
|
||||
public LoadoutPreference Preference
|
||||
private Loadout _preference = null!;
|
||||
public Loadout Preference
|
||||
{
|
||||
get => _preference;
|
||||
set
|
||||
@@ -53,7 +53,6 @@ public sealed partial class LoadoutPreferenceSelector : Control
|
||||
if (value.CustomColorTint != null)
|
||||
UpdatePaint(new(DummyEntityUid, _entityManager.GetComponent<PaintedComponent>(DummyEntityUid)), _entityManager);
|
||||
HeirloomButton.Pressed = value.CustomHeirloom ?? false;
|
||||
PreferenceButton.Pressed = value.Selected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +83,7 @@ public sealed partial class LoadoutPreferenceSelector : Control
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<LoadoutPreference>? PreferenceChanged;
|
||||
|
||||
public event Action<Loadout>? PreferenceChanged;
|
||||
|
||||
public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
|
||||
HumanoidCharacterProfile profile, ref Dictionary<string, EntityUid> entities,
|
||||
@@ -207,28 +205,6 @@ public sealed partial class LoadoutPreferenceSelector : Control
|
||||
},
|
||||
},
|
||||
});
|
||||
PreferenceButton.OnToggled += args =>
|
||||
{
|
||||
if (args.Pressed == _preference.Selected)
|
||||
return;
|
||||
|
||||
_preference.Selected = args.Pressed;
|
||||
PreferenceChanged?.Invoke(Preference);
|
||||
};
|
||||
HeirloomButton.OnToggled += args =>
|
||||
{
|
||||
if (args.Pressed == _preference.Selected)
|
||||
return;
|
||||
|
||||
_preference.CustomHeirloom = args.Pressed ? true : null;
|
||||
PreferenceChanged?.Invoke(Preference);
|
||||
};
|
||||
SaveButton.OnPressed += _ =>
|
||||
{
|
||||
_preference.CustomColorTint = SpecialColorTintToggle.Pressed ? ColorEdit.Color.ToHex() : null;
|
||||
_preference.Selected = PreferenceButton.Pressed;
|
||||
PreferenceChanged?.Invoke(Preference);
|
||||
};
|
||||
|
||||
// Update prefs cache when something changes
|
||||
NameEdit.OnTextChanged += _ =>
|
||||
|
||||
@@ -104,16 +104,10 @@
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
<!-- Right Panel -->
|
||||
<PanelContainer Name="RightSide" Access="Public" StyleClasses="AngleRect" HorizontalAlignment="Right" VerticalExpand="True"
|
||||
<PanelContainer Name="RightSide" Access="Public" StyleClasses="ChatMainPanel" HorizontalAlignment="Right" VerticalExpand="True"
|
||||
VerticalAlignment="Stretch">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<!-- Top row -->
|
||||
<BoxContainer Orientation="Horizontal" MinSize="0 40" Name="HeaderContainer" Access="Public"
|
||||
SeparationOverride="4">
|
||||
<Label Name="ServerName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
|
||||
HorizontalExpand="True" HorizontalAlignment="Left" />
|
||||
</BoxContainer>
|
||||
<controls:HSpacer Spacing="10" />
|
||||
<Control Margin="0,3,0,0">
|
||||
<PanelContainer StyleClasses="PaperBackground" Margin="5,10,5,5">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using Content.Shared.Clothing.Loadouts.Systems;
|
||||
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
// WWDP PARTIAL CLASS
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private void InitializeCharacterMenu()
|
||||
{
|
||||
Loadouts.OnLoadoutsChanged += OnLoadoutsChange;
|
||||
}
|
||||
|
||||
private void UpdateLoadouts()
|
||||
{
|
||||
if (Profile == null)
|
||||
return;
|
||||
|
||||
var highJob = _controller.GetPreferredJob(Profile);
|
||||
|
||||
Loadouts.SetData(
|
||||
Profile.LoadoutPreferencesList,
|
||||
new(
|
||||
highJob,
|
||||
Profile,
|
||||
_requirements.GetRawPlayTimeTrackers(),
|
||||
_requirements.IsWhitelisted()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void CheckpointLoadouts()
|
||||
{
|
||||
if (Profile == null)
|
||||
return;
|
||||
Loadouts.SetCheckpoint();
|
||||
}
|
||||
|
||||
private void OnLoadoutsChange(List<Loadout> loadouts)
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithLoadoutPreference(loadouts);
|
||||
ReloadProfilePreview();
|
||||
ReloadClothes();
|
||||
UpdateLoadouts();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using Content.StyleSheetify.Client.StyleSheet;
|
||||
using Content.StyleSheetify.Client.StyleSheet.StyleBox;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
|
||||
namespace Content.Client.Stylesheets;
|
||||
|
||||
|
||||
// WWDP CLASS
|
||||
public sealed class DummyStylesheetManager : IStylesheetManager
|
||||
{
|
||||
public Stylesheet SheetNano { get; } = new Stylesheet([]);
|
||||
public Stylesheet SheetSpace { get; } = new Stylesheet([]);
|
||||
public StylesheetReference SheetNano { get; } = StylesheetReference.Empty;
|
||||
public StylesheetReference SheetSpace { get; } = StylesheetReference.Empty;
|
||||
|
||||
public DummyStylesheetManager()
|
||||
{
|
||||
@@ -16,5 +15,4 @@ public sealed class DummyStylesheetManager : IStylesheetManager
|
||||
}
|
||||
|
||||
public void Initialize(){}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Content.StyleSheetify.Client.StyleSheet;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Stylesheets
|
||||
{
|
||||
public interface IStylesheetManager
|
||||
{
|
||||
Stylesheet SheetNano { get; }
|
||||
Stylesheet SheetSpace { get; }
|
||||
StylesheetReference SheetNano { get; } // WWDP EDIT
|
||||
StylesheetReference SheetSpace { get; } // WWDP EDIT
|
||||
|
||||
void Initialize();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.StyleSheetify.Client.StyleSheet;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
||||
namespace Content.Client.Stylesheets
|
||||
{
|
||||
@@ -12,74 +9,17 @@ namespace Content.Client.Stylesheets
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IContentStyleSheetManager _contentStyleSheetManager = default!;
|
||||
[Dependency] private readonly IContentStyleSheetManager _contentStyleSheetManager = default!; // WWDP EDIT
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
public StylesheetReference SheetNano { get; private set; } = default!; // WWDP EDIT
|
||||
public StylesheetReference SheetSpace { get; private set; } = default!; // WWDP EDIT
|
||||
|
||||
public Stylesheet SheetNano { get; private set; } = default!;
|
||||
public Stylesheet SheetSpace { get; private set; } = default!;
|
||||
|
||||
// WWDP EDIT START
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = Logger.GetSawmill("StylesheetManager");
|
||||
LoadStyles();
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
private void LoadStyles()
|
||||
{
|
||||
SheetNano = TryMerge(new StyleNano(_resourceCache).Stylesheet, "nano");
|
||||
SheetSpace = TryMerge(new StyleSpace(_resourceCache).Stylesheet, "space");
|
||||
SheetNano = _contentStyleSheetManager.MergeStyles(new StyleNano(_resourceCache).Stylesheet, "nano"); // WWDP EDIT
|
||||
SheetSpace = _contentStyleSheetManager.MergeStyles(new StyleSpace(_resourceCache).Stylesheet, "space"); // WWDP EDIT
|
||||
|
||||
_userInterfaceManager.Stylesheet = SheetNano;
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
Logger.Debug("Reloading styles...");
|
||||
LoadStyles();
|
||||
}
|
||||
|
||||
private Stylesheet TryMerge(Stylesheet stylesheet, string prefix)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<StyleSheetPrototype>(prefix, out var proto))
|
||||
return stylesheet;
|
||||
|
||||
var rules = stylesheet.Rules.ToDictionary(r => r.Selector, r => r);
|
||||
var newRules = _contentStyleSheetManager.GetStyleRules(proto).ToDictionary(r => r.Selector, r => r);
|
||||
|
||||
var mergedPropsCount = 0;
|
||||
var mergedStylesCount = 0;
|
||||
var addedStylesCount = 0;
|
||||
|
||||
foreach (var (key,value) in newRules)
|
||||
{
|
||||
if (rules.TryGetValue(key, out var oriValue))
|
||||
{
|
||||
var oriProps = oriValue.Properties.ToDictionary(a => a.Name);
|
||||
foreach (var props in value.Properties)
|
||||
{
|
||||
oriProps[props.Name] = props;
|
||||
mergedPropsCount++;
|
||||
}
|
||||
|
||||
rules[key] = new(key, oriProps.Values.ToList());
|
||||
mergedStylesCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
rules[key] = value;
|
||||
addedStylesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_sawmill.Debug($"Successfully merged style {prefix}: {mergedPropsCount} props merged and {mergedStylesCount} styles merged and {addedStylesCount} styles added!");
|
||||
|
||||
return new(rules.Values.ToList());
|
||||
}
|
||||
|
||||
// WWDP EDIT END
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:xe="clr-namespace:Content.Client.UserInterface.XamlExtensions"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.MenuBar.Widgets"
|
||||
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Controls"
|
||||
Name = "MenuButtons"
|
||||
VerticalExpand="False"
|
||||
Orientation="Horizontal"
|
||||
@@ -13,7 +14,11 @@
|
||||
VerticalAlignment="Top"
|
||||
SeparationOverride="5"
|
||||
>
|
||||
<ui:MenuButton
|
||||
<controls:FlexBox
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
JustifyContent="Center">
|
||||
<ui:MenuButton
|
||||
Name="EscapeButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/menu.png'}"
|
||||
@@ -24,103 +29,104 @@
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenRight}"
|
||||
IconScale="2,2"
|
||||
/> <!-- WD EDIT -->
|
||||
<ui:MenuButton
|
||||
Name="GuidebookButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/chest.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-guide-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenGuidebook}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="CharacterButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/person.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-character-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenCharacterMenu}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="EmotesButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/emotion.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-emotions-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenEmotesMenu}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/> <!-- WD EDIT -->
|
||||
<ui:MenuButton
|
||||
Name="CraftingButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/craft.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenCraftingMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-crafting-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="ActionButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/fist.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenActionsMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-actions-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="LanguageButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/language.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenLanguageMenu}"
|
||||
ToolTip="Open the Language Menu"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="AdminButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/panel.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenAdminMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-admin-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="SandboxButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/sandbox.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenSandboxWindow}"
|
||||
ToolTip="{Loc 'game-hud-open-sandbox-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="AHelpButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/warning.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenAHelp}"
|
||||
ToolTip="{Loc 'ui-options-function-open-a-help'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenLeft}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="GuidebookButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/chest.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-guide-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenGuidebook}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="CharacterButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/person.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-character-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenCharacterMenu}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="EmotesButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/emotion.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-emotions-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenEmotesMenu}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/> <!-- WD EDIT -->
|
||||
<ui:MenuButton
|
||||
Name="CraftingButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/craft.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenCraftingMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-crafting-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="ActionButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/fist.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenActionsMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-actions-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="LanguageButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/language.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenLanguageMenu}"
|
||||
ToolTip="Open the Language Menu"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="AdminButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/panel.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenAdminMenu}"
|
||||
ToolTip="{Loc 'game-hud-open-admin-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="SandboxButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/sandbox.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenSandboxWindow}"
|
||||
ToolTip="{Loc 'game-hud-open-sandbox-menu-button-tooltip'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="AHelpButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/_White/NovaUI/Icons/warning.png'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenAHelp}"
|
||||
ToolTip="{Loc 'ui-options-function-open-a-help'}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenLeft}"
|
||||
IconScale="2,2"
|
||||
/>
|
||||
</controls:FlexBox>
|
||||
</widgets:GameTopMenuBar>
|
||||
|
||||
99
Content.Client/_White/DatumContainer/LocalDatumContainer.cs
Normal file
99
Content.Client/_White/DatumContainer/LocalDatumContainer.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
|
||||
namespace Content.Client._White.DatumContainer;
|
||||
|
||||
|
||||
public sealed class LocalDatumContainer<T> where T : notnull
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||
|
||||
private readonly ResPath _datumPath;
|
||||
private readonly ResPath _rootPath = new ResPath("/Datum/");
|
||||
|
||||
private Dictionary<string, T> _data = new();
|
||||
|
||||
public LocalDatumContainer(string datumName)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_datumPath = _rootPath / new ResPath($"{datumName}.yaml");
|
||||
LoadDataFromUserData();
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key,[NotNullWhen(true)] out T? value) =>
|
||||
_data.TryGetValue(key, out value);
|
||||
|
||||
public void SetValue(string key, T? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
RemoveValue(key);
|
||||
return;
|
||||
}
|
||||
|
||||
_data[key] = value;
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public void RemoveValue(string key)
|
||||
{
|
||||
_data.Remove(key);
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void Dirty()
|
||||
{
|
||||
try
|
||||
{
|
||||
var rootNode = _serializationManager.WriteValue(_data, notNullableOverride: true);
|
||||
using var stream = _resourceManager.UserData.Open(_datumPath, FileMode.Create);
|
||||
using var textWriter = new StreamWriter(stream);
|
||||
rootNode.Write(textWriter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorS("datum", $"Failed to save datum to {_datumPath}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDataFromUserData()
|
||||
{
|
||||
if(!_resourceManager.UserData.IsDir(_rootPath))
|
||||
_resourceManager.UserData.CreateDir(_rootPath);
|
||||
|
||||
if (!_resourceManager.UserData.Exists(_datumPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = _resourceManager.UserData.Open(_datumPath, FileMode.Open);
|
||||
using var textReadStream = new StreamReader(stream);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(textReadStream);
|
||||
|
||||
if (yamlStream.Documents.Count == 0)
|
||||
return;
|
||||
|
||||
var loaded = _serializationManager.Read(
|
||||
typeof(Dictionary<string, T>),
|
||||
yamlStream.Documents[0].RootNode.ToDataNode(),
|
||||
notNullableOverride: false);
|
||||
|
||||
if (loaded is Dictionary<string, T> dictionary)
|
||||
_data = dictionary;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.WarningS("datum", $"Failed to load datum from {_datumPath}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public T? GetValueOrDefault(string key) => _data.GetValueOrDefault(key);
|
||||
}
|
||||
110
Content.Client/_White/Loadouts/LoadoutCategory.cs
Normal file
110
Content.Client/_White/Loadouts/LoadoutCategory.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Clothing.Loadouts.Prototypes;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
|
||||
namespace Content.Client._White.Loadouts;
|
||||
|
||||
|
||||
public interface ILoadoutMenuEntry
|
||||
{
|
||||
public ILoadoutMenuEntry? Parent { get; set; }
|
||||
public string Label { get; }
|
||||
|
||||
public void Act(BoxContainer loadoutsContainer, LoadoutPicker loadoutPicker);
|
||||
public void Exit(BoxContainer loadoutsContainer , LoadoutPicker loadoutPicker);
|
||||
}
|
||||
|
||||
public sealed class LoadoutCategoryShowMenuEntry : ILoadoutMenuEntry
|
||||
{
|
||||
private readonly ProtoId<LoadoutCategoryPrototype> _loadoutCategory;
|
||||
public ILoadoutMenuEntry? Parent { get; set; }
|
||||
public string Label { get; }
|
||||
|
||||
public LoadoutCategoryShowMenuEntry(ProtoId<LoadoutCategoryPrototype> loadoutCategory)
|
||||
{
|
||||
_loadoutCategory = loadoutCategory;
|
||||
Label = Loc.GetString($"loadout-category-{loadoutCategory}");
|
||||
}
|
||||
|
||||
public void Act(BoxContainer loadoutsContainer, LoadoutPicker loadoutPicker)
|
||||
{
|
||||
loadoutPicker.LoadCategoryButtons(_loadoutCategory);
|
||||
}
|
||||
|
||||
public void Exit(BoxContainer loadoutsContainer, LoadoutPicker loadoutPicker)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LoadoutEntriesContainerMenuEntry : ILoadoutMenuEntry
|
||||
{
|
||||
public ILoadoutMenuEntry? Parent { get; set;}
|
||||
public string Label { get; }
|
||||
|
||||
private readonly List<ILoadoutMenuEntry> _children = [];
|
||||
public IReadOnlyList<ILoadoutMenuEntry> Children => _children;
|
||||
private readonly List<(BaseButton, Action<BaseButton.ButtonEventArgs>)> _currBrns = [];
|
||||
|
||||
public LoadoutEntriesContainerMenuEntry(ProtoId<LoadoutCategoryPrototype> loadoutCategoryProtoId)
|
||||
{
|
||||
Label = Loc.GetString($"loadout-category-{loadoutCategoryProtoId}");
|
||||
}
|
||||
|
||||
public LoadoutEntriesContainerMenuEntry(string label)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public void AddChild(params ILoadoutMenuEntry[] children)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
_children.Add(child);
|
||||
child.Parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void Act(BoxContainer loadoutsContainer, LoadoutPicker loadoutPicker)
|
||||
{
|
||||
foreach (var menuEntry in _children)
|
||||
{
|
||||
var button = new Button()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 15,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = menuEntry.Label,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action<BaseButton.ButtonEventArgs> handler = (_) => loadoutPicker.CurrentEntry = menuEntry;
|
||||
button.OnPressed += handler;
|
||||
|
||||
_currBrns.Add((button, handler));
|
||||
loadoutsContainer.AddChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit(BoxContainer loadoutsContainer, LoadoutPicker loadoutPicker)
|
||||
{
|
||||
foreach (var (button, handler) in _currBrns)
|
||||
{
|
||||
button.OnPressed -= handler;
|
||||
}
|
||||
_currBrns.Clear();
|
||||
}
|
||||
}
|
||||
40
Content.Client/_White/Loadouts/LoadoutEntry.xaml
Normal file
40
Content.Client/_White/Loadouts/LoadoutEntry.xaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<controls:LoadoutEntry
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client._White.Loadouts"
|
||||
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<Control>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<controls1:StyledButtonGroup Name="ButtonGroup" Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Button
|
||||
Name="PreferenceButton"
|
||||
ToggleMode="True"
|
||||
VerticalAlignment="Center" >
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="LoadoutCostText"
|
||||
MinWidth="32"
|
||||
StyleClasses="LabelHeading"
|
||||
MaxWidth="32"
|
||||
ClipText="True"
|
||||
Margin="0,0,8,0"/>
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#2f2f2f" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<SpriteView Name="PreviewLoadout" OverrideDirection="South" VerticalAlignment="Center" Margin="0" Scale="1.2,1.2" Stretch="Fill"/>
|
||||
</PanelContainer>
|
||||
<Label Name="LoadoutNameLabel" Margin="8,0,0,0"/>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
|
||||
<Button Visible="False"
|
||||
Name="HeirloomButton"
|
||||
Text="{Loc 'humanoid-profile-editor-loadouts-heirloom'}"
|
||||
ToolTip="{Loc 'humanoid-profile-editor-loadouts-heirloom-tooltip'}"
|
||||
ToggleMode="True"/>
|
||||
|
||||
<Button Name="HeadingButton" Text="{Loc 'humanoid-profile-editor-loadouts-customize'}"/>
|
||||
</controls1:StyledButtonGroup>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
</controls:LoadoutEntry>
|
||||
202
Content.Client/_White/Loadouts/LoadoutEntry.xaml.cs
Normal file
202
Content.Client/_White/Loadouts/LoadoutEntry.xaml.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Clothing.Loadouts.Prototypes;
|
||||
using Content.Shared.Clothing.Loadouts.Systems;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
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._White.Loadouts;
|
||||
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutEntry : Control, IComparable<LoadoutEntry>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public Action<LoadoutEntry>? OnLoadoutDirty;
|
||||
public Action<LoadoutEntry>? OnEditLoadoutRequired;
|
||||
|
||||
private Loadout? _currentLoadout;
|
||||
private EntityUid _loadoutUid;
|
||||
|
||||
public Loadout Loadout
|
||||
{
|
||||
get => _currentLoadout ?? throw new InvalidOperationException("Loadout not initialized. Call SetLoadout first.");
|
||||
set => SetLoadout(value);
|
||||
}
|
||||
|
||||
private int _cost;
|
||||
|
||||
public int Cost
|
||||
{
|
||||
get => _cost;
|
||||
private set
|
||||
{
|
||||
LoadoutCostText.Text = value.ToString();
|
||||
_cost = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanWear { get; private set; } = true;
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get => PreferenceButton.Pressed;
|
||||
set => PreferenceButton.Pressed = value;
|
||||
}
|
||||
|
||||
public string LoadoutName
|
||||
{
|
||||
get => LoadoutNameLabel.Text ?? string.Empty;
|
||||
private set => LoadoutNameLabel.Text = value;
|
||||
}
|
||||
|
||||
public string LoadoutDescription { get; private set; } = string.Empty;
|
||||
|
||||
private Tooltip _reasonTooltip = new();
|
||||
|
||||
public LoadoutEntry()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
HeadingButton.OnPressed += HeadingButtonPressed;
|
||||
PreferenceButton.OnPressed += PreferenceButtonPressed;
|
||||
TooltipSupplier = OnTooltipSupplierRequired;
|
||||
}
|
||||
|
||||
private Control? OnTooltipSupplierRequired(Control sender) => CanWear ? null : _reasonTooltip;
|
||||
|
||||
private void PreferenceButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
OnLoadoutDirty?.Invoke(this);
|
||||
}
|
||||
|
||||
public bool EnsureIsWearable(CharacterRequirementsArgs args, int loadoutPoint)
|
||||
{
|
||||
if (CheckIsWearable(args, loadoutPoint, out var reason))
|
||||
{
|
||||
if (CanWear)
|
||||
return true;
|
||||
|
||||
PreferenceButton.RemoveStyleClass(StyleBase.ButtonDanger);
|
||||
CanWear = true;
|
||||
PreferenceButton.Disabled = false;
|
||||
HeirloomButton.Visible = true;
|
||||
HeadingButton.Visible = true;
|
||||
PreferenceButton.MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CanWear)
|
||||
return false;
|
||||
|
||||
PreferenceButton.AddStyleClass(StyleBase.ButtonDanger);
|
||||
CanWear = false;
|
||||
PreferenceButton.Disabled = true;
|
||||
HeirloomButton.Visible = false;
|
||||
HeadingButton.Visible = false;
|
||||
PreferenceButton.MouseFilter = MouseFilterMode.Ignore;
|
||||
_reasonTooltip.SetMessage(FormattedMessage.FromMarkupPermissive(reason));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CheckIsWearable(CharacterRequirementsArgs characterRequirementsArgs, int loadoutPoint,[NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
ProtoId<LoadoutPrototype> loadoutPrototype = Loadout.LoadoutName;
|
||||
reason = null;
|
||||
|
||||
if (!_prototypeManager.TryIndex(loadoutPrototype, out var prototype))
|
||||
{
|
||||
reason = Loc.GetString("loadout-error-prototype-not-found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(prototype.Cost > loadoutPoint)
|
||||
{
|
||||
reason = Loc.GetString("loadout-error-too-expensive");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var requirement in prototype.Requirements)
|
||||
{
|
||||
if (!characterRequirementsArgs.IsValid(requirement, prototype, out reason))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HeadingButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
OnEditLoadoutRequired?.Invoke(this);
|
||||
}
|
||||
public void SetLoadout(Loadout loadout)
|
||||
{
|
||||
_entityManager.DeleteEntity(_loadoutUid);
|
||||
var loadoutProto = _prototypeManager.Index<LoadoutPrototype>(loadout.LoadoutName);
|
||||
var firstItem = loadoutProto.Items.FirstOrDefault();
|
||||
if (firstItem == default)
|
||||
throw new InvalidOperationException($"Loadout {loadout.LoadoutName} has no items");
|
||||
|
||||
_loadoutUid = _entityManager.SpawnEntity(firstItem, MapCoordinates.Nullspace);
|
||||
Cost = loadoutProto.Cost;
|
||||
PreviewLoadout.SetEntity(_loadoutUid);
|
||||
|
||||
LoadoutName =
|
||||
Loc.GetString($"loadout-name-{loadoutProto.ID}") == $"loadout-name-{loadoutProto.ID}"
|
||||
? _entityManager.GetComponent<MetaDataComponent>(_loadoutUid).EntityName
|
||||
: Loc.GetString($"loadout-name-{loadoutProto.ID}");
|
||||
|
||||
// Display the item's label if it's present
|
||||
if (_entityManager.TryGetComponent(_loadoutUid, out LabelComponent? labelComponent))
|
||||
{
|
||||
var itemLabel = labelComponent.CurrentLabel;
|
||||
if (!string.IsNullOrEmpty(itemLabel))
|
||||
LoadoutName += $" ({Loc.GetString(itemLabel)})";
|
||||
}
|
||||
|
||||
LoadoutDescription =
|
||||
!Loc.TryGetString($"loadout-description-{loadoutProto.ID}", out var description)
|
||||
? _entityManager.GetComponent<MetaDataComponent>(_loadoutUid).EntityDescription
|
||||
: description;
|
||||
|
||||
_currentLoadout = loadout;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
HeadingButton.OnPressed -= HeadingButtonPressed;
|
||||
PreferenceButton.OnPressed -= PreferenceButtonPressed;
|
||||
_entityManager.DeleteEntity(_loadoutUid);
|
||||
}
|
||||
|
||||
public int CompareTo(LoadoutEntry? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
return 0;
|
||||
if (other is null)
|
||||
return 1;
|
||||
|
||||
var canWearCompare = other.CanWear.CompareTo(CanWear);
|
||||
if(canWearCompare != 0)
|
||||
return canWearCompare;
|
||||
|
||||
return string.Compare(Loadout.LoadoutName, other.Loadout.LoadoutName, StringComparison.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
83
Content.Client/_White/Loadouts/LoadoutPicker.Categories.cs
Normal file
83
Content.Client/_White/Loadouts/LoadoutPicker.Categories.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Clothing.Loadouts.Prototypes;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
||||
namespace Content.Client._White.Loadouts;
|
||||
|
||||
|
||||
public sealed partial class LoadoutPicker
|
||||
{
|
||||
private List<LoadoutCategoryPrototype> _rootCategories = [];
|
||||
|
||||
public IEnumerable<LoadoutCategoryPrototype> GetRootCategories()
|
||||
{
|
||||
return _rootCategories;
|
||||
}
|
||||
|
||||
private ILoadoutMenuEntry? _currentEntry;
|
||||
|
||||
public ILoadoutMenuEntry CurrentEntry
|
||||
{
|
||||
get => _currentEntry ?? throw new InvalidOperationException();
|
||||
set
|
||||
{
|
||||
_currentEntry?.Exit(Loadouts, this);
|
||||
ClearupEdit();
|
||||
ClearLoadoutCategoryButtons();
|
||||
_currentEntry = value;
|
||||
EntryBackButton.Visible = _currentEntry.Parent != null;
|
||||
_currentEntry.Act(Loadouts, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CacheRootCategories()
|
||||
{
|
||||
_rootCategories =
|
||||
_prototypeManager.EnumeratePrototypes<LoadoutCategoryPrototype>().Where(p => p.Root)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void InitializeCategories()
|
||||
{
|
||||
EntryBackButton.OnPressed += EntryBackButtonPressed;
|
||||
var rootEntry = new LoadoutEntriesContainerMenuEntry("root");
|
||||
foreach (var category in GetRootCategories())
|
||||
{
|
||||
rootEntry.AddChild(BuildMenuGroup(category.ID).Item1);
|
||||
}
|
||||
|
||||
CurrentEntry = rootEntry;
|
||||
}
|
||||
|
||||
private void EntryBackButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if(CurrentEntry.Parent != null)
|
||||
CurrentEntry = CurrentEntry.Parent;
|
||||
}
|
||||
|
||||
private (ILoadoutMenuEntry, int) BuildMenuGroup(ProtoId<LoadoutCategoryPrototype> categoryPrototypeId)
|
||||
{
|
||||
var weight = 0;
|
||||
|
||||
if(!_prototypeManager.TryIndex(categoryPrototypeId, out var categoryPrototype))
|
||||
throw new Exception($"Cannot load prototype {categoryPrototypeId}");
|
||||
|
||||
if (categoryPrototype.SubCategories.Count == 0)
|
||||
return (new LoadoutCategoryShowMenuEntry(categoryPrototypeId), 1);
|
||||
|
||||
var entry = new LoadoutEntriesContainerMenuEntry(categoryPrototypeId);
|
||||
|
||||
foreach (var category in categoryPrototype.SubCategories)
|
||||
{
|
||||
var child = BuildMenuGroup(category);
|
||||
if(child.Item2 == 0) continue;
|
||||
entry.AddChild(child.Item1);
|
||||
weight+= child.Item2;
|
||||
}
|
||||
|
||||
return (entry, weight);
|
||||
}
|
||||
}
|
||||
81
Content.Client/_White/Loadouts/LoadoutPicker.xaml
Normal file
81
Content.Client/_White/Loadouts/LoadoutPicker.xaml
Normal file
@@ -0,0 +1,81 @@
|
||||
<controls:LoadoutPicker
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client._White.Loadouts"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<!-- Primary container -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<!-- Loadouts -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Button Name="EntryBackButton" Text="..."/>
|
||||
<Label Name="LoadoutPoints" Text="Дурак!" Margin="8 0" />
|
||||
<LineEdit Name="LoadoutSearch" PlaceHolder="{Loc 'markings-search'}" HorizontalExpand="True" />
|
||||
<Button Name="ResetButton" Text="{Loc 'ui-options-bind-reset'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<ScrollContainer HorizontalExpand="True" MinSize="300 300">
|
||||
<BoxContainer Name="Loadouts" Orientation="Vertical" HorizontalExpand="True" MinSize="300 300" />
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Loadout config -->
|
||||
<PanelContainer HorizontalExpand="True" Name="LoadoutConfigContainer" Visible="False">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#2f2f2f" BorderColor="#2f2f2faf" BorderThickness="1" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="3" HorizontalExpand="True">
|
||||
<BoxContainer Name="SpecialName" Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-loadouts-customize-name'}" />
|
||||
<LineEdit Name="NameEdit" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Name="SpecialDescription" Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-loadouts-customize-description'}" />
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#222222" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<controls1:ResizableControl
|
||||
AllowedResizeDirection="Vertical"
|
||||
HorizontalExpand="True"
|
||||
MinSize="128 64">
|
||||
<TextEdit Name="DescriptionEdit" HorizontalExpand="True" VerticalExpand="True" Margin="3" />
|
||||
</controls1:ResizableControl>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!--WD EDIT START-->
|
||||
<BoxContainer Name="SpecialBookText" Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-loadouts-customize-book-text'}" />
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#222222" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<controls1:ResizableControl
|
||||
AllowedResizeDirection="Vertical"
|
||||
HorizontalExpand="True"
|
||||
MinSize="128 64">
|
||||
<TextEdit Name="BookTextEdit" StyleClasses="PaperLineEdit" HorizontalExpand="True" VerticalExpand="True" Margin="3" />
|
||||
</controls1:ResizableControl>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<!--WD EDIT END-->
|
||||
|
||||
<Button Name="SpecialColorTintToggle" Text="{Loc 'humanoid-profile-editor-loadouts-customize-color'}" ToggleMode="True" Margin="0 3 0 0" StyleClasses="OpenBoth" />
|
||||
<ColorSelectorSliders Name="ColorEdit" Visible="False" Color="#fff" HorizontalExpand="True" />
|
||||
|
||||
<Button Name="SaveButton" Text="{Loc 'humanoid-profile-editor-loadouts-customize-save'}" HorizontalExpand="True" Margin="0 3 0 0" StyleClasses="OpenBoth" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controls:LoadoutPicker>
|
||||
489
Content.Client/_White/Loadouts/LoadoutPicker.xaml.cs
Normal file
489
Content.Client/_White/Loadouts/LoadoutPicker.xaml.cs
Normal file
@@ -0,0 +1,489 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client._White.DatumContainer;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Clothing.Loadouts.Prototypes;
|
||||
using Content.Shared.Clothing.Loadouts.Systems;
|
||||
using Content.Shared.Customization.Systems;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
|
||||
namespace Content.Client._White.Loadouts;
|
||||
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutPicker : Control
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private readonly Dictionary<string, Loadout> _selectedLoadouts = [];
|
||||
private readonly List<LoadoutEntry> _loadoutEntries = new();
|
||||
|
||||
private readonly LocalDatumContainer<string> _customColorTints;
|
||||
private readonly LocalDatumContainer<bool> _customHeirloom;
|
||||
private readonly LocalDatumContainer<string> _customName;
|
||||
private readonly LocalDatumContainer<string> _customDescription;
|
||||
private readonly LocalDatumContainer<string> _customContent;
|
||||
|
||||
private List<Loadout> _checkpointLoadouts = new();
|
||||
private CharacterRequirementsArgs? _checkpointRequirements;
|
||||
|
||||
public Action<List<Loadout>>? OnLoadoutsChanged;
|
||||
|
||||
private int MaxLoadoutPoints => _configManager.GetCVar(CCVars.GameLoadoutsPoints);
|
||||
|
||||
private int _loadoutPoints = 0;
|
||||
|
||||
public CharacterRequirementsArgs CharacterRequirementsArgs = default!;
|
||||
private ProtoId<LoadoutCategoryPrototype>? _selectedLoadoutCategory;
|
||||
private Dictionary<ProtoId<LoadoutCategoryPrototype>, List<LoadoutPrototype>> _loadoutCache = [];
|
||||
|
||||
public int LoadoutPoint
|
||||
{
|
||||
get => _loadoutPoints;
|
||||
|
||||
set
|
||||
{
|
||||
LoadoutPoints.Text = Loc.GetString("marking-points-remaining", ("points", value));
|
||||
_loadoutPoints = value;
|
||||
}
|
||||
}
|
||||
|
||||
public LoadoutPicker()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_customName = new("custom_name");
|
||||
_customDescription = new("custom_description");
|
||||
_customContent = new("custom_content");
|
||||
_customHeirloom = new("custom_heirloom");
|
||||
_customColorTints = new("custom_color_tints");
|
||||
|
||||
SaveButton.OnPressed += SaveButtonPressed;
|
||||
SpecialColorTintToggle.OnPressed += SpecialColorTintTogglePressed;
|
||||
ResetButton.OnPressed += ResetButtonPressed;
|
||||
LoadoutSearch.OnTextChanged += args => Populate(args.Text);
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypesReloaded;
|
||||
|
||||
InitializeCache();
|
||||
InitializeCategories();
|
||||
}
|
||||
|
||||
private void InitializeCache()
|
||||
{
|
||||
CacheRootCategories();
|
||||
_loadoutCache.Clear();
|
||||
foreach (var loadoutPrototype in _prototypeManager.EnumeratePrototypes<LoadoutPrototype>())
|
||||
{
|
||||
if (!_loadoutCache.TryGetValue(loadoutPrototype.Category, out var loadoutList))
|
||||
{
|
||||
loadoutList = new();
|
||||
_loadoutCache.Add(loadoutPrototype.Category, loadoutList);
|
||||
}
|
||||
|
||||
loadoutList.Add(loadoutPrototype);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (obj.WasModified<LoadoutPrototype>())
|
||||
InitializeCache();
|
||||
}
|
||||
|
||||
private void Populate(string _)
|
||||
{
|
||||
if (_selectedLoadoutCategory != null)
|
||||
LoadCategoryButtons(_selectedLoadoutCategory.Value);
|
||||
}
|
||||
|
||||
private void ResetButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if(_checkpointRequirements is null)
|
||||
return;
|
||||
|
||||
SetData(_checkpointLoadouts, _checkpointRequirements);
|
||||
}
|
||||
|
||||
public void SetCheckpoint()
|
||||
{
|
||||
_checkpointLoadouts = _selectedLoadouts.Values.ToList();
|
||||
_checkpointRequirements = CharacterRequirementsArgs;
|
||||
}
|
||||
|
||||
public bool IsCategoryValid(ProtoId<LoadoutCategoryPrototype> category) => _loadoutCache.ContainsKey(category);
|
||||
|
||||
public void SetData(IEnumerable<Loadout> selectedPreferenceList, CharacterRequirementsArgs characterRequirements)
|
||||
{
|
||||
ClearLoadouts();
|
||||
ClearupEdit();
|
||||
LoadoutSearch.Clear();
|
||||
|
||||
CharacterRequirementsArgs = characterRequirements;
|
||||
|
||||
foreach (var preference in selectedPreferenceList)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<LoadoutPrototype>(preference.LoadoutName, out var proto))
|
||||
{
|
||||
Logger.Error($"Cannot add loadout to selected loadouts: prototype {preference.LoadoutName} not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
var loadoutEntry = CreateEntry(preference.LoadoutName);
|
||||
|
||||
if (!TrySelectLoadout(loadoutEntry))
|
||||
{
|
||||
Logger.Warning($"Removing loadout {preference.LoadoutName} from selected list.");
|
||||
continue;
|
||||
}
|
||||
|
||||
LoadoutPoint -= loadoutEntry.Cost;
|
||||
|
||||
if (proto.CustomContent && loadoutEntry.Loadout.CustomContent != preference.CustomContent)
|
||||
{
|
||||
Logger.Warning("CustomContent mismatch, syncing...");
|
||||
loadoutEntry.Loadout.CustomContent = preference.CustomContent;
|
||||
_customContent.SetValue(preference.LoadoutName, preference.CustomContent);
|
||||
}
|
||||
|
||||
if (proto.CustomName && loadoutEntry.Loadout.CustomName != preference.CustomName)
|
||||
{
|
||||
Logger.Warning("CustomName mismatch, syncing...");
|
||||
loadoutEntry.Loadout.CustomName = preference.CustomName;
|
||||
_customName.SetValue(preference.LoadoutName, preference.CustomName);
|
||||
}
|
||||
|
||||
if (proto.CustomDescription && loadoutEntry.Loadout.CustomDescription != preference.CustomDescription)
|
||||
{
|
||||
Logger.Warning("CustomDescription mismatch, syncing...");
|
||||
loadoutEntry.Loadout.CustomDescription = preference.CustomDescription;
|
||||
_customDescription.SetValue(preference.LoadoutName, preference.CustomDescription);
|
||||
}
|
||||
|
||||
if (proto.CanBeHeirloom && loadoutEntry.Loadout.CustomHeirloom != preference.CustomHeirloom)
|
||||
{
|
||||
Logger.Warning("CustomHeirloom mismatch, syncing...");
|
||||
loadoutEntry.Loadout.CustomHeirloom = preference.CustomHeirloom;
|
||||
|
||||
if( preference.CustomHeirloom is null)
|
||||
_customHeirloom.RemoveValue(preference.LoadoutName);
|
||||
else
|
||||
_customHeirloom.SetValue(preference.LoadoutName, preference.CustomHeirloom.Value);
|
||||
}
|
||||
|
||||
if (proto.CustomColorTint && loadoutEntry.Loadout.CustomColorTint != preference.CustomColorTint)
|
||||
{
|
||||
Logger.Warning("CustomColorTint mismatch, syncing...");
|
||||
loadoutEntry.Loadout.CustomColorTint = preference.CustomColorTint;
|
||||
_customColorTints.SetValue(preference.LoadoutName, preference.CustomColorTint);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentEntry = CurrentEntry;
|
||||
}
|
||||
|
||||
public bool LoadCategoryButtons(ProtoId<LoadoutCategoryPrototype> loadoutCategoryPrototype)
|
||||
{
|
||||
ClearLoadoutCategoryButtons();
|
||||
ClearupEdit();
|
||||
|
||||
var loadoutPrototypes = GroupLoadoutsByGroup(loadoutCategoryPrototype).ToList();
|
||||
if (loadoutPrototypes.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var loadoutPrototype in loadoutPrototypes)
|
||||
{
|
||||
var loadoutEntry = new LoadoutEntry();
|
||||
|
||||
if(_selectedLoadouts.TryGetValue(loadoutPrototype.ID, out var loadout))
|
||||
{
|
||||
loadoutEntry.SetLoadout(loadout);
|
||||
loadoutEntry.Selected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newLoadout = new Loadout(
|
||||
loadoutPrototype.ID,
|
||||
loadoutPrototype.CustomName ? _customName.GetValueOrDefault(loadoutPrototype.ID) : null,
|
||||
loadoutPrototype.CustomDescription ? _customDescription.GetValueOrDefault(loadoutPrototype.ID) : null,
|
||||
loadoutPrototype.CustomContent ? _customContent.GetValueOrDefault(loadoutPrototype.ID) : null,
|
||||
loadoutPrototype.CustomColorTint ? _customColorTints.GetValueOrDefault(loadoutPrototype.ID) : null,
|
||||
loadoutPrototype.CanBeHeirloom ? _customHeirloom.GetValueOrDefault(loadoutPrototype.ID) : null
|
||||
);
|
||||
loadoutEntry.SetLoadout(newLoadout);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(LoadoutSearch.Text) && !loadoutEntry.LoadoutName.Contains(LoadoutSearch.Text))
|
||||
continue;
|
||||
|
||||
loadoutEntry.OnEditLoadoutRequired += OnEntryEditLoadoutRequired;
|
||||
loadoutEntry.OnLoadoutDirty += OnEntryLoadoutDirty;
|
||||
loadoutEntry.EnsureIsWearable(CharacterRequirementsArgs, LoadoutPoint);
|
||||
|
||||
_loadoutEntries.Add(loadoutEntry);
|
||||
}
|
||||
|
||||
SortAndPasteEntries();
|
||||
|
||||
_selectedLoadoutCategory = loadoutCategoryPrototype;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Dirty()
|
||||
{
|
||||
OnLoadoutsChanged?.Invoke(_selectedLoadouts.Select(x => x.Value).ToList());
|
||||
}
|
||||
|
||||
private LoadoutEntry CreateEntry(string loadoutName)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<LoadoutPrototype>(loadoutName, out var prototype))
|
||||
{
|
||||
throw new Exception("Could not find a prototype " + loadoutName);
|
||||
}
|
||||
|
||||
var loadout = new Loadout(
|
||||
loadoutName,
|
||||
prototype.CustomName ? _customName.GetValueOrDefault(loadoutName) : null,
|
||||
prototype.CustomDescription ? _customDescription.GetValueOrDefault(loadoutName) : null,
|
||||
prototype.CustomContent ? _customContent.GetValueOrDefault(loadoutName) : null,
|
||||
prototype.CustomColorTint ? _customColorTints.GetValueOrDefault(loadoutName) : null,
|
||||
prototype.CanBeHeirloom ? _customHeirloom.GetValueOrDefault(loadoutName) : null
|
||||
);
|
||||
|
||||
var entry = new LoadoutEntry();
|
||||
entry.SetLoadout(loadout);
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void SpecialColorTintTogglePressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if(_currPrototype == null || !_currPrototype.CustomColorTint)
|
||||
return;
|
||||
|
||||
ColorEdit.Visible = SpecialColorTintToggle.Pressed;
|
||||
}
|
||||
|
||||
private void OnEntryLoadoutDirty(LoadoutEntry entry)
|
||||
{
|
||||
if (TryFreeLoadout(entry.Loadout))
|
||||
{
|
||||
entry.Selected = false;
|
||||
Dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
entry.Selected = TrySelectLoadout(entry);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private bool TrySelectLoadout(LoadoutEntry entry)
|
||||
{
|
||||
if (entry.EnsureIsWearable(CharacterRequirementsArgs, LoadoutPoint))
|
||||
{
|
||||
_selectedLoadouts.Add(entry.Loadout.LoadoutName, entry.Loadout);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryFreeLoadout(Loadout loadout)
|
||||
{
|
||||
if (!_selectedLoadouts.Remove(loadout.LoadoutName, out _))
|
||||
return false;
|
||||
|
||||
if(_prototypeManager.TryIndex<LoadoutPrototype>(loadout.LoadoutName, out var prototype))
|
||||
LoadoutPoint += prototype.Cost;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnEntryEditLoadoutRequired(LoadoutEntry entry)
|
||||
{
|
||||
EditLoadout(entry);
|
||||
}
|
||||
|
||||
private LoadoutPrototype? _currPrototype;
|
||||
private LoadoutEntry? _currEdit;
|
||||
|
||||
public void EditLoadout(LoadoutEntry loadoutEntry)
|
||||
{
|
||||
ClearupEdit();
|
||||
var loadout = loadoutEntry.Loadout;
|
||||
if (!_prototypeManager.TryIndex<LoadoutPrototype>(loadout.LoadoutName, out var loadoutPrototype))
|
||||
{
|
||||
Logger.Error($"Unable to load loadout: unknown prototype {loadout.LoadoutName}");
|
||||
return;
|
||||
}
|
||||
|
||||
LoadoutConfigContainer.Visible = true;
|
||||
|
||||
SpecialName.Visible = loadoutPrototype.CustomName;
|
||||
SpecialDescription.Visible = loadoutPrototype.CustomDescription;
|
||||
SpecialBookText.Visible = loadoutPrototype.CustomContent;
|
||||
SpecialColorTintToggle.Visible = loadoutPrototype.CustomColorTint;
|
||||
|
||||
if (loadoutPrototype.CustomName)
|
||||
NameEdit.Text = loadout.CustomName ?? "";
|
||||
|
||||
if (loadoutPrototype.CustomDescription)
|
||||
DescriptionEdit.TextRope = new Rope.Leaf(loadout.CustomDescription ?? "");
|
||||
|
||||
if(loadoutPrototype.CustomContent)
|
||||
BookTextEdit.TextRope = new Rope.Leaf(loadout.CustomContent ?? "");
|
||||
|
||||
if (loadoutPrototype.CustomColorTint )
|
||||
{
|
||||
if(loadout.CustomColorTint is not null)
|
||||
{
|
||||
SpecialColorTintToggle.Pressed = true;
|
||||
ColorEdit.Color = Color.FromHex(loadout.CustomColorTint);
|
||||
ColorEdit.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SpecialColorTintToggle.Pressed = false;
|
||||
ColorEdit.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
_currEdit = loadoutEntry;
|
||||
_currPrototype = loadoutPrototype;
|
||||
}
|
||||
|
||||
private void ClearLoadoutCategoryButtons()
|
||||
{
|
||||
foreach (var entry in _loadoutEntries)
|
||||
{
|
||||
entry.OnEditLoadoutRequired -= OnEntryEditLoadoutRequired;
|
||||
entry.OnLoadoutDirty -= OnEntryLoadoutDirty;
|
||||
}
|
||||
|
||||
_loadoutEntries.Clear();
|
||||
Loadouts.Children.Clear();
|
||||
}
|
||||
|
||||
private void SortAndPasteEntries()
|
||||
{
|
||||
Loadouts.Children.Clear();
|
||||
_loadoutEntries.Sort();
|
||||
foreach (var entry in _loadoutEntries)
|
||||
{
|
||||
Loadouts.Children.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if(_currEdit == null)
|
||||
return;
|
||||
|
||||
var oldValue = _currEdit.Loadout;
|
||||
oldValue.CustomName = TextOrNull(NameEdit.Text);
|
||||
oldValue.CustomContent = TextOrNull(BookTextEdit.TextRope);
|
||||
oldValue.CustomDescription = TextOrNull(DescriptionEdit.TextRope);
|
||||
oldValue.CustomColorTint = _currPrototype?.CustomColorTint == true && SpecialColorTintToggle.Pressed
|
||||
? ColorEdit.Color.ToHex()
|
||||
: null;
|
||||
|
||||
if(oldValue.CustomName != null) _customName.SetValue(oldValue.LoadoutName, oldValue.CustomName);
|
||||
else _customName.RemoveValue(oldValue.LoadoutName);
|
||||
if(oldValue.CustomContent != null) _customContent.SetValue(oldValue.LoadoutName, oldValue.CustomContent);
|
||||
else _customContent.RemoveValue(oldValue.LoadoutName);
|
||||
if(oldValue.CustomDescription != null) _customDescription.SetValue(oldValue.LoadoutName, oldValue.CustomDescription);
|
||||
else _customDescription.RemoveValue(oldValue.LoadoutName);
|
||||
if(oldValue.CustomColorTint != null) _customColorTints.SetValue(oldValue.LoadoutName, oldValue.CustomColorTint);
|
||||
else _customColorTints.RemoveValue(oldValue.LoadoutName);
|
||||
|
||||
ClearupEdit();
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void ClearupEdit()
|
||||
{
|
||||
LoadoutConfigContainer.Visible = false;
|
||||
_currEdit = null;
|
||||
_currPrototype = null;
|
||||
|
||||
NameEdit.Text = "";
|
||||
DescriptionEdit.TextRope = Rope.Leaf.Empty;
|
||||
BookTextEdit.TextRope = Rope.Leaf.Empty;
|
||||
SpecialColorTintToggle.Pressed = false;
|
||||
ColorEdit.Visible = false;
|
||||
}
|
||||
|
||||
private string? TextOrNull(Rope.Node node)
|
||||
{
|
||||
var text = Rope.Collapse(node);
|
||||
return string.IsNullOrEmpty(text) ? null : text;
|
||||
}
|
||||
|
||||
private string? TextOrNull(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return null;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private void ClearLoadouts()
|
||||
{
|
||||
ClearLoadoutCategoryButtons();
|
||||
_selectedLoadouts.Clear();
|
||||
LoadoutPoint = MaxLoadoutPoints;
|
||||
}
|
||||
|
||||
private IEnumerable<LoadoutPrototype> GroupLoadoutsByGroup(ProtoId<LoadoutCategoryPrototype> loadoutCategoryPrototype)
|
||||
{
|
||||
if (_loadoutCache.TryGetValue(loadoutCategoryPrototype, out var loadouts))
|
||||
return loadouts;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CharacterRequirementsArgs(
|
||||
JobPrototype job,
|
||||
HumanoidCharacterProfile profile,
|
||||
IReadOnlyDictionary<string, TimeSpan> playTimes,
|
||||
bool whitelisted,
|
||||
int depth = 0,
|
||||
MindComponent? mind = null,
|
||||
IDependencyCollection? dependencies = null)
|
||||
{
|
||||
public JobPrototype Job { get; set; } = job;
|
||||
public HumanoidCharacterProfile Profile { get; set; } = profile;
|
||||
public IReadOnlyDictionary<string, TimeSpan> PlayTimes { get; set; } = playTimes;
|
||||
public bool Whitelisted { get; set; } = whitelisted;
|
||||
public int Depth { get; set; } = depth;
|
||||
public MindComponent? Mind { get; set; } = mind;
|
||||
public IDependencyCollection? Dependencies { get; set; } = dependencies;
|
||||
|
||||
public bool IsValid(CharacterRequirement requirement, IPrototype prototype,[NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
Dependencies ??= IoCManager.Instance!;
|
||||
var valid = requirement.IsValid(
|
||||
Job,
|
||||
Profile,
|
||||
PlayTimes,
|
||||
Whitelisted,
|
||||
prototype,
|
||||
Dependencies.Resolve<IEntityManager>(),
|
||||
Dependencies.Resolve<IPrototypeManager>(),
|
||||
Dependencies.Resolve<IConfigurationManager>(),
|
||||
out reason,
|
||||
Depth,
|
||||
Mind);
|
||||
return requirement.Inverted ? !valid : valid;
|
||||
}
|
||||
}
|
||||
345
Content.Client/_White/UserInterface/Controls/FlexBox.cs
Normal file
345
Content.Client/_White/UserInterface/Controls/FlexBox.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client._White.UserInterface.Controls;
|
||||
|
||||
[Virtual]
|
||||
public class FlexBox : Container
|
||||
{
|
||||
public enum FlexAlignContent
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Stretch,
|
||||
SpaceBetween,
|
||||
SpaceAround
|
||||
}
|
||||
|
||||
public enum FlexAlignItems
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Stretch
|
||||
}
|
||||
|
||||
public enum FlexDirection
|
||||
{
|
||||
Row,
|
||||
RowReverse,
|
||||
Column,
|
||||
ColumnReverse
|
||||
}
|
||||
|
||||
public enum FlexJustifyContent
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
}
|
||||
|
||||
public enum FlexWrap
|
||||
{
|
||||
NoWrap,
|
||||
Wrap,
|
||||
WrapReverse
|
||||
}
|
||||
|
||||
public const string StylePropertyGap = "gap";
|
||||
public const string StylePropertyRowGap = "row-gap";
|
||||
public const string StylePropertyColumnGap = "column-gap";
|
||||
public const string StylePropertyAlignItems = "align-items";
|
||||
public const string StylePropertyOrder = "order";
|
||||
|
||||
private const float DefaultGap = 0f;
|
||||
|
||||
public FlexDirection Direction { get; set; } = FlexDirection.Row;
|
||||
public FlexWrap Wrap { get; set; } = FlexWrap.Wrap;
|
||||
public FlexAlignItems AlignItems { get; set; } = FlexAlignItems.Stretch;
|
||||
public FlexJustifyContent JustifyContent { get; set; } = FlexJustifyContent.FlexStart;
|
||||
public FlexAlignContent AlignContent { get; set; } = FlexAlignContent.FlexStart;
|
||||
|
||||
public float? GapOverride { get; set; }
|
||||
public float? RowGapOverride { get; set; }
|
||||
public float? ColumnGapOverride { get; set; }
|
||||
|
||||
private float ActualGap => GetStyleFloat(StylePropertyGap, GapOverride, DefaultGap);
|
||||
private float ActualRowGap => GetStyleFloat(StylePropertyRowGap, RowGapOverride, ActualGap);
|
||||
private float ActualColumnGap => GetStyleFloat(StylePropertyColumnGap, ColumnGapOverride, ActualGap);
|
||||
|
||||
private float GetStyleFloat(string property, float? overrideValue, float defaultValue)
|
||||
{
|
||||
if (overrideValue.HasValue)
|
||||
return overrideValue.Value;
|
||||
|
||||
if (TryGetStyleProperty(property, out float value))
|
||||
return value;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var children = new List<Control>(Children.Count());
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child.Visible)
|
||||
children.Add(child);
|
||||
}
|
||||
|
||||
if (children.Count == 0)
|
||||
return Vector2.Zero;
|
||||
|
||||
foreach (var child in children)
|
||||
child.Measure(Vector2.PositiveInfinity);
|
||||
|
||||
var lines = BuildLines(children, availableSize);
|
||||
|
||||
var isRow = IsRow;
|
||||
var main = lines.Max(l => l.MainSize);
|
||||
var cross = lines.Sum(l => l.FinalCrossSize) +
|
||||
Math.Max(0, lines.Count - 1) * CrossGap;
|
||||
|
||||
return isRow
|
||||
? new Vector2(main, cross)
|
||||
: new Vector2(cross, main);
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var children = Children.Where(c => c.Visible).ToList();
|
||||
if (children.Count == 0)
|
||||
return finalSize;
|
||||
|
||||
var lines = BuildLines(children, finalSize);
|
||||
|
||||
PositionLines(lines, finalSize);
|
||||
|
||||
foreach (var line in lines)
|
||||
PositionItems(line, finalSize);
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private bool IsRow =>
|
||||
Direction == FlexDirection.Row ||
|
||||
Direction == FlexDirection.RowReverse;
|
||||
|
||||
private bool IsReverse =>
|
||||
Direction == FlexDirection.RowReverse ||
|
||||
Direction == FlexDirection.ColumnReverse;
|
||||
|
||||
private float MainGap => IsRow ? ActualColumnGap : ActualRowGap;
|
||||
private float CrossGap => IsRow ? ActualRowGap : ActualColumnGap;
|
||||
|
||||
private List<FlexLine> BuildLines(
|
||||
List<Control> children,
|
||||
Vector2 size)
|
||||
{
|
||||
children.Sort(static (a, b) =>
|
||||
{
|
||||
var ao = a.TryGetStyleProperty(StylePropertyOrder, out int av) ? av : 0;
|
||||
var bo = b.TryGetStyleProperty(StylePropertyOrder, out int bv) ? bv : 0;
|
||||
return ao.CompareTo(bo);
|
||||
});
|
||||
|
||||
var lines = new List<FlexLine>();
|
||||
var line = new FlexLine();
|
||||
|
||||
var maxMain = IsRow ? size.X : size.Y;
|
||||
var wrap = Wrap != FlexWrap.NoWrap && !float.IsInfinity(maxMain);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var main = IsRow ? child.DesiredSize.X : child.DesiredSize.Y;
|
||||
var cross = IsRow ? child.DesiredSize.Y : child.DesiredSize.X;
|
||||
|
||||
var projected =
|
||||
line.Items.Count == 0
|
||||
? main
|
||||
: line.MainSize + MainGap + main;
|
||||
|
||||
if (wrap && projected > maxMain && line.Items.Count > 0)
|
||||
{
|
||||
lines.Add(line);
|
||||
line = new FlexLine();
|
||||
}
|
||||
|
||||
line.Add(child, main, cross, MainGap);
|
||||
}
|
||||
|
||||
if (line.Items.Count > 0)
|
||||
lines.Add(line);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private void PositionLines(List<FlexLine> lines, Vector2 size)
|
||||
{
|
||||
var crossSize = IsRow ? size.Y : size.X;
|
||||
var totalCross =
|
||||
lines.Sum(l => l.FinalCrossSize) +
|
||||
Math.Max(0, lines.Count - 1) * CrossGap;
|
||||
|
||||
var free = Math.Max(0, crossSize - totalCross);
|
||||
|
||||
float offset = 0;
|
||||
float extraGap = 0;
|
||||
|
||||
foreach (var l in lines)
|
||||
l.ExtraCrossSize = 0;
|
||||
|
||||
switch (AlignContent)
|
||||
{
|
||||
case FlexAlignContent.FlexEnd:
|
||||
offset = free;
|
||||
break;
|
||||
case FlexAlignContent.Center:
|
||||
offset = free / 2;
|
||||
break;
|
||||
case FlexAlignContent.SpaceBetween:
|
||||
if (lines.Count > 1)
|
||||
extraGap = free / (lines.Count - 1);
|
||||
break;
|
||||
case FlexAlignContent.SpaceAround:
|
||||
extraGap = free / lines.Count;
|
||||
offset = extraGap / 2;
|
||||
break;
|
||||
case FlexAlignContent.Stretch:
|
||||
var add = free / lines.Count;
|
||||
foreach (var l in lines)
|
||||
l.ExtraCrossSize = add;
|
||||
break;
|
||||
}
|
||||
|
||||
var pos = offset;
|
||||
|
||||
var wrapReverse = Wrap == FlexWrap.WrapReverse;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
line.CrossPos = wrapReverse
|
||||
? crossSize - pos - line.FinalCrossSize
|
||||
: pos;
|
||||
|
||||
pos += line.FinalCrossSize + CrossGap + extraGap;
|
||||
}
|
||||
}
|
||||
|
||||
private void PositionItems(FlexLine line, Vector2 size)
|
||||
{
|
||||
var mainSize = IsRow ? size.X : size.Y;
|
||||
var free = Math.Max(0, mainSize - line.MainSize);
|
||||
|
||||
float offset = 0;
|
||||
float extraGap = 0;
|
||||
|
||||
switch (JustifyContent)
|
||||
{
|
||||
case FlexJustifyContent.FlexEnd:
|
||||
offset = free;
|
||||
break;
|
||||
case FlexJustifyContent.Center:
|
||||
offset = free / 2;
|
||||
break;
|
||||
case FlexJustifyContent.SpaceBetween:
|
||||
if (line.Items.Count > 1)
|
||||
extraGap = free / (line.Items.Count - 1);
|
||||
break;
|
||||
case FlexJustifyContent.SpaceAround:
|
||||
extraGap = free / line.Items.Count;
|
||||
offset = extraGap / 2;
|
||||
break;
|
||||
case FlexJustifyContent.SpaceEvenly:
|
||||
extraGap = free / (line.Items.Count + 1);
|
||||
offset = extraGap;
|
||||
break;
|
||||
}
|
||||
|
||||
var pos = offset;
|
||||
|
||||
if (IsReverse)
|
||||
{
|
||||
for (var i = line.Items.Count - 1; i >= 0; i--)
|
||||
PositionItem(line.Items[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < line.Items.Count; i++)
|
||||
PositionItem(line.Items[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void PositionItem(FlexItem item)
|
||||
{
|
||||
var align = GetEffectiveAlignItems(item.Control);
|
||||
var crossSize = align == FlexAlignItems.Stretch
|
||||
? line.FinalCrossSize
|
||||
: item.CrossSize;
|
||||
|
||||
var crossPos = align switch
|
||||
{
|
||||
FlexAlignItems.Center => line.CrossPos + (line.FinalCrossSize - crossSize) / 2,
|
||||
FlexAlignItems.FlexEnd => line.CrossPos + line.FinalCrossSize - crossSize,
|
||||
_ => line.CrossPos
|
||||
};
|
||||
|
||||
var rect = IsRow
|
||||
? new UIBox2(pos, crossPos, pos + item.MainSize, crossPos + crossSize)
|
||||
: new UIBox2(crossPos, pos, crossPos + crossSize, pos + item.MainSize);
|
||||
|
||||
item.Control.Arrange(rect);
|
||||
|
||||
pos += item.MainSize + MainGap + extraGap;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetFlexOrder(Control c) =>
|
||||
c.TryGetStyleProperty(StylePropertyOrder, out int o) ? o : 0;
|
||||
|
||||
private FlexAlignItems GetEffectiveAlignItems(Control c) =>
|
||||
c.TryGetStyleProperty(StylePropertyAlignItems, out FlexAlignItems a)
|
||||
? a
|
||||
: AlignItems;
|
||||
|
||||
private sealed class FlexLine
|
||||
{
|
||||
public readonly List<FlexItem> Items = new();
|
||||
public float MainSize;
|
||||
public float CrossSize;
|
||||
public float ExtraCrossSize;
|
||||
public float CrossPos;
|
||||
public float FinalCrossSize => CrossSize + ExtraCrossSize;
|
||||
|
||||
public void Add(Control c, float main, float cross, float gap)
|
||||
{
|
||||
if (Items.Count > 0)
|
||||
MainSize += gap;
|
||||
|
||||
Items.Add(new FlexItem(c, main, cross));
|
||||
MainSize += main;
|
||||
CrossSize = Math.Max(CrossSize, cross);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FlexItem
|
||||
{
|
||||
public readonly Control Control;
|
||||
public readonly float MainSize;
|
||||
public readonly float CrossSize;
|
||||
|
||||
public FlexItem(Control c, float main, float cross)
|
||||
{
|
||||
Control = c;
|
||||
MainSize = main;
|
||||
CrossSize = cross;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ public static partial class PoolManager
|
||||
typeof(PoolManager).Assembly,
|
||||
|
||||
typeof(Content.StyleSheetify.Client.EntryPoint).Assembly, //WWDP EDIT
|
||||
typeof(Content.StyleSheetify.Shared.StylePrototypeIgnorance).Assembly, //WWDP EDIT
|
||||
typeof(Content.StyleSheetify.Shared.Dynamic.DynamicValue).Assembly, //WWDP EDIT
|
||||
}
|
||||
};
|
||||
|
||||
@@ -440,7 +440,7 @@ we are just going to end this here to save a lot of time. This is the exception
|
||||
typeof(PoolManager).Assembly,
|
||||
|
||||
typeof(Content.StyleSheetify.Server.EntryPoint).Assembly, //WWDP EDIT
|
||||
typeof(Content.StyleSheetify.Shared.StylePrototypeIgnorance).Assembly, //WWDP EDIT
|
||||
typeof(Content.StyleSheetify.Shared.Dynamic.DynamicValue).Assembly, //WWDP EDIT
|
||||
];
|
||||
_contentAssemblies.UnionWith(extraAssemblies);
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ public sealed class SandboxTest
|
||||
typeof(Shared.Entry.EntryPoint).Assembly,
|
||||
typeof(Client.Entry.EntryPoint).Assembly,
|
||||
|
||||
typeof(StyleSheetify.Client.EntryPoint).Assembly,
|
||||
typeof(StyleSheetify.Shared.StylePrototypeIgnorance).Assembly,
|
||||
typeof(StyleSheetify.Client.EntryPoint).Assembly, // WWDP EDIT
|
||||
typeof(StyleSheetify.Shared.Dynamic.DynamicValue).Assembly, // WWDP EDIT
|
||||
},
|
||||
Options = new GameControllerOptions { LoadConfigAndUserData = false }
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Content.Server.Database
|
||||
.HasIndex(p => new { HumanoidProfileId = p.ProfileId, p.TraitName })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<Loadout>()
|
||||
modelBuilder.Entity<LoadoutItem>()
|
||||
.HasIndex(p => new { HumanoidProfileId = p.ProfileId, p.LoadoutName })
|
||||
.IsUnique();
|
||||
|
||||
@@ -427,7 +427,7 @@ namespace Content.Server.Database
|
||||
public List<Job> Jobs { get; } = new();
|
||||
public List<Antag> Antags { get; } = new();
|
||||
public List<Trait> Traits { get; } = new();
|
||||
public List<Loadout> Loadouts { get; } = new();
|
||||
public List<LoadoutItem> Loadouts { get; } = new();
|
||||
|
||||
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
|
||||
|
||||
@@ -472,21 +472,36 @@ namespace Content.Server.Database
|
||||
public string TraitName { get; set; } = null!;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public partial class Loadout : Shared.Clothing.Loadouts.Systems.Loadout
|
||||
[Table("loadout")]
|
||||
public class LoadoutItem
|
||||
{
|
||||
public string LoadoutName { get; set; } = null!;
|
||||
public string? CustomName { get; set; }
|
||||
public string? CustomDescription { get; set; }
|
||||
public string? CustomContent { get; set; } // WD EDIT
|
||||
public string? CustomColorTint { get; set; }
|
||||
public bool? CustomHeirloom { get; set; }
|
||||
|
||||
public int Id { get; set; }
|
||||
public Profile Profile { get; set; } = null!;
|
||||
public int ProfileId { get; set; }
|
||||
|
||||
public Loadout(
|
||||
public LoadoutItem(
|
||||
string loadoutName,
|
||||
string? customName = null,
|
||||
string? customDescription = null,
|
||||
string? customContent = null, // WD EDIT
|
||||
string? customColorTint = null,
|
||||
bool? customHeirloom = null
|
||||
) : base(loadoutName, customName, customDescription, customContent, customColorTint, customHeirloom) { } // WD EDIT
|
||||
)
|
||||
{
|
||||
LoadoutName = loadoutName;
|
||||
CustomName = customName;
|
||||
CustomDescription = customDescription;
|
||||
CustomContent = customContent;
|
||||
CustomColorTint = customColorTint;
|
||||
CustomHeirloom = customHeirloom;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DbPreferenceUnavailableMode
|
||||
|
||||
@@ -201,7 +201,17 @@ namespace Content.Server.Database
|
||||
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => new ProtoId<AntagPrototype>(a.AntagName));
|
||||
var traits = profile.Traits.Select(t => new ProtoId<TraitPrototype>(t.TraitName));
|
||||
var loadouts = profile.Loadouts.Select(Shared.Clothing.Loadouts.Systems.Loadout (l) => l);
|
||||
// WWDP EDIT START
|
||||
var loadouts = profile.Loadouts.Select(l =>
|
||||
new Loadout(
|
||||
l.LoadoutName,
|
||||
l.CustomName,
|
||||
l.CustomDescription,
|
||||
l.CustomContent,
|
||||
l.CustomColorTint,
|
||||
l.CustomHeirloom
|
||||
));
|
||||
// WWDP EDIT END
|
||||
|
||||
var sex = Sex.Male;
|
||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||
@@ -283,15 +293,7 @@ namespace Content.Server.Database
|
||||
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
|
||||
antags.ToHashSet(),
|
||||
traits.ToHashSet(),
|
||||
loadouts.Select(l => new LoadoutPreference(l.LoadoutName)
|
||||
{
|
||||
CustomName = l.CustomName,
|
||||
CustomDescription = l.CustomDescription,
|
||||
CustomContent = l.CustomContent, // WD EDIT
|
||||
CustomColorTint = l.CustomColorTint,
|
||||
CustomHeirloom = l.CustomHeirloom,
|
||||
Selected = true,
|
||||
}).ToHashSet()
|
||||
loadouts.ToDictionary(p => p.LoadoutName) // WWDP EDIT
|
||||
);
|
||||
}
|
||||
|
||||
@@ -357,8 +359,8 @@ namespace Content.Server.Database
|
||||
);
|
||||
|
||||
profile.Loadouts.Clear();
|
||||
profile.Loadouts.AddRange(humanoid.LoadoutPreferences
|
||||
.Select(l => new Loadout(l.LoadoutName, l.CustomName, l.CustomDescription, l.CustomContent, l.CustomColorTint, l.CustomHeirloom))); // WD EDIT
|
||||
profile.Loadouts.AddRange(humanoid.LoadoutPreferencesList
|
||||
.Select(l => new LoadoutItem(l.LoadoutName, l.CustomName, l.CustomDescription, l.CustomContent, l.CustomColorTint, l.CustomHeirloom))); // WD EDIT
|
||||
|
||||
// WWDP EDIT START
|
||||
profile.BarkPause = humanoid.BarkSettings.Pause;
|
||||
|
||||
@@ -54,13 +54,13 @@ public sealed class SharedLoadoutSystem : EntitySystem
|
||||
_station.EquipStartingGear(uid, proto);
|
||||
}
|
||||
|
||||
public (List<EntityUid>, List<(EntityUid, LoadoutPreference, int)>) ApplyCharacterLoadout(
|
||||
public (List<EntityUid>, List<(EntityUid, Loadout, int)>) ApplyCharacterLoadout(
|
||||
EntityUid uid,
|
||||
ProtoId<JobPrototype> job,
|
||||
HumanoidCharacterProfile profile,
|
||||
Dictionary<string, TimeSpan> playTimes,
|
||||
bool whitelisted,
|
||||
out List<(EntityUid, LoadoutPreference)> heirlooms)
|
||||
out List<(EntityUid, Loadout)> heirlooms)
|
||||
{
|
||||
var jobPrototype = _prototype.Index(job);
|
||||
return ApplyCharacterLoadout(uid, jobPrototype, profile, playTimes, whitelisted, out heirlooms);
|
||||
@@ -76,21 +76,21 @@ public sealed class SharedLoadoutSystem : EntitySystem
|
||||
/// <param name="whitelisted">If the player is whitelisted</param>
|
||||
/// <param name="heirlooms">Every entity the player selected as a potential heirloom</param>
|
||||
/// <returns>A list of loadout items that couldn't be equipped but passed checks</returns>
|
||||
public (List<EntityUid>, List<(EntityUid, LoadoutPreference, int)>) ApplyCharacterLoadout(
|
||||
public (List<EntityUid>, List<(EntityUid, Loadout, int)>) ApplyCharacterLoadout(
|
||||
EntityUid uid,
|
||||
JobPrototype job,
|
||||
HumanoidCharacterProfile profile,
|
||||
Dictionary<string, TimeSpan> playTimes,
|
||||
bool whitelisted,
|
||||
out List<(EntityUid, LoadoutPreference)> heirlooms)
|
||||
out List<(EntityUid, Loadout)> heirlooms)
|
||||
{
|
||||
var failedLoadouts = new List<EntityUid>();
|
||||
var allLoadouts = new List<(EntityUid, LoadoutPreference, int)>();
|
||||
var allLoadouts = new List<(EntityUid, Loadout, int)>();
|
||||
heirlooms = new();
|
||||
if (!job.SpawnLoadout)
|
||||
return (failedLoadouts, allLoadouts);
|
||||
|
||||
foreach (var loadout in profile.LoadoutPreferences)
|
||||
foreach (var loadout in profile.LoadoutPreferencesList) // WWDP EDIT
|
||||
{
|
||||
var slot = "";
|
||||
|
||||
@@ -192,7 +192,7 @@ public sealed class SharedLoadoutSystem : EntitySystem
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable, ImplicitDataDefinitionForInheritors]
|
||||
public abstract partial class Loadout
|
||||
public sealed partial class Loadout
|
||||
{
|
||||
[DataField] public string LoadoutName { get; set; }
|
||||
[DataField] public string? CustomName { get; set; }
|
||||
@@ -201,7 +201,7 @@ public abstract partial class Loadout
|
||||
[DataField] public string? CustomColorTint { get; set; }
|
||||
[DataField] public bool? CustomHeirloom { get; set; }
|
||||
|
||||
protected Loadout(
|
||||
public Loadout(
|
||||
string loadoutName,
|
||||
string? customName = null,
|
||||
string? customDescription = null,
|
||||
@@ -219,21 +219,6 @@ public abstract partial class Loadout
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class LoadoutPreference : Loadout
|
||||
{
|
||||
[DataField] public bool Selected;
|
||||
|
||||
public LoadoutPreference(
|
||||
string loadoutName,
|
||||
string? customName = null,
|
||||
string? customDescription = null,
|
||||
string? customContent = null, // WD EDIT
|
||||
string? customColorTint = null,
|
||||
bool? customHeirloom = null
|
||||
) : base(loadoutName, customName, customDescription, customContent, customColorTint, customHeirloom) { } // WD EDIT
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised both directed and broadcast when a player has been spawned and then given a Loadout.
|
||||
/// Most useful to delay actions that should happen on spawn for when the loadouts have been handled.
|
||||
|
||||
@@ -373,7 +373,7 @@ public sealed partial class CharacterLoadoutRequirement : CharacterRequirement
|
||||
("loadouts", $"[color={color}]{string.Join($"[/color], [color={color}]",
|
||||
Loadouts.Select(l => Loc.GetString($"loadout-name-{l}")))}[/color]"));
|
||||
|
||||
return Loadouts.Any(l => profile.LoadoutPreferences.Select(l => l.LoadoutName).Contains(l.ToString()));
|
||||
return Loadouts.Any(l => profile.LoadoutPreferences.ContainsKey(l.ToString())); // WWDP EDIT
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,10 +53,11 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
private HashSet<ProtoId<TraitPrototype>> _traitPreferences = new();
|
||||
|
||||
/// <see cref="_loadoutPreferences"/>
|
||||
public HashSet<LoadoutPreference> LoadoutPreferences => _loadoutPreferences;
|
||||
public Dictionary<string, Loadout> LoadoutPreferences => _loadoutPreferences; // WWDP EDIT
|
||||
public IEnumerable<Loadout> LoadoutPreferencesList => _loadoutPreferences.Values; // WWDP EDIT
|
||||
|
||||
[DataField]
|
||||
private HashSet<LoadoutPreference> _loadoutPreferences = new();
|
||||
private Dictionary<string, Loadout> _loadoutPreferences = new(); // WWDP EDIT
|
||||
|
||||
[DataField]
|
||||
public string Name { get; set; } = "John Doe";
|
||||
@@ -184,7 +185,7 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
PreferenceUnavailableMode preferenceUnavailable,
|
||||
HashSet<ProtoId<AntagPrototype>> antagPreferences,
|
||||
HashSet<ProtoId<TraitPrototype>> traitPreferences,
|
||||
HashSet<LoadoutPreference> loadoutPreferences)
|
||||
Dictionary<string, Loadout> loadoutPreferences) // WWDP EDIT
|
||||
{
|
||||
Name = name;
|
||||
FlavorText = flavortext;
|
||||
@@ -264,7 +265,7 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
other.PreferenceUnavailable,
|
||||
new HashSet<ProtoId<AntagPrototype>>(other.AntagPreferences),
|
||||
new HashSet<ProtoId<TraitPrototype>>(other.TraitPreferences),
|
||||
new HashSet<LoadoutPreference>(other.LoadoutPreferences))
|
||||
new Dictionary<string, Loadout>(other.LoadoutPreferences)) // WWDP EDIT
|
||||
{
|
||||
}
|
||||
|
||||
@@ -495,33 +496,14 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
return new(this) { _traitPreferences = list };
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile WithLoadoutPreference(
|
||||
string loadoutId,
|
||||
bool pref,
|
||||
string? customName = null,
|
||||
string? customDescription = null,
|
||||
string? customContent = null, // WD EDIT
|
||||
string? customColor = null,
|
||||
bool? customHeirloom = null)
|
||||
// WWDP EDIT START
|
||||
// I'll rip the hands off whoever coded this piece of shit named Loadouts
|
||||
public HumanoidCharacterProfile WithLoadoutPreference(List<Loadout> loadouts)
|
||||
{
|
||||
// WD EDIT START
|
||||
if (customContent is { Length: > MaxCustomContentLength, })
|
||||
{
|
||||
var truncated = customContent.AsSpan(0, MaxCustomContentLength);
|
||||
while (truncated.Length > 0 && char.IsLowSurrogate(truncated[^1]))
|
||||
truncated = truncated[..^1];
|
||||
customContent = truncated.ToString();
|
||||
}
|
||||
// WD EDIT END
|
||||
|
||||
var list = new HashSet<LoadoutPreference>(_loadoutPreferences);
|
||||
|
||||
list.RemoveWhere(l => l.LoadoutName == loadoutId);
|
||||
if (pref)
|
||||
list.Add(new(loadoutId, customName, customDescription, customContent, customColor, customHeirloom) { Selected = pref }); // WD EDIT
|
||||
|
||||
return new HumanoidCharacterProfile(this) { _loadoutPreferences = list };
|
||||
var dictionary = loadouts.ToDictionary(p => p.LoadoutName);
|
||||
return new(this) { _loadoutPreferences = dictionary };
|
||||
}
|
||||
// WWDP EDIT END
|
||||
|
||||
public string Summary =>
|
||||
Loc.GetString(
|
||||
@@ -694,8 +676,7 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
.ToList();
|
||||
|
||||
var loadouts = LoadoutPreferences
|
||||
.Where(l => prototypeManager.HasIndex<LoadoutPrototype>(l.LoadoutName))
|
||||
.Distinct()
|
||||
.Where(l => prototypeManager.HasIndex<LoadoutPrototype>(l.Key))
|
||||
.ToList();
|
||||
|
||||
Name = name;
|
||||
@@ -724,7 +705,6 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
_traitPreferences.UnionWith(traits);
|
||||
|
||||
_loadoutPreferences.Clear();
|
||||
_loadoutPreferences.UnionWith(loadouts);
|
||||
|
||||
// WD EDIT START
|
||||
prototypeManager.TryIndex<TTSVoicePrototype>(Voice, out var voice);
|
||||
@@ -734,25 +714,26 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile
|
||||
if(!CanHaveBark(prototypeManager, collection))
|
||||
BarkVoice = SharedHumanoidAppearanceSystem.DefaultBarkVoice;
|
||||
|
||||
for (var i = 0; i < loadouts.Count; i++)
|
||||
foreach (var (key, loadout) in loadouts)
|
||||
{
|
||||
var loadout = loadouts[i];
|
||||
if (loadout.CustomContent is not { Length: > MaxCustomContentLength, })
|
||||
continue;
|
||||
var truncated = loadout.CustomContent.AsSpan(0, MaxCustomContentLength);
|
||||
while (truncated.Length > 0 && char.IsLowSurrogate(truncated[^1]))
|
||||
truncated = truncated[..^1];
|
||||
if (loadout.CustomContent is not { Length: > MaxCustomContentLength, })
|
||||
{
|
||||
_loadoutPreferences[key] = loadout;
|
||||
continue;
|
||||
}
|
||||
var truncated = loadout.CustomContent.AsSpan(0, MaxCustomContentLength);
|
||||
while (truncated.Length > 0 && char.IsLowSurrogate(truncated[^1]))
|
||||
truncated = truncated[..^1];
|
||||
|
||||
var truncatedLoadout = new LoadoutPreference(
|
||||
loadout.LoadoutName,
|
||||
loadout.CustomName,
|
||||
loadout.CustomDescription,
|
||||
truncated.ToString(),
|
||||
loadout.CustomColorTint,
|
||||
loadout.CustomHeirloom)
|
||||
{ Selected = loadout.Selected, };
|
||||
var truncatedLoadout = new Loadout(
|
||||
loadout.LoadoutName,
|
||||
loadout.CustomName,
|
||||
loadout.CustomDescription,
|
||||
truncated.ToString(),
|
||||
loadout.CustomColorTint,
|
||||
loadout.CustomHeirloom);
|
||||
|
||||
loadouts[i] = truncatedLoadout;
|
||||
_loadoutPreferences[key] = truncatedLoadout;
|
||||
}
|
||||
// WD EDIT END
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed partial class CharacterItemGroupItem
|
||||
p => protoMan.Index<TraitPrototype>((string) p).ID == ID);
|
||||
return value != null;
|
||||
case "loadout":
|
||||
return profile.LoadoutPreferences.TryFirstOrDefault(
|
||||
return profile.LoadoutPreferencesList.TryFirstOrDefault( // WWDP EDIT
|
||||
p => protoMan.Index<LoadoutPrototype>(((Loadout) p).LoadoutName).ID == ID, out value);
|
||||
default:
|
||||
DebugTools.Assert($"Invalid CharacterItemGroupItem Type: {Type}");
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Combat Maid
|
||||
# loadout menu
|
||||
loadout-error-prototype-not-found = Loadout prototype not found.
|
||||
loadout-error-too-expensive = You cannot afford this item.
|
||||
|
||||
# Combat Maid
|
||||
loadout-category-JobsCommandMaid = Maid
|
||||
|
||||
character-item-group-LoadoutMaidHats = Maid Hats
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Combat Maid
|
||||
# loadout menu
|
||||
loadout-error-prototype-not-found = Прототип лоадаута не найден
|
||||
loadout-error-too-expensive = Данный предмет вам не по карману!
|
||||
|
||||
# Combat Maid
|
||||
loadout-category-JobsCommandMaid = Горничная
|
||||
|
||||
character-item-group-LoadoutMaidHats = Головные уборы горничной
|
||||
|
||||
@@ -504,21 +504,21 @@ Global
|
||||
{41B450C0-A361-4CD7-8121-7072B8995CFC} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{7B9472D3-79D4-48D1-9B22-BCDE518FE842} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{1FAE651D-29D8-437A-9864-47CE0D180016} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{3CFEB7DB-12C6-46F3-89FC-1450F3016FFA} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{8922428F-17C3-47A7-BFE9-570DEB2464DA} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{16F7DE32-0186-44B9-9345-0C20D1BF2422} = {AFF53804-115F-4E67-B81F-26265EA27880}
|
||||
{AFF53804-115F-4E67-B81F-26265EA27880} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{23F09C45-950E-4DB7-A465-E937450FF008} = {AFF53804-115F-4E67-B81F-26265EA27880}
|
||||
{440426C1-8DCA-43F6-967F-94439B8DAF47} = {AFF53804-115F-4E67-B81F-26265EA27880}
|
||||
{88B0FC0F-7209-40E2-AF16-EB90AF727C5B} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{A3C5B00A-D232-4A01-B82E-B0E58BFD5C12} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{8A21C7CA-2EB8-40E5-8043-33582C06D139} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{952AAF2A-DF63-4A7D-8094-3453893EBA80} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{A965CB3B-FD31-44AF-8872-85ABA436098D} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{7844DA69-B0F0-49FB-A05E-ECA37372277A} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{3CFEB7DB-12C6-46F3-89FC-1450F3016FFA} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{07CA34A1-1D37-4771-A2E3-495A1044AE0B} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{88B0FC0F-7209-40E2-AF16-EB90AF727C5B} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{83F510FE-9B50-4D96-AFAB-CC13998D6AFE} = {7844DA69-B0F0-49FB-A05E-ECA37372277A}
|
||||
{7844DA69-B0F0-49FB-A05E-ECA37372277A} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{8B35C796-2DCD-4B48-B159-689C0796B6A8} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE}
|
||||
{C310B97E-ECC2-44DA-BABE-012E11F136BC} = {1BED7A6E-3F61-47AE-AED8-1176F241CC1E}
|
||||
{127EE7A3-F1DE-45D0-8255-D0183B4FA42E} = {1BED7A6E-3F61-47AE-AED8-1176F241CC1E}
|
||||
|
||||
Submodule StyleSheetify updated: 4bd9f72cf3...9168c35cc6
Reference in New Issue
Block a user