Files
crystall-punk-14/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs
Fildrance 4bcf3c3c0e Station AI ability to electricute doors (#32012)
* Boom! Emergency access!

* Emergency access sound

* locale

* Updated sounds

* bleh

* Door electrify base

* feat: popups on attempt to activate AI action when wires cut

* refactor: use SharedApcPowerReceiverComponent to check if AI can interact with door

* refactor: added icon and sound for door overcharge

* meta.json should use tabs not spaces

* refactor: extracted sounds for airlock overcharge to static field in system

* refactor: cleanup, ScarKy0 mentions for resources

* refactor: removed unused textures

* feat: now notification is displayed when AI attempting to interact with door which have wire cut

* StationAiWhitelistComponent is properly gating  BUI OnMessageAttempt, SharedPowerReceiverSystem.IsPowered is now used to check if device powered

* refactor: use PlayLocal to play electrify sound only for AI player

* refactor: SetBoltsDown now uses TrySetBoltDown, checks for power.

* bolts now check for power using SharedPowerReceiverSystem

* electrify localization and louder electrify sounds

* extracted ShowDeviceNotRespondingPopup, reverted airlocks not opening/closing when ai wire was cut

* refactor: cleanup

* New sprites and fixes

* Copyright

* even more sprite changes

* refactore: cleanup, rename overcharge => electrify

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-09-27 17:22:17 +10:00

230 lines
6.9 KiB
C#

using System.Diagnostics.CodeAnalysis;
using Content.Shared.Actions.Events;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Silicons.StationAi;
public abstract partial class SharedStationAiSystem
{
/*
* Added when an entity is inserted into a StationAiCore.
*/
//TODO: Fix this, please
private const string JobNameLocId = "job-name-station-ai";
private void InitializeHeld()
{
SubscribeLocalEvent<StationAiRadialMessage>(OnRadialMessage);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnMessageAttempt);
SubscribeLocalEvent<StationAiWhitelistComponent, GetVerbsEvent<AlternativeVerb>>(OnTargetVerbs);
SubscribeLocalEvent<StationAiHeldComponent, InteractionAttemptEvent>(OnHeldInteraction);
SubscribeLocalEvent<StationAiHeldComponent, AttemptRelayActionComponentChangeEvent>(OnHeldRelay);
SubscribeLocalEvent<StationAiHeldComponent, JumpToCoreEvent>(OnCoreJump);
SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
}
private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args)
{
if (args.Handled)
{
return;
}
if (!HasComp<StationAiHeldComponent>(args.ForActor))
{
return;
}
args.Title = $"{Name(args.ForActor)} ({Loc.GetString(JobNameLocId)})";
args.Handled = true;
}
private void OnCoreJump(Entity<StationAiHeldComponent> ent, ref JumpToCoreEvent args)
{
if (!TryGetCore(ent.Owner, out var core) || core.Comp?.RemoteEntity == null)
return;
_xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ;
}
/// <summary>
/// Tries to get the entity held in the AI core.
/// </summary>
private bool TryGetHeld(Entity<StationAiCoreComponent?> entity, out EntityUid held)
{
held = EntityUid.Invalid;
if (!Resolve(entity.Owner, ref entity.Comp))
return false;
if (!_containers.TryGetContainer(entity.Owner, StationAiCoreComponent.Container, out var container) ||
container.ContainedEntities.Count == 0)
return false;
held = container.ContainedEntities[0];
return true;
}
private bool TryGetCore(EntityUid ent, out Entity<StationAiCoreComponent?> core)
{
if (!_containers.TryGetContainingContainer(ent, out var container) ||
container.ID != StationAiCoreComponent.Container ||
!TryComp(container.Owner, out StationAiCoreComponent? coreComp) ||
coreComp.RemoteEntity == null)
{
core = (EntityUid.Invalid, null);
return false;
}
core = (container.Owner, coreComp);
return true;
}
private void OnHeldRelay(Entity<StationAiHeldComponent> ent, ref AttemptRelayActionComponentChangeEvent args)
{
if (!TryGetCore(ent.Owner, out var core))
return;
args.Target = core.Comp?.RemoteEntity;
}
private void OnRadialMessage(StationAiRadialMessage ev)
{
if (!TryGetEntity(ev.Entity, out var target))
return;
ev.Event.User = ev.Actor;
RaiseLocalEvent(target.Value, (object) ev.Event);
}
private void OnMessageAttempt(BoundUserInterfaceMessageAttempt ev)
{
if (ev.Actor == ev.Target)
return;
if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) &&
(!TryComp(ev.Target, out StationAiWhitelistComponent? whitelistComponent) ||
!ValidateAi((ev.Actor, aiComp))))
{
if (whitelistComponent is { Enabled: false })
{
ShowDeviceNotRespondingPopup(ev.Actor);
}
ev.Cancel();
}
}
private void OnHeldInteraction(Entity<StationAiHeldComponent> ent, ref InteractionAttemptEvent args)
{
// Cancel if it's not us or something with a whitelist, or whitelist is disabled.
args.Cancelled = (!TryComp(args.Target, out StationAiWhitelistComponent? whitelistComponent)
|| !whitelistComponent.Enabled)
&& ent.Owner != args.Target
&& args.Target != null;
if (whitelistComponent is { Enabled: false })
{
ShowDeviceNotRespondingPopup(ent.Owner);
}
}
private void OnTargetVerbs(Entity<StationAiWhitelistComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanComplexInteract
|| !HasComp<StationAiHeldComponent>(args.User))
{
return;
}
var user = args.User;
var target = args.Target;
var isOpen = _uiSystem.IsUiOpen(target, AiUi.Key, user);
var verb = new AlternativeVerb
{
Text = isOpen ? Loc.GetString("ai-close") : Loc.GetString("ai-open"),
Act = () =>
{
// no need to show menu if device is not powered.
if (!PowerReceiver.IsPowered(ent.Owner))
{
ShowDeviceNotRespondingPopup(user);
return;
}
if (isOpen)
{
_uiSystem.CloseUi(ent.Owner, AiUi.Key, user);
}
else
{
_uiSystem.OpenUi(ent.Owner, AiUi.Key, user);
}
}
};
args.Verbs.Add(verb);
}
private void ShowDeviceNotRespondingPopup(EntityUid toEntity)
{
_popup.PopupClient(Loc.GetString("ai-device-not-responding"), toEntity, PopupType.MediumCaution);
}
}
/// <summary>
/// Raised from client to server as a BUI message wrapping the event to perform.
/// Also handles AI action validation.
/// </summary>
[Serializable, NetSerializable]
public sealed class StationAiRadialMessage : BoundUserInterfaceMessage
{
public BaseStationAiAction Event = default!;
}
// Do nothing on server just here for shared move along.
/// <summary>
/// Raised on client to get the relevant data for radial actions.
/// </summary>
public sealed class StationAiRadial : BaseStationAiAction
{
public SpriteSpecifier? Sprite;
public string? Tooltip;
public BaseStationAiAction Event = default!;
}
/// <summary>
/// Abstract parent for radial actions events.
/// When a client requests a radial action this will get sent.
/// </summary>
[Serializable, NetSerializable]
public abstract class BaseStationAiAction
{
[field:NonSerialized]
public EntityUid User { get; set; }
}
// No idea if there's a better way to do this.
/// <summary>
/// Grab actions possible for an AI on the target entity.
/// </summary>
[ByRefEvent]
public record struct GetStationAiRadialEvent()
{
public List<StationAiRadial> Actions = new();
}
[Serializable, NetSerializable]
public enum AiUi : byte
{
Key,
}