Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wizard Item Recall Spell #34411

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions Content.Client/Actions/ActionsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, Ba
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;
Expand Down
11 changes: 11 additions & 0 deletions Content.Client/ItemRecall/ItemRecallSystem.cs
Original file line number Diff line number Diff line change
@@ -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
{

}
ScarKy0 marked this conversation as resolved.
Show resolved Hide resolved
47 changes: 47 additions & 0 deletions Content.Server/ItemRecall/ItemRecallSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Content.Shared.Actions;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.ItemRecall;
using Content.Shared.Popups;
using Content.Shared.Projectiles;

namespace Content.Server.ItemRecall;

/// <summary>
/// System for handling the ItemRecall ability for wizards.
/// </summary>
public sealed partial class ItemRecallSystem : SharedItemRecallSystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedProjectileSystem _proj = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<RecallMarkerComponent, RecallItemEvent>(OnItemRecall);
}

private void OnItemRecall(Entity<RecallMarkerComponent> ent, ref RecallItemEvent args)
{
RecallItem(ent);
}

private void RecallItem(Entity<RecallMarkerComponent> ent)
{
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.PopupEntity(Loc.GetString("item-recall-item-summon", ("item", ent)), actionOwner.Value, actionOwner.Value);

_hands.TryForcePickupAnyHand(actionOwner.Value, ent);
}
}
ScarKy0 marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions Content.Shared/Actions/BaseActionComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ public EntityUid? EntityIcon
[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]
public bool RaiseOnAction;
ScarKy0 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Whether or not to automatically add this action to the action bar when it becomes available.
/// </summary>
Expand Down Expand Up @@ -212,6 +219,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;
Expand All @@ -223,6 +231,7 @@ protected BaseActionComponentState(BaseActionComponent component, IEntityManager
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;
Expand Down
3 changes: 3 additions & 0 deletions Content.Shared/Actions/SharedActionsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,9 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti
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;
}
Expand Down
20 changes: 20 additions & 0 deletions Content.Shared/ItemRecall/ItemRecallComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared.ItemRecall;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedItemRecallSystem))]
public sealed partial class ItemRecallComponent : Component
{
[DataField]
public LocId? WhileMarkedName = "item-recall-marked-name";

[DataField]
public LocId? WhileMarkedDescription = "item-recall-marked-description";

/// <summary>
/// The entity currently marked to be recalled by this action.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? MarkedEntity;
}
ScarKy0 marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 15 additions & 0 deletions Content.Shared/ItemRecall/ItemRecallEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Content.Shared.Actions;

namespace Content.Shared.ItemRecall;

/// <summary>
/// Raised when using the ItemRecall action.
/// </summary>
[ByRefEvent]
public sealed partial class OnItemRecallActionEvent : InstantActionEvent;

/// <summary>
/// Raised on the item to recall it back to its user.
/// </summary>
[ByRefEvent]
public record struct RecallItemEvent;
14 changes: 14 additions & 0 deletions Content.Shared/ItemRecall/RecallMarkerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared.ItemRecall;

[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;
}
134 changes: 134 additions & 0 deletions Content.Shared/ItemRecall/SharedItemRecallSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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.Network;

namespace Content.Shared.ItemRecall;

/// <summary>
/// System for handling the ItemRecall ability for wizards.
/// </summary>
public abstract partial class SharedItemRecallSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ItemRecallComponent, OnItemRecallActionEvent>(OnItemRecallActionUse);

SubscribeLocalEvent<RecallMarkerComponent, ComponentShutdown>(OnRecallMarkerShutdown);
}

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;
ScarKy0 marked this conversation as resolved.
Show resolved Hide resolved
}

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, args.Performer);
return;
}

var ev = new RecallItemEvent();
RaiseLocalEvent(ent.Comp.MarkedEntity.Value, ref ev);
args.Handled = true;
}

private void OnRecallMarkerShutdown(Entity<RecallMarkerComponent> ent, ref ComponentShutdown args)
{
TryUnmarkItem(ent);
}

private void TryMarkItem(Entity<ItemRecallComponent> ent, EntityUid item, EntityUid markedBy)
{
EnsureComp<RecallMarkerComponent>(item, out var marker);
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>(item, out var instantAction))
return;

var actionOwner = instantAction.AttachedEntity;

if (actionOwner == null)
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.
_popups.PopupClient(Loc.GetString("item-recall-item-unmark", ("item", item)), actionOwner.Value, actionOwner.Value, PopupType.MediumCaution);

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;

var proto = Prototype(action);

if (proto == null)
return;

if (action.Comp.MarkedEntity == null)
{
_metaData.SetEntityName(action, proto.Name);
_metaData.SetEntityDescription(action, proto.Description);
_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);
}
}
}
50 changes: 31 additions & 19 deletions Content.Shared/Projectiles/SharedProjectileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,7 @@ private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent componen
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.DamagedEntity = 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);
Expand Down Expand Up @@ -135,6 +117,36 @@ private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableP
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.DamagedEntity = false;
}

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))
Expand Down
12 changes: 12 additions & 0 deletions Resources/Locale/en-US/item-recall/item-recall.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
item-recall-grimoire-name = Item Recall
item-recall-grimoire-desc = Mark a held item and summon it back at any time with just a snap of your fingers!

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.

13 changes: 13 additions & 0 deletions Resources/Prototypes/Catalog/spellbook_catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,16 @@
- SpellbookJaunt
- !type:ListingLimitedStockCondition
stock: 2

- type: listing
id: SpellbookItemRecallSwap
name: item-recall-grimoire-name
description: item-recall-grimoire-desc
productAction: ActionItemRecall
cost:
WizCoin: 1
categories:
- SpellbookUtility
conditions:
- !type:ListingLimitedStockCondition
stock: 1
Loading
Loading