Skip to content

Commit

Permalink
Add EntityWorldTargetAction (#29819)
Browse files Browse the repository at this point in the history
* Add EntityWorldTargetAction initial implementation

* Update obsolete methods

* Partially working EntityWorldTargetAction

* Fix entity selection

* Move and clean up AfterInteract

* Fix building new walls

* Readd no entity or coordinates error

* Consolidate action validation code

* Add summaries to component

---------

Co-authored-by: Ed <[email protected]>
  • Loading branch information
ShadowCommander and TheShuEd authored Aug 8, 2024
1 parent 489efeb commit 1df8451
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 19 deletions.
13 changes: 13 additions & 0 deletions Content.Client/Actions/ActionsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public override void Initialize()
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
}

private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
Expand Down Expand Up @@ -76,6 +77,18 @@ private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent
BaseHandleState<WorldTargetActionComponent>(uid, component, state);
}

private void OnEntityWorldTargetHandleState(EntityUid uid,
EntityWorldTargetActionComponent component,
ref ComponentHandleState args)
{
if (args.Current is not EntityWorldTargetActionComponentState state)
return;

component.Whitelist = state.Whitelist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
}

private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{
// TODO ACTIONS use auto comp states
Expand Down
48 changes: 46 additions & 2 deletions Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,13 @@ private bool TargetingOnUse(in PointerInputCmdArgs args)
switch (action)
{
case WorldTargetActionComponent mapTarget:
return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;
return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;

case EntityTargetActionComponent entTarget:
return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;
return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;

case EntityWorldTargetActionComponent entMapTarget:
return TryTargetEntityWorld(args, actionId, entMapTarget, user, comp) || !entMapTarget.InteractOnMiss;

default:
Logger.Error($"Unknown targeting action: {actionId.GetType()}");
Expand Down Expand Up @@ -266,6 +269,47 @@ private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, En
return true;
}

private bool TryTargetEntityWorld(in PointerInputCmdArgs args,
EntityUid actionId,
EntityWorldTargetActionComponent action,
EntityUid user,
ActionsComponent actionComp)
{
if (_actionsSystem == null)
return false;

var entity = args.EntityUid;
var coords = args.Coordinates;

if (!_actionsSystem.ValidateEntityWorldTarget(user, entity, coords, (actionId, action)))
{
if (action.DeselectOnMiss)
StopTargeting();

return false;
}

if (action.ClientExclusive)
{
if (action.Event != null)
{
action.Event.Entity = entity;
action.Event.Coords = coords;
action.Event.Performer = user;
action.Event.Action = actionId;
}

_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
}
else
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(args.EntityUid), EntityManager.GetNetCoordinates(coords)));

if (!action.Repeat)
StopTargeting();

return true;
}

public void UnloadButton()
{
if (ActionButton == null)
Expand Down
25 changes: 25 additions & 0 deletions Content.Server/Actions/ActionOnInteractSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component,
}
}

// Then EntityWorld target actions
var entWorldOptions = GetValidActions<EntityWorldTargetActionComponent>(actionEnts, args.CanReach);
for (var i = entWorldOptions.Count - 1; i >= 0; i--)
{
var action = entWorldOptions[i];
if (!_actions.ValidateEntityWorldTarget(args.User, args.Target, args.ClickLocation, action))
entWorldOptions.RemoveAt(i);
}

if (entWorldOptions.Count > 0)
{
var (entActId, entAct) = _random.Pick(entWorldOptions);
if (entAct.Event != null)
{
entAct.Event.Performer = args.User;
entAct.Event.Action = entActId;
entAct.Event.Entity = args.Target;
entAct.Event.Coords = args.ClickLocation;
}

_actions.PerformAction(args.User, null, entActId, entAct, entAct.Event, _timing.CurTime, false);
args.Handled = true;
return;
}

// else: try world target actions
var options = GetValidActions<WorldTargetActionComponent>(component.ActionEntities, args.CanReach);
for (var i = options.Count - 1; i >= 0; i--)
Expand Down
28 changes: 28 additions & 0 deletions Content.Shared/Actions/ActionEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ public RequestPerformActionEvent(NetEntity action, NetCoordinates entityCoordina
Action = action;
EntityCoordinatesTarget = entityCoordinatesTarget;
}

public RequestPerformActionEvent(NetEntity action, NetEntity entityTarget, NetCoordinates entityCoordinatesTarget)
{
Action = action;
EntityTarget = entityTarget;
EntityCoordinatesTarget = entityCoordinatesTarget;
}
}

/// <summary>
Expand Down Expand Up @@ -144,6 +151,27 @@ public abstract partial class WorldTargetActionEvent : BaseActionEvent
public EntityCoordinates Target;
}

/// <summary>
/// This is the type of event that gets raised when an <see cref="EntityWorldTargetActionComponent"/> is performed.
/// The <see cref="BaseActionEvent.Performer"/>, <see cref="Entity"/>, and <see cref="Coords"/>
/// fields will automatically be filled out by the <see cref="SharedActionsSystem"/>.
/// </summary>
/// <remarks>
/// To define a new action for some system, you need to create an event that inherits from this class.
/// </remarks>
public abstract partial class EntityWorldTargetActionEvent : BaseActionEvent
{
/// <summary>
/// The entity that the user targeted.
/// </summary>
public EntityUid? Entity;

/// <summary>
/// The coordinates of the location that the user targeted.
/// </summary>
public EntityCoordinates? Coords;
}

/// <summary>
/// Base class for events that are raised when an action gets performed. This should not generally be used outside of the action
/// system.
Expand Down
42 changes: 42 additions & 0 deletions Content.Shared/Actions/EntityWorldTargetActionComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;

namespace Content.Shared.Actions;

/// <summary>
/// Used on action entities to define an action that triggers when targeting an entity or entity coordinates.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class EntityWorldTargetActionComponent : BaseTargetActionComponent
{
public override BaseActionEvent? BaseEvent => Event;

/// <summary>
/// The local-event to raise when this action is performed.
/// </summary>
[DataField]
[NonSerialized]
public EntityWorldTargetActionEvent? Event;

/// <summary>
/// Determines which entities are valid targets for this action.
/// </summary>
/// <remarks>No whitelist check when null.</remarks>
[DataField] public EntityWhitelist? Whitelist;

/// <summary>
/// Whether this action considers the user as a valid target entity when using this action.
/// </summary>
[DataField] public bool CanTargetSelf = true;
}

[Serializable, NetSerializable]
public sealed class EntityWorldTargetActionComponentState(
EntityWorldTargetActionComponent component,
IEntityManager entManager)
: BaseActionComponentState(component, entManager)
{
public EntityWhitelist? Whitelist = component.Whitelist;
public bool CanTargetSelf = component.CanTargetSelf;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Robust.Shared.Map;

namespace Content.Shared.Actions.Events;

[ByRefEvent]
public record struct ValidateActionEntityWorldTargetEvent(
EntityUid User,
EntityUid? Target,
EntityCoordinates? Coords,
bool Cancelled = false);
Loading

0 comments on commit 1df8451

Please sign in to comment.