Merge remote-tracking branch 'space-station-14/master' into 29-04-2024-upstream
# Conflicts: # Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -11,10 +14,12 @@ namespace Content.Client.Administration.Systems
|
||||
{
|
||||
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _admin = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs);
|
||||
|
||||
}
|
||||
|
||||
private void AddAdminVerbs(GetVerbsEvent<Verb> args)
|
||||
@@ -33,6 +38,24 @@ namespace Content.Client.Administration.Systems
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
if (!_admin.IsAdmin(args.User))
|
||||
return;
|
||||
|
||||
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
|
||||
args.ExtraCategories.Add(VerbCategory.Admin);
|
||||
|
||||
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun) && HasComp<MindContainerComponent>(args.Target))
|
||||
args.ExtraCategories.Add(VerbCategory.Antag);
|
||||
|
||||
if (_admin.HasAdminFlag(args.User, AdminFlags.Debug))
|
||||
args.ExtraCategories.Add(VerbCategory.Debug);
|
||||
|
||||
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun))
|
||||
args.ExtraCategories.Add(VerbCategory.Smite);
|
||||
|
||||
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
|
||||
args.ExtraCategories.Add(VerbCategory.Tricks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
Content.Client/Chat/UI/EmotesMenu.xaml
Normal file
31
Content.Client/Chat/UI/EmotesMenu.xaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
BackButtonStyleClass="RadialMenuBackButton"
|
||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="450 450">
|
||||
|
||||
<!-- Main -->
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Actions/scream.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
</ui:RadialContainer>
|
||||
|
||||
<!-- General -->
|
||||
<ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
<!-- Vocal -->
|
||||
<ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
<!-- Hands -->
|
||||
<ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
</ui:RadialMenu>
|
||||
112
Content.Client/Chat/UI/EmotesMenu.xaml.cs
Normal file
112
Content.Client/Chat/UI/EmotesMenu.xaml.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Chat.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class EmotesMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly EntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
|
||||
|
||||
public EmotesMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
|
||||
var emotes = _prototypeManager.EnumeratePrototypes<EmotePrototype>();
|
||||
foreach (var emote in emotes)
|
||||
{
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
if (emote.Category == EmoteCategory.Invalid ||
|
||||
emote.ChatTriggers.Count == 0 ||
|
||||
!(player.HasValue && (emote.Whitelist?.IsValid(player.Value, _entManager) ?? true)) ||
|
||||
(emote.Blacklist?.IsValid(player.Value, _entManager) ?? false))
|
||||
continue;
|
||||
|
||||
if (!emote.Available &&
|
||||
_entManager.TryGetComponent<SpeechComponent>(player.Value, out var speech) &&
|
||||
!speech.AllowedEmotes.Contains(emote.ID))
|
||||
continue;
|
||||
|
||||
var parent = FindControl<RadialContainer>(emote.Category.ToString());
|
||||
|
||||
var button = new EmoteMenuButton
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = Loc.GetString(emote.Name),
|
||||
ProtoId = emote.ID,
|
||||
};
|
||||
|
||||
var tex = new TextureRect
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Texture = _spriteSystem.Frame0(emote.Icon),
|
||||
TextureScale = new Vector2(2f, 2f),
|
||||
};
|
||||
|
||||
button.AddChild(tex);
|
||||
parent.AddChild(button);
|
||||
foreach (var child in main.Children)
|
||||
{
|
||||
if (child is not RadialMenuTextureButton castChild)
|
||||
continue;
|
||||
|
||||
if (castChild.TargetLayer == emote.Category.ToString())
|
||||
{
|
||||
castChild.Visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set up menu actions
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child is not RadialContainer container)
|
||||
continue;
|
||||
AddEmoteClickAction(container);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddEmoteClickAction(RadialContainer container)
|
||||
{
|
||||
foreach (var child in container.Children)
|
||||
{
|
||||
if (child is not EmoteMenuButton castChild)
|
||||
continue;
|
||||
|
||||
castChild.OnButtonUp += _ =>
|
||||
{
|
||||
OnPlayEmote?.Invoke(castChild.ProtoId);
|
||||
Close();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class EmoteMenuButton : RadialMenuTextureButton
|
||||
{
|
||||
public ProtoId<EmotePrototype> ProtoId { get; set; }
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public sealed class EyeLerpingSystem : EntitySystem
|
||||
private void HandleMapChange(EntityUid uid, LerpingEyeComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
// Is this actually a map change? If yes, stop any lerps
|
||||
if (args.OldMapId != args.Transform.MapID)
|
||||
if (args.OldMapId != args.Transform.MapUid)
|
||||
component.LastRotation = GetRotation(uid, args.Transform);
|
||||
}
|
||||
|
||||
|
||||
48
Content.Client/Fax/System/FaxVisualsSystem.cs
Normal file
48
Content.Client/Fax/System/FaxVisualsSystem.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Content.Shared.Fax.Components;
|
||||
using Content.Shared.Fax;
|
||||
using Robust.Client.Animations;
|
||||
|
||||
namespace Content.Client.Fax.System;
|
||||
|
||||
/// <summary>
|
||||
/// Visualizer for the fax machine which displays the correct sprite based on the inserted entity.
|
||||
/// </summary>
|
||||
public sealed class FaxVisualsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _player = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FaxMachineComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
}
|
||||
|
||||
private void OnAppearanceChanged(EntityUid uid, FaxMachineComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (_appearance.TryGetData(uid, FaxMachineVisuals.VisualState, out FaxMachineVisualState visuals) && visuals == FaxMachineVisualState.Inserting)
|
||||
{
|
||||
_player.Play(uid, new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(2.4),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
{
|
||||
LayerKey = FaxMachineVisuals.VisualState,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(component.InsertingState, 0f),
|
||||
new AnimationTrackSpriteFlick.KeyFrame("icon", 2.4f),
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "faxecute");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public sealed class FaxBoundUi : BoundUserInterface
|
||||
{
|
||||
if (_dialogIsOpen)
|
||||
return;
|
||||
|
||||
|
||||
_dialogIsOpen = true;
|
||||
var filters = new FileDialogFilters(new FileDialogFilters.Group("txt"));
|
||||
await using var file = await _fileDialogManager.OpenFile(filters);
|
||||
@@ -52,8 +52,27 @@ public sealed class FaxBoundUi : BoundUserInterface
|
||||
}
|
||||
|
||||
using var reader = new StreamReader(file);
|
||||
|
||||
var firstLine = await reader.ReadLineAsync();
|
||||
string? label = null;
|
||||
var content = await reader.ReadToEndAsync();
|
||||
SendMessage(new FaxFileMessage(content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)], _window.OfficePaper));
|
||||
|
||||
if (firstLine is { })
|
||||
{
|
||||
if (firstLine.StartsWith('#'))
|
||||
{
|
||||
label = firstLine[1..].Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
content = firstLine + "\n" + content;
|
||||
}
|
||||
}
|
||||
|
||||
SendMessage(new FaxFileMessage(
|
||||
label?[..Math.Min(label.Length, FaxFileMessageValidation.MaxLabelSize)],
|
||||
content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)],
|
||||
_window.OfficePaper));
|
||||
}
|
||||
|
||||
private void OnSendButtonPressed()
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Client.Chemistry.EntitySystems;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using JetBrains.Annotations;
|
||||
@@ -128,7 +129,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
|
||||
var groupLabel = new RichTextLabel();
|
||||
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
|
||||
("group", group), ("rate", effect.MetabolismRate)));
|
||||
("group", _prototype.Index<MetabolismGroupPrototype>(group).LocalizedName), ("rate", effect.MetabolismRate)));
|
||||
var descriptionLabel = new RichTextLabel
|
||||
{
|
||||
Margin = new Thickness(25, 0, 10, 0)
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
|
||||
var groupTitleText = $"{Loc.GetString(
|
||||
"health-analyzer-window-damage-group-text",
|
||||
("damageGroup", Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId)),
|
||||
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
|
||||
("amount", damageAmount)
|
||||
)}";
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
|
||||
var damageString = Loc.GetString(
|
||||
"health-analyzer-window-damage-type-text",
|
||||
("damageType", Loc.GetString("health-analyzer-window-damage-type-" + type)),
|
||||
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
|
||||
("amount", typeAmount)
|
||||
);
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Content.Client.Input
|
||||
human.AddFunction(ContentKeyFunctions.UseItemInHand);
|
||||
human.AddFunction(ContentKeyFunctions.AltUseItemInHand);
|
||||
human.AddFunction(ContentKeyFunctions.OpenCharacterMenu);
|
||||
human.AddFunction(ContentKeyFunctions.OpenEmotesMenu);
|
||||
human.AddFunction(ContentKeyFunctions.ActivateItemInWorld);
|
||||
human.AddFunction(ContentKeyFunctions.ThrowItemInHand);
|
||||
human.AddFunction(ContentKeyFunctions.AltActivateItemInWorld);
|
||||
|
||||
18
Content.Client/Labels/EntitySystems/HandLabelerSystem.cs
Normal file
18
Content.Client/Labels/EntitySystems/HandLabelerSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Client.Labels.UI;
|
||||
using Content.Shared.Labels;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
|
||||
namespace Content.Client.Labels.EntitySystems;
|
||||
|
||||
public sealed class HandLabelerSystem : SharedHandLabelerSystem
|
||||
{
|
||||
protected override void UpdateUI(Entity<HandLabelerComponent> ent)
|
||||
{
|
||||
if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, HandLabelerUiKey.Key, out var bui)
|
||||
&& bui is HandLabelerBoundUserInterface cBui)
|
||||
{
|
||||
cBui.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Labels;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Labels.UI
|
||||
@@ -8,11 +9,14 @@ namespace Content.Client.Labels.UI
|
||||
/// </summary>
|
||||
public sealed class HandLabelerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private HandLabelerWindow? _window;
|
||||
|
||||
public HandLabelerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -27,24 +31,25 @@ namespace Content.Client.Labels.UI
|
||||
|
||||
_window.OnClose += Close;
|
||||
_window.OnLabelChanged += OnLabelChanged;
|
||||
Reload();
|
||||
}
|
||||
|
||||
private void OnLabelChanged(string newLabel)
|
||||
{
|
||||
SendMessage(new HandLabelerLabelChangedMessage(newLabel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI state based on server-sent info
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not HandLabelerBoundUserInterfaceState cast)
|
||||
// Focus moment
|
||||
if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler) &&
|
||||
labeler.AssignedLabel.Equals(newLabel))
|
||||
return;
|
||||
|
||||
_window.SetCurrentLabel(cast.CurrentLabel);
|
||||
SendPredictedMessage(new HandLabelerLabelChangedMessage(newLabel));
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_window == null || !_entManager.TryGetComponent(Owner, out HandLabelerComponent? component))
|
||||
return;
|
||||
|
||||
_window.SetCurrentLabel(component.AssignedLabel);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -9,17 +9,40 @@ namespace Content.Client.Labels.UI
|
||||
{
|
||||
public event Action<string>? OnLabelChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Is the user currently entering text into the control?
|
||||
/// </summary>
|
||||
private bool _focused;
|
||||
// TODO LineEdit Make this a bool on the LineEdit control
|
||||
|
||||
private string _label = string.Empty;
|
||||
|
||||
public HandLabelerWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
LabelLineEdit.OnTextEntered += e => OnLabelChanged?.Invoke(e.Text);
|
||||
LabelLineEdit.OnFocusExit += e => OnLabelChanged?.Invoke(e.Text);
|
||||
LabelLineEdit.OnTextEntered += e =>
|
||||
{
|
||||
_label = e.Text;
|
||||
OnLabelChanged?.Invoke(_label);
|
||||
};
|
||||
|
||||
LabelLineEdit.OnFocusEnter += _ => _focused = true;
|
||||
LabelLineEdit.OnFocusExit += _ =>
|
||||
{
|
||||
_focused = false;
|
||||
LabelLineEdit.Text = _label;
|
||||
};
|
||||
}
|
||||
|
||||
public void SetCurrentLabel(string label)
|
||||
{
|
||||
LabelLineEdit.Text = label;
|
||||
if (label == _label)
|
||||
return;
|
||||
|
||||
_label = label;
|
||||
if (!_focused)
|
||||
LabelLineEdit.Text = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lathe.UI;
|
||||
@@ -19,6 +21,8 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resources = default!;
|
||||
|
||||
private EntityUid _owner;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly LatheSystem _lathe;
|
||||
@@ -104,12 +108,21 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
RecipeList.Children.Clear();
|
||||
foreach (var prototype in sortedRecipesToShow)
|
||||
{
|
||||
var icon = prototype.Icon == null
|
||||
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
|
||||
: _spriteSystem.Frame0(prototype.Icon);
|
||||
List<Texture> textures;
|
||||
if (_prototypeManager.TryIndex(prototype.Result, out EntityPrototype? entityProto) && entityProto != null)
|
||||
{
|
||||
textures = SpriteComponent.GetPrototypeTextures(entityProto, _resources).Select(o => o.Default).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
textures = prototype.Icon == null
|
||||
? new List<Texture> { _spriteSystem.GetPrototypeIcon(prototype.Result).Default }
|
||||
: new List<Texture> { _spriteSystem.Frame0(prototype.Icon) };
|
||||
}
|
||||
|
||||
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
|
||||
|
||||
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, icon);
|
||||
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, textures);
|
||||
control.OnButtonPressed += s =>
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||
|
||||
@@ -5,11 +5,15 @@
|
||||
Margin="0"
|
||||
StyleClasses="ButtonSquare">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<TextureRect
|
||||
Name="RecipeTexture"
|
||||
<LayeredTextureRect
|
||||
Name="RecipeTextures"
|
||||
Margin="0 0 4 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="KeepAspectCentered"
|
||||
MinSize="32 32"
|
||||
Stretch="KeepAspectCentered" />
|
||||
CanShrink="true"
|
||||
/>
|
||||
<Label Name="RecipeName" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
|
||||
@@ -2,8 +2,8 @@ using Content.Shared.Research.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Content.Client.Lathe.UI;
|
||||
|
||||
@@ -13,12 +13,12 @@ public sealed partial class RecipeControl : Control
|
||||
public Action<string>? OnButtonPressed;
|
||||
public Func<string> TooltipTextSupplier;
|
||||
|
||||
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Texture? texture = null)
|
||||
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, List<Texture> textures)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecipeName.Text = recipe.Name;
|
||||
RecipeTexture.Texture = texture;
|
||||
RecipeTextures.Textures = textures;
|
||||
Button.Disabled = !canProduce;
|
||||
TooltipTextSupplier = tooltipTextSupplier;
|
||||
Button.TooltipSupplier = SupplyTooltip;
|
||||
|
||||
@@ -195,7 +195,7 @@ public sealed partial class ReplaySpectatorSystem
|
||||
if (uid != _player.LocalEntity)
|
||||
return;
|
||||
|
||||
if (args.Transform.MapUid != null || args.OldMapId == MapId.Nullspace)
|
||||
if (args.Transform.MapUid != null || args.OldMapId == null)
|
||||
return;
|
||||
|
||||
if (_spectatorData != null)
|
||||
|
||||
11
Content.Client/Tips/TippyUI.xaml
Normal file
11
Content.Client/Tips/TippyUI.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<tips:TippyUI xmlns="https://spacestation14.io"
|
||||
xmlns:tips="clr-namespace:Content.Client.Tips"
|
||||
MinSize="64 64"
|
||||
Visible="False">
|
||||
<PanelContainer Name="LabelPanel" Access="Public" Visible="False" MaxWidth="300" MaxHeight="200">
|
||||
<ScrollContainer Name="ScrollingContents" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="False" ReturnMeasure="True">
|
||||
<RichTextLabel Name="Label" Access="Public"/>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<SpriteView Name="Entity" Access="Public" MinSize="128 128"/>
|
||||
</tips:TippyUI>
|
||||
54
Content.Client/Tips/TippyUI.xaml.cs
Normal file
54
Content.Client/Tips/TippyUI.xaml.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Client.Paper;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TippyUI : UIWidget
|
||||
{
|
||||
public TippyState State = TippyState.Hidden;
|
||||
public bool ModifyLayers = true;
|
||||
|
||||
public TippyUI()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void InitLabel(PaperVisualsComponent? visuals, IResourceCache resCache)
|
||||
{
|
||||
if (visuals == null)
|
||||
return;
|
||||
|
||||
Label.ModulateSelfOverride = visuals.FontAccentColor;
|
||||
|
||||
if (visuals.BackgroundImagePath == null)
|
||||
return;
|
||||
|
||||
LabelPanel.ModulateSelfOverride = visuals.BackgroundModulate;
|
||||
var backgroundImage = resCache.GetResource<TextureResource>(visuals.BackgroundImagePath);
|
||||
var backgroundImageMode = visuals.BackgroundImageTile ? StyleBoxTexture.StretchMode.Tile : StyleBoxTexture.StretchMode.Stretch;
|
||||
var backgroundPatchMargin = visuals.BackgroundPatchMargin;
|
||||
LabelPanel.PanelOverride = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundImage,
|
||||
TextureScale = visuals.BackgroundScale,
|
||||
Mode = backgroundImageMode,
|
||||
PatchMarginLeft = backgroundPatchMargin.Left,
|
||||
PatchMarginBottom = backgroundPatchMargin.Bottom,
|
||||
PatchMarginRight = backgroundPatchMargin.Right,
|
||||
PatchMarginTop = backgroundPatchMargin.Top
|
||||
};
|
||||
}
|
||||
|
||||
public enum TippyState : byte
|
||||
{
|
||||
Hidden,
|
||||
Revealing,
|
||||
Speaking,
|
||||
Hiding,
|
||||
}
|
||||
}
|
||||
241
Content.Client/Tips/TippyUIController.cs
Normal file
241
Content.Client/Tips/TippyUIController.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using Content.Client.Gameplay;
|
||||
using System.Numerics;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Paper;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Client.Tips.TippyUI;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
public sealed class TippyUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IResourceCache _resCache = default!;
|
||||
[UISystemDependency] private readonly AudioSystem _audio = default!;
|
||||
|
||||
public const float Padding = 50;
|
||||
public static Angle WaddleRotation = Angle.FromDegrees(10);
|
||||
|
||||
private EntityUid _entity;
|
||||
private float _secondsUntilNextState;
|
||||
private int _previousStep = 0;
|
||||
private TippyEvent? _currentMessage;
|
||||
private readonly Queue<TippyEvent> _queuedMessages = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UIManager.OnScreenChanged += OnScreenChanged;
|
||||
SubscribeNetworkEvent<TippyEvent>(OnTippyEvent);
|
||||
}
|
||||
|
||||
private void OnTippyEvent(TippyEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
_queuedMessages.Enqueue(msg);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var screen = UIManager.ActiveScreen;
|
||||
if (screen == null)
|
||||
{
|
||||
_queuedMessages.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var tippy = screen.GetOrAddWidget<TippyUI>();
|
||||
_secondsUntilNextState -= args.DeltaSeconds;
|
||||
|
||||
if (_secondsUntilNextState <= 0)
|
||||
NextState(tippy);
|
||||
else
|
||||
{
|
||||
var pos = UpdatePosition(tippy, screen.Size, args); ;
|
||||
LayoutContainer.SetPosition(tippy, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 UpdatePosition(TippyUI tippy, Vector2 screenSize, FrameEventArgs args)
|
||||
{
|
||||
if (_currentMessage == null)
|
||||
return default;
|
||||
|
||||
var slideTime = _currentMessage.SlideTime;
|
||||
|
||||
var offset = tippy.State switch
|
||||
{
|
||||
TippyState.Hidden => 0,
|
||||
TippyState.Revealing => Math.Clamp(1 - _secondsUntilNextState / slideTime, 0, 1),
|
||||
TippyState.Hiding => Math.Clamp(_secondsUntilNextState / slideTime, 0, 1),
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
var waddle = _currentMessage.WaddleInterval;
|
||||
|
||||
if (_currentMessage == null
|
||||
|| waddle <= 0
|
||||
|| tippy.State == TippyState.Hidden
|
||||
|| tippy.State == TippyState.Speaking
|
||||
|| !EntityManager.TryGetComponent(_entity, out SpriteComponent? sprite))
|
||||
{
|
||||
return new Vector2(screenSize.X - offset * (tippy.DesiredSize.X + Padding), (screenSize.Y - tippy.DesiredSize.Y) / 2);
|
||||
}
|
||||
|
||||
var numSteps = (int) Math.Ceiling(slideTime / waddle);
|
||||
var curStep = (int) Math.Floor(numSteps * offset);
|
||||
var stepSize = (tippy.DesiredSize.X + Padding) / numSteps;
|
||||
|
||||
if (curStep != _previousStep)
|
||||
{
|
||||
_previousStep = curStep;
|
||||
sprite.Rotation = sprite.Rotation > 0
|
||||
? -WaddleRotation
|
||||
: WaddleRotation;
|
||||
|
||||
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step))
|
||||
{
|
||||
var audioParams = step.FootstepSoundCollection.Params
|
||||
.AddVolume(-7f)
|
||||
.WithVariation(0.1f);
|
||||
_audio.PlayGlobal(step.FootstepSoundCollection, EntityUid.Invalid, audioParams);
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector2(screenSize.X - stepSize * curStep, (screenSize.Y - tippy.DesiredSize.Y) / 2);
|
||||
}
|
||||
|
||||
private void NextState(TippyUI tippy)
|
||||
{
|
||||
SpriteComponent? sprite;
|
||||
switch (tippy.State)
|
||||
{
|
||||
case TippyState.Hidden:
|
||||
if (!_queuedMessages.TryDequeue(out var next))
|
||||
return;
|
||||
|
||||
if (next.Proto != null)
|
||||
{
|
||||
_entity = EntityManager.SpawnEntity(next.Proto, MapCoordinates.Nullspace);
|
||||
tippy.ModifyLayers = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_entity = EntityManager.SpawnEntity(_cfg.GetCVar(CCVars.TippyEntity), MapCoordinates.Nullspace);
|
||||
tippy.ModifyLayers = true;
|
||||
}
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
if (!EntityManager.HasComponent<PaperVisualsComponent>(_entity))
|
||||
{
|
||||
var paper = EntityManager.AddComponent<PaperVisualsComponent>(_entity);
|
||||
paper.BackgroundImagePath = "/Textures/Interface/Paper/paper_background_default.svg.96dpi.png";
|
||||
paper.BackgroundPatchMargin = new(16f, 16f, 16f, 16f);
|
||||
paper.BackgroundModulate = new(255, 255, 204);
|
||||
paper.FontAccentColor = new(0, 0, 0);
|
||||
}
|
||||
tippy.InitLabel(EntityManager.GetComponentOrNull<PaperVisualsComponent>(_entity), _resCache);
|
||||
|
||||
var scale = sprite.Scale;
|
||||
if (tippy.ModifyLayers)
|
||||
{
|
||||
sprite.Scale = Vector2.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.Scale = new Vector2(3, 3);
|
||||
}
|
||||
tippy.Entity.SetEntity(_entity);
|
||||
tippy.Entity.Scale = scale;
|
||||
|
||||
_currentMessage = next;
|
||||
_secondsUntilNextState = next.SlideTime;
|
||||
tippy.State = TippyState.Revealing;
|
||||
_previousStep = 0;
|
||||
if (tippy.ModifyLayers)
|
||||
{
|
||||
sprite.LayerSetAnimationTime("revealing", 0);
|
||||
sprite.LayerSetVisible("revealing", true);
|
||||
sprite.LayerSetVisible("speaking", false);
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
}
|
||||
sprite.Rotation = 0;
|
||||
tippy.Label.SetMarkup(_currentMessage.Msg);
|
||||
tippy.Label.Visible = false;
|
||||
tippy.LabelPanel.Visible = false;
|
||||
tippy.Visible = true;
|
||||
sprite.Visible = true;
|
||||
break;
|
||||
|
||||
case TippyState.Revealing:
|
||||
tippy.State = TippyState.Speaking;
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
sprite.Rotation = 0;
|
||||
_previousStep = 0;
|
||||
if (tippy.ModifyLayers)
|
||||
{
|
||||
sprite.LayerSetAnimationTime("speaking", 0);
|
||||
sprite.LayerSetVisible("revealing", false);
|
||||
sprite.LayerSetVisible("speaking", true);
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
}
|
||||
tippy.Label.Visible = true;
|
||||
tippy.LabelPanel.Visible = true;
|
||||
tippy.InvalidateArrange();
|
||||
tippy.InvalidateMeasure();
|
||||
if (_currentMessage != null)
|
||||
_secondsUntilNextState = _currentMessage.SpeakTime;
|
||||
|
||||
break;
|
||||
|
||||
case TippyState.Speaking:
|
||||
tippy.State = TippyState.Hiding;
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
if (tippy.ModifyLayers)
|
||||
{
|
||||
sprite.LayerSetAnimationTime("hiding", 0);
|
||||
sprite.LayerSetVisible("revealing", false);
|
||||
sprite.LayerSetVisible("speaking", false);
|
||||
sprite.LayerSetVisible("hiding", true);
|
||||
}
|
||||
tippy.LabelPanel.Visible = false;
|
||||
if (_currentMessage != null)
|
||||
_secondsUntilNextState = _currentMessage.SlideTime;
|
||||
break;
|
||||
|
||||
default: // finished hiding
|
||||
|
||||
EntityManager.DeleteEntity(_entity);
|
||||
_entity = default;
|
||||
tippy.Visible = false;
|
||||
_currentMessage = null;
|
||||
_secondsUntilNextState = 0;
|
||||
tippy.State = TippyState.Hidden;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScreenChanged((UIScreen? Old, UIScreen? New) ev)
|
||||
{
|
||||
ev.Old?.RemoveWidget<TippyUI>();
|
||||
_currentMessage = null;
|
||||
EntityManager.DeleteEntity(_entity);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Actions;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
@@ -42,6 +43,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
|
||||
[UISystemDependency] private readonly ActionsSystem? _actionsSystem = default;
|
||||
[UISystemDependency] private readonly InteractionOutlineSystem? _interactionOutline = default;
|
||||
@@ -356,6 +358,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
QueueWindowUpdate();
|
||||
|
||||
// TODO ACTIONS allow buttons to persist across state applications
|
||||
// Then we don't have to interrupt drags any time the buttons get rebuilt.
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (_actionsSystem != null)
|
||||
_container?.SetActionData(_actionsSystem, _actions.ToArray());
|
||||
}
|
||||
@@ -516,7 +522,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
button.ClearData();
|
||||
if (_container?.TryGetButtonIndex(button, out position) ?? false)
|
||||
{
|
||||
_actions.RemoveAt(position);
|
||||
if (_actions.Count > position && position >= 0)
|
||||
_actions.RemoveAt(position);
|
||||
}
|
||||
}
|
||||
else if (button.TryReplaceWith(actionId.Value, _actionsSystem) &&
|
||||
@@ -539,23 +546,22 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void DragAction()
|
||||
{
|
||||
if (_menuDragHelper.Dragged is not {ActionId: {} action} dragged)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
EntityUid? swapAction = null;
|
||||
if (UIManager.CurrentlyHovered is ActionButton button)
|
||||
var currentlyHovered = UIManager.MouseGetControl(_input.MouseScreenPosition);
|
||||
if (currentlyHovered is ActionButton button)
|
||||
{
|
||||
if (!_menuDragHelper.IsDragging || _menuDragHelper.Dragged?.ActionId is not { } type)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
swapAction = button.ActionId;
|
||||
SetAction(button, type, false);
|
||||
SetAction(button, action, false);
|
||||
}
|
||||
|
||||
if (_menuDragHelper.Dragged is {Parent: ActionButtonContainer} old)
|
||||
{
|
||||
SetAction(old, swapAction, false);
|
||||
}
|
||||
if (dragged.Parent is ActionButtonContainer)
|
||||
SetAction(dragged, swapAction, false);
|
||||
|
||||
if (_actionsSystem != null)
|
||||
_container?.SetActionData(_actionsSystem, _actions.ToArray());
|
||||
@@ -610,27 +616,27 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
|
||||
private void OnActionPressed(GUIBoundKeyEventArgs args, ActionButton button)
|
||||
{
|
||||
if (args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
if (button.ActionId == null)
|
||||
{
|
||||
var ev = new FillActionSlotEvent();
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
|
||||
if (ev.Action != null)
|
||||
SetAction(button, ev.Action);
|
||||
}
|
||||
else
|
||||
{
|
||||
_menuDragHelper.MouseDown(button);
|
||||
}
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||
if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||
{
|
||||
SetAction(button, null);
|
||||
args.Handle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
args.Handle();
|
||||
if (button.ActionId != null)
|
||||
{
|
||||
_menuDragHelper.MouseDown(button);
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new FillActionSlotEvent();
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
|
||||
if (ev.Action != null)
|
||||
SetAction(button, ev.Action);
|
||||
}
|
||||
|
||||
private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button)
|
||||
@@ -638,33 +644,31 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
if (args.Function != EngineKeyFunctions.UIClick || _actionsSystem == null)
|
||||
return;
|
||||
|
||||
//todo: make dragging onto the same spot NOT trigger again
|
||||
if (UIManager.CurrentlyHovered == button)
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
args.Handle();
|
||||
|
||||
if (_actionsSystem.TryGetActionData(button.ActionId, out var baseAction))
|
||||
{
|
||||
if (baseAction is BaseTargetActionComponent action)
|
||||
{
|
||||
// for target actions, we go into "select target" mode, we don't
|
||||
// message the server until we actually pick our target.
|
||||
|
||||
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||
// targeting
|
||||
ToggleTargeting(button.ActionId.Value, action);
|
||||
return;
|
||||
}
|
||||
|
||||
_actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (_menuDragHelper.IsDragging)
|
||||
{
|
||||
DragAction();
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handle();
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (!_actionsSystem.TryGetActionData(button.ActionId, out var baseAction))
|
||||
return;
|
||||
|
||||
if (baseAction is not BaseTargetActionComponent action)
|
||||
{
|
||||
_actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
|
||||
return;
|
||||
}
|
||||
|
||||
// for target actions, we go into "select target" mode, we don't
|
||||
// message the server until we actually pick our target.
|
||||
|
||||
// if we're clicking the same thing we're already targeting for, then we simply cancel
|
||||
// targeting
|
||||
ToggleTargeting(button.ActionId.Value, action);
|
||||
}
|
||||
|
||||
private bool OnMenuBeginDrag()
|
||||
|
||||
@@ -152,16 +152,8 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
|
||||
OnThemeUpdated();
|
||||
|
||||
OnKeyBindDown += args =>
|
||||
{
|
||||
Depress(args, true);
|
||||
OnPressed(args);
|
||||
};
|
||||
OnKeyBindUp += args =>
|
||||
{
|
||||
Depress(args, false);
|
||||
OnUnpressed(args);
|
||||
};
|
||||
OnKeyBindDown += OnPressed;
|
||||
OnKeyBindUp += OnUnpressed;
|
||||
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
}
|
||||
@@ -175,11 +167,23 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
|
||||
private void OnPressed(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick)
|
||||
return;
|
||||
|
||||
if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||
Depress(args, true);
|
||||
|
||||
ActionPressed?.Invoke(args, this);
|
||||
}
|
||||
|
||||
private void OnUnpressed(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick)
|
||||
return;
|
||||
|
||||
if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||
Depress(args, false);
|
||||
|
||||
ActionUnpressed?.Invoke(args, this);
|
||||
}
|
||||
|
||||
@@ -378,12 +382,6 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
if (_action is not {Enabled: true})
|
||||
return;
|
||||
|
||||
if (_depressed && !depress)
|
||||
{
|
||||
// fire the action
|
||||
OnUnpressed(args);
|
||||
}
|
||||
|
||||
_depressed = depress;
|
||||
DrawModeChanged();
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ public sealed class ChatUIController : UIController
|
||||
}
|
||||
|
||||
// only admins can see / filter asay
|
||||
if (_admin.HasFlag(AdminFlags.Admin))
|
||||
if (_admin.HasFlag(AdminFlags.Adminchat))
|
||||
{
|
||||
FilterableChannels |= ChatChannel.Admin;
|
||||
FilterableChannels |= ChatChannel.AdminAlert;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
using Content.Client.Chat.UI;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Emotes;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayState>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
private MenuButton? EmotesButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.EmotesButton;
|
||||
private EmotesMenu? _menu;
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenEmotesMenu,
|
||||
InputCmdHandler.FromDelegate(_ => ToggleEmotesMenu(false)))
|
||||
.Register<EmotesUIController>();
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
CommandBinds.Unregister<EmotesUIController>();
|
||||
}
|
||||
|
||||
private void ToggleEmotesMenu(bool centered)
|
||||
{
|
||||
if (_menu == null)
|
||||
{
|
||||
// setup window
|
||||
_menu = UIManager.CreateWindow<EmotesMenu>();
|
||||
_menu.OnClose += OnWindowClosed;
|
||||
_menu.OnOpen += OnWindowOpen;
|
||||
_menu.OnPlayEmote += OnPlayEmote;
|
||||
|
||||
if (EmotesButton != null)
|
||||
EmotesButton.SetClickPressed(true);
|
||||
|
||||
if (centered)
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open the menu, centered on the mouse
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_menu.OnClose -= OnWindowClosed;
|
||||
_menu.OnOpen -= OnWindowOpen;
|
||||
_menu.OnPlayEmote -= OnPlayEmote;
|
||||
|
||||
if (EmotesButton != null)
|
||||
EmotesButton.SetClickPressed(false);
|
||||
|
||||
CloseMenu();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadButton()
|
||||
{
|
||||
if (EmotesButton == null)
|
||||
return;
|
||||
|
||||
EmotesButton.OnPressed -= ActionButtonPressed;
|
||||
}
|
||||
|
||||
public void LoadButton()
|
||||
{
|
||||
if (EmotesButton == null)
|
||||
return;
|
||||
|
||||
EmotesButton.OnPressed += ActionButtonPressed;
|
||||
}
|
||||
|
||||
private void ActionButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
ToggleEmotesMenu(true);
|
||||
}
|
||||
|
||||
private void OnWindowClosed()
|
||||
{
|
||||
if (EmotesButton != null)
|
||||
EmotesButton.Pressed = false;
|
||||
|
||||
CloseMenu();
|
||||
}
|
||||
|
||||
private void OnWindowOpen()
|
||||
{
|
||||
if (EmotesButton != null)
|
||||
EmotesButton.Pressed = true;
|
||||
}
|
||||
|
||||
private void CloseMenu()
|
||||
{
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.Dispose();
|
||||
_menu = null;
|
||||
}
|
||||
|
||||
private void OnPlayEmote(ProtoId<EmotePrototype> protoId)
|
||||
{
|
||||
_entityManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Content.Client.UserInterface.Systems.Admin;
|
||||
using Content.Client.UserInterface.Systems.Bwoink;
|
||||
using Content.Client.UserInterface.Systems.Character;
|
||||
using Content.Client.UserInterface.Systems.Crafting;
|
||||
using Content.Client.UserInterface.Systems.Emotes;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
@@ -22,6 +23,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
[Dependency] private readonly ActionUIController _action = default!;
|
||||
[Dependency] private readonly SandboxUIController _sandbox = default!;
|
||||
[Dependency] private readonly GuidebookUIController _guidebook = default!;
|
||||
[Dependency] private readonly EmotesUIController _emotes = default!;
|
||||
|
||||
private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull<GameTopMenuBar>();
|
||||
|
||||
@@ -44,6 +46,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
_ahelp.UnloadButton();
|
||||
_action.UnloadButton();
|
||||
_sandbox.UnloadButton();
|
||||
_emotes.UnloadButton();
|
||||
}
|
||||
|
||||
public void LoadButtons()
|
||||
@@ -56,5 +59,6 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
_ahelp.LoadButton();
|
||||
_action.LoadButton();
|
||||
_sandbox.LoadButton();
|
||||
_emotes.LoadButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,16 @@
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="EmotesButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/Interface/emotes.svg.192dpi.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-emotes-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenEmotesMenu}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="CraftingButton"
|
||||
Access="Internal"
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Content.Client.Verbs.UI
|
||||
|
||||
public NetEntity CurrentTarget;
|
||||
public SortedSet<Verb> CurrentVerbs = new();
|
||||
public List<VerbCategory> ExtraCategories = new();
|
||||
|
||||
/// <summary>
|
||||
/// Separate from <see cref="ContextMenuUIController.RootMenu"/>, since we can open a verb menu as a submenu
|
||||
@@ -91,19 +92,12 @@ namespace Content.Client.Verbs.UI
|
||||
menu.MenuBody.DisposeAllChildren();
|
||||
|
||||
CurrentTarget = target;
|
||||
CurrentVerbs = _verbSystem.GetVerbs(target, user, Verb.VerbTypes, force);
|
||||
CurrentVerbs = _verbSystem.GetVerbs(target, user, Verb.VerbTypes, out ExtraCategories, force);
|
||||
OpenMenu = menu;
|
||||
|
||||
// Fill in client-side verbs.
|
||||
FillVerbPopup(menu);
|
||||
|
||||
// Add indicator that some verbs may be missing.
|
||||
// I long for the day when verbs will all be predicted and this becomes unnecessary.
|
||||
if (!target.IsClientSide())
|
||||
{
|
||||
_context.AddElement(menu, new ContextMenuElement(Loc.GetString("verb-system-waiting-on-server-text")));
|
||||
}
|
||||
|
||||
// if popup isn't null (ie we are opening out of an entity menu element),
|
||||
// assume that that is going to handle opening the submenu properly
|
||||
if (popup != null)
|
||||
@@ -128,11 +122,19 @@ namespace Content.Client.Verbs.UI
|
||||
var element = new VerbMenuElement(verb);
|
||||
_context.AddElement(popup, element);
|
||||
}
|
||||
|
||||
else if (listedCategories.Add(verb.Category.Text))
|
||||
AddVerbCategory(verb.Category, popup);
|
||||
}
|
||||
|
||||
if (ExtraCategories != null)
|
||||
{
|
||||
foreach (var category in ExtraCategories)
|
||||
{
|
||||
if (listedCategories.Add(category.Text))
|
||||
AddVerbCategory(category, popup);
|
||||
}
|
||||
}
|
||||
|
||||
popup.InvalidateMeasure();
|
||||
}
|
||||
|
||||
@@ -153,10 +155,11 @@ namespace Content.Client.Verbs.UI
|
||||
}
|
||||
}
|
||||
|
||||
if (verbsInCategory.Count == 0)
|
||||
if (verbsInCategory.Count == 0 && !ExtraCategories.Contains(category))
|
||||
return;
|
||||
|
||||
var element = new VerbMenuElement(category, verbsInCategory[0].TextStyleClass);
|
||||
var style = verbsInCategory.FirstOrDefault()?.TextStyleClass ?? Verb.DefaultTextStyleClass;
|
||||
var element = new VerbMenuElement(category, style);
|
||||
_context.AddElement(popup, element);
|
||||
|
||||
// Create the pop-up that appears when hovering over this element
|
||||
|
||||
@@ -165,29 +165,23 @@ namespace Content.Client.Verbs
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the server to send back a list of server-side verbs, for the given verb type.
|
||||
/// </summary>
|
||||
public SortedSet<Verb> GetVerbs(EntityUid target, EntityUid user, Type type, bool force = false)
|
||||
{
|
||||
return GetVerbs(GetNetEntity(target), user, new List<Type>() { type }, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ask the server to send back a list of server-side verbs, and for now return an incomplete list of verbs
|
||||
/// (only those defined locally).
|
||||
/// </summary>
|
||||
public SortedSet<Verb> GetVerbs(NetEntity target, EntityUid user, List<Type> verbTypes,
|
||||
bool force = false)
|
||||
public SortedSet<Verb> GetVerbs(NetEntity target, EntityUid user, List<Type> verbTypes, out List<VerbCategory> extraCategories, bool force = false)
|
||||
{
|
||||
if (!target.IsClientSide())
|
||||
RaiseNetworkEvent(new RequestServerVerbsEvent(target, verbTypes, adminRequest: force));
|
||||
|
||||
// Some admin menu interactions will try get verbs for entities that have not yet been sent to the player.
|
||||
if (!TryGetEntity(target, out var local))
|
||||
{
|
||||
extraCategories = new();
|
||||
return new();
|
||||
}
|
||||
|
||||
return GetLocalVerbs(local.Value, user, verbTypes, force);
|
||||
return GetLocalVerbs(local.Value, user, verbTypes, out extraCategories, force);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -78,17 +78,44 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
else
|
||||
UpBiasButton.Pressed = true;
|
||||
|
||||
var disabled = !state.ServerConnected || !state.CanScan || state.PointAmount <= 0;
|
||||
ExtractButton.Disabled = false;
|
||||
if (!state.ServerConnected)
|
||||
{
|
||||
ExtractButton.Disabled = true;
|
||||
ExtractButton.ToolTip = Loc.GetString("analysis-console-no-server-connected");
|
||||
}
|
||||
else if (!state.CanScan)
|
||||
{
|
||||
ExtractButton.Disabled = true;
|
||||
|
||||
ExtractButton.Disabled = disabled;
|
||||
// CanScan can be false if either there's no analyzer connected or if there's
|
||||
// no entity on the scanner. The `Information` text will always tell the user
|
||||
// of the former case, but in the latter, it'll only show a message if a scan
|
||||
// has never been performed, so add a tooltip to indicate that the artifact
|
||||
// is gone.
|
||||
if (state.AnalyzerConnected)
|
||||
{
|
||||
ExtractButton.ToolTip = Loc.GetString("analysis-console-no-artifact-placed");
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtractButton.ToolTip = null;
|
||||
}
|
||||
}
|
||||
else if (state.PointAmount <= 0)
|
||||
{
|
||||
ExtractButton.Disabled = true;
|
||||
ExtractButton.ToolTip = Loc.GetString("analysis-console-no-points-to-extract");
|
||||
}
|
||||
|
||||
if (disabled)
|
||||
if (ExtractButton.Disabled)
|
||||
{
|
||||
ExtractButton.RemoveStyleClass("ButtonColorGreen");
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtractButton.AddStyleClass("ButtonColorGreen");
|
||||
ExtractButton.ToolTip = null;
|
||||
}
|
||||
}
|
||||
private void UpdateArtifactIcon(EntityUid? uid)
|
||||
|
||||
Reference in New Issue
Block a user