mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 01:15:13 +01:00
* Initial resources commit * Initial code commit * Added additional resources * Continuing to build holopad and telephone systems * Added hologram shader * Added hologram system and entity * Holo calls now have a hologram of the user appear on them * Initial implementation of holopads transmitting nearby chatter * Added support for linking across multiple telephones/holopads/entities * Fixed a bunch of bugs * Tried simplifying holopad entity dependence, added support for mid-call user switching * Replaced PVS expansion with manually networked sprite states * Adjusted volume of ring tone * Added machine board * Minor features and tweaks * Resolving merge conflict * Recommit audio attributions * Telephone chat adjustments * Added support for AI interactions with holopads * Building the holopad UI * Holopad UI finished * Further UI tweaks * Station AI can hear local chatter when being projected from a holopad * Minor bug fixes * Added wire panels to holopads * Basic broadcasting * Start of emergency broadcasting code * Fixing issues with broadcasting * More work on emergency broadcasting * Updated holopad visuals * Added cooldown text to emergency broadcast and control lock out screen * Code clean up * Fixed issue with timing * Broadcasting now requires command access * Fixed some bugs * Added multiple holopad prototypes with different ranges * The AI no longer requires power to interact with holopads * Fixed some additional issues * Addressing more issues * Added emote support for holograms * Changed the broadcast lockout durations to their proper values * Added AI vision wire to holopads * Bug fixes * AI vision and interaction wires can be added to the same wire panel * Fixed error * More bug fixes * Fixed test fail * Embellished the emergency call lock out window * Holopads play borg sounds when speaking * Borg and AI names are listed as the caller ID on the holopad * Borg chassis can now be seen on holopad holograms * Holopad returns to a machine frame when badly damaged * Clarified some text * Fix merge conflict * Fixed merge conflict * Fixing merge conflict * Fixing merge conflict * Fixing merge conflict * Offset menu on open * AI can alt click on holopads to activate the projector * Bug fixes for intellicard interactions * Fixed speech issue with intellicards * The UI automatically opens for the AI when it alt-clicks on the holopad * Simplified shader math * Telephones will auto hang up 60 seconds after the last person on a call stops speaking * Added better support for AI requests when multiple AI cores are on the station * The call controls pop up for the AI when they accept a summons from a holopad * Compatibility mode fix for the hologram shader * Further shader fixes for compatibility mode * File clean up * More cleaning up * Removed access requirements from quantum holopads so they can used by nukies * The title of the holopad window now reflects the name of the device * Linked telephones will lose their connection if both move out of range of each other
587 lines
19 KiB
C#
587 lines
19 KiB
C#
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.GameObjects;
|
|
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;
|
|
|
|
[ValidatePrototypeId<EntityPrototype>]
|
|
private static readonly EntProtoId DefaultAi = "StationAiBrain";
|
|
|
|
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;
|
|
|
|
args.Verbs.Add(new Verb()
|
|
{
|
|
Text = Loc.GetString("station-ai-takeover"),
|
|
Category = VerbCategory.Debug,
|
|
Act = () =>
|
|
{
|
|
var brain = SpawnInContainerOrDrop(DefaultAi, ent.Owner, StationAiCoreComponent.Container);
|
|
_mind.ControlMob(user, brain);
|
|
},
|
|
Impact = LogImpact.High,
|
|
});
|
|
}
|
|
|
|
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 (TryGetHeldFromHolder((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> ent, bool isRemote)
|
|
{
|
|
if (isRemote == ent.Comp.Remote)
|
|
return;
|
|
|
|
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 bool TryGetStationAiCore(Entity<StationAiHeldComponent?> ent, [NotNullWhen(true)] out Entity<StationAiCoreComponent>? parentEnt)
|
|
{
|
|
parentEnt = null;
|
|
var parent = Transform(ent).ParentUid;
|
|
|
|
if (!parent.IsValid())
|
|
return false;
|
|
|
|
if (!TryComp<StationAiCoreComponent>(parent, out var stationAiCore))
|
|
return false;
|
|
|
|
parentEnt = new Entity<StationAiCoreComponent>(parent, stationAiCore);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool TryGetInsertedAI(Entity<StationAiCoreComponent> ent, [NotNullWhen(true)] out Entity<StationAiHeldComponent>? insertedAi)
|
|
{
|
|
insertedAi = null;
|
|
var insertedEnt = GetInsertedAI(ent);
|
|
|
|
if (TryComp<StationAiHeldComponent>(insertedEnt, out var stationAiHeld))
|
|
{
|
|
insertedAi = (insertedEnt.Value, stationAiHeld);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|