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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Content.Server/Actions/ActionsSystem.cs
Normal file
40
Content.Server/Actions/ActionsSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user