Patched Actions Rework (#6899)

* Rejig Actions

* fix merge errors

* lambda-b-gon

* fix PAI, add innate actions

* Revert "fix PAI, add innate actions"

This reverts commit 4b501ac083e979e31ebd98d7b98077e0dbdd344b.

* Just fix by making nullable.

if only require: true actually did something somehow.

* Make AddActions() ensure an actions component

and misc comments

* misc cleanup

* Limit range even when not checking for obstructions

* remove old guardian code

* rename function and make EntityUid nullable

* fix magboot bug

* fix action search menu

* make targeting toggle all equivalent actions

* fix combat popups (enabling <-> disabling)

* fix networking

* Allow action locking

* prevent telepathy
This commit is contained in:
Leon Friedrich
2022-02-26 18:24:08 +13:00
committed by GitHub
parent d32f884157
commit ff7d4ed9f6
135 changed files with 3156 additions and 5166 deletions

View File

@@ -1,29 +0,0 @@
using Content.Server.CombatMode;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Popups;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
[UsedImplicitly]
[DataDefinition]
public sealed class CombatMode : IToggleAction
{
public bool DoToggleAction(ToggleActionEventArgs args)
{
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(args.Performer, out CombatModeComponent? combatMode))
{
return false;
}
args.Performer.PopupMessage(Loc.GetString(args.ToggledOn ? "hud-combat-enabled" : "hud-combat-disabled"));
combatMode.IsInCombatMode = args.ToggledOn;
return true;
}
}
}

View File

@@ -1,35 +0,0 @@
using Content.Server.Popups;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Cooldown;
using JetBrains.Annotations;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
/// <summary>
/// Just shows a popup message.asd
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed class DebugInstant : IInstantAction, IInstantItemAction
{
[DataField("message")] public string Message { get; [UsedImplicitly] private set; } = "Instant action used.";
[DataField("cooldown")] public float Cooldown { get; [UsedImplicitly] private set; }
public void DoInstantAction(InstantItemActionEventArgs args)
{
args.Performer.PopupMessageEveryone(Message);
if (Cooldown > 0)
{
args.ItemActions?.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(Cooldown));
}
}
public void DoInstantAction(InstantActionEventArgs args)
{
args.Performer.PopupMessageEveryone(Message);
args.PerformerActions?.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(Cooldown));
}
}
}

View File

@@ -1,27 +0,0 @@
using Content.Server.Popups;
using Content.Shared.Actions.Behaviors;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
[UsedImplicitly]
[DataDefinition]
public sealed class DebugTargetEntity : ITargetEntityAction, ITargetEntityItemAction
{
public void DoTargetEntityAction(TargetEntityItemActionEventArgs args)
{
var entMan = IoCManager.Resolve<IEntityManager>();
args.Performer.PopupMessageEveryone(entMan.GetComponent<MetaDataComponent>(args.Item).EntityName + ": Clicked " +
entMan.GetComponent<MetaDataComponent>(args.Target).EntityName);
}
public void DoTargetEntityAction(TargetEntityActionEventArgs args)
{
args.Performer.PopupMessageEveryone("Clicked " + IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(args.Target).EntityName);
}
}
}

View File

@@ -1,26 +0,0 @@
using Content.Server.Popups;
using Content.Shared.Actions.Behaviors;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
[UsedImplicitly]
[DataDefinition]
public sealed class DebugTargetPoint : ITargetPointAction, ITargetPointItemAction
{
public void DoTargetPointAction(TargetPointItemActionEventArgs args)
{
args.Performer.PopupMessageEveryone(IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(args.Item).EntityName + ": Clicked local position " +
args.Target);
}
public void DoTargetPointAction(TargetPointActionEventArgs args)
{
args.Performer.PopupMessageEveryone("Clicked local position " +
args.Target);
}
}
}

View File

@@ -1,48 +0,0 @@
using Content.Server.Popups;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Behaviors.Item;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
[UsedImplicitly]
[DataDefinition]
public sealed class DebugToggle : IToggleAction, IToggleItemAction
{
[DataField("messageOn")] public string MessageOn { get; private set; } = "on!";
[DataField("messageOff")] public string MessageOff { get; private set; } = "off!";
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (args.ToggledOn)
{
args.Performer.PopupMessageEveryone(entMan.GetComponent<MetaDataComponent>(args.Item).EntityName + ": " + MessageOn);
}
else
{
args.Performer.PopupMessageEveryone(entMan.GetComponent<MetaDataComponent>(args.Item).EntityName + ": " +MessageOff);
}
return true;
}
public bool DoToggleAction(ToggleActionEventArgs args)
{
if (args.ToggledOn)
{
args.Performer.PopupMessageEveryone(MessageOn);
}
else
{
args.Performer.PopupMessageEveryone(MessageOff);
}
return true;
}
}
}

View File

@@ -1,126 +0,0 @@
using System;
using System.Linq;
using Content.Server.Act;
using Content.Server.Actions.Events;
using Content.Server.Administration.Logs;
using Content.Server.Interaction;
using Content.Server.Popups;
using Content.Server.Weapon.Melee;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Components;
using Content.Shared.Audio;
using Content.Shared.Cooldown;
using Content.Shared.Database;
using Content.Shared.Popups;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Actions.Actions
{
[UsedImplicitly]
[DataDefinition]
public sealed class DisarmAction : ITargetEntityAction
{
[DataField("failProb")] private float _failProb = 0.4f;
[DataField("pushProb")] private float _pushProb = 0.4f;
[DataField("cooldown")] private float _cooldown = 1.5f;
[ViewVariables]
[DataField("punchMissSound")]
private SoundSpecifier PunchMissSound { get; } = new SoundPathSpecifier("/Audio/Weapons/punchmiss.ogg");
[ViewVariables]
[DataField("disarmSuccessSound")]
private SoundSpecifier DisarmSuccessSound { get; } = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
public void DoTargetEntityAction(TargetEntityActionEventArgs args)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var disarmedActs = entMan.GetComponents<IDisarmedAct>(args.Target).ToArray();
var attemptEvent = new DisarmAttemptEvent(args.Target, args.Performer);
entMan.EventBus.RaiseLocalEvent(args.Target, attemptEvent);
if (attemptEvent.Cancelled)
return;
var sys = EntitySystem.Get<InteractionSystem>();
if (!sys.InRangeUnobstructed(args.Performer, args.Target)) return;
if (disarmedActs.Length == 0)
{
if (entMan.TryGetComponent(args.Performer, out ActorComponent? actor))
{
// Fall back to a normal interaction with the entity
var player = actor.PlayerSession;
var coordinates = entMan.GetComponent<TransformComponent>(args.Target).Coordinates;
var target = args.Target;
sys.HandleUseInteraction(player, coordinates, target);
return;
}
return;
}
if (!entMan.TryGetComponent<SharedActionsComponent?>(args.Performer, out var actions)) return;
if (args.Target == args.Performer || !EntitySystem.Get<ActionBlockerSystem>().CanAttack(args.Performer)) return;
var random = IoCManager.Resolve<IRobustRandom>();
var system = EntitySystem.Get<MeleeWeaponSystem>();
var diff = entMan.GetComponent<TransformComponent>(args.Target).MapPosition.Position - entMan.GetComponent<TransformComponent>(args.Performer).MapPosition.Position;
var angle = Angle.FromWorldVec(diff);
actions.Cooldown(ActionType.Disarm, Cooldowns.SecondsFromNow(_cooldown));
if (random.Prob(_failProb))
{
SoundSystem.Play(Filter.Pvs(args.Performer), PunchMissSound.GetSound(), args.Performer, AudioHelpers.WithVariation(0.025f));
args.Performer.PopupMessageOtherClients(Loc.GetString("disarm-action-popup-message-other-clients",
("performerName", entMan.GetComponent<MetaDataComponent>(args.Performer).EntityName),
("targetName", entMan.GetComponent<MetaDataComponent>(args.Target).EntityName)));
args.Performer.PopupMessageCursor(Loc.GetString("disarm-action-popup-message-cursor",
("targetName", entMan.GetComponent<MetaDataComponent>(args.Target).EntityName)));
system.SendLunge(angle, args.Performer);
return;
}
system.SendAnimation("disarm", angle, args.Performer, args.Performer, new[] { args.Target });
var eventArgs = new DisarmedActEvent() { Target = args.Target, Source = args.Performer, PushProbability = _pushProb };
entMan.EventBus.RaiseLocalEvent(args.Target, eventArgs);
EntitySystem.Get<AdminLogSystem>().Add(LogType.DisarmedAction, LogImpact.Low, $"{entMan.ToPrettyString(args.Performer):user} used disarm on {entMan.ToPrettyString(args.Target):target}");
// Check if the event has been handled, and if so, do nothing else!
if (eventArgs.Handled)
return;
// Sort by priority.
Array.Sort(disarmedActs, (a, b) => a.Priority.CompareTo(b.Priority));
// TODO: Remove this shit.
foreach (var disarmedAct in disarmedActs)
{
if (disarmedAct.Disarmed(eventArgs))
return;
}
SoundSystem.Play(Filter.Pvs(args.Performer), DisarmSuccessSound.GetSound(), entMan.GetComponent<TransformComponent>(args.Performer).Coordinates, AudioHelpers.WithVariation(0.025f));
}
}
}

View File

@@ -1,49 +0,0 @@
using System.Linq;
using Content.Server.Ghost;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Components;
using Content.Shared.Cooldown;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
/// <summary>
/// Blink lights and scare livings
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed class GhostBoo : IInstantAction
{
[DataField("radius")] private float _radius = 3;
[DataField("cooldown")] private float _cooldown = 120;
[DataField("maxTargets")] private int _maxTargets = 3;
public void DoInstantAction(InstantActionEventArgs args)
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (!entMan.TryGetComponent<SharedActionsComponent?>(args.Performer, out var actions)) return;
// find all IGhostBooAffected nearby and do boo on them
var ents = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(args.Performer, _radius);
var booCounter = 0;
foreach (var ent in ents)
{
var ghostBoo = new GhostBooEvent();
entMan.EventBus.RaiseLocalEvent(ent, ghostBoo);
if (ghostBoo.Handled)
booCounter++;
if (booCounter >= _maxTargets)
break;
}
actions.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(_cooldown));
}
}
}

View File

@@ -1,38 +0,0 @@
using Content.Server.Guardian;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Cooldown;
using Content.Shared.Popups;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Actions.Actions
{
/// <summary>
/// Manifests the guardian saved in the action, using the system
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed class ToggleGuardianAction : IInstantAction
{
[DataField("cooldown")] public float Cooldown { get; [UsedImplicitly] private set; }
public void DoInstantAction(InstantActionEventArgs args)
{
var entManager = IoCManager.Resolve<IEntityManager>();
if (entManager.TryGetComponent(args.Performer, out GuardianHostComponent? hostComponent) &&
hostComponent.HostedGuardian != null)
{
EntitySystem.Get<GuardianSystem>().ToggleGuardian(hostComponent);
args.PerformerActions?.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(Cooldown));
}
else
{
args.Performer.PopupMessage(Loc.GetString("guardian-missing-invalid-action"));
}
}
}
}

View File

@@ -1,36 +0,0 @@
using System;
using System.Linq;
using Content.Server.Ghost;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Components;
using Content.Shared.Cooldown;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Server.GameObjects;
using Content.Shared.Instruments;
namespace Content.Server.Actions.Actions
{
/// <summary>
/// Pull up MIDI instrument interface for PAIs to "play themselves"
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed class PAIMidi : IInstantAction
{
public void DoInstantAction(InstantActionEventArgs args)
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (!entMan.TryGetComponent<ServerUserInterfaceComponent?>(args.Performer, out var serverUi)) return;
if (!entMan.TryGetComponent<ActorComponent?>(args.Performer, out var actor)) return;
if (!serverUi.TryGetBoundUserInterface(InstrumentUiKey.Key,out var bui)) return;
bui.Toggle(actor.PlayerSession);
}
}
}

View File

@@ -1,71 +0,0 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Components;
using Content.Shared.Audio;
using Content.Shared.CharacterAppearance;
using Content.Shared.CharacterAppearance.Components;
using Content.Shared.Cooldown;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using System;
using Robust.Shared.Serialization;
namespace Content.Server.Actions.Actions
{
[UsedImplicitly]
[DataDefinition]
public sealed class ScreamAction : IInstantAction, ISerializationHooks
{
private const float Variation = 0.125f;
private const float Volume = 4f;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[DataField("male", required: true)] private SoundSpecifier _male = default!;
[DataField("female", required: true)] private SoundSpecifier _female = default!;
[DataField("wilhelm", required: true)] private SoundSpecifier _wilhelm = default!;
/// seconds
[DataField("cooldown")] private float _cooldown = 10;
void ISerializationHooks.AfterDeserialization()
{
IoCManager.InjectDependencies(this);
}
public void DoInstantAction(InstantActionEventArgs args)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanSpeak(args.Performer)) return;
if (!_entMan.TryGetComponent<HumanoidAppearanceComponent?>(args.Performer, out var humanoid)) return;
if (!_entMan.TryGetComponent<SharedActionsComponent?>(args.Performer, out var actions)) return;
if (_random.Prob(.01f))
{
SoundSystem.Play(Filter.Pvs(args.Performer), _wilhelm.GetSound(), args.Performer, AudioParams.Default.WithVolume(Volume));
}
else
{
switch (humanoid.Sex)
{
case Sex.Male:
SoundSystem.Play(Filter.Pvs(args.Performer), _male.GetSound(), args.Performer, AudioHelpers.WithVariation(Variation).WithVolume(Volume));
break;
case Sex.Female:
SoundSystem.Play(Filter.Pvs(args.Performer), _female.GetSound(), args.Performer, AudioHelpers.WithVariation(Variation).WithVolume(Volume));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
actions.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(_cooldown));
}
}
}

View File

@@ -0,0 +1,40 @@
using Content.Server.Chat.Managers;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
namespace Content.Server.Actions
{
[UsedImplicitly]
public sealed class ActionsSystem : SharedActionsSystem
{
[Dependency] private readonly IChatManager _chatMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActionsComponent, PlayerAttachedEvent>(OnPlayerAttached);
}
private void OnPlayerAttached(EntityUid uid, ActionsComponent component, PlayerAttachedEvent args)
{
// need to send state to new player.
component.Dirty();
}
protected override bool PerformBasicActions(EntityUid user, ActionType action)
{
var result = base.PerformBasicActions(user, action);
if (!string.IsNullOrWhiteSpace(action.Speech))
{
_chatMan.EntitySay(user, Loc.GetString(action.Speech));
result = true;
}
return result;
}
}
}

View File

@@ -1,64 +0,0 @@
using System;
using Content.Server.Administration;
using Content.Server.Commands;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Content.Server.Actions.Commands
{
[AdminCommand(AdminFlags.Debug)]
public sealed class CooldownAction : IConsoleCommand
{
public string Command => "coolaction";
public string Description => "Sets a cooldown on an action for a player, defaulting to current player";
public string Help => "coolaction <actionType> <seconds> <name or userID, omit for current player>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player as IPlayerSession;
if (player?.AttachedEntity is not {} attachedEntity) return;
if (args.Length > 2)
{
var target = args[2];
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(attachedEntity, out ServerActionsComponent? actionsComponent))
{
shell.WriteError("user has no actions component");
return;
}
var actionTypeRaw = args[0];
if (!Enum.TryParse<ActionType>(actionTypeRaw, out var actionType))
{
shell.WriteLine("unrecognized ActionType enum value, please" +
" ensure you used correct casing: " + actionTypeRaw);
return;
}
var actionMgr = IoCManager.Resolve<ActionManager>();
if (!actionMgr.TryGet(actionType, out var action))
{
shell.WriteLine("unrecognized actionType " + actionType);
return;
}
var cooldownStart = IoCManager.Resolve<IGameTiming>().CurTime;
if (!uint.TryParse(args[1], out var seconds))
{
shell.WriteLine("cannot parse seconds: " + args[1]);
return;
}
var cooldownEnd = cooldownStart.Add(TimeSpan.FromSeconds(seconds));
actionsComponent.Cooldown(action.ActionType, (cooldownStart, cooldownEnd));
}
}
}

View File

@@ -1,53 +0,0 @@
using System;
using Content.Server.Administration;
using Content.Server.Commands;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Actions.Commands
{
[AdminCommand(AdminFlags.Debug)]
public sealed class GrantAction : IConsoleCommand
{
public string Command => "grantaction";
public string Description => "Grants an action to a player, defaulting to current player";
public string Help => "grantaction <actionType> <name or userID, omit for current player>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player as IPlayerSession;
if (player?.AttachedEntity == null) return;
var attachedEntity = player.AttachedEntity.Value;
if (args.Length > 1)
{
var target = args[1];
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (attachedEntity == default) return;
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(attachedEntity, out ServerActionsComponent? actionsComponent))
{
shell.WriteLine("user has no actions component");
return;
}
var actionTypeRaw = args[0];
if (!Enum.TryParse<ActionType>(actionTypeRaw, out var actionType))
{
shell.WriteLine("unrecognized ActionType enum value, please" +
" ensure you used correct casing: " + actionTypeRaw);
return;
}
var actionMgr = IoCManager.Resolve<ActionManager>();
if (!actionMgr.TryGet(actionType, out var action))
{
shell.WriteLine("unrecognized actionType " + actionType);
return;
}
actionsComponent.Grant(action.ActionType);
}
}
}

View File

@@ -1,54 +0,0 @@
using System;
using Content.Server.Administration;
using Content.Server.Commands;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Actions.Commands
{
[AdminCommand(AdminFlags.Debug)]
public sealed class RevokeAction : IConsoleCommand
{
public string Command => "revokeaction";
public string Description => "Revokes an action from a player, defaulting to current player";
public string Help => "revokeaction <actionType> <name or userID, omit for current player>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player as IPlayerSession;
if (player?.AttachedEntity == null) return;
var attachedEntity = player.AttachedEntity.Value;
if (args.Length > 1)
{
var target = args[1];
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (attachedEntity == default) return;
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(attachedEntity, out ServerActionsComponent? actionsComponent))
{
shell.WriteLine("user has no actions component");
return;
}
var actionTypeRaw = args[0];
if (!Enum.TryParse<ActionType>(actionTypeRaw, out var actionType))
{
shell.WriteLine("unrecognized ActionType enum value, please" +
" ensure you used correct casing: " + actionTypeRaw);
return;
}
var actionMgr = IoCManager.Resolve<ActionManager>();
if (!actionMgr.TryGet(actionType, out var action))
{
shell.WriteLine("unrecognized actionType " + actionType);
return;
}
actionsComponent.Revoke(action.ActionType);
}
}
}

View File

@@ -1,215 +0,0 @@
using System;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Prototypes;
using Content.Shared.Interaction;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Players;
namespace Content.Server.Actions
{
[RegisterComponent]
[ComponentReference(typeof(SharedActionsComponent))]
public sealed class ServerActionsComponent : SharedActionsComponent
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntityManager _entities = default!;
private float MaxUpdateRange;
protected override void Initialize()
{
base.Initialize();
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnRangeChanged, true);
}
protected override void Shutdown()
{
base.Shutdown();
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnRangeChanged);
}
private void OnRangeChanged(float obj)
{
MaxUpdateRange = obj;
}
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
if (message is not BasePerformActionMessage performActionMessage) return;
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
if (session.AttachedEntity is not {Valid: true} player || player != Owner) return;
var attempt = ActionAttempt(performActionMessage, session);
if (attempt == null) return;
if (!attempt.TryGetActionState(this, out var actionState) || !actionState.Enabled)
{
Logger.DebugS("action", "user {0} attempted to use" +
" action {1} which is not granted to them", _entities.GetComponent<MetaDataComponent>(player).EntityName,
attempt);
return;
}
if (actionState.IsOnCooldown(GameTiming))
{
Logger.DebugS("action", "user {0} attempted to use" +
" action {1} which is on cooldown", _entities.GetComponent<MetaDataComponent>(player).EntityName,
attempt);
return;
}
switch (performActionMessage.BehaviorType)
{
case BehaviorType.Instant:
attempt.DoInstantAction(player);
break;
case BehaviorType.Toggle:
if (performActionMessage is not IToggleActionMessage toggleMsg) return;
if (toggleMsg.ToggleOn == actionState.ToggledOn)
{
Logger.DebugS("action", "user {0} attempted to" +
" toggle action {1} to {2}, but it is already toggled {2}", _entities.GetComponent<MetaDataComponent>(player).EntityName,
attempt.Action.Name, toggleMsg.ToggleOn);
return;
}
if (attempt.DoToggleAction(player, toggleMsg.ToggleOn))
{
attempt.ToggleAction(this, toggleMsg.ToggleOn);
}
else
{
// if client predicted the toggle will work, need to reset
// that prediction
Dirty();
}
break;
case BehaviorType.TargetPoint:
if (performActionMessage is not ITargetPointActionMessage targetPointMsg) return;
if (!CheckRangeAndSetFacing(targetPointMsg.Target, player)) return;
attempt.DoTargetPointAction(player, targetPointMsg.Target);
break;
case BehaviorType.TargetEntity:
if (performActionMessage is not ITargetEntityActionMessage targetEntityMsg) return;
if (!EntityManager.EntityExists(targetEntityMsg.Target))
{
Logger.DebugS("action", "user {0} attempted to" +
" perform target entity action {1} but could not find entity with " +
"provided uid {2}", _entities.GetComponent<MetaDataComponent>(player).EntityName, attempt.Action.Name,
targetEntityMsg.Target);
return;
}
if (!CheckRangeAndSetFacing(_entities.GetComponent<TransformComponent>(targetEntityMsg.Target).Coordinates, player)) return;
attempt.DoTargetEntityAction(player, targetEntityMsg.Target);
break;
case BehaviorType.None:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private IActionAttempt? ActionAttempt(BasePerformActionMessage message, ICommonSession session)
{
IActionAttempt? attempt;
var player = session.AttachedEntity;
switch (message)
{
case PerformActionMessage performActionMessage:
if (!ActionManager.TryGet(performActionMessage.ActionType, out var action))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" unrecognized action {1}", player,
performActionMessage.ActionType);
return null;
}
attempt = new ActionAttempt(action);
break;
case PerformItemActionMessage performItemActionMessage:
var type = performItemActionMessage.ActionType;
if (!ActionManager.TryGet(type, out var itemAction))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" unrecognized item action {1}",
player, type);
return null;
}
var item = performItemActionMessage.Item;
if (!EntityManager.EntityExists(item))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" item action {1} for unknown item {2}",
player, type, item);
return null;
}
if (!_entities.TryGetComponent<ItemActionsComponent?>(item, out var actionsComponent))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" item action {1} for item {2} which has no ItemActionsComponent",
player, type, item);
return null;
}
if (actionsComponent.Holder != player)
{
Logger.DebugS("action", "user {0} attempted to perform" +
" item action {1} for item {2} which they are not holding",
player, type, item);
return null;
}
attempt = new ItemActionAttempt(itemAction, item, actionsComponent);
break;
default:
return null;
}
if (message.BehaviorType != attempt.Action.BehaviorType)
{
Logger.DebugS("action", "user {0} attempted to" +
" perform action {1} as a {2} behavior, but this action is actually a" +
" {3} behavior", player, attempt, message.BehaviorType,
attempt.Action.BehaviorType);
return null;
}
return attempt;
}
private bool CheckRangeAndSetFacing(EntityCoordinates target, EntityUid player)
{
// ensure it's within their clickable range
var targetWorldPos = target.ToMapPos(EntityManager);
var rangeBox = new Box2(_entities.GetComponent<TransformComponent>(player).WorldPosition, _entities.GetComponent<TransformComponent>(player).WorldPosition)
.Enlarged(MaxUpdateRange);
if (!rangeBox.Contains(targetWorldPos))
{
Logger.DebugS("action", "user {0} attempted to" +
" perform target action further than allowed range",
_entities.GetComponent<MetaDataComponent>(player).EntityName);
return false;
}
EntitySystem.Get<RotateToFaceSystem>().TryFaceCoordinates(player, targetWorldPos);
return true;
}
}
}

View File

@@ -1,77 +0,0 @@
using Content.Server.Hands.Components;
using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Cooldown;
using Content.Shared.Item;
using Content.Shared.Popups;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.ViewVariables;
namespace Content.Server.Actions.Spells
{
[UsedImplicitly]
[DataDefinition]
public sealed class GiveItemSpell : IInstantAction
{ //TODO: Needs to be an EntityPrototype for proper validation
[ViewVariables] [DataField("castMessage")] public string? CastMessage { get; set; } = default!;
[ViewVariables] [DataField("cooldown")] public float CoolDown { get; set; } = 1f;
[ViewVariables] [DataField("spellItem", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] public string ItemProto { get; set; } = default!;
[ViewVariables] [DataField("castSound", required: true)] public SoundSpecifier CastSound { get; set; } = default!;
//Rubber-band snapping items into player's hands, originally was a workaround, later found it works quite well with stuns
//Not sure if needs fixing
public void DoInstantAction(InstantActionEventArgs args)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var caster = args.Performer;
if (!entMan.TryGetComponent(caster, out HandsComponent? handsComponent))
{
caster.PopupMessage(Loc.GetString("spell-fail-no-hands"));
return;
}
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(caster, null)) return;
// TODO: Nix when we get EntityPrototype serializers
if (!IoCManager.Resolve<IPrototypeManager>().HasIndex<EntityPrototype>(ItemProto))
{
Logger.Error($"Invalid prototype {ItemProto} supplied for {nameof(GiveItemSpell)}");
return;
}
// TODO: Look this is shitty and ideally a test would do it
var spawnedProto = entMan.SpawnEntity(ItemProto, entMan.GetComponent<TransformComponent>(caster).MapPosition);
if (!entMan.TryGetComponent(spawnedProto, out SharedItemComponent? itemComponent))
{
Logger.Error($"Tried to use {nameof(GiveItemSpell)} but prototype has no {nameof(SharedItemComponent)}?");
entMan.DeleteEntity(spawnedProto);
return;
}
args.PerformerActions?.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(CoolDown));
if (CastMessage != null)
caster.PopupMessageEveryone(CastMessage);
handsComponent.PutInHandOrDrop(itemComponent);
SoundSystem.Play(Filter.Pvs(caster), CastSound.GetSound(), caster);
}
}
}