From bf6fd4d581de27b8b293411c409aa7ed2c332b78 Mon Sep 17 00:00:00 2001 From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:56:08 +0100 Subject: [PATCH] Wizard Item Recall Spell (#34411) --- Content.Client/Actions/ActionsSystem.cs | 1 + Content.Client/ItemRecall/ItemRecallSystem.cs | 11 ++ Content.Server/ItemRecall/ItemRecallSystem.cs | 11 ++ Content.Shared/Actions/BaseActionComponent.cs | 10 + Content.Shared/Actions/SharedActionsSystem.cs | 3 + .../ItemRecall/ItemRecallComponent.cs | 43 ++++ Content.Shared/ItemRecall/ItemRecallEvents.cs | 9 + .../ItemRecall/RecallMarkerComponent.cs | 18 ++ .../ItemRecall/SharedItemRecallSystem.cs | 187 ++++++++++++++++++ .../Projectiles/SharedProjectileSystem.cs | 52 +++-- .../Locale/en-US/item-recall/item-recall.ftl | 9 + .../Locale/en-US/store/spellbook-catalog.ftl | 3 + .../Prototypes/Catalog/spellbook_catalog.yml | 13 ++ Resources/Prototypes/Magic/recall_spell.yml | 21 ++ .../Magic/magicactions.rsi/item_recall.png | Bin 0 -> 7081 bytes .../Objects/Magic/magicactions.rsi/meta.json | 5 +- 16 files changed, 376 insertions(+), 20 deletions(-) create mode 100644 Content.Client/ItemRecall/ItemRecallSystem.cs create mode 100644 Content.Server/ItemRecall/ItemRecallSystem.cs create mode 100644 Content.Shared/ItemRecall/ItemRecallComponent.cs create mode 100644 Content.Shared/ItemRecall/ItemRecallEvents.cs create mode 100644 Content.Shared/ItemRecall/RecallMarkerComponent.cs create mode 100644 Content.Shared/ItemRecall/SharedItemRecallSystem.cs create mode 100644 Resources/Locale/en-US/item-recall/item-recall.ftl create mode 100644 Resources/Prototypes/Magic/recall_spell.yml create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/item_recall.png diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index b594817701..d836c2ed7a 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -137,6 +137,7 @@ namespace Content.Client.Actions component.Priority = state.Priority; component.AttachedEntity = EnsureEntity(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 index 0000000000..11d3015c21 --- /dev/null +++ b/Content.Client/ItemRecall/ItemRecallSystem.cs @@ -0,0 +1,11 @@ +using Content.Shared.ItemRecall; + +namespace Content.Client.ItemRecall; + +/// +/// System for handling the ItemRecall ability for wizards. +/// +public sealed partial class ItemRecallSystem : SharedItemRecallSystem +{ + +} diff --git a/Content.Server/ItemRecall/ItemRecallSystem.cs b/Content.Server/ItemRecall/ItemRecallSystem.cs new file mode 100644 index 0000000000..88972e9e35 --- /dev/null +++ b/Content.Server/ItemRecall/ItemRecallSystem.cs @@ -0,0 +1,11 @@ +using Content.Shared.ItemRecall; + +namespace Content.Server.ItemRecall; + +/// +/// System for handling the ItemRecall ability for wizards. +/// +public sealed partial class ItemRecallSystem : SharedItemRecallSystem +{ + +} diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/BaseActionComponent.cs index c3aa6cc97e..25b36df2af 100644 --- a/Content.Shared/Actions/BaseActionComponent.cs +++ b/Content.Shared/Actions/BaseActionComponent.cs @@ -167,6 +167,14 @@ public abstract partial class BaseActionComponent : Component [DataField] public bool RaiseOnUser; + /// + /// 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. + /// + [DataField] + [Obsolete("This datafield will be reworked in an upcoming action refactor")] + public bool RaiseOnAction; + /// /// Whether or not to automatically add this action to the action bar when it becomes available. /// @@ -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; diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index fc6f0baf77..8079885a5a 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -679,6 +679,9 @@ public abstract class SharedActionsSystem : EntitySystem if (!action.RaiseOnUser && action.Container != null && !HasComp(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 index 0000000000..e057a9945c --- /dev/null +++ b/Content.Shared/ItemRecall/ItemRecallComponent.cs @@ -0,0 +1,43 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ItemRecall; + +/// +/// Component for the ItemRecall action. +/// Used for marking a held item and recalling it back into your hand with second action use. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))] +public sealed partial class ItemRecallComponent : Component +{ + /// + /// The name the action should have while an entity is marked. + /// + [DataField] + public LocId? WhileMarkedName = "item-recall-marked-name"; + + /// + /// The description the action should have while an entity is marked. + /// + [DataField] + public LocId? WhileMarkedDescription = "item-recall-marked-description"; + + /// + /// The name the action starts with. + /// This shouldn't be set in yaml. + /// + [DataField] + public string? InitialName; + + /// + /// The description the action starts with. + /// This shouldn't be set in yaml. + /// + [DataField] + public string? InitialDescription; + + /// + /// The entity currently marked to be recalled by this action. + /// + [DataField, AutoNetworkedField] + public EntityUid? MarkedEntity; +} diff --git a/Content.Shared/ItemRecall/ItemRecallEvents.cs b/Content.Shared/ItemRecall/ItemRecallEvents.cs new file mode 100644 index 0000000000..8bee46a098 --- /dev/null +++ b/Content.Shared/ItemRecall/ItemRecallEvents.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions; + +namespace Content.Shared.ItemRecall; + +/// +/// Raised when using the ItemRecall action. +/// +[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 index 0000000000..a85b22e9e3 --- /dev/null +++ b/Content.Shared/ItemRecall/RecallMarkerComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared.ItemRecall; + + +/// +/// Component used as a marker for an item marked by the ItemRecall ability. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))] +public sealed partial class RecallMarkerComponent : Component +{ + /// + /// The action that marked this item. + /// + [DataField, AutoNetworkedField] + public EntityUid? MarkedByAction; +} diff --git a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs new file mode 100644 index 0000000000..63d38203c6 --- /dev/null +++ b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs @@ -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; + +/// +/// System for handling the ItemRecall ability for wizards. +/// +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(OnMapInit); + SubscribeLocalEvent(OnItemRecallActionUse); + + SubscribeLocalEvent(OnRecallMarkerShutdown); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.InitialName = Name(ent); + ent.Comp.InitialDescription = Description(ent); + } + + private void OnItemRecallActionUse(Entity ent, ref OnItemRecallActionEvent args) + { + if (ent.Comp.MarkedEntity == null) + { + if (!TryComp(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(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 ent) + { + if (!Resolve(ent.Owner, ref ent.Comp, false)) + return; + + if (!TryComp(ent.Comp.MarkedByAction, out var instantAction)) + return; + + var actionOwner = instantAction.AttachedEntity; + + if (actionOwner == null) + return; + + if (TryComp(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 ent, ref ComponentShutdown args) + { + TryUnmarkItem(ent); + } + + private void TryMarkItem(Entity ent, EntityUid item) + { + if (!TryComp(ent, out var instantAction)) + return; + + var actionOwner = instantAction.AttachedEntity; + + if (actionOwner == null) + return; + + AddToPvsOverride(item, actionOwner.Value); + + var marker = AddComp(item); + ent.Comp.MarkedEntity = item; + Dirty(ent); + + marker.MarkedByAction = ent.Owner; + + UpdateActionAppearance(ent); + Dirty(item, marker); + } + + private void TryUnmarkItem(EntityUid item) + { + if (!TryComp(item, out var marker)) + return; + + if (!TryComp(marker.MarkedByAction, out var instantAction)) + return; + + if (TryComp(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(item); + } + + private void UpdateActionAppearance(Entity action) + { + if (!TryComp(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); + } +} diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index bca9b36f89..1d0fc16cbd 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -67,25 +67,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem return; } - var xform = Transform(uid); - TryComp(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(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(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(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 index 0000000000..680c7b7b3f --- /dev/null +++ b/Resources/Locale/en-US/item-recall/item-recall.ftl @@ -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. + diff --git a/Resources/Locale/en-US/store/spellbook-catalog.ftl b/Resources/Locale/en-US/store/spellbook-catalog.ftl index b18cac4f9a..95a8b25e68 100644 --- a/Resources/Locale/en-US/store/spellbook-catalog.ftl +++ b/Resources/Locale/en-US/store/spellbook-catalog.ftl @@ -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 diff --git a/Resources/Prototypes/Catalog/spellbook_catalog.yml b/Resources/Prototypes/Catalog/spellbook_catalog.yml index dfd171d9b2..3ba3189771 100644 --- a/Resources/Prototypes/Catalog/spellbook_catalog.yml +++ b/Resources/Prototypes/Catalog/spellbook_catalog.yml @@ -278,3 +278,16 @@ - 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 index 0000000000..c5bb96870d --- /dev/null +++ b/Resources/Prototypes/Magic/recall_spell.yml @@ -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 index 0000000000000000000000000000000000000000..00bbe363793c6f14ac45c6e52aba3b6852ab5ae9 GIT binary patch literal 7081 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 m!^VOu=Qc_=p|)?T6A)Q