Skip to content

Commit

Permalink
Complete rework of verbs (#1633)
Browse files Browse the repository at this point in the history
  • Loading branch information
wixoaGit authored Jan 27, 2024
1 parent 9c46754 commit fe6c1a8
Show file tree
Hide file tree
Showing 38 changed files with 1,066 additions and 442 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/mob/proc/test()
return

/proc/RunTest()
/proc/test_verb_duplicate()
var/mob/m = new
m.verbs += /mob/proc/test
m.verbs += /mob/proc/test
Expand Down
1 change: 1 addition & 0 deletions Content.IntegrationTests/DMProject/code.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
test_block()
test_color_matrix()
test_range()
test_verb_duplicate()
world.log << "IntegrationTests successful, /world/New() exiting..."
1 change: 1 addition & 0 deletions Content.IntegrationTests/DMProject/environment.dme
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
#include "Tests/block.dm"
#include "Tests/color_matrix.dm"
#include "Tests/range.dm"
#include "Tests/verb_duplicate.dm"
#include "map.dmm"
#include "interface.dmf"
179 changes: 179 additions & 0 deletions OpenDreamClient/ClientVerbSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System.Threading.Tasks;
using OpenDreamClient.Interface;
using OpenDreamClient.Rendering;
using OpenDreamShared.Dream;
using OpenDreamShared.Rendering;
using Robust.Client.Player;
using Robust.Shared.Asynchronous;
using Robust.Shared.Timing;

namespace OpenDreamClient;

public sealed class ClientVerbSystem : VerbSystem {
[Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;

private EntityQuery<DMISpriteComponent> _spriteQuery;
private EntityQuery<DreamMobSightComponent> _sightQuery;

private readonly Dictionary<int, VerbInfo> _verbs = new();
private List<int>? _clientVerbs;

public override void Initialize() {
_spriteQuery = _entityManager.GetEntityQuery<DMISpriteComponent>();
_sightQuery = _entityManager.GetEntityQuery<DreamMobSightComponent>();

_playerManager.LocalPlayerAttached += OnLocalPlayerAttached;

SubscribeNetworkEvent<AllVerbsEvent>(OnAllVerbsEvent);
SubscribeNetworkEvent<RegisterVerbEvent>(OnRegisterVerbEvent);
SubscribeNetworkEvent<UpdateClientVerbsEvent>(OnUpdateClientVerbsEvent);
}

public override void Shutdown() {
_verbs.Clear();
_clientVerbs = null;
}

/// <summary>
/// Prompt the user for the arguments to a verb, then ask the server to execute it
/// </summary>
/// <param name="src">The target of the verb</param>
/// <param name="verbId">ID of the verb to execute</param>
public async void ExecuteVerb(ClientObjectReference src, int verbId) {
var verbInfo = _verbs[verbId];

RaiseNetworkEvent(new ExecuteVerbEvent(src, verbId, await PromptVerbArguments(verbInfo)));
}

/// <summary>
/// Ask the server to execute a verb with the given arguments
/// </summary>
/// <param name="src">The target of the verb</param>
/// <param name="verbId">ID of the verb to execute</param>
/// <param name="arguments">Arguments to the verb</param>
/// <remarks>The server will not execute the verb if the arguments are invalid</remarks> // TODO: I think the server actually just errors, fix that
public void ExecuteVerb(ClientObjectReference src, int verbId, object?[] arguments) {
RaiseNetworkEvent(new ExecuteVerbEvent(src, verbId, arguments));
}

public IEnumerable<VerbInfo> GetAllVerbs() {
return _verbs.Values;
}

/// <summary>
/// Find all the verbs the client is currently capable of executing
/// </summary>
/// <param name="ignoreHiddenAttr">Whether to ignore "set hidden = TRUE"</param>
/// <returns>The ID, target, and information of every executable verb</returns>
public IEnumerable<(int Id, ClientObjectReference Src, VerbInfo VerbInfo)> GetExecutableVerbs(bool ignoreHiddenAttr = false) {
DMISpriteComponent? playerSprite = null;
sbyte? seeInvisibility = null;
if (_playerManager.LocalEntity != null) {
playerSprite = _spriteQuery.GetComponent(_playerManager.LocalEntity.Value);
seeInvisibility = _sightQuery.GetComponent(_playerManager.LocalEntity.Value).SeeInvisibility;
}

// First, the verbs attached to our client
if (_clientVerbs != null) {
foreach (var verbId in _clientVerbs) {
if (!_verbs.TryGetValue(verbId, out var verb))
continue;
if (verb.IsHidden(ignoreHiddenAttr, seeInvisibility ?? 0))
continue; // TODO: How do invisible client verbs work when you don't have a mob?

yield return (verbId, ClientObjectReference.Client, verb);
}
}

// Then, the verbs attached to our mob
if (playerSprite?.Icon.Appearance is { } playerAppearance) {
var playerNetEntity = _entityManager.GetNetEntity(_playerManager.LocalEntity);

if (playerNetEntity != null) {
foreach (var verbId in playerAppearance.Verbs) {
if (!_verbs.TryGetValue(verbId, out var verb))
continue;
if (verb.IsHidden(ignoreHiddenAttr, seeInvisibility!.Value))
continue;

yield return (verbId, new(playerNetEntity.Value), verb);
}
}
}
}

public bool TryGetVerbInfo(int verbId, out VerbInfo verbInfo) {
return _verbs.TryGetValue(verbId, out verbInfo);
}

/// <summary>
/// Look for a verb with the given command-name that the client can execute
/// </summary>
/// <param name="commandName">Command-name to look for</param>
/// <returns>The ID, target, and verb information if a verb was found</returns>
public (int Id, ClientObjectReference Src, VerbInfo VerbInfo)? FindVerbWithCommandName(string commandName) {
foreach (var verb in GetExecutableVerbs(true)) {
if (verb.VerbInfo.GetCommandName() == commandName)
return verb;
}

return null;
}

/// <summary>
/// Open prompt windows for the user to enter the arguments to a verb
/// </summary>
/// <param name="verbInfo">The verb to get arguments for</param>
/// <returns>The values the user gives</returns>
private async Task<object?[]> PromptVerbArguments(VerbInfo verbInfo) {
var argumentCount = verbInfo.Arguments.Length;
var arguments = (argumentCount > 0) ? new object?[argumentCount] : Array.Empty<object?>();

for (int i = 0; i < argumentCount; i++) {
var arg = verbInfo.Arguments[i];
var tcs = new TaskCompletionSource<object?>();

_taskManager.RunOnMainThread(() => {
_interfaceManager.Prompt(arg.Types, verbInfo.Name, arg.Name, string.Empty, (_, value) => {
tcs.SetResult(value);
});
});

arguments[i] = await tcs.Task; // Wait for this prompt to finish before moving on to the next
}

return arguments;
}

private void OnAllVerbsEvent(AllVerbsEvent e) {
_verbs.EnsureCapacity(e.Verbs.Count);

for (int i = 0; i < e.Verbs.Count; i++) {
var verb = e.Verbs[i];

_verbs.Add(i, verb);
}

_interfaceManager.DefaultInfo?.RefreshVerbs(this);
}

private void OnRegisterVerbEvent(RegisterVerbEvent e) {
_verbs.Add(e.VerbId, e.VerbInfo);
}

private void OnUpdateClientVerbsEvent(UpdateClientVerbsEvent e) {
_clientVerbs = e.VerbIds;
_interfaceManager.DefaultInfo?.RefreshVerbs(this);
}

private void OnLocalPlayerAttached(EntityUid obj) {
// Our mob changed, update our verb panels
// A little hacky, but also wait half a second for verb information about our mob to arrive
// TODO: Remove this timer
_timerManager.AddTimer(new Timer(500, false, () => _interfaceManager.DefaultInfo?.RefreshVerbs(this)));
}
}
2 changes: 1 addition & 1 deletion OpenDreamClient/Input/ContextMenu/ContextMenuItem.xaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io" MouseFilter="Stop">
<BoxContainer Orientation="Horizontal">
<TextureRect Name="Icon" CanShrink="True" Stretch="KeepAspect" SetWidth="32" SetHeight="32" />
<TextureRect Name="Icon" CanShrink="True" Stretch="KeepAspect" SetWidth="32" SetHeight="32" Margin="2"/>
<Label Name="NameLabel" />
</BoxContainer>
</PanelContainer>
66 changes: 27 additions & 39 deletions OpenDreamClient/Input/ContextMenu/ContextMenuItem.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,46 @@
using OpenDreamClient.Rendering;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace OpenDreamClient.Input.ContextMenu {
[GenerateTypedNameReferences]
public sealed partial class ContextMenuItem : PanelContainer {
private static readonly StyleBox HoverStyle = new StyleBoxFlat(Color.Gray);
namespace OpenDreamClient.Input.ContextMenu;

private readonly IUserInterfaceManager _uiManager;
[GenerateTypedNameReferences]
internal sealed partial class ContextMenuItem : PanelContainer {
private static readonly StyleBox HoverStyle = new StyleBoxFlat(Color.Gray);

private readonly MetaDataComponent? _entityMetaData;
private VerbMenuPopup? _currentVerbMenu;
public readonly EntityUid Entity;
public readonly MetaDataComponent EntityMetaData;
public readonly DMISpriteComponent? EntitySprite;

public ContextMenuItem(IUserInterfaceManager uiManager, IEntityManager entityManager, EntityUid entity) {
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
private readonly ContextMenuPopup _menu;

_uiManager = uiManager;
public ContextMenuItem(ContextMenuPopup menu, EntityUid entity, MetaDataComponent metadata, DMISpriteComponent sprite) {
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);

NameLabel.Margin = new Thickness(2, 0, 4, 0);
if (entityManager.TryGetComponent(entity, out _entityMetaData)) {
NameLabel.Text = _entityMetaData.EntityName;
}
Entity = entity;
EntityMetaData = metadata;
EntitySprite = sprite;
_menu = menu;

Icon.Margin = new Thickness(2);
if (entityManager.TryGetComponent(entity, out DMISpriteComponent? sprite)) {
Icon.Texture = sprite.Icon.CurrentFrame;
}
NameLabel.Margin = new Thickness(2, 0, 4, 0);
NameLabel.Text = metadata.EntityName;

OnMouseEntered += MouseEntered;
OnMouseExited += MouseExited;
}

private void MouseEntered(GUIMouseHoverEventArgs args) {
PanelOverride = HoverStyle;
Icon.Texture = sprite.Icon.CurrentFrame;
}

_currentVerbMenu = new VerbMenuPopup(_entityMetaData);
_uiManager.ModalRoot.AddChild(_currentVerbMenu);
protected override void MouseEntered() {
base.MouseEntered();

Vector2 desiredSize = _currentVerbMenu.DesiredSize;
_currentVerbMenu.Open(UIBox2.FromDimensions(new Vector2(GlobalPosition.X + Size.X, GlobalPosition.Y), desiredSize));
}
PanelOverride = HoverStyle;
_menu.SetActiveItem(this);
}

private void MouseExited(GUIMouseHoverEventArgs args) {
PanelOverride = null;
protected override void MouseExited() {
base.MouseExited();

if (_currentVerbMenu != null) {
_currentVerbMenu.Close();
_uiManager.ModalRoot.RemoveChild(_currentVerbMenu);
_currentVerbMenu = null;
}
}
PanelOverride = null;
}
}
Loading

0 comments on commit fe6c1a8

Please sign in to comment.