diff --git a/Content.Client/Overlays/ShowMindShieldIconsSystem.cs b/Content.Client/Overlays/ShowMindShieldIconsSystem.cs index 6ae2b46f79..494b80cfc5 100644 --- a/Content.Client/Overlays/ShowMindShieldIconsSystem.cs +++ b/Content.Client/Overlays/ShowMindShieldIconsSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Mindshield.Components; +using Content.Shared.Mindshield; using Content.Shared.Overlays; using Content.Shared.StatusIcon.Components; @@ -6,29 +6,17 @@ namespace Content.Client.Overlays; public sealed partial class ShowMindShieldIconsSystem : EquipmentHudSystem { - public override void Initialize() - { - base.Initialize(); + [Dependency] private MindShieldSystem _mindShield = default!; - SubscribeLocalEvent(OnGetStatusIconsEvent); - SubscribeLocalEvent(OnGetStatusIconsEventFake); - } - // TODO: Probably need to get this OFF of client since this can be read by bad actors rather easily - // ...imagine cheating in a game about silly paper dolls - private void OnGetStatusIconsEventFake(EntityUid uid, FakeMindShieldComponent component, ref GetStatusIconsEvent ev) - { - if (!IsActive) - return; - if (component.IsEnabled && ProtoMan.Resolve(component.MindShieldStatusIcon, out var fakeStatusIconPrototype)) - ev.StatusIcons.Add(fakeStatusIconPrototype); - } - - private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev) + [SubscribeLocalEvent] + private void OnGetStatusIconsEvent(Entity ent, ref GetStatusIconsEvent args) { + // Is active checks for our ability to display status icons if (!IsActive) return; - if (ProtoMan.Resolve(component.MindShieldStatusIcon, out var iconPrototype)) - ev.StatusIcons.Add(iconPrototype); + _mindShield.GetMindshieldStatus(ent.Owner, out _, out var isVisible); + if (isVisible && ProtoMan.Resolve(MindShieldSystem.StatusIcon, out var statusIconPrototype)) + args.StatusIcons.Add(statusIconPrototype); } } diff --git a/Content.Client/Revolutionary/RevolutionarySystem.cs b/Content.Client/Revolutionary/RevolutionarySystem.cs deleted file mode 100644 index 4edd4d3669..0000000000 --- a/Content.Client/Revolutionary/RevolutionarySystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.Revolutionary.Components; -using Content.Shared.Revolutionary; -using Content.Shared.StatusIcon.Components; - -namespace Content.Client.Revolutionary; - -/// -/// Used for the client to get status icons from other revs. -/// -public sealed partial class RevolutionarySystem : SharedRevolutionarySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(GetRevIcon); - SubscribeLocalEvent(GetHeadRevIcon); - } - - private void GetRevIcon(Entity ent, ref GetStatusIconsEvent args) - { - if (HasComp(ent)) - return; - - if (ProtoMan.Resolve(ent.Comp.StatusIcon, out var iconPrototype)) - args.StatusIcons.Add(iconPrototype); - } - - private void GetHeadRevIcon(Entity ent, ref GetStatusIconsEvent args) - { - if (ProtoMan.Resolve(ent.Comp.StatusIcon, out var iconPrototype)) - args.StatusIcons.Add(iconPrototype); - } -} diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index 2c8dbf0486..9a63c0fca1 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -30,6 +30,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Content.Shared.Cuffs.Components; using Robust.Shared.Player; +using Content.Shared.Mindshield; namespace Content.Server.GameTicking.Rules; @@ -52,6 +53,7 @@ public sealed partial class RevolutionaryRuleSystem : GameRuleSystem RevolutionaryNpcFaction = "Revolutionary"; @@ -152,7 +154,8 @@ public sealed partial class RevolutionaryRuleSystem : GameRuleSystem -/// System used for adding or removing components with a mindshield implant -/// as well as checking if the implanted is a Rev or Head Rev. -/// -public sealed partial class MindShieldSystem : EntitySystem -{ - [Dependency] private IAdminLogManager _adminLogManager = default!; - [Dependency] private RoleSystem _roleSystem = default!; - [Dependency] private MindSystem _mindSystem = default!; - [Dependency] private PopupSystem _popupSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnImplantImplanted); - SubscribeLocalEvent(OnImplantRemoved); - SubscribeLocalEvent(OnAttemptConvert); - } - - private void OnImplantImplanted(Entity ent, ref ImplantImplantedEvent ev) - { - EnsureComp(ev.Implanted); - MindShieldRemovalCheck(ev.Implanted, ev.Implant); - } - - /// - /// Checks if the implanted person was a Rev or Head Rev and remove role or destroy mindshield respectively. - /// - private void MindShieldRemovalCheck(EntityUid implanted, EntityUid implant) - { - if (HasComp(implanted)) - { - _popupSystem.PopupEntity(Loc.GetString("head-rev-break-mindshield"), implanted); - QueueDel(implant); - return; - } - - if (_mindSystem.TryGetMind(implanted, out var mindId, out _) && - _roleSystem.MindRemoveRole(mindId)) - { - _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(implanted)} was deconverted due to being implanted with a Mindshield."); - } - } - - private void OnImplantRemoved(Entity ent, ref ImplantRemovedEvent args) - { - RemComp(args.Implanted); - } - - private void OnAttemptConvert(Entity ent, ref AttemptConvertRevolutionaryEvent args) - { - args.Cancelled = true; - } -} - diff --git a/Content.Server/Revolutionary/RevolutionarySystem.cs b/Content.Server/Revolutionary/RevolutionarySystem.cs deleted file mode 100644 index 597c4896b9..0000000000 --- a/Content.Server/Revolutionary/RevolutionarySystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.Revolutionary; - -namespace Content.Server.Revolutionary; - -public sealed class RevolutionarySystem : SharedRevolutionarySystem; diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 51bb5827db..a324ed2c42 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -1,13 +1,13 @@ using System.Linq; using Content.Server.Actions; using Content.Server.Administration.Logs; +using Content.Shared.Mindshield; using Content.Server.Stack; using Content.Server.Store.Components; using Content.Shared.Actions; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; -using Content.Shared.Mindshield.Components; using Content.Shared.NPC.Systems; using Content.Shared.Store; using Content.Shared.Store.Components; @@ -26,6 +26,7 @@ public sealed partial class StoreSystem [Dependency] private SharedAudioSystem _audio = default!; [Dependency] private SharedHandsSystem _hands = default!; [Dependency] private StackSystem _stack = default!; + [Dependency] private MindShieldSystem _mindShield = default!; private void InitializeUi() { @@ -221,7 +222,8 @@ public sealed partial class StoreSystem logImpact = LogImpact.High; logExtraInfo = ", but was not from an expected faction"; - if (HasComp(buyer)) + _mindShield.GetMindshieldStatus(buyer, out var isMindshielded, out _); + if (isMindshielded) { logImpact = LogImpact.Extreme; logExtraInfo += " while also possessing a mindshield"; diff --git a/Content.Shared/Implants/MindshieldImplantSystem.cs b/Content.Shared/Implants/MindshieldImplantSystem.cs new file mode 100644 index 0000000000..185874b696 --- /dev/null +++ b/Content.Shared/Implants/MindshieldImplantSystem.cs @@ -0,0 +1,52 @@ +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.IdentityManagement; +using Content.Shared.Mind; +using Content.Shared.Mindshield.Components; +using Content.Shared.Popups; +using Content.Shared.Revolutionary.Components; +using Content.Shared.Roles; +using Content.Shared.Roles.Components; +using Content.Shared.Stunnable; + +namespace Content.Shared.Implants; + +public sealed partial class MindshieldImplantSystem : EntitySystem +{ + [Dependency] private SharedPopupSystem _popup = default!; + [Dependency] private SharedStunSystem _sharedStun = default!; + [Dependency] private SharedMindSystem _mind = default!; + [Dependency] private SharedRoleSystem _role = default!; + [Dependency] private ISharedAdminLogManager _log = default!; + + /// + /// When the mindshield is implanted in the rev it will popup saying they were deconverted. In Head Revs it will remove the mindshield component. + /// + [SubscribeLocalEvent] + private void OnImplantImplanted(Entity ent, ref ImplantImplantedEvent args) + { + // Entity that was implanted + var uid = args.Implanted; + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("head-rev-break-mindshield"), uid); + PredictedQueueDel(args.Implant); + return; + } + + if (TryComp(uid, out var comp)) + { + if (_mind.TryGetMind(uid, out var mindId, out _)) + { + _log.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to being implanted with a Mindshield."); + + if (_role.MindHasRole(mindId)) + _role.MindRemoveRole(mindId); + } + var name = Identity.Entity(uid, EntityManager); + RemComp(uid); + _sharedStun.TryUpdateParalyzeDuration(uid, comp.StunTime); + _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid); + } + } +} diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs index b92369ab44..c14c6210e8 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs @@ -2,6 +2,7 @@ using Content.Shared.Chat; using Content.Shared.IdentityManagement.Components; using Content.Shared.Implants.Components; using Content.Shared.Interaction.Events; +using Content.Shared.Mindshield; using Content.Shared.Mobs; using Content.Shared.Store; using Content.Shared.VoiceMask; @@ -18,10 +19,13 @@ public abstract partial class SharedSubdermalImplantSystem SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); + SubscribeLocalEvent(RelayToImplantEvent); // Ref relays, for when you need to write to the event! SubscribeLocalEvent(RefRelayToImplantEvent); SubscribeLocalEvent(RefRelayToImplantEvent); + SubscribeLocalEvent(RefRelayToImplantEvent, after: [typeof(MindShieldSystem)]); + SubscribeLocalEvent(RefRelayToImplantEvent); } /// diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 7283fb6153..39724ffd75 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -16,6 +16,7 @@ using Content.Shared.Gravity; using Content.Shared.IdentityManagement.Components; using Content.Shared.Implants; using Content.Shared.Inventory.Events; +using Content.Shared.Mindshield; using Content.Shared.Lube; using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; @@ -64,6 +65,7 @@ public partial class InventorySystem SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); + SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); @@ -85,6 +87,7 @@ public partial class InventorySystem SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent, after: [typeof(SharedSubdermalImplantSystem)]); // ordering so that things like mindshield-flipping hats can be done SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); diff --git a/Content.Shared/Mindshield/Components/FakeMindShieldImplantComponent.cs b/Content.Shared/Mindshield/Components/FakeMindShieldImplantComponent.cs deleted file mode 100644 index 788de804f4..0000000000 --- a/Content.Shared/Mindshield/Components/FakeMindShieldImplantComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Mindshield.Components; - -[RegisterComponent, NetworkedComponent] -public sealed partial class FakeMindShieldImplantComponent : Component -{ -} diff --git a/Content.Shared/Mindshield/Components/FakeMindshieldComponent.cs b/Content.Shared/Mindshield/Components/FakeMindshieldComponent.cs index 106f43367e..5705398d5b 100644 --- a/Content.Shared/Mindshield/Components/FakeMindshieldComponent.cs +++ b/Content.Shared/Mindshield/Components/FakeMindshieldComponent.cs @@ -1,22 +1,57 @@ -using Content.Shared.StatusIcon; +using Content.Shared.Tag; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; namespace Content.Shared.Mindshield.Components; +/// +/// A toggleable fake mindshield that only produces mindshield visuals, but does not protect against anything. +/// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(MindShieldSystem), typeof(FakeMindShieldSystem))] public sealed partial class FakeMindShieldComponent : Component { - /// /// The state of the Fake mindshield, if true the owning entity will display a mindshield effect on their job icon /// [DataField, AutoNetworkedField] - public bool IsEnabled { get; set; } = false; + public bool IsEnabled; /// - /// The Security status icon displayed to the security officer. Should be a duplicate of the one the mindshield uses since it's spoofing that + /// This tag should be placed on the fake mindshield action so there is a way to easily identify which action goes with which fake mindshield. Of course, this implies that no two similar fake mindshields should be on the same entity at the same time. /// [DataField, AutoNetworkedField] - public ProtoId MindShieldStatusIcon = "MindShieldIcon"; + public ProtoId ActionTag = "FakeMindShieldImplant"; + + /// + /// Makes it so chameleon implants affect this fake mindshield. + /// + [DataField, AutoNetworkedField] + public bool ChameleonControllable = true; + + /// + /// Whether the fake mindshield is innate to the entity. + /// When added to an entity while this field is set to true, the entity itself will gain the action & UI necessary to toggle its fake mindshield. + /// When this field is set to false, then the entity with this component will be a provider (either through implanting or through wearing) of the fake mindshield abilities for another entity. + /// + [DataField, AutoNetworkedField] + public bool IsInnate; + + /// + /// Action linked to the fake mindshield, used for innate components + /// + [DataField, AutoNetworkedField] + public EntProtoId Action = "FakeMindShieldToggleAction"; + + /// + /// Popup to show to the owner of the fake mindshield when it is enabled. + /// + [DataField, AutoNetworkedField] + public LocId EnabledPopup = "fake-mindshield-enabled"; + + /// + /// Popup to show to the owner of the fake mindshield when it is enabled. + /// + [DataField, AutoNetworkedField] + public LocId DisabledPopup = "fake-mindshield-disabled"; } diff --git a/Content.Shared/Mindshield/Components/MindShieldComponent.cs b/Content.Shared/Mindshield/Components/MindShieldComponent.cs index 77f5497973..db04097693 100644 --- a/Content.Shared/Mindshield/Components/MindShieldComponent.cs +++ b/Content.Shared/Mindshield/Components/MindShieldComponent.cs @@ -1,16 +1,10 @@ -using Content.Shared.Revolutionary; using Robust.Shared.GameStates; -using Content.Shared.StatusIcon; -using Robust.Shared.Prototypes; namespace Content.Shared.Mindshield.Components; /// -/// If a player has a Mindshield they will get this component to prevent conversion. +/// This component, on a clothing item, on an implant or on an entity, prevents "mind control". This means that you won't be convertable to the revolution, for instance. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] -public sealed partial class MindShieldComponent : Component -{ - [DataField, ViewVariables(VVAccess.ReadWrite)] - public ProtoId MindShieldStatusIcon = "MindShieldIcon"; -} +[RegisterComponent, NetworkedComponent] +[Access(typeof(MindShieldSystem))] +public sealed partial class MindShieldComponent : Component; diff --git a/Content.Shared/Mindshield/Components/MindShieldImplantComponent.cs b/Content.Shared/Mindshield/Components/MindShieldImplantComponent.cs index 09de4467be..7fd866e430 100644 --- a/Content.Shared/Mindshield/Components/MindShieldImplantComponent.cs +++ b/Content.Shared/Mindshield/Components/MindShieldImplantComponent.cs @@ -4,7 +4,7 @@ using Robust.Shared.GameStates; namespace Content.Shared.Mindshield.Components; /// -/// Component given to an entity to mark it is a mindshield implant. +/// Component given to an entity to mark it is a mindshield implant that will unconvert revolutionaries when implanted. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(RevolutionarySystem))] public sealed partial class MindShieldImplantComponent : Component; diff --git a/Content.Shared/Mindshield/Components/MindShieldStatusComponent.cs b/Content.Shared/Mindshield/Components/MindShieldStatusComponent.cs new file mode 100644 index 0000000000..393fd0afb2 --- /dev/null +++ b/Content.Shared/Mindshield/Components/MindShieldStatusComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Mindshield.Components; + +/// +/// This component stores an entity's mindshield status for easier retrieval later on +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(MindShieldSystem))] +public sealed partial class MindShieldStatusComponent : Component +{ + /// + /// Whether the entity is protected from mind control & co. + /// + [DataField, AutoNetworkedField] + public bool IsMindshielded; + + /// + /// Whether the sec HUD will show a mindshield icon + /// + [DataField, AutoNetworkedField] + public bool IsVisible; +} diff --git a/Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs b/Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs deleted file mode 100644 index 9414332068..0000000000 --- a/Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Actions.Components; -using Content.Shared.Implants; -using Content.Shared.Mindshield.Components; -using Content.Shared.Popups; -using Content.Shared.Tag; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Mindshield.FakeMindShield; - -/// -/// This system is responsible for handling the fake mindshield implant. -/// -public sealed partial class FakeMindShieldSystem : EntitySystem -{ - [Dependency] private SharedActionsSystem _actions = default!; - [Dependency] private SharedPopupSystem _popup = default!; - [Dependency] private TagSystem _tag = default!; - - /// - /// This tag should be placed on the fake mindshield action for identification. - /// - private static readonly ProtoId FakeMindShieldImplantTag = "FakeMindShieldImplant"; - - /// - /// Displays a popup to inform the player of activation or deactivation. - /// - /// - /// An associated tuple. The state in will be used - /// to display the appropriate popup. - /// - private void ShowTogglePopup(Entity ent) - { - var message = ent.Comp.IsEnabled - ? Loc.GetString("fake-mindshield-enabled") - : Loc.GetString("fake-mindshield-disabled"); - - _popup.PopupEntity(message, ent, ent); - } - - /// - /// Run when is raised. Toggles the component and the action indication. - /// - /// An associated tuple. The state in will be toggled. - /// The event arguments. Will be marked as handled. - [SubscribeLocalEvent] - private void OnToggleMindshield(Entity ent, ref FakeMindShieldToggleEvent args) - { - ent.Comp.IsEnabled = !ent.Comp.IsEnabled; - args.Toggle = true; - args.Handled = true; - ShowTogglePopup(ent); - Dirty(ent); - } - - /// - /// Handles implant interactions with chameleon outfits. - /// (De)Activates the mindshield if the chameleon outfit requires it. Displays a popup if state changes. - /// - /// An associated tuple. The state in will be set if changed. - /// The event arguments. - [SubscribeLocalEvent] - private void OnChameleonControllerOutfitSelected(Entity ent, ref ChameleonControllerOutfitSelectedEvent args) - { - if (ent.Comp.IsEnabled == args.ChameleonOutfit.HasMindShield) - return; - - // This assumes there is only one fake mindshield action per entity (This is currently enforced) - if (!TryComp(ent, out var actionsComp)) - return; - - ent.Comp.IsEnabled = args.ChameleonOutfit.HasMindShield; - - foreach (var action in actionsComp.Actions) - { - if (!_tag.HasTag(action, FakeMindShieldImplantTag)) - continue; - - if (!TryComp(action, out var actionComp)) - continue; - - _actions.SetToggled(action, args.ChameleonOutfit.HasMindShield); - ShowTogglePopup(ent); - - if (actionComp.UseDelay != null) - _actions.SetCooldown(action, actionComp.UseDelay.Value); - - break; - } - - Dirty(ent); - } -} - -/// -/// Generic . -/// -public sealed partial class FakeMindShieldToggleEvent : InstantActionEvent; diff --git a/Content.Shared/Mindshield/FakeMindshieldSystem.cs b/Content.Shared/Mindshield/FakeMindshieldSystem.cs new file mode 100644 index 0000000000..64783d56a3 --- /dev/null +++ b/Content.Shared/Mindshield/FakeMindshieldSystem.cs @@ -0,0 +1,200 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.Components; +using Content.Shared.Clothing; +using Content.Shared.Implants; +using Content.Shared.Inventory; +using Content.Shared.Mindshield.Components; +using Content.Shared.Popups; +using Content.Shared.Tag; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.Mindshield; + +/// +/// This system handles fake mindshields represented by . Works using 's +/// +public sealed partial class FakeMindShieldSystem : EntitySystem +{ + [Dependency] private SharedActionsSystem _actions = default!; + [Dependency] private TagSystem _tag = default!; + [Dependency] private IGameTiming _timing = default!; + [Dependency] private MindShieldSystem _mindShields = default!; + [Dependency] private SharedPopupSystem _popups = default!; + + public override void Initialize() + { + base.Initialize(); + + // Other events + SubscribeLocalEvent>((ent, ref relay) => OnChameleonControllerOutfitSelectedImplant(ent, ref relay.Args, relay.ImplantedEntity)); + + // Toggle events + SubscribeLocalEvent>((e, ref sk) => OnToggleMindshield(e, ref sk.Args)); + SubscribeLocalEvent>((e, ref sk) => OnToggleMindshield(e, ref sk.Args)); + + // Mindshield events + SubscribeLocalEvent>((a, ref k) => OnQueryStatus(a, ref k.Args)); + SubscribeLocalEvent>((a, ref k) => OnQueryStatus(a, ref k.Args)); + } + + /// + /// Displays a popup to inform the player of activation or deactivation. + /// + /// + /// An associated tuple. The state in will be used + /// to display the appropriate popup. + /// + /// The entity that toggled the fake mindshield. + private void ShowTogglePopup(Entity ent, EntityUid performer) + { + var message = ent.Comp.IsEnabled + ? Loc.GetString(ent.Comp.EnabledPopup) + : Loc.GetString(ent.Comp.DisabledPopup); + + _popups.PopupEntity(message, performer, performer); + } + + [SubscribeLocalEvent] + private void OnFakeMindshieldEquip(Entity ent, ref ClothingGotEquippedEvent args) + { + _mindShields.RefreshMindshieldStatus(args.Wearer); + } + + [SubscribeLocalEvent] + private void OnFakeMindshieldUnequip(Entity ent, ref ClothingGotUnequippedEvent args) + { + _mindShields.RefreshMindshieldStatus(args.Wearer); + } + + [SubscribeLocalEvent] + private void OnFakeMindshieldRemoved(Entity ent, ref ComponentRemove args) + { + if (!ent.Comp.IsInnate) + return; + + _mindShields.RefreshMindshieldStatus(ent.Owner); + } + + [SubscribeLocalEvent] + private void OnFakeMindshieldImplantRemoved(Entity ent, ref ImplantRemovedEvent args) + { + // when we reinject the mindshield, the action will be disabled (since its a fresh action) + // which would cause it to be desynced with the state of the mindshield + // so we just disable it here + ent.Comp.IsEnabled = false; + Dirty(ent); + + _mindShields.RefreshMindshieldStatus(args.Implanted); + } + + [SubscribeLocalEvent] + private void OnFakeMindshieldImplanted(Entity ent, ref ImplantImplantedEvent args) + { + _mindShields.RefreshMindshieldStatus(args.Implanted); + } + + [SubscribeLocalEvent] + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (!ent.Comp.IsInnate) + return; + + _actions.AddAction(ent.Owner, ent.Comp.Action); + _mindShields.RefreshMindshieldStatus(ent.Owner); + } + + [SubscribeLocalEvent] + private void OnQueryStatus(Entity ent, ref GetMindShieldStatusEvent args) + { + args.IsVisible |= ent.Comp.IsEnabled; + } + + [SubscribeLocalEvent] + private void OnToggleMindshield(Entity ent, ref FakeMindShieldToggleEvent args) + { + if (args.ActionTag != ent.Comp.ActionTag) + return; + + ent.Comp.IsEnabled = !ent.Comp.IsEnabled; + args.Toggle = true; + args.Handled = true; + ShowTogglePopup(ent, args.Performer); + Dirty(ent.Owner, ent.Comp); + _mindShields.RefreshMindshieldStatus(args.Performer); + } + + [SubscribeLocalEvent] + private void OnChameleonControllerOutfitSelected(Entity ent, ref ChameleonControllerOutfitSelectedEvent args) + { + HandleChameleonOutfit(ent, ent.Comp, ent.Owner, ent.Owner, ref args); + } + + private void OnChameleonControllerOutfitSelectedImplant(Entity ent, ref ChameleonControllerOutfitSelectedEvent args, EntityUid implantedEntity) + { + HandleChameleonOutfit(ent, ent.Comp, implantedEntity, implantedEntity, ref args); + } + + private void HandleChameleonOutfit(EntityUid fakeMindshieldUid, FakeMindShieldComponent component, EntityUid actionOwner, EntityUid statusOwner, ref ChameleonControllerOutfitSelectedEvent args) + { + if (!component.ChameleonControllable) + return; + + if (component.IsEnabled == args.ChameleonOutfit.HasMindShield) + return; + + // This assumes there is only one fake mindshield action per entity (This is currently enforced) + if (!TryComp(actionOwner, out var actionsComp)) + return; + + // In case the fake mindshield ever doesn't have an action. + var actionFound = false; + + foreach (var action in actionsComp.Actions) + { + if (!_tag.HasTag(action, component.ActionTag)) + continue; + + if (!TryComp(action, out var actionComp)) + continue; + + actionFound = true; + + if (_actions.IsCooldownActive(actionComp, _timing.CurTime)) + continue; + + component.IsEnabled = args.ChameleonOutfit.HasMindShield; + _actions.SetToggled(action, args.ChameleonOutfit.HasMindShield); + Dirty(fakeMindshieldUid, component); + + if (actionComp.UseDelay != null) + _actions.SetCooldown(action, actionComp.UseDelay.Value); + + _mindShields.RefreshMindshieldStatus(statusOwner); + return; + } + + // If they don't have the action for some reason, still set it correctly. + if (!actionFound) + { + component.IsEnabled = args.ChameleonOutfit.HasMindShield; + Dirty(fakeMindshieldUid, component); + _mindShields.RefreshMindshieldStatus(statusOwner); + } + } +} + +/// +/// Inventory relayed event telling all fake mindshields with the same ActionTag to try to toggle themselves. +/// +public sealed partial class FakeMindShieldToggleEvent : InstantActionEvent, IInventoryRelayEvent +{ + public SlotFlags TargetSlots => SlotFlags.WITHOUT_POCKET; + + /// + /// The tag the fake mindshield needs to have to be successfully toggled. + /// We seperate it out to allow an entity to, for example, have an implant one and a store bought one without conflicts. + /// + [DataField] + public ProtoId ActionTag = "FakeMindShieldImplant"; +} diff --git a/Content.Shared/Mindshield/MindShieldSystem.cs b/Content.Shared/Mindshield/MindShieldSystem.cs new file mode 100644 index 0000000000..0b5c187b36 --- /dev/null +++ b/Content.Shared/Mindshield/MindShieldSystem.cs @@ -0,0 +1,139 @@ +using Content.Shared.Clothing; +using Content.Shared.Implants; +using Content.Shared.Inventory; +using Content.Shared.Mindshield.Components; +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.Mindshield; + +public sealed partial class MindShieldSystem : EntitySystem +{ + [Dependency] private IGameTiming _timing = default!; + /// + /// Status icon displayed in the sec HUD. + /// + public static ProtoId StatusIcon = "MindShieldIcon"; + + public override void Initialize() + { + base.Initialize(); + + // Mind shield status events + SubscribeLocalEvent>((e, ref k) => OnStatusQuery(e, ref k.Args)); + SubscribeLocalEvent>((e, ref k) => OnStatusQuery(e, ref k.Args)); + } + + [SubscribeLocalEvent] + private void OnMindshieldUnequip(Entity ent, ref ClothingGotUnequippedEvent args) + { + if (_timing.ApplyingState) + return; + + RefreshMindshieldStatus(args.Wearer); + } + + [SubscribeLocalEvent] + private void OnMindshieldEquip(Entity ent, ref ClothingGotEquippedEvent args) + { + if (_timing.ApplyingState) + return; + + RefreshMindshieldStatus(args.Wearer); + } + + [SubscribeLocalEvent] + private void OnMindshieldRemoved(Entity ent, ref ComponentRemove args) + { + RefreshMindshieldStatus(ent.Owner); + } + + [SubscribeLocalEvent] + private void OnMindshieldImplantRemoved(Entity ent, ref ImplantRemovedEvent args) + { + RefreshMindshieldStatus(args.Implanted); + } + + [SubscribeLocalEvent] + private void OnMindshieldImplanted(Entity ent, ref ImplantImplantedEvent args) + { + RefreshMindshieldStatus(args.Implanted); + } + + [SubscribeLocalEvent] + private void OnMindshieldMapInit(Entity ent, ref MapInitEvent args) + { + // todo: make it not refresh on implant & clothing items + RefreshMindshieldStatus(ent.Owner); + } + + /// + /// This function updates . It should be called when anything makes a modification of its mindshielded-ness. + /// + public void RefreshMindshieldStatus(EntityUid ent) + { + var ev = new GetMindShieldStatusEvent(); + RaiseLocalEvent(ent, ref ev); + var mindshielded = ev.IsMindshielded; + var visible = ev.IsVisible; + + if (!mindshielded && !visible) + { + if (HasComp(ent)) + RemCompDeferred(ent); + } + else + { + EnsureComp(ent, out var c); + c.IsMindshielded = mindshielded; + c.IsVisible = visible; + Dirty(ent, c); + } + } + + [SubscribeLocalEvent] + private void OnStatusQuery(Entity e, ref GetMindShieldStatusEvent args) + { + args.IsMindshielded = true; + args.IsVisible = true; + } + + /// + /// Retrieves mindshielding data of an entity. Works via , and so requires proper dirtying on the part of mindshield providers. + /// + /// The entity to check the mindshield status of. + /// If the entity has a functional mind shield + /// Wether the entity shows a mindshield icon on the sec HUD + /// You should never look for a mindshield component and instead use this function. + public void GetMindshieldStatus(EntityUid entity, out bool isMindshielded, out bool isVisible) + { + if (TryComp(entity, out var comp)) + { + isMindshielded = comp.IsMindshielded; + isVisible = comp.IsVisible; + } + else + { + isMindshielded = isVisible = false; + } + } +} + +/// +/// Raised in order to get whether an entity is mindshielded visually, mechanically or both. +/// +[ByRefEvent] +public sealed class GetMindShieldStatusEvent : EntityEventArgs, IInventoryRelayEvent +{ + public SlotFlags TargetSlots => SlotFlags.WITHOUT_POCKET; + /// + /// Whether the entity is mindshielded. + /// + public bool IsMindshielded; + + /// + /// Whether a mindshield icon is present + /// + public bool IsVisible; +} diff --git a/Content.Shared/Revolutionary/Components/AlwaysRevolutionaryConvertibleComponent.cs b/Content.Shared/Revolutionary/Components/AlwaysRevolutionaryConvertibleComponent.cs index 694c6d2f06..4b5f09aee9 100644 --- a/Content.Shared/Revolutionary/Components/AlwaysRevolutionaryConvertibleComponent.cs +++ b/Content.Shared/Revolutionary/Components/AlwaysRevolutionaryConvertibleComponent.cs @@ -5,7 +5,7 @@ namespace Content.Shared.Revolutionary.Components; /// /// Component used for allowing non-humans to be converted. (Mainly monkeys) /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(RevolutionarySystem))] public sealed partial class AlwaysRevolutionaryConvertibleComponent : Component { diff --git a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs index 12589850e6..c8c7c41fea 100644 --- a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs +++ b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Revolutionary.Components; /// /// Component used for marking a Head Rev for conversion and winning/losing. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(RevolutionarySystem))] public sealed partial class HeadRevolutionaryComponent : Component { /// diff --git a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs index 513e8897bd..a32bbca7a9 100644 --- a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs +++ b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs @@ -9,7 +9,7 @@ namespace Content.Shared.Revolutionary.Components; /// /// Used for marking regular revs as well as storing icon prototypes so you can see fellow revs. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(RevolutionarySystem))] public sealed partial class RevolutionaryComponent : Component { /// @@ -24,5 +24,11 @@ public sealed partial class RevolutionaryComponent : Component [DataField] public SoundSpecifier RevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/headrev_start.ogg"); + /// + /// The duration of the stun when you get deconverted. + /// + [DataField] + public TimeSpan StunTime = TimeSpan.FromSeconds(4f); + public override bool SessionSpecific => true; } diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/RevolutionarySystem.cs similarity index 65% rename from Content.Shared/Revolutionary/SharedRevolutionarySystem.cs rename to Content.Shared/Revolutionary/RevolutionarySystem.cs index a5947fd0fc..fa8a4f926e 100644 --- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs +++ b/Content.Shared/Revolutionary/RevolutionarySystem.cs @@ -1,56 +1,26 @@ -using Content.Shared.IdentityManagement; -using Content.Shared.Mindshield.Components; -using Content.Shared.Popups; using Content.Shared.Revolutionary.Components; -using Content.Shared.Stunnable; using Robust.Shared.GameStates; using Robust.Shared.Player; using Content.Shared.Antag; +using Content.Shared.StatusIcon.Components; namespace Content.Shared.Revolutionary; -public abstract partial class SharedRevolutionarySystem : EntitySystem +public sealed partial class RevolutionarySystem : EntitySystem { - [Dependency] private SharedPopupSystem _popupSystem = default!; - [Dependency] private SharedStunSystem _sharedStun = default!; - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(MindShieldImplanted); - SubscribeLocalEvent(OnRevCompGetStateAttempt); - SubscribeLocalEvent(OnRevCompGetStateAttempt); SubscribeLocalEvent(DirtyRevComps); SubscribeLocalEvent(DirtyRevComps); SubscribeLocalEvent(DirtyRevComps); - SubscribeLocalEvent(OnAttemptConvert); - } - - /// - /// When the mindshield is implanted in the rev it will popup saying they were deconverted. In Head Revs it will remove the mindshield component. - /// - private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, MapInitEvent init) - { - if (HasComp(uid)) - { - RemCompDeferred(uid); - return; - } - - if (HasComp(uid)) - { - var stunTime = TimeSpan.FromSeconds(4); - var name = Identity.Entity(uid, EntityManager); - RemComp(uid); - _sharedStun.TryUpdateParalyzeDuration(uid, stunTime); - _popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid); - } } /// /// Determines if a HeadRev component should be sent to the client. /// + [SubscribeLocalEvent] private void OnRevCompGetStateAttempt(EntityUid uid, HeadRevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args) { args.Cancelled = !CanGetState(args.Player); @@ -59,6 +29,7 @@ public abstract partial class SharedRevolutionarySystem : EntitySystem /// /// Determines if a Rev component should be sent to the client. /// + [SubscribeLocalEvent] private void OnRevCompGetStateAttempt(EntityUid uid, RevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args) { args.Cancelled = !CanGetState(args.Player); @@ -80,6 +51,7 @@ public abstract partial class SharedRevolutionarySystem : EntitySystem return HasComp(uid); } + /// /// Dirties all the Rev components so they are sent to clients. /// @@ -102,6 +74,7 @@ public abstract partial class SharedRevolutionarySystem : EntitySystem } } + [SubscribeLocalEvent] private void OnAttemptConvert(Entity ent, ref AttemptConvertRevolutionaryEvent args) { args.Cancelled = true; @@ -112,4 +85,21 @@ public abstract partial class SharedRevolutionarySystem : EntitySystem { args.Cancelled = true; } + + [SubscribeLocalEvent] + private void GetRevIcon(Entity ent, ref GetStatusIconsEvent args) + { + if (HasComp(ent)) + return; + + if (ProtoMan.Resolve(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); + } + + [SubscribeLocalEvent] + private void GetHeadRevIcon(Entity ent, ref GetStatusIconsEvent args) + { + if (ProtoMan.Resolve(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); + } } diff --git a/Resources/Locale/en-US/changeling/changeling.ftl b/Resources/Locale/en-US/changeling/changeling.ftl index 02636a33e5..087e07f40f 100644 --- a/Resources/Locale/en-US/changeling/changeling.ftl +++ b/Resources/Locale/en-US/changeling/changeling.ftl @@ -51,5 +51,9 @@ changeling-takeover-success-self = We have successfully infested the body and as # stings changeling-sting-success = We silently sting {THE($target)}! +# mindshield +changeling-fake-mindshield-enabled = We emit fake mindshield waves. +changeling-fake-mindshield-disabled = We stop emitting fake mindshield waves. + # other changeling-paused-map-name = Changeling identity storage map diff --git a/Resources/Locale/en-US/store/changeling-catalog.ftl b/Resources/Locale/en-US/store/changeling-catalog.ftl index 5184fd62d9..7524ef322c 100644 --- a/Resources/Locale/en-US/store/changeling-catalog.ftl +++ b/Resources/Locale/en-US/store/changeling-catalog.ftl @@ -33,5 +33,8 @@ changeling-catalog-last-resort-desc = Gib your current body and escape as a weak Warning! This resets your progress as a changeling, removing your store purchases and absorbed identities! +changeling-catalog-voice-mindshield-name = Fake Mindshield +changeling-catalog-voice-mindshield-desc = Modify your neuron's natural waves to copy those produced by Nanotrasen mindshield implants, making you look mindshielded to security members. Can be toggled. + changeling-catalog-night-vision-name = Night Vision changeling-catalog-night-vision-desc = You modify your photoreceptors and heighten your senses as to become able to see in complete darkness. diff --git a/Resources/Prototypes/Actions/changeling.yml b/Resources/Prototypes/Actions/changeling.yml index d5864f781d..3554546312 100644 --- a/Resources/Prototypes/Actions/changeling.yml +++ b/Resources/Prototypes/Actions/changeling.yml @@ -304,3 +304,24 @@ probability: 0.5 - type: InstantAction event: !type:ScreechActionEvent + +- type: entity + parent: FakeMindShieldToggleAction + id: ActionChangelingFakeMindshieldToggle + name: '[color=green]Toggle Fake Mindshield[/color]' + description: Turn our mindshield visibility on or off. + components: + - type: Sprite + sprite: Interface/Actions/changeling2.rsi + layers: + - state: mindshield_off + map: ["enum.ActionVisuals.Icon"] + - state: mindshield_on + map: ["enum.ActionVisuals.IconToggled"] + visible: false + - type: InstantAction + event: !type:FakeMindShieldToggleEvent + actionTag: FakeMindShieldChangeling + - type: Tag + tags: + - FakeMindShieldChangeling # used to distinguish the changeling's toggle mindshield action from the implant's diff --git a/Resources/Prototypes/Catalog/changeling_catalog.yml b/Resources/Prototypes/Catalog/changeling_catalog.yml index 7c8676810e..748cd9f50b 100644 --- a/Resources/Prototypes/Catalog/changeling_catalog.yml +++ b/Resources/Prototypes/Catalog/changeling_catalog.yml @@ -174,3 +174,17 @@ - !type:ListingLimitedStockCondition stock: 1 productAction: ActionChangelingScreech + +- type: listing + id: ChangelingFakeMindshield + name: changeling-catalog-voice-mindshield-name + description: changeling-catalog-voice-mindshield-desc + icon: { sprite: /Textures/Interface/Actions/changeling2.rsi, state: mindshield_on } + cost: + ChangelingDNA: 10 + categories: + - ChangelingStoreStealth + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + productComponents: ChangelingFakeMindshieldDummy diff --git a/Resources/Prototypes/Entities/Mobs/Player/changeling.yml b/Resources/Prototypes/Entities/Mobs/Player/changeling.yml index 543a34e30c..b16ebc7862 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/changeling.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/changeling.yml @@ -108,3 +108,15 @@ noiseMultiplier: 2 overlayColor: "#AB0808AA" prioritized: false # Technically eye augments, so we want things such as NVGs to take priority if able + +- type: entity # dummy prototype for the store listing + id: ChangelingFakeMindshieldDummy + categories: [ HideSpawnMenu ] + components: + - type: FakeMindShield + actionTag: FakeMindShieldChangeling + chameleonControllable: false + isInnate: true + enabledPopup: changeling-fake-mindshield-enabled + disabledPopup: changeling-fake-mindshield-disabled + action: ActionChangelingFakeMindshieldToggle diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index f96703519d..efadfffe62 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -366,9 +366,7 @@ components: - type: SubdermalImplant implantAction: FakeMindShieldToggleAction - implantComponents: - - type: FakeMindShield # TODO: put the component on the implant and use implant relay events for the status icon - - type: FakeMindShieldImplant + - type: FakeMindShield # Sec and Command implants @@ -381,6 +379,7 @@ components: - type: SubdermalImplant - type: MindShieldImplant + - type: MindShield # Centcomm implants diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 08002a7947..860f07052f 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -559,6 +559,9 @@ - type: Tag id: FakeMindShieldImplant # Used by FakeMindShieldSystem to toggle the tagged action when a chameleon outfit is selected. +- type: Tag # Also used by FakeMindShieldSystem, but only for changelings + id: FakeMindShieldChangeling + - type: Tag id: FakeNukeDisk # Exists so that the fake nuke disk can be blacklisted by storages that blacklist the real disk. diff --git a/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon-on.png b/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon-on.png index 19b4e13ec1..45f085996a 100644 Binary files a/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon-on.png and b/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon-on.png differ diff --git a/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon.png b/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon.png index 9eb33d5350..658ff0afc1 100644 Binary files a/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon.png and b/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/icon.png differ diff --git a/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/meta.json index 31f288131f..9aa1bd716b 100644 --- a/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/actions_fakemindshield.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Created by Ubaser (Discord) for SS14, Modified for purpose by brassicaprime69 (Discord)", + "license": "CC-BY-3.0", + "copyright": "By ketufaispikinut (Github)", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Interface/Actions/changeling2.rsi/meta.json b/Resources/Textures/Interface/Actions/changeling2.rsi/meta.json index d1c4f3d110..07e68df6ba 100644 --- a/Resources/Textures/Interface/Actions/changeling2.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/changeling2.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by ketufaispikinut from a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (scientist suit), a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (hydro suit) and the changeling ability border/background sprites created by TiniestShark. Biodegrade, Mimicry and Extract Dna On/Off, Blind, Mute, Neurodepressant, Cryogenic, Lethal and Hallucination stings, screech, nv on/off by ketufaispikinut, based on TiniestShark's textures. Shop icon by ketufaispikinut, based on TiniestShark's textures. last_resort taken from https://github.com/tgstation/tgstation/blob/5c90546a27a1e5ea59b9f127ae274d7341acc70b/icons/mob/actions/actions_changeling.dmi and modified by Pok27 (GitHub). take_over_corpse by Pok27 (GitHub).", + "copyright": "Made by ketufaispikinut from a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (scientist suit), a sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039 (hydro suit) and the changeling ability border/background sprites created by TiniestShark. Biodegrade, Mimicry and Extract Dna On/Off, Blind, Mute, Neurodepressant, Cryogenic, Lethal and Hallucination stings, screech, nv on/off by ketufaispikinut, based on TiniestShark's textures. Shop icon by ketufaispikinut, based on TiniestShark's textures. last_resort taken from https://github.com/tgstation/tgstation/blob/5c90546a27a1e5ea59b9f127ae274d7341acc70b/icons/mob/actions/actions_changeling.dmi and modified by Pok27 (GitHub). take_over_corpse by Pok27 (GitHub). Mindshield on/off textures by ketufaispikinut, based on TiniestShark's textures.", "size": { "x": 32, "y": 32 @@ -43,6 +43,12 @@ { "name": "extract_dna_off" }, + { + "name": "mindshield_on" + }, + { + "name": "mindshield_off" + }, { "name": "shop" }, diff --git a/Resources/Textures/Interface/Actions/changeling2.rsi/mindshield_off.png b/Resources/Textures/Interface/Actions/changeling2.rsi/mindshield_off.png new file mode 100644 index 0000000000..5067de3032 Binary files /dev/null and b/Resources/Textures/Interface/Actions/changeling2.rsi/mindshield_off.png differ diff --git a/Resources/Textures/Interface/Actions/changeling2.rsi/mindshield_on.png b/Resources/Textures/Interface/Actions/changeling2.rsi/mindshield_on.png new file mode 100644 index 0000000000..1df291340b Binary files /dev/null and b/Resources/Textures/Interface/Actions/changeling2.rsi/mindshield_on.png differ