Actions System + UI (#2710)

Co-authored-by: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com>
This commit is contained in:
chairbender
2020-12-13 14:28:20 -08:00
committed by GitHub
parent fd0df9a00a
commit 7a3c281f60
150 changed files with 7283 additions and 854 deletions

View File

@@ -0,0 +1,39 @@
using Content.Server.Utility;
using Content.Shared.Actions;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
/// <summary>
/// Just shows a popup message.asd
/// </summary>
[UsedImplicitly]
public class DebugInstant : IInstantAction, IInstantItemAction
{
public string Message { get; private set; }
public float Cooldown { get; private set; }
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Message, "message", "Instant action used.");
serializer.DataField(this, x => x.Cooldown, "cooldown", 0);
}
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

@@ -0,0 +1,28 @@
using Content.Server.Utility;
using Content.Shared.Actions;
using JetBrains.Annotations;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
[UsedImplicitly]
public class DebugTargetEntity : ITargetEntityAction, ITargetEntityItemAction
{
public void ExposeData(ObjectSerializer serializer)
{
}
public void DoTargetEntityAction(TargetEntityItemActionEventArgs args)
{
args.Performer.PopupMessageEveryone(args.Item.Name + ": Clicked " +
args.Target.Name);
}
public void DoTargetEntityAction(TargetEntityActionEventArgs args)
{
args.Performer.PopupMessageEveryone("Clicked " +
args.Target.Name);
}
}
}

View File

@@ -0,0 +1,29 @@
using Content.Server.Utility;
using Content.Shared.Actions;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
[UsedImplicitly]
public class DebugTargetPoint : ITargetPointAction, ITargetPointItemAction
{
public void ExposeData(ObjectSerializer serializer)
{
}
public void DoTargetPointAction(TargetPointItemActionEventArgs args)
{
args.Performer.PopupMessageEveryone(args.Item.Name + ": Clicked local position " +
args.Target);
}
public void DoTargetPointAction(TargetPointActionEventArgs args)
{
args.Performer.PopupMessageEveryone("Clicked local position " +
args.Target);
}
}
}

View File

@@ -0,0 +1,48 @@
using Content.Server.Utility;
using Content.Shared.Actions;
using JetBrains.Annotations;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
[UsedImplicitly]
public class DebugToggle : IToggleAction, IToggleItemAction
{
public string MessageOn { get; private set; }
public string MessageOff { get; private set; }
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.MessageOn, "messageOn", "on!");
serializer.DataField(this, x => x.MessageOff, "messageOff", "off!");
}
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (args.ToggledOn)
{
args.Performer.PopupMessageEveryone(args.Item.Name + ": " + MessageOn);
}
else
{
args.Performer.PopupMessageEveryone(args.Item.Name + ": " +MessageOff);
}
return true;
}
public bool DoToggleAction(ToggleActionEventArgs args)
{
if (args.ToggledOn)
{
args.Performer.PopupMessageEveryone(MessageOn);
}
else
{
args.Performer.PopupMessageEveryone(MessageOff);
}
return true;
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Preferences;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
[UsedImplicitly]
public class ScreamAction : IInstantAction
{
private const float Variation = 0.125f;
private const float Volume = 4f;
private List<string> _male;
private List<string> _female;
private string _wilhelm;
/// seconds
private float _cooldown;
private IRobustRandom _random;
public ScreamAction()
{
_random = IoCManager.Resolve<IRobustRandom>();
}
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _male, "male", null);
serializer.DataField(ref _female, "female", null);
serializer.DataField(ref _wilhelm, "wilhelm", null);
serializer.DataField(ref _cooldown, "cooldown", 10);
}
public void DoInstantAction(InstantActionEventArgs args)
{
if (!ActionBlockerSystem.CanSpeak(args.Performer)) return;
if (!args.Performer.TryGetComponent<HumanoidAppearanceComponent>(out var humanoid)) return;
if (!args.Performer.TryGetComponent<SharedActionsComponent>(out var actions)) return;
if (_random.Prob(.01f) && !string.IsNullOrWhiteSpace(_wilhelm))
{
EntitySystem.Get<AudioSystem>().PlayFromEntity(_wilhelm, args.Performer, AudioParams.Default.WithVolume(Volume));
}
else
{
switch (humanoid.Sex)
{
case Sex.Male:
if (_male == null) break;
EntitySystem.Get<AudioSystem>().PlayFromEntity(_random.Pick(_male), args.Performer,
AudioHelpers.WithVariation(Variation).WithVolume(Volume));
break;
case Sex.Female:
if (_female == null) break;
EntitySystem.Get<AudioSystem>().PlayFromEntity(_random.Pick(_female), args.Performer,
AudioHelpers.WithVariation(Variation).WithVolume(Volume));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
actions.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(_cooldown));
}
}
}

View File

@@ -0,0 +1,24 @@
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.Alert;
using JetBrains.Annotations;
using Robust.Shared.Serialization;
namespace Content.Server.Alert.Click
{
/// <summary>
/// Resist fire
/// </summary>
[UsedImplicitly]
public class ResistFire : IAlertClick
{
public void ExposeData(ObjectSerializer serializer) { }
public void AlertClicked(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out FlammableComponent flammable))
{
flammable.Resist();
}
}
}
}

View File

@@ -0,0 +1,24 @@
using Content.Server.GameObjects.Components.Movement;
using Content.Shared.Alert;
using JetBrains.Annotations;
using Robust.Shared.Serialization;
namespace Content.Server.Alert.Click
{
/// <summary>
/// Stop piloting shuttle
/// </summary>
[UsedImplicitly]
public class StopPiloting : IAlertClick
{
public void ExposeData(ObjectSerializer serializer) { }
public void AlertClicked(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out ShuttleControllerComponent controller))
{
controller.RemoveController();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.Pulling;
using Content.Shared.GameObjects.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Serialization;
namespace Content.Server.Alert.Click
{
/// <summary>
/// Stop pulling something
/// </summary>
[UsedImplicitly]
public class StopPulling : IAlertClick
{
public void ExposeData(ObjectSerializer serializer) { }
public void AlertClicked(ClickAlertEventArgs args)
{
EntitySystem
.Get<SharedPullingSystem>()
.GetPulled(args.Player)?
.GetComponentOrNull<SharedPullableComponent>()?
.TryStopPull();
}
}
}

View File

@@ -0,0 +1,24 @@
using Content.Server.GameObjects.Components.Buckle;
using Content.Shared.Alert;
using Robust.Shared.Serialization;
using JetBrains.Annotations;
namespace Content.Server.Alert.Click
{
/// <summary>
/// Unbuckles if player is currently buckled.
/// </summary>
[UsedImplicitly]
public class Unbuckle : IAlertClick
{
public void ExposeData(ObjectSerializer serializer) { }
public void AlertClicked(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out BuckleComponent buckle))
{
buckle.TryUnbuckle(args.Player);
}
}
}
}

View File

@@ -0,0 +1,65 @@
#nullable enable
using System;
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
namespace Content.Server.Commands.Actions
{
[AdminCommand(AdminFlags.Debug)]
public sealed class CooldownAction : IClientCommand
{
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, IPlayerSession? player, string[] args)
{
if (player == null) return;
var attachedEntity = player.AttachedEntity;
if (args.Length > 2)
{
var target = args[2];
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (attachedEntity == null) return;
if (!attachedEntity.TryGetComponent(out ServerActionsComponent? actionsComponent))
{
shell.SendText(player, "user has no actions component");
return;
}
var actionTypeRaw = args[0];
if (!Enum.TryParse<ActionType>(actionTypeRaw, out var actionType))
{
shell.SendText(player, "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.SendText(player, "unrecognized actionType " + actionType);
return;
}
var cooldownStart = IoCManager.Resolve<IGameTiming>().CurTime;
if (!uint.TryParse(args[1], out var seconds))
{
shell.SendText(player, "cannot parse seconds: " + args[1]);
return;
}
var cooldownEnd = cooldownStart.Add(TimeSpan.FromSeconds(seconds));
actionsComponent.Cooldown(action.ActionType, (cooldownStart, cooldownEnd));
}
}
}

View File

@@ -0,0 +1,52 @@
#nullable enable
using System;
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.IoC;
namespace Content.Server.Commands.Actions
{
[AdminCommand(AdminFlags.Debug)]
public sealed class GrantAction : IClientCommand
{
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, IPlayerSession? player, string[] args)
{
if (player == null) return;
var attachedEntity = player.AttachedEntity;
if (args.Length > 1)
{
var target = args[1];
if (!Commands.CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (attachedEntity == null) return;
if (!attachedEntity.TryGetComponent(out ServerActionsComponent? actionsComponent))
{
shell.SendText(player, "user has no actions component");
return;
}
var actionTypeRaw = args[0];
if (!Enum.TryParse<ActionType>(actionTypeRaw, out var actionType))
{
shell.SendText(player, "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.SendText(player, "unrecognized actionType " + actionType);
return;
}
actionsComponent.Grant(action.ActionType);
}
}
}

View File

@@ -0,0 +1,53 @@
#nullable enable
using System;
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.IoC;
namespace Content.Server.Commands.Actions
{
[AdminCommand(AdminFlags.Debug)]
public sealed class RevokeAction : IClientCommand
{
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, IPlayerSession? player, string[] args)
{
if (player == null) return;
var attachedEntity = player.AttachedEntity;
if (args.Length > 1)
{
var target = args[1];
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (attachedEntity == null) return;
if (!attachedEntity.TryGetComponent(out ServerActionsComponent? actionsComponent))
{
shell.SendText(player, "user has no actions component");
return;
}
var actionTypeRaw = args[0];
if (!Enum.TryParse<ActionType>(actionTypeRaw, out var actionType))
{
shell.SendText(player, "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.SendText(player, "unrecognized actionType " + actionType);
return;
}
actionsComponent.Revoke(action.ActionType);
}
}
}

View File

@@ -19,22 +19,20 @@ namespace Content.Server.Commands.Alerts
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
var attachedEntity = player?.AttachedEntity;
if (attachedEntity == null)
if (player?.AttachedEntity == null)
{
shell.SendText(player, "You don't have an entity.");
return;
}
var attachedEntity = player.AttachedEntity;
if (args.Length > 1)
{
var target = args[1];
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (!CommandUtils.ValidateAttachedEntity(shell, player, attachedEntity)) return;
if (!attachedEntity.TryGetComponent(out ServerAlertsComponent? alertsComponent))
{
shell.SendText(player, "user has no alerts component");

View File

@@ -39,9 +39,6 @@ namespace Content.Server.Commands.Alerts
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (!CommandUtils.ValidateAttachedEntity(shell, player, attachedEntity))
return;
if (!attachedEntity.TryGetComponent(out ServerAlertsComponent? alertsComponent))
{
shell.SendText(player, "user has no alerts component");

View File

@@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
@@ -17,7 +19,7 @@ namespace Content.Server.Commands
/// sending a failure to the performer if unable to.
/// </summary>
public static bool TryGetSessionByUsernameOrId(IConsoleShell shell,
string usernameOrId, IPlayerSession performer, out IPlayerSession session)
string usernameOrId, IPlayerSession performer, [NotNullWhen(true)] out IPlayerSession? session)
{
var plyMgr = IoCManager.Resolve<IPlayerManager>();
if (plyMgr.TryGetSessionByUsername(usernameOrId, out session)) return true;
@@ -37,7 +39,7 @@ namespace Content.Server.Commands
/// sending a failure to the performer if unable to.
/// </summary>
public static bool TryGetAttachedEntityByUsernameOrId(IConsoleShell shell,
string usernameOrId, IPlayerSession performer, out IEntity attachedEntity)
string usernameOrId, IPlayerSession performer, [NotNullWhen(true)] out IEntity? attachedEntity)
{
attachedEntity = null;
if (!TryGetSessionByUsernameOrId(shell, usernameOrId, performer, out var session)) return false;
@@ -50,17 +52,5 @@ namespace Content.Server.Commands
attachedEntity = session.AttachedEntity;
return true;
}
/// <summary>
/// Checks if attached entity is null, returning false and sending a message
/// to performer if not.
/// </summary>
public static bool ValidateAttachedEntity(IConsoleShell shell, IPlayerSession performer, IEntity attachedEntity)
{
if (attachedEntity != null) return true;
shell.SendText(performer, "User has no attached entity.");
return false;
}
}
}

View File

@@ -10,6 +10,7 @@ using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Interfaces.PDA;
using Content.Server.Sandbox;
using Content.Shared.Actions;
using Content.Shared.Kitchen;
using Content.Shared.Alert;
using Robust.Server.Interfaces.Player;
@@ -81,6 +82,7 @@ namespace Content.Server
_gameTicker.Initialize();
IoCManager.Resolve<RecipeManager>().Initialize();
IoCManager.Resolve<AlertManager>().Initialize();
IoCManager.Resolve<ActionManager>().Initialize();
IoCManager.Resolve<BlackboardManager>().Initialize();
IoCManager.Resolve<ConsiderationsManager>().Initialize();
IoCManager.Resolve<IPDAUplinkManager>().Initialize();

View File

@@ -100,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return;
}
status?.ShowAlert(AlertType.Fire, onClickAlert: OnClickAlert);
status?.ShowAlert(AlertType.Fire);
if (FireStacks > 0)
{
@@ -152,14 +152,6 @@ namespace Content.Server.GameObjects.Components.Atmos
}
}
private void OnClickAlert(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out FlammableComponent flammable))
{
flammable.Resist();
}
}
public void CollideWith(IEntity collidedWith)
{
if (!collidedWith.TryGetComponent(out FlammableComponent otherFlammable))

View File

@@ -5,18 +5,22 @@ using Content.Server.Explosions;
using Content.Server.GameObjects.Components.Body.Respiratory;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Actions;
using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Atmos.GasTank;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.ComponentDependencies;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
@@ -30,13 +34,15 @@ namespace Content.Server.GameObjects.Components.Atmos
[ComponentReference(typeof(IActivate))]
public class GasTankComponent : SharedGasTankComponent, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
{
private const float MaxExplosionRange = 14f;
private const float MaxExplosionRange = 14f;
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
private float _pressureResistance;
private int _integrity = 3;
[ComponentDependency] private readonly ItemActionsComponent? _itemActions = null;
[ViewVariables] private BoundUserInterface? _userInterface;
[ViewVariables] public GasMixture? Air { get; set; }
@@ -191,14 +197,18 @@ namespace Content.Server.GameObjects.Components.Atmos
private void UpdateUserInterface(bool initialUpdate = false)
{
var internals = GetInternalsComponent();
_userInterface?.SetState(
new GasTankBoundUserInterfaceState
{
TankPressure = Air?.Pressure ?? 0,
OutputPressure = initialUpdate ? OutputPressure : (float?) null,
InternalsConnected = IsConnected,
CanConnectInternals = IsFunctional && GetInternalsComponent() != null
CanConnectInternals = IsFunctional && internals != null
});
if (internals == null) return;
_itemActions?.GrantOrUpdate(ItemActionType.ToggleInternals, IsFunctional, IsConnected);
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
@@ -214,8 +224,9 @@ namespace Content.Server.GameObjects.Components.Atmos
}
}
private void ToggleInternals()
internal void ToggleInternals()
{
if (!ActionBlockerSystem.CanUse(GetInternalsComponent()?.Owner)) return;
if (IsConnected)
{
DisconnectFromInternals();
@@ -311,6 +322,11 @@ namespace Content.Server.GameObjects.Components.Atmos
_integrity++;
}
public void Dropped(DroppedEventArgs eventArgs)
{
DisconnectFromInternals(eventArgs.User);
}
/// <summary>
/// Open interaction window
/// </summary>
@@ -341,10 +357,21 @@ namespace Content.Server.GameObjects.Components.Atmos
component.OpenInterface(actor.playerSession);
}
}
}
public void Dropped(DroppedEventArgs eventArgs)
[UsedImplicitly]
public class ToggleInternalsAction : IToggleItemAction
{
public void ExposeData(ObjectSerializer serializer) {}
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
DisconnectFromInternals(eventArgs.User);
if (!args.Item.TryGetComponent<GasTankComponent>(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

@@ -108,8 +108,7 @@ namespace Content.Server.GameObjects.Components.Buckle
if (Buckled)
{
_serverAlertsComponent.ShowAlert(BuckledTo != null ? BuckledTo.BuckledAlertType : AlertType.Buckled,
onClickAlert: OnClickAlert);
_serverAlertsComponent.ShowAlert(BuckledTo?.BuckledAlertType ?? AlertType.Buckled);
}
else
{
@@ -117,14 +116,6 @@ namespace Content.Server.GameObjects.Components.Buckle
}
}
private void OnClickAlert(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out BuckleComponent? buckle))
{
buckle.TryUnbuckle(args.Player);
}
}
/// <summary>
/// Reattaches this entity to the strap, modifying its position and rotation.

View File

@@ -30,6 +30,7 @@ namespace Content.Server.GameObjects.Components.GUI
[RegisterComponent]
[ComponentReference(typeof(IHandsComponent))]
[ComponentReference(typeof(ISharedHandsComponent))]
[ComponentReference(typeof(SharedHandsComponent))]
public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
@@ -82,7 +83,7 @@ namespace Content.Server.GameObjects.Components.GUI
}
}
public bool IsHolding(IEntity entity)
public override bool IsHolding(IEntity entity)
{
foreach (var hand in _hands)
{
@@ -165,6 +166,7 @@ namespace Content.Server.GameObjects.Components.GUI
}
Dirty();
var success = hand.Container.Insert(item.Owner);
if (success)
{
@@ -172,6 +174,9 @@ namespace Content.Server.GameObjects.Components.GUI
OnItemChanged?.Invoke();
}
_entitySystemManager.GetEntitySystem<InteractionSystem>().EquippedHandInteraction(Owner, item.Owner,
ToSharedHand(hand));
_entitySystemManager.GetEntitySystem<InteractionSystem>().HandSelectedInteraction(Owner, item.Owner);
return success;
@@ -266,6 +271,9 @@ namespace Content.Server.GameObjects.Components.GUI
return false;
}
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedHandInteraction(Owner, item.Owner,
ToSharedHand(hand));
if (doDropInteraction && !DroppedInteraction(item, false))
return false;
@@ -288,6 +296,61 @@ namespace Content.Server.GameObjects.Components.GUI
return true;
}
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
{
if (slot == null)
{
throw new ArgumentNullException(nameof(slot));
}
if (targetContainer == null)
{
throw new ArgumentNullException(nameof(targetContainer));
}
var hand = GetHand(slot);
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
{
return false;
}
if (!hand.Container.CanRemove(hand.Entity))
{
return false;
}
if (!targetContainer.CanInsert(hand.Entity))
{
return false;
}
var item = hand.Entity.GetComponent<ItemComponent>();
if (!hand.Container.Remove(hand.Entity))
{
throw new InvalidOperationException();
}
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedHandInteraction(Owner, item.Owner,
ToSharedHand(hand));
if (doDropInteraction && !DroppedInteraction(item, doMobChecks))
return false;
item.RemovedFromSlot();
if (!targetContainer.Insert(item.Owner))
{
throw new InvalidOperationException();
}
OnItemChanged?.Invoke();
Dirty();
return true;
}
public bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true)
{
if (entity == null)
@@ -323,57 +386,6 @@ namespace Content.Server.GameObjects.Components.GUI
return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction);
}
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
{
if (slot == null)
{
throw new ArgumentNullException(nameof(slot));
}
if (targetContainer == null)
{
throw new ArgumentNullException(nameof(targetContainer));
}
var hand = GetHand(slot);
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
{
return false;
}
if (!hand.Container.CanRemove(hand.Entity))
{
return false;
}
if (!targetContainer.CanInsert(hand.Entity))
{
return false;
}
var item = hand.Entity.GetComponent<ItemComponent>();
if (!hand.Container.Remove(hand.Entity))
{
throw new InvalidOperationException();
}
if (doDropInteraction && !DroppedInteraction(item, doMobChecks))
return false;
item.RemovedFromSlot();
if (!targetContainer.Insert(item.Owner))
{
throw new InvalidOperationException();
}
OnItemChanged?.Invoke();
Dirty();
return true;
}
public bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
{
if (entity == null)
@@ -463,19 +475,28 @@ namespace Content.Server.GameObjects.Components.GUI
for (var i = 0; i < _hands.Count; i++)
{
var location = i == 0
? HandLocation.Right
: i == _hands.Count - 1
? HandLocation.Left
: HandLocation.Middle;
var hand = _hands[i].ToShared(i, location);
var hand = _hands[i].ToShared(i, IndexToHandLocation(i));
hands[i] = hand;
}
return new HandsComponentState(hands, ActiveHand);
}
private HandLocation IndexToHandLocation(int index)
{
return index == 0
? HandLocation.Right
: index == _hands.Count - 1
? HandLocation.Left
: HandLocation.Middle;
}
private SharedHand ToSharedHand(Hand hand)
{
var index = _hands.IndexOf(hand);
return hand.ToShared(index, IndexToHandLocation(index));
}
public void SwapHands()
{
if (ActiveHand == null)

View File

@@ -25,6 +25,7 @@ using static Content.Shared.GameObjects.Components.Inventory.SharedInventoryComp
namespace Content.Server.GameObjects.Components.GUI
{
[RegisterComponent]
[ComponentReference(typeof(SharedInventoryComponent))]
public class InventoryComponent : SharedInventoryComponent, IExAct, IEffectBlocker, IPressureProtection
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
@@ -572,5 +573,20 @@ namespace Content.Server.GameObjects.Components.GUI
}
}
}
public override bool IsEquipped(IEntity item)
{
if (item == null) return false;
foreach (var containerSlot in _slotContainers.Values)
{
// we don't want a recursive check here
if (containerSlot.Contains(item))
{
return true;
}
}
return false;
}
}
}

View File

@@ -1,18 +1,26 @@
#nullable enable
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Clothing;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Power;
using Content.Shared.Actions;
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.ComponentDependencies;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
@@ -45,6 +53,8 @@ namespace Content.Server.GameObjects.Components.Interactable
[ViewVariables(VVAccess.ReadWrite)] public string? TurnOnFailSound;
[ViewVariables(VVAccess.ReadWrite)] public string? TurnOffSound;
[ComponentDependency] private readonly ItemActionsComponent? _itemActions = null;
/// <summary>
/// Client-side ItemStatus level
/// </summary>
@@ -98,8 +108,9 @@ namespace Content.Server.GameObjects.Components.Interactable
/// Illuminates the light if it is not active, extinguishes it if it is active.
/// </summary>
/// <returns>True if the light's status was toggled, false otherwise.</returns>
private bool ToggleStatus(IEntity user)
public bool ToggleStatus(IEntity user)
{
if (!ActionBlockerSystem.CanUse(user)) return false;
return Activated ? TurnOff() : TurnOn(user);
}
@@ -112,6 +123,7 @@ namespace Content.Server.GameObjects.Components.Interactable
SetState(false);
Activated = false;
UpdateLightAction();
if (makeNoise)
{
@@ -132,6 +144,7 @@ namespace Content.Server.GameObjects.Components.Interactable
{
if (TurnOnFailSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOnFailSound, Owner);
Owner.PopupMessage(user, Loc.GetString("Cell missing..."));
UpdateLightAction();
return false;
}
@@ -142,10 +155,12 @@ namespace Content.Server.GameObjects.Components.Interactable
{
if (TurnOnFailSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOnFailSound, Owner);
Owner.PopupMessage(user, Loc.GetString("Dead cell..."));
UpdateLightAction();
return false;
}
Activated = true;
UpdateLightAction();
SetState(true);
if (TurnOnSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOnSound, Owner);
@@ -175,6 +190,11 @@ namespace Content.Server.GameObjects.Components.Interactable
}
}
private void UpdateLightAction()
{
_itemActions?.Toggle(ItemActionType.ToggleLight, Activated);
}
public void OnUpdate(float frameTime)
{
if (Cell == null)
@@ -249,4 +269,17 @@ namespace Content.Server.GameObjects.Components.Interactable
}
}
}
[UsedImplicitly]
public class ToggleLightAction : IToggleItemAction
{
public void ExposeData(ObjectSerializer serializer) {}
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (!args.Item.TryGetComponent<HandheldLightComponent>(out var lightComponent)) return false;
if (lightComponent.Activated == args.ToggledOn) return false;
return lightComponent.ToggleStatus(args.Performer);
}
}
}

View File

@@ -0,0 +1,35 @@
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Items
{
/// <summary>
/// Pops up a message when equipped / unequipped (including hands).
/// For debugging purposes.
/// </summary>
[RegisterComponent]
public class DebugEquipComponent : Component, IEquipped, IEquippedHand, IUnequipped, IUnequippedHand
{
public override string Name => "DebugEquip";
public void Equipped(EquippedEventArgs eventArgs)
{
eventArgs.User.PopupMessage("equipped " + Owner.Name);
}
public void EquippedHand(EquippedHandEventArgs eventArgs)
{
eventArgs.User.PopupMessage("equipped hand " + Owner.Name);
}
public void Unequipped(UnequippedEventArgs eventArgs)
{
eventArgs.User.PopupMessage("unequipped " + Owner.Name);
}
public void UnequippedHand(UnequippedHandEventArgs eventArgs)
{
eventArgs.User.PopupMessage("unequipped hand" + Owner.Name);
}
}
}

View File

@@ -0,0 +1,199 @@
#nullable enable
using System;
using Content.Shared.Actions;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
namespace Content.Server.GameObjects.Components.Mobs
{
[RegisterComponent]
[ComponentReference(typeof(SharedActionsComponent))]
public sealed class ServerActionsComponent : SharedActionsComponent
{
[Dependency] private readonly IServerEntityManager _entityManager = default!;
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));
}
var player = session.AttachedEntity;
if (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", player.Name,
attempt);
return;
}
if (actionState.IsOnCooldown(GameTiming))
{
Logger.DebugS("action", "user {0} attempted to use" +
" action {1} which is on cooldown", player.Name,
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}", player.Name,
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.TryGetEntity(targetEntityMsg.Target, out var entity))
{
Logger.DebugS("action", "user {0} attempted to" +
" perform target entity action {1} but could not find entity with " +
"provided uid {2}", player.Name, attempt.Action.Name,
targetEntityMsg.Target);
return;
}
if (!CheckRangeAndSetFacing(entity.Transform.Coordinates, player)) return;
attempt.DoTargetEntityAction(player, entity);
break;
case BehaviorType.None:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private IActionAttempt? ActionAttempt(BasePerformActionMessage message, ICommonSession session)
{
IActionAttempt? attempt;
switch (message)
{
case PerformActionMessage performActionMessage:
if (!ActionManager.TryGet(performActionMessage.ActionType, out var action))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" unrecognized action {1}", session.AttachedEntity,
performActionMessage.ActionType);
return null;
}
attempt = new ActionAttempt(action);
break;
case PerformItemActionMessage performItemActionMessage:
if (!ActionManager.TryGet(performItemActionMessage.ActionType, out var itemAction))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" unrecognized item action {1}",
session.AttachedEntity, performItemActionMessage.ActionType);
return null;
}
if (!EntityManager.TryGetEntity(performItemActionMessage.Item, out var item))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" item action {1} for unknown item {2}",
session.AttachedEntity, performItemActionMessage.ActionType, performItemActionMessage.Item);
return null;
}
if (!item.TryGetComponent<ItemActionsComponent>(out var actionsComponent))
{
Logger.DebugS("action", "user {0} attempted to perform" +
" item action {1} for item {2} which has no ItemActionsComponent",
session.AttachedEntity, performItemActionMessage.ActionType, item);
return null;
}
if (actionsComponent.Holder != session.AttachedEntity)
{
Logger.DebugS("action", "user {0} attempted to perform" +
" item action {1} for item {2} which they are not holding",
session.AttachedEntity, performItemActionMessage.ActionType, 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", session.AttachedEntity, attempt, message.BehaviorType,
attempt.Action.BehaviorType);
return null;
}
return attempt;
}
private bool CheckRangeAndSetFacing(EntityCoordinates target, IEntity player)
{
// ensure it's within their clickable range
var targetWorldPos = target.ToMapPos(EntityManager);
var rangeBox = new Box2(player.Transform.WorldPosition, player.Transform.WorldPosition)
.Enlarged(_entityManager.MaxUpdateRange);
if (!rangeBox.Contains(targetWorldPos))
{
Logger.DebugS("action", "user {0} attempted to" +
" perform target action further than allowed range",
player.Name);
return false;
}
if (!ActionBlockerSystem.CanChangeDirection(player)) return true;
// don't set facing unless they clicked far enough away
var diff = targetWorldPos - player.Transform.WorldPosition;
if (diff.LengthSquared > 0.01f)
{
player.Transform.LocalRotation = new Angle(diff);
}
return true;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
@@ -42,11 +43,6 @@ namespace Content.Server.GameObjects.Components.Mobs
base.OnRemove();
}
public override ComponentState GetComponentState()
{
return new AlertsComponentState(CreateAlertStatesArray());
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
@@ -67,14 +63,21 @@ namespace Content.Server.GameObjects.Components.Mobs
break;
}
// TODO: Implement clicking other status effects in the HUD
if (AlertManager.TryDecode(msg.EncodedAlert, out var alert))
if (!IsShowingAlert(msg.AlertType))
{
PerformAlertClickCallback(alert, player);
Logger.DebugS("alert", "user {0} attempted to" +
" click alert {1} which is not currently showing for them",
player.Name, msg.AlertType);
break;
}
if (AlertManager.TryGet(msg.AlertType, out var alert))
{
alert.OnClick.AlertClicked(new ClickAlertEventArgs(player, alert));
}
else
{
Logger.WarningS("alert", "unrecognized encoded alert {0}", msg.EncodedAlert);
Logger.WarningS("alert", "unrecognized encoded alert {0}", msg.AlertType);
}
break;

View File

@@ -145,15 +145,7 @@ namespace Content.Server.GameObjects.Components.Movement
mind.Mind.Visit(Owner);
_controller = entity;
status.ShowAlert(_pilotingAlertType, onClickAlert: OnClickAlert);
}
private void OnClickAlert(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out ShuttleControllerComponent? controller))
{
controller.RemoveController();
}
status.ShowAlert(_pilotingAlertType);
}
/// <summary>

View File

@@ -1,12 +1,14 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Pulling;
using Content.Server.GameObjects.Components.Timing;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Input;
@@ -113,11 +115,9 @@ namespace Content.Server.GameObjects.EntitySystems.Click
}
/// <summary>
/// Activates the Activate behavior of an object
/// Activates the IActivate behavior of an object
/// Verifies that the user is capable of doing the use interaction first
/// </summary>
/// <param name="user"></param>
/// <param name="used"></param>
public void TryInteractionActivate(IEntity user, IEntity used)
{
if (user != null && used != null && ActionBlockerSystem.CanUse(user))
@@ -504,7 +504,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
}
/// <summary>
/// Activates the Use behavior of an object
/// Activates the IUse behaviors of an entity
/// Verifies that the user is capable of doing the use interaction first
/// </summary>
/// <param name="user"></param>
@@ -518,8 +518,8 @@ namespace Content.Server.GameObjects.EntitySystems.Click
}
/// <summary>
/// Activates/Uses an object in control/possession of a user
/// If the item has the IUse interface on one of its components we use the object in our hand
/// Activates the IUse behaviors of an entity without first checking
/// if the user is capable of doing the use interaction.
/// </summary>
public void UseInteraction(IEntity user, IEntity used)
{
@@ -679,6 +679,48 @@ namespace Content.Server.GameObjects.EntitySystems.Click
}
}
/// <summary>
/// Calls EquippedHand on all components that implement the IEquippedHand interface
/// on an item.
/// </summary>
public void EquippedHandInteraction(IEntity user, IEntity item, SharedHand hand)
{
var equippedHandMessage = new EquippedHandMessage(user, item, hand);
RaiseLocalEvent(equippedHandMessage);
if (equippedHandMessage.Handled)
{
return;
}
var comps = item.GetAllComponents<IEquippedHand>().ToList();
foreach (var comp in comps)
{
comp.EquippedHand(new EquippedHandEventArgs(user, hand));
}
}
/// <summary>
/// Calls UnequippedHand on all components that implement the IUnequippedHand interface
/// on an item.
/// </summary>
public void UnequippedHandInteraction(IEntity user, IEntity item, SharedHand hand)
{
var unequippedHandMessage = new UnequippedHandMessage(user, item, hand);
RaiseLocalEvent(unequippedHandMessage);
if (unequippedHandMessage.Handled)
{
return;
}
var comps = item.GetAllComponents<IUnequippedHand>().ToList();
foreach (var comp in comps)
{
comp.UnequippedHand(new UnequippedHandEventArgs(user, hand));
}
}
/// <summary>
/// Activates the Dropped behavior of an object
/// Verifies that the user is capable of doing the drop interaction first
@@ -757,7 +799,6 @@ namespace Content.Server.GameObjects.EntitySystems.Click
}
}
/// <summary>
/// Will have two behaviors, either "uses" the weapon at range on the entity if it is capable of accepting that action
/// Or it will use the weapon itself on the position clicked, regardless of what was there

View File

@@ -20,6 +20,7 @@ using Content.Server.PDA;
using Content.Server.Preferences;
using Content.Server.Sandbox;
using Content.Server.Utility;
using Content.Shared.Actions;
using Content.Shared.Interfaces;
using Content.Shared.Kitchen;
using Content.Shared.Alert;
@@ -43,6 +44,7 @@ namespace Content.Server
IoCManager.Register<IServerDbManager, ServerDbManager>();
IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<AlertManager, AlertManager>();
IoCManager.Register<ActionManager, ActionManager>();
IoCManager.Register<IPDAUplinkManager,PDAUplinkManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<INodeGroupManager, NodeGroupManager>();