Files
crystall-punk-14/Content.Shared/UserInterface/ActivatableUISystem.cs

290 lines
9.7 KiB
C#
Raw Permalink Normal View History

using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Managers;
2023-08-25 18:50:46 +10:00
using Content.Shared.Ghost;
using Content.Shared.Hands;
2022-03-15 16:59:20 +13:00
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
2024-05-03 11:37:20 +12:00
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Utility;
namespace Content.Shared.UserInterface;
2023-04-23 12:25:12 +10:00
public sealed partial class ActivatableUISystem : EntitySystem
{
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
2023-04-23 12:25:12 +10:00
[Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
2023-04-23 12:25:12 +10:00
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActivatableUIComponent, ComponentStartup>(OnStartup);
2024-05-03 11:37:20 +12:00
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
2023-04-23 12:25:12 +10:00
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
Add door electronics access configuration menu (#17778) * Add door electronics configuration menu * Use file-scoped namespaces Signed-off-by: c4llv07e <kseandi@gmail.com> * Open door electronics configuration menu only with network configurator Signed-off-by: c4llv07e <kseandi@gmail.com> * Doors will now try to move their AccessReaderComponent to their door electronics when the map is initialized Signed-off-by: c4llv07e <kseandi@gmail.com> * Make the access list in the id card computer a separate control Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix merge conflict Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove DoorElectronics tag Signed-off-by: c4llv07e <kseandi@gmail.com> * Integrate doors with #17927 Signed-off-by: c4llv07e <kseandi@gmail.com> * Move door electornics ui stuff to the right place Signed-off-by: c4llv07e <kseandi@gmail.com> * Some review fixes Signed-off-by: c4llv07e <kseandi@gmail.com> * More fixes Signed-off-by: c4llv07e <kseandi@gmail.com> * review fix Signed-off-by: c4llv07e <kseandi@gmail.com> * move all accesses from airlock prototypes to door electronics Signed-off-by: c4llv07e <kseandi@gmail.com> * rework door electronics config access list Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove Linq from the door electronics user interface * [WIP] Add EntityWhitelist to the activatable ui component Signed-off-by: c4llv07e <kseandi@gmail.com> * Better interaction system Signed-off-by: c4llv07e <kseandi@gmail.com> * Refactor Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix some door electronics not working without AccessReaderComponent Signed-off-by: c4llv07e <kseandi@gmail.com> * Move AccessReaderComponent update code to the AccessReaderSystem Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unnecesary newlines in the door access prototypes Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unused variables in access level control Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unnecessary method from the door electronics configuration menu Signed-off-by: c4llv07e <kseandi@gmail.com> * [WIP] change access type from string to ProtoId<AccessLevelPrototype> Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unused methods Signed-off-by: c4llv07e <kseandi@gmail.com> * Newline fix Signed-off-by: c4llv07e <kseandi@gmail.com> * Restored to a functional state Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix access configurator not working with door electronics AccessReaderComponent Signed-off-by: c4llv07e <kseandi@gmail.com> * Replace all string access fields with ProtoId Signed-off-by: c4llv07e <kseandi@gmail.com> * move access level control initialization into Populate method Signed-off-by: c4llv07e <kseandi@gmail.com> * Review --------- Signed-off-by: c4llv07e <kseandi@gmail.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 09:06:13 +03:00
SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing);
2023-04-23 12:25:12 +10:00
SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>(OnHandDeselected);
SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>(OnHandUnequipped);
2023-04-23 12:25:12 +10:00
SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(GetActivationVerb);
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<Verb>>(GetVerb);
2023-09-11 21:20:46 +10:00
SubscribeLocalEvent<UserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
2023-04-23 12:25:12 +10:00
InitializePower();
}
2022-03-15 16:59:20 +13:00
private void OnStartup(Entity<ActivatableUIComponent> ent, ref ComponentStartup args)
2023-04-23 12:25:12 +10:00
{
if (ent.Comp.Key == null)
{
Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}");
2023-04-23 12:25:12 +10:00
return;
}
2022-03-15 16:59:20 +13:00
// TODO BUI
// set interaction range to zero to avoid constant range checks.
//
// if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data))
// data.InteractionRange = 0;
2023-04-23 12:25:12 +10:00
}
2023-09-11 21:20:46 +10:00
private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args)
2023-04-23 12:25:12 +10:00
{
if (args.Handled || args.Key == null)
return;
2024-04-26 22:45:38 +10:00
args.Handled = _uiSystem.TryToggleUi(uid, args.Key, args.Performer);
2023-04-23 12:25:12 +10:00
}
private void GetActivationVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
2023-04-23 12:25:12 +10:00
{
if (component.VerbOnly || !ShouldAddVerb(uid, component, args))
2023-04-23 12:25:12 +10:00
return;
args.Verbs.Add(new ActivationVerb
{
Act = () => InteractUI(args.User, uid, component),
Text = Loc.GetString(component.VerbText),
// TODO VERB ICON find a better icon
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
});
}
2022-02-25 00:10:12 +13:00
private void GetVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<Verb> args)
{
if (!component.VerbOnly || !ShouldAddVerb(uid, component, args))
2023-04-23 12:25:12 +10:00
return;
args.Verbs.Add(new Verb
{
Act = () => InteractUI(args.User, uid, component),
Text = Loc.GetString(component.VerbText),
// TODO VERB ICON find a better icon
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
});
}
private bool ShouldAddVerb<T>(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<T> args) where T : Verb
{
if (!args.CanAccess)
return false;
if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default))
2024-05-03 11:37:20 +12:00
return false;
if (component.RequiresComplex)
{
if (args.Hands == null)
return false;
if (component.InHandsOnly)
{
if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands))
return false;
if (component.RequireActiveHand && args.Hands.ActiveHand != hand)
return false;
}
}
return args.CanInteract || HasComp<GhostComponent>(args.User) && !component.BlockSpectators;
2023-04-23 12:25:12 +10:00
}
2024-05-03 11:37:20 +12:00
private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
if (component.VerbOnly)
return;
if (component.RequiredItems != null)
return;
args.Handled = InteractUI(args.User, uid, component);
}
2023-04-23 12:25:12 +10:00
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
2023-10-11 02:17:59 -07:00
return;
if (component.VerbOnly)
2023-10-11 02:17:59 -07:00
return;
if (component.RequiredItems != null)
return;
2023-10-11 02:17:59 -07:00
args.Handled = InteractUI(args.User, uid, component);
2023-04-23 12:25:12 +10:00
}
private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args)
2023-04-23 12:25:12 +10:00
{
2023-10-11 02:17:59 -07:00
if (args.Handled)
return;
if (component.VerbOnly)
2023-10-11 02:17:59 -07:00
return;
if (component.RequiredItems == null)
return;
if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Used))
return;
Add door electronics access configuration menu (#17778) * Add door electronics configuration menu * Use file-scoped namespaces Signed-off-by: c4llv07e <kseandi@gmail.com> * Open door electronics configuration menu only with network configurator Signed-off-by: c4llv07e <kseandi@gmail.com> * Doors will now try to move their AccessReaderComponent to their door electronics when the map is initialized Signed-off-by: c4llv07e <kseandi@gmail.com> * Make the access list in the id card computer a separate control Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix merge conflict Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove DoorElectronics tag Signed-off-by: c4llv07e <kseandi@gmail.com> * Integrate doors with #17927 Signed-off-by: c4llv07e <kseandi@gmail.com> * Move door electornics ui stuff to the right place Signed-off-by: c4llv07e <kseandi@gmail.com> * Some review fixes Signed-off-by: c4llv07e <kseandi@gmail.com> * More fixes Signed-off-by: c4llv07e <kseandi@gmail.com> * review fix Signed-off-by: c4llv07e <kseandi@gmail.com> * move all accesses from airlock prototypes to door electronics Signed-off-by: c4llv07e <kseandi@gmail.com> * rework door electronics config access list Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove Linq from the door electronics user interface * [WIP] Add EntityWhitelist to the activatable ui component Signed-off-by: c4llv07e <kseandi@gmail.com> * Better interaction system Signed-off-by: c4llv07e <kseandi@gmail.com> * Refactor Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix some door electronics not working without AccessReaderComponent Signed-off-by: c4llv07e <kseandi@gmail.com> * Move AccessReaderComponent update code to the AccessReaderSystem Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unnecesary newlines in the door access prototypes Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unused variables in access level control Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unnecessary method from the door electronics configuration menu Signed-off-by: c4llv07e <kseandi@gmail.com> * [WIP] change access type from string to ProtoId<AccessLevelPrototype> Signed-off-by: c4llv07e <kseandi@gmail.com> * Remove unused methods Signed-off-by: c4llv07e <kseandi@gmail.com> * Newline fix Signed-off-by: c4llv07e <kseandi@gmail.com> * Restored to a functional state Signed-off-by: c4llv07e <kseandi@gmail.com> * Fix access configurator not working with door electronics AccessReaderComponent Signed-off-by: c4llv07e <kseandi@gmail.com> * Replace all string access fields with ProtoId Signed-off-by: c4llv07e <kseandi@gmail.com> * move access level control initialization into Populate method Signed-off-by: c4llv07e <kseandi@gmail.com> * Review --------- Signed-off-by: c4llv07e <kseandi@gmail.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 09:06:13 +03:00
args.Handled = InteractUI(args.User, uid, component);
}
2023-04-23 12:25:12 +10:00
private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
{
var user = args.Actor;
if (user != component.CurrentSingleUser)
2023-10-11 02:17:59 -07:00
return;
if (!Equals(args.UiKey, component.Key))
return;
2023-04-23 12:25:12 +10:00
SetCurrentSingleUser(uid, null, component);
}
2023-10-11 02:17:59 -07:00
private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui)
2023-04-23 12:25:12 +10:00
{
if (aui.Key == null || !_uiSystem.HasUi(uiEntity, aui.Key))
2023-04-23 12:25:12 +10:00
return false;
if (_uiSystem.IsUiOpen(uiEntity, aui.Key, user))
{
_uiSystem.CloseUi(uiEntity, aui.Key, user);
return true;
}
if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators))
2023-10-11 02:17:59 -07:00
return false;
if (aui.RequiresComplex)
{
if (!_blockerSystem.CanComplexInteract(user))
return false;
}
if (aui.InHandsOnly)
{
if (!TryComp(user, out HandsComponent? hands))
return false;
if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
return false;
if (aui.RequireActiveHand && hands.ActiveHand != hand)
return false;
}
if (aui.AdminOnly && !_adminManager.IsAdmin(user))
2023-10-11 02:17:59 -07:00
return false;
if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser)
2023-04-23 12:25:12 +10:00
{
var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity));
_popupSystem.PopupClient(message, uiEntity, user);
if (_uiSystem.IsUiOpen(uiEntity, aui.Key))
return true;
Log.Error($"Activatable UI has user without being opened? Entity: {ToPrettyString(uiEntity)}. User: {aui.CurrentSingleUser}, Key: {aui.Key}");
2023-04-23 12:25:12 +10:00
}
2023-04-23 12:25:12 +10:00
// If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
// This is so that stuff can require further conditions (like power).
var oae = new ActivatableUIOpenAttemptEvent(user);
2023-10-11 02:17:59 -07:00
var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity);
RaiseLocalEvent(user, uae);
RaiseLocalEvent(uiEntity, oae);
if (oae.Cancelled || uae.Cancelled)
return false;
2023-04-23 12:25:12 +10:00
// Give the UI an opportunity to prepare itself if it needs to do anything
// before opening
var bae = new BeforeActivatableUIOpenEvent(user);
2023-10-11 02:17:59 -07:00
RaiseLocalEvent(uiEntity, bae);
SetCurrentSingleUser(uiEntity, user, aui);
_uiSystem.OpenUi(uiEntity, aui.Key, user);
2023-04-23 12:25:12 +10:00
//Let the component know a user opened it so it can do whatever it needs to do
var aae = new AfterActivatableUIOpenEvent(user, user);
2023-10-11 02:17:59 -07:00
RaiseLocalEvent(uiEntity, aae);
2023-04-23 12:25:12 +10:00
return true;
}
public void SetCurrentSingleUser(EntityUid uid, EntityUid? user, ActivatableUIComponent? aui = null)
2023-04-23 12:25:12 +10:00
{
if (!Resolve(uid, ref aui))
return;
2023-04-23 12:25:12 +10:00
if (!aui.SingleUser)
return;
aui.CurrentSingleUser = user;
Dirty(uid, aui);
2023-10-11 02:17:59 -07:00
RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent());
}
2023-04-23 12:25:12 +10:00
public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null)
{
if (!Resolve(uid, ref aui, false))
2023-07-08 09:02:17 -07:00
return;
2023-10-11 02:17:59 -07:00
if (aui.Key == null)
{
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
return;
}
_uiSystem.CloseUi(uid, aui.Key);
}
private void OnHandDeselected(Entity<ActivatableUIComponent> ent, ref HandDeselectedEvent args)
{
if (ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand)
CloseAll(ent, ent);
}
private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args)
{
if (ent.Comp.InHandsOnly)
CloseAll(ent, ent);
}
2023-04-23 12:25:12 +10:00
}