Wizard Item Recall Spell (#34411)
This commit is contained in:
@@ -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;
|
||||
|
||||
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal file
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
11
Content.Server/ItemRecall/ItemRecallSystem.cs
Normal file
11
Content.Server/ItemRecall/ItemRecallSystem.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
43
Content.Shared/ItemRecall/ItemRecallComponent.cs
Normal file
43
Content.Shared/ItemRecall/ItemRecallComponent.cs
Normal file
@@ -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;
|
||||
}
|
||||
9
Content.Shared/ItemRecall/ItemRecallEvents.cs
Normal file
9
Content.Shared/ItemRecall/ItemRecallEvents.cs
Normal file
@@ -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;
|
||||
18
Content.Shared/ItemRecall/RecallMarkerComponent.cs
Normal file
18
Content.Shared/ItemRecall/RecallMarkerComponent.cs
Normal file
@@ -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;
|
||||
}
|
||||
187
Content.Shared/ItemRecall/SharedItemRecallSystem.cs
Normal file
187
Content.Shared/ItemRecall/SharedItemRecallSystem.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
9
Resources/Locale/en-US/item-recall/item-recall.ftl
Normal file
9
Resources/Locale/en-US/item-recall/item-recall.ftl
Normal file
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
21
Resources/Prototypes/Magic/recall_spell.yml
Normal file
21
Resources/Prototypes/Magic/recall_spell.yml
Normal file
@@ -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
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user