[Add] Species selection menu (#1009)

* - add: species selection menu

* - fix: Кролькины фиксы ч.1

* - fix: кролькины фиксы ч.2
This commit is contained in:
Cinkafox
2026-01-06 16:57:28 +03:00
committed by GitHub
parent df1ffb471e
commit 8ea10a1d94
15 changed files with 482 additions and 1 deletions

View File

@@ -41,7 +41,10 @@
<Control HorizontalExpand="True"/>
<TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3" VerticalAlignment="Center"
ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/>
<OptionButton Name="SpeciesButton" HorizontalAlignment="Right" />
<!--WD EDIT START-->
<OptionButton Visible="False" Name="SpeciesButton" HorizontalAlignment="Right" />
<Button Name="OpenSpeciesWindow" Text="{Loc 'species-window-open'}" HorizontalAlignment="Right"/>
<!--WD EDIT END-->
</BoxContainer>
<!-- Species -->
<BoxContainer Name="CCustomSpecieName" HorizontalExpand="True">

View File

@@ -619,6 +619,7 @@ namespace Content.Client.Lobby.UI
ReloadPreview();
InitializeCharacterMenu(); // WWDP EDIT
InitializeSpeciesSelection(); // WWDP EDIT;
IsDirty = false;
}

View File

@@ -0,0 +1,50 @@
using Content.Client._White.UserInterface.Windows;
using Content.Shared.Humanoid.Prototypes;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
namespace Content.Client.Lobby.UI;
// WWDP PARTIAL CLASS
public sealed partial class HumanoidProfileEditor
{
private SpeciesSelectWindow? _currentWindow;
public void InitializeSpeciesSelection()
{
OpenSpeciesWindow.OnPressed += OpenSpeciesWindowPressed;
}
private void OpenSpeciesWindowPressed(BaseButton.ButtonEventArgs obj)
{
if(Profile is null)
return;
OpenSpeciesWindow.Disabled = true;
_currentWindow = UserInterfaceManager.CreateWindow<SpeciesSelectWindow>();
_currentWindow.Initialize(Profile);
_currentWindow.OnClose += CurrentWindowClosed;
_currentWindow.OnSpeciesSelected += OnSpeciesSelected;
_currentWindow.OpenCentered();
}
private void OnSpeciesSelected(ProtoId<SpeciesPrototype> proto)
{
SetSpecies(proto);
UpdateHairPickers();
OnSkinColorOnValueChanged();
UpdateCustomSpecieNameEdit();
UpdateHeightWidthSliders();
_currentWindow?.Close();
}
private void CurrentWindowClosed()
{
if(_currentWindow == null)
return;
_currentWindow.OnClose -= CurrentWindowClosed;
_currentWindow.OnSpeciesSelected -= OnSpeciesSelected;
OpenSpeciesWindow.Disabled = false;
}
}

View File

@@ -0,0 +1,18 @@
<controls:SpeciesGroupContainer
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<BoxContainer HorizontalExpand="True" Orientation="Vertical" SeparationOverride="5" Margin="0,0,0,15">
<Label HorizontalAlignment="Center" Name="GroupLabel"/>
<PanelContainer HorizontalExpand="True" SetHeight="5">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#aaaaaa" />
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer
SeparationOverride="5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Name="SpeciesButtonContainer"/>
</BoxContainer>
</controls:SpeciesGroupContainer>

View File

@@ -0,0 +1,70 @@
using Content.Shared._White.SpeciesDictionary;
using Content.Shared.Humanoid.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._White.UserInterface.Controls;
[GenerateTypedNameReferences]
public sealed partial class SpeciesGroupContainer : Control
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly List<(Button, Action<BaseButton.ButtonEventArgs>)> _buttonsEvents = new();
public Action<ProtoId<SpeciesPrototype>>? OnSpeciesSelected { get; set; }
public SpeciesGroupContainer()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void Initialize(SpeciesDictionaryGroupPrototype group)
{
Clear();
GroupLabel.Text = Loc.GetString($"species-window-group-{group.ID.ToLower()}");
foreach (var species in _prototypeManager.EnumeratePrototypes<SpeciesDictionaryPrototype>())
{
if(species.GroupPrototype != group.ID)
continue;
if(!_prototypeManager.TryIndex<SpeciesPrototype>(species.ID, out var speciesPrototype) || !speciesPrototype.RoundStart)
continue;
var button = new Button()
{
Text = Loc.GetString($"species-name-{species.ID.ToLower()}"),
HorizontalAlignment = HAlignment.Stretch
};
if (group.Color.HasValue)
{
button.Modulate = group.Color.Value;
}
Action<BaseButton.ButtonEventArgs> onSelected = _ => OnSpeciesSelected?.Invoke(new(species.ID));
button.OnPressed += onSelected;
_buttonsEvents.Add((button, onSelected));
SpeciesButtonContainer.AddChild(button);
}
}
public void Clear()
{
foreach (var (button, eventInternal) in _buttonsEvents)
{
button.OnPressed -= eventInternal;
}
_buttonsEvents.Clear();
SpeciesButtonContainer.Children.Clear();
}
}

View File

@@ -0,0 +1,40 @@
<controls:SpeciesSelectWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._White.UserInterface.Windows"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'species-window-label'}" MinSize="800,500">
<BoxContainer SeparationOverride="15" Orientation="Horizontal">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer HScrollEnabled="False" SetWidth="250">
<BoxContainer Orientation="Vertical" Name="RaceGroupContainer"/>
</ScrollContainer>
</PanelContainer>
<BoxContainer HorizontalExpand="True" Name="SpeciesVisualContainer" Visible="False" SeparationOverride="5" Orientation="Vertical">
<PanelContainer>
<PanelContainer>
<Label Name="SpeciesNameLabel"
StyleClasses="LabelHeading"
ClipText="True"
Margin="0,0,8,0"/>
</PanelContainer>
<BoxContainer HorizontalAlignment="Right" Orientation="Horizontal">
<SpriteView Name="EntityFrontView" Scale="4,4"/>
<SpriteView Name="EntityRightView" Scale="4,4" OverrideDirection="East"/>
</BoxContainer>
</PanelContainer>
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer Margin="5" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer HorizontalExpand="True" Name="InfoContainer"/>
</ScrollContainer>
</PanelContainer>
<Button HorizontalAlignment="Center" Name="SelectButton" Text="{Loc 'species-window-select'}"/>
</BoxContainer>
</BoxContainer>
</controls:SpeciesSelectWindow>

View File

@@ -0,0 +1,152 @@
using System.Linq;
using Content.Client._White.UserInterface.Controls;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
using Content.Shared._White.SpeciesDictionary;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
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;
namespace Content.Client._White.UserInterface.Windows;
[GenerateTypedNameReferences]
public sealed partial class SpeciesSelectWindow : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly DocumentParsingManager _documentParsingManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action<ProtoId<SpeciesPrototype>>? OnSpeciesSelected;
private EntityUid _dummyUid;
private HumanoidCharacterProfile? _dummyProfile;
private Dictionary<ProtoId<SpeciesPrototype>, SpeciesDictionaryPrototype> _dictionaryCache = [];
private ProtoId<SpeciesPrototype>? _selectedSpecies;
public ProtoId<SpeciesPrototype>? SelectedSpecies
{
get => _selectedSpecies;
set
{
if(!_prototypeManager.TryIndex<SpeciesPrototype>(value, out var prototype) || _playerManager.LocalSession is null)
return;
InfoContainer.Children.Clear();
_selectedSpecies = value;
_entityManager.DeleteEntity(_dummyUid);
SpeciesVisualContainer.Visible = value != null;
if (_selectedSpecies == null)
return;
SpeciesNameLabel.Text = Loc.GetString($"species-name-{_selectedSpecies.Value.Id.ToLower()}");
if (_dummyProfile != null)
{
_dummyProfile = _dummyProfile.WithSpecies(_selectedSpecies.Value);
_dummyProfile.EnsureValid(_playerManager.LocalSession, IoCManager.Instance!);
}
_dummyUid = _entityManager.SpawnEntity(prototype.DollPrototype, MapCoordinates.Nullspace);
if (_entityManager.TryGetComponent<HumanoidAppearanceComponent>(_dummyUid, out var humanoid))
{
var hiddenLayers = humanoid.HiddenLayers;
var appearanceSystem = _entityManager.System<HumanoidAppearanceSystem>();
appearanceSystem.LoadProfile(_dummyUid, _dummyProfile, humanoid, false, false);
// Reapply the hidden layers set from clothing
appearanceSystem.SetLayersVisibility(_dummyUid, hiddenLayers, false, humanoid: humanoid);
}
EntityFrontView.SetEntity(_dummyUid);
EntityRightView.SetEntity(_dummyUid);
if(!_dictionaryCache.TryGetValue(_selectedSpecies.Value, out var dictionary))
return;
_documentParsingManager.TryAddMarkup(InfoContainer, dictionary.GuidePrototype);
}
}
private List<SpeciesGroupContainer> _groupContainers = new();
public SpeciesSelectWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
SelectButton.OnPressed += SelectButtonOnOnPressed;
}
public void Initialize(HumanoidCharacterProfile characterProfile)
{
DisposeContainers();
_dummyProfile = characterProfile.Clone();
var groups =
_prototypeManager.EnumeratePrototypes<SpeciesDictionaryGroupPrototype>().ToList();
groups.Sort((a, b) => a.Weight.CompareTo(b.Weight));
foreach (var group in groups)
{
var groupContainer = new SpeciesGroupContainer();
groupContainer.Initialize(group);
groupContainer.OnSpeciesSelected += OnSpeciesSelectedForView;
_groupContainers.Add(groupContainer);
RaceGroupContainer.AddChild(groupContainer);
}
foreach (var dictionary in _prototypeManager.EnumeratePrototypes<SpeciesDictionaryPrototype>())
_dictionaryCache[dictionary.ID] = dictionary;
SelectedSpecies = characterProfile.Species;
}
private void OnSpeciesSelectedForView(ProtoId<SpeciesPrototype> species)
{
SelectedSpecies = species;
}
private void SelectButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
if(_selectedSpecies == null)
return;
OnSpeciesSelected?.Invoke(_selectedSpecies.Value);
}
private void DisposeContainers()
{
foreach (var container in _groupContainers)
{
container.OnSpeciesSelected -= OnSpeciesSelectedForView;
container.Clear();
}
RaceGroupContainer.Children.Clear();
_dictionaryCache.Clear();
}
public override void Close()
{
base.Close();
_entityManager.DeleteEntity(_dummyUid);
SelectButton.OnPressed -= SelectButtonOnOnPressed;
DisposeContainers();
}
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.Guidebook;
using Robust.Shared.Prototypes;
namespace Content.Shared._White.SpeciesDictionary;
[Prototype]
public sealed class SpeciesDictionaryPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;
[DataField] public ProtoId<GuideEntryPrototype> GuidePrototype { get; private set; } = default!;
[DataField] public ProtoId<SpeciesDictionaryGroupPrototype> GroupPrototype { get; private set; } = default!;
}
[Prototype]
public sealed class SpeciesDictionaryGroupPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;
[DataField] public Color? Color { get; private set; }
[DataField] public int Weight { get; private set; }
}

View File

@@ -0,0 +1,8 @@
species-window-select = Select
species-window-group-humanoid = Humanoids
species-window-group-other = Other
species-window-open = Open
species-window-label = Species Selection Menu
species-name-slimeperson = Slime person

View File

@@ -0,0 +1,8 @@
species-window-select = Выбрать
species-window-group-humanoid = Гуманоиды
species-window-group-other = Другие
species-window-open = Открыть
species-window-label = Меню выбора рас
species-name-slimeperson = Слаймолюд

View File

@@ -5,6 +5,7 @@
children:
- Arachnid
- Diona
- Felinid # WWDP EDIT
- Human
- Moth
- Oni
@@ -16,6 +17,7 @@
- Plasmaman
# - Chitinid
- Tajaran
- Vulpkanin # WWDP EDIT
- Xelthia
# ImpStation additions
- Thaven
@@ -84,3 +86,17 @@
id: Xelthia
name: species-name-xelthia
text: "/ServerInfo/_EE/Guidebook/Mobs/Xelthia.xml"
# WWDP EDIT START
- type: guideEntry
id: Felinid
name: species-name-felinid
text: "/ServerInfo/Guidebook/Mobs/Felinid.xml"
- type: guideEntry
id: Vulpkanin
name: species-name-vulpkanin
text: "/ServerInfo/Guidebook/Mobs/Vulpkanin.xml"
# WWDP EDIT END

View File

@@ -0,0 +1,7 @@
- type: speciesDictionaryGroup
id: Other
weight: 3
- type: speciesDictionaryGroup
id: Humanoid
weight: 0

View File

@@ -0,0 +1,79 @@
- type: speciesDictionary
id: Human
guidePrototype: Human
groupPrototype: Humanoid
- type: speciesDictionary
id: Reptilian
guidePrototype: Reptilian
groupPrototype: Other
- type: speciesDictionary
id: SlimePerson
guidePrototype: SlimePerson
groupPrototype: Humanoid
- type: speciesDictionary
id: Oni
guidePrototype: Oni
groupPrototype: Other
- type: speciesDictionary
id: Felinid
guidePrototype: Felinid
groupPrototype: Humanoid
- type: speciesDictionary
id: Vulpkanin
guidePrototype: Vulpkanin
groupPrototype: Other
- type: speciesDictionary
id: Diona
guidePrototype: Diona
groupPrototype: Other
- type: speciesDictionary
id: Shadowkin
guidePrototype: Shadowkin
groupPrototype: Other
- type: speciesDictionary
id: Tajaran
guidePrototype: Tajaran
groupPrototype: Other
- type: speciesDictionary
id: Xelthia
guidePrototype: Xelthia
groupPrototype: Other
- type: speciesDictionary
id: Arachnid
guidePrototype: Arachnid
groupPrototype: Other
- type: speciesDictionary
id: Moth
guidePrototype: Moth
groupPrototype: Other
- type: speciesDictionary
id: IPC
guidePrototype: IPC
groupPrototype: Other
- type: speciesDictionary
id: Harpy
guidePrototype: Harpy
groupPrototype: Humanoid
- type: speciesDictionary
id: Plasmaman
guidePrototype: Plasmaman
groupPrototype: Other
- type: speciesDictionary
id: Thaven
guidePrototype: Thaven
groupPrototype: Other

View File

@@ -0,0 +1,3 @@
<Document>
Мяу
</Document>

View File

@@ -0,0 +1,3 @@
<Document>
Гав
</Document>