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(OnRadialMessage); SubscribeLocalEvent(OnMessageAttempt); SubscribeLocalEvent>(OnTargetVerbs); SubscribeLocalEvent(OnHeldInteraction); SubscribeLocalEvent(OnHeldRelay); SubscribeLocalEvent(OnCoreJump); SubscribeLocalEvent(OnTryGetIdentityShortInfo); } private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args) { if (args.Handled) { return; } if (!HasComp(args.ForActor)) { return; } args.Title = $"{Name(args.ForActor)} ({Loc.GetString(JobNameLocId)})"; args.Handled = true; } private void OnCoreJump(Entity ent, ref JumpToCoreEvent args) { if (!TryGetCore(ent.Owner, out var core) || core.Comp?.RemoteEntity == null) return; _xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ; } /// /// Tries to get the entity held in the AI core using StationAiCore. /// public bool TryGetHeld(Entity 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; } /// /// Tries to get the entity held in the AI using StationAiHolder. /// public bool TryGetHeld(Entity entity, out EntityUid held) { TryComp(entity.Owner, out var stationAiCore); return TryGetHeld((entity.Owner, stationAiCore), out held); } public bool TryGetCore(EntityUid entity, out Entity core) { var xform = Transform(entity); var meta = MetaData(entity); var ent = new Entity(entity, xform, meta); 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 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 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 ent, ref GetVerbsEvent args) { if (!args.CanComplexInteract || !HasComp(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); } } /// /// Raised from client to server as a BUI message wrapping the event to perform. /// Also handles AI action validation. /// [Serializable, NetSerializable] public sealed class StationAiRadialMessage : BoundUserInterfaceMessage { public BaseStationAiAction Event = default!; } // Do nothing on server just here for shared move along. /// /// Raised on client to get the relevant data for radial actions. /// public sealed class StationAiRadial : BaseStationAiAction { public SpriteSpecifier? Sprite; public string? Tooltip; public BaseStationAiAction Event = default!; } /// /// Abstract parent for radial actions events. /// When a client requests a radial action this will get sent. /// [Serializable, NetSerializable] public abstract class BaseStationAiAction { [field:NonSerialized] public EntityUid User { get; set; } } // No idea if there's a better way to do this. /// /// Grab actions possible for an AI on the target entity. /// [ByRefEvent] public record struct GetStationAiRadialEvent() { public List Actions = new(); } [Serializable, NetSerializable] public enum AiUi : byte { Key, }