feat: SimpleRadial menu support for sprite-view and more extensibility (#39223)
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
using Content.Shared.Changeling.Systems;
|
using Content.Client.Stylesheets;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Changeling.Components;
|
||||||
|
using Content.Shared.Changeling.Systems;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
@@ -7,28 +10,58 @@ namespace Content.Client.Changeling.UI;
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
{
|
{
|
||||||
private ChangelingTransformMenu? _window;
|
private SimpleRadialMenu? _menu;
|
||||||
|
private static readonly Color SelectedOptionBackground = StyleNano.ButtonColorGoodDefault.WithAlpha(128);
|
||||||
|
private static readonly Color SelectedOptionHoverBackground = StyleNano.ButtonColorGoodHovered.WithAlpha(128);
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
base.Open();
|
base.Open();
|
||||||
|
|
||||||
_window = this.CreateWindow<ChangelingTransformMenu>();
|
_menu = this.CreateWindow<SimpleRadialMenu>();
|
||||||
|
Update();
|
||||||
_window.OnIdentitySelect += SendIdentitySelect;
|
_menu.OpenOverMouseScreenPosition();
|
||||||
|
|
||||||
_window.Update(Owner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
if (_window == null)
|
if (_menu == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_window.Update(Owner);
|
if (!EntMan.TryGetComponent<ChangelingIdentityComponent>(Owner, out var lingIdentity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity?.CurrentIdentity);
|
||||||
|
|
||||||
|
_menu.SetButtons(models);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendIdentitySelect(NetEntity identityId)
|
private IEnumerable<RadialMenuOptionBase> ConvertToButtons(
|
||||||
|
IEnumerable<EntityUid> identities,
|
||||||
|
EntityUid? currentIdentity
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var buttons = new List<RadialMenuOptionBase>();
|
||||||
|
foreach (var identity in identities)
|
||||||
|
{
|
||||||
|
if (!EntMan.TryGetComponent<MetaDataComponent>(identity, out var metadata))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var option = new RadialMenuActionOption<NetEntity>(SendIdentitySelect, EntMan.GetNetEntity(identity))
|
||||||
|
{
|
||||||
|
IconSpecifier = RadialMenuIconSpecifier.With(identity),
|
||||||
|
ToolTip = metadata.EntityName,
|
||||||
|
BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null,
|
||||||
|
HoverBackgroundColor = (currentIdentity == identity) ? SelectedOptionHoverBackground : null
|
||||||
|
};
|
||||||
|
buttons.Add(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendIdentitySelect(NetEntity identityId)
|
||||||
{
|
{
|
||||||
SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId));
|
SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<ui:RadialMenu
|
|
||||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
|
||||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
|
||||||
VerticalExpand="True"
|
|
||||||
HorizontalExpand="True">
|
|
||||||
<ui:RadialContainer Name="Main">
|
|
||||||
</ui:RadialContainer>
|
|
||||||
</ui:RadialMenu>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Content.Client.UserInterface.Controls;
|
|
||||||
using Content.Shared.Changeling.Components;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
|
|
||||||
namespace Content.Client.Changeling.UI;
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class ChangelingTransformMenu : RadialMenu
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IEntityManager _entity = default!;
|
|
||||||
public event Action<NetEntity>? OnIdentitySelect;
|
|
||||||
|
|
||||||
public ChangelingTransformMenu()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(EntityUid uid)
|
|
||||||
{
|
|
||||||
Main.DisposeAllChildren();
|
|
||||||
|
|
||||||
if (!_entity.TryGetComponent<ChangelingIdentityComponent>(uid, out var identityComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var identityUid in identityComp.ConsumedIdentities)
|
|
||||||
{
|
|
||||||
if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var identityName = metadata.EntityName;
|
|
||||||
|
|
||||||
var button = new ChangelingTransformMenuButton()
|
|
||||||
{
|
|
||||||
StyleClasses = { "RadialMenuButton" },
|
|
||||||
SetSize = new Vector2(64, 64),
|
|
||||||
ToolTip = identityName,
|
|
||||||
};
|
|
||||||
|
|
||||||
var entView = new SpriteView()
|
|
||||||
{
|
|
||||||
SetSize = new Vector2(48, 48),
|
|
||||||
VerticalAlignment = VAlignment.Center,
|
|
||||||
HorizontalAlignment = HAlignment.Center,
|
|
||||||
Stretch = SpriteView.StretchMode.Fill,
|
|
||||||
};
|
|
||||||
entView.SetEntity(identityUid);
|
|
||||||
button.OnButtonUp += _ =>
|
|
||||||
{
|
|
||||||
OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
|
|
||||||
Close();
|
|
||||||
};
|
|
||||||
button.AddChild(entView);
|
|
||||||
Main.AddChild(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ChangelingTransformMenuButton : RadialMenuTextureButtonWithSector;
|
|
||||||
@@ -1,25 +1,58 @@
|
|||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Ghost.Roles;
|
using Content.Shared.Ghost.Roles;
|
||||||
|
using Content.Shared.Ghost.Roles.Components;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Ghost;
|
namespace Content.Client.Ghost;
|
||||||
|
|
||||||
public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface
|
public sealed class GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
{
|
{
|
||||||
private GhostRoleRadioMenu? _ghostRoleRadioMenu;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
public GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
private SimpleRadialMenu? _ghostRoleRadioMenu;
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
base.Open();
|
base.Open();
|
||||||
|
|
||||||
_ghostRoleRadioMenu = this.CreateWindow<GhostRoleRadioMenu>();
|
_ghostRoleRadioMenu = this.CreateWindow<SimpleRadialMenu>();
|
||||||
_ghostRoleRadioMenu.SetEntity(Owner);
|
|
||||||
_ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
|
// The purpose of this radial UI is for ghost role radios that allow you to select
|
||||||
|
// more than one potential option, such as with kobolds/lizards.
|
||||||
|
// This means that it won't show anything if SelectablePrototypes is empty.
|
||||||
|
if (!EntMan.TryGetComponent<GhostRoleMobSpawnerComponent>(Owner, out var comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var list = ConvertToButtons(comp.SelectablePrototypes);
|
||||||
|
|
||||||
|
_ghostRoleRadioMenu.SetButtons(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<RadialMenuOptionBase> ConvertToButtons(List<ProtoId<GhostRolePrototype>> protoIds)
|
||||||
|
{
|
||||||
|
var list = new List<RadialMenuOptionBase>();
|
||||||
|
foreach (var ghostRoleProtoId in protoIds)
|
||||||
|
{
|
||||||
|
// For each prototype we find we want to create a button that uses the name of the ghost role
|
||||||
|
// as the hover tooltip, and the icon is taken from either the ghost role entityprototype
|
||||||
|
// or the indicated icon entityprototype.
|
||||||
|
if (!_prototypeManager.Resolve(ghostRoleProtoId, out var ghostRoleProto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var option = new RadialMenuActionOption<ProtoId<GhostRolePrototype>>(SendGhostRoleRadioMessage, ghostRoleProtoId)
|
||||||
|
{
|
||||||
|
ToolTip = Loc.GetString(ghostRoleProto.Name),
|
||||||
|
// pick the icon if it exists, otherwise fallback to the ghost role's entity
|
||||||
|
IconSpecifier = ghostRoleProto.IconPrototype != null
|
||||||
|
&& _prototypeManager.Resolve(ghostRoleProto.IconPrototype, out var iconProto)
|
||||||
|
? RadialMenuIconSpecifier.With(iconProto)
|
||||||
|
: RadialMenuIconSpecifier.With(ghostRoleProto.EntityPrototype)
|
||||||
|
};
|
||||||
|
list.Add(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
|
private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<ui:RadialMenu
|
|
||||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
|
||||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
|
||||||
VerticalExpand="True"
|
|
||||||
HorizontalExpand="True">
|
|
||||||
<ui:RadialContainer Name="Main">
|
|
||||||
</ui:RadialContainer>
|
|
||||||
</ui:RadialMenu>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
using Content.Client.UserInterface.Controls;
|
|
||||||
using Content.Shared.Ghost.Roles;
|
|
||||||
using Content.Shared.Ghost.Roles.Components;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Content.Client.Ghost;
|
|
||||||
|
|
||||||
public sealed partial class GhostRoleRadioMenu : RadialMenu
|
|
||||||
{
|
|
||||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
|
|
||||||
public event Action<ProtoId<GhostRolePrototype>>? SendGhostRoleRadioMessageAction;
|
|
||||||
|
|
||||||
public EntityUid Entity { get; set; }
|
|
||||||
|
|
||||||
public GhostRoleRadioMenu()
|
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEntity(EntityUid uid)
|
|
||||||
{
|
|
||||||
Entity = uid;
|
|
||||||
RefreshUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshUI()
|
|
||||||
{
|
|
||||||
// The main control that will contain all the clickable options
|
|
||||||
var main = FindControl<RadialContainer>("Main");
|
|
||||||
|
|
||||||
// The purpose of this radial UI is for ghost role radios that allow you to select
|
|
||||||
// more than one potential option, such as with kobolds/lizards.
|
|
||||||
// This means that it won't show anything if SelectablePrototypes is empty.
|
|
||||||
if (!_entityManager.TryGetComponent<GhostRoleMobSpawnerComponent>(Entity, out var comp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var ghostRoleProtoString in comp.SelectablePrototypes)
|
|
||||||
{
|
|
||||||
// For each prototype we find we want to create a button that uses the name of the ghost role
|
|
||||||
// as the hover tooltip, and the icon is taken from either the ghost role entityprototype
|
|
||||||
// or the indicated icon entityprototype.
|
|
||||||
if (!_prototypeManager.TryIndex<GhostRolePrototype>(ghostRoleProtoString, out var ghostRoleProto))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var button = new GhostRoleRadioMenuButton()
|
|
||||||
{
|
|
||||||
SetSize = new Vector2(64, 64),
|
|
||||||
ToolTip = Loc.GetString(ghostRoleProto.Name),
|
|
||||||
ProtoId = ghostRoleProto.ID,
|
|
||||||
};
|
|
||||||
|
|
||||||
var entProtoView = new EntityPrototypeView()
|
|
||||||
{
|
|
||||||
SetSize = new Vector2(48, 48),
|
|
||||||
VerticalAlignment = VAlignment.Center,
|
|
||||||
HorizontalAlignment = HAlignment.Center,
|
|
||||||
Stretch = SpriteView.StretchMode.Fill
|
|
||||||
};
|
|
||||||
|
|
||||||
// pick the icon if it exists, otherwise fallback to the ghost role's entity
|
|
||||||
if (_prototypeManager.Resolve(ghostRoleProto.IconPrototype, out var iconProto))
|
|
||||||
entProtoView.SetPrototype(iconProto);
|
|
||||||
else
|
|
||||||
entProtoView.SetPrototype(ghostRoleProto.EntityPrototype);
|
|
||||||
|
|
||||||
button.AddChild(entProtoView);
|
|
||||||
main.AddChild(button);
|
|
||||||
AddGhostRoleRadioMenuButtonOnClickActions(main);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddGhostRoleRadioMenuButtonOnClickActions(Control control)
|
|
||||||
{
|
|
||||||
var mainControl = control as RadialContainer;
|
|
||||||
|
|
||||||
if (mainControl == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var child in mainControl.Children)
|
|
||||||
{
|
|
||||||
var castChild = child as GhostRoleRadioMenuButton;
|
|
||||||
|
|
||||||
if (castChild == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
castChild.OnButtonUp += _ =>
|
|
||||||
{
|
|
||||||
SendGhostRoleRadioMessageAction?.Invoke(castChild.ProtoId);
|
|
||||||
Close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButtonWithSector
|
|
||||||
{
|
|
||||||
public ProtoId<GhostRolePrototype> ProtoId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -51,10 +51,10 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
|
|||||||
_menu.OpenOverMouseScreenPosition();
|
_menu.OpenOverMouseScreenPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<RadialMenuOption> ConvertToButtons(HashSet<ProtoId<RCDPrototype>> prototypes)
|
private IEnumerable<RadialMenuOptionBase> ConvertToButtons(HashSet<ProtoId<RCDPrototype>> prototypes)
|
||||||
{
|
{
|
||||||
Dictionary<string, List<RadialMenuActionOption>> buttonsByCategory = new();
|
Dictionary<string, List<RadialMenuActionOptionBase>> buttonsByCategory = new();
|
||||||
ValueList<RadialMenuActionOption> topLevelActions = new();
|
ValueList<RadialMenuActionOptionBase> topLevelActions = new();
|
||||||
foreach (var protoId in prototypes)
|
foreach (var protoId in prototypes)
|
||||||
{
|
{
|
||||||
var prototype = _prototypeManager.Index(protoId);
|
var prototype = _prototypeManager.Index(protoId);
|
||||||
@@ -62,7 +62,7 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
|
|||||||
{
|
{
|
||||||
var topLevelActionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
|
var topLevelActionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
|
||||||
{
|
{
|
||||||
Sprite = prototype.Sprite,
|
IconSpecifier = RadialMenuIconSpecifier.With(prototype.Sprite),
|
||||||
ToolTip = GetTooltip(prototype)
|
ToolTip = GetTooltip(prototype)
|
||||||
};
|
};
|
||||||
topLevelActions.Add(topLevelActionOption);
|
topLevelActions.Add(topLevelActionOption);
|
||||||
@@ -74,26 +74,26 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
|
|||||||
|
|
||||||
if (!buttonsByCategory.TryGetValue(prototype.Category, out var list))
|
if (!buttonsByCategory.TryGetValue(prototype.Category, out var list))
|
||||||
{
|
{
|
||||||
list = new List<RadialMenuActionOption>();
|
list = new List<RadialMenuActionOptionBase>();
|
||||||
buttonsByCategory.Add(prototype.Category, list);
|
buttonsByCategory.Add(prototype.Category, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
|
var actionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
|
||||||
{
|
{
|
||||||
Sprite = prototype.Sprite,
|
IconSpecifier = RadialMenuIconSpecifier.With(prototype.Sprite),
|
||||||
ToolTip = GetTooltip(prototype)
|
ToolTip = GetTooltip(prototype)
|
||||||
};
|
};
|
||||||
list.Add(actionOption);
|
list.Add(actionOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
var models = new RadialMenuOption[buttonsByCategory.Count + topLevelActions.Count];
|
var models = new RadialMenuOptionBase[buttonsByCategory.Count + topLevelActions.Count];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var (key, list) in buttonsByCategory)
|
foreach (var (key, list) in buttonsByCategory)
|
||||||
{
|
{
|
||||||
var groupInfo = PrototypesGroupingInfo[key];
|
var groupInfo = PrototypesGroupingInfo[key];
|
||||||
models[i] = new RadialMenuNestedLayerOption(list)
|
models[i] = new RadialMenuNestedLayerOption(list)
|
||||||
{
|
{
|
||||||
Sprite = groupInfo.Sprite,
|
IconSpecifier = RadialMenuIconSpecifier.With(groupInfo.Sprite),
|
||||||
ToolTip = Loc.GetString(groupInfo.Tooltip)
|
ToolTip = Loc.GetString(groupInfo.Tooltip)
|
||||||
};
|
};
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ public sealed class StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : B
|
|||||||
_menu.Open();
|
_menu.Open();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<RadialMenuActionOption> ConvertToButtons(IReadOnlyList<StationAiRadial> actions)
|
private IEnumerable<RadialMenuActionOptionBase> ConvertToButtons(IReadOnlyList<StationAiRadial> actions)
|
||||||
{
|
{
|
||||||
var models = new RadialMenuActionOption[actions.Count];
|
var models = new RadialMenuActionOptionBase[actions.Count];
|
||||||
for (int i = 0; i < actions.Count; i++)
|
for (int i = 0; i < actions.Count; i++)
|
||||||
{
|
{
|
||||||
var action = actions[i];
|
var action = actions[i];
|
||||||
models[i] = new RadialMenuActionOption<BaseStationAiAction>(HandleRadialMenuClick, action.Event)
|
models[i] = new RadialMenuActionOption<BaseStationAiAction>(HandleRadialMenuClick, action.Event)
|
||||||
{
|
{
|
||||||
Sprite = action.Sprite,
|
IconSpecifier = RadialMenuIconSpecifier.With(action.Sprite),
|
||||||
ToolTip = action.Tooltip
|
ToolTip = action.Tooltip
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,10 +229,10 @@ public class RadialMenu : BaseWindow
|
|||||||
/// from interactions.
|
/// from interactions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Virtual]
|
[Virtual]
|
||||||
public class RadialMenuTextureButtonBase : TextureButton
|
public abstract class RadialMenuButtonBase : BaseButton
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected RadialMenuTextureButtonBase()
|
protected RadialMenuButtonBase()
|
||||||
{
|
{
|
||||||
EnableAllKeybinds = true;
|
EnableAllKeybinds = true;
|
||||||
}
|
}
|
||||||
@@ -242,8 +242,10 @@ public class RadialMenuTextureButtonBase : TextureButton
|
|||||||
{
|
{
|
||||||
if (args.Function == EngineKeyFunctions.UIClick
|
if (args.Function == EngineKeyFunctions.UIClick
|
||||||
|| args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
|| args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||||
|
{
|
||||||
base.KeyBindUp(args);
|
base.KeyBindUp(args);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -253,8 +255,14 @@ public class RadialMenuTextureButtonBase : TextureButton
|
|||||||
/// works only if control have parent, and ActiveContainer property is set.
|
/// works only if control have parent, and ActiveContainer property is set.
|
||||||
/// Also considers all space outside of radial menu buttons as itself for clicking.
|
/// Also considers all space outside of radial menu buttons as itself for clicking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTextureButtonBase
|
public sealed class RadialMenuContextualCentralTextureButton : TextureButton
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RadialMenuContextualCentralTextureButton()
|
||||||
|
{
|
||||||
|
EnableAllKeybinds = true;
|
||||||
|
}
|
||||||
|
|
||||||
public float InnerRadius { get; set; }
|
public float InnerRadius { get; set; }
|
||||||
|
|
||||||
public Vector2? ParentCenter { get; set; }
|
public Vector2? ParentCenter { get; set; }
|
||||||
@@ -271,15 +279,25 @@ public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTexture
|
|||||||
|
|
||||||
var innerRadiusSquared = InnerRadius * InnerRadius;
|
var innerRadiusSquared = InnerRadius * InnerRadius;
|
||||||
|
|
||||||
// comparing to squared values is faster then making sqrt
|
// comparing to squared values is faster, then making sqrt
|
||||||
return distSquared < innerRadiusSquared;
|
return distSquared < innerRadiusSquared;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Function == EngineKeyFunctions.UIClick
|
||||||
|
|| args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||||
|
{
|
||||||
|
base.KeyBindUp(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Menu button for outer area of radial menu (covers everything 'outside').
|
/// Menu button for outer area of radial menu (covers everything 'outside').
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase
|
public sealed class RadialMenuOuterAreaButton : RadialMenuButtonBase
|
||||||
{
|
{
|
||||||
public float OuterRadius { get; set; }
|
public float OuterRadius { get; set; }
|
||||||
|
|
||||||
@@ -303,7 +321,7 @@ public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Virtual]
|
[Virtual]
|
||||||
public class RadialMenuTextureButton : RadialMenuTextureButtonBase
|
public class RadialMenuButton : RadialMenuButtonBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Upon clicking this button the radial menu will be moved to the layer of this control.
|
/// Upon clicking this button the radial menu will be moved to the layer of this control.
|
||||||
@@ -319,9 +337,8 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A simple texture button that can move the user to a different layer within a radial menu
|
/// A simple texture button that can move the user to a different layer within a radial menu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RadialMenuTextureButton()
|
public RadialMenuButton()
|
||||||
{
|
{
|
||||||
EnableAllKeybinds = true;
|
|
||||||
OnButtonUp += OnClicked;
|
OnButtonUp += OnClicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +408,7 @@ public interface IRadialMenuItemWithSector
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Virtual]
|
[Virtual]
|
||||||
public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadialMenuItemWithSector
|
public class RadialMenuButtonWithSector : RadialMenuButton, IRadialMenuItemWithSector
|
||||||
{
|
{
|
||||||
private Vector2[]? _sectorPointsForDrawing;
|
private Vector2[]? _sectorPointsForDrawing;
|
||||||
|
|
||||||
@@ -500,7 +517,7 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A simple texture button that can move the user to a different layer within a radial menu
|
/// A simple texture button that can move the user to a different layer within a radial menu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RadialMenuTextureButtonWithSector()
|
public RadialMenuButtonWithSector()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using Robust.Client.GameObjects;
|
|||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Client.Input;
|
using Robust.Client.Input;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.UserInterface.Controls;
|
namespace Content.Client.UserInterface.Controls;
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
_attachMenuToEntity = owner;
|
_attachMenuToEntity = owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetButtons(IEnumerable<RadialMenuOption> models, SimpleRadialMenuSettings? settings = null)
|
public void SetButtons(IEnumerable<RadialMenuOptionBase> models, SimpleRadialMenuSettings? settings = null)
|
||||||
{
|
{
|
||||||
ClearExistingChildrenRadialButtons();
|
ClearExistingChildrenRadialButtons();
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Fill(
|
private void Fill(
|
||||||
IEnumerable<RadialMenuOption> models,
|
IEnumerable<RadialMenuOptionBase> models,
|
||||||
SpriteSystem sprites,
|
SpriteSystem sprites,
|
||||||
ICollection<Control> rootControlChildren,
|
ICollection<Control> rootControlChildren,
|
||||||
SimpleRadialMenuSettings settings
|
SimpleRadialMenuSettings settings
|
||||||
@@ -77,7 +79,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RadialMenuTextureButton RecursiveContainerExtraction(
|
private RadialMenuButton RecursiveContainerExtraction(
|
||||||
SpriteSystem sprites,
|
SpriteSystem sprites,
|
||||||
ICollection<Control> rootControlChildren,
|
ICollection<Control> rootControlChildren,
|
||||||
RadialMenuNestedLayerOption model,
|
RadialMenuNestedLayerOption model,
|
||||||
@@ -112,8 +114,8 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
return thisLayerLinkButton;
|
return thisLayerLinkButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RadialMenuTextureButton ConvertToButton(
|
private RadialMenuButton ConvertToButton(
|
||||||
RadialMenuOption model,
|
RadialMenuOptionBase model,
|
||||||
SpriteSystem sprites,
|
SpriteSystem sprites,
|
||||||
SimpleRadialMenuSettings settings,
|
SimpleRadialMenuSettings settings,
|
||||||
bool haveNested
|
bool haveNested
|
||||||
@@ -121,29 +123,26 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
{
|
{
|
||||||
var button = settings.UseSectors
|
var button = settings.UseSectors
|
||||||
? ConvertToButtonWithSector(model, settings)
|
? ConvertToButtonWithSector(model, settings)
|
||||||
: new RadialMenuTextureButton();
|
: new RadialMenuButton();
|
||||||
button.SetSize = new Vector2(64f, 64f);
|
button.SetSize = new Vector2(64f, 64f);
|
||||||
button.ToolTip = model.ToolTip;
|
button.ToolTip = model.ToolTip;
|
||||||
if (model.Sprite != null)
|
var imageControl = model.IconSpecifier switch
|
||||||
{
|
{
|
||||||
var scale = Vector2.One;
|
RadialMenuTextureIconSpecifier textureSpecifier => CreateTexture(textureSpecifier.Sprite, sprites),
|
||||||
|
RadialMenuEntityIconSpecifier entitySpecifier => CreateSpriteView(entitySpecifier.Entity),
|
||||||
|
RadialMenuEntityPrototypeIconSpecifier entProtoSpecifier => CreateEntityPrototypeView(entProtoSpecifier.ProtoId),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
var texture = sprites.Frame0(model.Sprite);
|
if(imageControl != null)
|
||||||
if (texture.Width <= 32)
|
button.AddChild(imageControl);
|
||||||
{
|
|
||||||
scale *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.TextureNormal = texture;
|
if (model is RadialMenuActionOptionBase actionOption)
|
||||||
button.Scale = scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model is RadialMenuActionOption actionOption)
|
|
||||||
{
|
{
|
||||||
button.OnPressed += _ =>
|
button.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
actionOption.OnPressed?.Invoke();
|
actionOption.OnPressed?.Invoke();
|
||||||
if(!haveNested)
|
if (!haveNested)
|
||||||
Close();
|
Close();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -151,9 +150,53 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RadialMenuTextureButtonWithSector ConvertToButtonWithSector(RadialMenuOption model, SimpleRadialMenuSettings settings)
|
private Control CreateEntityPrototypeView(EntProtoId protoId)
|
||||||
{
|
{
|
||||||
var button = new RadialMenuTextureButtonWithSector
|
var entProtoView = new EntityPrototypeView
|
||||||
|
{
|
||||||
|
SetSize = new Vector2(48, 48),
|
||||||
|
VerticalAlignment = VAlignment.Center,
|
||||||
|
HorizontalAlignment = HAlignment.Center,
|
||||||
|
Stretch = SpriteView.StretchMode.Fill,
|
||||||
|
};
|
||||||
|
entProtoView.SetPrototype(protoId);
|
||||||
|
return entProtoView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Control CreateSpriteView(EntityUid entityForSpriteView)
|
||||||
|
{
|
||||||
|
var entView = new SpriteView
|
||||||
|
{
|
||||||
|
SetSize = new Vector2(48, 48),
|
||||||
|
VerticalAlignment = VAlignment.Center,
|
||||||
|
HorizontalAlignment = HAlignment.Center,
|
||||||
|
Stretch = SpriteView.StretchMode.Fill,
|
||||||
|
};
|
||||||
|
entView.SetEntity(entityForSpriteView);
|
||||||
|
return entView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Control CreateTexture(SpriteSpecifier spriteSpecifier, SpriteSystem sprites)
|
||||||
|
{
|
||||||
|
var scale = Vector2.One;
|
||||||
|
|
||||||
|
var texture = sprites.Frame0(spriteSpecifier);
|
||||||
|
if (texture.Width <= 32)
|
||||||
|
{
|
||||||
|
scale *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageControl = new TextureRect()
|
||||||
|
{
|
||||||
|
Texture = texture,
|
||||||
|
TextureScale = scale
|
||||||
|
};
|
||||||
|
return imageControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RadialMenuButtonWithSector ConvertToButtonWithSector(RadialMenuOptionBase model, SimpleRadialMenuSettings settings)
|
||||||
|
{
|
||||||
|
var button = new RadialMenuButtonWithSector
|
||||||
{
|
{
|
||||||
DrawBorder = settings.DisplayBorders,
|
DrawBorder = settings.DisplayBorders,
|
||||||
DrawBackground = !settings.NoBackground
|
DrawBackground = !settings.NoBackground
|
||||||
@@ -228,32 +271,99 @@ public sealed partial class SimpleRadialMenu : RadialMenu
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public abstract class RadialMenuOption
|
/// Abstract representation of a way to specify icon in radial menu.
|
||||||
|
/// </summary>
|
||||||
|
public abstract record RadialMenuIconSpecifier
|
||||||
{
|
{
|
||||||
public string? ToolTip { get; init; }
|
/// <summary> Use entity prototype viewer. </summary>
|
||||||
|
public static RadialMenuIconSpecifier? With(EntProtoId? protoId)
|
||||||
|
{
|
||||||
|
if (protoId is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
public SpriteSpecifier? Sprite { get; init; }
|
return new RadialMenuEntityPrototypeIconSpecifier(protoId.Value);
|
||||||
public Color? BackgroundColor { get; set; }
|
}
|
||||||
public Color? HoverBackgroundColor { get; set; }
|
|
||||||
|
/// <summary> Use simple texture icon. </summary>
|
||||||
|
public static RadialMenuIconSpecifier? With(SpriteSpecifier? sprite)
|
||||||
|
{
|
||||||
|
if (sprite == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new RadialMenuTextureIconSpecifier(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Use entity sprite viewer. </summary>
|
||||||
|
public static RadialMenuIconSpecifier? With(EntityUid? entity)
|
||||||
|
{
|
||||||
|
if (entity == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new RadialMenuEntityIconSpecifier(entity.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class RadialMenuActionOption(Action onPressed) : RadialMenuOption
|
/// <summary> Marker that <see cref="SpriteView"/> should be used to display radial menu icon. </summary>
|
||||||
|
public sealed record RadialMenuEntityIconSpecifier(EntityUid Entity) : RadialMenuIconSpecifier;
|
||||||
|
|
||||||
|
/// <summary> Marker that <see cref="TextureRect"/> should be used to display radial menu icon. </summary>
|
||||||
|
public sealed record RadialMenuTextureIconSpecifier(SpriteSpecifier Sprite) : RadialMenuIconSpecifier;
|
||||||
|
|
||||||
|
/// <summary> Marker that <see cref="EntityPrototypeView"/> should be used to display radial menu icon. </summary>
|
||||||
|
public sealed record RadialMenuEntityPrototypeIconSpecifier(EntProtoId ProtoId) : RadialMenuIconSpecifier;
|
||||||
|
|
||||||
|
/// <summary> Container for common options for radial menu button. </summary>
|
||||||
|
public abstract class RadialMenuOptionBase
|
||||||
{
|
{
|
||||||
|
/// <summary> Tooltip to be displayed when button is hovered. </summary>
|
||||||
|
public string? ToolTip { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Color for button background.
|
||||||
|
/// Is used only with sector radial (<see cref="SimpleRadialMenuSettings.UseSectors"/>).
|
||||||
|
/// </summary>
|
||||||
|
public Color? BackgroundColor { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Color for button background when it is hovered.
|
||||||
|
/// Is used only with sector radial (<see cref="SimpleRadialMenuSettings.UseSectors"/>).
|
||||||
|
/// </summary>
|
||||||
|
public Color? HoverBackgroundColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifier that describes icon to be used for radial menu button.
|
||||||
|
/// </summary>
|
||||||
|
public RadialMenuIconSpecifier? IconSpecifier { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Base type for model of radial menu button with some action on button pressed. </summary>
|
||||||
|
/// <param name="onPressed"></param>
|
||||||
|
public abstract class RadialMenuActionOptionBase(Action onPressed) : RadialMenuOptionBase
|
||||||
|
{
|
||||||
|
/// <summary> Action to be executed on button press. </summary>
|
||||||
public Action OnPressed { get; } = onPressed;
|
public Action OnPressed { get; } = onPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RadialMenuActionOption<T>(Action<T> onPressed, T data)
|
/// <summary> Strong-typed model for radial menu button with action, stores provided data to be used upon button press. </summary>
|
||||||
: RadialMenuActionOption(onPressed: () => onPressed(data));
|
public sealed class RadialMenuActionOption<T>(Action<T> onPressed, T data) : RadialMenuActionOptionBase(onPressed: () => onPressed(data));
|
||||||
|
|
||||||
public sealed class RadialMenuNestedLayerOption(IReadOnlyCollection<RadialMenuOption> nested, float containerRadius = 100)
|
/// <summary>
|
||||||
: RadialMenuOption
|
/// Model for radial menu button that represents reference for next layer of radial buttons.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nested">List of button models for next layer of menu.</param>
|
||||||
|
/// <param name="containerRadius">Radius for radial menu buttons of next layer.</param>
|
||||||
|
public sealed class RadialMenuNestedLayerOption(IReadOnlyCollection<RadialMenuOptionBase> nested, float containerRadius = 100) : RadialMenuOptionBase
|
||||||
{
|
{
|
||||||
|
/// <summary> Radius for radial menu buttons of next layer. </summary>
|
||||||
public float? ContainerRadius { get; } = containerRadius;
|
public float? ContainerRadius { get; } = containerRadius;
|
||||||
|
|
||||||
public IReadOnlyCollection<RadialMenuOption> Nested { get; } = nested;
|
/// <summary> List of button models for next layer of menu. </summary>
|
||||||
|
public IReadOnlyCollection<RadialMenuOptionBase> Nested { get; } = nested;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Additional settings for radial menu render.
|
||||||
|
/// </summary>
|
||||||
public sealed class SimpleRadialMenuSettings
|
public sealed class SimpleRadialMenuSettings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -132,12 +132,12 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
_menu = null;
|
_menu = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<RadialMenuOption> ConvertToButtons(IEnumerable<EmotePrototype> emotePrototypes)
|
private IEnumerable<RadialMenuOptionBase> ConvertToButtons(IEnumerable<EmotePrototype> emotePrototypes)
|
||||||
{
|
{
|
||||||
var whitelistSystem = EntitySystemManager.GetEntitySystem<EntityWhitelistSystem>();
|
var whitelistSystem = EntitySystemManager.GetEntitySystem<EntityWhitelistSystem>();
|
||||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||||
|
|
||||||
Dictionary<EmoteCategory, List<RadialMenuOption>> emotesByCategory = new();
|
Dictionary<EmoteCategory, List<RadialMenuOptionBase>> emotesByCategory = new();
|
||||||
foreach (var emote in emotePrototypes)
|
foreach (var emote in emotePrototypes)
|
||||||
{
|
{
|
||||||
if(emote.Category == EmoteCategory.Invalid)
|
if(emote.Category == EmoteCategory.Invalid)
|
||||||
@@ -157,19 +157,19 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
|
|
||||||
if (!emotesByCategory.TryGetValue(emote.Category, out var list))
|
if (!emotesByCategory.TryGetValue(emote.Category, out var list))
|
||||||
{
|
{
|
||||||
list = new List<RadialMenuOption>();
|
list = new List<RadialMenuOptionBase>();
|
||||||
emotesByCategory.Add(emote.Category, list);
|
emotesByCategory.Add(emote.Category, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionOption = new RadialMenuActionOption<EmotePrototype>(HandleRadialButtonClick, emote)
|
var actionOption = new RadialMenuActionOption<EmotePrototype>(HandleRadialButtonClick, emote)
|
||||||
{
|
{
|
||||||
Sprite = emote.Icon,
|
IconSpecifier = RadialMenuIconSpecifier.With(emote.Icon),
|
||||||
ToolTip = Loc.GetString(emote.Name)
|
ToolTip = Loc.GetString(emote.Name)
|
||||||
};
|
};
|
||||||
list.Add(actionOption);
|
list.Add(actionOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
var models = new RadialMenuOption[emotesByCategory.Count];
|
var models = new RadialMenuOptionBase[emotesByCategory.Count];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var (key, list) in emotesByCategory)
|
foreach (var (key, list) in emotesByCategory)
|
||||||
{
|
{
|
||||||
@@ -177,7 +177,7 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
|
|
||||||
models[i] = new RadialMenuNestedLayerOption(list)
|
models[i] = new RadialMenuNestedLayerOption(list)
|
||||||
{
|
{
|
||||||
Sprite = tuple.Sprite,
|
IconSpecifier = RadialMenuIconSpecifier.With(tuple.Sprite),
|
||||||
ToolTip = Loc.GetString(tuple.Tooltip)
|
ToolTip = Loc.GetString(tuple.Tooltip)
|
||||||
};
|
};
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ namespace Content.Shared.Ghost.Roles.Components
|
|||||||
/// If this ghostrole spawner has multiple selectable ghostrole prototypes.
|
/// If this ghostrole spawner has multiple selectable ghostrole prototypes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public List<string> SelectablePrototypes = [];
|
public List<ProtoId<GhostRolePrototype>> SelectablePrototypes = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user