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,30 +1,6 @@
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
namespace Content.Server.Act
{
/// <summary>
/// Implements behavior when an entity is disarmed.
/// </summary>
[RequiresExplicitImplementation, Obsolete("Use the directed event instead.")]
public interface IDisarmedAct
{
/// <summary>
/// Behavior when the entity is disarmed.
/// Return true to prevent the default disarm behavior,
/// or rest of IDisarmedAct behaviors that come after this one from happening.
/// </summary>
bool Disarmed(DisarmedActEvent @event);
/// <summary>
/// Priority for this disarm act.
/// Used to determine act execution order.
/// </summary>
int Priority => 0;
}
public sealed class DisarmedActEvent : HandledEntityEventArgs
public sealed class DisarmedEvent : HandledEntityEventArgs
{
/// <summary>
/// The entity being disarmed.

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);
}
}
}

View File

@@ -1,30 +1,21 @@
using System;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Explosion.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Audio;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Components
{
@@ -90,6 +81,9 @@ namespace Content.Server.Atmos.Components
[DataField("tankFragmentScale")]
public float TankFragmentScale { get; set; } = 10 * Atmospherics.OneAtmosphere;
[DataField("toggleAction", required: true)]
public InstantAction ToggleAction = new();
protected override void Initialize()
{
base.Initialize();
@@ -165,6 +159,7 @@ namespace Content.Server.Atmos.Components
var internals = GetInternalsComponent();
if (internals == null) return;
IsConnected = internals.TryConnectTank(Owner);
EntitySystem.Get<SharedActionsSystem>().SetToggled(ToggleAction, IsConnected);
UpdateUserInterface();
}
@@ -172,6 +167,7 @@ namespace Content.Server.Atmos.Components
{
if (!IsConnected) return;
IsConnected = false;
EntitySystem.Get<SharedActionsSystem>().SetToggled(ToggleAction, false);
GetInternalsComponent(owner)?.DisconnectTank();
UpdateUserInterface();
}
@@ -187,9 +183,6 @@ namespace Content.Server.Atmos.Components
InternalsConnected = IsConnected,
CanConnectInternals = IsFunctional && internals != null
});
if (internals == null || !_entityManager.TryGetComponent<ItemActionsComponent>(Owner, out var itemActions)) return;
itemActions.GrantOrUpdate(ItemActionType.ToggleInternals, IsFunctional, IsConnected);
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
@@ -309,22 +302,4 @@ namespace Content.Server.Atmos.Components
DisconnectFromInternals(eventArgs.User);
}
}
[UsedImplicitly]
[DataDefinition]
public sealed class ToggleInternalsAction : IToggleItemAction
{
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(args.Performer, args.Item))
return false;
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<GasTankComponent?>(args.Item, out var gasTankComponent)) return false;
// no change
if (gasTankComponent.IsConnected == args.ToggledOn) return false;
gasTankComponent.ToggleInternals();
// did we successfully toggle to the desired status?
return gasTankComponent.IsConnected == args.ToggledOn;
}
}
}

View File

@@ -1,10 +1,9 @@
using Content.Server.Atmos.Components;
using Content.Shared.Actions;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.Atmos.EntitySystems
{
@@ -20,6 +19,23 @@ namespace Content.Server.Atmos.EntitySystems
{
base.Initialize();
SubscribeLocalEvent<GasTankComponent, GetVerbsEvent<ActivationVerb>>(AddOpenUIVerb);
SubscribeLocalEvent<GasTankComponent, GetActionsEvent>(OnGetActions);
SubscribeLocalEvent<GasTankComponent, ToggleActionEvent>(OnActionToggle);
}
private void OnGetActions(EntityUid uid, GasTankComponent component, GetActionsEvent args)
{
args.Actions.Add(component.ToggleAction);
}
private void OnActionToggle(EntityUid uid, GasTankComponent component, ToggleActionEvent args)
{
if (args.Handled)
return;
component.ToggleInternals();
args.Handled = true;
}
private void AddOpenUIVerb(EntityUid uid, GasTankComponent component, GetVerbsEvent<ActivationVerb> args)

View File

@@ -1,34 +0,0 @@
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffects;
/// <summary>
/// Forces someone to do a certain action, if they have it.
/// </summary>
public sealed class DoAction : ReagentEffect
{
[DataField("action", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<ActionPrototype>))]
public string Action = default!;
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out SharedActionsComponent? actions))
{
if (!IoCManager.Resolve<IPrototypeManager>().TryIndex<ActionPrototype>(Action, out var proto))
return;
if (actions.IsGranted(proto.ActionType))
{
var attempt = new ActionAttempt(proto);
attempt.DoInstantAction(args.SolutionEntity);
}
}
}
}

View File

@@ -0,0 +1,15 @@
using Content.Server.Speech;
using Content.Shared.Chemistry.Reagent;
namespace Content.Server.Chemistry.ReagentEffects;
/// <summary>
/// Forces someone to scream their lungs out.
/// </summary>
public sealed class Scream : ReagentEffect
{
public override void Effect(ReagentEffectArgs args)
{
EntitySystem.Get<VocalSystem>().TryScream(args.SolutionEntity);
}
}

View File

@@ -1,6 +1,4 @@
using Content.Shared.Actions;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Actions.Components;
using Content.Shared.Clothing;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
@@ -38,8 +36,6 @@ namespace Content.Server.Clothing.Components
EntitySystem.Get<MagbootsSystem>().UpdateMagbootEffects(container.Owner, Owner, true, this);
}
if(_entMan.TryGetComponent<ItemActionsComponent>(Owner, out var itemActions))
itemActions.Toggle(ItemActionType.ToggleMagboots, On);
if (_entMan.TryGetComponent<SharedItemComponent>(Owner, out var item))
item.EquippedPrefix = On ? "on" : null;
if(_entMan.TryGetComponent<SpriteComponent>(Owner, out var sprite))
@@ -49,14 +45,9 @@ namespace Content.Server.Clothing.Components
}
}
public void Toggle(EntityUid user)
{
On = !On;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
Toggle(eventArgs.User);
On = !On;
}
public override ComponentState GetComponentState()
@@ -64,18 +55,4 @@ namespace Content.Server.Clothing.Components
return new MagbootsComponentState(On);
}
}
[UsedImplicitly]
[DataDefinition]
public sealed class ToggleMagbootsAction : IToggleItemAction
{
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<MagbootsComponent?>(args.Item, out var magboots))
return false;
magboots.Toggle(args.Performer);
return true;
}
}
}

View File

@@ -1,18 +1,17 @@
using Content.Server.Alert;
using Content.Server.Atmos.Components;
using Content.Server.Clothing.Components;
using Content.Shared.Actions;
using Content.Shared.Alert;
using Content.Shared.Clothing;
using Content.Shared.Inventory.Events;
using Content.Shared.Movement.EntitySystems;
using Content.Shared.Slippery;
using Content.Shared.Verbs;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.Clothing
{
public sealed class MagbootsSystem : EntitySystem
public sealed class MagbootsSystem : SharedMagbootsSystem
{
[Dependency] private readonly AlertsSystem _alertsSystem = default!;

View File

@@ -1,10 +1,82 @@
using Content.Shared.CombatMode;
using Content.Server.Act;
using Content.Server.Actions.Events;
using Content.Server.Administration.Logs;
using Content.Server.Popups;
using Content.Server.Weapon.Melee;
using Content.Shared.ActionBlocker;
using Content.Shared.Audio;
using Content.Shared.CombatMode;
using Content.Shared.Database;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.CombatMode
{
[UsedImplicitly]
public sealed class CombatModeSystem : SharedCombatModeSystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly MeleeWeaponSystem _meleeWeaponSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly AdminLogSystem _logSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedCombatModeComponent, DisarmActionEvent>(OnEntityActionPerform);
}
private void OnEntityActionPerform(EntityUid uid, SharedCombatModeComponent component, DisarmActionEvent args)
{
if (args.Handled)
return;
if (!_actionBlockerSystem.CanAttack(args.Performer))
return;
var attemptEvent = new DisarmAttemptEvent(args.Target, args.Performer);
RaiseLocalEvent(args.Target, attemptEvent);
if (attemptEvent.Cancelled)
return;
var diff = Transform(args.Target).MapPosition.Position - Transform(args.Performer).MapPosition.Position;
var angle = Angle.FromWorldVec(diff);
var filterAll = Filter.Pvs(args.Performer);
var filterOther = filterAll.RemoveWhereAttachedEntity(e => e == args.Performer);
args.Handled = true;
if (_random.Prob(component.DisarmFailChance))
{
SoundSystem.Play(Filter.Pvs(args.Performer), component.DisarmFailSound.GetSound(), args.Performer, AudioHelpers.WithVariation(0.025f));
var targetName = Name(args.Target);
var msgOther = Loc.GetString(
"disarm-action-popup-message-other-clients",
("performerName", Name(args.Performer)),
("targetName", targetName));
var msgUser = Loc.GetString("disarm-action-popup-message-cursor", ("targetName", targetName ));
_popupSystem.PopupEntity(msgOther, args.Performer, filterOther);
_popupSystem.PopupEntity(msgUser, args.Performer, Filter.Entities(args.Performer));
_meleeWeaponSystem.SendLunge(angle, args.Performer);
return;
}
_meleeWeaponSystem.SendAnimation("disarm", angle, args.Performer, args.Performer, new[] { args.Target });
SoundSystem.Play(filterAll, component.DisarmSuccessSound.GetSound(), args.Performer, AudioHelpers.WithVariation(0.025f));
_logSystem.Add(LogType.DisarmedAction, $"{ToPrettyString(args.Performer):user} used disarm on {ToPrettyString(args.Target):target}");
var eventArgs = new DisarmedEvent() { Target = args.Target, Source = args.Performer, PushProbability = component.DisarmPushChance };
RaiseLocalEvent(args.Target, eventArgs);
}
}
}

View File

@@ -113,7 +113,6 @@ namespace Content.Server.Entry
else
{
IoCManager.Resolve<RecipeManager>().Initialize();
IoCManager.Resolve<ActionManager>().Initialize();
IoCManager.Resolve<BlackboardManager>().Initialize();
IoCManager.Resolve<ConsiderationsManager>().Initialize();
IoCManager.Resolve<IAdminManager>().Initialize();

View File

@@ -1,6 +1,7 @@
using System;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Ghost;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
namespace Content.Server.Ghost.Components
{
@@ -9,5 +10,23 @@ namespace Content.Server.Ghost.Components
public sealed class GhostComponent : SharedGhostComponent
{
public TimeSpan TimeOfDeath { get; set; } = TimeSpan.Zero;
[DataField("booRadius")]
public float BooRadius = 3;
[DataField("booMaxTargets")]
public int BooMaxTargets = 3;
[DataField("action")]
public InstantAction Action = new()
{
UseDelay = TimeSpan.FromSeconds(120),
Icon = new SpriteSpecifier.Texture(new ResourcePath("Interface/Actions/scream.png")),
Name = "action-name-boo",
Description = "action-description-boo",
Event = new BooActionEvent(),
};
}
public sealed class BooActionEvent : PerformActionEvent { }
}

View File

@@ -7,6 +7,7 @@ using Content.Server.Mind.Components;
using Content.Server.Players;
using Content.Server.Visible;
using Content.Server.Warps;
using Content.Shared.Actions;
using Content.Shared.Examine;
using Content.Shared.Follower;
using Content.Shared.Ghost;
@@ -15,10 +16,6 @@ using Content.Shared.Movement.EntitySystems;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Content.Server.Ghost
@@ -30,7 +27,9 @@ namespace Content.Server.Ghost
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly FollowerSystem _followerSystem = default!;
public override void Initialize()
@@ -51,6 +50,30 @@ namespace Content.Server.Ghost
SubscribeNetworkEvent<GhostReturnToBodyRequest>(OnGhostReturnToBodyRequest);
SubscribeNetworkEvent<GhostWarpToLocationRequestEvent>(OnGhostWarpToLocationRequest);
SubscribeNetworkEvent<GhostWarpToTargetRequestEvent>(OnGhostWarpToTargetRequest);
SubscribeLocalEvent<GhostComponent, BooActionEvent>(OnActionPerform);
}
private void OnActionPerform(EntityUid uid, GhostComponent component, BooActionEvent args)
{
if (args.Handled)
return;
var ents = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius);
var booCounter = 0;
foreach (var ent in ents)
{
var ghostBoo = new GhostBooEvent();
RaiseLocalEvent(ent, ghostBoo);
if (ghostBoo.Handled)
booCounter++;
if (booCounter >= component.BooMaxTargets)
break;
}
args.Handled = true;
}
private void OnRelayMoveInput(EntityUid uid, GhostOnMoveComponent component, RelayMoveInputEvent args)
@@ -78,6 +101,8 @@ namespace Content.Server.Ghost
}
component.TimeOfDeath = _gameTiming.RealTime;
_actions.AddAction(uid, component.Action, null);
}
private void OnGhostShutdown(EntityUid uid, GhostComponent component, ComponentShutdown args)
@@ -98,6 +123,8 @@ namespace Content.Server.Ghost
{
eye.VisibilityMask &= ~(uint) VisibilityFlags.Ghost;
}
_actions.RemoveAction(uid, component.Action);
}
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
namespace Content.Server.Guardian
{
@@ -22,5 +23,17 @@ namespace Content.Server.Guardian
/// Container which holds the guardian
/// </summary>
[ViewVariables] public ContainerSlot GuardianContainer = default!;
[DataField("action")]
public InstantAction Action = new()
{
Name = "action-name-guardian",
Description = "action-description-guardian",
Icon = new SpriteSpecifier.Texture(new ResourcePath("Interface/Actions/manifest.png")),
UseDelay = TimeSpan.FromSeconds(2),
Event = new GuardianToggleActionEvent(),
};
}
public sealed class GuardianToggleActionEvent : PerformActionEvent { };
}

View File

@@ -1,9 +1,7 @@
using Content.Server.Actions;
using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Examine;
@@ -13,10 +11,6 @@ using Content.Shared.MobState;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Player;
using Robust.Shared.Utility;
@@ -30,6 +24,7 @@ namespace Content.Server.Guardian
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DamageableSystem _damageSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionSystem = default!;
public override void Initialize()
{
@@ -50,9 +45,22 @@ namespace Content.Server.Guardian
SubscribeLocalEvent<GuardianHostComponent, MobStateChangedEvent>(OnHostStateChange);
SubscribeLocalEvent<GuardianHostComponent, ComponentShutdown>(OnHostShutdown);
SubscribeLocalEvent<GuardianHostComponent, GuardianToggleActionEvent>(OnPerformAction);
SubscribeLocalEvent<GuardianComponent, AttackAttemptEvent>(OnGuardianAttackAttempt);
}
private void OnPerformAction(EntityUid uid, GuardianHostComponent component, GuardianToggleActionEvent args)
{
if (args.Handled)
return;
if (component.HostedGuardian != null)
ToggleGuardian(component);
args.Handled = true;
}
private void OnGuardianUnplayer(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
{
var host = component.Host;
@@ -74,12 +82,14 @@ namespace Content.Server.Guardian
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
{
component.GuardianContainer = uid.EnsureContainer<ContainerSlot>("GuardianContainer");
_actionSystem.AddAction(uid, component.Action, null);
}
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
{
if (component.HostedGuardian == null) return;
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
_actionSystem.RemoveAction(uid, component.Action);
}
private void OnGuardianAttackAttempt(EntityUid uid, GuardianComponent component, AttackAttemptEvent args)
@@ -151,9 +161,6 @@ namespace Content.Server.Guardian
return;
}
// Can't work without actions
EntityManager.EnsureComponent<ServerActionsComponent>(target);
if (component.Injecting) return;
component.Injecting = true;
@@ -175,8 +182,7 @@ namespace Content.Server.Guardian
comp.Used ||
!TryComp<HandsComponent>(ev.User, out var hands) ||
!hands.IsHolding(comp.Owner) ||
HasComp<GuardianHostComponent>(ev.Target) ||
!TryComp<SharedActionsComponent>(ev.Target, out var actions))
HasComp<GuardianHostComponent>(ev.Target))
{
comp.Injecting = false;
return;
@@ -194,8 +200,6 @@ namespace Content.Server.Guardian
{
guardianComponent.Host = ev.Target;
// Grant the user the recall action and notify them
actions.Grant(ActionType.ManifestGuardian);
SoundSystem.Play(Filter.Entities(ev.Target), "/Audio/Effects/guardian_inject.ogg", ev.Target);
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, Filter.Entities(ev.Target));

View File

@@ -13,27 +13,19 @@ using Content.Shared.Popups;
using Content.Shared.Pulling.Components;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Hands.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedHandsComponent))]
#pragma warning disable 618
public sealed class HandsComponent : SharedHandsComponent, IBodyPartAdded, IBodyPartRemoved, IDisarmedAct
public sealed class HandsComponent : SharedHandsComponent, IBodyPartAdded, IBodyPartRemoved
#pragma warning restore 618
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IEntityManager _entities = default!;
[DataField("disarmedSound")] SoundSpecifier _disarmedSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
int IDisarmedAct.Priority => int.MaxValue; // We want this to be the last disarm act to run.
#region Pull/Disarm
void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs args)
@@ -62,33 +54,10 @@ namespace Content.Server.Hands.Components
RemoveHand(args.Slot);
}
bool IDisarmedAct.Disarmed(DisarmedActEvent @event)
{
if (BreakPulls())
return false;
var source = @event.Source;
var target = @event.Target;
SoundSystem.Play(Filter.Pvs(source), _disarmedSound.GetSound(), source, AudioHelpers.WithVariation(0.025f));
if (ActiveHand != null && Drop(ActiveHand, false))
{
source.PopupMessageOtherClients(Loc.GetString("hands-component-disarm-success-others-message", ("disarmer", _entities.GetComponent<MetaDataComponent>(source).EntityName), ("disarmed", _entities.GetComponent<MetaDataComponent>(target).EntityName)));
source.PopupMessageCursor(Loc.GetString("hands-component-disarm-success-message", ("disarmed", _entities.GetComponent<MetaDataComponent>(target).EntityName)));
}
else
{
source.PopupMessageOtherClients(Loc.GetString("hands-component-shove-success-others-message", ("shover", _entities.GetComponent<MetaDataComponent>(source).EntityName), ("shoved", _entities.GetComponent<MetaDataComponent>(target).EntityName)));
source.PopupMessageCursor(Loc.GetString("hands-component-shove-success-message", ("shoved", _entities.GetComponent<MetaDataComponent>(target).EntityName)));
}
return true;
}
private bool BreakPulls()
public bool BreakPulls()
{
// What is this API??
// I just wanted to do actions not deal with this shit...
if (!_entities.TryGetComponent(Owner, out SharedPullerComponent? puller)
|| puller.Pulling is not {Valid: true} pulling || !_entities.TryGetComponent(puller.Pulling.Value, out SharedPullableComponent? pullable))
return false;

View File

@@ -1,9 +1,12 @@
using System.Linq;
using Content.Server.Act;
using Content.Server.Administration.Logs;
using Content.Server.Hands.Components;
using Content.Server.Popups;
using Content.Server.Stack;
using Content.Server.Storage.Components;
using Content.Server.Strip;
using Content.Server.Stunnable;
using Content.Server.Throwing;
using Content.Shared.ActionBlocker;
using Content.Shared.Database;
@@ -18,6 +21,7 @@ using Content.Shared.Popups;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Input.Binding;
@@ -38,6 +42,7 @@ namespace Content.Server.Hands.Systems
[Dependency] private readonly AdminLogSystem _logSystem = default!;
[Dependency] private readonly StrippableSystem _strippableSystem = default!;
[Dependency] private readonly SharedHandVirtualItemSystem _virtualSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
@@ -48,6 +53,7 @@ namespace Content.Server.Hands.Systems
SubscribeNetworkEvent<ClientInteractUsingInHandMsg>(HandleInteractUsingInHand);
SubscribeNetworkEvent<UseInHandMsg>(HandleUseInHand);
SubscribeNetworkEvent<MoveItemFromHandMsg>(HandleMoveItemFromHand);
SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] { typeof(StunSystem) });
SubscribeLocalEvent<HandsComponent, PullAttemptMessage>(HandlePullAttempt);
SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
@@ -76,6 +82,26 @@ namespace Content.Server.Hands.Systems
args.State = new HandsComponentState(hands.Hands, hands.ActiveHand);
}
private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent args)
{
if (args.Handled || component.BreakPulls())
return;
if (component.ActiveHand == null || !component.Drop(component.ActiveHand, false))
return;
var targetName = Name(args.Target);
var msgOther = Loc.GetString("hands-component-disarm-success-others-message", ("disarmer", Name(args.Source)), ("disarmed", targetName));
var msgUser = Loc.GetString("hands-component-disarm-success-message", ("disarmed", targetName));
var filter = Filter.Pvs(args.Source).RemoveWhereAttachedEntity(e => e == args.Source);
_popupSystem.PopupEntity(msgOther, args.Source, filter);
_popupSystem.PopupEntity(msgUser, args.Source, Filter.Entities(args.Source));
args.Handled = true; // no shove/stun.
}
#region EntityInsertRemove
public override void RemoveHeldEntityFromHand(EntityUid uid, Hand hand, SharedHandsComponent? hands = null)
{

View File

@@ -35,7 +35,6 @@ namespace Content.Server.IoC
IoCManager.Register<IServerPreferencesManager, ServerPreferencesManager>();
IoCManager.Register<IServerDbManager, ServerDbManager>();
IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<ActionManager, ActionManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<BlackboardManager, BlackboardManager>();
IoCManager.Register<ConsiderationsManager, ConsiderationsManager>();

View File

@@ -2,7 +2,6 @@ using System.Threading.Tasks;
using Content.Server.Clothing.Components;
using Content.Server.Light.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Light.Component;
@@ -48,17 +47,4 @@ namespace Content.Server.Light.Components
/// </summary>
public byte? LastLevel;
}
[UsedImplicitly]
[DataDefinition]
public sealed class ToggleLightAction : IToggleItemAction
{
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(args.Performer, args.Item)) return false;
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<HandheldLightComponent?>(args.Item, out var lightComponent)) return false;
if (lightComponent.Activated == args.ToggledOn) return false;
return EntitySystem.Get<HandheldLightSystem>().ToggleStatus(args.Performer, lightComponent);
}
}
}

View File

@@ -1,7 +1,5 @@
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Sound;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Light.Components
{
@@ -16,5 +14,8 @@ namespace Content.Server.Light.Components
public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
[ViewVariables] public bool LightOn = false;
[DataField("toggleAction", required: true)]
public InstantAction ToggleAction = new();
}
}

View File

@@ -1,25 +1,19 @@
using System.Collections.Generic;
using Content.Server.Clothing.Components;
using Content.Server.Actions;
using Content.Server.Light.Components;
using Content.Server.Popups;
using Content.Server.PowerCell;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Light.Component;
using Content.Shared.Rounding;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.Utility;
@@ -30,6 +24,7 @@ namespace Content.Server.Light.EntitySystems
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly ActionsSystem _actionSystem = default!;
// TODO: Ideally you'd be able to subscribe to power stuff to get events at certain percentages.. or something?
// But for now this will be better anyway.
@@ -47,6 +42,27 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<HandheldLightComponent, GetActionsEvent>(OnGetActions);
SubscribeLocalEvent<HandheldLightComponent, ToggleActionEvent>(OnToggleAction);
}
private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetActionsEvent args)
{
args.Actions.Add(component.ToggleAction);
}
private void OnToggleAction(EntityUid uid, HandheldLightComponent component, ToggleActionEvent args)
{
if (args.Handled)
return;
if (component.Activated)
TurnOff(component);
else
TurnOn(args.Performer, component);
args.Handled = true;
}
private void OnGetState(EntityUid uid, HandheldLightComponent component, ref ComponentGetState args)
@@ -155,7 +171,6 @@ namespace Content.Server.Light.EntitySystems
SetState(component, false);
component.Activated = false;
UpdateLightAction(component);
_activeLights.Remove(component);
component.LastLevel = null;
component.Dirty(EntityManager);
@@ -174,7 +189,6 @@ namespace Content.Server.Light.EntitySystems
{
SoundSystem.Play(Filter.Pvs(component.Owner), component.TurnOnFailSound.GetSound(), component.Owner);
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-missing-message"), component.Owner, Filter.Entities(user));
UpdateLightAction(component);
return false;
}
@@ -185,12 +199,10 @@ namespace Content.Server.Light.EntitySystems
{
SoundSystem.Play(Filter.Pvs(component.Owner), component.TurnOnFailSound.GetSound(), component.Owner);
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-dead-message"), component.Owner, Filter.Entities(user));
UpdateLightAction(component);
return false;
}
component.Activated = true;
UpdateLightAction(component);
SetState(component, true);
_activeLights.Add(component);
component.LastLevel = GetLevel(component);
@@ -217,13 +229,8 @@ namespace Content.Server.Light.EntitySystems
{
item.EquippedPrefix = on ? "on" : "off";
}
}
private void UpdateLightAction(HandheldLightComponent component)
{
if (!EntityManager.TryGetComponent(component.Owner, out ItemActionsComponent? actions)) return;
actions.Toggle(ItemActionType.ToggleLight, component.Activated);
_actionSystem.SetToggled(component.ToggleAction, on);
}
public void TryUpdate(HandheldLightComponent component, float frameTime)

View File

@@ -1,24 +1,41 @@
using Content.Server.Light.Components;
using Content.Server.Light.Events;
using Content.Shared.Actions;
using Content.Shared.Light;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using System;
using Robust.Shared.IoC;
namespace Content.Server.Light.EntitySystems
{
public sealed class UnpoweredFlashlightSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerbs);
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetActionsEvent>(OnGetActions);
SubscribeLocalEvent<UnpoweredFlashlightComponent, ToggleActionEvent>(OnToggleAction);
}
private void OnToggleAction(EntityUid uid, UnpoweredFlashlightComponent component, ToggleActionEvent args)
{
if (args.Handled)
return;
ToggleLight(component);
args.Handled = true;
}
private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetActionsEvent args)
{
args.Actions.Add(component.ToggleAction);
}
private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetVerbsEvent<ActivationVerb> args)
@@ -49,7 +66,7 @@ namespace Content.Server.Light.EntitySystems
SoundSystem.Play(Filter.Pvs(light.Owner), flashlight.ToggleSound.GetSound(), flashlight.Owner);
RaiseLocalEvent(flashlight.Owner, new LightToggleEvent(flashlight.LightOn));
_actionsSystem.SetToggled(flashlight.ToggleAction, flashlight.LightOn);
}
}
}

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Log;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Content.Shared.Actions;
namespace Content.Server.PAI
{
@@ -20,6 +21,7 @@ namespace Content.Server.PAI
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
public override void Initialize()
{
@@ -30,6 +32,21 @@ namespace Content.Server.PAI
SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<PAIComponent, GetVerbsEvent<ActivationVerb>>(AddWipeVerb);
SubscribeLocalEvent<PAIComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<PAIComponent, ComponentShutdown>(OnShutdown);
}
private void OnStartup(EntityUid uid, PAIComponent component, ComponentStartup args)
{
if (component.MidiAction != null)
_actionsSystem.AddAction(uid, component.MidiAction, null);
}
private void OnShutdown(EntityUid uid, PAIComponent component, ComponentShutdown args)
{
if (component.MidiAction != null)
_actionsSystem.RemoveAction(uid, component.MidiAction);
}
private void OnExamined(EntityUid uid, PAIComponent component, ExaminedEvent args)

View File

@@ -0,0 +1,42 @@
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.Utility;
namespace Content.Server.Speech.Components;
/// <summary>
/// Component required for entities to be able to scream.
/// </summary>
[RegisterComponent]
public sealed class VocalComponent : Component
{
[DataField("maleScream")]
public SoundSpecifier MaleScream = new SoundCollectionSpecifier("MaleScreams");
[DataField("femaleScream")]
public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams");
[DataField("wilhelm")]
public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg");
[DataField("audioParams")]
public AudioParams AudioParams = AudioParams.Default.WithVolume(4f);
public const float Variation = 0.125f;
// Not using the in-build sound support for actions, given that the sound is modified non-prototype specific factors like gender.
[DataField("action", required: true)]
public InstantAction Action = new()
{
UseDelay = TimeSpan.FromSeconds(10),
Icon = new SpriteSpecifier.Texture(new ResourcePath("Interface/Actions/scream.png")),
Name = "action-name-scream",
Description = "AAAAAAAAAAAAAAAAAAAAAAAAA",
Event = new ScreamActionEvent(),
};
}
public sealed class ScreamActionEvent : PerformActionEvent { };

View File

@@ -0,0 +1,86 @@
using Content.Server.Speech.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.CharacterAppearance;
using Content.Shared.CharacterAppearance.Components;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Speech;
/// <summary>
/// Fer Screamin
/// </summary>
/// <remarks>
/// Or I guess other vocalizations, like laughing. If fun is ever legalized on the station.
/// </remarks>
public sealed class VocalSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VocalComponent, ScreamActionEvent>(OnActionPerform);
SubscribeLocalEvent<VocalComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<VocalComponent, ComponentShutdown>(OnShutdown);
}
private void OnStartup(EntityUid uid, VocalComponent component, ComponentStartup args)
{
_actions.AddAction(uid, component.Action, null);
}
private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args)
{
_actions.RemoveAction(uid, component.Action);
}
private void OnActionPerform(EntityUid uid, VocalComponent component, ScreamActionEvent args)
{
if (args.Handled)
return;
args.Handled = TryScream(uid, component);
}
public bool TryScream(EntityUid uid, VocalComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return false;
if (!_blocker.CanSpeak(uid))
return false;
// Currently this requires humanoid appearance & doesn't have any sort of fall-back or gender-neutral scream.
if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid))
return false;
if (_random.Prob(.01f))
{
SoundSystem.Play(Filter.Pvs(uid), component.Wilhelm.GetSound(), uid, component.AudioParams);
return true;
}
var scale = (float) _random.NextGaussian(1, VocalComponent.Variation);
var pitchedParams = component.AudioParams.WithPitchScale(scale);
switch (humanoid.Sex)
{
case Sex.Male:
SoundSystem.Play(Filter.Pvs(uid), component.MaleScream.GetSound(), uid, pitchedParams);
break;
case Sex.Female:
SoundSystem.Play(Filter.Pvs(uid), component.FemaleScream.GetSound(), uid, pitchedParams);
break;
default:
throw new ArgumentOutOfRangeException();
}
return true;
}
}

View File

@@ -26,10 +26,10 @@ namespace Content.Server.Stunnable
{
base.Initialize();
SubscribeLocalEvent<StatusEffectsComponent, DisarmedActEvent>(OnDisarmed);
SubscribeLocalEvent<StatusEffectsComponent, DisarmedEvent>(OnDisarmed);
}
private void OnDisarmed(EntityUid uid, StatusEffectsComponent status, DisarmedActEvent args)
private void OnDisarmed(EntityUid uid, StatusEffectsComponent status, DisarmedEvent args)
{
if (args.Handled || !_random.Prob(args.PushProbability))
return;

View File

@@ -1,5 +1,7 @@
using Content.Server.Administration.Managers;
using Content.Server.Ghost.Components;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Hands;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
@@ -21,12 +23,29 @@ namespace Content.Server.UserInterface
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>((uid, aui, _) => CloseAll(uid, aui));
SubscribeLocalEvent<ActivatableUIComponent, UnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
// *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
SubscribeLocalEvent<ActivatableUIComponent, EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(AddOpenUiVerb);
SubscribeLocalEvent<ServerUserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
}
private void OnActionPerform(EntityUid uid, ServerUserInterfaceComponent component, OpenUiActionEvent args)
{
if (args.Handled || args.Key == null)
return;
if (!TryComp(args.Performer, out ActorComponent? actor))
return;
if (!component.TryGetBoundUserInterface(args.Key, out var bui))
return;
bui.Toggle(actor.PlayerSession);
args.Handled = true;
}
private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)

View File

@@ -0,0 +1,23 @@
using Content.Shared.Actions;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
namespace Content.Server.UserInterface;
public sealed class OpenUiActionEvent : PerformActionEvent, ISerializationHooks
{
[ViewVariables]
public Enum? Key { get; set; }
[DataField("key", readOnly: true, required: true)]
private string _keyRaw = default!;
void ISerializationHooks.AfterDeserialization()
{
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
if (reflectionManager.TryParseEnumReference(_keyRaw, out var key))
Key = key;
else
Logger.Error($"Invalid UI key ({_keyRaw}) in open-UI action");
}
}

View File

@@ -28,7 +28,7 @@ namespace Content.Server.Wieldable
SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<WieldableComponent, ItemWieldedEvent>(OnItemWielded);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, UnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
@@ -208,7 +208,7 @@ namespace Content.Server.Wieldable
_virtualItemSystem.DeleteInHandsMatching(args.User.Value, uid);
}
private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, UnequippedHandEvent args)
private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, GotUnequippedHandEvent args)
{
if (!component.Wielded || component.Owner != args.Unequipped)
return;