2021-08-22 03:20:18 +10:00
|
|
|
using System;
|
2021-02-25 19:42:16 -06:00
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
2021-06-09 22:19:39 +02:00
|
|
|
using Content.Client.Examine;
|
|
|
|
|
using Content.Client.Interactable;
|
|
|
|
|
using Content.Client.Items.Managers;
|
|
|
|
|
using Content.Client.Verbs;
|
|
|
|
|
using Content.Client.Viewport;
|
2021-06-13 14:52:40 +02:00
|
|
|
using Content.Shared.CCVar;
|
2021-02-25 19:42:16 -06:00
|
|
|
using Content.Shared.Input;
|
2021-10-05 14:29:03 +11:00
|
|
|
using Content.Shared.Interaction.Helpers;
|
2021-02-25 19:42:16 -06:00
|
|
|
using Robust.Client.GameObjects;
|
2021-04-19 09:52:40 +02:00
|
|
|
using Robust.Client.Graphics;
|
2021-02-25 19:42:16 -06:00
|
|
|
using Robust.Client.Input;
|
|
|
|
|
using Robust.Client.Player;
|
|
|
|
|
using Robust.Client.State;
|
|
|
|
|
using Robust.Client.UserInterface;
|
|
|
|
|
using Robust.Shared.Configuration;
|
|
|
|
|
using Robust.Shared.Containers;
|
|
|
|
|
using Robust.Shared.GameObjects;
|
|
|
|
|
using Robust.Shared.Input;
|
|
|
|
|
using Robust.Shared.Input.Binding;
|
|
|
|
|
using Robust.Shared.IoC;
|
|
|
|
|
using Robust.Shared.Map;
|
|
|
|
|
using Robust.Shared.Timing;
|
2021-06-09 22:19:39 +02:00
|
|
|
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
2021-02-25 19:42:16 -06:00
|
|
|
using Timer = Robust.Shared.Timing.Timer;
|
2021-06-09 22:19:39 +02:00
|
|
|
namespace Content.Client.ContextMenu.UI
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
|
|
|
|
public class ContextMenuPresenter : IDisposable
|
|
|
|
|
{
|
|
|
|
|
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
|
|
|
|
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
|
|
|
|
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
|
|
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
|
|
|
[Dependency] private readonly IStateManager _stateManager = default!;
|
|
|
|
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
|
|
|
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
|
|
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
2021-04-19 09:52:40 +02:00
|
|
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
2021-02-25 19:42:16 -06:00
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
|
|
|
|
private CancellationTokenSource? _cancelHover;
|
|
|
|
|
|
2021-02-25 19:42:16 -06:00
|
|
|
private readonly IContextMenuView _contextMenuView;
|
|
|
|
|
private readonly VerbSystem _verbSystem;
|
|
|
|
|
|
|
|
|
|
private MapCoordinates _mapCoordinates;
|
|
|
|
|
|
|
|
|
|
public ContextMenuPresenter(VerbSystem verbSystem)
|
|
|
|
|
{
|
|
|
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
|
|
|
|
|
|
_verbSystem = verbSystem;
|
|
|
|
|
|
|
|
|
|
_contextMenuView = new ContextMenuView();
|
|
|
|
|
_contextMenuView.OnKeyBindDownSingle += OnKeyBindDownSingle;
|
|
|
|
|
_contextMenuView.OnMouseEnteredSingle += OnMouseEnteredSingle;
|
|
|
|
|
_contextMenuView.OnMouseExitedSingle += OnMouseExitedSingle;
|
|
|
|
|
_contextMenuView.OnMouseHoveringSingle += OnMouseHoveringSingle;
|
|
|
|
|
|
|
|
|
|
_contextMenuView.OnKeyBindDownStack += OnKeyBindDownStack;
|
|
|
|
|
_contextMenuView.OnMouseEnteredStack += OnMouseEnteredStack;
|
|
|
|
|
|
|
|
|
|
_contextMenuView.OnExitedTree += OnExitedTree;
|
|
|
|
|
_contextMenuView.OnCloseRootMenu += OnCloseRootMenu;
|
|
|
|
|
_contextMenuView.OnCloseChildMenu += OnCloseChildMenu;
|
|
|
|
|
|
|
|
|
|
_cfg.OnValueChanged(CCVars.ContextMenuGroupingType, _contextMenuView.OnGroupingContextMenuChanged, true);
|
2021-10-05 14:29:03 +11:00
|
|
|
|
|
|
|
|
CommandBinds.Builder
|
|
|
|
|
.Bind(ContentKeyFunctions.OpenContextMenu, new PointerInputCmdHandler(HandleOpenContextMenu))
|
|
|
|
|
.Register<ContextMenuPresenter>();
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region View Events
|
|
|
|
|
private void OnCloseChildMenu(object? sender, int depth)
|
|
|
|
|
{
|
|
|
|
|
_contextMenuView.CloseContextPopups(depth);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnCloseRootMenu(object? sender, EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
_contextMenuView.CloseContextPopups();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnExitedTree(object? sender, ContextMenuElement e)
|
|
|
|
|
{
|
|
|
|
|
_contextMenuView.UpdateParents(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnMouseEnteredStack(object? sender, StackContextElement e)
|
|
|
|
|
{
|
|
|
|
|
var realGlobalPosition = e.GlobalPosition;
|
|
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
_cancelHover?.Cancel();
|
|
|
|
|
_cancelHover = new();
|
2021-02-25 19:42:16 -06:00
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
Timer.Spawn(HoverDelay, () =>
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
|
|
|
|
if (_contextMenuView.Menus.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
|
|
|
|
|
|
|
|
|
var filteredEntities = e.ContextEntities.Where(entity => !entity.Deleted);
|
|
|
|
|
if (filteredEntities.Any())
|
|
|
|
|
{
|
|
|
|
|
_contextMenuView.AddChildMenu(filteredEntities, realGlobalPosition, e);
|
|
|
|
|
}
|
2021-10-05 14:29:03 +11:00
|
|
|
}, _cancelHover.Token);
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnKeyBindDownStack(object? sender, (GUIBoundKeyEventArgs, StackContextElement) e)
|
|
|
|
|
{
|
|
|
|
|
var (args, stack) = e;
|
|
|
|
|
var firstEntity = stack.ContextEntities.FirstOrDefault(ent => !ent.Deleted);
|
|
|
|
|
|
|
|
|
|
if (firstEntity == null) return;
|
|
|
|
|
|
2021-08-22 03:20:18 +10:00
|
|
|
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.AltActivateItemInWorld || args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
|
|
|
|
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
|
|
|
|
|
|
|
|
|
var func = args.Function;
|
|
|
|
|
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func);
|
|
|
|
|
|
|
|
|
|
var message = new FullInputCmdMessage(_gameTiming.CurTick, _gameTiming.TickFraction, funcId,
|
|
|
|
|
BoundKeyState.Down, firstEntity.Transform.Coordinates, args.PointerLocation, firstEntity.Uid);
|
|
|
|
|
|
|
|
|
|
var session = _playerManager.LocalPlayer?.Session;
|
|
|
|
|
if (session != null)
|
|
|
|
|
{
|
|
|
|
|
inputSys.HandleInputCommand(session, func, message);
|
|
|
|
|
}
|
|
|
|
|
CloseAllMenus();
|
2021-04-19 01:45:02 +02:00
|
|
|
args.Handle();
|
2021-02-25 19:42:16 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_itemSlotManager.OnButtonPressed(args, firstEntity))
|
|
|
|
|
{
|
|
|
|
|
CloseAllMenus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnMouseHoveringSingle(object? sender, SingleContextElement e)
|
|
|
|
|
{
|
|
|
|
|
if (!e.DrawOutline) return;
|
|
|
|
|
|
|
|
|
|
var localPlayer = _playerManager.LocalPlayer;
|
|
|
|
|
if (localPlayer?.ControlledEntity != null)
|
|
|
|
|
{
|
|
|
|
|
var inRange =
|
|
|
|
|
localPlayer.InRangeUnobstructed(e.ContextEntity, ignoreInsideBlocker: true);
|
2021-04-19 09:52:40 +02:00
|
|
|
|
|
|
|
|
// BUG: This assumes that the main viewport is the viewport that the context menu is active on.
|
|
|
|
|
// This is not necessarily true but we currently have no way to find the viewport (reliably)
|
|
|
|
|
// from the input event.
|
|
|
|
|
//
|
|
|
|
|
// This might be particularly important in the future with a more advanced mapping mode.
|
|
|
|
|
var renderScale = _eyeManager.MainViewport.GetRenderScale();
|
|
|
|
|
e.OutlineComponent?.UpdateInRange(inRange, renderScale);
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnMouseEnteredSingle(object? sender, SingleContextElement e)
|
|
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
// close other pop-ups after a short delay
|
|
|
|
|
_cancelHover?.Cancel();
|
|
|
|
|
_cancelHover = new();
|
|
|
|
|
|
|
|
|
|
Timer.Spawn(HoverDelay, () =>
|
|
|
|
|
{
|
|
|
|
|
if (_contextMenuView.Menus.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
|
|
|
|
|
|
|
|
|
}, _cancelHover.Token);
|
|
|
|
|
|
2021-02-25 19:42:16 -06:00
|
|
|
|
|
|
|
|
var entity = e.ContextEntity;
|
|
|
|
|
|
|
|
|
|
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
|
|
|
|
|
|
|
|
|
if (entity.Deleted) return;
|
|
|
|
|
|
|
|
|
|
var localPlayer = _playerManager.LocalPlayer;
|
|
|
|
|
if (localPlayer?.ControlledEntity == null) return;
|
|
|
|
|
|
2021-04-19 09:52:40 +02:00
|
|
|
var renderScale = _eyeManager.MainViewport.GetRenderScale();
|
|
|
|
|
e.OutlineComponent?.OnMouseEnter(localPlayer.InRangeUnobstructed(entity, ignoreInsideBlocker: true), renderScale);
|
2021-02-25 19:42:16 -06:00
|
|
|
if (e.SpriteComp != null)
|
|
|
|
|
{
|
2021-06-09 22:19:39 +02:00
|
|
|
e.SpriteComp.DrawDepth = (int) DrawDepth.HighlightedItems;
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
e.DrawOutline = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnMouseExitedSingle(object? sender, SingleContextElement e)
|
|
|
|
|
{
|
|
|
|
|
if (!e.ContextEntity.Deleted)
|
|
|
|
|
{
|
|
|
|
|
if (e.SpriteComp != null)
|
|
|
|
|
{
|
|
|
|
|
e.SpriteComp.DrawDepth = e.OriginalDrawDepth;
|
|
|
|
|
}
|
|
|
|
|
e.OutlineComponent?.OnMouseLeave();
|
|
|
|
|
}
|
|
|
|
|
e.DrawOutline = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnKeyBindDownSingle(object? sender, (GUIBoundKeyEventArgs, SingleContextElement) valueTuple)
|
|
|
|
|
{
|
|
|
|
|
var (args, single) = valueTuple;
|
|
|
|
|
var entity = single.ContextEntity;
|
|
|
|
|
if (args.Function == ContentKeyFunctions.OpenContextMenu)
|
|
|
|
|
{
|
|
|
|
|
_verbSystem.OnContextButtonPressed(entity);
|
2021-04-19 01:45:02 +02:00
|
|
|
args.Handle();
|
2021-02-25 19:42:16 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
|
|
|
|
{
|
|
|
|
|
_systemManager.GetEntitySystem<ExamineSystem>().DoExamine(entity);
|
2021-04-19 01:45:02 +02:00
|
|
|
args.Handle();
|
2021-02-25 19:42:16 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-22 03:20:18 +10:00
|
|
|
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.AltActivateItemInWorld || args.Function == ContentKeyFunctions.Point ||
|
2021-02-25 19:42:16 -06:00
|
|
|
args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
|
|
|
|
{
|
|
|
|
|
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
|
|
|
|
|
|
|
|
|
var func = args.Function;
|
|
|
|
|
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func);
|
|
|
|
|
|
|
|
|
|
var message = new FullInputCmdMessage(_gameTiming.CurTick, _gameTiming.TickFraction, funcId,
|
|
|
|
|
BoundKeyState.Down, entity.Transform.Coordinates, args.PointerLocation, entity.Uid);
|
|
|
|
|
|
|
|
|
|
var session = _playerManager.LocalPlayer?.Session;
|
|
|
|
|
if (session != null)
|
|
|
|
|
{
|
|
|
|
|
inputSys.HandleInputCommand(session, func, message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CloseAllMenus();
|
2021-04-19 01:45:02 +02:00
|
|
|
args.Handle();
|
2021-02-25 19:42:16 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_itemSlotManager.OnButtonPressed(args, single.ContextEntity))
|
|
|
|
|
{
|
|
|
|
|
CloseAllMenus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Model Updates
|
2021-10-05 14:29:03 +11:00
|
|
|
private bool HandleOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
if (args.State != BoundKeyState.Down)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-02-25 19:42:16 -06:00
|
|
|
|
|
|
|
|
if (_stateManager.CurrentState is not GameScreenBase)
|
|
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
return false;
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
|
|
|
|
if (player == null)
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
return false;
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_mapCoordinates = args.Coordinates.ToMap(_entityManager);
|
|
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
if (!_verbSystem.TryGetContextEntities(player, _mapCoordinates, out var entities, ignoreVisibility: _verbSystem.CanSeeAllContext))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// do we need to do visiblity checks?
|
|
|
|
|
if (_verbSystem.CanSeeAllContext)
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
|
|
|
|
_contextMenuView.AddRootMenu(entities);
|
2021-10-05 14:29:03 +11:00
|
|
|
return true;
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
//visibility checks
|
|
|
|
|
player.TryGetContainer(out var playerContainer);
|
|
|
|
|
foreach (var entity in entities.ToList())
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
if (!entity.TryGetComponent(out ISpriteComponent? spriteComponent) ||
|
|
|
|
|
!spriteComponent.Visible ||
|
|
|
|
|
!CanSeeContainerCheck(entity, playerContainer))
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
entities.Remove(entity);
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
}
|
2021-10-05 14:29:03 +11:00
|
|
|
|
|
|
|
|
if (entities.Count == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
_contextMenuView.AddRootMenu(entities);
|
|
|
|
|
return true;
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
/// <summary>
|
|
|
|
|
/// Can the player see the entity through any entity containers?
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// This is similar to <see cref="ContainerHelpers.IsInSameOrParentContainer()"/>, except that we do not
|
|
|
|
|
/// allow the player to be the "parent" container and we allow for see-through containers (display cases).
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private bool CanSeeContainerCheck(IEntity entity, IContainer? playerContainer)
|
|
|
|
|
{
|
|
|
|
|
// is the player inside this entity?
|
|
|
|
|
if (playerContainer?.Owner == entity)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
entity.TryGetContainer(out var entityContainer);
|
|
|
|
|
|
|
|
|
|
// are they in the same container (or none?)
|
|
|
|
|
if (playerContainer == entityContainer)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Is the entity in a display case?
|
|
|
|
|
if (playerContainer == null && entityContainer!.ShowContents)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check that entities in the context menu are still visible. If not, remove them from the context menu.
|
|
|
|
|
/// </summary>
|
2021-02-25 19:42:16 -06:00
|
|
|
public void Update()
|
|
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
if (_contextMenuView.Elements.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
|
|
|
|
|
|
|
|
|
if (player == null)
|
|
|
|
|
return;
|
2021-02-25 19:42:16 -06:00
|
|
|
|
|
|
|
|
foreach (var entity in _contextMenuView.Elements.Keys.ToList())
|
|
|
|
|
{
|
2021-10-05 14:29:03 +11:00
|
|
|
if (entity.Deleted || !_verbSystem.CanSeeAllContext && !player.InRangeUnOccluded(entity))
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
|
|
|
|
_contextMenuView.RemoveEntity(entity);
|
2021-10-05 14:29:03 +11:00
|
|
|
if (_verbSystem.CurrentTarget == entity.Uid)
|
|
|
|
|
_verbSystem.CloseVerbMenu();
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
2021-10-05 14:29:03 +11:00
|
|
|
public void CloseAllMenus()
|
2021-02-25 19:42:16 -06:00
|
|
|
{
|
|
|
|
|
_contextMenuView.CloseContextPopups();
|
|
|
|
|
_verbSystem.CloseVerbMenu();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_contextMenuView.OnKeyBindDownSingle -= OnKeyBindDownSingle;
|
|
|
|
|
_contextMenuView.OnMouseEnteredSingle -= OnMouseEnteredSingle;
|
|
|
|
|
_contextMenuView.OnMouseExitedSingle -= OnMouseExitedSingle;
|
|
|
|
|
_contextMenuView.OnMouseHoveringSingle -= OnMouseHoveringSingle;
|
|
|
|
|
|
|
|
|
|
_contextMenuView.OnKeyBindDownStack -= OnKeyBindDownStack;
|
|
|
|
|
_contextMenuView.OnMouseEnteredStack -= OnMouseEnteredStack;
|
|
|
|
|
|
|
|
|
|
_contextMenuView.OnExitedTree -= OnExitedTree;
|
|
|
|
|
_contextMenuView.OnCloseRootMenu -= OnCloseRootMenu;
|
|
|
|
|
_contextMenuView.OnCloseChildMenu -= OnCloseChildMenu;
|
2021-10-05 14:29:03 +11:00
|
|
|
|
|
|
|
|
CommandBinds.Unregister<ContextMenuPresenter>();
|
2021-02-25 19:42:16 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|