Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Actions.Prototypes;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;

View File

@@ -1,9 +1,9 @@
#nullable enable
using System;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Actions.Components;
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Currently just a marker interface delineating the different possible

View File

@@ -1,7 +1,7 @@
#nullable enable
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Action which does something immediately when used and has

View File

@@ -1,7 +1,7 @@
#nullable enable
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Action which is used on a targeted entity.

View File

@@ -1,7 +1,8 @@
#nullable enable
using Content.Shared.Actions.Behaviors.Item;
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Item action which is used on a targeted entity.

View File

@@ -2,7 +2,7 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Action which requires the user to select a target point, which

View File

@@ -1,8 +1,9 @@
#nullable enable
using Content.Shared.Actions.Behaviors.Item;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Item action which requires the user to select a target point, which

View File

@@ -1,7 +1,7 @@
#nullable enable
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors
{
/// <summary>
/// Action which can be toggled on and off

View File

@@ -1,7 +1,7 @@
#nullable enable
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors.Item
{
/// <summary>
/// Item action which does something immediately when used and has

View File

@@ -1,9 +1,9 @@
#nullable enable
using System;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Actions.Components;
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors.Item
{
/// <summary>
/// Currently just a marker interface delineating the different possible

View File

@@ -1,7 +1,7 @@
#nullable enable
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Behaviors.Item
{
/// <summary>
/// Item action which can be toggled on and off

View File

@@ -0,0 +1,250 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Actions.Components
{
/// <summary>
/// This should be used on items which provide actions. Defines which actions the item provides
/// and allows modifying the states of those actions. Item components should use this rather than
/// SharedActionsComponent on the player to handle granting / revoking / modifying the states of the
/// actions provided by this item.
///
/// When a player equips this item, all the actions defined in this component will be granted to the
/// player in their current states. This means the states will persist between players.
///
/// Currently only maintained server side and not synced to client, as are all the equip/unequip events.
/// </summary>
[RegisterComponent]
public class ItemActionsComponent : Component, IEquippedHand, IEquipped, IUnequipped, IUnequippedHand
{
public override string Name => "ItemActions";
/// <summary>
/// Configuration for the item actions initially provided by this item. Actions defined here
/// will be automatically granted unless their state is modified using the methods
/// on this component. Additional actions can be granted by this item via GrantOrUpdate
/// </summary>
public IEnumerable<ItemActionConfig> ActionConfigs => _actionConfigs;
public bool IsEquipped => InSlot != EquipmentSlotDefines.Slots.NONE || InHand != null;
/// <summary>
/// Slot currently equipped to, NONE if not equipped to an equip slot.
/// </summary>
public EquipmentSlotDefines.Slots InSlot { get; private set; }
/// <summary>
/// hand it's currently in, null if not in a hand.
/// </summary>
public SharedHand? InHand { get; private set; }
/// <summary>
/// Entity currently holding this in hand or equip slot. Null if not held.
/// </summary>
public IEntity? Holder { get; private set; }
// cached actions component of the holder, since we'll need to access it frequently
private SharedActionsComponent? _holderActionsComponent;
[DataField("actions")]
private List<ItemActionConfig> _actionConfigs
{
get => internalActionConfigs;
set
{
internalActionConfigs = value;
foreach (var actionConfig in value)
{
GrantOrUpdate(actionConfig.ActionType, actionConfig.Enabled, false, null);
}
}
}
// State of all actions provided by this item.
private readonly Dictionary<ItemActionType, ActionState> _actions = new();
private List<ItemActionConfig> internalActionConfigs = new ();
protected override void Startup()
{
base.Startup();
GrantOrUpdateAllToHolder();
}
protected override void Shutdown()
{
base.Shutdown();
RevokeAllFromHolder();
}
private void GrantOrUpdateAllToHolder()
{
if (_holderActionsComponent == null) return;
foreach (var (actionType, state) in _actions)
{
_holderActionsComponent.GrantOrUpdateItemAction(actionType, Owner.Uid, state);
}
}
private void RevokeAllFromHolder()
{
if (_holderActionsComponent == null) return;
foreach (var (actionType, state) in _actions)
{
_holderActionsComponent.RevokeItemAction(actionType, Owner.Uid);
}
}
/// <summary>
/// Update the state of the action, granting it if it isn't already granted.
/// If the action had any existing state, those specific fields will be overwritten by any
/// corresponding non-null arguments.
/// </summary>
/// <param name="actionType">action being granted / updated</param>
/// <param name="enabled">When null, preserves the current enable status of the action, defaulting
/// to true if action has no current state.
/// When non-null, indicates whether the entity is able to perform the action (if disabled,
/// the player will see they have the action but it will appear greyed out)</param>
/// <param name="toggleOn">When null, preserves the current toggle status of the action, defaulting
/// to false if action has no current state.
/// When non-null, action will be shown toggled to this value</param>
/// <param name="cooldown"> When null (unless clearCooldown is true), preserves the current cooldown status of the action, defaulting
/// to no cooldown if action has no current state.
/// When non-null or clearCooldown is true, action cooldown will be set to this value. Note that this cooldown
/// is tied to this item.</param>
/// <param name="clearCooldown"> If true, setting cooldown to null will clear the current cooldown
/// of this action rather than preserving it.</param>
public void GrantOrUpdate(ItemActionType actionType, bool? enabled = null,
bool? toggleOn = null,
(TimeSpan start, TimeSpan end)? cooldown = null, bool clearCooldown = false)
{
var dirty = false;
// this will be overwritten if we find the value in our dict, otherwise
// we will use this as our new action state.
if (!_actions.TryGetValue(actionType, out var actionState))
{
dirty = true;
actionState = new ActionState(enabled ?? true, toggleOn ?? false);
}
if (enabled.HasValue && enabled != actionState.Enabled)
{
dirty = true;
actionState.Enabled = true;
}
if ((cooldown.HasValue || clearCooldown) && actionState.Cooldown != cooldown)
{
dirty = true;
actionState.Cooldown = cooldown;
}
if (toggleOn.HasValue && actionState.ToggledOn != toggleOn.Value)
{
dirty = true;
actionState.ToggledOn = toggleOn.Value;
}
if (!dirty) return;
_actions[actionType] = actionState;
_holderActionsComponent?.GrantOrUpdateItemAction(actionType, Owner.Uid, actionState);
}
/// <summary>
/// Update the cooldown of a particular action. Actions on cooldown cannot be used.
/// Setting the cooldown to null clears it.
/// </summary>
public void Cooldown(ItemActionType actionType, (TimeSpan start, TimeSpan end)? cooldown = null)
{
GrantOrUpdate(actionType, cooldown: cooldown, clearCooldown: true);
}
/// <summary>
/// Enable / disable this action. Disabled actions are still shown to the player, but
/// shown as not usable.
/// </summary>
public void SetEnabled(ItemActionType actionType, bool enabled)
{
GrantOrUpdate(actionType, enabled);
}
/// <summary>
/// Toggle the action on / off
/// </summary>
public void Toggle(ItemActionType actionType, bool toggleOn)
{
GrantOrUpdate(actionType, toggleOn: toggleOn);
}
void IEquippedHand.EquippedHand(EquippedHandEventArgs eventArgs)
{
// this entity cannot be granted actions if no actions component
if (!eventArgs.User.TryGetComponent<SharedActionsComponent>(out var actionsComponent))
return;
Holder = eventArgs.User;
_holderActionsComponent = actionsComponent;
InSlot = EquipmentSlotDefines.Slots.NONE;
InHand = eventArgs.Hand;
GrantOrUpdateAllToHolder();
}
void IEquipped.Equipped(EquippedEventArgs eventArgs)
{
// this entity cannot be granted actions if no actions component
if (!eventArgs.User.TryGetComponent<SharedActionsComponent>(out var actionsComponent))
return;
Holder = eventArgs.User;
_holderActionsComponent = actionsComponent;
InSlot = eventArgs.Slot;
InHand = null;
GrantOrUpdateAllToHolder();
}
void IUnequipped.Unequipped(UnequippedEventArgs eventArgs)
{
RevokeAllFromHolder();
Holder = null;
_holderActionsComponent = null;
InSlot = EquipmentSlotDefines.Slots.NONE;
InHand = null;
}
void IUnequippedHand.UnequippedHand(UnequippedHandEventArgs eventArgs)
{
RevokeAllFromHolder();
Holder = null;
_holderActionsComponent = null;
InSlot = EquipmentSlotDefines.Slots.NONE;
InHand = null;
}
}
/// <summary>
/// Configuration for an item action provided by an item.
/// </summary>
[DataDefinition]
public class ItemActionConfig : ISerializationHooks
{
[DataField("actionType", required: true)]
public ItemActionType ActionType { get; private set; } = ItemActionType.Error;
/// <summary>
/// Whether action is initially enabled on this item. Defaults to true.
/// </summary>
public bool Enabled { get; private set; } = true;
void ISerializationHooks.AfterDeserialization()
{
if (ActionType == ItemActionType.Error)
{
Logger.ErrorS("action", "invalid or missing actionType");
}
}
}
}

View File

@@ -0,0 +1,654 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Actions.Prototypes;
using Content.Shared.NetIDs;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Actions.Components
{
/// <summary>
/// Manages the actions available to an entity.
/// Should only be used for player-controlled entities.
///
/// Actions are granted directly to the owner entity. Item actions are granted via a particular item which
/// must be in the owner's inventory (the action is revoked when item leaves the owner's inventory). This
/// should almost always be done via ItemActionsComponent on the item entity (which also tracks the
/// cooldowns associated with the actions on that item).
///
/// Actions can still have an associated state even when revoked. For example, a flashlight toggle action
/// may be unusable while the player is stunned, but this component will still have an entry for the action
/// so the user can see whether it's currently toggled on or off.
/// </summary>
public abstract class SharedActionsComponent : Component
{
private static readonly TimeSpan CooldownExpiryThreshold = TimeSpan.FromSeconds(10);
[Dependency]
protected readonly ActionManager ActionManager = default!;
[Dependency]
protected readonly IGameTiming GameTiming = default!;
[Dependency]
protected readonly IEntityManager EntityManager = default!;
public override string Name => "Actions";
public override uint? NetID => ContentNetIDs.ACTIONS;
/// <summary>
/// Actions granted to this entity as soon as they spawn, regardless
/// of the status of the entity.
/// </summary>
public IEnumerable<ActionType> InnateActions => _innateActions ?? Enumerable.Empty<ActionType>();
[DataField("innateActions")]
private List<ActionType>? _innateActions = null;
// entries are removed from this if they are at the initial state (not enabled, no cooldown, toggled off).
// a system runs which periodically removes cooldowns from entries when they are revoked and their
// cooldowns have expired for a long enough time, also removing the entry if it is then at initial state.
// This helps to keep our component state smaller.
[ViewVariables]
private Dictionary<ActionType, ActionState> _actions = new();
// Holds item action states. Item actions are only added to this when granted, and are removed
// when revoked or when they leave inventory. This is almost entirely handled by ItemActionsComponent on
// item entities.
[ViewVariables]
private Dictionary<EntityUid, Dictionary<ItemActionType, ActionState>> _itemActions =
new();
protected override void Startup()
{
foreach (var actionType in InnateActions)
{
Grant(actionType);
}
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new ActionComponentState(_actions, _itemActions);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not ActionComponentState state)
{
return;
}
_actions = state.Actions;
_itemActions = state.ItemActions;
}
/// <summary>
/// Gets the action state associated with the specified action type, if it has been
/// granted, has a cooldown, or has been toggled on
/// </summary>
/// <returns>false if not found for this action type</returns>
public bool TryGetActionState(ActionType actionType, out ActionState actionState)
{
return _actions.TryGetValue(actionType, out actionState);
}
/// <summary>
/// Gets the item action states associated with the specified item if any have been granted
/// and not yet revoked.
/// </summary>
/// <returns>false if no states found for this item action type.</returns>
public bool TryGetItemActionStates(EntityUid item, [NotNullWhen((true))] out IReadOnlyDictionary<ItemActionType, ActionState>? itemActionStates)
{
if (_itemActions.TryGetValue(item, out var actualItemActionStates))
{
itemActionStates = actualItemActionStates;
return true;
}
itemActionStates = null;
return false;
}
/// <seealso cref="TryGetItemActionStates(Robust.Shared.GameObjects.EntityUid,out System.Collections.Generic.IReadOnlyDictionary{Content.Shared.Actions.ItemActionType,Content.Shared.Actions.Components.ActionState}?)"/>
public bool TryGetItemActionStates(IEntity item,
[NotNullWhen((true))] out IReadOnlyDictionary<ItemActionType, ActionState>? itemActionStates)
{
return TryGetItemActionStates(item.Uid, out itemActionStates);
}
/// <summary>
/// Gets the item action state associated with the specified item action type for the specified item, if it has any.
/// </summary>
/// <returns>false if no state found for this item action type for this item</returns>
public bool TryGetItemActionState(ItemActionType actionType, EntityUid item, out ActionState actionState)
{
if (_itemActions.TryGetValue(item, out var actualItemActionStates))
{
return actualItemActionStates.TryGetValue(actionType, out actionState);
}
actionState = default;
return false;
}
/// <returns>true if the action is granted and enabled (if item action, if granted and enabled for any item)</returns>
public bool IsGranted(BaseActionPrototype actionType)
{
return actionType switch
{
ActionPrototype actionPrototype => IsGranted(actionPrototype.ActionType),
ItemActionPrototype itemActionPrototype => IsGranted(itemActionPrototype.ActionType),
_ => false
};
}
public bool IsGranted(ActionType actionType)
{
if (TryGetActionState(actionType, out var actionState))
{
return actionState.Enabled;
}
return false;
}
/// <returns>true if the action is granted and enabled for any item. This
/// has to traverse the entire item state dictionary so please avoid frequent calls.</returns>
public bool IsGranted(ItemActionType actionType)
{
return _itemActions.Values.SelectMany(vals => vals)
.Any(state => state.Key == actionType && state.Value.Enabled);
}
/// <seealso cref="TryGetItemActionState(Content.Shared.Actions.ItemActionType,Robust.Shared.GameObjects.EntityUid,out Content.Shared.Actions.Components.ActionState)"/>
public bool TryGetItemActionState(ItemActionType actionType, IEntity item, out ActionState actionState)
{
return TryGetItemActionState(actionType, item.Uid, out actionState);
}
/// <summary>
/// Gets all action types that have non-initial state (granted, have a cooldown, or toggled on).
/// </summary>
public IReadOnlyDictionary<ActionType, ActionState> ActionStates()
{
return _actions;
}
/// <summary>
/// Gets all items that have actions currently granted (that are not revoked
/// and still in inventory).
/// Map from item uid -> (action type -> associated action state)
/// PLEASE DO NOT MODIFY THE INNER DICTIONARY! I CANNOT CAST IT TO IReadOnlyDictionary!
/// </summary>
public IReadOnlyDictionary<EntityUid,Dictionary<ItemActionType, ActionState>> ItemActionStates()
{
return _itemActions;
}
/// <summary>
/// Creates or updates the action state with the supplied non-null values
/// </summary>
private void GrantOrUpdate(ActionType actionType, bool? enabled = null, bool? toggleOn = null,
(TimeSpan start, TimeSpan end)? cooldown = null, bool clearCooldown = false)
{
var dirty = false;
if (!_actions.TryGetValue(actionType, out var actionState))
{
// no state at all for this action, create it anew
dirty = true;
actionState = new ActionState(enabled ?? false, toggleOn ?? false);
}
if (enabled.HasValue && actionState.Enabled != enabled.Value)
{
dirty = true;
actionState.Enabled = enabled.Value;
}
if ((cooldown.HasValue || clearCooldown) && actionState.Cooldown != cooldown)
{
dirty = true;
actionState.Cooldown = cooldown;
}
if (toggleOn.HasValue && actionState.ToggledOn != toggleOn.Value)
{
dirty = true;
actionState.ToggledOn = toggleOn.Value;
}
if (!dirty) return;
_actions[actionType] = actionState;
AfterActionChanged();
Dirty();
}
/// <summary>
/// Intended to only be used by ItemActionsComponent.
/// Updates the state of the item action provided by the item, granting the action
/// if it is not yet granted to the player. Should be called whenever the
/// status changes. The existing state will be completely overwritten by the new state.
/// </summary>
public void GrantOrUpdateItemAction(ItemActionType actionType, EntityUid item, ActionState state)
{
if (!_itemActions.TryGetValue(item, out var itemStates))
{
itemStates = new Dictionary<ItemActionType, ActionState>();
_itemActions[item] = itemStates;
}
itemStates[actionType] = state;
AfterActionChanged();
Dirty();
}
/// <summary>
/// Intended to only be used by ItemActionsComponent. Revokes the item action so the player no longer
/// sees it and can no longer use it.
/// </summary>
public void RevokeItemAction(ItemActionType actionType, EntityUid item)
{
if (!_itemActions.TryGetValue(item, out var itemStates))
return;
itemStates.Remove(actionType);
if (itemStates.Count == 0)
{
_itemActions.Remove(item);
}
AfterActionChanged();
Dirty();
}
/// <summary>
/// Grants the entity the ability to perform the action, optionally overriding its
/// current state with specified values.
///
/// Even if the action was already granted, if the action had any state (cooldown, toggle) prior to this method
/// being called, it will be preserved, with specific fields optionally overridden by any of the provided
/// non-null arguments.
/// </summary>
/// <param name="toggleOn">When null, preserves the current toggle status of the action, defaulting
/// to false if action has no current state.
/// When non-null, action will be shown toggled to this value</param>
/// <param name="cooldown"> When null, preserves the current cooldown status of the action, defaulting
/// to no cooldown if action has no current state.
/// When non-null, action cooldown will be set to this value.</param>
public void Grant(ActionType actionType, bool? toggleOn = null,
(TimeSpan start, TimeSpan end)? cooldown = null)
{
GrantOrUpdate(actionType, true, toggleOn, cooldown);
}
/// <summary>
/// Grants the entity the ability to perform the action, resetting its state
/// to its initial state and settings its state based on supplied parameters.
///
/// Even if the action was already granted, if the action had any state (cooldown, toggle) prior to this method
/// being called, it will be reset to initial (no cooldown, toggled off).
/// </summary>
/// <param name="toggleOn">action will be shown toggled to this value</param>
/// <param name="cooldown">action cooldown will be set to this value (by default the cooldown is cleared).</param>
public void GrantFromInitialState(ActionType actionType, bool toggleOn = false,
(TimeSpan start, TimeSpan end)? cooldown = null)
{
_actions.Remove(actionType);
Grant(actionType, toggleOn, cooldown);
}
/// <summary>
/// Sets the cooldown for the action. Actions on cooldown cannot be used.
///
/// This will work even if the action is revoked -
/// for example if there's an ability with a cooldown which is temporarily unusable due
/// to the player being stunned, the cooldown will still tick down even while the player
/// is stunned.
///
/// Setting cooldown to null clears it.
/// </summary>
public void Cooldown(ActionType actionType, (TimeSpan start, TimeSpan end)? cooldown)
{
GrantOrUpdate(actionType, cooldown: cooldown, clearCooldown: true);
}
/// <summary>
/// Revokes the ability to perform the action for this entity. Current state
/// of the action (toggle / cooldown) is preserved.
/// </summary>
public void Revoke(ActionType actionType)
{
if (!_actions.TryGetValue(actionType, out var actionState)) return;
if (!actionState.Enabled) return;
actionState.Enabled = false;
// don't store it anymore if its at its initial state.
if (actionState.IsAtInitialState)
{
_actions.Remove(actionType);
}
else
{
_actions[actionType] = actionState;
}
AfterActionChanged();
Dirty();
}
/// <summary>
/// Toggles the action to the specified value. Works even if the action is on cooldown
/// or revoked.
/// </summary>
public void ToggleAction(ActionType actionType, bool toggleOn)
{
Grant(actionType, toggleOn);
}
/// <summary>
/// Clears any cooldowns which have expired beyond the predefined threshold.
/// this should be run periodically to ensure we don't have unbounded growth of
/// our saved action data, and keep our component state sent to the client as minimal as possible.
/// </summary>
public void ExpireCooldowns()
{
// actions - only clear cooldowns and remove associated action state
// if the action is at initial state
var actionTypesToRemove = new List<ActionType>();
foreach (var (actionType, actionState) in _actions)
{
// ignore it unless we may be able to delete it due to
// clearing the cooldown
if (!actionState.IsAtInitialStateExceptCooldown) continue;
if (!actionState.Cooldown.HasValue)
{
actionTypesToRemove.Add(actionType);
continue;
}
var expiryTime = GameTiming.CurTime - actionState.Cooldown.Value.Item2;
if (expiryTime > CooldownExpiryThreshold)
{
actionTypesToRemove.Add(actionType);
}
}
foreach (var remove in actionTypesToRemove)
{
_actions.Remove(remove);
}
}
/// <summary>
/// Invoked after a change has been made to an action state in this component.
/// </summary>
protected virtual void AfterActionChanged() { }
}
[Serializable, NetSerializable]
public class ActionComponentState : ComponentState
{
public Dictionary<ActionType, ActionState> Actions;
public Dictionary<EntityUid, Dictionary<ItemActionType, ActionState>> ItemActions;
public ActionComponentState(Dictionary<ActionType, ActionState> actions,
Dictionary<EntityUid, Dictionary<ItemActionType, ActionState>> itemActions) : base(ContentNetIDs.ACTIONS)
{
Actions = actions;
ItemActions = itemActions;
}
}
[Serializable, NetSerializable]
public struct ActionState
{
/// <summary>
/// False if this action is not currently allowed to be performed.
/// </summary>
public bool Enabled;
/// <summary>
/// Only used for toggle actions, indicates whether it's currently toggled on or off
/// TODO: Eventually this should probably be a byte so we it can toggle through multiple states.
/// </summary>
public bool ToggledOn;
public (TimeSpan start, TimeSpan end)? Cooldown;
public bool IsAtInitialState => IsAtInitialStateExceptCooldown && !Cooldown.HasValue;
public bool IsAtInitialStateExceptCooldown => !Enabled && !ToggledOn;
/// <summary>
/// Creates an action state for the indicated type, defaulting to the
/// initial state.
/// </summary>
public ActionState(bool enabled = false, bool toggledOn = false, (TimeSpan start, TimeSpan end)? cooldown = null)
{
Enabled = enabled;
ToggledOn = toggledOn;
Cooldown = cooldown;
}
public bool IsOnCooldown(TimeSpan curTime)
{
if (Cooldown == null) return false;
return curTime < Cooldown.Value.Item2;
}
public bool IsOnCooldown(IGameTiming gameTiming)
{
return IsOnCooldown(gameTiming.CurTime);
}
public bool Equals(ActionState other)
{
return Enabled == other.Enabled && ToggledOn == other.ToggledOn && Nullable.Equals(Cooldown, other.Cooldown);
}
public override bool Equals(object? obj)
{
return obj is ActionState other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(Enabled, ToggledOn, Cooldown);
}
}
[Serializable, NetSerializable]
public abstract class BasePerformActionMessage : ComponentMessage
{
public abstract BehaviorType BehaviorType { get; }
}
[Serializable, NetSerializable]
public abstract class PerformActionMessage : BasePerformActionMessage
{
public readonly ActionType ActionType;
protected PerformActionMessage(ActionType actionType)
{
Directed = true;
ActionType = actionType;
}
}
[Serializable, NetSerializable]
public abstract class PerformItemActionMessage : BasePerformActionMessage
{
public readonly ItemActionType ActionType;
public readonly EntityUid Item;
protected PerformItemActionMessage(ItemActionType actionType, EntityUid item)
{
Directed = true;
ActionType = actionType;
Item = item;
}
}
/// <summary>
/// A message that tells server we want to run the instant action logic.
/// </summary>
[Serializable, NetSerializable]
public class PerformInstantActionMessage : PerformActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.Instant;
public PerformInstantActionMessage(ActionType actionType) : base(actionType)
{
}
}
/// <summary>
/// A message that tells server we want to run the instant action logic.
/// </summary>
[Serializable, NetSerializable]
public class PerformInstantItemActionMessage : PerformItemActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.Instant;
public PerformInstantItemActionMessage(ItemActionType actionType, EntityUid item) : base(actionType, item)
{
}
}
public interface IToggleActionMessage
{
bool ToggleOn { get; }
}
public interface ITargetPointActionMessage
{
/// <summary>
/// Targeted local coordinates
/// </summary>
EntityCoordinates Target { get; }
}
public interface ITargetEntityActionMessage
{
/// <summary>
/// Targeted entity
/// </summary>
EntityUid Target { get; }
}
/// <summary>
/// A message that tells server we want to toggle on the indicated action.
/// </summary>
[Serializable, NetSerializable]
public class PerformToggleOnActionMessage : PerformActionMessage, IToggleActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.Toggle;
public bool ToggleOn => true;
public PerformToggleOnActionMessage(ActionType actionType) : base(actionType) { }
}
/// <summary>
/// A message that tells server we want to toggle off the indicated action.
/// </summary>
[Serializable, NetSerializable]
public class PerformToggleOffActionMessage : PerformActionMessage, IToggleActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.Toggle;
public bool ToggleOn => false;
public PerformToggleOffActionMessage(ActionType actionType) : base(actionType) { }
}
/// <summary>
/// A message that tells server we want to toggle on the indicated action.
/// </summary>
[Serializable, NetSerializable]
public class PerformToggleOnItemActionMessage : PerformItemActionMessage, IToggleActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.Toggle;
public bool ToggleOn => true;
public PerformToggleOnItemActionMessage(ItemActionType actionType, EntityUid item) : base(actionType, item) { }
}
/// <summary>
/// A message that tells server we want to toggle off the indicated action.
/// </summary>
[Serializable, NetSerializable]
public class PerformToggleOffItemActionMessage : PerformItemActionMessage, IToggleActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.Toggle;
public bool ToggleOn => false;
public PerformToggleOffItemActionMessage(ItemActionType actionType, EntityUid item) : base(actionType, item) { }
}
/// <summary>
/// A message that tells server we want to target the provided point with a particular action.
/// </summary>
[Serializable, NetSerializable]
public class PerformTargetPointActionMessage : PerformActionMessage, ITargetPointActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.TargetPoint;
private readonly EntityCoordinates _target;
public EntityCoordinates Target => _target;
public PerformTargetPointActionMessage(ActionType actionType, EntityCoordinates target) : base(actionType)
{
_target = target;
}
}
/// <summary>
/// A message that tells server we want to target the provided point with a particular action.
/// </summary>
[Serializable, NetSerializable]
public class PerformTargetPointItemActionMessage : PerformItemActionMessage, ITargetPointActionMessage
{
private readonly EntityCoordinates _target;
public EntityCoordinates Target => _target;
public override BehaviorType BehaviorType => BehaviorType.TargetPoint;
public PerformTargetPointItemActionMessage(ItemActionType actionType, EntityUid item, EntityCoordinates target) : base(actionType, item)
{
_target = target;
}
}
/// <summary>
/// A message that tells server we want to target the provided entity with a particular action.
/// </summary>
[Serializable, NetSerializable]
public class PerformTargetEntityActionMessage : PerformActionMessage, ITargetEntityActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.TargetEntity;
private readonly EntityUid _target;
public EntityUid Target => _target;
public PerformTargetEntityActionMessage(ActionType actionType, EntityUid target) : base(actionType)
{
_target = target;
}
}
/// <summary>
/// A message that tells server we want to target the provided entity with a particular action.
/// </summary>
[Serializable, NetSerializable]
public class PerformTargetEntityItemActionMessage : PerformItemActionMessage, ITargetEntityActionMessage
{
public override BehaviorType BehaviorType => BehaviorType.TargetEntity;
private readonly EntityUid _target;
public EntityUid Target => _target;
public PerformTargetEntityItemActionMessage(ItemActionType actionType, EntityUid item, EntityUid target) : base(actionType, item)
{
_target = target;
}
}
}

View File

@@ -0,0 +1,209 @@
#nullable enable
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Prototypes;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
namespace Content.Shared.Actions
{
/// <summary>
/// An attempt to perform a specific action. Main purpose of this interface is to
/// reduce code duplication related to handling attempts to perform non-item vs item actions by
/// providing a single interface for various functionality that needs to be performed on both.
/// </summary>
public interface IActionAttempt
{
/// <summary>
/// Action Prototype attempting to be performed
/// </summary>
BaseActionPrototype Action { get; }
ComponentMessage PerformInstantActionMessage();
ComponentMessage PerformToggleActionMessage(bool on);
ComponentMessage PerformTargetPointActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args);
ComponentMessage PerformTargetEntityActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args);
/// <summary>
/// Tries to get the action state for this action from the actionsComponent, returning
/// true if found.
/// </summary>
bool TryGetActionState(SharedActionsComponent actionsComponent, out ActionState actionState);
/// <summary>
/// Toggles the action within the provided action component
/// </summary>
void ToggleAction(SharedActionsComponent actionsComponent, bool toggleOn);
/// <summary>
/// Perform the server-side logic of the action
/// </summary>
void DoInstantAction(IEntity player);
/// <summary>
/// Perform the server-side logic of the toggle action
/// </summary>
/// <returns>true if the attempt to toggle was successful, meaning the state should be toggled to the
/// indicated value</returns>
bool DoToggleAction(IEntity player, bool on);
/// <summary>
/// Perform the server-side logic of the target point action
/// </summary>
void DoTargetPointAction(IEntity player, EntityCoordinates target);
/// <summary>
/// Perform the server-side logic of the target entity action
/// </summary>
void DoTargetEntityAction(IEntity player, IEntity target);
}
public class ActionAttempt : IActionAttempt
{
private readonly ActionPrototype _action;
public BaseActionPrototype Action => _action;
public ActionAttempt(ActionPrototype action)
{
_action = action;
}
public bool TryGetActionState(SharedActionsComponent actionsComponent, out ActionState actionState)
{
return actionsComponent.TryGetActionState(_action.ActionType, out actionState);
}
public void ToggleAction(SharedActionsComponent actionsComponent, bool toggleOn)
{
actionsComponent.ToggleAction(_action.ActionType, toggleOn);
}
public void DoInstantAction(IEntity player)
{
_action.InstantAction.DoInstantAction(new InstantActionEventArgs(player, _action.ActionType));
}
public bool DoToggleAction(IEntity player, bool on)
{
return _action.ToggleAction.DoToggleAction(new ToggleActionEventArgs(player, _action.ActionType, on));
}
public void DoTargetPointAction(IEntity player, EntityCoordinates target)
{
_action.TargetPointAction.DoTargetPointAction(new TargetPointActionEventArgs(player, target, _action.ActionType));
}
public void DoTargetEntityAction(IEntity player, IEntity target)
{
_action.TargetEntityAction.DoTargetEntityAction(new TargetEntityActionEventArgs(player, _action.ActionType,
target));
}
public ComponentMessage PerformInstantActionMessage()
{
return new PerformInstantActionMessage(_action.ActionType);
}
public ComponentMessage PerformToggleActionMessage(bool toggleOn)
{
if (toggleOn)
{
return new PerformToggleOnActionMessage(_action.ActionType);
}
return new PerformToggleOffActionMessage(_action.ActionType);
}
public ComponentMessage PerformTargetPointActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args)
{
return new PerformTargetPointActionMessage(_action.ActionType, args.Coordinates);
}
public ComponentMessage PerformTargetEntityActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args)
{
return new PerformTargetEntityActionMessage(_action.ActionType, args.EntityUid);
}
public override string ToString()
{
return $"{nameof(_action)}: {_action.ActionType}";
}
}
public class ItemActionAttempt : IActionAttempt
{
private readonly ItemActionPrototype _action;
private readonly IEntity _item;
private readonly ItemActionsComponent _itemActions;
public BaseActionPrototype Action => _action;
public ItemActionAttempt(ItemActionPrototype action, IEntity item, ItemActionsComponent itemActions)
{
_action = action;
_item = item;
_itemActions = itemActions;
}
public void DoInstantAction(IEntity player)
{
_action.InstantAction.DoInstantAction(new InstantItemActionEventArgs(player, _item, _action.ActionType));
}
public bool DoToggleAction(IEntity player, bool on)
{
return _action.ToggleAction.DoToggleAction(new ToggleItemActionEventArgs(player, on, _item, _action.ActionType));
}
public void DoTargetPointAction(IEntity player, EntityCoordinates target)
{
_action.TargetPointAction.DoTargetPointAction(new TargetPointItemActionEventArgs(player, target, _item,
_action.ActionType));
}
public void DoTargetEntityAction(IEntity player, IEntity target)
{
_action.TargetEntityAction.DoTargetEntityAction(new TargetEntityItemActionEventArgs(player, target,
_item, _action.ActionType));
}
public bool TryGetActionState(SharedActionsComponent actionsComponent, out ActionState actionState)
{
return actionsComponent.TryGetItemActionState(_action.ActionType, _item, out actionState);
}
public void ToggleAction(SharedActionsComponent actionsComponent, bool toggleOn)
{
_itemActions.Toggle(_action.ActionType, toggleOn);
}
public ComponentMessage PerformInstantActionMessage()
{
return new PerformInstantItemActionMessage(_action.ActionType, _item.Uid);
}
public ComponentMessage PerformToggleActionMessage(bool toggleOn)
{
if (toggleOn)
{
return new PerformToggleOnItemActionMessage(_action.ActionType, _item.Uid);
}
return new PerformToggleOffItemActionMessage(_action.ActionType, _item.Uid);
}
public ComponentMessage PerformTargetPointActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args)
{
return new PerformTargetPointItemActionMessage(_action.ActionType, _item.Uid, args.Coordinates);
}
public ComponentMessage PerformTargetEntityActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args)
{
return new PerformTargetEntityItemActionMessage(_action.ActionType, _item.Uid, args.EntityUid);
}
public override string ToString()
{
return $"{nameof(_action)}: {_action.ActionType}, {nameof(_item)}: {_item}";
}
}
}

View File

@@ -1,12 +1,13 @@
#nullable enable
using Content.Shared.Interfaces;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Module;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Prototypes
{
/// <summary>
/// An action which is granted directly to an entity (such as an innate ability

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Prototypes
{
/// <summary>
/// Base class for action prototypes.

View File

@@ -1,12 +1,14 @@
#nullable enable
using Content.Shared.Interfaces;
using Content.Shared.Actions.Behaviors;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Module;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Actions
namespace Content.Shared.Actions.Prototypes
{
/// <summary>
/// An action which is granted to an entity via an item (such as toggling a flashlight).

View File

@@ -0,0 +1,30 @@
#nullable enable
using Content.Shared.Actions.Components;
using Robust.Shared.GameObjects;
namespace Content.Shared.Actions
{
/// <summary>
/// Evicts action states with expired cooldowns.
/// </summary>
public class SharedActionSystem : EntitySystem
{
private const float CooldownCheckIntervalSeconds = 10;
private float _timeSinceCooldownCheck;
public override void Update(float frameTime)
{
base.Update(frameTime);
_timeSinceCooldownCheck += frameTime;
if (_timeSinceCooldownCheck < CooldownCheckIntervalSeconds) return;
foreach (var comp in ComponentManager.EntityQuery<SharedActionsComponent>(false))
{
comp.ExpireCooldowns();
}
_timeSinceCooldownCheck -= CooldownCheckIntervalSeconds;
}
}
}