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

314 lines
10 KiB
C#
Raw 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;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
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 SharedContainerSystem _container = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
private readonly List<EntityUid> _toClose = new();
2023-04-23 12:25:12 +10:00
public override void Initialize()
{
base.Initialize();
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);
// TODO ActivatableUI
// Add UI-user component, and listen for user container changes.
// I.e., should lose a computer UI if a player gets shut into a locker.
SubscribeLocalEvent<ActivatableUIComponent, EntGotInsertedIntoContainerMessage>(OnGotInserted);
SubscribeLocalEvent<ActivatableUIComponent, EntGotRemovedFromContainerMessage>(OnGotRemoved);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
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
2023-04-23 12:25:12 +10:00
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
{
if (!TryComp(ev.Target, out ActivatableUIComponent? comp))
return;
2022-03-15 16:59:20 +13:00
2023-04-23 12:25:12 +10:00
if (!comp.RequireHands)
return;
2022-03-15 16:59:20 +13:00
if (!TryComp(ev.Actor, out HandsComponent? hands) || hands.Hands.Count == 0)
2023-04-23 12:25:12 +10:00
ev.Cancel();
}
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
{
// TODO VERBS add "open UI" icon
Act = () => InteractUI(args.User, uid, component),
Text = Loc.GetString(component.VerbText)
});
}
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
{
// TODO VERBS add "open UI" icon
Act = () => InteractUI(args.User, uid, component),
Text = Loc.GetString(component.VerbText)
});
}
private bool ShouldAddVerb<T>(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<T> args) where T : Verb
{
if (!args.CanAccess)
return false;
if (component.RequireHands)
{
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 || component.AllowSpectator && HasComp<GhostComponent>(args.User);
2023-04-23 12:25:12 +10:00
}
2023-04-23 12:25:12 +10:00
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
{
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;
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 (!component.RequiredItems.IsValid(args.Used, EntityManager))
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) && (!aui.AllowSpectator || !HasComp<GhostComponent>(user)))
2023-10-11 02:17:59 -07:00
return false;
if (aui.RequireHands)
{
if (!TryComp(user, out HandsComponent? hands))
return false;
if (aui.InHandsOnly)
{
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.PopupEntity(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.RequireHands && ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand)
CloseAll(ent, ent);
}
private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args)
{
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly)
CloseAll(ent, ent);
}
private void OnGotInserted(Entity<ActivatableUIComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
CheckAccess((ent, ent));
}
private void OnGotRemoved(Entity<ActivatableUIComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
CheckAccess((ent, ent));
}
public void CheckAccess(Entity<ActivatableUIComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp))
2023-10-11 02:17:59 -07:00
return;
if (ent.Comp.Key == null)
{
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}");
2023-04-23 12:25:12 +10:00
return;
}
foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key))
{
if (!_container.IsInSameOrParentContainer(user, ent)
&& !_interaction.CanAccessViaStorage(user, ent))
{
_toClose.Add(user);
continue;
}
if (!_interaction.InRangeUnobstructed(user, ent))
_toClose.Add(user);
}
foreach (var user in _toClose)
{
_uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user);
}
2023-10-11 02:17:59 -07:00
_toClose.Clear();
}
2023-04-23 12:25:12 +10:00
}