using System.Linq;
using Content.Server.Chat.Managers;
-using Content.Server.Chat.Systems;
using Content.Shared.Chat;
+using Content.Shared.Mind;
+using Content.Shared.Roles;
using Content.Shared.Silicons.StationAi;
using Content.Shared.StationAi;
-using Robust.Shared.Audio.Systems;
+using Robust.Shared.Audio;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
{
[Dependency] private readonly IChatManager _chats = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+ [Dependency] private readonly SharedRoleSystem _roles = default!;
private readonly HashSet<Entity<StationAiCoreComponent>> _ais = new();
return true;
}
+ public override void AnnounceIntellicardUsage(EntityUid uid, SoundSpecifier? cue = null)
+ {
+ if (!TryComp<ActorComponent>(uid, out var actor))
+ return;
+
+ var msg = Loc.GetString("ai-consciousness-download-warning");
+ var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
+ _chats.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Red);
+
+ if (cue != null && _mind.TryGetMind(uid, out var mindId, out _))
+ _roles.MindPlaySound(mindId, cue);
+ }
+
private void AnnounceSnip(EntityUid entity)
{
var xform = Transform(entity);
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Intellicard;
+
+/// <summary>
+/// Allows this entity to download the station AI onto an AiHolderComponent.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class IntellicardComponent : Component
+{
+ /// <summary>
+ /// The duration it takes to download the AI from an AiHolder.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int DownloadTime = 15;
+
+ /// <summary>
+ /// The duration it takes to upload the AI to an AiHolder.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int UploadTime = 3;
+
+ /// <summary>
+ /// The sound that plays for the AI
+ /// when they are being downloaded
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public SoundSpecifier? WarningSound = new SoundPathSpecifier("/Audio/Misc/notice2.ogg");
+
+ /// <summary>
+ /// The delay before allowing the warning to play again in seconds.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan WarningDelay = TimeSpan.FromSeconds(8);
+
+ [ViewVariables]
+ public TimeSpan NextWarningAllowed = TimeSpan.Zero;
+}
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.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.Components;
[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!;
SubscribeLocalEvent<StationAiHolderComponent, MapInitEvent>(OnHolderMapInit);
SubscribeLocalEvent<StationAiHolderComponent, EntInsertedIntoContainerMessage>(OnHolderConInsert);
SubscribeLocalEvent<StationAiHolderComponent, EntRemovedFromContainerMessage>(OnHolderConRemove);
+ SubscribeLocalEvent<StationAiHolderComponent, IntellicardDoAfterEvent>(OnIntellicardDoAfter);
SubscribeLocalEvent<StationAiCoreComponent, EntInsertedIntoContainerMessage>(OnAiInsert);
SubscribeLocalEvent<StationAiCoreComponent, EntRemovedFromContainerMessage>(OnAiRemove);
args.InRange = _vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile);
}
- private void OnHolderInteract(Entity<StationAiHolderComponent> ent, ref AfterInteractEvent args)
+
+ private void OnIntellicardDoAfter(Entity<StationAiHolderComponent> ent, ref IntellicardDoAfterEvent args)
{
- if (!TryComp(args.Target, out StationAiHolderComponent? targetHolder))
+ 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.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User, excludeUserAudio: true))
+ if (!_slots.TryInsert(args.Args.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User, excludeUserAudio: true))
{
return;
}
}
// Otherwise try to take from them
- if (_slots.CanEject(args.Target.Value, args.User, targetHolder.Slot))
+ 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))
{
}
}
+ 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);
_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)
}
+[Serializable, NetSerializable]
+public sealed partial class IntellicardDoAfterEvent : SimpleDoAfterEvent;
+
+
[Serializable, NetSerializable]
public enum StationAiVisualState : byte
{