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;
--- /dev/null
+using Content.Shared.ItemRecall;
+
+namespace Content.Client.ItemRecall;
+
+/// <summary>
+/// System for handling the ItemRecall ability for wizards.
+/// </summary>
+public sealed partial class ItemRecallSystem : SharedItemRecallSystem
+{
+
+}
--- /dev/null
+using Content.Shared.ItemRecall;
+
+namespace Content.Server.ItemRecall;
+
+/// <summary>
+/// System for handling the ItemRecall ability for wizards.
+/// </summary>
+public sealed partial class ItemRecallSystem : SharedItemRecallSystem
+{
+
+}
[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>
public int Priority;
public NetEntity? AttachedEntity;
public bool RaiseOnUser;
+ public bool RaiseOnAction;
public bool AutoPopulate;
public bool Temporary;
public ItemActionIconStyle ItemIconStyle;
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;
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;
}
--- /dev/null
+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;
+}
--- /dev/null
+using Content.Shared.Actions;
+
+namespace Content.Shared.ItemRecall;
+
+/// <summary>
+/// Raised when using the ItemRecall action.
+/// </summary>
+[ByRefEvent]
+public sealed partial class OnItemRecallActionEvent : InstantActionEvent;
--- /dev/null
+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;
+}
--- /dev/null
+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);
+ }
+}
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);
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))
--- /dev/null
+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.
+
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
- 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
--- /dev/null
+- 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
{
"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
},
{
"name": "gib"
+ },
+ {
+ "name": "item_recall"
}
]
}