]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Wizard Item Recall Spell (#34411)
authorScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Sat, 8 Feb 2025 21:56:08 +0000 (22:56 +0100)
committerGitHub <noreply@github.com>
Sat, 8 Feb 2025 21:56:08 +0000 (16:56 -0500)
16 files changed:
Content.Client/Actions/ActionsSystem.cs
Content.Client/ItemRecall/ItemRecallSystem.cs [new file with mode: 0644]
Content.Server/ItemRecall/ItemRecallSystem.cs [new file with mode: 0644]
Content.Shared/Actions/BaseActionComponent.cs
Content.Shared/Actions/SharedActionsSystem.cs
Content.Shared/ItemRecall/ItemRecallComponent.cs [new file with mode: 0644]
Content.Shared/ItemRecall/ItemRecallEvents.cs [new file with mode: 0644]
Content.Shared/ItemRecall/RecallMarkerComponent.cs [new file with mode: 0644]
Content.Shared/ItemRecall/SharedItemRecallSystem.cs [new file with mode: 0644]
Content.Shared/Projectiles/SharedProjectileSystem.cs
Resources/Locale/en-US/item-recall/item-recall.ftl [new file with mode: 0644]
Resources/Locale/en-US/store/spellbook-catalog.ftl
Resources/Prototypes/Catalog/spellbook_catalog.yml
Resources/Prototypes/Magic/recall_spell.yml [new file with mode: 0644]
Resources/Textures/Objects/Magic/magicactions.rsi/item_recall.png [new file with mode: 0644]
Resources/Textures/Objects/Magic/magicactions.rsi/meta.json

index b594817701ea69e4fa3272f3d47936e266f00f8a..d836c2ed7a887e2d2e0199c3c86fdfb2ddaf9d73 100644 (file)
@@ -137,6 +137,7 @@ namespace Content.Client.Actions
             component.Priority = state.Priority;
             component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
             component.RaiseOnUser = state.RaiseOnUser;
+            component.RaiseOnAction = state.RaiseOnAction;
             component.AutoPopulate = state.AutoPopulate;
             component.Temporary = state.Temporary;
             component.ItemIconStyle = state.ItemIconStyle;
diff --git a/Content.Client/ItemRecall/ItemRecallSystem.cs b/Content.Client/ItemRecall/ItemRecallSystem.cs
new file mode 100644 (file)
index 0000000..11d3015
--- /dev/null
@@ -0,0 +1,11 @@
+using Content.Shared.ItemRecall;
+
+namespace Content.Client.ItemRecall;
+
+/// <summary>
+/// System for handling the ItemRecall ability for wizards.
+/// </summary>
+public sealed partial class ItemRecallSystem : SharedItemRecallSystem
+{
+
+}
diff --git a/Content.Server/ItemRecall/ItemRecallSystem.cs b/Content.Server/ItemRecall/ItemRecallSystem.cs
new file mode 100644 (file)
index 0000000..88972e9
--- /dev/null
@@ -0,0 +1,11 @@
+using Content.Shared.ItemRecall;
+
+namespace Content.Server.ItemRecall;
+
+/// <summary>
+/// System for handling the ItemRecall ability for wizards.
+/// </summary>
+public sealed partial class ItemRecallSystem : SharedItemRecallSystem
+{
+
+}
index c3aa6cc97eebef76ff5db9bd3dada2ba1ac1f334..25b36df2afe986d0e83b46e50f8e0a5be2d8fb42 100644 (file)
@@ -167,6 +167,14 @@ public abstract partial class BaseActionComponent : Component
     [DataField]
     public bool RaiseOnUser;
 
+    /// <summary>
+    ///     If true, this will cause the the action event to always be raised directed at the action itself instead of the action's container/provider.
+    ///     Takes priority over RaiseOnUser.
+    /// </summary>
+    [DataField]
+    [Obsolete("This datafield will be reworked in an upcoming action refactor")]
+    public bool RaiseOnAction;
+
     /// <summary>
     ///     Whether or not to automatically add this action to the action bar when it becomes available.
     /// </summary>
@@ -212,6 +220,7 @@ public abstract class BaseActionComponentState : ComponentState
     public int Priority;
     public NetEntity? AttachedEntity;
     public bool RaiseOnUser;
+    public bool RaiseOnAction;
     public bool AutoPopulate;
     public bool Temporary;
     public ItemActionIconStyle ItemIconStyle;
@@ -223,6 +232,7 @@ public abstract class BaseActionComponentState : ComponentState
         EntityIcon = entManager.GetNetEntity(component.EntIcon);
         AttachedEntity = entManager.GetNetEntity(component.AttachedEntity);
         RaiseOnUser = component.RaiseOnUser;
+        RaiseOnAction = component.RaiseOnAction;
         Icon = component.Icon;
         IconOn = component.IconOn;
         IconColor = component.IconColor;
index fc6f0baf7721a94fac5810d72d8af69027aa15cd..8079885a5ac5a9cd4bae0aa95b193d979f568eda 100644 (file)
@@ -679,6 +679,9 @@ public abstract class SharedActionsSystem : EntitySystem
             if (!action.RaiseOnUser && action.Container != null && !HasComp<MindComponent>(action.Container))
                 target = action.Container.Value;
 
+            if (action.RaiseOnAction)
+                target = actionId;
+
             RaiseLocalEvent(target, (object) actionEvent, broadcast: true);
             handled = actionEvent.Handled;
         }
diff --git a/Content.Shared/ItemRecall/ItemRecallComponent.cs b/Content.Shared/ItemRecall/ItemRecallComponent.cs
new file mode 100644 (file)
index 0000000..e057a99
--- /dev/null
@@ -0,0 +1,43 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.ItemRecall;
+
+/// <summary>
+/// Component for the ItemRecall action.
+/// Used for marking a held item and recalling it back into your hand with second action use.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))]
+public sealed partial class ItemRecallComponent : Component
+{
+    /// <summary>
+    /// The name the action should have while an entity is marked.
+    /// </summary>
+    [DataField]
+    public LocId? WhileMarkedName = "item-recall-marked-name";
+
+    /// <summary>
+    /// The description the action should have while an entity is marked.
+    /// </summary>
+    [DataField]
+    public LocId? WhileMarkedDescription = "item-recall-marked-description";
+
+    /// <summary>
+    /// The name the action starts with.
+    /// This shouldn't be set in yaml.
+    /// </summary>
+    [DataField]
+    public string? InitialName;
+
+    /// <summary>
+    /// The description the action starts with.
+    /// This shouldn't be set in yaml.
+    /// </summary>
+    [DataField]
+    public string? InitialDescription;
+
+    /// <summary>
+    /// The entity currently marked to be recalled by this action.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? MarkedEntity;
+}
diff --git a/Content.Shared/ItemRecall/ItemRecallEvents.cs b/Content.Shared/ItemRecall/ItemRecallEvents.cs
new file mode 100644 (file)
index 0000000..8bee46a
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.Actions;
+
+namespace Content.Shared.ItemRecall;
+
+/// <summary>
+/// Raised when using the ItemRecall action.
+/// </summary>
+[ByRefEvent]
+public sealed partial class OnItemRecallActionEvent : InstantActionEvent;
diff --git a/Content.Shared/ItemRecall/RecallMarkerComponent.cs b/Content.Shared/ItemRecall/RecallMarkerComponent.cs
new file mode 100644 (file)
index 0000000..a85b22e
--- /dev/null
@@ -0,0 +1,18 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.ItemRecall;
+
+
+/// <summary>
+/// Component used as a marker for an item marked by the ItemRecall ability.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))]
+public sealed partial class RecallMarkerComponent : Component
+{
+    /// <summary>
+    /// The action that marked this item.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? MarkedByAction;
+}
diff --git a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs
new file mode 100644 (file)
index 0000000..63d3820
--- /dev/null
@@ -0,0 +1,187 @@
+using Content.Shared.Actions;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Popups;
+using Content.Shared.Projectiles;
+using Robust.Shared.GameStates;
+using Robust.Shared.Player;
+
+namespace Content.Shared.ItemRecall;
+
+/// <summary>
+/// System for handling the ItemRecall ability for wizards.
+/// </summary>
+public abstract partial class SharedItemRecallSystem : EntitySystem
+{
+    [Dependency] private readonly ISharedPlayerManager _player = default!;
+    [Dependency] private readonly SharedPvsOverrideSystem _pvs = default!;
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
+    [Dependency] private readonly SharedHandsSystem _hands = default!;
+    [Dependency] private readonly MetaDataSystem _metaData = default!;
+    [Dependency] private readonly SharedPopupSystem _popups = default!;
+    [Dependency] private readonly SharedProjectileSystem _proj = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ItemRecallComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<ItemRecallComponent, OnItemRecallActionEvent>(OnItemRecallActionUse);
+
+        SubscribeLocalEvent<RecallMarkerComponent, ComponentShutdown>(OnRecallMarkerShutdown);
+    }
+
+    private void OnMapInit(Entity<ItemRecallComponent> ent, ref MapInitEvent args)
+    {
+        ent.Comp.InitialName = Name(ent);
+        ent.Comp.InitialDescription = Description(ent);
+    }
+
+    private void OnItemRecallActionUse(Entity<ItemRecallComponent> ent, ref OnItemRecallActionEvent args)
+    {
+        if (ent.Comp.MarkedEntity == null)
+        {
+            if (!TryComp<HandsComponent>(args.Performer, out var hands))
+                return;
+
+            var markItem = _hands.GetActiveItem((args.Performer, hands));
+
+            if (markItem == null)
+            {
+                _popups.PopupClient(Loc.GetString("item-recall-item-mark-empty"), args.Performer, args.Performer);
+                return;
+            }
+
+            if (HasComp<RecallMarkerComponent>(markItem))
+            {
+                _popups.PopupClient(Loc.GetString("item-recall-item-already-marked", ("item", markItem)), args.Performer, args.Performer);
+                return;
+            }
+
+            _popups.PopupClient(Loc.GetString("item-recall-item-marked", ("item", markItem.Value)), args.Performer, args.Performer);
+            TryMarkItem(ent, markItem.Value);
+            return;
+        }
+
+        RecallItem(ent.Comp.MarkedEntity.Value);
+        args.Handled = true;
+    }
+
+    private void RecallItem(Entity<RecallMarkerComponent?> ent)
+    {
+        if (!Resolve(ent.Owner, ref ent.Comp, false))
+            return;
+
+        if (!TryComp<InstantActionComponent>(ent.Comp.MarkedByAction, out var instantAction))
+            return;
+
+        var actionOwner = instantAction.AttachedEntity;
+
+        if (actionOwner == null)
+            return;
+
+        if (TryComp<EmbeddableProjectileComponent>(ent, out var projectile))
+            _proj.UnEmbed(ent, projectile, actionOwner.Value);
+
+        _popups.PopupPredicted(Loc.GetString("item-recall-item-summon", ("item", ent)), actionOwner.Value, actionOwner.Value);
+
+        _hands.TryForcePickupAnyHand(actionOwner.Value, ent);
+    }
+
+    private void OnRecallMarkerShutdown(Entity<RecallMarkerComponent> ent, ref ComponentShutdown args)
+    {
+        TryUnmarkItem(ent);
+    }
+
+    private void TryMarkItem(Entity<ItemRecallComponent> ent, EntityUid item)
+    {
+        if (!TryComp<InstantActionComponent>(ent, out var instantAction))
+            return;
+
+        var actionOwner = instantAction.AttachedEntity;
+
+        if (actionOwner == null)
+            return;
+
+        AddToPvsOverride(item, actionOwner.Value);
+
+        var marker = AddComp<RecallMarkerComponent>(item);
+        ent.Comp.MarkedEntity = item;
+        Dirty(ent);
+
+        marker.MarkedByAction = ent.Owner;
+
+        UpdateActionAppearance(ent);
+        Dirty(item, marker);
+    }
+
+    private void TryUnmarkItem(EntityUid item)
+    {
+        if (!TryComp<RecallMarkerComponent>(item, out var marker))
+            return;
+
+        if (!TryComp<InstantActionComponent>(marker.MarkedByAction, out var instantAction))
+            return;
+
+        if (TryComp<ItemRecallComponent>(marker.MarkedByAction, out var action))
+        {
+            // For some reason client thinks the station grid owns the action on client and this doesn't work. It doesn't work in PopupEntity(mispredicts) and PopupPredicted either(doesnt show).
+            // I don't have the heart to move this code to server because of this small thing.
+            // This line will only do something once that is fixed.
+            if (instantAction.AttachedEntity != null)
+            {
+                _popups.PopupClient(Loc.GetString("item-recall-item-unmark", ("item", item)), instantAction.AttachedEntity.Value, instantAction.AttachedEntity.Value, PopupType.MediumCaution);
+                RemoveFromPvsOverride(item, instantAction.AttachedEntity.Value);
+            }
+
+            action.MarkedEntity = null;
+            UpdateActionAppearance((marker.MarkedByAction.Value, action));
+            Dirty(marker.MarkedByAction.Value, action);
+        }
+
+        RemCompDeferred<RecallMarkerComponent>(item);
+    }
+
+    private void UpdateActionAppearance(Entity<ItemRecallComponent> action)
+    {
+        if (!TryComp<InstantActionComponent>(action, out var instantAction))
+            return;
+
+        if (action.Comp.MarkedEntity == null)
+        {
+            if (action.Comp.InitialName != null)
+                _metaData.SetEntityName(action, action.Comp.InitialName);
+            if (action.Comp.InitialDescription != null)
+                _metaData.SetEntityDescription(action, action.Comp.InitialDescription);
+            _actions.SetEntityIcon(action, null, instantAction);
+        }
+        else
+        {
+            if (action.Comp.WhileMarkedName != null)
+                _metaData.SetEntityName(action, Loc.GetString(action.Comp.WhileMarkedName,
+                    ("item", action.Comp.MarkedEntity.Value)));
+
+            if (action.Comp.WhileMarkedDescription != null)
+                _metaData.SetEntityDescription(action, Loc.GetString(action.Comp.WhileMarkedDescription,
+                    ("item", action.Comp.MarkedEntity.Value)));
+
+            _actions.SetEntityIcon(action, action.Comp.MarkedEntity, instantAction);
+        }
+    }
+
+    private void AddToPvsOverride(EntityUid uid, EntityUid user)
+    {
+        if (!_player.TryGetSessionByEntity(user, out var mindSession))
+            return;
+
+        _pvs.AddSessionOverride(uid, mindSession);
+    }
+
+    private void RemoveFromPvsOverride(EntityUid uid, EntityUid user)
+    {
+        if (!_player.TryGetSessionByEntity(user, out var mindSession))
+            return;
+
+        _pvs.RemoveSessionOverride(uid, mindSession);
+    }
+}
index bca9b36f8985a6eb2f5e5329d79514018a9a147e..1d0fc16cbd7d03c9eb56d5d2e4af0bad5245ea2e 100644 (file)
@@ -67,25 +67,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem
             return;
         }
 
-        var xform = Transform(uid);
-        TryComp<PhysicsComponent>(uid, out var physics);
-        _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform);
-        _transform.AttachToGridOrMap(uid, xform);
-        component.EmbeddedIntoUid = null;
-        Dirty(uid, component);
-
-        // Reset whether the projectile has damaged anything if it successfully was removed
-        if (TryComp<ProjectileComponent>(uid, out var projectile))
-        {
-            projectile.Shooter = null;
-            projectile.Weapon = null;
-            projectile.ProjectileSpent = false;
-        }
-
-        // Land it just coz uhhh yeah
-        var landEv = new LandEvent(args.User, true);
-        RaiseLocalEvent(uid, ref landEv);
-        _physics.WakeBody(uid, body: physics);
+        UnEmbed(uid, component, args.User);
 
         // try place it in the user's hand
         _hands.TryPickupAnyHand(args.User, uid);
@@ -135,6 +117,38 @@ public abstract partial class SharedProjectileSystem : EntitySystem
         Dirty(uid, component);
     }
 
+    public void UnEmbed(EntityUid uid, EmbeddableProjectileComponent? component, EntityUid? user = null)
+    {
+        if (!Resolve(uid, ref component))
+            return;
+
+        var xform = Transform(uid);
+        TryComp<PhysicsComponent>(uid, out var physics);
+        _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform);
+        _transform.AttachToGridOrMap(uid, xform);
+        component.EmbeddedIntoUid = null;
+        Dirty(uid, component);
+
+        // Reset whether the projectile has damaged anything if it successfully was removed
+        if (TryComp<ProjectileComponent>(uid, out var projectile))
+        {
+            projectile.Shooter = null;
+            projectile.Weapon = null;
+            projectile.ProjectileSpent = false;
+
+            Dirty(uid, projectile);
+        }
+
+        if (user != null)
+        {
+            // Land it just coz uhhh yeah
+            var landEv = new LandEvent(user, true);
+            RaiseLocalEvent(uid, ref landEv);
+        }
+
+        _physics.WakeBody(uid, body: physics);
+    }
+
     private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
     {
         if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))
diff --git a/Resources/Locale/en-US/item-recall/item-recall.ftl b/Resources/Locale/en-US/item-recall/item-recall.ftl
new file mode 100644 (file)
index 0000000..680c7b7
--- /dev/null
@@ -0,0 +1,9 @@
+item-recall-marked-name = Recall {CAPITALIZE($item)}
+item-recall-marked-description = Recall {THE($item)} back into your hand.
+
+item-recall-item-marked = You draw a magical sigil on {THE($item)}.
+item-recall-item-already-marked = {CAPITALIZE(THE($item))} is already marked!
+item-recall-item-mark-empty = You must be holding an item!
+item-recall-item-summon = {CAPITALIZE(THE($item))} appears in your hand!
+item-recall-item-unmark = You feel your connection with {THE($item)} sever.
+
index b18cac4f9a70bd3e7ce148a62bcd317d596a2e05..95a8b25e686dad10b6c748afb48d0b1ff9cd4441 100644 (file)
@@ -35,6 +35,9 @@ spellbook-cluwne-desc = For when you really hate someone and Smite isn't enough.
 spellbook-slip-name = Slippery Slope
 spellbook-slip-desc = Learn the ancient ways of the Janitor and curse your target to be slippery. Requires Wizard Robe & Hat.
 
+spellbook-item-recall-name = Item Recall
+spellbook-item-recall-description = Mark a held item and summon it back at any time with just a snap of your fingers!
+
 # Equipment
 
 spellbook-wand-polymorph-door-name = Wand of Entrance
index dfd171d9b277e0bd499fae8c850ff5d1420e5cd3..3ba3189771bec33397ca137bb20ce76fdba1a2a8 100644 (file)
     - SpellbookJaunt
   - !type:ListingLimitedStockCondition
     stock: 2
+
+- type: listing
+  id: SpellbookItemRecallSwap
+  name: spellbook-item-recall-name
+  description: spellbook-item-recall-description
+  productAction: ActionItemRecall
+  cost:
+    WizCoin: 1
+  categories:
+  - SpellbookUtility
+  conditions:
+  - !type:ListingLimitedStockCondition
+    stock: 1
diff --git a/Resources/Prototypes/Magic/recall_spell.yml b/Resources/Prototypes/Magic/recall_spell.yml
new file mode 100644 (file)
index 0000000..c5bb968
--- /dev/null
@@ -0,0 +1,21 @@
+- type: entity
+  id: ActionItemRecall
+  name: Mark Item
+  description: Mark a held item to later summon into your hand.
+  components:
+  - type: InstantAction
+    useDelay: 10
+    raiseOnAction: true
+    itemIconStyle: BigAction
+    sound: !type:SoundPathSpecifier
+      path: /Audio/Magic/forcewall.ogg
+      params:
+        volume: -5
+        pitch: 1.2
+        maxDistance: 5
+        variation: 0.2
+    icon:
+      sprite: Objects/Magic/magicactions.rsi
+      state: item_recall
+    event: !type:OnItemRecallActionEvent
+  - type: ItemRecall
diff --git a/Resources/Textures/Objects/Magic/magicactions.rsi/item_recall.png b/Resources/Textures/Objects/Magic/magicactions.rsi/item_recall.png
new file mode 100644 (file)
index 0000000..00bbe36
Binary files /dev/null and b/Resources/Textures/Objects/Magic/magicactions.rsi/item_recall.png differ
index a8da3d8bc3a63cedb2319ed259d895bb4504cfbf..a1112f0c6d6091a1a44951ecdd4bd39e959e2955 100644 (file)
@@ -1,7 +1,7 @@
 {
   "version": 1,
   "license": "CC-BY-SA-3.0",
-  "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13/commit/78db6bd5c2b2b3d1f5cd8fd75be3a39d5d929943 andhttps://github.com/tgstation/tgstation/commit/906fb0682bab6a0975b45036001c54f021f58ae7 ",
+  "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13/commit/78db6bd5c2b2b3d1f5cd8fd75be3a39d5d929943 and https://github.com/tgstation/tgstation/commit/906fb0682bab6a0975b45036001c54f021f58ae7, item_recall by ScarKy0",
   "size": {
     "x": 32,
     "y": 32
@@ -30,6 +30,9 @@
     },
     {
       "name": "gib"
+    },
+    {
+      "name": "item_recall"
     }
   ]
 }