| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- using Content.Shared.ActionBlocker;
- using Content.Shared.Actions;
- using Content.Shared.Administration.Managers;
- using Content.Shared.Containers.ItemSlots;
- using Content.Shared.Database;
- using Content.Shared.Doors.Systems;
- using Content.Shared.DoAfter;
- using Content.Shared.Electrocution;
- using Content.Shared.Intellicard;
- using Content.Shared.Interaction;
- using Content.Shared.Item.ItemToggle;
- using Content.Shared.Mind;
- using Content.Shared.Movement.Components;
- using Content.Shared.Movement.Systems;
- using Content.Shared.Popups;
- using Content.Shared.Power;
- using Content.Shared.Power.EntitySystems;
- using Content.Shared.StationAi;
- using Content.Shared.Verbs;
- using Robust.Shared.Audio;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Containers;
- using Robust.Shared.Map;
- using Robust.Shared.Map.Components;
- using Robust.Shared.Network;
- using Robust.Shared.Physics;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Serialization;
- using Robust.Shared.Timing;
- using System.Diagnostics.CodeAnalysis;
- namespace Content.Shared.Silicons.StationAi;
- public abstract partial class SharedStationAiSystem : EntitySystem
- {
- [Dependency] private readonly ISharedAdminManager _admin = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly INetManager _net = default!;
- [Dependency] private readonly ItemSlotsSystem _slots = default!;
- [Dependency] private readonly ItemToggleSystem _toggles = default!;
- [Dependency] private readonly ActionBlockerSystem _blocker = default!;
- [Dependency] private readonly MetaDataSystem _metadata = default!;
- [Dependency] private readonly SharedAirlockSystem _airlocks = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedContainerSystem _containers = default!;
- [Dependency] private readonly SharedDoorSystem _doors = default!;
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
- [Dependency] private readonly SharedElectrocutionSystem _electrify = default!;
- [Dependency] private readonly SharedEyeSystem _eye = default!;
- [Dependency] protected readonly SharedMapSystem Maps = default!;
- [Dependency] private readonly SharedMindSystem _mind = default!;
- [Dependency] private readonly SharedMoverController _mover = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedPowerReceiverSystem PowerReceiver = default!;
- [Dependency] private readonly SharedTransformSystem _xforms = default!;
- [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
- [Dependency] private readonly StationAiVisionSystem _vision = default!;
- // StationAiHeld is added to anything inside of an AI core.
- // StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core).
- // StationAiCore holds functionality related to the core itself.
- // StationAiWhitelist is a general whitelist to stop it being able to interact with anything
- // StationAiOverlay handles the static overlay. It also handles interaction blocking on client and server
- // for anything under it.
- private EntityQuery<BroadphaseComponent> _broadphaseQuery;
- private EntityQuery<MapGridComponent> _gridQuery;
- private const float MaxVisionMultiplier = 5f;
- public override void Initialize()
- {
- base.Initialize();
- _broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
- _gridQuery = GetEntityQuery<MapGridComponent>();
- InitializeAirlock();
- InitializeHeld();
- InitializeLight();
- SubscribeLocalEvent<StationAiWhitelistComponent, BoundUserInterfaceCheckRangeEvent>(OnAiBuiCheck);
- SubscribeLocalEvent<StationAiOverlayComponent, AccessibleOverrideEvent>(OnAiAccessible);
- SubscribeLocalEvent<StationAiOverlayComponent, InRangeOverrideEvent>(OnAiInRange);
- SubscribeLocalEvent<StationAiOverlayComponent, MenuVisibilityEvent>(OnAiMenu);
- SubscribeLocalEvent<StationAiHolderComponent, ComponentInit>(OnHolderInit);
- SubscribeLocalEvent<StationAiHolderComponent, ComponentRemove>(OnHolderRemove);
- SubscribeLocalEvent<StationAiHolderComponent, AfterInteractEvent>(OnHolderInteract);
- SubscribeLocalEvent<StationAiHolderComponent, MapInitEvent>(OnHolderMapInit);
- SubscribeLocalEvent<StationAiHolderComponent, EntInsertedIntoContainerMessage>(OnHolderConInsert);
- SubscribeLocalEvent<StationAiHolderComponent, EntRemovedFromContainerMessage>(OnHolderConRemove);
- SubscribeLocalEvent<StationAiHolderComponent, IntellicardDoAfterEvent>(OnIntellicardDoAfter);
- SubscribeLocalEvent<StationAiCoreComponent, EntInsertedIntoContainerMessage>(OnAiInsert);
- SubscribeLocalEvent<StationAiCoreComponent, EntRemovedFromContainerMessage>(OnAiRemove);
- SubscribeLocalEvent<StationAiCoreComponent, MapInitEvent>(OnAiMapInit);
- SubscribeLocalEvent<StationAiCoreComponent, ComponentShutdown>(OnAiShutdown);
- SubscribeLocalEvent<StationAiCoreComponent, PowerChangedEvent>(OnCorePower);
- SubscribeLocalEvent<StationAiCoreComponent, GetVerbsEvent<Verb>>(OnCoreVerbs);
- }
- private void OnCoreVerbs(Entity<StationAiCoreComponent> ent, ref GetVerbsEvent<Verb> args)
- {
- if (!_admin.IsAdmin(args.User) ||
- TryGetHeld((ent.Owner, ent.Comp), out _))
- {
- return;
- }
- var user = args.User;
- }
- private void OnAiAccessible(Entity<StationAiOverlayComponent> ent, ref AccessibleOverrideEvent args)
- {
- args.Handled = true;
- // Hopefully AI never needs storage
- if (_containers.TryGetContainingContainer(args.Target, out var targetContainer))
- {
- return;
- }
- if (!_containers.IsInSameOrTransparentContainer(args.User, args.Target, otherContainer: targetContainer))
- {
- return;
- }
- args.Accessible = true;
- }
- private void OnAiMenu(Entity<StationAiOverlayComponent> ent, ref MenuVisibilityEvent args)
- {
- args.Visibility &= ~MenuVisibility.NoFov;
- }
- private void OnAiBuiCheck(Entity<StationAiWhitelistComponent> ent, ref BoundUserInterfaceCheckRangeEvent args)
- {
- if (!HasComp<StationAiHeldComponent>(args.Actor))
- return;
- args.Result = BoundUserInterfaceRangeResult.Fail;
- // Similar to the inrange check but more optimised so server doesn't die.
- var targetXform = Transform(args.Target);
- // No cross-grid
- if (targetXform.GridUid != args.Actor.Comp.GridUid)
- {
- return;
- }
- if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid))
- {
- return;
- }
- var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates);
- lock (_vision)
- {
- if (_vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile, fastPath: true))
- {
- args.Result = BoundUserInterfaceRangeResult.Pass;
- }
- }
- }
- private void OnAiInRange(Entity<StationAiOverlayComponent> ent, ref InRangeOverrideEvent args)
- {
- args.Handled = true;
- var targetXform = Transform(args.Target);
- // No cross-grid
- if (targetXform.GridUid != Transform(args.User).GridUid)
- {
- return;
- }
- // Validate it's in camera range yes this is expensive.
- // Yes it needs optimising
- if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid))
- {
- return;
- }
- var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates);
- args.InRange = _vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile);
- }
- private void OnIntellicardDoAfter(Entity<StationAiHolderComponent> ent, ref IntellicardDoAfterEvent args)
- {
- if (args.Cancelled)
- return;
- if (args.Handled)
- return;
- if (!TryComp(args.Args.Target, out StationAiHolderComponent? targetHolder))
- return;
- // Try to insert our thing into them
- if (_slots.CanEject(ent.Owner, args.User, ent.Comp.Slot))
- {
- if (!_slots.TryInsert(args.Args.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User, excludeUserAudio: true))
- {
- return;
- }
- args.Handled = true;
- return;
- }
- // Otherwise try to take from them
- if (_slots.CanEject(args.Args.Target.Value, args.User, targetHolder.Slot))
- {
- if (!_slots.TryInsert(ent.Owner, ent.Comp.Slot, targetHolder.Slot.Item!.Value, args.User, excludeUserAudio: true))
- {
- return;
- }
- args.Handled = true;
- }
- }
- private void OnHolderInteract(Entity<StationAiHolderComponent> ent, ref AfterInteractEvent args)
- {
- if (args.Handled || !args.CanReach || args.Target == null)
- return;
- if (!TryComp(args.Target, out StationAiHolderComponent? targetHolder))
- return;
- //Don't want to download/upload between several intellicards. You can just pick it up at that point.
- if (HasComp<IntellicardComponent>(args.Target))
- return;
- if (!TryComp(args.Used, out IntellicardComponent? intelliComp))
- return;
- var cardHasAi = _slots.CanEject(ent.Owner, args.User, ent.Comp.Slot);
- var coreHasAi = _slots.CanEject(args.Target.Value, args.User, targetHolder.Slot);
- if (cardHasAi && coreHasAi)
- {
- _popup.PopupClient(Loc.GetString("intellicard-core-occupied"), args.User, args.User, PopupType.Medium);
- args.Handled = true;
- return;
- }
- if (!cardHasAi && !coreHasAi)
- {
- _popup.PopupClient(Loc.GetString("intellicard-core-empty"), args.User, args.User, PopupType.Medium);
- args.Handled = true;
- return;
- }
- if (TryGetHeld((args.Target.Value, targetHolder), out var held) && _timing.CurTime > intelliComp.NextWarningAllowed)
- {
- intelliComp.NextWarningAllowed = _timing.CurTime + intelliComp.WarningDelay;
- AnnounceIntellicardUsage(held, intelliComp.WarningSound);
- }
- var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cardHasAi ? intelliComp.UploadTime : intelliComp.DownloadTime, new IntellicardDoAfterEvent(), args.Target, ent.Owner)
- {
- BreakOnDamage = true,
- BreakOnMove = true,
- NeedHand = true,
- BreakOnDropItem = true
- };
- _doAfter.TryStartDoAfter(doAfterArgs);
- args.Handled = true;
- }
- private void OnHolderInit(Entity<StationAiHolderComponent> ent, ref ComponentInit args)
- {
- _slots.AddItemSlot(ent.Owner, StationAiHolderComponent.Container, ent.Comp.Slot);
- }
- private void OnHolderRemove(Entity<StationAiHolderComponent> ent, ref ComponentRemove args)
- {
- _slots.RemoveItemSlot(ent.Owner, ent.Comp.Slot);
- }
- private void OnHolderConInsert(Entity<StationAiHolderComponent> ent, ref EntInsertedIntoContainerMessage args)
- {
- UpdateAppearance((ent.Owner, ent.Comp));
- }
- private void OnHolderConRemove(Entity<StationAiHolderComponent> ent, ref EntRemovedFromContainerMessage args)
- {
- UpdateAppearance((ent.Owner, ent.Comp));
- }
- private void OnHolderMapInit(Entity<StationAiHolderComponent> ent, ref MapInitEvent args)
- {
- UpdateAppearance(ent.Owner);
- }
- private void OnAiShutdown(Entity<StationAiCoreComponent> ent, ref ComponentShutdown args)
- {
- // TODO: Tryqueuedel
- if (_net.IsClient)
- return;
- QueueDel(ent.Comp.RemoteEntity);
- ent.Comp.RemoteEntity = null;
- }
- private void OnCorePower(Entity<StationAiCoreComponent> ent, ref PowerChangedEvent args)
- {
- // TODO: I think in 13 they just straightup die so maybe implement that
- if (args.Powered)
- {
- if (!SetupEye(ent))
- return;
- AttachEye(ent);
- }
- else
- {
- ClearEye(ent);
- }
- }
- private void OnAiMapInit(Entity<StationAiCoreComponent> ent, ref MapInitEvent args)
- {
- SetupEye(ent);
- AttachEye(ent);
- }
- public void SwitchRemoteEntityMode(Entity<StationAiCoreComponent?> entity, bool isRemote)
- {
- if (entity.Comp?.Remote == null || entity.Comp.Remote == isRemote)
- return;
- var ent = new Entity<StationAiCoreComponent>(entity.Owner, entity.Comp);
- ent.Comp.Remote = isRemote;
- EntityCoordinates? coords = ent.Comp.RemoteEntity != null ? Transform(ent.Comp.RemoteEntity.Value).Coordinates : null;
- // Attach new eye
- ClearEye(ent);
- if (SetupEye(ent, coords))
- AttachEye(ent);
- // Adjust user FoV
- var user = GetInsertedAI(ent);
- if (TryComp<EyeComponent>(user, out var eye))
- _eye.SetDrawFov(user.Value, !isRemote);
- }
- private bool SetupEye(Entity<StationAiCoreComponent> ent, EntityCoordinates? coords = null)
- {
- if (_net.IsClient)
- return false;
- if (ent.Comp.RemoteEntity != null)
- return false;
- var proto = ent.Comp.RemoteEntityProto;
- if (coords == null)
- coords = Transform(ent.Owner).Coordinates;
- if (!ent.Comp.Remote)
- proto = ent.Comp.PhysicalEntityProto;
- if (proto != null)
- {
- ent.Comp.RemoteEntity = SpawnAtPosition(proto, coords.Value);
- Dirty(ent);
- }
- return true;
- }
- private void ClearEye(Entity<StationAiCoreComponent> ent)
- {
- if (_net.IsClient)
- return;
- QueueDel(ent.Comp.RemoteEntity);
- ent.Comp.RemoteEntity = null;
- Dirty(ent);
- }
- private void AttachEye(Entity<StationAiCoreComponent> ent)
- {
- if (ent.Comp.RemoteEntity == null)
- return;
- if (!_containers.TryGetContainer(ent.Owner, StationAiHolderComponent.Container, out var container) ||
- container.ContainedEntities.Count != 1)
- {
- return;
- }
- // Attach them to the portable eye that can move around.
- var user = container.ContainedEntities[0];
- if (TryComp(user, out EyeComponent? eyeComp))
- {
- _eye.SetDrawFov(user, false, eyeComp);
- _eye.SetTarget(user, ent.Comp.RemoteEntity.Value, eyeComp);
- }
- _mover.SetRelay(user, ent.Comp.RemoteEntity.Value);
- }
- private EntityUid? GetInsertedAI(Entity<StationAiCoreComponent> ent)
- {
- if (!_containers.TryGetContainer(ent.Owner, StationAiHolderComponent.Container, out var container) ||
- container.ContainedEntities.Count != 1)
- {
- return null;
- }
- return container.ContainedEntities[0];
- }
- private void OnAiInsert(Entity<StationAiCoreComponent> ent, ref EntInsertedIntoContainerMessage args)
- {
- if (args.Container.ID != StationAiCoreComponent.Container)
- return;
- if (_timing.ApplyingState)
- return;
- ent.Comp.Remote = true;
- SetupEye(ent);
- // Just so text and the likes works properly
- _metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName);
- AttachEye(ent);
- }
- private void OnAiRemove(Entity<StationAiCoreComponent> ent, ref EntRemovedFromContainerMessage args)
- {
- if (_timing.ApplyingState)
- return;
- ent.Comp.Remote = true;
- // Reset name to whatever
- _metadata.SetEntityName(ent.Owner, Prototype(ent.Owner)?.Name ?? string.Empty);
- // Remove eye relay
- RemCompDeferred<RelayInputMoverComponent>(args.Entity);
- if (TryComp(args.Entity, out EyeComponent? eyeComp))
- {
- _eye.SetDrawFov(args.Entity, true, eyeComp);
- _eye.SetTarget(args.Entity, null, eyeComp);
- }
- ClearEye(ent);
- }
- private void UpdateAppearance(Entity<StationAiHolderComponent?> entity)
- {
- if (!Resolve(entity.Owner, ref entity.Comp, false))
- return;
- if (!_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) ||
- container.Count == 0)
- {
- _appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty);
- return;
- }
- _appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied);
- }
- public virtual void AnnounceIntellicardUsage(EntityUid uid, SoundSpecifier? cue = null) { }
- public virtual bool SetVisionEnabled(Entity<StationAiVisionComponent> entity, bool enabled, bool announce = false)
- {
- if (entity.Comp.Enabled == enabled)
- return false;
- entity.Comp.Enabled = enabled;
- Dirty(entity);
- return true;
- }
- public virtual bool SetWhitelistEnabled(Entity<StationAiWhitelistComponent> entity, bool value, bool announce = false)
- {
- if (entity.Comp.Enabled == value)
- return false;
- entity.Comp.Enabled = value;
- Dirty(entity);
- return true;
- }
- /// <summary>
- /// BUI validation for ai interactions.
- /// </summary>
- private bool ValidateAi(Entity<StationAiHeldComponent?> entity)
- {
- if (!Resolve(entity.Owner, ref entity.Comp, false))
- {
- return false;
- }
- return _blocker.CanComplexInteract(entity.Owner);
- }
- }
- public sealed partial class JumpToCoreEvent : InstantActionEvent
- {
- }
- [Serializable, NetSerializable]
- public sealed partial class IntellicardDoAfterEvent : SimpleDoAfterEvent;
- [Serializable, NetSerializable]
- public enum StationAiVisualState : byte
- {
- Key,
- }
- [Serializable, NetSerializable]
- public enum StationAiState : byte
- {
- Empty,
- Occupied,
- Dead,
- }
|