From 7a3c281f602690d51cd8cfd762e9d114f7ba5cfe Mon Sep 17 00:00:00 2001 From: chairbender Date: Sun, 13 Dec 2020 14:28:20 -0800 Subject: [PATCH] Actions System + UI (#2710) Co-authored-by: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- Content.Client/ClientContentIoC.cs | 2 + Content.Client/EntryPoint.cs | 2 + .../HUD/Inventory/ClientInventoryComponent.cs | 8 + .../HumanInventoryInterfaceController.cs | 11 + .../Inventory/InventoryInterfaceController.cs | 5 + .../Components/Items/HandsComponent.cs | 13 + .../Mobs/Actions/ActionAssignment.cs | 90 +++ .../Mobs/Actions/ActionAssignments.cs | 304 ++++++++ .../Components/Mobs/ClientActionsComponent.cs | 273 ++++++++ .../Components/Mobs/ClientAlertsComponent.cs | 188 ++--- .../EntitySystems/ActionsSystem.cs | 91 +++ .../GameObjects/EntitySystems/AlertsSystem.cs | 27 - .../EntitySystems/DragDropSystem.cs | 331 ++++----- Content.Client/IgnoredComponents.cs | 2 + Content.Client/Input/ContentContexts.cs | 11 + .../UserInterface/ActionAlertTooltip.cs | 100 +++ Content.Client/UserInterface/ActionMenu.cs | 499 ++++++++++++++ .../UserInterface/ActionMenuItem.cs | 68 ++ Content.Client/UserInterface/ActionsUI.cs | 556 +++++++++++++++ Content.Client/UserInterface/AlertsUI.cs | 51 +- .../UserInterface/Controls/ActionSlot.cs | 652 ++++++++++++++++++ .../Controls}/AlertControl.cs | 71 +- .../UserInterface/ItemSlotButton.cs | 21 + .../UserInterface/OptionsMenu.KeyRebind.cs | 14 +- .../UserInterface/Stylesheets/StyleNano.cs | 149 ++++ .../UserInterface/TutorialWindow.cs | 27 +- Content.Client/Utility/DragDropHelper.cs | 172 +++++ .../Components/Mobs/ActionsComponentTests.cs | 186 +++++ .../Components/Mobs/AlertsComponentTests.cs | 4 +- .../Tests/Gravity/WeightlessStatusTests.cs | 2 +- Content.Server/Actions/DebugInstant.cs | 39 ++ Content.Server/Actions/DebugTargetEntity.cs | 28 + Content.Server/Actions/DebugTargetPoint.cs | 29 + Content.Server/Actions/DebugToggle.cs | 48 ++ Content.Server/Actions/ScreamAction.cs | 82 +++ Content.Server/Alert/Click/ResistFire.cs | 24 + Content.Server/Alert/Click/StopPiloting.cs | 24 + Content.Server/Alert/Click/StopPulling.cs | 27 + Content.Server/Alert/Click/Unbuckle.cs | 24 + .../Commands/Actions/CooldownAction.cs | 65 ++ .../Commands/Actions/GrantAction.cs | 52 ++ .../Commands/Actions/RevokeAction.cs | 53 ++ Content.Server/Commands/Alerts/ClearAlert.cs | 8 +- Content.Server/Commands/Alerts/ShowAlert.cs | 3 - Content.Server/Commands/CommandUtils.cs | 20 +- Content.Server/EntryPoint.cs | 2 + .../Components/Atmos/FlammableComponent.cs | 10 +- .../Components/Atmos/GasTankComponent.cs | 37 +- .../Components/Buckle/BuckleComponent.cs | 11 +- .../Components/GUI/HandsComponent.cs | 139 ++-- .../Components/GUI/InventoryComponent.cs | 16 + .../Interactable/HandheldLightComponent.cs | 35 +- .../Components/Items/DebugEquipComponent.cs | 35 + .../Components/Mobs/ServerActionsComponent.cs | 199 ++++++ .../Components/Mobs/ServerAlertsComponent.cs | 21 +- .../Movement/ShuttleControllerComponent.cs | 10 +- .../EntitySystems/Click/InteractionSystem.cs | 55 +- Content.Server/ServerContentIoC.cs | 2 + Content.Shared/Actions/ActionManager.cs | 70 ++ Content.Shared/Actions/ActionPrototype.cs | 100 +++ Content.Shared/Actions/ActionType.cs | 33 + Content.Shared/Actions/BaseActionPrototype.cs | 166 +++++ Content.Shared/Actions/IActionBehavior.cs | 44 ++ Content.Shared/Actions/IInstantAction.cs | 27 + Content.Shared/Actions/IInstantItemAction.cs | 28 + Content.Shared/Actions/IItemActionBehavior.cs | 52 ++ Content.Shared/Actions/ITargetEntityAction.cs | 33 + .../Actions/ITargetEntityItemAction.cs | 34 + Content.Shared/Actions/ITargetPointAction.cs | 33 + .../Actions/ITargetPointItemAction.cs | 33 + Content.Shared/Actions/IToggleAction.cs | 41 ++ Content.Shared/Actions/IToggleItemAction.cs | 42 ++ Content.Shared/Actions/ItemActionPrototype.cs | 122 ++++ Content.Shared/Alert/AlertManager.cs | 98 +-- Content.Shared/Alert/AlertPrototype.cs | 45 +- Content.Shared/Alert/AlertType.cs | 4 +- Content.Shared/Alert/IAlertClick.cs | 37 + .../Inventory/EquipmentSlotDefinitions.cs | 4 +- .../Inventory/SharedInventoryComponent.cs | 5 + .../Components/Items/SharedHandsComponent.cs | 4 + .../Components/Mobs/IActionAttempt.cs | 208 ++++++ .../Components/Mobs/ItemActionsComponent.cs | 249 +++++++ .../Components/Mobs/SharedActionsComponent.cs | 652 ++++++++++++++++++ .../Components/Mobs/SharedAlertsComponent.cs | 167 ++--- .../Pulling/SharedPullerComponent.cs | 10 +- Content.Shared/GameObjects/ContentNetIDs.cs | 3 +- .../EntitySystems/SharedActionSystem.cs | 30 + Content.Shared/Input/ContentKeyFunctions.cs | 11 + .../Components/Interaction/IActivate.cs | 7 +- .../Components/Interaction/IAfterInteract.cs | 5 +- .../Components/Interaction/IEquipped.cs | 21 +- .../Components/Interaction/IEquippedHand.cs | 64 ++ .../Components/Interaction/IInteractUsing.cs | 5 +- .../Components/Interaction/IUnequipped.cs | 21 +- .../Components/Interaction/IUnequippedHand.cs | 63 ++ .../Components/Interaction/IUse.cs | 3 +- Content.Shared/Utility/Cooldowns.cs | 28 + .../Mobs/ServerAlertsComponentTests.cs | 18 +- .../Shared/Alert/AlertManagerTests.cs | 19 +- .../Shared/Alert/AlertPrototypeTests.cs | 5 +- .../Audio/Voice/Human/femalescream_1.ogg | Bin 0 -> 24030 bytes .../Audio/Voice/Human/femalescream_2.ogg | Bin 0 -> 23775 bytes .../Audio/Voice/Human/femalescream_3.ogg | Bin 0 -> 26449 bytes .../Audio/Voice/Human/femalescream_4.ogg | Bin 0 -> 15307 bytes .../Audio/Voice/Human/femalescream_5.ogg | Bin 0 -> 20124 bytes Resources/Audio/Voice/Human/license.txt | 16 + Resources/Audio/Voice/Human/malescream_1.ogg | Bin 0 -> 16926 bytes Resources/Audio/Voice/Human/malescream_2.ogg | Bin 0 -> 29480 bytes Resources/Audio/Voice/Human/malescream_3.ogg | Bin 0 -> 14994 bytes Resources/Audio/Voice/Human/malescream_4.ogg | Bin 0 -> 18682 bytes Resources/Audio/Voice/Human/malescream_5.ogg | Bin 0 -> 23909 bytes Resources/Audio/Voice/Human/malescream_6.ogg | Bin 0 -> 19172 bytes Resources/Audio/Voice/Human/manlaugh1.ogg | Bin 0 -> 55970 bytes Resources/Audio/Voice/Human/manlaugh2.ogg | Bin 0 -> 28951 bytes .../Audio/Voice/Human/wilhelm_scream.ogg | Bin 0 -> 13984 bytes Resources/Audio/Voice/Human/womanlaugh.ogg | Bin 0 -> 12461 bytes Resources/Prototypes/Actions/actions.yml | 91 +++ Resources/Prototypes/Actions/item_actions.yml | 121 ++++ Resources/Prototypes/Alerts/alerts.yml | 4 + .../Entities/Clothing/Head/hardhats.yml | 3 + .../Prototypes/Entities/Mobs/Player/human.yml | 5 +- .../Entities/Objects/Misc/fluff_lights.yml | 3 + .../Entities/Objects/Tools/flashlight.yml | 3 + .../Entities/Objects/Tools/gas_tanks.yml | 5 +- .../Entities/Objects/Tools/lantern.yml | 3 + .../Textures/Interface/Actions/internal0.png | Bin 0 -> 850 bytes .../Textures/Interface/Actions/internal1.png | Bin 0 -> 861 bytes .../Textures/Interface/Actions/meta.json | 23 + .../Textures/Interface/Actions/scream.png | Bin 0 -> 265 bytes .../Nano/black_panel_dark_thin_border.png | Bin 0 -> 137 bytes .../Nano/black_panel_light_thin_border.png | Bin 0 -> 138 bytes .../Nano/black_panel_red_thin_border.png | Bin 0 -> 135 bytes Resources/Textures/Interface/Nano/gear.svg | 3 + .../Textures/Interface/Nano/gear.svg.png | Bin 0 -> 612 bytes .../Textures/Interface/Nano/left_arrow.svg | 3 + .../Interface/Nano/left_arrow.svg.png | Bin 0 -> 310 bytes .../Nano/light_panel_background_bordered.png | Bin 0 -> 147 bytes Resources/Textures/Interface/Nano/lock.svg | 3 + .../Textures/Interface/Nano/lock.svg.png | Bin 0 -> 446 bytes .../Textures/Interface/Nano/lock_open.svg | 64 ++ .../Textures/Interface/Nano/lock_open.svg.png | Bin 0 -> 557 bytes .../Textures/Interface/Nano/right_arrow.svg | 3 + .../Interface/Nano/right_arrow.svg.png | Bin 0 -> 309 bytes Resources/Textures/Interface/Nano/square.png | Bin 0 -> 170 bytes Resources/Textures/Interface/Nano/square.svg | 70 ++ ...transparent_window_background_bordered.png | Bin 144 -> 131 bytes .../Nano/window_background_bordered.png | Bin 213 -> 147 bytes .../Tools/flashlight.rsi/lantern_on.png | Bin 0 -> 293 bytes .../Objects/Tools/flashlight.rsi/meta.json | 9 + Resources/keybinds.yml | 33 + 150 files changed, 7283 insertions(+), 854 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignment.cs create mode 100644 Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignments.cs create mode 100644 Content.Client/GameObjects/Components/Mobs/ClientActionsComponent.cs create mode 100644 Content.Client/GameObjects/EntitySystems/ActionsSystem.cs delete mode 100644 Content.Client/GameObjects/EntitySystems/AlertsSystem.cs create mode 100644 Content.Client/UserInterface/ActionAlertTooltip.cs create mode 100644 Content.Client/UserInterface/ActionMenu.cs create mode 100644 Content.Client/UserInterface/ActionMenuItem.cs create mode 100644 Content.Client/UserInterface/ActionsUI.cs create mode 100644 Content.Client/UserInterface/Controls/ActionSlot.cs rename Content.Client/{GameObjects/Components/Mobs => UserInterface/Controls}/AlertControl.cs (53%) create mode 100644 Content.Client/Utility/DragDropHelper.cs create mode 100644 Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs create mode 100644 Content.Server/Actions/DebugInstant.cs create mode 100644 Content.Server/Actions/DebugTargetEntity.cs create mode 100644 Content.Server/Actions/DebugTargetPoint.cs create mode 100644 Content.Server/Actions/DebugToggle.cs create mode 100644 Content.Server/Actions/ScreamAction.cs create mode 100644 Content.Server/Alert/Click/ResistFire.cs create mode 100644 Content.Server/Alert/Click/StopPiloting.cs create mode 100644 Content.Server/Alert/Click/StopPulling.cs create mode 100644 Content.Server/Alert/Click/Unbuckle.cs create mode 100644 Content.Server/Commands/Actions/CooldownAction.cs create mode 100644 Content.Server/Commands/Actions/GrantAction.cs create mode 100644 Content.Server/Commands/Actions/RevokeAction.cs create mode 100644 Content.Server/GameObjects/Components/Items/DebugEquipComponent.cs create mode 100644 Content.Server/GameObjects/Components/Mobs/ServerActionsComponent.cs create mode 100644 Content.Shared/Actions/ActionManager.cs create mode 100644 Content.Shared/Actions/ActionPrototype.cs create mode 100644 Content.Shared/Actions/ActionType.cs create mode 100644 Content.Shared/Actions/BaseActionPrototype.cs create mode 100644 Content.Shared/Actions/IActionBehavior.cs create mode 100644 Content.Shared/Actions/IInstantAction.cs create mode 100644 Content.Shared/Actions/IInstantItemAction.cs create mode 100644 Content.Shared/Actions/IItemActionBehavior.cs create mode 100644 Content.Shared/Actions/ITargetEntityAction.cs create mode 100644 Content.Shared/Actions/ITargetEntityItemAction.cs create mode 100644 Content.Shared/Actions/ITargetPointAction.cs create mode 100644 Content.Shared/Actions/ITargetPointItemAction.cs create mode 100644 Content.Shared/Actions/IToggleAction.cs create mode 100644 Content.Shared/Actions/IToggleItemAction.cs create mode 100644 Content.Shared/Actions/ItemActionPrototype.cs create mode 100644 Content.Shared/Alert/IAlertClick.cs create mode 100644 Content.Shared/GameObjects/Components/Mobs/IActionAttempt.cs create mode 100644 Content.Shared/GameObjects/Components/Mobs/ItemActionsComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Mobs/SharedActionsComponent.cs create mode 100644 Content.Shared/GameObjects/EntitySystems/SharedActionSystem.cs create mode 100644 Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquippedHand.cs create mode 100644 Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequippedHand.cs create mode 100644 Content.Shared/Utility/Cooldowns.cs create mode 100644 Resources/Audio/Voice/Human/femalescream_1.ogg create mode 100644 Resources/Audio/Voice/Human/femalescream_2.ogg create mode 100644 Resources/Audio/Voice/Human/femalescream_3.ogg create mode 100644 Resources/Audio/Voice/Human/femalescream_4.ogg create mode 100644 Resources/Audio/Voice/Human/femalescream_5.ogg create mode 100644 Resources/Audio/Voice/Human/license.txt create mode 100644 Resources/Audio/Voice/Human/malescream_1.ogg create mode 100644 Resources/Audio/Voice/Human/malescream_2.ogg create mode 100644 Resources/Audio/Voice/Human/malescream_3.ogg create mode 100644 Resources/Audio/Voice/Human/malescream_4.ogg create mode 100644 Resources/Audio/Voice/Human/malescream_5.ogg create mode 100644 Resources/Audio/Voice/Human/malescream_6.ogg create mode 100644 Resources/Audio/Voice/Human/manlaugh1.ogg create mode 100644 Resources/Audio/Voice/Human/manlaugh2.ogg create mode 100644 Resources/Audio/Voice/Human/wilhelm_scream.ogg create mode 100644 Resources/Audio/Voice/Human/womanlaugh.ogg create mode 100644 Resources/Prototypes/Actions/actions.yml create mode 100644 Resources/Prototypes/Actions/item_actions.yml create mode 100644 Resources/Textures/Interface/Actions/internal0.png create mode 100644 Resources/Textures/Interface/Actions/internal1.png create mode 100644 Resources/Textures/Interface/Actions/meta.json create mode 100644 Resources/Textures/Interface/Actions/scream.png create mode 100644 Resources/Textures/Interface/Nano/black_panel_dark_thin_border.png create mode 100644 Resources/Textures/Interface/Nano/black_panel_light_thin_border.png create mode 100644 Resources/Textures/Interface/Nano/black_panel_red_thin_border.png create mode 100644 Resources/Textures/Interface/Nano/gear.svg create mode 100644 Resources/Textures/Interface/Nano/gear.svg.png create mode 100644 Resources/Textures/Interface/Nano/left_arrow.svg create mode 100644 Resources/Textures/Interface/Nano/left_arrow.svg.png create mode 100644 Resources/Textures/Interface/Nano/light_panel_background_bordered.png create mode 100644 Resources/Textures/Interface/Nano/lock.svg create mode 100644 Resources/Textures/Interface/Nano/lock.svg.png create mode 100644 Resources/Textures/Interface/Nano/lock_open.svg create mode 100644 Resources/Textures/Interface/Nano/lock_open.svg.png create mode 100644 Resources/Textures/Interface/Nano/right_arrow.svg create mode 100644 Resources/Textures/Interface/Nano/right_arrow.svg.png create mode 100644 Resources/Textures/Interface/Nano/square.png create mode 100644 Resources/Textures/Interface/Nano/square.svg create mode 100644 Resources/Textures/Objects/Tools/flashlight.rsi/lantern_on.png diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index d63ad75a32..0faab676a3 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -12,6 +12,7 @@ using Content.Client.UserInterface; using Content.Client.UserInterface.AdminMenu; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; +using Content.Shared.Actions; using Content.Shared.Interfaces; using Content.Shared.Alert; using Robust.Shared.IoC; @@ -39,6 +40,7 @@ namespace Content.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); } diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index e4e7cdd425..0f9752f4d5 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -13,6 +13,7 @@ using Content.Client.StationEvents; using Content.Client.UserInterface; using Content.Client.UserInterface.AdminMenu; using Content.Client.UserInterface.Stylesheets; +using Content.Shared.Actions; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Cargo; using Content.Shared.GameObjects.Components.Chemistry; @@ -157,6 +158,7 @@ namespace Content.Client IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); _baseClient.RunLevelChanged += (sender, args) => { diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs index 9fabec2678..97ff7454e5 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs @@ -20,10 +20,13 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory /// A character UI which shows items the user has equipped within his inventory /// [RegisterComponent] + [ComponentReference(typeof(SharedInventoryComponent))] public class ClientInventoryComponent : SharedInventoryComponent { private readonly Dictionary _slots = new(); + public IReadOnlyDictionary AllSlots => _slots; + [ViewVariables] public InventoryInterfaceController InterfaceController { get; private set; } = default!; private ISpriteComponent? _sprite; @@ -70,6 +73,11 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory } } + public override bool IsEquipped(IEntity item) + { + return item != null && _slots.Values.Any(e => e == item); + } + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { base.HandleComponentState(curState, nextState); diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs b/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs index 05b2a6a696..135b9ca531 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Content.Client.UserInterface; using Content.Client.Utility; using JetBrains.Annotations; @@ -84,6 +85,16 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory public override SS14Window Window => _window; private HumanInventoryWindow _window; + public override IEnumerable GetItemSlotButtons(Slots slot) + { + if (!_inventoryButtons.TryGetValue(slot, out var buttons)) + { + return Enumerable.Empty(); + } + + return buttons; + } + public override void AddToSlot(Slots slot, IEntity entity) { base.AddToSlot(slot, entity); diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs b/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs index 73c18852f5..789d303c30 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Content.Client.UserInterface; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.Input; @@ -53,6 +54,10 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory { } + /// the button controls associated with the + /// specified slot, if any. Empty if none. + public abstract IEnumerable GetItemSlotButtons(EquipmentSlotDefines.Slots slot); + public virtual void AddToSlot(EquipmentSlotDefines.Slots slot, IEntity entity) { } diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 8bf8bf6c9d..654f7e6c0c 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -15,6 +15,7 @@ namespace Content.Client.GameObjects.Components.Items { [RegisterComponent] [ComponentReference(typeof(ISharedHandsComponent))] + [ComponentReference(typeof(SharedHandsComponent))] public class HandsComponent : SharedHandsComponent { [Dependency] private readonly IGameHud _gameHud = default!; @@ -31,6 +32,18 @@ namespace Content.Client.GameObjects.Components.Items [ViewVariables] public IEntity? ActiveHand => GetEntity(ActiveIndex); + public override bool IsHolding(IEntity entity) + { + foreach (var hand in _hands) + { + if (hand.Entity == entity) + { + return true; + } + } + return false; + } + private void AddHand(Hand hand) { _sprite?.LayerMapReserveBlank($"hand-{hand.Name}"); diff --git a/Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignment.cs b/Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignment.cs new file mode 100644 index 0000000000..2146d5e90b --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignment.cs @@ -0,0 +1,90 @@ +using System; +using Content.Shared.Actions; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components.Mobs.Actions +{ + public struct ActionAssignment : IEquatable + { + private readonly ActionType _actionType; + private readonly ItemActionType _itemActionType; + private readonly EntityUid _item; + public Assignment Assignment { get; private init; } + + private ActionAssignment(Assignment assignment, ActionType actionType, ItemActionType itemActionType, EntityUid item) + { + Assignment = assignment; + _actionType = actionType; + _itemActionType = itemActionType; + _item = item; + } + + /// the action type, if our Assignment is Assignment.Action + /// true only if our Assignment is Assignment.Action + public bool TryGetAction(out ActionType actionType) + { + actionType = _actionType; + return Assignment == Assignment.Action; + } + + /// the item action type, if our Assignment is Assignment.ItemActionWithoutItem + /// true only if our Assignment is Assignment.ItemActionWithoutItem + public bool TryGetItemActionWithoutItem(out ItemActionType itemActionType) + { + itemActionType = _itemActionType; + return Assignment == Assignment.ItemActionWithoutItem; + } + + /// the item action type, if our Assignment is Assignment.ItemActionWithItem + /// the item UID providing the action, if our Assignment is Assignment.ItemActionWithItem + /// true only if our Assignment is Assignment.ItemActionWithItem + public bool TryGetItemActionWithItem(out ItemActionType itemActionType, out EntityUid item) + { + itemActionType = _itemActionType; + item = _item; + return Assignment == Assignment.ItemActionWithItem; + } + + public static ActionAssignment For(ActionType actionType) + { + return new(Assignment.Action, actionType, default, default); + } + + public static ActionAssignment For(ItemActionType actionType) + { + return new(Assignment.ItemActionWithoutItem, default, actionType, default); + } + + public static ActionAssignment For(ItemActionType actionType, EntityUid item) + { + return new(Assignment.ItemActionWithItem, default, actionType, item); + } + + public bool Equals(ActionAssignment other) + { + return _actionType == other._actionType && _itemActionType == other._itemActionType && Equals(_item, other._item); + } + + public override bool Equals(object obj) + { + return obj is ActionAssignment other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_actionType, _itemActionType, _item); + } + + public override string ToString() + { + return $"{nameof(_actionType)}: {_actionType}, {nameof(_itemActionType)}: {_itemActionType}, {nameof(_item)}: {_item}, {nameof(Assignment)}: {Assignment}"; + } + } + + public enum Assignment : byte + { + Action, + ItemActionWithoutItem, + ItemActionWithItem + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignments.cs b/Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignments.cs new file mode 100644 index 0000000000..9afe2e27e6 --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/Actions/ActionAssignments.cs @@ -0,0 +1,304 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Actions; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components.Mobs.Actions +{ + /// + /// Tracks and manages the hotbar assignments for actions. + /// + public class ActionAssignments + { + // the slots and assignments fields hold client's assignments (what action goes in what slot), + // which are completely client side and independent of what actions they've actually been granted and + // what item the action is actually for. + + /// + /// x = hotbar number, y = slot of that hotbar (index 0 corresponds to the one labeled "1", + /// index 9 corresponds to the one labeled "0"). Essentially the inverse of _assignments. + /// + private readonly ActionAssignment?[,] _slots; + + /// + /// Hotbar and slot assignment for each action type (slot index 0 corresponds to the one labeled "1", + /// slot index 9 corresponds to the one labeled "0"). The key corresponds to an index in the _slots array. + /// The value is a list because actions can be assigned to multiple slots. Even if an action type has not been granted, + /// it can still be assigned to a slot. Essentially the inverse of _slots. + /// There will be no entry if there is no assignment (no empty lists in this dict) + /// + private readonly Dictionary> _assignments; + + /// + /// Actions which have been manually cleared by the user, thus should not + /// auto-populate. + /// + private readonly HashSet _preventAutoPopulate = new(); + private readonly Dictionary> _preventAutoPopulateItem = new(); + + private readonly byte _numHotbars; + private readonly byte _numSlots; + + public ActionAssignments(byte numHotbars, byte numSlots) + { + _numHotbars = numHotbars; + _numSlots = numSlots; + _assignments = new Dictionary>(); + _slots = new ActionAssignment?[numHotbars,numSlots]; + } + + /// + /// Updates the assignments based on the current states of all the actions. + /// Newly-granted actions or item actions which don't have an assignment will be assigned a slot + /// automatically (unless they've been manually cleared). Item-based actions + /// which no longer have an associated state will be decoupled from their item. + /// + public void Reconcile(byte currentHotbar, IReadOnlyDictionary actionStates, + IReadOnlyDictionary> itemActionStates) + { + // if we've been granted any actions which have no assignment to any hotbar, we must auto-populate them + // into the hotbar so the user knows about them. + // We fill their current hotbar first, rolling over to the next open slot on the next hotbar. + foreach (var actionState in actionStates) + { + var assignment = ActionAssignment.For(actionState.Key); + if (actionState.Value.Enabled && !_assignments.ContainsKey(assignment)) + { + AutoPopulate(assignment, currentHotbar, false); + } + } + + + + foreach (var (item, itemStates) in itemActionStates) + { + foreach (var itemActionState in itemStates) + { + // unlike regular actions, we DO actually show user their new item action even when it's disabled. + // this allows them to instantly see when an action may be possible that is provided by an item but + // something is preventing it + // Note that we are checking if there is an explicit assignment for this item action + item, + // we will determine during auto-population if we should tie the item to an existing "item action only" + // assignment + var assignment = ActionAssignment.For(itemActionState.Key, item); + if (!_assignments.ContainsKey(assignment)) + { + AutoPopulate(assignment, currentHotbar, false); + } + } + } + + // We need to figure out which current item action assignments we had + // which once had an associated item but have been revoked (based on our newly provided action states) + // so we can dissociate them from the item. If the provided action states do not + // have a state for this action type + item, we can assume that the action has been revoked for that item. + var assignmentsWithoutItem = new List>>(); + foreach (var assignmentEntry in _assignments) + { + if (!assignmentEntry.Key.TryGetItemActionWithItem(out var actionType, out var item)) continue; + + // we have this assignment currently tied to an item, + // check if it no longer has an associated item in our dict of states + if (itemActionStates.TryGetValue(item, out var states)) + { + if (states.ContainsKey(actionType)) + { + // we have a state for this item + action type so we won't + // remove the item from the assignment + continue; + } + } + assignmentsWithoutItem.Add(assignmentEntry); + } + // reassign without the item for each assignment we found that no longer has an associated item + foreach (var (assignment, slots) in assignmentsWithoutItem) + { + foreach (var (hotbar, slot) in slots) + { + if (!assignment.TryGetItemActionWithItem(out var actionType, out _)) continue; + AssignSlot(hotbar, slot, + ActionAssignment.For(actionType)); + } + } + + // Additionally, we must find items which have no action states at all in our newly provided states so + // we can assume their item was unequipped and reset them to allow auto-population. + var itemsWithoutState = _preventAutoPopulateItem.Keys.Where(item => !itemActionStates.ContainsKey(item)); + foreach (var toRemove in itemsWithoutState) + { + _preventAutoPopulateItem.Remove(toRemove); + } + } + + /// + /// Assigns the indicated hotbar slot to the specified action type. + /// + /// hotbar whose slot is being assigned + /// slot of the hotbar to assign to (0 = the slot labeled 1, 9 = the slot labeled 0) + /// action to assign to the slot + public void AssignSlot(byte hotbar, byte slot, ActionAssignment actionType) + { + ClearSlot(hotbar, slot, false); + _slots[hotbar, slot] = actionType; + if (_assignments.TryGetValue(actionType, out var slotList)) + { + slotList.Add((hotbar, slot)); + } + else + { + var newList = new List<(byte Hotbar, byte Slot)> {(hotbar, slot)}; + _assignments[actionType] = newList; + } + } + + /// + /// Clear the assignment from the indicated slot. + /// + /// hotbar whose slot is being cleared + /// slot of the hotbar to clear (0 = the slot labeled 1, 9 = the slot labeled 0) + /// if true, the action assigned to this slot + /// will be prevented from being auto-populated in the future when it is newly granted. + /// Item actions will automatically be allowed to auto populate again + /// when their associated item are unequipped. This ensures that items that are newly + /// picked up will always present their actions to the user even if they had earlier been cleared. + /// + public void ClearSlot(byte hotbar, byte slot, bool preventAutoPopulate) + { + // remove this particular assignment from our data structures + // (keeping in mind something can be assigned multiple slots) + var currentAction = _slots[hotbar, slot]; + if (!currentAction.HasValue) return; + if (preventAutoPopulate) + { + var assignment = currentAction.Value; + + if (assignment.TryGetAction(out var actionType)) + { + _preventAutoPopulate.Add(actionType); + } + else if (assignment.TryGetItemActionWithItem(out var itemActionType, out var item)) + { + if (!_preventAutoPopulateItem.TryGetValue(item, out var actionTypes)) + { + actionTypes = new HashSet(); + _preventAutoPopulateItem[item] = actionTypes; + } + + actionTypes.Add(itemActionType); + } + } + var assignmentList = _assignments[currentAction.Value]; + assignmentList = assignmentList.Where(a => a.Hotbar != hotbar || a.Slot != slot).ToList(); + if (assignmentList.Count == 0) + { + _assignments.Remove(currentAction.Value); + } + else + { + _assignments[currentAction.Value] = assignmentList; + } + _slots[hotbar, slot] = null; + } + + /// + /// Finds the next open slot the action can go in and assigns it there, + /// starting from the currently selected hotbar. + /// Does not update any UI elements, only updates the assignment data structures. + /// + /// if true, will force the assignment to occur + /// regardless of whether this assignment has been prevented from auto population + /// via ClearSlot's preventAutoPopulate parameter. If false, will have no effect + /// if this assignment has been prevented from auto population. + public void AutoPopulate(ActionAssignment toAssign, byte currentHotbar, bool force = true) + { + if (ShouldPreventAutoPopulate(toAssign, force)) return; + // if the assignment to make is an item action with an associated item, + // then first look for currently assigned item actions without an item, to replace with this + // assignment + if (toAssign.TryGetItemActionWithItem(out var actionType, out var _)) + { + if (_assignments.TryGetValue(ActionAssignment.For(actionType), + out var possibilities)) + { + // use the closest assignment to current hotbar + byte hotbar = 0; + byte slot = 0; + var minCost = int.MaxValue; + foreach (var possibility in possibilities) + { + var cost = possibility.Slot + _numSlots * (currentHotbar >= possibility.Hotbar + ? currentHotbar - possibility.Hotbar + : (_numHotbars - currentHotbar) + possibility.Hotbar); + if (cost < minCost) + { + hotbar = possibility.Hotbar; + slot = possibility.Slot; + minCost = cost; + } + } + + if (minCost != int.MaxValue) + { + AssignSlot(hotbar, slot, toAssign); + return; + } + } + } + + for (byte hotbarOffset = 0; hotbarOffset < _numHotbars; hotbarOffset++) + { + for (byte slot = 0; slot < _numSlots; slot++) + { + var hotbar = (byte) ((currentHotbar + hotbarOffset) % _numHotbars); + var slotAssignment = _slots[hotbar, slot]; + if (slotAssignment.HasValue) + { + // if the assignment in this slot is an item action without an associated item, + // then tie it to the current item if we are trying to auto populate an item action. + if (toAssign.Assignment == Assignment.ItemActionWithItem && + slotAssignment.Value.Assignment == Assignment.ItemActionWithoutItem) + { + AssignSlot(hotbar, slot, toAssign); + return; + } + continue; + } + // slot's empty, assign + AssignSlot(hotbar, slot, toAssign); + return; + } + } + // there was no empty slot + } + + private bool ShouldPreventAutoPopulate(ActionAssignment assignment, bool force) + { + if (force) return false; + + if (assignment.TryGetAction(out var actionType)) + { + return _preventAutoPopulate.Contains(actionType); + } + + if (assignment.TryGetItemActionWithItem(out var itemActionType, out var item)) + { + return _preventAutoPopulateItem.TryGetValue(item, + out var itemActionTypes) && itemActionTypes.Contains(itemActionType); + } + + return false; + } + + /// + /// Gets the assignment to the indicated slot if there is one. + /// + public ActionAssignment? this[in byte hotbar, in byte slot] => _slots[hotbar, slot]; + + /// true if we have the assignment assigned to some slot + public bool HasAssignment(ActionAssignment assignment) + { + return _assignments.ContainsKey(assignment); + } + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/ClientActionsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientActionsComponent.cs new file mode 100644 index 0000000000..bbcf23ff36 --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/ClientActionsComponent.cs @@ -0,0 +1,273 @@ +#nullable enable +using System.Collections.Generic; +using Content.Client.GameObjects.Components.HUD.Inventory; +using Content.Client.GameObjects.Components.Items; +using Content.Client.GameObjects.Components.Mobs.Actions; +using Content.Client.UserInterface; +using Content.Client.UserInterface.Controls; +using Content.Shared.Actions; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.EntitySystems; +using Robust.Client.Interfaces.UserInterface; +using Robust.Client.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Input.Binding; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; + +namespace Content.Client.GameObjects.Components.Mobs +{ + /// + [RegisterComponent] + [ComponentReference(typeof(SharedActionsComponent))] + public sealed class ClientActionsComponent : SharedActionsComponent + { + public const byte Hotbars = 10; + public const byte Slots = 10; + + [Dependency] private readonly IPlayerManager _playerManager = default!; + + [ComponentDependency] private readonly HandsComponent? _handsComponent = null; + [ComponentDependency] private readonly ClientInventoryComponent? _inventoryComponent = null; + + private ActionsUI? _ui; + private readonly List _highlightingItemSlots = new(); + + /// + /// Current assignments for all hotbars / slots for this entity. + /// + public ActionAssignments Assignments { get; } = new(Hotbars, Slots); + + /// + /// Allows calculating if we need to act due to this component being controlled by the current mob + /// + [ViewVariables] + private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner; + + + protected override void Shutdown() + { + base.Shutdown(); + PlayerDetached(); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + switch (message) + { + case PlayerAttachedMsg _: + PlayerAttached(); + break; + case PlayerDetachedMsg _: + PlayerDetached(); + break; + } + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + base.HandleComponentState(curState, nextState); + + if (curState is not ActionComponentState) + { + return; + } + + UpdateUI(); + } + + private void PlayerAttached() + { + if (!CurrentlyControlled || _ui != null) + { + return; + } + + _ui = new ActionsUI(this); + IoCManager.Resolve().StateRoot.AddChild(_ui); + UpdateUI(); + } + + private void PlayerDetached() + { + if (_ui == null) return; + IoCManager.Resolve().StateRoot.RemoveChild(_ui); + _ui = null; + } + + public void HandleHotbarKeybind(byte slot, in PointerInputCmdHandler.PointerInputCmdArgs args) + { + _ui?.HandleHotbarKeybind(slot, args); + } + + /// + /// Updates the displayed hotbar (and menu) based on current state of actions. + /// + private void UpdateUI() + { + if (!CurrentlyControlled || _ui == null) + { + return; + } + + Assignments.Reconcile(_ui.SelectedHotbar, ActionStates(), ItemActionStates()); + + _ui.UpdateUI(); + } + + public void AttemptAction(ActionSlot slot) + { + + var attempt = slot.ActionAttempt(); + if (attempt == null) return; + + switch (attempt.Action.BehaviorType) + { + case BehaviorType.Instant: + // for instant actions, we immediately tell the server we're doing it + SendNetworkMessage(attempt.PerformInstantActionMessage()); + break; + case BehaviorType.Toggle: + // for toggle actions, we immediately tell the server we're toggling it. + if (attempt.TryGetActionState(this, out var actionState)) + { + // TODO: At the moment we always predict that the toggle will work clientside, + // even if it sometimes may not (it will be reset by the server if wrong). + attempt.ToggleAction(this, !actionState.ToggledOn); + slot.ToggledOn = !actionState.ToggledOn; + SendNetworkMessage(attempt.PerformToggleActionMessage(!actionState.ToggledOn)); + } + else + { + Logger.ErrorS("action", "attempted to toggle action {0} which has" + + " unknown state", attempt); + } + + break; + case BehaviorType.TargetPoint: + case BehaviorType.TargetEntity: + // for target actions, we go into "select target" mode, we don't + // message the server until we actually pick our target. + + // if we're clicking the same thing we're already targeting for, then we simply cancel + // targeting + _ui?.ToggleTargeting(slot); + break; + case BehaviorType.None: + break; + default: + Logger.ErrorS("action", "unhandled action press for action {0}", + attempt); + break; + } + } + + /// + /// Handles clicks when selecting the target for an action. Only has an effect when currently + /// selecting a target. + /// + public bool TargetingOnUse(in PointerInputCmdHandler.PointerInputCmdArgs args) + { + // not currently predicted + if (EntitySystem.Get().Predicted) return false; + + // only do something for actual target-based actions + if (_ui?.SelectingTargetFor?.Action == null || + (_ui.SelectingTargetFor.Action.BehaviorType != BehaviorType.TargetEntity && + _ui.SelectingTargetFor.Action.BehaviorType != BehaviorType.TargetPoint)) return false; + + var attempt = _ui.SelectingTargetFor.ActionAttempt(); + if (attempt == null) + { + _ui.StopTargeting(); + return false; + } + + switch (_ui.SelectingTargetFor.Action.BehaviorType) + { + case BehaviorType.TargetPoint: + { + // send our action to the server, we chose our target + SendNetworkMessage(attempt.PerformTargetPointActionMessage(args)); + if (!attempt.Action.Repeat) + { + _ui.StopTargeting(); + } + return true; + } + // target the currently hovered entity, if there is one + case BehaviorType.TargetEntity when args.EntityUid != EntityUid.Invalid: + { + // send our action to the server, we chose our target + SendNetworkMessage(attempt.PerformTargetEntityActionMessage(args)); + if (!attempt.Action.Repeat) + { + _ui.StopTargeting(); + } + return true; + } + default: + _ui.StopTargeting(); + return false; + } + } + + protected override void AfterActionChanged() + { + UpdateUI(); + } + + /// + /// Highlights the item slot (inventory or hand) that contains this item + /// + /// + public void HighlightItemSlot(IEntity item) + { + StopHighlightingItemSlots(); + + // figure out if it's in hand or inventory and highlight it + foreach (var hand in _handsComponent!.Hands) + { + if (hand.Entity != item || hand.Button == null) continue; + _highlightingItemSlots.Add(hand.Button); + hand.Button.Highlight(true); + return; + } + + foreach (var (slot, slotItem) in _inventoryComponent!.AllSlots) + { + if (slotItem != item) continue; + foreach (var itemSlotButton in + _inventoryComponent.InterfaceController.GetItemSlotButtons(slot)) + { + _highlightingItemSlots.Add(itemSlotButton); + itemSlotButton.Highlight(true); + } + return; + } + } + + /// + /// Stops highlighting any item slots we are currently highlighting. + /// + public void StopHighlightingItemSlots() + { + foreach (var itemSlot in _highlightingItemSlots) + { + itemSlot.Highlight(false); + } + _highlightingItemSlots.Clear(); + } + + public void ToggleActionsMenu() + { + _ui?.ToggleActionsMenu(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/ClientAlertsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientAlertsComponent.cs index 4f2a1bbb6c..ae92f09ee3 100644 --- a/Content.Client/GameObjects/Components/Mobs/ClientAlertsComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/ClientAlertsComponent.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Content.Client.UserInterface; -using Content.Client.UserInterface.Stylesheets; +using Content.Client.UserInterface.Controls; using Content.Shared.Alert; using Content.Shared.GameObjects.Components.Mobs; using Robust.Client.GameObjects; -using Robust.Client.Interfaces.Graphics; using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.UserInterface; using Robust.Client.Player; -using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Prototypes; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Mobs @@ -29,19 +24,11 @@ namespace Content.Client.GameObjects.Components.Mobs [ComponentReference(typeof(SharedAlertsComponent))] public sealed class ClientAlertsComponent : SharedAlertsComponent { - private static readonly float TooltipTextMaxWidth = 265; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; private AlertsUI _ui; - private PanelContainer _tooltip; - private RichTextLabel _stateName; - private RichTextLabel _stateDescription; - private RichTextLabel _stateCooldown; private AlertOrderPrototype _alertOrder; - private bool _tooltipReady; [ViewVariables] private readonly Dictionary _alertControls @@ -49,7 +36,6 @@ namespace Content.Client.GameObjects.Components.Mobs /// /// Allows calculating if we need to act due to this component being controlled by the current mob - /// TODO: should be revisited after space-wizards/RobustToolbox#1255 /// [ViewVariables] private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner; @@ -78,14 +64,11 @@ namespace Content.Client.GameObjects.Components.Mobs { base.HandleComponentState(curState, nextState); - if (curState is not AlertsComponentState state) + if (curState is not AlertsComponentState) { return; } - // update the dict of states based on the array we got in the message - SetAlerts(state.Alerts); - UpdateAlertsControls(); } @@ -102,48 +85,24 @@ namespace Content.Client.GameObjects.Components.Mobs Logger.ErrorS("alert", "no alertOrder prototype found, alerts will be in random order"); } - _ui = new AlertsUI(IoCManager.Resolve()); - var uiManager = IoCManager.Resolve(); - uiManager.StateRoot.AddChild(_ui); - - _tooltip = new PanelContainer - { - Visible = false, - StyleClasses = { StyleNano.StyleClassTooltipPanel } - }; - var tooltipVBox = new VBoxContainer - { - RectClipContent = true - }; - _tooltip.AddChild(tooltipVBox); - _stateName = new RichTextLabel - { - MaxWidth = TooltipTextMaxWidth, - StyleClasses = { StyleNano.StyleClassTooltipAlertTitle } - }; - tooltipVBox.AddChild(_stateName); - _stateDescription = new RichTextLabel - { - MaxWidth = TooltipTextMaxWidth, - StyleClasses = { StyleNano.StyleClassTooltipAlertDescription } - }; - tooltipVBox.AddChild(_stateDescription); - _stateCooldown = new RichTextLabel - { - MaxWidth = TooltipTextMaxWidth, - StyleClasses = { StyleNano.StyleClassTooltipAlertCooldown } - }; - tooltipVBox.AddChild(_stateCooldown); - - uiManager.PopupRoot.AddChild(_tooltip); + _ui = new AlertsUI(); + IoCManager.Resolve().StateRoot.AddChild(_ui); UpdateAlertsControls(); } private void PlayerDetached() { - _ui?.Dispose(); - _ui = null; + foreach (var alertControl in _alertControls.Values) + { + alertControl.OnPressed -= AlertControlOnPressed; + } + + if (_ui != null) + { + IoCManager.Resolve().StateRoot.RemoveChild(_ui); + _ui = null; + } _alertControls.Clear(); } @@ -168,39 +127,49 @@ namespace Content.Client.GameObjects.Components.Mobs toRemove.Add(existingKey); } } - foreach (var alertKeyToRemove in toRemove) { - // remove and dispose the control _alertControls.Remove(alertKeyToRemove, out var control); - control?.Dispose(); + if (control == null) return; + _ui.Grid.Children.Remove(control); } // now we know that alertControls contains alerts that should still exist but // may need to updated, // also there may be some new alerts we need to show. // further, we need to ensure they are ordered w.r.t their configured order - foreach (var alertStatus in EnumerateAlertStates()) + foreach (var (alertKey, alertState) in EnumerateAlertStates()) { - if (!AlertManager.TryDecode(alertStatus.AlertEncoded, out var newAlert)) + if (!alertKey.AlertType.HasValue) { - Logger.ErrorS("alert", "Unable to decode alert {0}", alertStatus.AlertEncoded); + Logger.WarningS("alert", "found alertkey without alerttype," + + " alert keys should never be stored without an alerttype set: {0}", alertKey); + continue; + } + var alertType = alertKey.AlertType.Value; + if (!AlertManager.TryGet(alertType, out var newAlert)) + { + Logger.ErrorS("alert", "Unrecognized alertType {0}", alertType); continue; } if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) && existingAlertControl.Alert.AlertType == newAlert.AlertType) { - // id is the same, simply update the existing control severity - existingAlertControl.SetSeverity(alertStatus.Severity); + // key is the same, simply update the existing control severity / cooldown + existingAlertControl.SetSeverity(alertState.Severity); + existingAlertControl.Cooldown = alertState.Cooldown; } else { - existingAlertControl?.Dispose(); + if (existingAlertControl != null) + { + _ui.Grid.Children.Remove(existingAlertControl); + } // this is a new alert + alert key or just a different alert with the same // key, create the control and add it in the appropriate order - var newAlertControl = CreateAlertControl(newAlert, alertStatus); + var newAlertControl = CreateAlertControl(newAlert, alertState); if (_alertOrder != null) { var added = false; @@ -233,14 +202,11 @@ namespace Content.Client.GameObjects.Components.Mobs private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState) { - - var alertControl = new AlertControl(alert, alertState.Severity, _resourceCache); - // show custom tooltip for the status control - alertControl.OnShowTooltip += AlertOnOnShowTooltip; - alertControl.OnHideTooltip += AlertOnOnHideTooltip; - + var alertControl = new AlertControl(alert, alertState.Severity, _resourceCache) + { + Cooldown = alertState.Cooldown + }; alertControl.OnPressed += AlertControlOnPressed; - return alertControl; } @@ -249,36 +215,6 @@ namespace Content.Client.GameObjects.Components.Mobs AlertPressed(args, args.Button as AlertControl); } - private void AlertOnOnHideTooltip(object sender, EventArgs e) - { - _tooltipReady = false; - _tooltip.Visible = false; - } - - private void AlertOnOnShowTooltip(object sender, EventArgs e) - { - var alertControl = (AlertControl) sender; - _stateName.SetMessage(alertControl.Alert.Name); - _stateDescription.SetMessage(alertControl.Alert.Description); - // check for a cooldown - if (alertControl.TotalDuration != null && alertControl.TotalDuration > 0) - { - _stateCooldown.SetMessage(FormattedMessage.FromMarkup("[color=#776a6a]" + - alertControl.TotalDuration + - " sec cooldown[/color]")); - _stateCooldown.Visible = true; - } - else - { - _stateCooldown.Visible = false; - } - // TODO: Text display of cooldown - Tooltips.PositionTooltip(_tooltip); - // if we set it visible here the size of the previous tooltip will flicker for a frame, - // so instead we wait until FrameUpdate to make it visible - _tooltipReady = true; - } - private void AlertPressed(BaseButton.ButtonEventArgs args, AlertControl alert) { if (args.Event.Function != EngineKeyFunctions.UIClick) @@ -286,57 +222,17 @@ namespace Content.Client.GameObjects.Components.Mobs return; } - if (AlertManager.TryEncode(alert.Alert, out var encoded)) - { - SendNetworkMessage(new ClickAlertMessage(encoded)); - } - else - { - Logger.ErrorS("alert", "unable to encode alert {0}", alert.Alert.AlertType); - } - + SendNetworkMessage(new ClickAlertMessage(alert.Alert.AlertType)); } - public void FrameUpdate(float frameTime) + protected override void AfterShowAlert() { - if (_tooltipReady) - { - _tooltipReady = false; - _tooltip.Visible = true; - } - foreach (var (alertKey, alertControl) in _alertControls) - { - // reconcile all alert controls with their current cooldowns - if (TryGetAlertState(alertKey, out var alertState)) - { - alertControl.UpdateCooldown(alertState.Cooldown, _gameTiming.CurTime); - } - else - { - Logger.WarningS("alert", "coding error - no alert state for alert {0} " + - "even though we had an AlertControl for it, this" + - " should never happen", alertControl.Alert.AlertType); - } - - } + UpdateAlertsControls(); } protected override void AfterClearAlert() { UpdateAlertsControls(); } - - public override void OnRemove() - { - base.OnRemove(); - - foreach (var alertControl in _alertControls.Values) - { - alertControl.OnShowTooltip -= AlertOnOnShowTooltip; - alertControl.OnHideTooltip -= AlertOnOnHideTooltip; - alertControl.OnPressed -= AlertControlOnPressed; - } - - } } } diff --git a/Content.Client/GameObjects/EntitySystems/ActionsSystem.cs b/Content.Client/GameObjects/EntitySystems/ActionsSystem.cs new file mode 100644 index 0000000000..b30d6c0cff --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/ActionsSystem.cs @@ -0,0 +1,91 @@ +using Content.Client.GameObjects.Components.Mobs; +using Content.Shared.Input; +using JetBrains.Annotations; +using Robust.Client.Player; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.IoC; + +namespace Content.Client.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class ActionsSystem : EntitySystem + { + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public override void Initialize() + { + base.Initialize(); + + // set up hotkeys for hotbar + CommandBinds.Builder + .Bind(ContentKeyFunctions.OpenActionsMenu, + InputCmdHandler.FromDelegate(_ => ToggleActionsMenu())) + .Bind(ContentKeyFunctions.Hotbar1, + HandleHotbarKeybind(0)) + .Bind(ContentKeyFunctions.Hotbar2, + HandleHotbarKeybind(1)) + .Bind(ContentKeyFunctions.Hotbar3, + HandleHotbarKeybind(2)) + .Bind(ContentKeyFunctions.Hotbar4, + HandleHotbarKeybind(3)) + .Bind(ContentKeyFunctions.Hotbar5, + HandleHotbarKeybind(4)) + .Bind(ContentKeyFunctions.Hotbar6, + HandleHotbarKeybind(5)) + .Bind(ContentKeyFunctions.Hotbar7, + HandleHotbarKeybind(6)) + .Bind(ContentKeyFunctions.Hotbar8, + HandleHotbarKeybind(7)) + .Bind(ContentKeyFunctions.Hotbar9, + HandleHotbarKeybind(8)) + .Bind(ContentKeyFunctions.Hotbar0, + HandleHotbarKeybind(9)) + // when selecting a target, we intercept clicks in the game world, treating them as our target selection. We want to + // take priority before any other systems handle the click. + .BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse), + typeof(ConstructionSystem), typeof(DragDropSystem)) + .Register(); + } + + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); + } + + private PointerInputCmdHandler HandleHotbarKeybind(byte slot) + { + // delegate to the ActionsUI, simulating a click on it + return new((in PointerInputCmdHandler.PointerInputCmdArgs args) => + { + var playerEntity = _playerManager.LocalPlayer.ControlledEntity; + if (playerEntity == null || + !playerEntity.TryGetComponent( out var actionsComponent)) return false; + + actionsComponent.HandleHotbarKeybind(slot, args); + return true; + }, + false); + } + + private bool TargetingOnUse(in PointerInputCmdHandler.PointerInputCmdArgs args) + { + var playerEntity = _playerManager.LocalPlayer.ControlledEntity; + if (playerEntity == null || + !playerEntity.TryGetComponent( out var actionsComponent)) return false; + + return actionsComponent.TargetingOnUse(args); + } + + private void ToggleActionsMenu() + { + var playerEntity = _playerManager.LocalPlayer.ControlledEntity; + if (playerEntity == null || + !playerEntity.TryGetComponent( out var actionsComponent)) return; + + actionsComponent.ToggleActionsMenu(); + } + } +} diff --git a/Content.Client/GameObjects/EntitySystems/AlertsSystem.cs b/Content.Client/GameObjects/EntitySystems/AlertsSystem.cs deleted file mode 100644 index e7c267329c..0000000000 --- a/Content.Client/GameObjects/EntitySystems/AlertsSystem.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Client.GameObjects.Components.Mobs; -using JetBrains.Annotations; -using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.Timing; -using Robust.Shared.IoC; - -namespace Content.Client.GameObjects.EntitySystems -{ - [UsedImplicitly] - public class AlertsSystem : EntitySystem - { - [Dependency] private readonly IGameTiming _gameTiming = default!; - - public override void FrameUpdate(float frameTime) - { - base.FrameUpdate(frameTime); - - if (!_gameTiming.IsFirstTimePredicted) - return; - - foreach (var clientAlertsComponent in EntityManager.ComponentManager.EntityQuery()) - { - clientAlertsComponent.FrameUpdate(frameTime); - } - } - } -} diff --git a/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs b/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs index 8cb69b8e2d..97c8aa37dd 100644 --- a/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Content.Client.State; +using Content.Client.Utility; using Content.Shared.GameObjects; using Content.Shared.GameObjects.EntitySystemMessages; using Content.Shared.GameObjects.EntitySystems; @@ -10,7 +11,6 @@ using Robust.Client.GameObjects; using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Graphics.Shaders; using Robust.Client.Interfaces.Graphics.ClientEye; -using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.State; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Input; @@ -18,7 +18,6 @@ using Robust.Shared.Input.Binding; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; namespace Content.Client.GameObjects.EntitySystems @@ -30,12 +29,9 @@ namespace Content.Client.GameObjects.EntitySystems public class DragDropSystem : EntitySystem { [Dependency] private readonly IStateManager _stateManager = default!; - [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - // drag will be triggered when mouse leaves this deadzone around the click position. - private const float DragDeadzone = 2f; // how often to recheck possible targets (prevents calling expensive // check logic each update) private const float TargetRecheckInterval = 0.25f; @@ -50,14 +46,10 @@ namespace Content.Client.GameObjects.EntitySystems // entity performing the drag action private IEntity _dragger; - private IEntity _draggedEntity; private readonly List _draggables = new(); private IEntity _dragShadow; - private DragState _state; // time since mouse down over the dragged entity private float _mouseDownTime; - // screen pos where the mouse down began - private Vector2 _mouseDownScreenPos; // how much time since last recheck of all possible targets private float _targetRecheckTime; // reserved initial mousedown event so we can replay it if no drag ends up being performed @@ -66,6 +58,8 @@ namespace Content.Client.GameObjects.EntitySystems // can ignore any events sent to this system private bool _isReplaying; + private DragDropHelper _dragDropHelper; + private ShaderInstance _dropTargetInRangeShader; private ShaderInstance _dropTargetOutOfRangeShader; private SharedInteractionSystem _interactionSystem; @@ -73,20 +67,9 @@ namespace Content.Client.GameObjects.EntitySystems private readonly List _highlightedSprites = new(); - private enum DragState : byte - { - NotDragging, - // not dragging yet, waiting to see - // if they hold for long enough - MouseDown, - // currently dragging something - Dragging, - } - - public override void Initialize() { - _state = DragState.NotDragging; + _dragDropHelper = new DragDropHelper(OnBeginDrag, OnContinueDrag, OnEndDrag); _dropTargetInRangeShader = _prototypeManager.Index(ShaderDropTargetInRange).Instance(); _dropTargetOutOfRangeShader = _prototypeManager.Index(ShaderDropTargetOutOfRange).Instance(); @@ -101,7 +84,7 @@ namespace Content.Client.GameObjects.EntitySystems public override void Shutdown() { - CancelDrag(false, null); + _dragDropHelper.EndDrag(); CommandBinds.Unregister(); base.Shutdown(); } @@ -132,7 +115,7 @@ namespace Content.Client.GameObjects.EntitySystems var dragger = args.Session.AttachedEntity; // cancel any current dragging if there is one (shouldn't be because they would've had to have lifted // the mouse, canceling the drag, but just being cautious) - CancelDrag(false, null); + _dragDropHelper.EndDrag(); // possibly initiating a drag // check if the clicked entity is draggable @@ -150,96 +133,43 @@ namespace Content.Client.GameObjects.EntitySystems var dragEventArgs = new StartDragDropEventArgs(args.Session.AttachedEntity, entity); if (draggable.CanStartDrag(dragEventArgs)) { - // wait to initiate a drag - _dragger = dragger; - _draggedEntity = entity; _draggables.Add(draggable); - _mouseDownTime = 0; - _state = DragState.MouseDown; - _mouseDownScreenPos = _inputManager.MouseScreenPosition; - // don't want anything else to process the click, - // but we will save the event so we can "re-play" it if this drag does - // not turn into an actual drag so the click can be handled normally - _savedMouseDown = args; canDrag = true; } } + if (canDrag) + { + // wait to initiate a drag + _dragDropHelper.MouseDown(entity); + _dragger = dragger; + _mouseDownTime = 0; + // don't want anything else to process the click, + // but we will save the event so we can "re-play" it if this drag does + // not turn into an actual drag so the click can be handled normally + _savedMouseDown = args; + } + return canDrag; } return false; } - private bool OnUseMouseUp(in PointerInputCmdHandler.PointerInputCmdArgs args) + + private bool OnBeginDrag() { - if (_state == DragState.MouseDown) + if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted) { - // quick mouseup, definitely treat it as a normal click by - // replaying the original - CancelDrag(true, args.OriginalMessage); - return false; - } - if (_state != DragState.Dragging) return false; - - // remaining CancelDrag calls will not replay the click because - // by this time we've determined the input was actually a drag attempt - - - // tell the server we are dropping if we are over a valid drop target in range. - // We don't use args.EntityUid here because drag interactions generally should - // work even if there's something "on top" of the drop target - if (!_interactionSystem.InRangeUnobstructed(_dragger, - args.Coordinates, ignoreInsideBlocker: true)) - { - CancelDrag(false, null); + // something happened to the clicked entity or we moved the mouse off the target so + // we shouldn't replay the original click return false; } - var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates); - - foreach (var entity in entities) + if (_dragDropHelper.Dragged.TryGetComponent(out var draggedSprite)) { - // check if it's able to be dropped on by current dragged entity - var dropArgs = new DragDropEventArgs(_dragger, args.Coordinates, _draggedEntity, entity); - - foreach (var draggable in _draggables) - { - if (!draggable.CanDrop(dropArgs)) - { - continue; - } - - // tell the server about the drop attempt - RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _draggedEntity.Uid, - entity.Uid)); - - draggable.Drop(dropArgs); - - CancelDrag(false, null); - return true; - } - } - - CancelDrag(false, null); - return false; - } - - private void StartDragging() - { - // this is checked elsewhere but adding this as a failsafe - if (_draggedEntity == null || _draggedEntity.Deleted) - { - Logger.Error("Programming error. Cannot initiate drag, no dragged entity or entity" + - " was deleted."); - return; - } - - if (_draggedEntity.TryGetComponent(out var draggedSprite)) - { - _state = DragState.Dragging; // pop up drag shadow under mouse - var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition); + var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition); _dragShadow = EntityManager.SpawnEntity("dragshadow", mousePos); var dragSprite = _dragShadow.GetComponent(); dragSprite.CopyFrom(draggedSprite); @@ -249,22 +179,132 @@ namespace Content.Client.GameObjects.EntitySystems dragSprite.DrawDepth = (int) DrawDepth.Overlays; if (dragSprite.Directional) { - _dragShadow.Transform.WorldRotation = _draggedEntity.Transform.WorldRotation; + _dragShadow.Transform.WorldRotation = _dragDropHelper.Dragged.Transform.WorldRotation; } HighlightTargets(); + + // drag initiated + return true; } - else + + Logger.Warning("Unable to display drag shadow for {0} because it" + + " has no sprite component.", _dragDropHelper.Dragged.Name); + return false; + } + + private bool OnContinueDrag(float frameTime) + { + if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted) { - Logger.Warning("Unable to display drag shadow for {0} because it" + - " has no sprite component.", _draggedEntity.Name); + return false; } + // still in range of the thing we are dragging? + if (!_interactionSystem.InRangeUnobstructed(_dragger, _dragDropHelper.Dragged)) + { + return false; + } + + // keep dragged entity under mouse + var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition); + // TODO: would use MapPosition instead if it had a setter, but it has no setter. + // is that intentional, or should we add a setter for Transform.MapPosition? + _dragShadow.Transform.WorldPosition = mousePos.Position; + + _targetRecheckTime += frameTime; + if (_targetRecheckTime > TargetRecheckInterval) + { + HighlightTargets(); + _targetRecheckTime = 0; + } + + return true; + } + + private void OnEndDrag() + { + RemoveHighlights(); + if (_dragShadow != null) + { + EntityManager.DeleteEntity(_dragShadow); + } + + _dragShadow = null; + _draggables.Clear(); + _dragger = null; + _mouseDownTime = 0; + _savedMouseDown = null; + } + + private bool OnUseMouseUp(in PointerInputCmdHandler.PointerInputCmdArgs args) + { + if (!_dragDropHelper.IsDragging) + { + // haven't started the drag yet, quick mouseup, definitely treat it as a normal click by + // replaying the original cmd + if (_savedMouseDown.HasValue && _mouseDownTime < MaxMouseDownTimeForReplayingClick) + { + var savedValue = _savedMouseDown.Value; + _isReplaying = true; + // adjust the timing info based on the current tick so it appears as if it happened now + var replayMsg = savedValue.OriginalMessage; + var adjustedInputMsg = new FullInputCmdMessage(args.OriginalMessage.Tick, args.OriginalMessage.SubTick, + replayMsg.InputFunctionId, replayMsg.State, replayMsg.Coordinates, replayMsg.ScreenCoordinates, replayMsg.Uid); + + _inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use, + adjustedInputMsg, true); + _isReplaying = false; + } + _dragDropHelper.EndDrag(); + return false; + } + + // now when ending the drag, we will not replay the click because + // by this time we've determined the input was actually a drag attempt + + // tell the server we are dropping if we are over a valid drop target in range. + // We don't use args.EntityUid here because drag interactions generally should + // work even if there's something "on top" of the drop target + if (!_interactionSystem.InRangeUnobstructed(_dragger, + args.Coordinates, ignoreInsideBlocker: true)) + { + _dragDropHelper.EndDrag(); + return false; + } + + var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates); + + foreach (var entity in entities) + { + // check if it's able to be dropped on by current dragged entity + var dropArgs = new DragDropEventArgs(_dragger, args.Coordinates, _dragDropHelper.Dragged, entity); + + foreach (var draggable in _draggables) + { + if (!draggable.CanDrop(dropArgs)) + { + continue; + } + + // tell the server about the drop attempt + RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _dragDropHelper.Dragged.Uid, + entity.Uid)); + + draggable.Drop(dropArgs); + + _dragDropHelper.EndDrag(); + return true; + } + } + + _dragDropHelper.EndDrag(); + return false; } private void HighlightTargets() { - if (_state != DragState.Dragging || _draggedEntity == null || - _draggedEntity.Deleted || _dragShadow == null || _dragShadow.Deleted) + if (_dragDropHelper.Dragged == null || + _dragDropHelper.Dragged.Deleted || _dragShadow == null || _dragShadow.Deleted) { Logger.Warning("Programming error. Can't highlight drag and drop targets, not currently " + "dragging anything or dragged entity / shadow was deleted."); @@ -289,7 +329,7 @@ namespace Content.Client.GameObjects.EntitySystems if (inRangeSprite.Visible == false) continue; // check if it's able to be dropped on by current dragged entity - var canDropArgs = new CanDropEventArgs(_dragger, _draggedEntity, pvsEntity); + var canDropArgs = new CanDropEventArgs(_dragger, _dragDropHelper.Dragged, pvsEntity); var anyValidDraggable = _draggables.Any(draggable => draggable.CanDrop(canDropArgs)); if (anyValidDraggable) @@ -314,95 +354,10 @@ namespace Content.Client.GameObjects.EntitySystems _highlightedSprites.Clear(); } - /// - /// Cancels the drag, firing our saved drag event if instructed to do so and - /// we are within the threshold for replaying the click - /// (essentially reverting the drag attempt and allowing the original click - /// to proceed as if no drag was performed) - /// - /// if fireSavedCmd is true, this should be passed with the value of - /// the pointer cmd that caused the drag to be cancelled - private void CancelDrag(bool fireSavedCmd, FullInputCmdMessage cause) - { - RemoveHighlights(); - if (_dragShadow != null) - { - EntityManager.DeleteEntity(_dragShadow); - } - - _dragShadow = null; - _draggedEntity = null; - _draggables.Clear(); - _dragger = null; - _state = DragState.NotDragging; - - _mouseDownTime = 0; - - if (fireSavedCmd && _savedMouseDown.HasValue && _mouseDownTime < MaxMouseDownTimeForReplayingClick) - { - var savedValue = _savedMouseDown.Value; - _isReplaying = true; - // adjust the timing info based on the current tick so it appears as if it happened now - var replayMsg = savedValue.OriginalMessage; - var adjustedInputMsg = new FullInputCmdMessage(cause.Tick, cause.SubTick, replayMsg.InputFunctionId, replayMsg.State, replayMsg.Coordinates, replayMsg.ScreenCoordinates, replayMsg.Uid); - - _inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use, - adjustedInputMsg, true); - _isReplaying = false; - } - - _savedMouseDown = null; - - } - public override void Update(float frameTime) { base.Update(frameTime); - if (_state == DragState.MouseDown) - { - var screenPos = _inputManager.MouseScreenPosition; - if (_draggedEntity == null || _draggedEntity.Deleted) - { - // something happened to the clicked entity or we moved the mouse off the target so - // we shouldn't replay the original click - CancelDrag(false, null); - return; - } - else if ((_mouseDownScreenPos - screenPos).Length > DragDeadzone) - { - // initiate actual drag - StartDragging(); - _mouseDownTime = 0; - } - } - else if (_state == DragState.Dragging) - { - if (_draggedEntity == null || _draggedEntity.Deleted) - { - CancelDrag(false, null); - return; - } - // still in range of the thing we are dragging? - if (!_interactionSystem.InRangeUnobstructed(_dragger, _draggedEntity)) - { - CancelDrag(false, null); - return; - } - - // keep dragged entity under mouse - var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition); - // TODO: would use MapPosition instead if it had a setter, but it has no setter. - // is that intentional, or should we add a setter for Transform.MapPosition? - _dragShadow.Transform.WorldPosition = mousePos.Position; - - _targetRecheckTime += frameTime; - if (_targetRecheckTime > TargetRecheckInterval) - { - HighlightTargets(); - _targetRecheckTime = 0; - } - - } + _dragDropHelper.Update(frameTime); } } } diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 0cb7efa887..f61fac1c5f 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -210,6 +210,8 @@ "CrematoriumEntityStorage", "RandomArcade", "RandomSpriteState", + "DebugEquip", + "InnateActions", "ReagentGrinder", "Grindable", "Juiceable", diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index fcd0be1519..c10d16857f 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -46,6 +46,17 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.Arcade1); human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + human.AddFunction(ContentKeyFunctions.OpenActionsMenu); + human.AddFunction(ContentKeyFunctions.Hotbar0); + human.AddFunction(ContentKeyFunctions.Hotbar1); + human.AddFunction(ContentKeyFunctions.Hotbar2); + human.AddFunction(ContentKeyFunctions.Hotbar3); + human.AddFunction(ContentKeyFunctions.Hotbar4); + human.AddFunction(ContentKeyFunctions.Hotbar5); + human.AddFunction(ContentKeyFunctions.Hotbar6); + human.AddFunction(ContentKeyFunctions.Hotbar7); + human.AddFunction(ContentKeyFunctions.Hotbar8); + human.AddFunction(ContentKeyFunctions.Hotbar9); var ghost = contexts.New("ghost", "common"); ghost.AddFunction(EngineKeyFunctions.MoveUp); diff --git a/Content.Client/UserInterface/ActionAlertTooltip.cs b/Content.Client/UserInterface/ActionAlertTooltip.cs new file mode 100644 index 0000000000..07d081eee4 --- /dev/null +++ b/Content.Client/UserInterface/ActionAlertTooltip.cs @@ -0,0 +1,100 @@ +#nullable enable + +using System; +using Content.Client.UserInterface.Stylesheets; +using Content.Shared.Actions; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.UserInterface +{ + /// + /// Tooltip for actions or alerts because they are very similar. + /// + public class ActionAlertTooltip : PanelContainer + { + private const float TooltipTextMaxWidth = 350; + + private readonly RichTextLabel _cooldownLabel; + private readonly IGameTiming _gameTiming; + + /// + /// Current cooldown displayed in this tooltip. Set to null to show no cooldown. + /// + public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; } + + public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null) + { + _gameTiming = IoCManager.Resolve(); + + SetOnlyStyleClass(StyleNano.StyleClassTooltipPanel); + + VBoxContainer vbox; + AddChild(vbox = new VBoxContainer {RectClipContent = true}); + var nameLabel = new RichTextLabel + { + MaxWidth = TooltipTextMaxWidth, + StyleClasses = {StyleNano.StyleClassTooltipActionTitle} + }; + nameLabel.SetMessage(name); + vbox.AddChild(nameLabel); + + if (desc != null && !string.IsNullOrWhiteSpace(desc.ToString())) + { + var description = new RichTextLabel + { + MaxWidth = TooltipTextMaxWidth, + StyleClasses = {StyleNano.StyleClassTooltipActionDescription} + }; + description.SetMessage(desc); + vbox.AddChild(description); + } + + vbox.AddChild(_cooldownLabel = new RichTextLabel + { + MaxWidth = TooltipTextMaxWidth, + StyleClasses = {StyleNano.StyleClassTooltipActionCooldown}, + Visible = false + }); + + if (!string.IsNullOrWhiteSpace(requires)) + { + var requiresLabel = new RichTextLabel + { + MaxWidth = TooltipTextMaxWidth, + StyleClasses = {StyleNano.StyleClassTooltipActionRequirements} + }; + requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" + + requires + + "[/color]")); + vbox.AddChild(requiresLabel); + } + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + if (!Cooldown.HasValue) + { + _cooldownLabel.Visible = false; + return; + } + + var timeLeft = Cooldown.Value.End - _gameTiming.CurTime; + if (timeLeft > TimeSpan.Zero) + { + var duration = Cooldown.Value.End - Cooldown.Value.Start; + _cooldownLabel.SetMessage(FormattedMessage.FromMarkup( + $"[color=#a10505]{duration.Seconds} sec cooldown ({timeLeft.Seconds + 1} sec remaining)[/color]")); + _cooldownLabel.Visible = true; + } + else + { + _cooldownLabel.Visible = false; + } + } + } +} diff --git a/Content.Client/UserInterface/ActionMenu.cs b/Content.Client/UserInterface/ActionMenu.cs new file mode 100644 index 0000000000..07973f9470 --- /dev/null +++ b/Content.Client/UserInterface/ActionMenu.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Content.Client.GameObjects.Components.Mobs; +using Content.Client.GameObjects.Components.Mobs.Actions; +using Content.Client.UserInterface.Controls; +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.Actions; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.Utility; +using Robust.Shared.Input; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.Timing; + +namespace Content.Client.UserInterface +{ + /// + /// Action selection menu, allows filtering and searching over all possible + /// actions and populating those actions into the hotbar. + /// + public class ActionMenu : SS14Window + { + private const string ItemTag = "item"; + private const string NotItemTag = "not item"; + private const string InstantActionTag = "instant"; + private const string ToggleActionTag = "toggle"; + private const string TargetActionTag = "target"; + private const string AllActionsTag = "all"; + private const string GrantedActionsTag = "granted"; + private const int MinSearchLength = 3; + private static readonly Regex NonAlphanumeric = new Regex(@"\W", RegexOptions.Compiled); + private static readonly Regex Whitespace = new Regex(@"\s+", RegexOptions.Compiled); + private static readonly BaseActionPrototype[] EmptyActionList = Array.Empty(); + + // parallel list of actions currently selectable in itemList + private BaseActionPrototype[] _actionList; + + private readonly ActionManager _actionManager; + private readonly ClientActionsComponent _actionsComponent; + private readonly ActionsUI _actionsUI; + private readonly LineEdit _searchBar; + private readonly MultiselectOptionButton _filterButton; + private readonly Label _filterLabel; + private readonly Button _clearButton; + private readonly GridContainer _resultsGrid; + private readonly TextureRect _dragShadow; + private readonly DragDropHelper _dragDropHelper; + + + public ActionMenu(ClientActionsComponent actionsComponent, ActionsUI actionsUI) + { + _actionsComponent = actionsComponent; + _actionsUI = actionsUI; + _actionManager = IoCManager.Resolve(); + Title = Loc.GetString("Actions"); + CustomMinimumSize = (300, 300); + + Contents.AddChild(new VBoxContainer + { + Children = + { + new HBoxContainer + { + Children = + { + (_searchBar = new LineEdit + { + StyleClasses = { StyleNano.StyleClassActionSearchBox }, + SizeFlagsHorizontal = SizeFlags.FillExpand, + PlaceHolder = Loc.GetString("Search") + }), + (_filterButton = new MultiselectOptionButton() + { + Label = Loc.GetString("Filter") + }), + } + }, + (_clearButton = new Button + { + Text = Loc.GetString("Clear"), + }), + (_filterLabel = new Label()), + new ScrollContainer + { + //TODO: needed? CustomMinimumSize = new Vector2(200.0f, 0.0f), + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + (_resultsGrid = new GridContainer + { + MaxWidth = 300 + }) + } + } + } + }); + + // populate filters from search tags + var filterTags = new List(); + foreach (var action in _actionManager.EnumerateActions()) + { + filterTags.AddRange(action.Filters); + } + + // special one to filter to only include item actions + filterTags.Add(ItemTag); + filterTags.Add(NotItemTag); + filterTags.Add(InstantActionTag); + filterTags.Add(ToggleActionTag); + filterTags.Add(TargetActionTag); + filterTags.Add(AllActionsTag); + filterTags.Add(GrantedActionsTag); + + foreach (var tag in filterTags.Distinct().OrderBy(tag => tag)) + { + _filterButton.AddItem( CultureInfo.CurrentCulture.TextInfo.ToTitleCase(tag), tag); + } + + UpdateFilterLabel(); + + _dragShadow = new TextureRect + { + CustomMinimumSize = (64, 64), + Stretch = TextureRect.StretchMode.Scale, + Visible = false + }; + UserInterfaceManager.PopupRoot.AddChild(_dragShadow); + LayoutContainer.SetSize(_dragShadow, (64, 64)); + + _dragDropHelper = new DragDropHelper(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag); + } + + + protected override void EnteredTree() + { + base.EnteredTree(); + _clearButton.OnPressed += OnClearButtonPressed; + _searchBar.OnTextChanged += OnSearchTextChanged; + _filterButton.OnItemSelected += OnFilterItemSelected; + + foreach (var actionMenuControl in _resultsGrid.Children) + { + var actionMenuItem = (actionMenuControl as ActionMenuItem); + actionMenuItem.OnButtonDown += OnItemButtonDown; + actionMenuItem.OnButtonUp += OnItemButtonUp; + actionMenuItem.OnPressed += OnItemPressed; + } + } + + protected override void ExitedTree() + { + base.ExitedTree(); + _clearButton.OnPressed -= OnClearButtonPressed; + _searchBar.OnTextChanged -= OnSearchTextChanged; + _filterButton.OnItemSelected -= OnFilterItemSelected; + + foreach (var actionMenuControl in _resultsGrid.Children) + { + var actionMenuItem = (actionMenuControl as ActionMenuItem); + actionMenuItem.OnButtonDown -= OnItemButtonDown; + actionMenuItem.OnButtonUp -= OnItemButtonUp; + actionMenuItem.OnPressed -= OnItemPressed; + } + } + + private void OnFilterItemSelected(MultiselectOptionButton.ItemPressedEventArgs args) + { + UpdateFilterLabel(); + SearchAndDisplay(); + } + + protected override void Resized() + { + base.Resized(); + // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done, + // currently no good way to let the grid know what size it has to "work with", so must manually resize + _resultsGrid.MaxWidth = Width; + } + + private bool OnBeginActionDrag() + { + _dragShadow.Texture = _dragDropHelper.Dragged.Action.Icon.Frame0(); + // don't make visible until frameupdate, otherwise it'll flicker + LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled - (32, 32)); + return true; + } + + private bool OnContinueActionDrag(float frameTime) + { + // keep dragged entity centered under mouse + LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled - (32, 32)); + // we don't set this visible until frameupdate, otherwise it flickers + _dragShadow.Visible = true; + return true; + } + + private void OnEndActionDrag() + { + _dragShadow.Visible = false; + } + + private void OnItemButtonDown(BaseButton.ButtonEventArgs args) + { + if (args.Event.Function != EngineKeyFunctions.UIClick) return; + _dragDropHelper.MouseDown(args.Button as ActionMenuItem); + } + + private void OnItemButtonUp(BaseButton.ButtonEventArgs args) + { + // note the buttonup only fires on the control that was originally + // pressed to initiate the drag, NOT the one we are currently hovering + if (args.Event.Function != EngineKeyFunctions.UIClick) return; + + if (UserInterfaceManager.CurrentlyHovered is ActionSlot targetSlot) + { + if (!_dragDropHelper.IsDragging || _dragDropHelper.Dragged?.Action == null) + { + _dragDropHelper.EndDrag(); + return; + } + + // drag and drop + switch (_dragDropHelper.Dragged.Action) + { + // assign the dragged action to the target slot + case ActionPrototype actionPrototype: + _actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, ActionAssignment.For(actionPrototype.ActionType)); + break; + case ItemActionPrototype itemActionPrototype: + // the action menu doesn't show us if the action has an associated item, + // so when we perform the assignment, we should check if we currently have an unassigned state + // for this item and assign it tied to that item if so, otherwise assign it "itemless" + + // this is not particularly efficient but we don't maintain an index from + // item action type to its action states, and this method should be pretty infrequent so it's probably fine + var assigned = false; + foreach (var (item, itemStates) in _actionsComponent.ItemActionStates()) + { + foreach (var (actionType, _) in itemStates) + { + if (actionType != itemActionPrototype.ActionType) continue; + var assignment = ActionAssignment.For(actionType, item); + if (_actionsComponent.Assignments.HasAssignment(assignment)) continue; + // no assignment for this state, assign tied to the item + assigned = true; + _actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, assignment); + break; + } + + if (assigned) + { + break; + } + } + + if (!assigned) + { + _actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, targetSlot.SlotIndex, ActionAssignment.For(itemActionPrototype.ActionType)); + } + break; + } + + _actionsUI.UpdateUI(); + } + + _dragDropHelper.EndDrag(); + } + + private void OnItemPressed(BaseButton.ButtonEventArgs args) + { + if (args.Button is not ActionMenuItem actionMenuItem) return; + switch (actionMenuItem.Action) + { + case ActionPrototype actionPrototype: + _actionsComponent.Assignments.AutoPopulate(ActionAssignment.For(actionPrototype.ActionType), _actionsUI.SelectedHotbar); + break; + case ItemActionPrototype itemActionPrototype: + _actionsComponent.Assignments.AutoPopulate(ActionAssignment.For(itemActionPrototype.ActionType), _actionsUI.SelectedHotbar); + break; + default: + Logger.ErrorS("action", "unexpected action prototype {0}", actionMenuItem.Action); + break; + } + + _actionsUI.UpdateUI(); + } + + private void OnClearButtonPressed(BaseButton.ButtonEventArgs args) + { + _searchBar.Clear(); + _filterButton.DeselectAll(); + UpdateFilterLabel(); + SearchAndDisplay(); + } + + private void OnSearchTextChanged(LineEdit.LineEditEventArgs obj) + { + SearchAndDisplay(); + } + + private void SearchAndDisplay() + { + var search = Standardize(_searchBar.Text); + // only display nothing if there are no filters selected and text is not long enough. + // otherwise we will search if even one filter is applied, regardless of length of search string + if (_filterButton.SelectedKeys.Count == 0 && + (string.IsNullOrWhiteSpace(search) || search.Length < MinSearchLength)) + { + ClearList(); + return; + } + + var matchingActions = _actionManager.EnumerateActions() + .Where(a => MatchesSearchCriteria(a, search, _filterButton.SelectedKeys)); + + PopulateActions(matchingActions); + } + + private void UpdateFilterLabel() + { + if (_filterButton.SelectedKeys.Count == 0) + { + _filterLabel.Visible = false; + } + else + { + _filterLabel.Visible = true; + _filterLabel.Text = Loc.GetString("Filters: {0}", string.Join(", ", _filterButton.SelectedLabels)); + } + } + + private bool MatchesSearchCriteria(BaseActionPrototype action, string standardizedSearch, + IReadOnlyList selectedFilterTags) + { + // check filter tag match first - each action must contain all filter tags currently selected. + // if no tags selected, don't check tags + if (selectedFilterTags.Count > 0 && selectedFilterTags.Any(filterTag => !ActionMatchesFilterTag(action, filterTag))) + { + return false; + } + + // check search tag match against the search query + if (action.Keywords.Any(standardizedSearch.Contains)) + { + return true; + } + + if (Standardize(ActionTypeString(action)).Contains(standardizedSearch)) + { + return true; + } + + // allows matching by typing spaces between the enum case changes, like "xeno spit" if the + // actiontype is "XenoSpit" + if (Standardize(ActionTypeString(action), true).Contains(standardizedSearch)) + { + return true; + } + + if (Standardize(action.Name.ToString()).Contains(standardizedSearch)) + { + return true; + } + + return false; + + } + + private string ActionTypeString(BaseActionPrototype baseActionPrototype) + { + if (baseActionPrototype is ActionPrototype actionPrototype) + { + return actionPrototype.ActionType.ToString(); + } + if (baseActionPrototype is ItemActionPrototype itemActionPrototype) + { + return itemActionPrototype.ActionType.ToString(); + } + throw new InvalidOperationException(); + } + + private bool ActionMatchesFilterTag(BaseActionPrototype action, string tag) + { + return tag switch + { + AllActionsTag => true, + GrantedActionsTag => _actionsComponent.IsGranted(action), + ItemTag => action is ItemActionPrototype, + NotItemTag => action is ActionPrototype, + InstantActionTag => action.BehaviorType == BehaviorType.Instant, + TargetActionTag => action.BehaviorType == BehaviorType.TargetEntity || + action.BehaviorType == BehaviorType.TargetPoint, + ToggleActionTag => action.BehaviorType == BehaviorType.Toggle, + _ => action.Filters.Contains(tag) + }; + } + + + /// + /// Standardized form is all lowercase, no non-alphanumeric characters (converted to whitespace), + /// trimmed, 1 space max per whitespace gap, + /// and optional spaces between case change + /// + private static string Standardize(string rawText, bool splitOnCaseChange = false) + { + rawText ??= ""; + + // treat non-alphanumeric characters as whitespace + rawText = NonAlphanumeric.Replace(rawText, " "); + + // trim spaces and reduce internal whitespaces to 1 max + rawText = Whitespace.Replace(rawText, " ").Trim(); + if (splitOnCaseChange) + { + // insert a space when case switches from lower to upper + rawText = AddSpaces(rawText, true); + } + + return rawText.ToLowerInvariant().Trim(); + } + + // taken from https://stackoverflow.com/a/272929 (CC BY-SA 3.0) + private static string AddSpaces(string text, bool preserveAcronyms) + { + if (string.IsNullOrWhiteSpace(text)) + return string.Empty; + var newText = new StringBuilder(text.Length * 2); + newText.Append(text[0]); + for (var i = 1; i < text.Length; i++) + { + if (char.IsUpper(text[i])) + { + if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) || + (preserveAcronyms && char.IsUpper(text[i - 1]) && + i < text.Length - 1 && !char.IsUpper(text[i + 1]))) + newText.Append(' '); + } + + newText.Append(text[i]); + } + return newText.ToString(); + } + + private void PopulateActions(IEnumerable actions) + { + ClearList(); + + _actionList = actions.ToArray(); + foreach (var action in _actionList.OrderBy(act => act.Name.ToString())) + { + var actionItem = new ActionMenuItem(action); + _resultsGrid.Children.Add(actionItem); + actionItem.SetActionState(_actionsComponent.IsGranted(action)); + + actionItem.OnButtonDown += OnItemButtonDown; + actionItem.OnButtonUp += OnItemButtonUp; + actionItem.OnPressed += OnItemPressed; + } + } + + private void ClearList() + { + // TODO: Not sure if this unsub is needed if children are all being cleared + foreach (var actionItem in _resultsGrid.Children) + { + ((ActionMenuItem) actionItem).OnPressed -= OnItemPressed; + } + _resultsGrid.Children.Clear(); + _actionList = EmptyActionList; + } + + /// + /// Should be invoked when action states change, ensures + /// currently displayed actions are properly showing their revoked / granted status + /// + public void UpdateUI() + { + foreach (var actionItem in _resultsGrid.Children) + { + var actionMenuItem = ((ActionMenuItem) actionItem); + actionMenuItem.SetActionState(_actionsComponent.IsGranted(actionMenuItem.Action)); + } + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.Update(args); + _dragDropHelper.Update(args.DeltaSeconds); + } + } +} diff --git a/Content.Client/UserInterface/ActionMenuItem.cs b/Content.Client/UserInterface/ActionMenuItem.cs new file mode 100644 index 0000000000..d678847c5e --- /dev/null +++ b/Content.Client/UserInterface/ActionMenuItem.cs @@ -0,0 +1,68 @@ +#nullable enable + +using Content.Client.UserInterface.Stylesheets; +using Content.Shared.Actions; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.Utility; + +namespace Content.Client.UserInterface +{ + /// + /// An individual action visible in the action menu. + /// + public class ActionMenuItem : ContainerButton + { + // shorter than default tooltip delay so user can + // quickly explore what each action is + private const float CustomTooltipDelay = 0.2f; + + public BaseActionPrototype Action { get; private set; } + + public ActionMenuItem(BaseActionPrototype action) + { + Action = action; + + CustomMinimumSize = (64, 64); + SizeFlagsVertical = SizeFlags.None; + + AddChild(new TextureRect + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + Stretch = TextureRect.StretchMode.Scale, + Texture = action.Icon.Frame0() + }); + + TooltipDelay = CustomTooltipDelay; + TooltipSupplier = SupplyTooltip; + } + + private Control SupplyTooltip(Control? sender) + { + return new ActionAlertTooltip(Action.Name, Action.Description, Action.Requires); + } + + /// + /// Change how this is displayed depending on if it is granted or revoked + /// + public void SetActionState(bool granted) + { + if (granted) + { + if (HasStyleClass(StyleNano.StyleClassActionMenuItemRevoked)) + { + RemoveStyleClass(StyleNano.StyleClassActionMenuItemRevoked); + } + } + else + { + if (!HasStyleClass(StyleNano.StyleClassActionMenuItemRevoked)) + { + AddStyleClass(StyleNano.StyleClassActionMenuItemRevoked); + } + } + } + + } +} diff --git a/Content.Client/UserInterface/ActionsUI.cs b/Content.Client/UserInterface/ActionsUI.cs new file mode 100644 index 0000000000..7f5698a16d --- /dev/null +++ b/Content.Client/UserInterface/ActionsUI.cs @@ -0,0 +1,556 @@ +#nullable enable +using System.Collections.Generic; +using Content.Client.GameObjects.Components.Mobs; +using Content.Client.GameObjects.Components.Mobs.Actions; +using Content.Client.UserInterface.Controls; +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.Actions; +using Robust.Client.Graphics; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.Utility; +using Robust.Shared.GameObjects; +using Robust.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Timing; + +namespace Content.Client.UserInterface +{ + /// + /// The action hotbar on the left side of the screen. + /// + public sealed class ActionsUI : Container + { + private readonly ClientActionsComponent _actionsComponent; + private readonly ActionManager _actionManager; + private readonly IEntityManager _entityManager; + private readonly IGameTiming _gameTiming; + + private readonly ActionSlot[] _slots; + + private readonly GridContainer _slotContainer; + + private readonly TextureButton _lockButton; + private readonly TextureButton _settingsButton; + private readonly TextureButton _previousHotbarButton; + private readonly Label _loadoutNumber; + private readonly TextureButton _nextHotbarButton; + private readonly Texture _lockTexture; + private readonly Texture _unlockTexture; + + private readonly TextureRect _dragShadow; + + private readonly ActionMenu _menu; + + /// + /// Index of currently selected hotbar + /// + public byte SelectedHotbar { get; private set; } + + /// + /// Action slot we are currently selecting a target for. + /// + public ActionSlot? SelectingTargetFor { get; private set; } + + /// + /// Drag drop helper for coordinating drag drops between action slots + /// + public DragDropHelper DragDropHelper { get; } + + /// + /// Whether the bar is currently locked by the user. This is intended to prevent drag / drop + /// and right click clearing slots. Anything else is still doable. + /// + public bool Locked { get; private set; } + + /// + /// All the action slots in order. + /// + public IEnumerable Slots => _slots; + + public ActionsUI(ClientActionsComponent actionsComponent) + { + _actionsComponent = actionsComponent; + _actionManager = IoCManager.Resolve(); + _entityManager = IoCManager.Resolve(); + _gameTiming = IoCManager.Resolve(); + _menu = new ActionMenu(_actionsComponent, this); + LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.End); + LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End); + LayoutContainer.SetAnchorTop(this, 0f); + LayoutContainer.SetAnchorBottom(this, 0.8f); + LayoutContainer.SetMarginLeft(this, 10); + LayoutContainer.SetMarginTop(this, 100); + + SizeFlagsHorizontal = SizeFlags.None; + SizeFlagsVertical = SizeFlags.FillExpand; + + var resourceCache = IoCManager.Resolve(); + + // everything needs to go within an inner panel container so the panel resizes to fit the elements. + // Because ActionsUI is being anchored by layoutcontainer, the hotbar backing would appear too tall + // if ActionsUI was the panel container + + var panelContainer = new PanelContainer() + { + StyleClasses = {StyleNano.StyleClassHotbarPanel}, + SizeFlagsHorizontal = SizeFlags.None, + SizeFlagsVertical = SizeFlags.None + }; + AddChild(panelContainer); + + var hotbarContainer = new VBoxContainer + { + SeparationOverride = 3, + SizeFlagsHorizontal = SizeFlags.None + }; + panelContainer.AddChild(hotbarContainer); + + var settingsContainer = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand + }; + hotbarContainer.AddChild(settingsContainer); + + settingsContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); + _lockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock.svg.png"); + _unlockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock_open.svg.png"); + _lockButton = new TextureButton + { + TextureNormal = _unlockTexture, + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsStretchRatio = 1 + }; + settingsContainer.AddChild(_lockButton); + settingsContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 2 }); + _settingsButton = new TextureButton + { + TextureNormal = resourceCache.GetTexture("/Textures/Interface/Nano/gear.svg.png"), + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsStretchRatio = 1 + }; + settingsContainer.AddChild(_settingsButton); + settingsContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); + + // this allows a 2 column layout if window gets too small + _slotContainer = new GridContainer + { + MaxHeight = CalcMaxHeight() + }; + hotbarContainer.AddChild(_slotContainer); + + var loadoutContainer = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand + }; + hotbarContainer.AddChild(loadoutContainer); + + loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); + _previousHotbarButton = new TextureButton + { + TextureNormal = resourceCache.GetTexture("/Textures/Interface/Nano/left_arrow.svg.png"), + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsStretchRatio = 1 + }; + loadoutContainer.AddChild(_previousHotbarButton); + loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 2 }); + _loadoutNumber = new Label + { + Text = "1", + SizeFlagsStretchRatio = 1 + }; + loadoutContainer.AddChild(_loadoutNumber); + loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 2 }); + _nextHotbarButton = new TextureButton + { + TextureNormal = resourceCache.GetTexture("/Textures/Interface/Nano/right_arrow.svg.png"), + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsStretchRatio = 1 + }; + loadoutContainer.AddChild(_nextHotbarButton); + loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); + + _slots = new ActionSlot[ClientActionsComponent.Slots]; + + _dragShadow = new TextureRect + { + CustomMinimumSize = (64, 64), + Stretch = TextureRect.StretchMode.Scale, + Visible = false + }; + UserInterfaceManager.PopupRoot.AddChild(_dragShadow); + LayoutContainer.SetSize(_dragShadow, (64, 64)); + + for (byte i = 0; i < ClientActionsComponent.Slots; i++) + { + var slot = new ActionSlot(this, actionsComponent, i); + _slotContainer.AddChild(slot); + _slots[i] = slot; + } + + DragDropHelper = new DragDropHelper(OnBeginActionDrag, OnContinueActionDrag, OnEndActionDrag); + } + + protected override void EnteredTree() + { + base.EnteredTree(); + _lockButton.OnPressed += OnLockPressed; + _nextHotbarButton.OnPressed += NextHotbar; + _previousHotbarButton.OnPressed += PreviousHotbar; + _settingsButton.OnPressed += OnToggleActionsMenu; + } + + protected override void ExitedTree() + { + base.ExitedTree(); + StopTargeting(); + _menu.Close(); + _lockButton.OnPressed -= OnLockPressed; + _nextHotbarButton.OnPressed -= NextHotbar; + _previousHotbarButton.OnPressed -= PreviousHotbar; + _settingsButton.OnPressed -= OnToggleActionsMenu; + } + + protected override Vector2 CalculateMinimumSize() + { + // allows us to shrink down to a 2-column layout minimum + return (10, 400); + } + + protected override void Resized() + { + base.Resized(); + _slotContainer.MaxHeight = CalcMaxHeight(); + } + + private float CalcMaxHeight() + { + // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done, + // this is here because there isn't currently a good way to allow the grid to adjust its height based + // on constraints, otherwise we would use anchors to lay it out + + // it looks bad to have an uneven number of slots in the columns, + // so we either do a single column or 2 equal sized columns + if (Height < 650) + { + // 2 column + return 400; + } + else + { + // 1 column + return 900; + } + } + + protected override void UIScaleChanged() + { + _slotContainer.MaxHeight = CalcMaxHeight(); + base.UIScaleChanged(); + } + + /// + /// Refresh the display of all the slots in the currently displayed hotbar, + /// to reflect the current component state and assignments of actions component. + /// + public void UpdateUI() + { + _menu.UpdateUI(); + + foreach (var actionSlot in Slots) + { + var assignedActionType = _actionsComponent.Assignments[SelectedHotbar, actionSlot.SlotIndex]; + if (!assignedActionType.HasValue) + { + actionSlot.Clear(); + continue; + } + + if (assignedActionType.Value.TryGetAction(out var actionType)) + { + UpdateActionSlot(actionType, actionSlot, assignedActionType); + } + else if (assignedActionType.Value.TryGetItemActionWithoutItem(out var itemlessActionType)) + { + UpdateActionSlot(itemlessActionType, actionSlot, assignedActionType); + } + else if (assignedActionType.Value.TryGetItemActionWithItem(out var itemActionType, out var item)) + { + UpdateActionSlot(item, itemActionType, actionSlot, assignedActionType); + } + else + { + Logger.ErrorS("action", "unexpected Assignment type {0}", + assignedActionType.Value.Assignment); + actionSlot.Clear(); + } + } + } + + private void UpdateActionSlot(ActionType actionType, ActionSlot actionSlot, ActionAssignment? assignedActionType) + { + if (_actionManager.TryGet(actionType, out var action)) + { + actionSlot.Assign(action, true); + } + else + { + Logger.ErrorS("action", "unrecognized actionType {0}", assignedActionType); + actionSlot.Clear(); + return; + } + + if (!_actionsComponent.TryGetActionState(actionType, out var actionState) || !actionState.Enabled) + { + // action is currently disabled + + // just revoked an action we were trying to target with, stop targeting + if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action) + { + StopTargeting(); + } + + actionSlot.DisableAction(); + actionSlot.Cooldown = null; + } + else + { + // action is currently granted + actionSlot.EnableAction(); + actionSlot.Cooldown = actionState.Cooldown; + + // if we are targeting with an action now on cooldown, stop targeting + if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action && + actionState.IsOnCooldown(_gameTiming)) + { + StopTargeting(); + } + } + + // check if we need to toggle it + if (action.BehaviorType == BehaviorType.Toggle) + { + actionSlot.ToggledOn = actionState.ToggledOn; + } + } + + private void UpdateActionSlot(ItemActionType itemlessActionType, ActionSlot actionSlot, + ActionAssignment? assignedActionType) + { + if (_actionManager.TryGet(itemlessActionType, out var action)) + { + actionSlot.Assign(action); + } + else + { + Logger.ErrorS("action", "unrecognized actionType {0}", assignedActionType); + actionSlot.Clear(); + } + actionSlot.Cooldown = null; + } + + private void UpdateActionSlot(EntityUid item, ItemActionType itemActionType, ActionSlot actionSlot, + ActionAssignment? assignedActionType) + { + if (!_entityManager.TryGetEntity(item, out var itemEntity)) return; + if (_actionManager.TryGet(itemActionType, out var action)) + { + actionSlot.Assign(action, itemEntity, true); + } + else + { + Logger.ErrorS("action", "unrecognized actionType {0}", assignedActionType); + actionSlot.Clear(); + return; + } + + if (!_actionsComponent.TryGetItemActionState(itemActionType, item, out var actionState)) + { + // action is no longer tied to an item, this should never happen as we + // check this at the start of this method. But just to be safe + // we will restore our assignment here to the correct state + Logger.ErrorS("action", "coding error, expected actionType {0} to have" + + " a state but it didn't", assignedActionType); + _actionsComponent.Assignments.AssignSlot(SelectedHotbar, actionSlot.SlotIndex, + ActionAssignment.For(itemActionType)); + actionSlot.Assign(action); + return; + } + + if (!actionState.Enabled) + { + // just disabled an action we were trying to target with, stop targeting + if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action) + { + StopTargeting(); + } + + actionSlot.DisableAction(); + } + else + { + // action is currently granted + actionSlot.EnableAction(); + + // if we are targeting with an action now on cooldown, stop targeting + if (SelectingTargetFor?.Action != null && SelectingTargetFor.Action == action && + SelectingTargetFor.Item == itemEntity && + actionState.IsOnCooldown(_gameTiming)) + { + StopTargeting(); + } + } + actionSlot.Cooldown = actionState.Cooldown; + + // check if we need to toggle it + if (action.BehaviorType == BehaviorType.Toggle) + { + actionSlot.ToggledOn = actionState.ToggledOn; + } + } + + private void NextHotbar(BaseButton.ButtonEventArgs args) + { + ChangeHotbar((byte) ((SelectedHotbar + 1) % ClientActionsComponent.Hotbars)); + } + + private void PreviousHotbar(BaseButton.ButtonEventArgs args) + { + var newBar = SelectedHotbar == 0 ? ClientActionsComponent.Hotbars - 1 : SelectedHotbar - 1; + ChangeHotbar((byte) newBar); + } + + + private void ChangeHotbar(byte hotbar) + { + StopTargeting(); + SelectedHotbar = hotbar; + _loadoutNumber.Text = (hotbar + 1).ToString(); + UpdateUI(); + } + + /// + /// If currently targeting with this slot, stops targeting. + /// If currently targeting with no slot or a different slot, switches to + /// targeting with the specified slot. + /// + /// + public void ToggleTargeting(ActionSlot slot) + { + if (SelectingTargetFor == slot) + { + StopTargeting(); + return; + } + StartTargeting(slot); + } + + /// + /// Puts us in targeting mode, where we need to pick either a target point or entity + /// + private void StartTargeting(ActionSlot actionSlot) + { + // If we were targeting something else we should stop + StopTargeting(); + + SelectingTargetFor = actionSlot; + + // show it as toggled on to indicate we are currently selecting a target for it + if (!actionSlot.ToggledOn) + { + actionSlot.ToggledOn = true; + } + } + + /// + /// Switch out of targeting mode if currently selecting target for an action + /// + public void StopTargeting() + { + if (SelectingTargetFor == null) return; + if (SelectingTargetFor.ToggledOn) + { + SelectingTargetFor.ToggledOn = false; + } + SelectingTargetFor = null; + } + + private void OnToggleActionsMenu(BaseButton.ButtonEventArgs args) + { + ToggleActionsMenu(); + } + + public void ToggleActionsMenu() + { + if (_menu.IsOpen) + { + _menu.Close(); + } + else + { + _menu.OpenCentered(); + } + } + + private void OnLockPressed(BaseButton.ButtonEventArgs obj) + { + Locked = !Locked; + _lockButton.TextureNormal = Locked ? _lockTexture : _unlockTexture; + } + + private bool OnBeginActionDrag() + { + // only initiate the drag if the slot has an action in it + if (Locked || DragDropHelper.Dragged.Action == null) return false; + + _dragShadow.Texture = DragDropHelper.Dragged.Action.Icon.Frame0(); + LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled - (32, 32)); + DragDropHelper.Dragged.CancelPress(); + return true; + } + + private bool OnContinueActionDrag(float frameTime) + { + // stop if there's no action in the slot + if (Locked || DragDropHelper.Dragged.Action == null) return false; + + // keep dragged entity centered under mouse + LayoutContainer.SetPosition(_dragShadow, UserInterfaceManager.MousePositionScaled - (32, 32)); + // we don't set this visible until frameupdate, otherwise it flickers + _dragShadow.Visible = true; + return true; + } + + private void OnEndActionDrag() + { + _dragShadow.Visible = false; + } + + /// + /// Handle keydown / keyup for one of the slots via a keybinding, simulates mousedown/mouseup on it. + /// + /// slot index to to receive the press (0 corresponds to the one labeled 1, 9 corresponds to the one labeled 0) + public void HandleHotbarKeybind(byte slot, PointerInputCmdHandler.PointerInputCmdArgs args) + { + var actionSlot = _slots[slot]; + actionSlot.Depress(args.State == BoundKeyState.Down); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.Update(args); + DragDropHelper.Update(args.DeltaSeconds); + } + } +} diff --git a/Content.Client/UserInterface/AlertsUI.cs b/Content.Client/UserInterface/AlertsUI.cs index f4c7effac9..656f9f18c8 100644 --- a/Content.Client/UserInterface/AlertsUI.cs +++ b/Content.Client/UserInterface/AlertsUI.cs @@ -1,10 +1,8 @@ using System; using Content.Client.UserInterface.Stylesheets; -using Robust.Client.Graphics.Drawing; using Robust.Client.Interfaces.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Maths; namespace Content.Client.UserInterface @@ -16,58 +14,51 @@ namespace Content.Client.UserInterface { public GridContainer Grid { get; } - private readonly IClyde _clyde; - - public AlertsUI(IClyde clyde) + public AlertsUI() { - _clyde = clyde; + LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin); + LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End); + LayoutContainer.SetAnchorTop(this, 0f); + LayoutContainer.SetAnchorRight(this, 1f); + LayoutContainer.SetAnchorBottom(this, 1f); + LayoutContainer.SetMarginBottom(this, -180); + LayoutContainer.SetMarginTop(this, 250); + LayoutContainer.SetMarginRight(this, -10); var panelContainer = new PanelContainer { StyleClasses = {StyleNano.StyleClassTransparentBorderedWindowPanel}, - SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd, + SizeFlagsVertical = SizeFlags.None }; AddChild(panelContainer); Grid = new GridContainer { - MaxHeight = CalcMaxHeight(clyde.ScreenSize), + MaxHeight = 64, ExpandBackwards = true }; panelContainer.AddChild(Grid); - clyde.OnWindowResized += ClydeOnOnWindowResized; - - LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin); - LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10); - LayoutContainer.SetMarginTop(this, 250); } - protected override void UIScaleChanged() - { - Grid.MaxHeight = CalcMaxHeight(_clyde.ScreenSize); - base.UIScaleChanged(); - } - - private void ClydeOnOnWindowResized(WindowResizedEventArgs obj) + protected override void Resized() { // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done, // this is here because there isn't currently a good way to allow the grid to adjust its height based // on constraints, otherwise we would use anchors to lay it out - Grid.MaxHeight = CalcMaxHeight(obj.NewSize);; + base.Resized(); + Grid.MaxHeight = Height; } - private float CalcMaxHeight(Vector2i screenSize) + protected override Vector2 CalculateMinimumSize() { - return Math.Max(((screenSize.Y) / UIScale) - 420, 1); + // allows us to shrink down to a single row + return (64, 64); } - protected override void Dispose(bool disposing) + protected override void UIScaleChanged() { - base.Dispose(disposing); - - if (disposing) - { - _clyde.OnWindowResized -= ClydeOnOnWindowResized; - } + Grid.MaxHeight = Height; + base.UIScaleChanged(); } } } diff --git a/Content.Client/UserInterface/Controls/ActionSlot.cs b/Content.Client/UserInterface/Controls/ActionSlot.cs new file mode 100644 index 0000000000..0e1f24a86a --- /dev/null +++ b/Content.Client/UserInterface/Controls/ActionSlot.cs @@ -0,0 +1,652 @@ +#nullable enable +using System; +using Content.Client.GameObjects.Components.Mobs; +using Content.Client.UserInterface.Stylesheets; +using Content.Shared.Actions; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Client.Graphics; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.Utility; +using Robust.Shared.Input; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.UserInterface.Controls +{ + /// + /// A slot in the action hotbar. Not extending BaseButton because + /// its needs diverged too much. + /// + public class ActionSlot : PanelContainer + { + // shorter than default tooltip delay so user can more easily + // see what actions they've been given + private const float CustomTooltipDelay = 0.5f; + + private static readonly string EnabledColor = "#7b7e9e"; + private static readonly string DisabledColor = "#950000"; + + /// + /// Current action in this slot. + /// + public BaseActionPrototype? Action { get; private set; } + + /// + /// true if there is an action assigned to the slot + /// + public bool HasAssignment => Action != null; + + private bool HasToggleSprite => Action != null && Action.IconOn != SpriteSpecifier.Invalid; + + /// + /// Only applicable when an action is in this slot. + /// True if the action is currently shown as enabled, false if action disabled. + /// + public bool ActionEnabled { get; private set; } + + /// + /// Is there an action in the slot that can currently be used? + /// + public bool CanUseAction => HasAssignment && ActionEnabled && !IsOnCooldown; + + /// + /// Item the action is provided by, only valid if Action is an ItemActionPrototype. May be null + /// if the item action is not yet tied to an item. + /// + public IEntity? Item { get; private set; } + + /// + /// Whether the action in this slot should be shown as toggled on. Separate from Depressed. + /// + public bool ToggledOn + { + get => _toggledOn; + set + { + if (_toggledOn == value) return; + _toggledOn = value; + UpdateIcons(); + DrawModeChanged(); + } + } + + /// + /// 1-10 corresponding to the number label on the slot (10 is labeled as 0) + /// + private byte SlotNumber => (byte) (SlotIndex + 1); + public byte SlotIndex { get; } + + /// + /// Current cooldown displayed in this slot. Set to null to show no cooldown. + /// + public (TimeSpan Start, TimeSpan End)? Cooldown + { + get => _cooldown; + set + { + _cooldown = value; + if (SuppliedTooltip is ActionAlertTooltip actionAlertTooltip) + { + actionAlertTooltip.Cooldown = value; + } + } + } + private (TimeSpan Start, TimeSpan End)? _cooldown; + + public bool IsOnCooldown => Cooldown.HasValue && _gameTiming.CurTime < Cooldown.Value.End; + + private readonly IGameTiming _gameTiming; + private readonly RichTextLabel _number; + private readonly TextureRect _bigActionIcon; + private readonly TextureRect _smallActionIcon; + private readonly SpriteView _smallItemSpriteView; + private readonly SpriteView _bigItemSpriteView; + private readonly CooldownGraphic _cooldownGraphic; + private readonly ActionsUI _actionsUI; + private readonly ClientActionsComponent _actionsComponent; + private bool _toggledOn; + // whether button is currently pressed down by mouse or keybind down. + private bool _depressed; + private bool _beingHovered; + + /// + /// Creates an action slot for the specified number + /// + /// slot index this corresponds to, 0-9 (0 labeled as 1, 8, labeled "9", 9 labeled as "0". + public ActionSlot(ActionsUI actionsUI, ClientActionsComponent actionsComponent, byte slotIndex) + { + _actionsComponent = actionsComponent; + _actionsUI = actionsUI; + _gameTiming = IoCManager.Resolve(); + SlotIndex = slotIndex; + MouseFilter = MouseFilterMode.Stop; + + CustomMinimumSize = (64, 64); + SizeFlagsVertical = SizeFlags.None; + TooltipDelay = CustomTooltipDelay; + TooltipSupplier = SupplyTooltip; + + _number = new RichTextLabel + { + StyleClasses = {StyleNano.StyleClassHotbarSlotNumber} + }; + _number.SetMessage(SlotNumberLabel()); + + _bigActionIcon = new TextureRect + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + Stretch = TextureRect.StretchMode.Scale, + Visible = false + }; + _bigItemSpriteView = new SpriteView + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + Scale = (2,2), + Visible = false + }; + _smallActionIcon = new TextureRect + { + SizeFlagsHorizontal = SizeFlags.ShrinkEnd, + SizeFlagsVertical = SizeFlags.ShrinkEnd, + Stretch = TextureRect.StretchMode.Scale, + Visible = false + }; + _smallItemSpriteView = new SpriteView + { + SizeFlagsHorizontal = SizeFlags.ShrinkEnd, + SizeFlagsVertical = SizeFlags.ShrinkEnd, + Visible = false + }; + + _cooldownGraphic = new CooldownGraphic {Progress = 0, Visible = false}; + + // padding to the left of the number to shift it right + var paddingBox = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + CustomMinimumSize = (64, 64) + }; + paddingBox.AddChild(new Control() + { + CustomMinimumSize = (4, 4), + SizeFlagsVertical = SizeFlags.Fill + }); + paddingBox.AddChild(_number); + + // padding to the left of the small icon + var paddingBoxItemIcon = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + CustomMinimumSize = (64, 64) + }; + paddingBoxItemIcon.AddChild(new Control() + { + CustomMinimumSize = (32, 32), + SizeFlagsVertical = SizeFlags.Fill + }); + paddingBoxItemIcon.AddChild(new Control + { + Children = + { + _smallActionIcon, + _smallItemSpriteView + } + }); + AddChild(_bigActionIcon); + AddChild(_bigItemSpriteView); + AddChild(_cooldownGraphic); + AddChild(paddingBox); + AddChild(paddingBoxItemIcon); + DrawModeChanged(); + } + + private Control? SupplyTooltip(Control sender) + { + return Action == null ? null : + new ActionAlertTooltip(Action.Name, Action.Description, Action.Requires) {Cooldown = Cooldown}; + } + + /// + /// Action attempt for performing the action in the slot + /// + public IActionAttempt? ActionAttempt() + { + IActionAttempt? attempt = Action switch + { + ActionPrototype actionPrototype => new ActionAttempt(actionPrototype), + ItemActionPrototype itemActionPrototype => + (Item != null && Item.TryGetComponent(out var itemActions)) ? + new ItemActionAttempt(itemActionPrototype, Item, itemActions) : null, + _ => null + }; + return attempt; + } + + protected override void MouseEntered() + { + base.MouseEntered(); + + _beingHovered = true; + DrawModeChanged(); + if (Action is not ItemActionPrototype) return; + if (Item == null) return; + _actionsComponent.HighlightItemSlot(Item); + } + + protected override void MouseExited() + { + base.MouseExited(); + _beingHovered = false; + CancelPress(); + DrawModeChanged(); + _actionsComponent.StopHighlightingItemSlots(); + } + + protected override void KeyBindDown(GUIBoundKeyEventArgs args) + { + base.KeyBindDown(args); + + if (args.Function == EngineKeyFunctions.UIRightClick) + { + if (!_actionsUI.Locked && !_actionsUI.DragDropHelper.IsDragging) + { + _actionsComponent.Assignments.ClearSlot(_actionsUI.SelectedHotbar, SlotIndex, true); + _actionsUI.StopTargeting(); + _actionsUI.UpdateUI(); + } + return; + } + + // only handle clicks, and can't do anything to this if no assignment + if (args.Function != EngineKeyFunctions.UIClick || !HasAssignment) + return; + + // might turn into a drag or a full press if released + Depress(true); + _actionsUI.DragDropHelper.MouseDown(this); + DrawModeChanged(); + } + + protected override void KeyBindUp(GUIBoundKeyEventArgs args) + { + base.KeyBindUp(args); + + if (args.Function != EngineKeyFunctions.UIClick) + return; + + // might be finishing a drag or using the action + if (_actionsUI.DragDropHelper.IsDragging && + _actionsUI.DragDropHelper.Dragged == this && + UserInterfaceManager.CurrentlyHovered is ActionSlot targetSlot && + targetSlot != this) + { + // finish the drag, swap the 2 slots + var fromIdx = SlotIndex; + var fromAssignment = _actionsComponent.Assignments[_actionsUI.SelectedHotbar, fromIdx]; + var toIdx = targetSlot.SlotIndex; + var toAssignment = _actionsComponent.Assignments[_actionsUI.SelectedHotbar, toIdx]; + + if (fromIdx == toIdx) return; + if (!fromAssignment.HasValue) return; + + _actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, toIdx, fromAssignment.Value); + if (toAssignment.HasValue) + { + _actionsComponent.Assignments.AssignSlot(_actionsUI.SelectedHotbar, fromIdx, toAssignment.Value); + } + else + { + _actionsComponent.Assignments.ClearSlot(_actionsUI.SelectedHotbar, fromIdx, false); + } + _actionsUI.UpdateUI(); + } + else + { + // perform the action + if (UserInterfaceManager.CurrentlyHovered == this) + { + Depress(false); + } + } + _actionsUI.DragDropHelper.EndDrag(); + DrawModeChanged(); + } + + /// + /// Cancel current press without triggering the action + /// + public void CancelPress() + { + _depressed = false; + DrawModeChanged(); + } + + /// + /// Press this button down. If it was depressed and now set to not depressed, will + /// trigger the action. Only has an effect if CanUseAction. + /// + public void Depress(bool depress) + { + if (!CanUseAction) return; + + if (_depressed && !depress) + { + // fire the action + // no left-click interaction with it on cooldown or revoked + _actionsComponent.AttemptAction(this); + } + _depressed = depress; + DrawModeChanged(); + } + + /// + /// Updates the action assigned to this slot. + /// + /// action to assign + /// whether action should initially appear enable or disabled + public void Assign(ActionPrototype action, bool actionEnabled) + { + // already assigned + if (Action != null && Action == action) return; + + Action = action; + Item = null; + _depressed = false; + ToggledOn = false; + ActionEnabled = actionEnabled; + Cooldown = null; + HideTooltip(); + UpdateIcons(); + DrawModeChanged(); + _number.SetMessage(SlotNumberLabel()); + } + + /// + /// Updates the item action assigned to this slot. The action will always be shown as disabled + /// until it is tied to a specific item. + /// + /// action to assign + public void Assign(ItemActionPrototype action) + { + // already assigned + if (Action != null && Action == action && Item == null) return; + + Action = action; + Item = null; + _depressed = false; + ToggledOn = false; + ActionEnabled = false; + Cooldown = null; + HideTooltip(); + UpdateIcons(); + DrawModeChanged(); + _number.SetMessage(SlotNumberLabel()); + } + + /// + /// Updates the item action assigned to this slot, tied to a specific item. + /// + /// action to assign + /// item the action is provided by + /// whether action should initially appear enable or disabled + public void Assign(ItemActionPrototype action, IEntity item, bool actionEnabled) + { + // already assigned + if (Action != null && Action == action && Item == item) return; + + Action = action; + Item = item; + _depressed = false; + ToggledOn = false; + ActionEnabled = false; + Cooldown = null; + HideTooltip(); + UpdateIcons(); + DrawModeChanged(); + _number.SetMessage(SlotNumberLabel()); + } + + /// + /// Clears the action assigned to this slot + /// + public void Clear() + { + if (!HasAssignment) return; + Action = null; + Item = null; + ToggledOn = false; + _depressed = false; + Cooldown = null; + HideTooltip(); + UpdateIcons(); + DrawModeChanged(); + _number.SetMessage(SlotNumberLabel()); + } + + /// + /// Display the action in this slot (if there is one) as enabled + /// + public void EnableAction() + { + if (ActionEnabled || !HasAssignment) return; + + ActionEnabled = true; + _depressed = false; + DrawModeChanged(); + _number.SetMessage(SlotNumberLabel()); + } + + /// + /// Display the action in this slot (if there is one) as disabled. + /// The slot is still clickable. + /// + public void DisableAction() + { + if (!ActionEnabled || !HasAssignment) return; + + ActionEnabled = false; + _depressed = false; + DrawModeChanged(); + _number.SetMessage(SlotNumberLabel()); + } + + private FormattedMessage SlotNumberLabel() + { + if (SlotNumber > 10) return FormattedMessage.FromMarkup(""); + var number = Loc.GetString(SlotNumber == 10 ? "0" : SlotNumber.ToString()); + var color = (ActionEnabled || !HasAssignment) ? EnabledColor : DisabledColor; + return FormattedMessage.FromMarkup("[color=" + color + "]" + number + "[/color]"); + } + + private void UpdateIcons() + { + if (!HasAssignment) + { + SetActionIcon(null); + SetItemIcon(null); + return; + } + + if (HasToggleSprite && ToggledOn && Action != null) + { + SetActionIcon(Action.IconOn.Frame0()); + } + else if (Action != null) + { + SetActionIcon(Action.Icon.Frame0()); + } + + if (Item != null) + { + SetItemIcon(Item.TryGetComponent(out var spriteComponent) ? spriteComponent : null); + } + else + { + SetItemIcon(null); + } + } + + private void SetActionIcon(Texture? texture) + { + if (texture == null || !HasAssignment) + { + _bigActionIcon.Texture = null; + _bigActionIcon.Visible = false; + _smallActionIcon.Texture = null; + _smallActionIcon.Visible = false; + } + else + { + if (Action is ItemActionPrototype {IconStyle: ItemActionIconStyle.BigItem}) + { + _bigActionIcon.Texture = null; + _bigActionIcon.Visible = false; + _smallActionIcon.Texture = texture; + _smallActionIcon.Visible = true; + } + else + { + _bigActionIcon.Texture = texture; + _bigActionIcon.Visible = true; + _smallActionIcon.Texture = null; + _smallActionIcon.Visible = false; + } + + } + } + + private void SetItemIcon(ISpriteComponent? sprite) + { + if (sprite == null || !HasAssignment) + { + _bigItemSpriteView.Visible = false; + _bigItemSpriteView.Sprite = null; + _smallItemSpriteView.Visible = false; + _smallItemSpriteView.Sprite = null; + } + else + { + if (Action is ItemActionPrototype actionPrototype) + { + switch (actionPrototype.IconStyle) + { + case ItemActionIconStyle.BigItem: + { + _bigItemSpriteView.Visible = true; + _bigItemSpriteView.Sprite = sprite; + _smallItemSpriteView.Visible = false; + _smallItemSpriteView.Sprite = null; + break; + } + case ItemActionIconStyle.BigAction: + { + _bigItemSpriteView.Visible = false; + _bigItemSpriteView.Sprite = null; + _smallItemSpriteView.Visible = true; + _smallItemSpriteView.Sprite = sprite; + break; + } + case ItemActionIconStyle.NoItem: + { + _bigItemSpriteView.Visible = false; + _bigItemSpriteView.Sprite = null; + _smallItemSpriteView.Visible = false; + _smallItemSpriteView.Sprite = null; + break; + } + } + + } + else + { + _bigItemSpriteView.Visible = false; + _bigItemSpriteView.Sprite = null; + _smallItemSpriteView.Visible = false; + _smallItemSpriteView.Sprite = null; + } + + } + } + + + private void DrawModeChanged() + { + // always show the normal empty button style if no action in this slot + if (!HasAssignment) + { + SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal); + return; + } + + // it's only depress-able if it's usable, so if we're depressed + // show the depressed style + if (_depressed) + { + SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassPressed); + return; + } + + // show a hover only if the action is usable + if (_beingHovered) + { + if (ActionEnabled && !IsOnCooldown) + { + SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover); + return; + } + } + + // if it's toggled on, always show the toggled on style (currently same as depressed style) + if (ToggledOn) + { + // when there's a toggle sprite, we're showing that sprite instead of highlighting this slot + SetOnlyStylePseudoClass(HasToggleSprite ? ContainerButton.StylePseudoClassNormal : + ContainerButton.StylePseudoClassPressed); + return; + } + + + if (!ActionEnabled) + { + SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled); + return; + } + + + SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal); + } + + + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + if (!Cooldown.HasValue) + { + _cooldownGraphic.Visible = false; + _cooldownGraphic.Progress = 0; + return; + } + + var duration = Cooldown.Value.End - Cooldown.Value.Start; + var curTime = _gameTiming.CurTime; + var length = duration.TotalSeconds; + var progress = (curTime - Cooldown.Value.Start).TotalSeconds / length; + var ratio = (progress <= 1 ? (1 - progress) : (curTime - Cooldown.Value.End).TotalSeconds * -5); + + _cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1); + _cooldownGraphic.Visible = ratio > -1f; + } + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/AlertControl.cs b/Content.Client/UserInterface/Controls/AlertControl.cs similarity index 53% rename from Content.Client/GameObjects/Components/Mobs/AlertControl.cs rename to Content.Client/UserInterface/Controls/AlertControl.cs index b723550d33..b7925e7b38 100644 --- a/Content.Client/GameObjects/Components/Mobs/AlertControl.cs +++ b/Content.Client/UserInterface/Controls/AlertControl.cs @@ -1,30 +1,48 @@ #nullable enable using System; -using Content.Client.UserInterface; using Content.Client.Utility; using Content.Shared.Alert; using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Timing; -namespace Content.Client.GameObjects.Components.Mobs +namespace Content.Client.UserInterface.Controls { public class AlertControl : BaseButton { + // shorter than default tooltip delay so user can more easily + // see what alerts they have + private const float CustomTooltipDelay = 0.5f; + public AlertPrototype Alert { get; } /// - /// Total duration of the cooldown in seconds. Null if no duration / cooldown. + /// Current cooldown displayed in this slot. Set to null to show no cooldown. /// - public int? TotalDuration { get; set; } + public (TimeSpan Start, TimeSpan End)? Cooldown + { + get => _cooldown; + set + { + _cooldown = value; + if (SuppliedTooltip is ActionAlertTooltip actionAlertTooltip) + { + actionAlertTooltip.Cooldown = value; + } + } + } + private (TimeSpan Start, TimeSpan End)? _cooldown; private short? _severity; + private readonly IGameTiming _gameTiming; private readonly TextureRect _icon; private readonly CooldownGraphic _cooldownGraphic; - private readonly IResourceCache _resourceCache; - /// /// Creates an alert control reflecting the indicated alert + state /// @@ -33,6 +51,9 @@ namespace Content.Client.GameObjects.Components.Mobs /// resourceCache to use to load alert icon textures public AlertControl(AlertPrototype alert, short? severity, IResourceCache resourceCache) { + _gameTiming = IoCManager.Resolve(); + TooltipDelay = CustomTooltipDelay; + TooltipSupplier = SupplyTooltip; _resourceCache = resourceCache; Alert = alert; _severity = severity; @@ -49,6 +70,11 @@ namespace Content.Client.GameObjects.Components.Mobs } + private Control SupplyTooltip(Control? sender) + { + return new ActionAlertTooltip(Alert.Name, Alert.Description) {Cooldown = Cooldown}; + } + /// /// Change the alert severity, changing the displayed icon /// @@ -61,33 +87,24 @@ namespace Content.Client.GameObjects.Components.Mobs } } - /// - /// Updates the displayed cooldown amount, doing nothing if alertCooldown is null - /// - /// cooldown start and end - /// current game time - public void UpdateCooldown((TimeSpan Start, TimeSpan End)? alertCooldown, in TimeSpan curTime) + protected override void FrameUpdate(FrameEventArgs args) { - if (!alertCooldown.HasValue) + base.FrameUpdate(args); + if (!Cooldown.HasValue) { - _cooldownGraphic.Progress = 0; _cooldownGraphic.Visible = false; - TotalDuration = null; + _cooldownGraphic.Progress = 0; + return; } - else - { - var start = alertCooldown.Value.Start; - var end = alertCooldown.Value.End; + var duration = Cooldown.Value.End - Cooldown.Value.Start; + var curTime = _gameTiming.CurTime; + var length = duration.TotalSeconds; + var progress = (curTime - Cooldown.Value.Start).TotalSeconds / length; + var ratio = (progress <= 1 ? (1 - progress) : (curTime - Cooldown.Value.End).TotalSeconds * -5); - var length = (end - start).TotalSeconds; - var progress = (curTime - start).TotalSeconds / length; - var ratio = (progress <= 1 ? (1 - progress) : (curTime - end).TotalSeconds * -5); - - TotalDuration = (int?) Math.Round(length); - _cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1); - _cooldownGraphic.Visible = ratio > -1f; - } + _cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1); + _cooldownGraphic.Visible = ratio > -1f; } } } diff --git a/Content.Client/UserInterface/ItemSlotButton.cs b/Content.Client/UserInterface/ItemSlotButton.cs index 24ed144d4c..7715c565d9 100644 --- a/Content.Client/UserInterface/ItemSlotButton.cs +++ b/Content.Client/UserInterface/ItemSlotButton.cs @@ -1,14 +1,19 @@ using System; using Robust.Client.Graphics; +using Robust.Client.Graphics.Shaders; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; +using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface { public class ItemSlotButton : MarginContainer { + private const string HighlightShader = "SelectionOutlineInrange"; + public TextureRect Button { get; } public SpriteView SpriteView { get; } public SpriteView HoverSpriteView { get; } @@ -21,9 +26,11 @@ namespace Content.Client.UserInterface public bool EntityHover => HoverSpriteView.Sprite != null; public bool MouseIsHovering = false; + private readonly ShaderInstance _highlightShader; public ItemSlotButton(Texture texture, Texture storageTexture) { + _highlightShader = IoCManager.Resolve().Index(HighlightShader).Instance(); CustomMinimumSize = (64, 64); AddChild(Button = new TextureRect @@ -95,6 +102,20 @@ namespace Content.Client.UserInterface } } + public void Highlight(bool on) + { + // I make no claim that this actually looks good but it's a start. + if (on) + { + Button.ShaderOverride = _highlightShader; + } + else + { + Button.ShaderOverride = null; + } + + } + private void OnButtonPressed(GUIBoundKeyEventArgs args) { OnPressed?.Invoke(args); diff --git a/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs b/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs index ee4f6b1172..c208e42536 100644 --- a/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs +++ b/Content.Client/UserInterface/OptionsMenu.KeyRebind.cs @@ -150,7 +150,6 @@ namespace Content.Client.UserInterface AddButton(ContentKeyFunctions.ReleasePulledObject, "Release pulled object"); AddButton(ContentKeyFunctions.Point, "Point at location"); - AddHeader("User Interface"); AddButton(ContentKeyFunctions.FocusChat, "Focus chat"); AddButton(ContentKeyFunctions.FocusOOC, "Focus chat (OOC)"); @@ -160,6 +159,7 @@ namespace Content.Client.UserInterface AddButton(ContentKeyFunctions.OpenCraftingMenu, "Open crafting menu"); AddButton(ContentKeyFunctions.OpenInventoryMenu, "Open inventory"); AddButton(ContentKeyFunctions.OpenTutorial, "Open tutorial"); + AddButton(ContentKeyFunctions.OpenActionsMenu, "Open action menu"); AddButton(ContentKeyFunctions.OpenEntitySpawnWindow, "Open entity spawn menu"); AddButton(ContentKeyFunctions.OpenSandboxWindow, "Open sandbox menu"); AddButton(ContentKeyFunctions.OpenTileSpawnWindow, "Open tile spawn menu"); @@ -169,6 +169,18 @@ namespace Content.Client.UserInterface AddButton(ContentKeyFunctions.TakeScreenshot, "Take screenshot"); AddButton(ContentKeyFunctions.TakeScreenshotNoUI, "Take screenshot (without UI)"); + AddHeader("Hotbar"); + AddButton(ContentKeyFunctions.Hotbar1, "Hotbar slot 1"); + AddButton(ContentKeyFunctions.Hotbar2, "Hotbar slot 2"); + AddButton(ContentKeyFunctions.Hotbar3, "Hotbar slot 3"); + AddButton(ContentKeyFunctions.Hotbar4, "Hotbar slot 4"); + AddButton(ContentKeyFunctions.Hotbar5, "Hotbar slot 5"); + AddButton(ContentKeyFunctions.Hotbar6, "Hotbar slot 6"); + AddButton(ContentKeyFunctions.Hotbar7, "Hotbar slot 7"); + AddButton(ContentKeyFunctions.Hotbar8, "Hotbar slot 8"); + AddButton(ContentKeyFunctions.Hotbar9, "Hotbar slot 9"); + AddButton(ContentKeyFunctions.Hotbar0, "Hotbar slot 0"); + AddHeader("Map Editor"); AddButton(EngineKeyFunctions.EditorPlaceObject, "Place object"); AddButton(EngineKeyFunctions.EditorCancelPlace, "Cancel placement"); diff --git a/Content.Client/UserInterface/Stylesheets/StyleNano.cs b/Content.Client/UserInterface/Stylesheets/StyleNano.cs index a29b687b72..734946b907 100644 --- a/Content.Client/UserInterface/Stylesheets/StyleNano.cs +++ b/Content.Client/UserInterface/Stylesheets/StyleNano.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Client.GameObjects.EntitySystems; +using Content.Client.UserInterface.Controls; using Content.Client.Utility; using Robust.Client.Graphics.Drawing; using Robust.Client.Interfaces.ResourceManagement; @@ -15,10 +16,19 @@ namespace Content.Client.UserInterface.Stylesheets { public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel"; public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel"; + public const string StyleClassHotbarPanel = "HotbarPanel"; public const string StyleClassTooltipPanel = "tooltipBox"; public const string StyleClassTooltipAlertTitle = "tooltipAlertTitle"; public const string StyleClassTooltipAlertDescription = "tooltipAlertDesc"; public const string StyleClassTooltipAlertCooldown = "tooltipAlertCooldown"; + public const string StyleClassTooltipActionTitle = "tooltipActionTitle"; + public const string StyleClassTooltipActionDescription = "tooltipActionDesc"; + public const string StyleClassTooltipActionCooldown = "tooltipActionCooldown"; + public const string StyleClassTooltipActionRequirements = "tooltipActionCooldown"; + public const string StyleClassHotbarSlotNumber = "hotbarSlotNumber"; + public const string StyleClassActionSearchBox = "actionSearchBox"; + public const string StyleClassActionMenuItemRevoked = "actionMenuItemRevoked"; + public const string StyleClassSliderRed = "Red"; public const string StyleClassSliderGreen = "Green"; @@ -60,6 +70,8 @@ namespace Content.Client.UserInterface.Stylesheets var notoSansItalic12 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Italic.ttf", 12); var notoSansBold12 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 12); var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14); + var notoSansDisplayBold16 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 16); + var notoSans15 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 15); var notoSans16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 16); var notoSansBold16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 16); var notoSansBold18 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 18); @@ -95,6 +107,61 @@ namespace Content.Client.UserInterface.Stylesheets }; borderedTransparentWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2); + var hotbarBackground = new StyleBoxTexture + { + Texture = borderedWindowBackgroundTex, + }; + hotbarBackground.SetPatchMargin(StyleBox.Margin.All, 2); + hotbarBackground.SetExpandMargin(StyleBox.Margin.All, 4); + + var buttonRectTex = resCache.GetTexture("/Textures/Interface/Nano/light_panel_background_bordered.png"); + var buttonRect = new StyleBoxTexture(BaseButton) + { + Texture = buttonRectTex + }; + buttonRect.SetPatchMargin(StyleBox.Margin.All, 2); + buttonRect.SetPadding(StyleBox.Margin.All, 2); + buttonRect.SetContentMarginOverride(StyleBox.Margin.Vertical, 2); + buttonRect.SetContentMarginOverride(StyleBox.Margin.Horizontal, 2); + + var buttonRectHover = new StyleBoxTexture(buttonRect) + { + Modulate = ButtonColorHovered + }; + + var buttonRectPressed = new StyleBoxTexture(buttonRect) + { + Modulate = ButtonColorPressed + }; + + var buttonRectDisabled = new StyleBoxTexture(buttonRect) + { + Modulate = ButtonColorDisabled + }; + + var buttonRectActionMenuItemTex = resCache.GetTexture("/Textures/Interface/Nano/black_panel_light_thin_border.png"); + var buttonRectActionMenuRevokedItemTex = resCache.GetTexture("/Textures/Interface/Nano/black_panel_red_thin_border.png"); + var buttonRectActionMenuItem = new StyleBoxTexture(BaseButton) + { + Texture = buttonRectActionMenuItemTex + }; + buttonRectActionMenuItem.SetPatchMargin(StyleBox.Margin.All, 2); + buttonRectActionMenuItem.SetPadding(StyleBox.Margin.All, 2); + buttonRectActionMenuItem.SetContentMarginOverride(StyleBox.Margin.Vertical, 2); + buttonRectActionMenuItem.SetContentMarginOverride(StyleBox.Margin.Horizontal, 2); + var buttonRectActionMenuItemRevoked = new StyleBoxTexture(buttonRectActionMenuItem) + { + Texture = buttonRectActionMenuRevokedItemTex + }; + var buttonRectActionMenuItemHover = new StyleBoxTexture(buttonRectActionMenuItem) + { + Modulate = ButtonColorHovered + }; + var buttonRectActionMenuItemPressed = new StyleBoxTexture(buttonRectActionMenuItem) + { + Modulate = ButtonColorPressed + }; + var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png"); var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png"); @@ -105,6 +172,14 @@ namespace Content.Client.UserInterface.Stylesheets lineEdit.SetPatchMargin(StyleBox.Margin.All, 3); lineEdit.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5); + var actionSearchBoxTex = resCache.GetTexture("/Textures/Interface/Nano/black_panel_dark_thin_border.png"); + var actionSearchBox = new StyleBoxTexture + { + Texture = actionSearchBoxTex, + }; + actionSearchBox.SetPatchMargin(StyleBox.Margin.All, 3); + actionSearchBox.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5); + var tabContainerPanelTex = resCache.GetTexture("/Textures/Interface/Nano/tabcontainer_panel.png"); var tabContainerPanel = new StyleBoxTexture { @@ -280,6 +355,12 @@ namespace Content.Client.UserInterface.Stylesheets { new StyleProperty(PanelContainer.StylePropertyPanel, borderedTransparentWindowBackground), }), + // Hotbar background + new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassHotbarPanel}, null, null), + new[] + { + new StyleProperty(PanelContainer.StylePropertyPanel, hotbarBackground), + }), // Window header. new StyleRule( new SelectorElement(typeof(PanelContainer), new[] {SS14Window.StyleClassWindowHeader}, null, null), @@ -376,6 +457,43 @@ namespace Content.Client.UserInterface.Stylesheets new StyleProperty("font-color", Color.FromHex("#E5E5E581")), }), + // action slot hotbar buttons + new StyleRule(new SelectorElement(typeof(ActionSlot), null, null, new[] {ContainerButton.StylePseudoClassNormal}), new[] + { + new StyleProperty(PanelContainer.StylePropertyPanel, buttonRect), + }), + new StyleRule(new SelectorElement(typeof(ActionSlot), null, null, new[] {ContainerButton.StylePseudoClassHover}), new[] + { + new StyleProperty(PanelContainer.StylePropertyPanel, buttonRectHover), + }), + new StyleRule(new SelectorElement(typeof(ActionSlot), null, null, new[] {ContainerButton.StylePseudoClassPressed}), new[] + { + new StyleProperty(PanelContainer.StylePropertyPanel, buttonRectPressed), + }), + new StyleRule(new SelectorElement(typeof(ActionSlot), null, null, new[] {ContainerButton.StylePseudoClassDisabled}), new[] + { + new StyleProperty(PanelContainer.StylePropertyPanel, buttonRectDisabled), + }), + + // action menu item buttons + new StyleRule(new SelectorElement(typeof(ActionMenuItem), null, null, new[] {ContainerButton.StylePseudoClassNormal}), new[] + { + new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonRectActionMenuItem), + }), + // we don't actually disable the action menu items, only change their style based on the underlying action being revoked + new StyleRule(new SelectorElement(typeof(ActionMenuItem), new [] {StyleClassActionMenuItemRevoked}, null, new[] {ContainerButton.StylePseudoClassNormal}), new[] + { + new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonRectActionMenuItemRevoked), + }), + new StyleRule(new SelectorElement(typeof(ActionMenuItem), null, null, new[] {ContainerButton.StylePseudoClassHover}), new[] + { + new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonRectActionMenuItemHover), + }), + new StyleRule(new SelectorElement(typeof(ActionMenuItem), null, null, new[] {ContainerButton.StylePseudoClassPressed}), new[] + { + new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonRectActionMenuItemPressed), + }), + // Main menu: Make those buttons bigger. new StyleRule(new SelectorChild( new SelectorElement(typeof(Button), null, "mainMenu", null), @@ -413,6 +531,13 @@ namespace Content.Client.UserInterface.Stylesheets new StyleProperty("font-color", Color.Gray), }), + // Action searchbox lineedit + new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassActionSearchBox}, null, null), + new[] + { + new StyleProperty(LineEdit.StylePropertyStyleBox, actionSearchBox), + }), + // TabContainer new StyleRule(new SelectorElement(typeof(TabContainer), null, null, null), new[] @@ -531,6 +656,30 @@ namespace Content.Client.UserInterface.Stylesheets new StyleProperty("font", notoSans16) }), + // action tooltip + new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipActionTitle}, null, null), new[] + { + new StyleProperty("font", notoSansBold16) + }), + new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipActionDescription}, null, null), new[] + { + new StyleProperty("font", notoSans15) + }), + new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipActionCooldown}, null, null), new[] + { + new StyleProperty("font", notoSans15) + }), + new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipActionRequirements}, null, null), new[] + { + new StyleProperty("font", notoSans15) + }), + + // hotbar slot + new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassHotbarSlotNumber}, null, null), new[] + { + new StyleProperty("font", notoSansDisplayBold16) + }), + // Entity tooltip new StyleRule( new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null, diff --git a/Content.Client/UserInterface/TutorialWindow.cs b/Content.Client/UserInterface/TutorialWindow.cs index eec5731bbb..ca1c4c0c53 100644 --- a/Content.Client/UserInterface/TutorialWindow.cs +++ b/Content.Client/UserInterface/TutorialWindow.cs @@ -74,6 +74,7 @@ Smart equip from belt: [color=#a4885c]{25}[/color] Open inventory: [color=#a4885c]{7}[/color] Open character window: [color=#a4885c]{8}[/color] Open crafting window: [color=#a4885c]{9}[/color] +Open action menu: [color=#a4885c]{33}[/color] Focus chat: [color=#a4885c]{10}[/color] Focus OOC: [color=#a4885c]{26}[/color] Focus Admin Chat: [color=#a4885c]{27}[/color] @@ -94,7 +95,18 @@ Toggle debug overlay: [color=#a4885c]{18}[/color] Toggle entity spawner: [color=#a4885c]{19}[/color] Toggle tile spawner: [color=#a4885c]{20}[/color] Toggle sandbox window: [color=#a4885c]{21}[/color] -Toggle admin menu [color=#a4885c]{31}[/color]", +Toggle admin menu [color=#a4885c]{31}[/color] +Hotbar slot 1: [color=#a4885c]{34}[/color] +Hotbar slot 2: [color=#a4885c]{35}[/color] +Hotbar slot 3: [color=#a4885c]{36}[/color] +Hotbar slot 4: [color=#a4885c]{37}[/color] +Hotbar slot 5: [color=#a4885c]{38}[/color] +Hotbar slot 6: [color=#a4885c]{39}[/color] +Hotbar slot 7: [color=#a4885c]{40}[/color] +Hotbar slot 8: [color=#a4885c]{41}[/color] +Hotbar slot 9: [color=#a4885c]{42}[/color] +Hotbar slot 0: [color=#a4885c]{43}[/color] + ", Key(MoveUp), Key(MoveLeft), Key(MoveDown), Key(MoveRight), Key(SwapHands), Key(ActivateItemInHand), @@ -124,7 +136,18 @@ Toggle admin menu [color=#a4885c]{31}[/color]", Key(TryPullObject), Key(MovePulledObject), Key(OpenAdminMenu), - Key(ReleasePulledObject))); + Key(ReleasePulledObject), + Key(OpenActionsMenu), + Key(Hotbar1), + Key(Hotbar2), + Key(Hotbar3), + Key(Hotbar4), + Key(Hotbar5), + Key(Hotbar6), + Key(Hotbar7), + Key(Hotbar8), + Key(Hotbar9), + Key(Hotbar0))); //Gameplay VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nGameplay" }); diff --git a/Content.Client/Utility/DragDropHelper.cs b/Content.Client/Utility/DragDropHelper.cs new file mode 100644 index 0000000000..71add4725f --- /dev/null +++ b/Content.Client/Utility/DragDropHelper.cs @@ -0,0 +1,172 @@ +using Robust.Client.Interfaces.Input; +using Robust.Shared.IoC; +using Robust.Shared.Maths; + +namespace Content.Client.Utility +{ + /// + /// Helper for implementing drag and drop interactions. + /// + /// The basic flow for a drag drop interaction as per this helper is: + /// 1. User presses mouse down on something (using class should communicate this to helper by calling MouseDown()). + /// 2. User continues to hold the mouse down and moves the mouse outside of the defined + /// deadzone. OnBeginDrag is invoked to see if a drag should be initiated. If so, initiates a drag. + /// If user didn't move the mouse beyond the deadzone the drag is not initiated (OnEndDrag invoked). + /// 3. Every Update/FrameUpdate, OnContinueDrag is invoked. + /// 4. User lifts mouse up. This is not handled by DragDropHelper. The using class of the helper should + /// do whatever they want and then end the drag by calling EndDrag() (which invokes OnEndDrag). + /// + /// If for any reason the drag is ended, OnEndDrag is invoked. + /// + /// thing being dragged and dropped + public class DragDropHelper + { + private const float DefaultDragDeadzone = 2f; + + private readonly IInputManager _inputManager; + + private readonly OnBeginDrag _onBeginDrag; + private readonly OnEndDrag _onEndDrag; + private readonly OnContinueDrag _onContinueDrag; + private readonly float _deadzone; + + /// + /// Convenience method, current mouse screen position as provided by inputmanager. + /// + public Vector2 MouseScreenPosition => _inputManager.MouseScreenPosition; + + /// + /// True if initiated a drag and currently dragging something. + /// I.e. this will be false if we've just had a mousedown over something but the mouse + /// has not moved outside of the drag deadzone. + /// + public bool IsDragging => _state == DragState.Dragging; + + /// + /// Current thing being dragged or which mouse button is being held down on. + /// + public T Dragged { get; private set; } + + // screen pos where the mouse down began for the drag + private Vector2 _mouseDownScreenPos; + private DragState _state = DragState.NotDragging; + + private enum DragState : byte + { + NotDragging, + // not dragging yet, waiting to see + // if they hold for long enough + MouseDown, + // currently dragging something + Dragging, + } + + /// + /// + /// + /// drag will be triggered when mouse leaves + /// this deadzone around the mousedown position + public DragDropHelper(OnBeginDrag onBeginDrag, OnContinueDrag onContinueDrag, + OnEndDrag onEndDrag, float deadzone = DefaultDragDeadzone) + { + _deadzone = deadzone; + _inputManager = IoCManager.Resolve(); + _onBeginDrag = onBeginDrag; + _onEndDrag = onEndDrag; + _onContinueDrag = onContinueDrag; + } + + /// + /// Tell the helper that the mouse button was pressed down on + /// a target, thus a drag has the possibility to begin for this target. + /// Assumes current mouse screen position is the location the mouse was clicked. + /// + /// EndDrag should be called when the drag is done. + /// + public void MouseDown(T target) + { + if (_state != DragState.NotDragging) + { + EndDrag(); + } + + Dragged = target; + _state = DragState.MouseDown; + _mouseDownScreenPos = _inputManager.MouseScreenPosition; + } + + /// + /// Stop the current drag / drop operation no matter what state it is in. + /// + public void EndDrag() + { + Dragged = default; + _state = DragState.NotDragging; + _onEndDrag.Invoke(); + } + + private void StartDragging() + { + if (_onBeginDrag.Invoke()) + { + _state = DragState.Dragging; + } + else + { + EndDrag(); + } + } + + /// + /// Should be invoked by using class every FrameUpdate or Update. + /// + public void Update(float frameTime) + { + switch (_state) + { + // check if dragging should begin + case DragState.MouseDown: + { + var screenPos = _inputManager.MouseScreenPosition; + if ((_mouseDownScreenPos - screenPos).Length > _deadzone) + { + StartDragging(); + } + + break; + } + case DragState.Dragging: + { + if (!_onContinueDrag.Invoke(frameTime)) + { + EndDrag(); + } + + break; + } + } + } + } + + /// + /// Invoked when a drag is confirmed and going to be initiated. Implementation should + /// typically set the drag shadow texture based on the target. + /// + /// true if drag should begin, false to end. + public delegate bool OnBeginDrag(); + + /// + /// Invoked every frame when drag is ongoing. Typically implementation should + /// make the drag shadow follow the mouse position. + /// + /// true if drag should continue, false to end. + public delegate bool OnContinueDrag(float frameTime); + + /// + /// invoked when + /// the drag drop is ending for any reason. This + /// should typically just clear the drag shadow. + /// + public delegate void OnEndDrag(); + +} diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs new file mode 100644 index 0000000000..850391c21d --- /dev/null +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Content.Client.GameObjects.Components.Mobs; +using Content.Client.UserInterface; +using Content.Client.UserInterface.Controls; +using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.Actions; +using Content.Shared.Alert; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.EntitySystems; +using NUnit.Framework; +using Robust.Client.Interfaces.UserInterface; +using Robust.Client.Player; + +namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs +{ + [TestFixture] + [TestOf(typeof(SharedActionsComponent))] + [TestOf(typeof(ClientActionsComponent))] + [TestOf(typeof(ServerActionsComponent))] + public class ActionsComponentTests : ContentIntegrationTest + { + [Test] + public async Task GrantsAndRevokesActionsTest() + { + var (client, server) = await StartConnectedServerClientPair(); + + await server.WaitIdleAsync(); + await client.WaitIdleAsync(); + + var serverPlayerManager = server.ResolveDependency(); + var innateActions = new List(); + + await server.WaitAssertion(() => + { + var player = serverPlayerManager.GetAllPlayers().Single(); + var playerEnt = player.AttachedEntity; + var actionsComponent = playerEnt.GetComponent(); + + // player should begin with their innate actions granted + innateActions.AddRange(actionsComponent.InnateActions); + foreach (var innateAction in actionsComponent.InnateActions) + { + Assert.That(actionsComponent.TryGetActionState(innateAction, out var innateState)); + Assert.That(innateState.Enabled); + } + + actionsComponent.Grant(ActionType.DebugInstant); + Assert.That(actionsComponent.TryGetActionState(ActionType.HumanScream, out var state) && state.Enabled); + }); + + // check that client has the actions + await server.WaitRunTicks(5); + await client.WaitRunTicks(5); + + var clientPlayerMgr = client.ResolveDependency(); + var clientUIMgr = client.ResolveDependency(); + var expectedOrder = new List(); + await client.WaitAssertion(() => + { + + var local = clientPlayerMgr.LocalPlayer; + var controlled = local.ControlledEntity; + var actionsComponent = controlled.GetComponent(); + + // we should have our innate actions and debug1. + foreach (var innateAction in innateActions) + { + Assert.That(actionsComponent.TryGetActionState(innateAction, out var innateState)); + Assert.That(innateState.Enabled); + } + Assert.That(actionsComponent.TryGetActionState(ActionType.DebugInstant, out var state) && state.Enabled); + + // innate actions should've auto-populated into our slots (in non-deterministic order), + // but debug1 should be in the last slot + var actionsUI = + clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is ActionsUI) as ActionsUI; + Assert.That(actionsUI, Is.Not.Null); + + var expectedInnate = new HashSet(innateActions); + var expectEmpty = false; + expectedOrder.Clear(); + foreach (var slot in actionsUI.Slots) + { + if (expectEmpty) + { + Assert.That(slot.HasAssignment, Is.False); + Assert.That(slot.Item, Is.Null); + Assert.That(slot.Action, Is.Null); + Assert.That(slot.ActionEnabled, Is.False); + continue; + } + Assert.That(slot.HasAssignment); + // all the actions we gave so far are not tied to an item + Assert.That(slot.Item, Is.Null); + Assert.That(slot.Action, Is.Not.Null); + Assert.That(slot.ActionEnabled); + var asAction = slot.Action as ActionPrototype; + Assert.That(asAction, Is.Not.Null); + expectedOrder.Add(asAction.ActionType); + + if (expectedInnate.Count != 0) + { + Assert.That(expectedInnate.Remove(asAction.ActionType)); + } + else + { + Assert.That(asAction.ActionType, Is.EqualTo(ActionType.DebugInstant)); + Assert.That(slot.Cooldown, Is.Null); + expectEmpty = true; + } + } + }); + + // now revoke the action and check that the client sees it as revoked + await server.WaitAssertion(() => + { + var player = serverPlayerManager.GetAllPlayers().Single(); + var playerEnt = player.AttachedEntity; + var actionsComponent = playerEnt.GetComponent(); + actionsComponent.Revoke(ActionType.DebugInstant); + }); + + await server.WaitRunTicks(5); + await client.WaitRunTicks(5); + + await client.WaitAssertion(() => + { + + var local = clientPlayerMgr.LocalPlayer; + var controlled = local.ControlledEntity; + var actionsComponent = controlled.GetComponent(); + + // we should have our innate actions, but debug1 should be revoked + foreach (var innateAction in innateActions) + { + Assert.That(actionsComponent.TryGetActionState(innateAction, out var innateState)); + Assert.That(innateState.Enabled); + } + Assert.That(actionsComponent.TryGetActionState(ActionType.DebugInstant, out var state), Is.False); + + // all actions should be in the same order as before, but the slot with DebugInstant should appear + // disabled. + var actionsUI = + clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is ActionsUI) as ActionsUI; + Assert.That(actionsUI, Is.Not.Null); + + var idx = 0; + foreach (var slot in actionsUI.Slots) + { + if (idx < expectedOrder.Count) + { + var expected = expectedOrder[idx++]; + Assert.That(slot.HasAssignment); + // all the actions we gave so far are not tied to an item + Assert.That(slot.Item, Is.Null); + Assert.That(slot.Action, Is.Not.Null); + var asAction = slot.Action as ActionPrototype; + Assert.That(asAction, Is.Not.Null); + + if (asAction.ActionType == ActionType.DebugInstant) + { + Assert.That(slot.ActionEnabled, Is.False); + } + else + { + Assert.That(slot.ActionEnabled); + } + } + else + { + Assert.That(slot.HasAssignment, Is.False); + Assert.That(slot.Item, Is.Null); + Assert.That(slot.Action, Is.Null); + Assert.That(slot.ActionEnabled, Is.False); + continue; + } + } + }); + } + + } +} diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs index e48a18daac..073592d783 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs @@ -2,14 +2,12 @@ using System.Threading.Tasks; using Content.Client.GameObjects.Components.Mobs; using Content.Client.UserInterface; +using Content.Client.UserInterface.Controls; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Alert; using NUnit.Framework; using Robust.Client.Interfaces.UserInterface; using Robust.Client.Player; -using Robust.Shared.Interfaces.Map; -using Robust.Shared.IoC; -using Robust.Shared.Map; namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs { diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index b9697ce770..c6ea3464a2 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -25,7 +25,7 @@ namespace Content.IntegrationTests.Tests.Gravity name: HumanDummy id: HumanDummy components: - - type: AlertsUI + - type: Alerts "; [Test] public async Task WeightlessStatusTest() diff --git a/Content.Server/Actions/DebugInstant.cs b/Content.Server/Actions/DebugInstant.cs new file mode 100644 index 0000000000..04a99ede01 --- /dev/null +++ b/Content.Server/Actions/DebugInstant.cs @@ -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 +{ + /// + /// Just shows a popup message.asd + /// + [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)); + } + } +} diff --git a/Content.Server/Actions/DebugTargetEntity.cs b/Content.Server/Actions/DebugTargetEntity.cs new file mode 100644 index 0000000000..89d4a6d751 --- /dev/null +++ b/Content.Server/Actions/DebugTargetEntity.cs @@ -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); + } + } +} diff --git a/Content.Server/Actions/DebugTargetPoint.cs b/Content.Server/Actions/DebugTargetPoint.cs new file mode 100644 index 0000000000..00429a1a0c --- /dev/null +++ b/Content.Server/Actions/DebugTargetPoint.cs @@ -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); + } + } +} diff --git a/Content.Server/Actions/DebugToggle.cs b/Content.Server/Actions/DebugToggle.cs new file mode 100644 index 0000000000..56ba9d8284 --- /dev/null +++ b/Content.Server/Actions/DebugToggle.cs @@ -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; + } + } +} diff --git a/Content.Server/Actions/ScreamAction.cs b/Content.Server/Actions/ScreamAction.cs new file mode 100644 index 0000000000..e89d2452e7 --- /dev/null +++ b/Content.Server/Actions/ScreamAction.cs @@ -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 _male; + private List _female; + private string _wilhelm; + /// seconds + private float _cooldown; + + private IRobustRandom _random; + + public ScreamAction() + { + _random = IoCManager.Resolve(); + } + + 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(out var humanoid)) return; + if (!args.Performer.TryGetComponent(out var actions)) return; + + if (_random.Prob(.01f) && !string.IsNullOrWhiteSpace(_wilhelm)) + { + EntitySystem.Get().PlayFromEntity(_wilhelm, args.Performer, AudioParams.Default.WithVolume(Volume)); + } + else + { + switch (humanoid.Sex) + { + case Sex.Male: + if (_male == null) break; + EntitySystem.Get().PlayFromEntity(_random.Pick(_male), args.Performer, + AudioHelpers.WithVariation(Variation).WithVolume(Volume)); + break; + case Sex.Female: + if (_female == null) break; + EntitySystem.Get().PlayFromEntity(_random.Pick(_female), args.Performer, + AudioHelpers.WithVariation(Variation).WithVolume(Volume)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + + + actions.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(_cooldown)); + } + } +} diff --git a/Content.Server/Alert/Click/ResistFire.cs b/Content.Server/Alert/Click/ResistFire.cs new file mode 100644 index 0000000000..e7f7e42231 --- /dev/null +++ b/Content.Server/Alert/Click/ResistFire.cs @@ -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 +{ + /// + /// Resist fire + /// + [UsedImplicitly] + public class ResistFire : IAlertClick + { + public void ExposeData(ObjectSerializer serializer) { } + + public void AlertClicked(ClickAlertEventArgs args) + { + if (args.Player.TryGetComponent(out FlammableComponent flammable)) + { + flammable.Resist(); + } + } + } +} diff --git a/Content.Server/Alert/Click/StopPiloting.cs b/Content.Server/Alert/Click/StopPiloting.cs new file mode 100644 index 0000000000..cc8164c071 --- /dev/null +++ b/Content.Server/Alert/Click/StopPiloting.cs @@ -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 +{ + /// + /// Stop piloting shuttle + /// + [UsedImplicitly] + public class StopPiloting : IAlertClick + { + public void ExposeData(ObjectSerializer serializer) { } + + public void AlertClicked(ClickAlertEventArgs args) + { + if (args.Player.TryGetComponent(out ShuttleControllerComponent controller)) + { + controller.RemoveController(); + } + } + } +} diff --git a/Content.Server/Alert/Click/StopPulling.cs b/Content.Server/Alert/Click/StopPulling.cs new file mode 100644 index 0000000000..2153769ae6 --- /dev/null +++ b/Content.Server/Alert/Click/StopPulling.cs @@ -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 +{ + /// + /// Stop pulling something + /// + [UsedImplicitly] + public class StopPulling : IAlertClick + { + public void ExposeData(ObjectSerializer serializer) { } + + public void AlertClicked(ClickAlertEventArgs args) + { + EntitySystem + .Get() + .GetPulled(args.Player)? + .GetComponentOrNull()? + .TryStopPull(); + } + } +} diff --git a/Content.Server/Alert/Click/Unbuckle.cs b/Content.Server/Alert/Click/Unbuckle.cs new file mode 100644 index 0000000000..742f19ad29 --- /dev/null +++ b/Content.Server/Alert/Click/Unbuckle.cs @@ -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 +{ + /// + /// Unbuckles if player is currently buckled. + /// + [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); + } + } + } +} diff --git a/Content.Server/Commands/Actions/CooldownAction.cs b/Content.Server/Commands/Actions/CooldownAction.cs new file mode 100644 index 0000000000..15c9b9a903 --- /dev/null +++ b/Content.Server/Commands/Actions/CooldownAction.cs @@ -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 "; + + 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(actionTypeRaw, out var actionType)) + { + shell.SendText(player, "unrecognized ActionType enum value, please" + + " ensure you used correct casing: " + actionTypeRaw); + return; + } + var actionMgr = IoCManager.Resolve(); + + if (!actionMgr.TryGet(actionType, out var action)) + { + shell.SendText(player, "unrecognized actionType " + actionType); + return; + } + + var cooldownStart = IoCManager.Resolve().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)); + } + } +} diff --git a/Content.Server/Commands/Actions/GrantAction.cs b/Content.Server/Commands/Actions/GrantAction.cs new file mode 100644 index 0000000000..fd2cd3d6b8 --- /dev/null +++ b/Content.Server/Commands/Actions/GrantAction.cs @@ -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 "; + 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(actionTypeRaw, out var actionType)) + { + shell.SendText(player, "unrecognized ActionType enum value, please" + + " ensure you used correct casing: " + actionTypeRaw); + return; + } + var actionMgr = IoCManager.Resolve(); + if (!actionMgr.TryGet(actionType, out var action)) + { + shell.SendText(player, "unrecognized actionType " + actionType); + return; + } + actionsComponent.Grant(action.ActionType); + } + } +} diff --git a/Content.Server/Commands/Actions/RevokeAction.cs b/Content.Server/Commands/Actions/RevokeAction.cs new file mode 100644 index 0000000000..be7ca6082d --- /dev/null +++ b/Content.Server/Commands/Actions/RevokeAction.cs @@ -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 "; + + 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(actionTypeRaw, out var actionType)) + { + shell.SendText(player, "unrecognized ActionType enum value, please" + + " ensure you used correct casing: " + actionTypeRaw); + return; + } + var actionMgr = IoCManager.Resolve(); + if (!actionMgr.TryGet(actionType, out var action)) + { + shell.SendText(player, "unrecognized actionType " + actionType); + return; + } + + actionsComponent.Revoke(action.ActionType); + } + } +} diff --git a/Content.Server/Commands/Alerts/ClearAlert.cs b/Content.Server/Commands/Alerts/ClearAlert.cs index 5761df0401..c8fabad81c 100644 --- a/Content.Server/Commands/Alerts/ClearAlert.cs +++ b/Content.Server/Commands/Alerts/ClearAlert.cs @@ -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"); diff --git a/Content.Server/Commands/Alerts/ShowAlert.cs b/Content.Server/Commands/Alerts/ShowAlert.cs index ef85091bb5..20a08cc809 100644 --- a/Content.Server/Commands/Alerts/ShowAlert.cs +++ b/Content.Server/Commands/Alerts/ShowAlert.cs @@ -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"); diff --git a/Content.Server/Commands/CommandUtils.cs b/Content.Server/Commands/CommandUtils.cs index b6535dc7d3..66e36773fb 100644 --- a/Content.Server/Commands/CommandUtils.cs +++ b/Content.Server/Commands/CommandUtils.cs @@ -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. /// 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(); 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. /// 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; } - - /// - /// Checks if attached entity is null, returning false and sending a message - /// to performer if not. - /// - 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; - } - } } diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 9275046155..f7bbac022d 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -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().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Server/GameObjects/Components/Atmos/FlammableComponent.cs b/Content.Server/GameObjects/Components/Atmos/FlammableComponent.cs index 3bfdcd27f9..17c40e111b 100644 --- a/Content.Server/GameObjects/Components/Atmos/FlammableComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/FlammableComponent.cs @@ -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)) diff --git a/Content.Server/GameObjects/Components/Atmos/GasTankComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasTankComponent.cs index afb603b0b4..5dd0bcb40d 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasTankComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasTankComponent.cs @@ -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); + } + /// /// Open interaction window /// @@ -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(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; } } } diff --git a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs index 2c7cdc67d4..5fef60e9de 100644 --- a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs +++ b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs @@ -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); - } - } - /// /// Reattaches this entity to the strap, modifying its position and rotation. diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 7be23be0e4..35b22f0c59 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -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().EquippedHandInteraction(Owner, item.Owner, + ToSharedHand(hand)); + _entitySystemManager.GetEntitySystem().HandSelectedInteraction(Owner, item.Owner); return success; @@ -266,6 +271,9 @@ namespace Content.Server.GameObjects.Components.GUI return false; } + _entitySystemManager.GetEntitySystem().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(); + + if (!hand.Container.Remove(hand.Entity)) + { + throw new InvalidOperationException(); + } + + _entitySystemManager.GetEntitySystem().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(); - - 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) diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index 3de18229ef..33152537ec 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -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; + } } } diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index 25f3314fc5..958ec9768f 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -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; + /// /// Client-side ItemStatus level /// @@ -98,8 +108,9 @@ namespace Content.Server.GameObjects.Components.Interactable /// Illuminates the light if it is not active, extinguishes it if it is active. /// /// True if the light's status was toggled, false otherwise. - 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().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().PlayFromEntity(TurnOnFailSound, Owner); Owner.PopupMessage(user, Loc.GetString("Dead cell...")); + UpdateLightAction(); return false; } Activated = true; + UpdateLightAction(); SetState(true); if (TurnOnSound != null) EntitySystem.Get().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(out var lightComponent)) return false; + if (lightComponent.Activated == args.ToggledOn) return false; + return lightComponent.ToggleStatus(args.Performer); + } + } } diff --git a/Content.Server/GameObjects/Components/Items/DebugEquipComponent.cs b/Content.Server/GameObjects/Components/Items/DebugEquipComponent.cs new file mode 100644 index 0000000000..6fd0a50f99 --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/DebugEquipComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Items +{ + /// + /// Pops up a message when equipped / unequipped (including hands). + /// For debugging purposes. + /// + [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); + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/ServerActionsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerActionsComponent.cs new file mode 100644 index 0000000000..e5e80544b5 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/ServerActionsComponent.cs @@ -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(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; + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/ServerAlertsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerAlertsComponent.cs index f035cedd84..2ec4c6f398 100644 --- a/Content.Server/GameObjects/Components/Mobs/ServerAlertsComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/ServerAlertsComponent.cs @@ -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; diff --git a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs index e21cabb0d5..5660f28d57 100644 --- a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs @@ -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); } /// diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 0210f5de3b..b1cb6b286c 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -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 } /// - /// 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 /// - /// - /// 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 } /// - /// 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 /// /// @@ -518,8 +518,8 @@ namespace Content.Server.GameObjects.EntitySystems.Click } /// - /// 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. /// public void UseInteraction(IEntity user, IEntity used) { @@ -679,6 +679,48 @@ namespace Content.Server.GameObjects.EntitySystems.Click } } + /// + /// Calls EquippedHand on all components that implement the IEquippedHand interface + /// on an item. + /// + 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().ToList(); + + foreach (var comp in comps) + { + comp.EquippedHand(new EquippedHandEventArgs(user, hand)); + } + } + + /// + /// Calls UnequippedHand on all components that implement the IUnequippedHand interface + /// on an item. + /// + 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().ToList(); + + foreach (var comp in comps) + { + comp.UnequippedHand(new UnequippedHandEventArgs(user, hand)); + } + } + /// /// 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 } } - /// /// 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 diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index 9f150e83df..09a1f2d17c 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -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(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Shared/Actions/ActionManager.cs b/Content.Shared/Actions/ActionManager.cs new file mode 100644 index 0000000000..dd9e3fe893 --- /dev/null +++ b/Content.Shared/Actions/ActionManager.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions +{ + /// + /// Provides access to all configured actions by action type. + /// + public class ActionManager + { + [Dependency] + private readonly IPrototypeManager _prototypeManager = default!; + + private Dictionary _typeToAction; + private Dictionary _typeToItemAction; + + public void Initialize() + { + _typeToAction = new Dictionary(); + foreach (var action in _prototypeManager.EnumeratePrototypes()) + { + if (!_typeToAction.TryAdd(action.ActionType, action)) + { + Logger.ErrorS("action", + "Found action with duplicate actionType {0} - all actions must have" + + " a unique actionType, this one will be skipped", action.ActionType); + } + } + + _typeToItemAction = new Dictionary(); + foreach (var action in _prototypeManager.EnumeratePrototypes()) + { + if (!_typeToItemAction.TryAdd(action.ActionType, action)) + { + Logger.ErrorS("action", + "Found itemAction with duplicate actionType {0} - all actions must have" + + " a unique actionType, this one will be skipped", action.ActionType); + } + } + } + + /// all action prototypes of all types + public IEnumerable EnumerateActions() + { + return _typeToAction.Values.Concat(_typeToItemAction.Values); + } + + + /// + /// Tries to get the action of the indicated type + /// + /// true if found + public bool TryGet(ActionType actionType, out ActionPrototype action) + { + return _typeToAction.TryGetValue(actionType, out action); + } + + /// + /// Tries to get the item action of the indicated type + /// + /// true if found + public bool TryGet(ItemActionType actionType, out ItemActionPrototype action) + { + return _typeToItemAction.TryGetValue(actionType, out action); + } + } +} diff --git a/Content.Shared/Actions/ActionPrototype.cs b/Content.Shared/Actions/ActionPrototype.cs new file mode 100644 index 0000000000..c5f5d7a0bc --- /dev/null +++ b/Content.Shared/Actions/ActionPrototype.cs @@ -0,0 +1,100 @@ +using Content.Shared.Interfaces; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using YamlDotNet.RepresentationModel; +using Robust.Shared.Log; + +namespace Content.Shared.Actions +{ + /// + /// An action which is granted directly to an entity (such as an innate ability + /// or skill). + /// + [Prototype("action")] + public class ActionPrototype : BaseActionPrototype + { + /// + /// Type of action, no 2 action prototypes should have the same one. + /// + public ActionType ActionType { get; private set; } + + /// + /// The IInstantAction that should be invoked when performing this + /// action. Null if this is not an Instant ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public IInstantAction InstantAction { get; private set; } + + /// + /// The IToggleAction that should be invoked when performing this + /// action. Null if this is not a Toggle ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public IToggleAction ToggleAction { get; private set; } + + /// + /// The ITargetEntityAction that should be invoked when performing this + /// action. Null if this is not a TargetEntity ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public ITargetEntityAction TargetEntityAction { get; private set; } + + /// + /// The ITargetPointAction that should be invoked when performing this + /// action. Null if this is not a TargetPoint ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public ITargetPointAction TargetPointAction { get; private set; } + + public override void LoadFrom(YamlMappingNode mapping) + { + base.LoadFrom(mapping); + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(this, x => x.ActionType, "actionType", ActionType.Error); + if (ActionType == ActionType.Error) + { + Logger.ErrorS("action", "missing or invalid actionType for action with name {0}", Name); + } + + // TODO: Split this class into server/client after RobustToolbox#1405 + if (IoCManager.Resolve().IsClientModule) return; + + IActionBehavior behavior = null; + serializer.DataField(ref behavior, "behavior", null); + switch (behavior) + { + case null: + BehaviorType = BehaviorType.None; + Logger.ErrorS("action", "missing or invalid behavior for action with name {0}", Name); + break; + case IInstantAction instantAction: + ValidateBehaviorType(BehaviorType.Instant, typeof(IInstantAction)); + BehaviorType = BehaviorType.Instant; + InstantAction = instantAction; + break; + case IToggleAction toggleAction: + ValidateBehaviorType(BehaviorType.Toggle, typeof(IToggleAction)); + BehaviorType = BehaviorType.Toggle; + ToggleAction = toggleAction; + break; + case ITargetEntityAction targetEntity: + ValidateBehaviorType(BehaviorType.TargetEntity, typeof(ITargetEntityAction)); + BehaviorType = BehaviorType.TargetEntity; + TargetEntityAction = targetEntity; + break; + case ITargetPointAction targetPointAction: + ValidateBehaviorType(BehaviorType.TargetPoint, typeof(ITargetPointAction)); + BehaviorType = BehaviorType.TargetPoint; + TargetPointAction = targetPointAction; + break; + default: + BehaviorType = BehaviorType.None; + Logger.ErrorS("action", "unrecognized behavior type for action with name {0}", Name); + break; + } + + } + } +} diff --git a/Content.Shared/Actions/ActionType.cs b/Content.Shared/Actions/ActionType.cs new file mode 100644 index 0000000000..151d78b1dd --- /dev/null +++ b/Content.Shared/Actions/ActionType.cs @@ -0,0 +1,33 @@ +namespace Content.Shared.Actions +{ + /// + /// Every possible action. Corresponds to actionType in action prototypes. + /// + public enum ActionType : byte + { + Error, + HumanScream, + DebugInstant, + DebugToggle, + DebugTargetPoint, + DebugTargetPointRepeat, + DebugTargetEntity, + DebugTargetEntityRepeat + } + + /// + /// Every possible item action. Corresponds to actionType in itemAction prototypes. + /// + public enum ItemActionType : byte + { + Error, + ToggleInternals, + ToggleLight, + DebugInstant, + DebugToggle, + DebugTargetPoint, + DebugTargetPointRepeat, + DebugTargetEntity, + DebugTargetEntityRepeat + } +} diff --git a/Content.Shared/Actions/BaseActionPrototype.cs b/Content.Shared/Actions/BaseActionPrototype.cs new file mode 100644 index 0000000000..4068c9da79 --- /dev/null +++ b/Content.Shared/Actions/BaseActionPrototype.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Interfaces; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Actions +{ + /// + /// Base class for action prototypes. + /// + public abstract class BaseActionPrototype : IPrototype + { + /// + /// Icon representing this action in the UI. + /// + [ViewVariables] + public SpriteSpecifier Icon { get; private set; } + + /// + /// For toggle actions only, icon to show when toggled on. If omitted, + /// the action will simply be highlighted when turned on. + /// + [ViewVariables] + public SpriteSpecifier IconOn { get; private set; } + + + + /// + /// Name to show in UI. Accepts formatting. + /// + public FormattedMessage Name { get; private set; } + + /// + /// Description to show in UI. Accepts formatting. + /// + public FormattedMessage Description { get; private set; } + + /// + /// Requirements message to show in UI. Accepts formatting, but generally should be avoided + /// so the requirements message isn't too prominent in the tooltip. + /// + public string Requires { get; private set; } + + /// + /// The type of behavior this action has. This is valid clientside and serverside. + /// + public BehaviorType BehaviorType { get; protected set; } + + /// + /// For targetpoint or targetentity actions, if this is true the action will remain + /// selected after it is used, so it can be continuously re-used. If this is false, + /// the action will be deselected after one use. + /// + public bool Repeat { get; private set; } + + /// + /// Filters that can be used to filter this item in action menu. + /// + public IEnumerable Filters { get; private set; } + + /// + /// Keywords that can be used to search this item in action menu. + /// + public IEnumerable Keywords { get; private set; } + + public virtual void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataReadFunction("name", string.Empty, + s => Name = FormattedMessage.FromMarkup(s)); + serializer.DataReadFunction("description", string.Empty, + s => Description = FormattedMessage.FromMarkup(s)); + + serializer.DataField(this, x => x.Requires,"requires", null); + serializer.DataField(this, x => x.Icon,"icon", SpriteSpecifier.Invalid); + serializer.DataField(this, x => x.IconOn,"iconOn", SpriteSpecifier.Invalid); + + // client needs to know what type of behavior it is even if the actual implementation is only + // on server side. If we wanted to avoid this we'd need to always add a shared or clientside interface + // for each action even if there was only server-side logic, which would be cumbersome + serializer.DataField(this, x => x.BehaviorType, "behaviorType", BehaviorType.None); + if (BehaviorType == BehaviorType.None) + { + Logger.ErrorS("action", "Missing behaviorType for action with name {0}", Name); + } + + if (BehaviorType != BehaviorType.Toggle && IconOn != SpriteSpecifier.Invalid) + { + Logger.ErrorS("action", "for action {0}, iconOn was specified but behavior" + + " type was {1}. iconOn is only supported for Toggle behavior type.", Name); + } + + serializer.DataField(this, x => x.Repeat, "repeat", false); + if (Repeat && BehaviorType != BehaviorType.TargetEntity && BehaviorType != BehaviorType.TargetPoint) + { + Logger.ErrorS("action", " action named {0} used repeat: true, but this is only supported for" + + " TargetEntity and TargetPoint behaviorType and its behaviorType is {1}", + Name, BehaviorType); + } + + serializer.DataReadFunction("filters", new List(), + rawTags => + { + Filters = rawTags.Select(rawTag => rawTag.Trim()).ToList(); + }); + + serializer.DataReadFunction("keywords", new List(), + rawTags => + { + Keywords = rawTags.Select(rawTag => rawTag.Trim()).ToList(); + }); + } + + protected void ValidateBehaviorType(BehaviorType expected, Type actualInterface) + { + if (BehaviorType != expected) + { + Logger.ErrorS("action", "for action named {0}, behavior implements " + + "{1}, so behaviorType should be {2} but was {3}", Name, actualInterface.Name, expected, BehaviorType); + } + } + } + + /// + /// The behavior / logic of the action. Each of these corresponds to a particular IActionBehavior + /// (for actions) or IItemActionBehavior (for item actions) + /// interface. Corresponds to action.behaviorType in YAML + /// + public enum BehaviorType + { + /// + /// Action doesn't do anything. + /// + None, + + /// + /// IInstantAction/IInstantItemAction. Action which does something immediately when used and has + /// no target. + /// + Instant, + + /// + /// IToggleAction/IToggleItemAction Action which can be toggled on and off + /// + Toggle, + + /// + /// ITargetEntityAction/ITargetEntityItemAction. Action which is used on a targeted entity. + /// + TargetEntity, + + /// + /// ITargetPointAction/ITargetPointItemAction. Action which requires the user to select a target point, which + /// does not necessarily have an entity on it. + /// + TargetPoint + } +} diff --git a/Content.Shared/Actions/IActionBehavior.cs b/Content.Shared/Actions/IActionBehavior.cs new file mode 100644 index 0000000000..fad5518d77 --- /dev/null +++ b/Content.Shared/Actions/IActionBehavior.cs @@ -0,0 +1,44 @@ +using System; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; + +namespace Content.Shared.Actions +{ + /// + /// Currently just a marker interface delineating the different possible + /// types of action behaviors. + /// + public interface IActionBehavior : IExposeData { } + + /// + /// Base class for all action event args + /// + public abstract class ActionEventArgs : EventArgs + { + /// + /// Entity performing the action. + /// + public readonly IEntity Performer; + /// + /// Action being performed + /// + public readonly ActionType ActionType; + /// + /// Actions component of the performer. + /// + public readonly SharedActionsComponent PerformerActions; + + public ActionEventArgs(IEntity performer, ActionType actionType) + { + Performer = performer; + ActionType = actionType; + if (!Performer.TryGetComponent(out PerformerActions)) + { + throw new InvalidOperationException($"performer {performer.Name} tried to perform action {actionType} " + + $" but the performer had no actions component," + + " which should never occur"); + } + } + } +} diff --git a/Content.Shared/Actions/IInstantAction.cs b/Content.Shared/Actions/IInstantAction.cs new file mode 100644 index 0000000000..73c24f1a67 --- /dev/null +++ b/Content.Shared/Actions/IInstantAction.cs @@ -0,0 +1,27 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; + +namespace Content.Shared.Actions +{ + /// + /// Action which does something immediately when used and has + /// no target. + /// + public interface IInstantAction : IActionBehavior + { + + /// + /// Invoked when the instant action should be performed. + /// Implementation should perform the server side logic of the action. + /// + void DoInstantAction(InstantActionEventArgs args); + } + + public class InstantActionEventArgs : ActionEventArgs + { + public InstantActionEventArgs(IEntity performer, ActionType actionType) : base(performer, actionType) + { + } + } +} diff --git a/Content.Shared/Actions/IInstantItemAction.cs b/Content.Shared/Actions/IInstantItemAction.cs new file mode 100644 index 0000000000..74ac02c28d --- /dev/null +++ b/Content.Shared/Actions/IInstantItemAction.cs @@ -0,0 +1,28 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; + +namespace Content.Shared.Actions +{ + /// + /// Item action which does something immediately when used and has + /// no target. + /// + public interface IInstantItemAction : IItemActionBehavior + { + + /// + /// Invoked when the instant action should be performed. + /// Implementation should perform the server side logic of the action. + /// + void DoInstantAction(InstantItemActionEventArgs args); + } + + public class InstantItemActionEventArgs : ItemActionEventArgs + { + public InstantItemActionEventArgs(IEntity performer, IEntity item, ItemActionType actionType) : + base(performer, item, actionType) + { + } + } +} diff --git a/Content.Shared/Actions/IItemActionBehavior.cs b/Content.Shared/Actions/IItemActionBehavior.cs new file mode 100644 index 0000000000..dd1ee99b3d --- /dev/null +++ b/Content.Shared/Actions/IItemActionBehavior.cs @@ -0,0 +1,52 @@ +using System; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; + +namespace Content.Shared.Actions +{ + /// + /// Currently just a marker interface delineating the different possible + /// types of item action behaviors. + /// + public interface IItemActionBehavior : IExposeData + { + + } + + /// + /// Base class for all item action event args + /// + public abstract class ItemActionEventArgs : EventArgs + { + /// + /// Entity performing the action. + /// + public readonly IEntity Performer; + /// + /// Item being used to perform the action + /// + public readonly IEntity Item; + /// + /// Action being performed + /// + public readonly ItemActionType ActionType; + /// + /// Item actions component of the item. + /// + public readonly ItemActionsComponent ItemActions; + + public ItemActionEventArgs(IEntity performer, IEntity item, ItemActionType actionType) + { + Performer = performer; + ActionType = actionType; + Item = item; + if (!Item.TryGetComponent(out ItemActions)) + { + throw new InvalidOperationException($"performer {performer.Name} tried to perform item action {actionType} " + + $" for item {Item.Name} but the item had no ItemActionsComponent," + + " which should never occur"); + } + } + } +} diff --git a/Content.Shared/Actions/ITargetEntityAction.cs b/Content.Shared/Actions/ITargetEntityAction.cs new file mode 100644 index 0000000000..44266bd0ea --- /dev/null +++ b/Content.Shared/Actions/ITargetEntityAction.cs @@ -0,0 +1,33 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Map; + +namespace Content.Shared.Actions +{ + /// + /// Action which is used on a targeted entity. + /// + public interface ITargetEntityAction : IActionBehavior + { + /// + /// Invoked when the target entity action should be performed. + /// Implementation should perform the server side logic of the action. + /// + void DoTargetEntityAction(TargetEntityActionEventArgs args); + } + + public class TargetEntityActionEventArgs : ActionEventArgs + { + /// + /// Entity being targeted + /// + public readonly IEntity Target; + + public TargetEntityActionEventArgs(IEntity performer, ActionType actionType, IEntity target) : + base(performer, actionType) + { + Target = target; + } + } +} diff --git a/Content.Shared/Actions/ITargetEntityItemAction.cs b/Content.Shared/Actions/ITargetEntityItemAction.cs new file mode 100644 index 0000000000..388ea55414 --- /dev/null +++ b/Content.Shared/Actions/ITargetEntityItemAction.cs @@ -0,0 +1,34 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Map; + +namespace Content.Shared.Actions +{ + /// + /// Item action which is used on a targeted entity. + /// + public interface ITargetEntityItemAction : IItemActionBehavior + { + /// + /// Invoked when the target entity action should be performed. + /// Implementation should perform the server side logic of the action. + /// + void DoTargetEntityAction(TargetEntityItemActionEventArgs args); + } + + public class TargetEntityItemActionEventArgs : ItemActionEventArgs + { + /// + /// Entity being targeted + /// + public readonly IEntity Target; + + public TargetEntityItemActionEventArgs(IEntity performer, IEntity target, IEntity item, + ItemActionType actionType) : base(performer, item, actionType) + { + Target = target; + + } + } +} diff --git a/Content.Shared/Actions/ITargetPointAction.cs b/Content.Shared/Actions/ITargetPointAction.cs new file mode 100644 index 0000000000..7e78cefebf --- /dev/null +++ b/Content.Shared/Actions/ITargetPointAction.cs @@ -0,0 +1,33 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; + +namespace Content.Shared.Actions +{ + /// + /// Action which requires the user to select a target point, which + /// does not necessarily have an entity on it. + /// + public interface ITargetPointAction : IActionBehavior + { + /// + /// Invoked when the target point action should be performed. + /// Implementation should perform the server side logic of the action. + /// + void DoTargetPointAction(TargetPointActionEventArgs args); + } + + public class TargetPointActionEventArgs : ActionEventArgs + { + /// + /// Local coordinates of the targeted position. + /// + public readonly EntityCoordinates Target; + + public TargetPointActionEventArgs(IEntity performer, EntityCoordinates target, ActionType actionType) + : base(performer, actionType) + { + Target = target; + } + } +} diff --git a/Content.Shared/Actions/ITargetPointItemAction.cs b/Content.Shared/Actions/ITargetPointItemAction.cs new file mode 100644 index 0000000000..5a7c2384f9 --- /dev/null +++ b/Content.Shared/Actions/ITargetPointItemAction.cs @@ -0,0 +1,33 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; + +namespace Content.Shared.Actions +{ + /// + /// Item action which requires the user to select a target point, which + /// does not necessarily have an entity on it. + /// + public interface ITargetPointItemAction : IItemActionBehavior + { + /// + /// Invoked when the target point action should be performed. + /// Implementation should perform the server side logic of the action. + /// + void DoTargetPointAction(TargetPointItemActionEventArgs args); + } + + public class TargetPointItemActionEventArgs : ItemActionEventArgs + { + /// + /// Local coordinates of the targeted position. + /// + public readonly EntityCoordinates Target; + + public TargetPointItemActionEventArgs(IEntity performer, EntityCoordinates target, IEntity item, + ItemActionType actionType) : base(performer, item, actionType) + { + Target = target; + } + } +} diff --git a/Content.Shared/Actions/IToggleAction.cs b/Content.Shared/Actions/IToggleAction.cs new file mode 100644 index 0000000000..d20b576ea3 --- /dev/null +++ b/Content.Shared/Actions/IToggleAction.cs @@ -0,0 +1,41 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.Actions +{ + /// + /// Action which can be toggled on and off + /// + public interface IToggleAction : IActionBehavior + { + /// + /// Invoked when the action will be toggled on/off. + /// Implementation should perform the server side logic of whatever + /// happens when it is toggled on / off. + /// + /// true if the attempt to toggle was successful, meaning the state should be toggled to the desired value. + /// False to leave toggle status unchanged. This is NOT returning the new toggle status, it is only returning + /// whether the attempt to toggle to the indicated status was successful. + /// + /// Note that it's still okay if the implementation directly modifies toggle status via SharedActionsComponent, + /// this is just an additional level of safety to ensure implementations will always + /// explicitly indicate if the toggle status should be changed. + bool DoToggleAction(ToggleActionEventArgs args); + } + + public class ToggleActionEventArgs : ActionEventArgs + { + /// + /// True if the toggle is attempting to be toggled on, false if attempting to toggle off + /// + public readonly bool ToggledOn; + /// + /// Opposite of ToggledOn + /// + public bool ToggledOff => !ToggledOn; + + public ToggleActionEventArgs(IEntity performer, ActionType actionType, bool toggledOn) : base(performer, actionType) + { + ToggledOn = toggledOn; + } + } +} diff --git a/Content.Shared/Actions/IToggleItemAction.cs b/Content.Shared/Actions/IToggleItemAction.cs new file mode 100644 index 0000000000..7930dce933 --- /dev/null +++ b/Content.Shared/Actions/IToggleItemAction.cs @@ -0,0 +1,42 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.Actions +{ + /// + /// Item action which can be toggled on and off + /// + public interface IToggleItemAction : IItemActionBehavior + { + /// + /// Invoked when the action will be toggled on/off. + /// Implementation should perform the server side logic of whatever + /// happens when it is toggled on / off. + /// + /// true if the attempt to toggle was successful, meaning the state should be toggled to the desired value. + /// False to leave toggle status unchanged. This is NOT returning the new toggle status, it is only returning + /// whether the attempt to toggle to the indicated status was successful. + /// + /// Note that it's still okay if the implementation directly modifies toggle status via ItemActionsComponent, + /// this is just an additional level of safety to ensure implementations will always + /// explicitly indicate if the toggle status should be changed. + bool DoToggleAction(ToggleItemActionEventArgs args); + } + + public class ToggleItemActionEventArgs : ItemActionEventArgs + { + /// + /// True if the toggle was toggled on, false if it was toggled off + /// + public readonly bool ToggledOn; + /// + /// Opposite of ToggledOn + /// + public bool ToggledOff => !ToggledOn; + + public ToggleItemActionEventArgs(IEntity performer, bool toggledOn, IEntity item, + ItemActionType actionType) : base(performer, item, actionType) + { + ToggledOn = toggledOn; + } + } +} diff --git a/Content.Shared/Actions/ItemActionPrototype.cs b/Content.Shared/Actions/ItemActionPrototype.cs new file mode 100644 index 0000000000..25de3910c7 --- /dev/null +++ b/Content.Shared/Actions/ItemActionPrototype.cs @@ -0,0 +1,122 @@ +using Content.Shared.Interfaces; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Actions +{ + /// + /// An action which is granted to an entity via an item (such as toggling a flashlight). + /// + [Prototype("itemAction")] + public class ItemActionPrototype : BaseActionPrototype + { + /// + /// Type of item action, no 2 itemAction prototypes should have the same one. + /// + public ItemActionType ActionType { get; private set; } + + /// + public ItemActionIconStyle IconStyle { get; private set; } + + /// + /// The IInstantItemAction that should be invoked when performing this + /// action. Null if this is not an Instant ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public IInstantItemAction InstantAction { get; private set; } + + /// + /// The IToggleItemAction that should be invoked when performing this + /// action. Null if this is not a Toggle ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public IToggleItemAction ToggleAction { get; private set; } + + /// + /// The ITargetEntityItemAction that should be invoked when performing this + /// action. Null if this is not a TargetEntity ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public ITargetEntityItemAction TargetEntityAction { get; private set; } + + /// + /// The ITargetPointItemAction that should be invoked when performing this + /// action. Null if this is not a TargetPoint ActionBehaviorType. + /// Will be null on client side if the behavior is not in Content.Client. + /// + public ITargetPointItemAction TargetPointAction { get; private set; } + + public override void LoadFrom(YamlMappingNode mapping) + { + base.LoadFrom(mapping); + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(this, x => x.ActionType, "actionType", ItemActionType.Error); + if (ActionType == ItemActionType.Error) + { + Logger.ErrorS("action", "missing or invalid actionType for action with name {0}", Name); + } + + serializer.DataField(this, x => x.IconStyle, "iconStyle", ItemActionIconStyle.BigItem); + + // TODO: Split this class into server/client after RobustToolbox#1405 + if (IoCManager.Resolve().IsClientModule) return; + + IItemActionBehavior behavior = null; + serializer.DataField(ref behavior, "behavior", null); + switch (behavior) + { + case null: + BehaviorType = BehaviorType.None; + Logger.ErrorS("action", "missing or invalid behavior for action with name {0}", Name); + break; + case IInstantItemAction instantAction: + ValidateBehaviorType(BehaviorType.Instant, typeof(IInstantItemAction)); + BehaviorType = BehaviorType.Instant; + InstantAction = instantAction; + break; + case IToggleItemAction toggleAction: + ValidateBehaviorType(BehaviorType.Toggle, typeof(IToggleItemAction)); + BehaviorType = BehaviorType.Toggle; + ToggleAction = toggleAction; + break; + case ITargetEntityItemAction targetEntity: + ValidateBehaviorType(BehaviorType.TargetEntity, typeof(ITargetEntityItemAction)); + BehaviorType = BehaviorType.TargetEntity; + TargetEntityAction = targetEntity; + break; + case ITargetPointItemAction targetPointAction: + ValidateBehaviorType(BehaviorType.TargetPoint, typeof(ITargetPointItemAction)); + BehaviorType = BehaviorType.TargetPoint; + TargetPointAction = targetPointAction; + break; + default: + BehaviorType = BehaviorType.None; + Logger.ErrorS("action", "unrecognized behavior type for action with name {0}", Name); + break; + } + } + } + + /// + /// Determines how the action icon appears in the hotbar for item actions. + /// + public enum ItemActionIconStyle : byte + { + /// + /// The default - the item icon will be big with a small action icon in the corner + /// + BigItem, + /// + /// The action icon will be big with a small item icon in the corner + /// + BigAction, + /// + /// BigAction but no item icon will be shown in the corner. + /// + NoItem + } +} diff --git a/Content.Shared/Alert/AlertManager.cs b/Content.Shared/Alert/AlertManager.cs index 392b731a83..64afd7a998 100644 --- a/Content.Shared/Alert/AlertManager.cs +++ b/Content.Shared/Alert/AlertManager.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Linq; -using Content.Shared.Prototypes.Kitchen; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Prototypes; @@ -8,41 +6,28 @@ using Robust.Shared.Prototypes; namespace Content.Shared.Alert { /// - /// Provides access to all configured alerts. Ability to encode/decode a given state - /// to an int. + /// Provides access to all configured alerts by alert type. /// public class AlertManager { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - private AlertPrototype[] _orderedAlerts; - private Dictionary _typeToIndex; + private Dictionary _typeToAlert; public void Initialize() { - // order by type value so we can map between the id and an integer index and use - // the index for compact alert change messages - _orderedAlerts = - _prototypeManager.EnumeratePrototypes() - .OrderBy(prototype => prototype.AlertType).ToArray(); - _typeToIndex = new Dictionary(); + _typeToAlert = new Dictionary(); - for (var i = 0; i < _orderedAlerts.Length; i++) + foreach (var alert in _prototypeManager.EnumeratePrototypes()) { - if (i > byte.MaxValue) - { - Logger.ErrorS("alert", "too many alerts for byte encoding ({0})! encoding will need" + - " to be changed to use a ushort rather than byte", _typeToIndex.Count); - break; - } - if (!_typeToIndex.TryAdd(_orderedAlerts[i].AlertType, (byte) i)) + if (!_typeToAlert.TryAdd(alert.AlertType, alert)) { Logger.ErrorS("alert", - "Found alert with duplicate id {0}", _orderedAlerts[i].AlertType); + "Found alert with duplicate alertType {0} - all alerts must have" + + " a unique alerttype, this one will be skipped", alert.AlertType); } } - } /// @@ -51,74 +36,7 @@ namespace Content.Shared.Alert /// true if found public bool TryGet(AlertType alertType, out AlertPrototype alert) { - if (_typeToIndex.TryGetValue(alertType, out var idx)) - { - alert = _orderedAlerts[idx]; - return true; - } - - alert = null; - return false; - } - - /// - /// Tries to get the alert of the indicated type along with its encoding - /// - /// true if found - public bool TryGetWithEncoded(AlertType alertType, out AlertPrototype alert, out byte encoded) - { - if (_typeToIndex.TryGetValue(alertType, out var idx)) - { - alert = _orderedAlerts[idx]; - encoded = (byte) idx; - return true; - } - - alert = null; - encoded = 0; - return false; - } - - /// - /// Tries to get the compact encoded representation of this alert - /// - /// true if successful - public bool TryEncode(AlertPrototype alert, out byte encoded) - { - return TryEncode(alert.AlertType, out encoded); - } - - /// - /// Tries to get the compact encoded representation of the alert with - /// the indicated id - /// - /// true if successful - public bool TryEncode(AlertType alertType, out byte encoded) - { - if (_typeToIndex.TryGetValue(alertType, out var idx)) - { - encoded = idx; - return true; - } - - encoded = 0; - return false; - } - - /// - /// Tries to get the alert from the encoded representation - /// - /// true if successful - public bool TryDecode(byte encodedAlert, out AlertPrototype alert) - { - if (encodedAlert >= _orderedAlerts.Length) - { - alert = null; - return false; - } - - alert = _orderedAlerts[encodedAlert]; - return true; + return _typeToAlert.TryGetValue(alertType, out alert); } } } diff --git a/Content.Shared/Alert/AlertPrototype.cs b/Content.Shared/Alert/AlertPrototype.cs index c9e7427ae1..9a6e0bd674 100644 --- a/Content.Shared/Alert/AlertPrototype.cs +++ b/Content.Shared/Alert/AlertPrototype.cs @@ -1,4 +1,6 @@ using System; +using Content.Shared.Interfaces; +using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -70,6 +72,11 @@ namespace Content.Shared.Alert /// public bool SupportsSeverity => MaxSeverity != -1; + /// + /// Defines what to do when the alert is clicked. + /// + public IAlertClick OnClick { get; private set; } + public void LoadFrom(YamlMappingNode mapping) { var serializer = YamlObjectSerializer.NewReader(mapping); @@ -94,6 +101,9 @@ namespace Content.Shared.Alert Category = alertCategory; } AlertKey = new AlertKey(AlertType, Category); + + if (IoCManager.Resolve().IsClientModule) return; + serializer.DataField(this, x => x.OnClick, "onClick", null); } /// severity level, if supported by this alert @@ -143,30 +153,26 @@ namespace Content.Shared.Alert [Serializable, NetSerializable] public struct AlertKey { - private readonly AlertType? _alertType; - private readonly AlertCategory? _alertCategory; + public readonly AlertType? AlertType; + public readonly AlertCategory? AlertCategory; /// NOTE: if the alert has a category you must pass the category for this to work - /// properly as a key. I.e. if the alert has a category and you pass only the ID, and you - /// compare this to another AlertKey that has both the category and the same ID, it will not consider them equal. + /// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you + /// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal. public AlertKey(AlertType? alertType, AlertCategory? alertCategory) { - // if there is a category, ignore the alerttype. - if (alertCategory != null) - { - _alertCategory = alertCategory; - _alertType = null; - } - else - { - _alertCategory = null; - _alertType = alertType; - } + AlertCategory = alertCategory; + AlertType = alertType; } public bool Equals(AlertKey other) { - return _alertType == other._alertType && _alertCategory == other._alertCategory; + // compare only on alert category if we have one + if (AlertCategory.HasValue) + { + return other.AlertCategory == AlertCategory; + } + return AlertType == other.AlertType && AlertCategory == other.AlertCategory; } public override bool Equals(object obj) @@ -176,11 +182,14 @@ namespace Content.Shared.Alert public override int GetHashCode() { - return HashCode.Combine(_alertType, _alertCategory); + // use only alert category if we have one + if (AlertCategory.HasValue) return AlertCategory.GetHashCode(); + return AlertType.GetHashCode(); } /// alert category, must not be null - /// An alert key for the provided alert category + /// An alert key for the provided alert category. This must only be used for + /// queries and never storage, as it is lacking an alert type. public static AlertKey ForCategory(AlertCategory category) { return new(null, category); diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index b4a3866e0c..8c2afb48d8 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -16,8 +16,10 @@ /// /// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML + /// NOTE: Using byte for a compact encoding when sending this in messages, can upgrade + /// to ushort /// - public enum AlertType + public enum AlertType : byte { Error, LowPressure, diff --git a/Content.Shared/Alert/IAlertClick.cs b/Content.Shared/Alert/IAlertClick.cs new file mode 100644 index 0000000000..b6240edde5 --- /dev/null +++ b/Content.Shared/Alert/IAlertClick.cs @@ -0,0 +1,37 @@ +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; + +namespace Content.Shared.Alert +{ + /// + /// Defines what should happen when an alert is clicked. + /// + public interface IAlertClick : IExposeData + { + + /// + /// Invoked on server side when user clicks an alert. + /// + /// + void AlertClicked(ClickAlertEventArgs args); + } + + public class ClickAlertEventArgs : EventArgs + { + /// + /// Player clicking the alert + /// + public readonly IEntity Player; + /// + /// Alert that was clicked + /// + public readonly AlertPrototype Alert; + + public ClickAlertEventArgs(IEntity player, AlertPrototype alert) + { + Player = player; + Alert = alert; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs b/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs index bd944f2958..b7561b4f89 100644 --- a/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs +++ b/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Items; using JetBrains.Annotations; using Robust.Shared.Serialization; @@ -26,7 +27,7 @@ namespace Content.Shared.GameObjects.Components.Inventory /// Uniquely identifies a single slot in an inventory. /// [Serializable, NetSerializable] - public enum Slots + public enum Slots : byte { NONE = 0, HEAD, @@ -148,6 +149,5 @@ namespace Content.Shared.GameObjects.Components.Inventory "Hands_left", "Hands_right", }; - } } diff --git a/Content.Shared/GameObjects/Components/Inventory/SharedInventoryComponent.cs b/Content.Shared/GameObjects/Components/Inventory/SharedInventoryComponent.cs index dfea45389e..608941b390 100644 --- a/Content.Shared/GameObjects/Components/Inventory/SharedInventoryComponent.cs +++ b/Content.Shared/GameObjects/Components/Inventory/SharedInventoryComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.Serialization; @@ -47,6 +48,10 @@ namespace Content.Shared.GameObjects.Components.Inventory InventoryInstance = DynamicTypeFactory.CreateInstance(type); } + /// true if the item is equipped to an equip slot (NOT inside an equipped container + /// like inside a backpack) + public abstract bool IsEquipped(IEntity item); + [Serializable, NetSerializable] protected class InventoryComponentState : ComponentState { diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs index c8eb3a5255..83e24cdadb 100644 --- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -1,6 +1,7 @@ #nullable enable using System; using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Items @@ -9,6 +10,9 @@ namespace Content.Shared.GameObjects.Components.Items { public sealed override string Name => "Hands"; public sealed override uint? NetID => ContentNetIDs.HANDS; + + /// true if the item is in one of the hands + public abstract bool IsHolding(IEntity item); } [Serializable, NetSerializable] diff --git a/Content.Shared/GameObjects/Components/Mobs/IActionAttempt.cs b/Content.Shared/GameObjects/Components/Mobs/IActionAttempt.cs new file mode 100644 index 0000000000..3dd493804c --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/IActionAttempt.cs @@ -0,0 +1,208 @@ +#nullable enable + +using Content.Shared.Actions; +using Robust.Shared.GameObjects; +using Robust.Shared.Input.Binding; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; + +namespace Content.Shared.GameObjects.Components.Mobs +{ + /// + /// 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. + /// + public interface IActionAttempt + { + /// + /// Action Prototype attempting to be performed + /// + BaseActionPrototype Action { get; } + ComponentMessage PerformInstantActionMessage(); + ComponentMessage PerformToggleActionMessage(bool on); + ComponentMessage PerformTargetPointActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args); + ComponentMessage PerformTargetEntityActionMessage(PointerInputCmdHandler.PointerInputCmdArgs args); + /// + /// Tries to get the action state for this action from the actionsComponent, returning + /// true if found. + /// + bool TryGetActionState(SharedActionsComponent actionsComponent, out ActionState actionState); + + /// + /// Toggles the action within the provided action component + /// + void ToggleAction(SharedActionsComponent actionsComponent, bool toggleOn); + + /// + /// Perform the server-side logic of the action + /// + void DoInstantAction(IEntity player); + + /// + /// Perform the server-side logic of the toggle action + /// + /// true if the attempt to toggle was successful, meaning the state should be toggled to the + /// indicated value + bool DoToggleAction(IEntity player, bool on); + + /// + /// Perform the server-side logic of the target point action + /// + void DoTargetPointAction(IEntity player, EntityCoordinates target); + + /// + /// Perform the server-side logic of the target entity action + /// + 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}"; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/ItemActionsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/ItemActionsComponent.cs new file mode 100644 index 0000000000..fa599e8cb5 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/ItemActionsComponent.cs @@ -0,0 +1,249 @@ +#nullable enable +using System; +using System.Collections.Generic; +using Content.Shared.Actions; +using Content.Shared.GameObjects.Components.Inventory; +using Content.Shared.GameObjects.Components.Items; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Log; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Mobs +{ + /// + /// 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. + /// + [RegisterComponent] + public class ItemActionsComponent : Component, IEquippedHand, IEquipped, IUnequipped, IUnequippedHand + { + public override string Name => "ItemActions"; + + /// + /// 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 + /// + public IEnumerable ActionConfigs => _actionConfigs; + + public bool IsEquipped => InSlot != EquipmentSlotDefines.Slots.NONE || InHand != null; + /// + /// Slot currently equipped to, NONE if not equipped to an equip slot. + /// + public EquipmentSlotDefines.Slots InSlot { get; private set; } + /// + /// hand it's currently in, null if not in a hand. + /// + public SharedHand? InHand { get; private set; } + + /// + /// Entity currently holding this in hand or equip slot. Null if not held. + /// + public IEntity? Holder { get; private set; } + // cached actions component of the holder, since we'll need to access it frequently + private SharedActionsComponent? _holderActionsComponent; + private List _actionConfigs = new(); + // State of all actions provided by this item. + private readonly Dictionary _actions = new(); + + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _actionConfigs,"actions", new List()); + + foreach (var actionConfig in _actionConfigs) + { + GrantOrUpdate(actionConfig.ActionType, actionConfig.Enabled, false, null); + } + } + + 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); + } + } + + /// + /// 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. + /// + /// action being granted / updated + /// 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) + /// 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 + /// 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. + /// If true, setting cooldown to null will clear the current cooldown + /// of this action rather than preserving it. + 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); + } + + /// + /// Update the cooldown of a particular action. Actions on cooldown cannot be used. + /// Setting the cooldown to null clears it. + /// + public void Cooldown(ItemActionType actionType, (TimeSpan start, TimeSpan end)? cooldown = null) + { + GrantOrUpdate(actionType, cooldown: cooldown, clearCooldown: true); + } + + /// + /// Enable / disable this action. Disabled actions are still shown to the player, but + /// shown as not usable. + /// + public void SetEnabled(ItemActionType actionType, bool enabled) + { + GrantOrUpdate(actionType, enabled); + } + + /// + /// Toggle the action on / off + /// + public void Toggle(ItemActionType actionType, bool toggleOn) + { + GrantOrUpdate(actionType, toggleOn: toggleOn); + } + + public void EquippedHand(EquippedHandEventArgs eventArgs) + { + // this entity cannot be granted actions if no actions component + if (!eventArgs.User.TryGetComponent(out var actionsComponent)) + return; + Holder = eventArgs.User; + _holderActionsComponent = actionsComponent; + InSlot = EquipmentSlotDefines.Slots.NONE; + InHand = eventArgs.Hand; + GrantOrUpdateAllToHolder(); + } + + public void Equipped(EquippedEventArgs eventArgs) + { + // this entity cannot be granted actions if no actions component + if (!eventArgs.User.TryGetComponent(out var actionsComponent)) + return; + Holder = eventArgs.User; + _holderActionsComponent = actionsComponent; + InSlot = eventArgs.Slot; + InHand = null; + GrantOrUpdateAllToHolder(); + } + + public void Unequipped(UnequippedEventArgs eventArgs) + { + RevokeAllFromHolder(); + Holder = null; + _holderActionsComponent = null; + InSlot = EquipmentSlotDefines.Slots.NONE; + InHand = null; + + } + + public void UnequippedHand(UnequippedHandEventArgs eventArgs) + { + RevokeAllFromHolder(); + Holder = null; + _holderActionsComponent = null; + InSlot = EquipmentSlotDefines.Slots.NONE; + InHand = null; + } + } + + /// + /// Configuration for an item action provided by an item. + /// + public class ItemActionConfig : IExposeData + { + public ItemActionType ActionType { get; private set; } + /// + /// Whether action is initially enabled on this item. Defaults to true. + /// + public bool Enabled { get; private set; } + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(this, x => x.ActionType, "actionType", ItemActionType.Error); + if (ActionType == ItemActionType.Error) + { + Logger.ErrorS("action", "invalid or missing actionType"); + } + serializer.DataField(this, x => x.Enabled, "enabled", true); + } + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedActionsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedActionsComponent.cs new file mode 100644 index 0000000000..5a9f77a27f --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/SharedActionsComponent.cs @@ -0,0 +1,652 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.Actions; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.GameObjects.Components.Mobs +{ + /// + /// 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. + /// + 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; + + /// + /// Actions granted to this entity as soon as they spawn, regardless + /// of the status of the entity. + /// + public IEnumerable InnateActions => _innateActions ?? Enumerable.Empty(); + private List? _innateActions; + + + // 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 _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> _itemActions = + new(); + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _innateActions,"innateActions", null); + } + protected override void Startup() + { + foreach (var actionType in InnateActions) + { + Grant(actionType); + } + } + + + public override ComponentState GetComponentState() + { + 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; + } + + /// + /// Gets the action state associated with the specified action type, if it has been + /// granted, has a cooldown, or has been toggled on + /// + /// false if not found for this action type + public bool TryGetActionState(ActionType actionType, out ActionState actionState) + { + return _actions.TryGetValue(actionType, out actionState); + } + + /// + /// Gets the item action states associated with the specified item if any have been granted + /// and not yet revoked. + /// + /// false if no states found for this item action type. + public bool TryGetItemActionStates(EntityUid item, [NotNullWhen((true))] out IReadOnlyDictionary? itemActionStates) + { + if (_itemActions.TryGetValue(item, out var actualItemActionStates)) + { + itemActionStates = actualItemActionStates; + return true; + } + + itemActionStates = null; + return false; + } + + /// + public bool TryGetItemActionStates(IEntity item, + [NotNullWhen((true))] out IReadOnlyDictionary? itemActionStates) + { + return TryGetItemActionStates(item.Uid, out itemActionStates); + } + + /// + /// Gets the item action state associated with the specified item action type for the specified item, if it has any. + /// + /// false if no state found for this item action type for this item + 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; + } + + /// true if the action is granted and enabled (if item action, if granted and enabled for any item) + 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; + } + + /// 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. + public bool IsGranted(ItemActionType actionType) + { + return _itemActions.Values.SelectMany(vals => vals) + .Any(state => state.Key == actionType && state.Value.Enabled); + } + + /// + public bool TryGetItemActionState(ItemActionType actionType, IEntity item, out ActionState actionState) + { + return TryGetItemActionState(actionType, item.Uid, out actionState); + } + + /// + /// Gets all action types that have non-initial state (granted, have a cooldown, or toggled on). + /// + public IReadOnlyDictionary ActionStates() + { + return _actions; + } + + /// + /// 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! + /// + public IReadOnlyDictionary> ItemActionStates() + { + return _itemActions; + } + + /// + /// Creates or updates the action state with the supplied non-null values + /// + 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(); + } + + /// + /// 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. + /// + public void GrantOrUpdateItemAction(ItemActionType actionType, EntityUid item, ActionState state) + { + if (!_itemActions.TryGetValue(item, out var itemStates)) + { + itemStates = new Dictionary(); + _itemActions[item] = itemStates; + } + + itemStates[actionType] = state; + AfterActionChanged(); + Dirty(); + } + + /// + /// Intended to only be used by ItemActionsComponent. Revokes the item action so the player no longer + /// sees it and can no longer use it. + /// + public void RevokeItemAction(ItemActionType actionType, EntityUid item) + { + if (!_itemActions.TryGetValue(item, out var itemStates)) + return; + + itemStates.Remove(actionType); + AfterActionChanged(); + Dirty(); + } + + /// + /// 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. + /// + /// 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 + /// 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. + public void Grant(ActionType actionType, bool? toggleOn = null, + (TimeSpan start, TimeSpan end)? cooldown = null) + { + GrantOrUpdate(actionType, true, toggleOn, cooldown); + } + + /// + /// 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). + /// + /// action will be shown toggled to this value + /// action cooldown will be set to this value (by default the cooldown is cleared). + public void GrantFromInitialState(ActionType actionType, bool toggleOn = false, + (TimeSpan start, TimeSpan end)? cooldown = null) + { + _actions.Remove(actionType); + Grant(actionType, toggleOn, cooldown); + } + + /// + /// 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. + /// + public void Cooldown(ActionType actionType, (TimeSpan start, TimeSpan end)? cooldown) + { + GrantOrUpdate(actionType, cooldown: cooldown, clearCooldown: true); + } + + /// + /// Revokes the ability to perform the action for this entity. Current state + /// of the action (toggle / cooldown) is preserved. + /// + 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(); + } + + /// + /// Toggles the action to the specified value. Works even if the action is on cooldown + /// or revoked. + /// + public void ToggleAction(ActionType actionType, bool toggleOn) + { + Grant(actionType, toggleOn); + } + + /// + /// 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. + /// + public void ExpireCooldowns() + { + + // actions - only clear cooldowns and remove associated action state + // if the action is at initial state + var actionTypesToRemove = new List(); + 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); + } + } + + /// + /// Invoked after a change has been made to an action state in this component. + /// + protected virtual void AfterActionChanged() { } + } + + [Serializable, NetSerializable] + public class ActionComponentState : ComponentState + { + public Dictionary Actions; + public Dictionary> ItemActions; + + public ActionComponentState(Dictionary actions, + Dictionary> itemActions) : base(ContentNetIDs.ACTIONS) + { + Actions = actions; + ItemActions = itemActions; + } + } + + [Serializable, NetSerializable] + public struct ActionState + { + /// + /// False if this action is not currently allowed to be performed. + /// + public bool Enabled; + /// + /// 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. + /// + public bool ToggledOn; + public (TimeSpan start, TimeSpan end)? Cooldown; + public bool IsAtInitialState => IsAtInitialStateExceptCooldown && !Cooldown.HasValue; + public bool IsAtInitialStateExceptCooldown => !Enabled && !ToggledOn; + + /// + /// Creates an action state for the indicated type, defaulting to the + /// initial state. + /// + 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; + } + } + + + /// + /// A message that tells server we want to run the instant action logic. + /// + [Serializable, NetSerializable] + public class PerformInstantActionMessage : PerformActionMessage + { + public override BehaviorType BehaviorType => BehaviorType.Instant; + + public PerformInstantActionMessage(ActionType actionType) : base(actionType) + { + } + } + + /// + /// A message that tells server we want to run the instant action logic. + /// + [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 + { + /// + /// Targeted local coordinates + /// + EntityCoordinates Target { get; } + } + + public interface ITargetEntityActionMessage + { + /// + /// Targeted entity + /// + EntityUid Target { get; } + } + + /// + /// A message that tells server we want to toggle on the indicated action. + /// + [Serializable, NetSerializable] + public class PerformToggleOnActionMessage : PerformActionMessage, IToggleActionMessage + { + public override BehaviorType BehaviorType => BehaviorType.Toggle; + public bool ToggleOn => true; + public PerformToggleOnActionMessage(ActionType actionType) : base(actionType) { } + } + + /// + /// A message that tells server we want to toggle off the indicated action. + /// + [Serializable, NetSerializable] + public class PerformToggleOffActionMessage : PerformActionMessage, IToggleActionMessage + { + public override BehaviorType BehaviorType => BehaviorType.Toggle; + public bool ToggleOn => false; + public PerformToggleOffActionMessage(ActionType actionType) : base(actionType) { } + } + + /// + /// A message that tells server we want to toggle on the indicated action. + /// + [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) { } + } + + /// + /// A message that tells server we want to toggle off the indicated action. + /// + [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) { } + } + + /// + /// A message that tells server we want to target the provided point with a particular action. + /// + [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; + } + } + + /// + /// A message that tells server we want to target the provided point with a particular action. + /// + [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; + } + } + + /// + /// A message that tells server we want to target the provided entity with a particular action. + /// + [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; + } + } + + /// + /// A message that tells server we want to target the provided entity with a particular action. + /// + [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; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedAlertsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedAlertsComponent.cs index 162dacd1c5..30b24fdb14 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedAlertsComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedAlertsComponent.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; using Content.Shared.Alert; using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Serialization; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Shared.GameObjects.Components.Mobs @@ -18,16 +15,30 @@ namespace Content.Shared.GameObjects.Components.Mobs /// public abstract class SharedAlertsComponent : Component { - private static readonly AlertState[] NO_ALERTS = new AlertState[0]; - [Dependency] protected readonly AlertManager AlertManager = default!; - public override string Name => "AlertsUI"; + public override string Name => "Alerts"; public override uint? NetID => ContentNetIDs.ALERTS; - [ViewVariables] - private Dictionary _alerts = new(); + [ViewVariables] private Dictionary _alerts = new(); + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + + if (curState is not AlertsComponentState state) + { + return; + } + + _alerts = state.Alerts; + } + + public override ComponentState GetComponentState() + { + return new AlertsComponentState(_alerts); + } /// true iff an alert of the indicated alert category is currently showing public bool IsShowingAlertCategory(AlertCategory alertCategory) @@ -53,82 +64,14 @@ namespace Content.Shared.GameObjects.Components.Mobs return _alerts.ContainsKey(alertKey); } - protected IEnumerable EnumerateAlertStates() + protected IEnumerable> EnumerateAlertStates() { - return _alerts.Values.Select(alertData => alertData.AlertState); - } - - /// - /// Invokes the alert's specified callback if there is one. - /// Not intended to be used on clientside. - /// - protected void PerformAlertClickCallback(AlertPrototype alert, IEntity owner) - { - if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback)) - { - alertStateCallback.OnClickAlert?.Invoke(new ClickAlertEventArgs(owner, alert)); - } - else - { - Logger.DebugS("alert", "player {0} attempted to invoke" + - " alert click for {1} but that alert is not currently" + - " showing", owner.Name, alert.AlertType); - } - } - - /// - /// Creates a new array containing all of the current alert states. - /// - /// - protected AlertState[] CreateAlertStatesArray() - { - if (_alerts.Count == 0) return NO_ALERTS; - var states = new AlertState[_alerts.Count]; - // because I don't trust LINQ - var idx = 0; - foreach (var alertData in _alerts.Values) - { - states[idx++] = alertData.AlertState; - } - - return states; + return _alerts; } protected bool TryGetAlertState(AlertKey key, out AlertState alertState) { - if (_alerts.TryGetValue(key, out var alertData)) - { - alertState = alertData.AlertState; - return true; - } - - alertState = default; - return false; - } - - /// - /// Replace the current active alerts with the specified alerts. Any - /// OnClickAlert callbacks on the active alerts will be erased. - /// - protected void SetAlerts(AlertState[] alerts) - { - var newAlerts = new Dictionary(); - foreach (var alertState in alerts) - { - if (AlertManager.TryDecode(alertState.AlertEncoded, out var alert)) - { - newAlerts[alert.AlertKey] = new ClickableAlertState - { - AlertState = alertState - }; - } - else - { - Logger.ErrorS("alert", "unrecognized encoded alert {0}", alertState.AlertEncoded); - } - } - - _alerts = newAlerts; + return _alerts.TryGetValue(key, out alertState); } /// @@ -136,30 +79,24 @@ namespace Content.Shared.GameObjects.Components.Mobs /// it will be updated / replaced with the specified values. /// /// type of the alert to set - /// callback to invoke when ClickAlertMessage is received by the server - /// after being clicked by client. Has no effect when specified on the clientside. /// severity, if supported by the alert /// cooldown start and end, if null there will be no cooldown (and it will /// be erased if there is currently a cooldown for the alert) - public void ShowAlert(AlertType alertType, short? severity = null, OnClickAlert onClickAlert = null, - ValueTuple? cooldown = null) + public void ShowAlert(AlertType alertType, short? severity = null, ValueTuple? cooldown = null) { - if (AlertManager.TryGetWithEncoded(alertType, out var alert, out var encoded)) + if (AlertManager.TryGet(alertType, out var alert)) { if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) && - alertStateCallback.AlertState.AlertEncoded == encoded && - alertStateCallback.AlertState.Severity == severity && alertStateCallback.AlertState.Cooldown == cooldown) + alert.AlertType == alertType && + alertStateCallback.Severity == severity && alertStateCallback.Cooldown == cooldown) { - alertStateCallback.OnClickAlert = onClickAlert; return; } - _alerts[alert.AlertKey] = new ClickableAlertState - { - AlertState = new AlertState - {Cooldown = cooldown, AlertEncoded = encoded, Severity = severity}, - OnClickAlert = onClickAlert - }; + _alerts[alert.AlertKey] = new AlertState + {Cooldown = cooldown, Severity = severity}; + + AfterShowAlert(); Dirty(); @@ -212,7 +149,12 @@ namespace Content.Shared.GameObjects.Components.Mobs } /// - /// Invoked after clearing an alert prior to dirtying the control + /// Invoked after showing an alert prior to dirtying the component + /// + protected virtual void AfterShowAlert() { } + + /// + /// Invoked after clearing an alert prior to dirtying the component /// protected virtual void AfterClearAlert() { } } @@ -220,9 +162,9 @@ namespace Content.Shared.GameObjects.Components.Mobs [Serializable, NetSerializable] public class AlertsComponentState : ComponentState { - public AlertState[] Alerts; + public Dictionary Alerts; - public AlertsComponentState(AlertState[] alerts) : base(ContentNetIDs.ALERTS) + public AlertsComponentState(Dictionary alerts) : base(ContentNetIDs.ALERTS) { Alerts = alerts; } @@ -234,46 +176,19 @@ namespace Content.Shared.GameObjects.Components.Mobs [Serializable, NetSerializable] public class ClickAlertMessage : ComponentMessage { - public readonly byte EncodedAlert; + public readonly AlertType AlertType; - public ClickAlertMessage(byte encodedAlert) + public ClickAlertMessage(AlertType alertType) { Directed = true; - EncodedAlert = encodedAlert; + AlertType = alertType; } } [Serializable, NetSerializable] public struct AlertState { - public byte AlertEncoded; public short? Severity; public ValueTuple? Cooldown; } - - public struct ClickableAlertState - { - public AlertState AlertState; - public OnClickAlert OnClickAlert; - } - - public delegate void OnClickAlert(ClickAlertEventArgs args); - - public class ClickAlertEventArgs : EventArgs - { - /// - /// Player clicking the alert - /// - public readonly IEntity Player; - /// - /// Alert that was clicked - /// - public readonly AlertPrototype Alert; - - public ClickAlertEventArgs(IEntity player, AlertPrototype alert) - { - Player = player; - Alert = alert; - } - } } diff --git a/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs b/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs index 391eeb5ccd..ae4719fe9c 100644 --- a/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs +++ b/Content.Shared/GameObjects/Components/Pulling/SharedPullerComponent.cs @@ -69,17 +69,11 @@ namespace Content.Shared.GameObjects.Components.Pulling { case PullStartedMessage msg: Pulling = msg.Pulled.Owner; - if (ownerStatus != null) - { - ownerStatus.ShowAlert(AlertType.Pulling, onClickAlert: OnClickAlert); - } + ownerStatus?.ShowAlert(AlertType.Pulling); break; case PullStoppedMessage _: Pulling = null; - if (ownerStatus != null) - { - ownerStatus.ClearAlert(AlertType.Pulling); - } + ownerStatus?.ClearAlert(AlertType.Pulling); break; } } diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index d0a17dc3c0..3c54d324ed 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -86,7 +86,8 @@ public const uint SINGULARITY = 1080; public const uint CHARACTERINFO = 1081; public const uint REAGENT_GRINDER = 1082; - public const uint DAMAGEABLE = 1083; + public const uint ACTIONS = 1083; + public const uint DAMAGEABLE = 1084; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Content.Shared/GameObjects/EntitySystems/SharedActionSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedActionSystem.cs new file mode 100644 index 0000000000..f7ed7cac43 --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/SharedActionSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Shared.GameObjects.EntitySystems +{ + /// + /// Evicts action states with expired cooldowns. + /// + 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(false)) + { + comp.ExpireCooldowns(); + } + _timeSinceCooldownCheck -= CooldownCheckIntervalSeconds; + } + } +} diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index d21f722d99..3d9e61a2e0 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -41,5 +41,16 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction Arcade1 = "Arcade1"; public static readonly BoundKeyFunction Arcade2 = "Arcade2"; public static readonly BoundKeyFunction Arcade3 = "Arcade3"; + public static readonly BoundKeyFunction OpenActionsMenu = "OpenAbilitiesMenu"; + public static readonly BoundKeyFunction Hotbar0 = "Hotbar0"; + public static readonly BoundKeyFunction Hotbar1 = "Hotbar1"; + public static readonly BoundKeyFunction Hotbar2 = "Hotbar2"; + public static readonly BoundKeyFunction Hotbar3 = "Hotbar3"; + public static readonly BoundKeyFunction Hotbar4 = "Hotbar4"; + public static readonly BoundKeyFunction Hotbar5 = "Hotbar5"; + public static readonly BoundKeyFunction Hotbar6 = "Hotbar6"; + public static readonly BoundKeyFunction Hotbar7 = "Hotbar7"; + public static readonly BoundKeyFunction Hotbar8 = "Hotbar8"; + public static readonly BoundKeyFunction Hotbar9 = "Hotbar9"; } } diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IActivate.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IActivate.cs index 07b0b9b2cf..80bf4c05e5 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IActivate.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IActivate.cs @@ -6,8 +6,11 @@ using Robust.Shared.Interfaces.GameObjects; namespace Content.Shared.Interfaces.GameObjects.Components { /// - /// This interface gives components behavior when being activated in the world when the user - /// is in range and has unobstructed access to the target entity (allows inside blockers). + /// This interface gives components behavior when being activated (by default, + /// this is done via the "E" key) when the user is in range and has unobstructed access to the target entity + /// (allows inside blockers). This includes activating an object in the world as well as activating an + /// object in inventory. Unlike IUse, this can be performed on entities that aren't in the active hand, + /// even when the active hand is currently holding something else. /// public interface IActivate { diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs index 9c5724fb28..55ca853924 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs @@ -7,8 +7,9 @@ using Robust.Shared.Map; namespace Content.Shared.Interfaces.GameObjects.Components { /// - /// This interface gives components a behavior when clicking on another object and no interaction occurs, - /// at any range. + /// This interface gives components a behavior when their entity is in the active hand, when + /// clicking on another object and no interaction occurs, at any range. This includes + /// clicking on an object in the world as well as clicking on an object in inventory. /// public interface IAfterInteract { diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquipped.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquipped.cs index f2c0ae0df6..f5b4cd5685 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquipped.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquipped.cs @@ -7,22 +7,35 @@ using Robust.Shared.Interfaces.GameObjects; namespace Content.Shared.Interfaces.GameObjects.Components { /// - /// This interface gives components behavior when their owner is put in an inventory slot. + /// This interface gives components behavior when their entity is put in a non-hand inventory slot, + /// regardless of where it came from. This includes moving the entity from a hand slot into a non-hand slot + /// (which would also fire ). + /// + /// This DOES NOT fire when putting the entity into a hand slot (), nor + /// does it fire when putting the entity into held/equipped storage. /// public interface IEquipped { void Equipped(EquippedEventArgs eventArgs); } - public class EquippedEventArgs : EventArgs + public abstract class UserEventArgs : EventArgs { - public EquippedEventArgs(IEntity user, EquipmentSlotDefines.Slots slot) + public IEntity User { get; } + + protected UserEventArgs(IEntity user) { User = user; + } + } + + public class EquippedEventArgs : UserEventArgs + { + public EquippedEventArgs(IEntity user, EquipmentSlotDefines.Slots slot) : base(user) + { Slot = slot; } - public IEntity User { get; } public EquipmentSlotDefines.Slots Slot { get; } } diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquippedHand.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquippedHand.cs new file mode 100644 index 0000000000..768149c4f7 --- /dev/null +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IEquippedHand.cs @@ -0,0 +1,64 @@ +using System; +using Content.Shared.GameObjects.Components.Inventory; +using Content.Shared.GameObjects.Components.Items; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.Interfaces.GameObjects.Components +{ + /// + /// This interface gives components behavior when their entity is put in a hand inventory slot, + /// even if it came from another hand slot (which would also fire ). + /// This includes moving the entity from a non-hand slot into a hand slot + /// (which would also fire ). + /// + public interface IEquippedHand + { + void EquippedHand(EquippedHandEventArgs eventArgs); + } + + public class EquippedHandEventArgs : UserEventArgs + { + public EquippedHandEventArgs(IEntity user, SharedHand hand) : base(user) + { + Hand = hand; + } + + public SharedHand Hand { get; } + } + + /// + /// Raised when putting the entity into a hand slot + /// + [PublicAPI] + public class EquippedHandMessage : EntitySystemMessage + { + /// + /// If this message has already been "handled" by a previous system. + /// + public bool Handled { get; set; } + + /// + /// Entity that equipped the item. + /// + public IEntity User { get; } + + /// + /// Item that was equipped. + /// + public IEntity Equipped { get; } + + /// + /// Hand the item is going into. + /// + public SharedHand Hand { get; } + + public EquippedHandMessage(IEntity user, IEntity equipped, SharedHand hand) + { + User = user; + Equipped = equipped; + Hand = hand; + } + } +} diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs index aadfa594db..d233957b76 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs @@ -8,8 +8,9 @@ using Robust.Shared.Map; namespace Content.Shared.Interfaces.GameObjects.Components { /// - /// This interface gives components behavior when being clicked on by a user with an object in their hand - /// who is in range and has unobstructed reach of the target entity (allows inside blockers). + /// This interface gives components behavior when their entity is clicked on by a user with an object in their hand + /// who is in range and has unobstructed reach of the target entity (allows inside blockers). This includes + /// clicking on an object in the world as well as clicking on an object in inventory. /// public interface IInteractUsing { diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequipped.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequipped.cs index 5d9cb951ce..d9037fd93d 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequipped.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequipped.cs @@ -7,22 +7,25 @@ using Robust.Shared.Interfaces.GameObjects; namespace Content.Shared.Interfaces.GameObjects.Components { /// - /// This interface gives components behavior when their owner is removed from an inventory slot. + /// This interface gives components behavior when their entity is removed from a non-hand inventory slot, + /// regardless of where it's going to. This includes moving the entity from a non-hand slot into a hand slot + /// (which would also fire ). + /// + /// This DOES NOT fire when removing the entity from a hand slot (), nor + /// does it fire when removing the entity from held/equipped storage. /// public interface IUnequipped { void Unequipped(UnequippedEventArgs eventArgs); } - public class UnequippedEventArgs : EventArgs + public class UnequippedEventArgs : UserEventArgs { - public UnequippedEventArgs(IEntity user, EquipmentSlotDefines.Slots slot) + public UnequippedEventArgs(IEntity user, EquipmentSlotDefines.Slots slot) : base(user) { - User = user; Slot = slot; } - public IEntity User { get; } public EquipmentSlotDefines.Slots Slot { get; } } @@ -43,19 +46,19 @@ namespace Content.Shared.Interfaces.GameObjects.Components public IEntity User { get; } /// - /// Item that was equipped. + /// Item that was unequipped. /// - public IEntity Equipped { get; } + public IEntity Unequipped { get; } /// /// Slot where the item was removed from. /// public EquipmentSlotDefines.Slots Slot { get; } - public UnequippedMessage(IEntity user, IEntity equipped, EquipmentSlotDefines.Slots slot) + public UnequippedMessage(IEntity user, IEntity unequipped, EquipmentSlotDefines.Slots slot) { User = user; - Equipped = equipped; + Unequipped = unequipped; Slot = slot; } } diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequippedHand.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequippedHand.cs new file mode 100644 index 0000000000..c738752896 --- /dev/null +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUnequippedHand.cs @@ -0,0 +1,63 @@ +using System; +using Content.Shared.GameObjects.Components.Inventory; +using Content.Shared.GameObjects.Components.Items; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.Interfaces.GameObjects.Components +{ + /// + /// This interface gives components behavior when their entity is removed from a hand slot, + /// even if it is going into another hand slot (which would also fire ). + /// This includes moving the entity from a hand slot into a non-hand slot (which would also fire ). + /// + public interface IUnequippedHand + { + void UnequippedHand(UnequippedHandEventArgs eventArgs); + } + + public class UnequippedHandEventArgs : UserEventArgs + { + public UnequippedHandEventArgs(IEntity user, SharedHand hand) : base(user) + { + Hand = hand; + } + + public SharedHand Hand { get; } + } + + /// + /// Raised when removing the entity from an inventory slot. + /// + [PublicAPI] + public class UnequippedHandMessage : EntitySystemMessage + { + /// + /// If this message has already been "handled" by a previous system. + /// + public bool Handled { get; set; } + + /// + /// Entity that equipped the item. + /// + public IEntity User { get; } + + /// + /// Item that was unequipped. + /// + public IEntity Unequipped { get; } + + /// + /// Hand the item is removed from. + /// + public SharedHand Hand { get; } + + public UnequippedHandMessage(IEntity user, IEntity unequipped, SharedHand hand) + { + User = user; + Unequipped = unequipped; + Hand = hand; + } + } +} diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUse.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUse.cs index ec6422deab..e2028ab551 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUse.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IUse.cs @@ -6,7 +6,8 @@ using Robust.Shared.Interfaces.GameObjects; namespace Content.Shared.Interfaces.GameObjects.Components { /// - /// This interface gives components behavior when using the entity in your hands + /// This interface gives components behavior when using the entity in your active hand + /// (done by clicking the entity in the active hand or pressing the keybind that defaults to Z). /// public interface IUse { diff --git a/Content.Shared/Utility/Cooldowns.cs b/Content.Shared/Utility/Cooldowns.cs new file mode 100644 index 0000000000..04a2aa39f4 --- /dev/null +++ b/Content.Shared/Utility/Cooldowns.cs @@ -0,0 +1,28 @@ +using System; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; + +namespace Content.Shared.Utility +{ + /// + /// Utilities for working with cooldowns. + /// + public static class Cooldowns + { + /// game timing to use, otherwise will resolve using IoCManager. + /// a cooldown interval starting at GameTiming.Curtime and ending at (offset) from CurTime. + /// For example, passing TimeSpan.FromSeconds(5) will create an interval + /// from now to 5 seconds from now. + public static (TimeSpan start, TimeSpan end) FromNow(TimeSpan offset, IGameTiming gameTiming = null) + { + var now = (gameTiming ?? IoCManager.Resolve()).CurTime; + return (now, now + offset); + } + + /// + public static (TimeSpan start, TimeSpan end) SecondsFromNow(double seconds, IGameTiming gameTiming = null) + { + return FromNow(TimeSpan.FromSeconds(seconds), gameTiming); + } + } +} diff --git a/Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs b/Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs index 89ddb24c53..9f3faf4e9d 100644 --- a/Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs +++ b/Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs @@ -1,14 +1,10 @@ using System.IO; -using System.Linq; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Alert; using Content.Shared.GameObjects.Components.Mobs; -using Content.Shared.Utility; using NUnit.Framework; -using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.Tests.Server.GameObjects.Components.Mobs @@ -48,23 +44,23 @@ namespace Content.Tests.Server.GameObjects.Components.Mobs var alertsComponent = new ServerAlertsComponent(); alertsComponent = IoCManager.InjectDependencies(alertsComponent); - Assert.That(alertManager.TryGetWithEncoded(AlertType.LowPressure, out var lowpressure, out var lpencoded)); - Assert.That(alertManager.TryGetWithEncoded(AlertType.HighPressure, out var highpressure, out var hpencoded)); + Assert.That(alertManager.TryGet(AlertType.LowPressure, out var lowpressure)); + Assert.That(alertManager.TryGet(AlertType.HighPressure, out var highpressure)); alertsComponent.ShowAlert(AlertType.LowPressure); var alertState = alertsComponent.GetComponentState() as AlertsComponentState; Assert.NotNull(alertState); - Assert.That(alertState.Alerts.Length, Is.EqualTo(1)); - Assert.That(alertState.Alerts[0], Is.EqualTo(new AlertState{AlertEncoded = lpencoded})); + Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); + Assert.That(alertState.Alerts.ContainsKey(lowpressure.AlertKey)); alertsComponent.ShowAlert(AlertType.HighPressure); alertState = alertsComponent.GetComponentState() as AlertsComponentState; - Assert.That(alertState.Alerts.Length, Is.EqualTo(1)); - Assert.That(alertState.Alerts[0], Is.EqualTo(new AlertState{AlertEncoded = hpencoded})); + Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); + Assert.That(alertState.Alerts.ContainsKey(highpressure.AlertKey)); alertsComponent.ClearAlertCategory(AlertCategory.Pressure); alertState = alertsComponent.GetComponentState() as AlertsComponentState; - Assert.That(alertState.Alerts.Length, Is.EqualTo(0)); + Assert.That(alertState.Alerts.Count, Is.EqualTo(0)); } } } diff --git a/Content.Tests/Shared/Alert/AlertManagerTests.cs b/Content.Tests/Shared/Alert/AlertManagerTests.cs index 9a82d89e08..540444469b 100644 --- a/Content.Tests/Shared/Alert/AlertManagerTests.cs +++ b/Content.Tests/Shared/Alert/AlertManagerTests.cs @@ -37,25 +37,10 @@ namespace Content.Tests.Shared.Alert Assert.That(alertManager.TryGet(AlertType.HighPressure, out var highPressure)); Assert.That(highPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/highpressure.png")); - Assert.That(alertManager.TryGetWithEncoded(AlertType.LowPressure, out lowPressure, out var encodedLowPressure)); + Assert.That(alertManager.TryGet(AlertType.LowPressure, out lowPressure)); Assert.That(lowPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/lowpressure.png")); - Assert.That(alertManager.TryGetWithEncoded(AlertType.HighPressure, out highPressure, out var encodedHighPressure)); + Assert.That(alertManager.TryGet(AlertType.HighPressure, out highPressure)); Assert.That(highPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/highpressure.png")); - - Assert.That(alertManager.TryEncode(lowPressure, out var encodedLowPressure2)); - Assert.That(encodedLowPressure2, Is.EqualTo(encodedLowPressure)); - Assert.That(alertManager.TryEncode(highPressure, out var encodedHighPressure2)); - Assert.That(encodedHighPressure2, Is.EqualTo(encodedHighPressure)); - Assert.That(encodedLowPressure, Is.Not.EqualTo(encodedHighPressure)); - - Assert.That(alertManager.TryDecode(encodedLowPressure, out var decodedLowPressure)); - Assert.That(decodedLowPressure, Is.EqualTo(lowPressure)); - Assert.That(alertManager.TryDecode(encodedHighPressure, out var decodedHighPressure)); - Assert.That(decodedHighPressure, Is.EqualTo(highPressure)); - - Assert.False(alertManager.TryEncode(AlertType.Debug1, out _)); - Assert.False(alertManager.TryGetWithEncoded(AlertType.Debug1, out _, out _)); - } } } diff --git a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs index 6729028337..b557fca622 100644 --- a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs +++ b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs @@ -1,9 +1,13 @@ using System.IO; +using Content.Server.Utility; using Content.Shared.Alert; +using Content.Shared.Interfaces; using NUnit.Framework; using Robust.Shared.Interfaces.Log; +using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.UnitTesting; using YamlDotNet.RepresentationModel; @@ -22,7 +26,6 @@ namespace Content.Tests.Shared.Alert minSeverity: 0 maxSeverity: 6"; - [Test] public void TestAlertKey() { diff --git a/Resources/Audio/Voice/Human/femalescream_1.ogg b/Resources/Audio/Voice/Human/femalescream_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..c0f80a1408d74807c8e088617ea615938897fea7 GIT binary patch literal 24030 zcmagG2|QHM|1f;+%rN$yY-PwcB1^JWOjPzIk}XS?WS1?l`!E#%mf|2%RL*=f`u#?&|e`=Gu_?YXEY*2{Ki`~P*cV*df!2m3AEe9!MU z_H!k8x;dWS0Z-5+D9Fhx$SKGjAV?Z}dAs@gx%j(y2N0>S5t@Im5;e6>BY+OxkvAYT zp*;!a0I&hzAWCGpdrp^_my|6TlA5GLZ8gc=NKLxY#Oo9*-~3+}f}%4w062h3(#JoV z&~@)pb>)?a_sddsGg8SX;BTI!sM5drEAHg|_}OC*?=GSk9kUe;fOH}02uE|7QZJ4% z!q<4=<+`9>i*SCrAA?ju)>Q_D7DA+f!c^{EBeki5NAYSuC7<K|GJfI>8Z_8l(Wr(Eq%1v}r1 z8vl_#$$9Yof#YVU49(#YXloJVG7uCv5EN#W8f{Y(W>phyGZ<~V9DSDe+JDZKu+e4e zxq~_Z0a<%hlgEiUBK$dDRdcXW*PdX2GnFI+`a}_8;v?P1=iE!(9#o$%uJ&rLkZ!MF z+Ia->6i1y6$Z|`2{Qo)6StYyt-%rx{7AZi2w0w6#@ZAN`QdBLU&f~}AmrZOM=4qEsh8Spn8fTW;O z0n7CKc~(!OUEay|I) z`o^gT5-$ol|Gzg7sx1AIwgFTBS$naRF&AGt!?v7yV9Z~%XQNv#8&OeIA3ySYKkDpXDG~>2?&mH)I$Hh#^_QOG! z59Tg?K`x)FU2L9*Sylf>Vg9Mj=s@8A&>X6Yh^WSo>m=g-m*!+i#(!0b->aJ_YLzG+ zlI(gTt?+)vRACOo|E4+TZ|0}o%#XP_851vgBiZdnTG5Lf&$iO(m;cxEzcokOPZXX| zbB_Co{twL|%L{5jZK{wqT=<9Mk@t|G0mpa!PX+)$PGHjCu_Gr3zhKPJTj^CR>#@yOB34pX~sXDzivH!cmDM zHHoTknHDIflHA7(BICrA;|a`f9-qFsVgV zBJXamE>R|#_W)7sF^AK5`lDXm*4!js{hJSSIh<~kz?T3X{xN3vByb}nE&v>vOc{vL zNv2)Ii3HO&`R{_JdaC*9W?iauyQA9VX&*+i5%UDix`^)tqdJa$Pmk<5DhU9AjKDul zNk!-5fK38Wd_}wOD(Z8I>2n_M=5iU}G#t36*r|B%G~=0iM&=G9_`oQ8&r zr_4Ezn{%6gNH#RD^6FM}`E<(s3y*nUwaXAsxJ7c{=mA43__H7O1`Z^L+4p@qbs})HI%HkK>)ogs@*<6*)E|J;X3nCTW)K~Qm5ViFyWbR(g6*qfM0;vvsU$Qm-Qfm1r6Z->~s~ z@i^HXcBGl!s+#!h?|E?PqxBQ&82Zpd_}_l<_cXLRC>D@VdY3G!38AX((HPS%T{kwr z4pk|7%??$GLO%mdBGJ6#D4keUmnxCi)UQe>mdB$nCDU};gGhu}$iTQ`M{Q{hZ!{qc z%vSW^7}Vls0<=4{e0jW)J-oSGPHZB1ypS(>TuzX-y#6ejQhZKbMyG6=$W&a)@UQV>q)pB-^va%7UfMhG~iAjKYKpeIpy1yZym5Q;)xQ-`&C$+JNc zT}gDmg1#6Og>xm1sn#%p{RJoVYsZd=fdo7t@jim%s=Bf{cBuXkRD_mMFp5R0IX#j^ zla3os1Ny2CRd`|3gdH&6%Z`MXyrf=ghpFBa0p26#1yDPZOc?->@`Vvm`AKL0t9P=X z&BTBZs?F3>#b+m-AT|?{5>4T75T!ytCP=%c)6|Y6>t6&Y8lDG(*q~T7TWjf+G+BC- z3g`7ZJ~=o5g}7=W%|j1%cyzu1V3-CE%CQ2+l_YrSr4*fakU$BLjIr|9xEycFPo`_p z5t~wBjPsb68hSt{ON~EbVF&!#3_DaO1*w;G|M>qEnEbC6{r?XVg)q)yKMlQgGp_*M zzo`5K$IeeD&;JDJssDcdkC^>`MeqNomW@XV6uJL-0i*^7JlMtyoYqz$@-s?Pab#nG zf!^CxIAhw~QP+q+a-$%7vbFS7CB>kW5Q$KHphhQI>)rP^HI)*}OQxERkJfrJ5H?;c zuiw-ij-p1F_v|BesKKK;#Jqm%TQX4P^x#j&*n`(h;Ui6hj!)!sdrhH!@;W_%3S{j8 zr|uy`tm=1+t%CH27NXzr*1>*jMnLiQ1*lH>g=_b6E8o1Q?C@F`2HToD)TSl|rjQp| zRA~`DrU#{pDkH*CkPZLtMJ4?AJvOz;f1(8iYVzN6NhX>*qD7Yt?-6%HA(Px`iNO~A zzW{DW7^65~D3=5w|GSsO0ms-0?~+WZ5(!B|?bs33off??m0UXy3;%nU8Vf=Z#Zs;C z-XEfm+C)4R$KGH~&bR0RlnHG2o2uGRU3B=Gc>9v% z^SI1RJ$|G+#-22O=%gqA_DRxdJ;^&)d;OkN1$DrTl0XOK*n|&UAt7A6EpVVH91&!d zlY8XP;BenK6d0Ho-bMNMx?urZR14Fc7)vG&&Y~+hj%vX2j6(|^Wr~Y?c8l?c0-45k zIFJxUzzN4RAzTS)B&T=qS7zgy*Ys+~^H}~RzzEm_Kq50iL?lf$p5-=c0^1$-M2@?h zNl<(NpdctKKt*X{Vq(t4KYBZk6+Px0mD~6YC$`f~z0lG9OI2_??SBa}>Da$+YJyB1 z#(MngU4BU^qQb%NL!+NRt^fKpyN%3lgT-yyzuU;#BJV{}mm&6T!4pT-3=eSeFwss} zeps)$=N(}uKmRz5roEv2A@?&=akn0V*|Luw%)&vXj^^Aw8=dS zzOgk-@Fx`P_7c26Gav8_*F$2Wv{sF8PQ7^iF)QCmzm$pjw%bJ?#mh7(>y3&RDkl#N z*BgC4I2?RLzqQosX2Wx*?R#(Ip7WG>h5>n|>d`&)I`4dY0JMR4Acs{Um0#zEHW9RF z{XY6(@%V)2+~@DZBSHt0er-j&jc-(h2v4><6i@Jx-Pyof94H^$_mKbwhc>B~ z_fJo5D^Ib|?$7Hd#C#7|cX^qj%I%{G4+yqJDQQRX-K=!M_YsPIOqCx-h}|$FE@aZX z^3jwBh=SUv8e`FNc^c(nrJvPp3m%UE^XPZ$iASz{tBxc z`pZJqfq#z&b`bPl2=0OYKcyCv_(h|;WkbWTe)0ZA`y+h07XJ673q4Q!tp?rb)Y50- zx#O{qupquRYhVK?)$|KsZY#UmO~;`utSm3hfb|R0H=J|HJJ|*=`5>JkQt7%>Owz{} z@+zN(uSTinoDD7A^-5LlwZcyuA&0n?*z1Guddk}ko-bD#)fTL=S>ZRvU2$^2j%kj$ ze2Xsd8~%Oj)y`j&6E$fEzcb7xb21)V^06sL22Z`wu-;6GGC{g7WI*qzzUzy@?5)u& zzg$+3MhCP9yO`2|%TLhkd+4F!mqma=Z&GyzuQ*e?@O|CKb3%!w^_}GO+T?Vl zurxSu^$4)tOq2oSwHLx9jP8YB)lA&2-9DS?mO0sP?r002s+;Ju(`LicWbW1XwAPmy z=e?s)P?8akPe&)(qUy%Aw%QfaGY!EYih!7R&tvije{-w83rP=KAD{Ou;^Jj|r3-R= zroVu>f!ZZC13-EHIB*|rM6=L2JqwoLQ^G1gRjx8*jaSX&?CF{xLx6Ngt(f9Cw+9%x z_(d^-WmKz?b^F(H{(c+TaD4dR@`XquR&GBEbx8&Pb{_|DK#qGB) zobP%6y6W(X^+fd|6UWJm#WU)-o(ZWR-zcIMejRgVd4KuUJJ-KXo%=MHLIX$w**rf< zOTU;=T6WZDdcPM3pvX9JCQwvuWOv!vckMo-d_y!@U6$XA7Awv3vf*%4HSl>QqF2Rz zcvEi0%b_aD)t}bjc9;dnZ(t#-A*?cK@!EFp66?}O6|6V^ttRm}o)-sD-8PTN`q+j; zVy7K9T>x%tW%tU+CT5^LY(3)im$AZ*0OQ}%ZiKJu$>HERNOdD4wQA&RLIV%JKf_$Y z%jFvj0x~o!=jU7nhm@Ybxb=Oy{U%9TEYYqy?;r-C4+S?u%>~fwKdu^`Ff>}s)tU|t zU)K65&k0Zo5d7Jzf5@M8stT_@dODTAM!CP(c_CH^Q{gwp1<++$-;S-%b&Q4A%^rt) z875sVsb`&|(=bLJpSas_K=DAw127B=sVjS^o?yE`V_S|LylN5;U!F{HFdIES7e@;g z&K<^r2tQW($bHr|IFmnRqxPcVW3k6a5TL>f(3`~^;MBJHHXZ`FE{WI?PiyRIAUA%0 zczC&8Ba~6Rqh30icFq3^va1Etg-}2-#?$1)*(=6OXb1?NzPX>1y^wIi~dA$*nrN?}e`ad837IdWcFnYaUyEV`oa{ z=)0q}4{!XP-QMpVFX8QiG}F*X)wca^B3dy4N++skdDc!n-DNv{Vp969$}WJK zsk87ygU}dQiUrm-r@YV3UPq^Oa+LC4o>rp76fxb|5<5CyEo7H==c&f=_9j$dk#GRR zkFzzA^+8qxGpujHO7DA&I{mrXmAa1S^*`K;BiX+{(+JvOMO29cpf$ntKW?q{`q{% zTTZ^&T`uYX4fE5o+>A6pezyB#A`Xpos~_1C_Mh0u!(<51m^xJL?>VgX^2;Uu9*^rw zMw!>l8k(PUde>sgv1r;J>vf&MZt~+xcmib!fLg74oR_X+wio~@qU><%7p0N>BkSqa zHaLnQ0+2lo)!)l$$SgqU8exvvgrTx<{^cKba>I7_Wf-Abe>)58_-K8$>lB`66m-{V z2P~E9C@!;*FoU{|DJBR-kH9Erk&ob43@ zCRBOS`}77QDDG%nb;9sVpziyIz7;=RWQyUoS6BO4nflZz%ZzNS1>8kk-zrWmCBMqo z3eBLym{@&wNEt2bei%~Wc!6^A-derbc75>bck+TM)&&Ee9I3gY9Ii;_o>R;nvNd>h z_hhHeWx;F5-*{n?8(ZE^xenY8S{fO;11JvzED=Ddy{=%;{6&9vx%UAOEUX1Y_GCEO zXZNq+>1lD;0aYxVw;iAO$lsLS;@x?#f*VI!%N$IC$f0Sp3mR&7gBU=ML~aMi+G)VM zbc_DnZePNEl)3Wk)D?jOnEhKrMIh0U6Z>b@hCIGm{Pw_sj7zcZOBowMAn5vYqt;_d zIzZHOe?v|zy)_+qi}$A21^>N6r1wTncV#mly6BJaT3%`WI6PnU4Tn|%QcafXyoX<3 z*Tl@%g8j^2pWBBMOD39^@IPod zY3@iQ7cMA+Hw8f7aah0UtzLg1JH4BF%|7iqmAhQxLj9GK7oT0R1zp^a*it87{5E(3 z2EQBiNFTUSw`ORDSUVn~?D~VSax!8ch!pnEey?K?1>r;`*=+wsq)r?-D~_a63L zW@H5*2mk|GD-fzjX6gB1(#g%i`|~~pR0cX=qp@JH{O|=upaSE;z#-Z!K*FY&g*<@F zSNVPe2WHwV_@q28ON#0?VcuRSC1)u*F^JQ8vx4%=Y|D+uF0J`0h3!AHmz@7i!$mps z93~wh&GHmJ^9w(rFuSy-v&i>a`6vkGtVRRcJ4ZH3kgTF*^Uvuwx*5X{G%%5Mksq|l z9|k{f%xGQgFRMBQO~@q~K*5*MRo zqyN^*J*K)nVT?r=s}Y{oK#DmI7E|qr=VVv==KF?!aBg=**dwu!*K(+ED@VX~KPh=I zG3snv-2w&V_}XfEXg_8}1i zhfY3!%XFnqE+pTn+(+OhHh1nu-AC1wleB8J2xO%WCRSLQ2)6(UV(I$0D<_yomO?`*!D;F}c3gOV@t4{IARtw34!I2Wzl^ zJiE;fHT;~OrugxOUVLU z@zHAvs^q7kR9^{w=8qacp4TvC1ILTRX? zQ!hr+8Wq1(Z>1tp{AcTa2{;H>#G2Dy_%2cojEB83 zW^~wQ{wps;*|qs%INb5^`_9$OKI2DgR=rwmbJ{e)l~Kga${EXHD|K7UniWj%Myi)s zYNx#3Wen|1?$tXSr*qTNjKdqV=~K3Y@c@_}zEgHoI_Cr+U)C9E)genXgg7GVe9f}k zRVig%Xp=@@we+!@R3(1^&7WSv9zr)phjqy?2+KlBk^9KtOG3&K>8yiFqHq9*&6qyu zKV2BB{H|R}FvXn+)Q%D1a(d$G-Yu@io>wecA|GbaG#lx+e=o=TRyvaKv9#`uP?w99 ze(SL+<|u!t>iMvt3dG@L8v{_15eQNE>t~aO68S zdXz(JddDvmW@Ik!Meg}z@h;%VB5>w&-8efwCE5A*(gw9h{44p!=Mh@EBY^C!iA4(- ztT0R11hLQgAL*Ht7*$9_P8AXmU0-Og${>{o$uRXb^ChIVyfQ(-)1FC=S&?k#ZAC@Q#lzK@CteK%n73fa79cx2OjN+Z zmuv#?I;hyl)AtbrpL*WI7Xk1rT?Q1hg%Ji&PL#=r$+&=Gj~?nCZV}IkmK@4$OKrI^ zYg*h#P@nqKZFf7CvgCVI%S{8h z?dtavbo$(lKAx9mQ8|y2J0MkTcv+UK0LUJHm*4MN60wk2R*7-IY#n-tr5GbUA_%a< z4E|?_04Zh%8$d09P<{P`0l}Ahn{ILC*EZwq#_ZzqHg;tjKmT`j-j0t)cJ=$8{u)cp z`MVibr}(CJfh>D6T%%?rtueQ7JRIPw;$zypf=&AR8i~fU(!6*Dli{|>M`Im z#|9kyn7cDsPzDpC_2?P7Mza$3wnCbNqHD(&1#c+vOJAAQ;pLU}ZR3!EH&c7TVZC`e@N(1i^H+-8-ti$jRhkX2 z=Wl4rleUG+qQ18$mDuppcp_*mOnyEY{)sS}Q{k5GKQhDuLK&&k^4Vw(87?%{kMDn- z7*+N7&pL>Df-5%{)<*|GF1|vFkqMSO1hW*$w7VHmSaYT1V*q}jk`sh;UEPoY1!1-w zbsFT8=L@cXHIN8mYVWtzcKq_-YHX9q=kFcqt4QOxjhwC~kF2+cN5R|A-** zdRI0pjx%$`(sfMZHO7Tec*cjJeMkn_2qW&;`n|0Tyyx*>L^R&+y6P{w>nB*6vbf+0w4m;3WDqt;vpbC}1nOwjjkQ?;RMm_g-q5)YhXYR14VWjB)bi^TTh9BzkC4TSPpirxl6wmvjyXV@UQvrEEz+)>dM$h0K+U-mCL zvhm`)YA%-!%gxo%zcafQig0I6m-H*S9U8Yrz_9bz(05yVzkj-zGI7o{L}BTo7T;c( zH{`8~u}`nR7RbMpc{pdhG}w~fsl!_WEHJkxW$TVr)&12k!Bo(u@OI_h=d{dzj!pC%;sq_$Xy&Nw26XR0RzrXt0|xXQ zI@@r&8EJQ6TiaWbkMlGQXbqmICb&N~sB2dHdF?JA8y8(3rl4_p8PBY`B2SCbyFqp8yTE>q~01>gcH_R`GI<0+j--4Wdxw4%dTa68y*#_G5}PA#@864dqQab zQ0(&)zEw?-^KY7>w4T|+WwdfE21p5Gope}FPM#E0#HE}>v5JTwB_QiL7<5R3BLcF| z*boLxXFf30fb|c7XRhb|CS0MpGf!L99LUymc>z{%Uu|wDP9*#i&~-W}IA1+Bo2ACP z@O8K&uG?SP=27i&%c|6AdQLDnr8YbgN@<>$SBi$5gSi)nU7;*uZLOYo4!z{?Jf`1pJS3B@MVeE8qV?8Zjfcb zN-28{!WCa{PjixHc7&&M*`0;dADa?<6id3n2}0SSA0#%*E_eR1S#I9@YO#1`!DJ~V z905VFmZW?`8mw^eTjktv4@R&gpVQz&Jyo{AwSrW6E|9hK`MN&CF^+x?%`uO1{zP1o zwVSD@b4z0~@?@25=K64Y!7jxF!W~^7A)&a>d-|Wb=^DAR?%t?tum)_l_9_H0x3fKO z&xNM^9980b;cN@Xjm0}tRUrw#Tapiz4$a&xk-ZP!9-C~p=wdd>NRx+MuioXxs-2i6Yb$Vo_W(Mmo>bM z^zRfH`k=;7TWXmii@@D$%PIPtO1w#y+8`^Bl=vZu_N6+dTb+7_j0OoRwj6m4iVk zvK%h7Try#hOFQ${NP-!$x@A~1^K)HQYA6j%sa5kC3w%bRC&Y-e8J~>L@>_x|gJCY~ z9HrL}A3R%S6>4Zm3ji?}9vN6_eZSH0{GieoXUiGZNROyfaeI)tUssCVJo+D7Z6Rpm zrL?i#cU_6G_i4e<5g0xIkiHgl{%yhM$5utOn#PH5OLw0XM>if|Wyj;dO7P_^9O%ZD zHz3hJv8b$_e1J?LHk=4+8r%;`F1*KVm#Y&Jrgo7K@M9NLt#Gh{cfixPysE!G95s>! z@aab>EONXkp6$IYEfCP54hMX<=z-vq;n`3C1os#z~JG^qr!l!FmYW%Jdm8ZxKeZ0 zXPvwyECAejaL`r_Zg`Kc3&t7lQD8#Qm-={+8YaxVcHq&r;`z1|-{V|Hs+{EWafevV z=%S*sejOTrC^XQ1WDNu4G!&)#7|>m%jOfygkj4bh>RQ}Bn1`o0jp4xLL?#|&JZFYg zn-jBJE(bi~O)srn|BHN5abBk>$AI$UE41Jd$04|oC2=%4YcgU0SyT}B1vGM2pc8r- zc%YFCVESTPs2f}ewyF30ns?rXy_|5Q@WyYy1TGJx{$*9*eAa0-2HeBokrR)saJiG+ zZ6P1JX#yS-@4f849oetngCJ-VP%&(-;#~Tif2-`vs={>#1~MHG$h&*L?~}@b1Uy;l z+-bj|?v#CJJLnK|?C(1K1@x2Vb2cLfnO)~g`Ms z^gKf8eHgxVd}-f@tyXyuyw9_K)#K`Cgq+cb=!}#g_rG}RlPuj!9i74nNY>p{8BYOH zQp9m08cHKNH+zxh4L>vc=q*eP$I7EsPXI!9!O-nQ*~-}0kAi=_`pOorOrG^=0R7a6 zbr0C&Rwx(j#m=AX;E&2{1(jkbvjdXBEWU%f+!h|R>50w2kS%ewPgGcp4h)TtFm%it zSXx^jNwKf)4RBRCVW>aqwPLNod;=4?79tw)nTt~yA<87L)#OW)BZE_GIoAe{2L%uVsT)93kGE%=s)>UrV8{?>C5&Ca0&My84;yW zrm={iB(;Qr`;l1!VSuSJAGZ5}K-9c$h(_!klVn`#DGz38jrZ5CX!Q+V^gVZJVOVi- zCPbRt1muRZM8_#p!exKsMY{6fa)&)&%1^pPP8+>EYaFKxDC`bOat3Qzl`p1l$n?`n ze#zi_e5-Hzk`b1`48C^M3KfrPHcZ4PC;`f4WNx=ihH5{xWK-FdWtD~QS|NDiTBlF zbmR2tguBl4eEx_mt%H-T%}l(0$LHqU6W_mHkDR$qK6V`(i#&;hR@cmW-#+c%YEzO3 z3f`lMty_z~*FN5kWc`eQ`my2-e_a!_>}IFFbaSh3dW+4X?>QmmktLaj`F7h zimh<784^wml3o7(xj%F!Z-LcV`-dp~7_0)qy6iP+P#{i9W?OeMb-8+Ue~=2vO@Ku( z3m7Z`-u}wLENW&yQgmfEGyJ_FG%&ZeXoH@Rrj{9Ngjp zjR)Pwoj+Z^D+h})W(RD#C%m6yDXjgEt3H0p9TlTpd9Px~aw`nIf)Nfp1vmO{rGod& z(kRo|TG&sV1BN`w*flE>thyRI_xczeDB2ImFrozDKbtfLVG|zI8U-|Ppg20uk85#h zdS-HLV5s9=XJc=7b1S*ErGrYJM`bj_X%?mBYZ5P!_LoaAo+o=_fC;@7SUgCRYY)&^ zJ2K*(tyYfQ3afb9o!W7fx>yIm(=8g%@W9{}thQQ#M7^J$z-~#J{_>b^9Hn`KzxHcO zM*JmGJ;t)mZDiHI{b#aX5nw0F{Ca@NSe>+=UHX7Yz`38Hr|@vxeG=j-dy(5W#9`?7 zobQGFpGu+&}BJt)3Z2(M%sweI(eW?!CDhlT9q^AzF|Yfi#|Pd-^fW->2cy zqa}&s7VBa*=2{?l_WF;pRf`{r;NnR#0pn;bziIhBbD<=F1_NpuAZlm^baP9_+Xn|+ zJ9lNC<<{sEtje;7kxQiq02%ZNG+A}bJywbAWy|J!NXhSbWDh~zzf4BVFf7NUP<)m? z=rIo1L4U66Fph^}u`Wjy6?k~Q{kSA8P^b#GTihGCxAYM={cMAzAS!J+)nQT++mVns zRa<;*V8VU#gXJFhc&!0Qbq?QhoCupZ48WKkvO@Shx~AY-=YMFHM~S*kB;Wy9ejDho zf4OraMFs@Ehx%u7{?NqRO5I;;;fLNB?*!K&erHZ^UAHrNPoCZ4gW+g`gBbeVN z{O}1wz@P-6mc8v8#8{|UFH^DJm(srXQp?pDgC8(%X9ncVVl5u9#Y=)euhsXyF83qT zCbkNC9*Zy7|~+ zXY~#S@zlf*z1jl_c5mdqG4LHO%sMRXlC4@5(Rdr~hbYx9sX4^+iW^{iyzfLXqV2MC znp=in7X0=DxTyp6x{$N6TV1Ll&3p8#CJ4p?!3+x|Lp8oTR~Fcgrxqy^^SV&igN1<5+i-IC#MKJDG{QbZmlUn{0Jb=9a zZZLT1s~O(m^Ro`%_u<{Ic-MJAXsz>A+b$gE zFMfdy@sW+AAot+J@dSw;O;C@0BA7F08SzDHrhDoLOWU=|1}ykS1eqArA7-Z1gb4Dr zyPypG;J2{0*}R$|dxcL^1r)$SJRl{bYo1^#dwILi5lS>gmS zzJbrN#|i)fcN5&cn(4f7xoEDp3OmIi$3Vc+)u5!&97CkhhlW&QTY$7^uz18g!>kDqK2R4Pg+5n^n()C5=*8cHbkhMV@uQ;fSedR~VFbAhD~2G>sTgOwHA6F&Ft&Oifb zBBcBPfY-;S^j<3OtH5X*hrL{iY!(Ka>%<;`TX-@MG?4$}{8j1i<5iXKP1ES?S{bO{ z2Z3gW3NwIhs)?rZF}Doc;VvM2Bw%^8j=(Jf!x5@Gh9($j?g8ZTqgWO;#1eeKD6ZkN z_aU3q*T|HY29%tNMt;?f&WU=Vw}Z0r&`O>V({cwNyJ|ZUW=izzMjyVMU!A5WXk6`;T9%_X!j7m!4+B%?mx36N+Seg0Wsl2q%bqdNtO18t68AF`aG-ug z6dP@WfexnZ&tDH)uf2KKS5|zOO%ceq$M$f`5eZg^sOYg|;d^e20F>GC=~$JcEL+() zt^x+a=sc`m0?Eq=YQ_jmXvm=!fQz972Xxj~g2yTtrCE0MYwq)++TI?_K0r2ldw0kT zkcY0X6W?y{1(HpF$1Z%Ni5CN;!TK3}Vb#(u2YMiPEW}Di=ee*UD`hoWH$PU2fiI84 zz=lL*0(VHha8GsTqvnl5nRY{>Hd*Bx6ND}Qy!LvL!JZuxAMvJP8%x<~mNvk-!G)l* zKXKEQZKvroUrx`y3)hSi1Sr%z!jY`eOkNTUtawG|02;lb)+`O8bA$0V8_4As9zOUKza9D{+GtRBcpd^-EEO9T+i zK5Q3Fke`mY^o-Ov6X{^#aQI&kp4Yv9h6BDe0xJ(~d%oU2#dV77*&$hf!?HQkwSp}hzawS2LQsiVK|aG|bXpdL_7>?wsx(L<lRZX9OSD8AkbnmkyC zVF{{jyzMflPnSSdmkQimnevYA>{(yF2>0r2$ip%PD>#;&NokouN^uFtAx5Dvq_+i@ z_c@&k_H#HG(xNkpc(X6ncMWXs;U2p=(*SblpOgC>3jMfH4A>7x!}NSghk>Z?9zTX; zDf^DbK*t;TOu`USx$IT$ynRzQx$n#U*`n!!a-Z`EXNvw2W&MLn#N*scO8RDZ{1j$o5~Nw$y+4Q2`7xc+nm#_yxk! zVBH9myQE^{Xvll!IskQ>G@z`r0Ck*BOmwi<^eHYGCo9S>5L{gW&{Dj!_tV=Qv@9Y| zu3-|P7-l`G%}+lKP1y9tf+RpgZBUK@)5LdggMftu;CnG93&H6jsFb<5o<%SBK83T} zQQZ~ry*WO5@P(sB$-zE8X>WnA)-0>!56QSC4I1%0kMLF-jcE4H5`|to`4XHB^Q#4% z!=`1?`SE&Pqwd9fLsm|5Z}Tp1&+emF>1BTdKjq~^i_FnQ_=yNLpF*l(;kX?QU$QmEw00sB4D8KA_Bi)b2JXmU z^MfCX?g3OWI}FN;XrP)*-U!#s3aN*|69JC+$^%PQ2fk^A6b}+#H9@P%Vvo2RXFuU9%9gbuk73F$Coqqxnx{VMv;V0sk0D1(F)j8L&Sw2_7 zH62+TY;;jMh{;A3lyb>)rXUe<;sfd!z^_6Nc(RJR->JYrt=;FYXAT`XMld{w9iR_p zNJtW~((ztAf0n!v4p;)X3nJ>2obcapERvi1(At`0VUg(YnfUO}Pu=6-myYMshtcW4 z(HKmzycx!{d0{Dga+edyxbi^OQTJK&tc-UqZRw%Z`oaJ>|0P&nSS{f@cx&b9v+83U z3_J73{vSRx7H2&ufg+#_y&8;w zEmpvqaQsL->w9fR=-lJGS2`C;;`Dwu-OfwerOGP@9UQCTe_T8@TUvr&gJ?$p$?VJ@ zY)mWE|3~0zHWm;b7WGP%B$>O0HL!p-gg0FsS*#QRY_OZ=X7>D57TBz*ViuN0&dV>v zQf#2MQ=s$dL~-}l1Or_L4^|>XH%SL>B3dd<~Z0p>k|OFC1OY1&$_)^Ip6!k z*PpxQ3OU*LK^X#?v~!OD-PxVk60HN2w4bcx5t7DxVSPZZWFo-o{r z^w>3Ww2j18ND=~qAFGVF*&k{1hl22*+J-2c2Rx{?4Jv6?Vp9S*KlFc}`uc5ZbYSS` zcy~w3VCTe7XxShK-<8IlI8&Fn(!V!HRDTdxw)}uifn>}8>-y)R>)B?#3q245CVk%Q z0e=TY>eizp#bYsia@Q$q}1*pPQS2nSF~I0swv!0a*GCO$ppb zb?$H^eS{Lc`)5#Kl~Fd3d{W&NLCXaa#y_2dpK4j0;8-+7rVe(p3 z_%%5}4x58>SL<)iYZ=hLI(H?54!ojsXlR_osK8BT&B2Dv-nxDhyr4onVY7YP=HLOC zBgEhSQ#UBxZGXagBd5Xr&ap<09p6Jy16E%%w_6ZW1AxKG5+G$p*8vIgRZImAUHAsF zpkSitUuzii>2!rgVagToGi%@Ne)cK|0}_lIRW@x{Kn#3_f8)??Ie-OA!Ir&t0JT;> z^6Df6D0YnGN+?_#rKf+S(1Cf?eLt3=;PoGGy8s+`)`FWJI`SIlU-i`Vl*4}Q!#)jI z@@<=jVMb>k%yH$Y!9{&^Kh;n=LF?8aMGZFK5u-;;GHW=sSIR^a7M_>7|I}4`)yopq zH{LXa8xZyyP*;y%V|_EI@h1$qVj2g277{E=eQq*1Z;1 zce4P&gp{$i*8dj=66uHo@u>n-nUXUymEksUOZCxo|MWEMII~Pg{{h^O**cX}0sMfD z_7|O#hFn=ji-M8wGoX37ogcXiv}A7bvs`0(@~uZY<{NapXoru@_Ofr-03BIbPYmo? zjWb@Z8&+DDeV;elJ1KuBhPqb{3qomIFyZo`oE4aiMj+ysuk1&RuwVv|qOqjNqf}D$ zKY-Mmyl2cko&A1jWT;*C^PRI}qb=-U@^}OL zIoX9cUIubqN^w#y2!01gMlC%uO+1^0o194-p) zGjfB0<1y+7Hs}To)o}j#;(2R<*w`-n)1H-+0Za8-(pR<^5@_muz#P;R7#U4EXmMBL zb*>&cXFw=~x4<3my@-x00zSd#~nb#5E z3Rab$E?*T#yszHS^3G|nqLX5|P7_X~%Ih%C>~&Y0&73Wd`zD$#@EckZRz;jXL2TO! z0j!`v8dHu(!)XMBe0KwbUaVj@Z>l^K6Ej4Vd<#nLPkqK?Sm3ar@B)AbVwn;T1Y7n8 z;BEC7roTcePB3V_e8_JUC+GjsAKiuAc_Xr*&ZfqL5(ET3l!O>d$-VO47kiExQ8qp6 zQ5Lu9HQ2=myn{T342Xb@liq{B9DsKWK7g#ISuUbK5_3Sf>RjhE2gNVRl&2AFd&Iv_l7>dtM64+1kNK<)?a(?}Ux3QkV6HxuVzNM3R@|ZFgB5oeUajFJkrB-K?vnjFl2R3V zw5QoIQkX_l2UCj=p)}9NC8w`tM&?+F+D5o?&3=Ya{wb@4d%oIP+W^);IhaL9oITa~zwa zdikXl8|@y}Jr*H%T ze&iO<=`vAvRB^Y}{T{F~??SJM{cTU*!z#oGmN4k$K~4&uav1!;N;=b@0xiA!Utuao$O8aLH7JT z{l53#&+A$DeP8eEeZ8;y%>$qrd7ZqXQ_S1DO+eaioGSxn8nDS)nyM#b_ek~bwS~E; z)+dDZ-_5A91*#(&n}hnsqE?5nzt)y_tT8-(J7R$LPbl{9Wt<&W=OO9oq&*aPkkh0$wh1?Vd{j@>2}m}b^>GPAhpCqpQ7K-{6V(=^UP`vnj%X6e4- z0+*K!;A?}vVrjnFC)gk|a%c2*Y$`oLNg80XXtv60U4g*EpAaxOR zBpFn|gpWzE2y_8R#$!N$OBB8d?6G3|Tl?Xk35?N^V>FHsk|Ox%ZYPgW*!R?pPtARh z4El%CElv6~n)J2kdhf8uTDtQTJm}>Ix!U|kfKb`o2N~taF7%fjvbbU;UC+ULpM{?n+Lf|Q0jj(t0z%op zHNbZQ;w}X!aghHHgiDM_;~)m%BXfsK`!q3rT4vzpSU30(jUom%hybK0&^VXU1fYP^ zy?>9kdI*G)zkK#aN82LUJ@V#+EjhnBH?s!=hhlD^}mk< zSySM&5J7DNJFsgZQ2SbD@g3d%Rxhll3!te~jgt3Pq@ICMAgp7F7Uqd<-q=7I&*=N{ z03vlSJHjE55 zs9W8MZB4O+wf{;Az+T*EAHYmo3#+g1En}j=qSEHp>`q4jk0M5EsF38ay}Yx0uHAR{ zJANtM#Wa;|Ll*Etzf-}}a>0(r|A8~hrWaeXPOOWEo4QvBC8KC+E)Z3KY3a4&Z>(q# zr0r3MC-D{iku8Yp@IPiWMfG`+0e5#&_T-7~#UDz0Of3-fvu?s@2#1$*HHKCbyJ!JH z?gh7!kGT1WY*KVv$OfT(x3@WsidHKjr@uC-E$9oSZ1HYEt}WxXJ}JpBwTdewqLLwd z8%=~vGvS1+$d{35ikVG0@>}Q!M8GyaQ~;c+b^6)Hn&l?}>j-#1An=o0_$Tx0WyXaqYaIMK&uDX3ACM{GeuolmM@hS3~1xL1W z;)SVUpCYvUPVhPz{0_N-7kRrN=YY8niO41%?NmKGZ!fIi7Jct$NI6+1xjrPbYc>?u zJ{TwKapQAw=y+PtMH!ifKA(eF13aVV>79)rs~t}i8Thzk&|B~(6){jc)$X-c#?6qq zaL|xuDE&D!8|}Z^AI=gePNrm>{foA|wb1N7slVArV4dWe-f?Q>HRXx6uKgpXzsRx; zu3Mj*o4z|#5!Qv~t^L`0kpZ;HoBOd3Rer)j%gyl^-+FH@(s{E>zQS`j`6Djm64+5D zOKhmGPElBLd%shNve?R>ZP>y(rLHvM7W@&4=szx1Js~u^%6dc@_szKaeV|!p_U;y+ z%FvcrrrK$LUd4j^NBYvv7kz*=aG78hm;eE=IXjbqf5v^ezslZhnI4><^-lZg>I}BO zqJvaeE1#`oA-|R#-K$Y+R}d2?ttbuPFQQEn>k>9C)1IN~wa%8vmF!>`%LU z-}jbisGmzpz&F!6YIb6QXIX0d7M`2-jpvPpN7OAoW44_~WDrytaw(L^#aPta$tk?) zEZV@9^{S@)_LtC-2Ht&y(F9OEb`$sQHMRt z)alqr1dRMCZzwyC5KEY;4V<};oN{!7QmS>Xq*9Q@rfJOBr4(b?Rq%1$m+1F;CU@Nu z?CP$_qh%gS`y#1P3WW#%#>SrRmd9fqtKb%a`2d!O#_VA>#G%Np{k?oR_{)FL%1Za zplV<(a-kgtATx9ldWar_xG>QYHcTo!e! zAvjqYRW0d6_kh{uB4#V)dP79YPU|Z`vO?e6->gt}m1%4jY$|!|%32~&W=|UR2P(#C ze1s&X<{j6RkYpX-Rx0nP2J6558cZgrg#wqnqRQb)k%YZ&yBK%uRquo23gb6_^mzodbbGt~9T9Ci!E92)aqr_)+ z!-Teiy=W%hestDL;_UxVDR}VXES#x3o22GZ-ATMMp{qqLB z8g+FLL<^L7p+mgcRaDLop!Wb8XQby85G)7prv!;K*dze!R4iFDNCn~j&4XoI`|JCT z0sU=ie!m!sm0up*qCi`lwXPuiG+E)JnI8ztaesEeQ)1>_Gb-FUKp}5i593~l5zg9C zofqXI*L*e>&6RSr4^`2L4!Vm_KaP_Ph}-ZG2_i(`!YLX|WWPkH+Uf(`^w*RjAc4ZwGxYym0;PpwI)K-K~Ond;r03lPHp zeJWsWB)6Oe`eJ3$^?0^Gb<&v?!)jE-S75N@#ZeP{Zq{H+tHKo*>6gn>+sW;W36xI! z{vtkr%mu`qiR?QXpT8wA9EBl>Gj5mD)XV<*Z}YL47hk+#*vijpGz9Cm`nM1=KH?6W z{~koeFBvC3;qNvh{&Zt`og-@^W{zi;(5q}k@F~bI@4TT$k4V^`DCNmZlV<|HU(+F~ z+W6YP4b)!=+(u~<3cCZxU-9jc;{Q7df=>ck&Hs&mO2of8TQ`Q4Qf~EkcTKfz5C7^I z{n1|muHt}KEl8)8Yf)UUAYNdp&j}eZ3|Jrmm_nk6E*?WQD!U=;1X`WtDYJRtEf!=V zglW&b=xVpg5Ni);lAgTnU~rI}M+3gSwnOxc-M%X*#VSx1p*Pb^riagvqK;c0F#~#{ zHPiZOEa4&*lvB;|q}fomsqnL3C08;*)%l=yZLe}}Gu3RK22odeR*n3|M zj+)>6B5n3k`DJNtp-!GZShf0cP`hEqquAvjS5|7k2s&vuko{-sUh$AykGqaVM}m#O zk2}Z6E*O%);j6@sg;Xm88`wd|_&&UfA?PR>(?d}!C)3`3*7OMwf{b8x8r}bC$A>;E z>86W0lMVhA<-Y|7FC<%(tI%4uP#YGGo+4b|SM?bVXMEbVbwx|8YJ_EcTA;bDN4RE* ztK!!2;Di2!CAkMBH9h<>n|gixL?J((P|BZus*rtWcJ)SQFXN@$0axek&BMlebmi5q zqYTAJw0yf8KQF13sTMjB1CtkNk640{kac531nxQaXEzGi-~Ibs4B-Ub(~z44VhR=& zaQZ)8%%MSsGfD3+TuP_ezYP6Xw#m_|fWeDa*f7KIWNu${1=b0D!El{|>o<8Z`ek71 zS4K;(>9?EGvdr?Wm1ghf$^!}7iCsk(RD}mqAE3#cZI`XFcK&BR^YoQ=n=Gp3#mL4} zgpvDIOwECkj7mMx{r6A;?Pr$t-&%;()Si&VbcrQ-#Zs$i^^j6yXcPLcy==l1&h^m?B}0Nj!vy!NMWTZmew1$@i-bb-jQ03vOmlBR?87fv$REe1b} zP%&%tikboe=4zL-;FixcWXE$S_48sF-){ z-fRbuNOD3WDIm7}x0<|vxvPBIwZJuGANBEBViw+}(JkU`TFI`STZ*jh+9g^4Tyj02 zs<7<(O7(KzU+8|rdbMA9u&ptvaXfak^<0ZIu+xt78RD|sUw^(UVj>00zYMKr;Y7cH zGmJvw-F|uhplEUyw9pMtQB{_Y9h_MfAPDe7a&T5quVMi>pdFVf-SbxEo4Hi&X z*?aCN9iFVz3sn`n{Tl5CP7{dDx_COk+*+T9R>^mz`Ph_3IbF^(G`;)fr8<|`6mU)n zz*&z+n&IH3H3!y7rd<-b1X(AKjqGIXCc7Q)NqX5=##F*c#}o9)D289}>o88n9v;z0)(0bMcN!Zd>q zsD0S(j9R*IX%%>{gmsBaVwXqO#4v~=v^PP8_Iw$$BoZ@7J&15a1^p$Ri+;0YZ@8>I zY(Uq<*)+Ypq=u~(S0mkSI)_Q3Y1Tov-jdsKMX)<3_!_l$9!IMA9otDJh(+c2g`Nkw zUk;HGrvQ3vG4%XvXS9zXcX8a#=UnafYK#39A_1^OiV#=WI3C1$JIbC}M0VZ~~jbI~1UE)z1}{{c$JTL>lYletWPYZJ#gCGSjX!jTVn81jIXF%3^?iPcz(p zWos2}^DHHgr86=uo>dKNGyaPMGeuRQ?%N>@md}#>+M*H)B!o$w;dNgYecYbCe`W<& zZIpH{TTQBt86;FYt*tz)JWD*fR@x~ivC6shDMT>G8D0Fo2ZiITsUQb#WRL}_j`BMT zKE${3B@pqFXl>^zOCC6ulimD^*wNT)(T$}|@Tr`Rz%)?K&~_W=RvbaSzyybO4Gj+_ zG!SAF(V}*2GeEP=inB_|StgMuBB2_ZLEMND=5%oIsX>GdhHcKt0T1NMs#yF$~uM3)KX8rM+ zQ;>x-`IT6t+;UU9dTc@7fgDL}PWZ66q9vF6+(6qm(xL^}KKbY$If>%;-$Zrrxs>PQ z*89f{(8+9{_g=HT7W2Q>Ta^h(t4FVrYOZqiD0oqLp5~+3TVJoT=`V?6W3s@X9%QqE z$C1;0qd`pWAwdN_wOaK??DEblZ67=$LiYU=kTNeNKo0|G+og59V2*Xw zAO6y6SN447X*PZRA;u0OAEMHNBY56epkseoT;JDoCv-=G1wA$`lx0V{*hmz$cdN~C zZn>A^J$Q=9>HclT4|UAg8gfdbBtTS|jJ%Qsa8`g4H%Ede;HT@bmEer9awnyVuu+pu zpDxz2-myY|ZPUM>+Xoi^wAe0ha&Q|cneQL=PrgLNZVLGC*`tno3bZ};DjE8z+TSjk z8E#E%HsRNq9bF>?oG$yTi#V|6yLv<%F*!dq%{#^dKRa??GJNpS|r{zu2 z^(^ovuqzb;xR$e@BM7o#(ZE>D>Um|VQV37RULviRZeig{x|r}cDq}oE1KalEZzBJD NOcOgOw!W{x{{a#Wy5j%< literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/femalescream_2.ogg b/Resources/Audio/Voice/Human/femalescream_2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..978a236dd1f21fa12a754ad764187b07dd961bfa GIT binary patch literal 23775 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 Niv9ir5j*RQ(0quv=a1 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/femalescream_5.ogg b/Resources/Audio/Voice/Human/femalescream_5.ogg new file mode 100644 index 0000000000000000000000000000000000000000..96a08297b2b05ca373bc13d043e40504bdee5e99 GIT binary patch literal 20124 zcmagF2RL29*DpNh=v}lBT}agEf&_;U97OLmN_5dhKSA^sj^1k!y+=eRdPENqLbQ;O z=mg)E{NMY&-~GPl-u-N2_Fj9|%&b{!&2QGsQMI<#1aR=5D_81YCR#`Q48{QSeCld$ z>vr`DCR_fWPrP7%GtDs7tCIigx+-}^Sr@$Vf%M+x|LYpS_{WF~qU+f@+uTugwPtd# zHP`veo=K5OfRA5*Pk>L5iCxvv$=2D`%FW8jo#To(4C`M}4w(l!Fn|la;a6trMUBO4 z13(IZ87l{I;uA%VB6J?RcPd)pD%Z;wk&2G!rM8IX@B6QWNyw5C08D^l$BQjkQ?wfu zv!=cs>zXTO`$+T^6HcW1k{Djs!7U4?;`(BHr%?_zTq1oe0LcZR!2E(j<0>%^XAY%? zk}x{ge&$!1uK1iKxk2~>{Y=5i0-J@2kHj}i3Sz~-v)7Nw5b)HGBbE85bSx_0VH^9o zjf$;j{wKlv+YctFEi%|NmZUPcSM0+%ic?)7R{y%i1W-^nf&5Df#TtsC8oH5>tg64c z)yaiF3d(6}sc1u&hq11w)r_adjHkbT>I=hWfBohchO;k>k6t{c4*k!4?7w()b^q-; z6Aa|u6iZ&=$Y;5czbuxI5fWMs2bNbNVZuvb;YcV@EPi5FX`9h#^SaS-sFr)EmhkT< z(3oOgwFczcrWgNz?kD=mR{!@KVbjkE5Rfe2yU@LNVU^QheecSR^H;+C02)(-26wNk zki47Fd$;@0E$=QnfL0#aS0?$dLAV-r01*6iy)JYEkTf)SCR~Me-6Uq*RAwMiTuJ2r zd_DiGFHlFAvz{c|p>PG`AO95#3Svv0hx>r=Uro>l;d$Sf(#O*GM#PBIS941#GA24B zv@&kLDNW7zJAw|QG%L_patx;R=BACMyNxOmrWgIod6Q+}RFsZG?`YODiTh|CL*Lns zr{k1<(u2zCkEO4byrF<{5$KHD4R5sm@%xWjl$vCYc8Yz4N<_?M*uTLuhH@vCnLkJH zuHOCc>VtsB?eQ#iUse#4e=N2+1#>ZTB7=JzabhliKSv=$OzcBsX|S3G&Fv!hpuat5 z0w9F+uNMDT@>iArX>nmfB90cqbVq%t);E&>8PLLG2isUc-CWa#(!}p#`JpV|E^g7%W?o@G$DU&5^WyM|2{ML zkqq9y0sb$`aiMtsl5XH7t5Pki$~gDPZz0t^p^xNp>Ov}-l*S(^JtnBEG)0UjgsmpD zt)@J!J~dhyHu>u}{>Nbcna$#i$N#XLD-&T6i(OGj!2EB^$z_jS7LC2Bn82!^aLYT{ zIwHL^Eqk*xAOC-|9Gl2jsgbY3BiF-Y*&~u|Bht&>!ZX+NTBX=*Z=1L0MHRnsPxy5sB7>{X$Vhg@MvpE{GTlb zqD~2^Ob9`OO#*-hfSb@Dhmc0P#>+TK=;89Ov0~+kVdwQwhrA*cBorfJagUe2Ezz7H z)=jv#q`^)ps~jA|b~l!ZsH@mWaa9x-a(kFS9RL)6KYtIk>!{*DuIwm3UKYAv>~>M& zxFQEnGPNKFTQQl%N@l^h;y@vqS}8KCkjx^Y0xAS>pg)4Vv3N=tf&u_@LJfS57if)9 zj6kfe(bDZQjQ`z%~6i`bRomitC_)L((C{*b-XVlC0+seuqt!fBfNrwKqM(XC^f(PsJ!X>qO{X1=Q~$$Ch2k$O z5$%~qVRd(Hb!{tsEp2^IEBzyF-D!U%h^ndbiBem;QTx+@qjs|YS9fh=U8_$=+WJ%h zhBGg|9{PW!zT!MHpcIO}DknFDXlBFDol8YjiBDtT4>qD9bM!thFwy zEFY|`dR^(T1yL(Y%4)gG%D7+GmYT}s@;qxoz7%G1Ky`{9J#c8X>Ww_CiuAuCD8|P40^D~BEhRfKr&;~?FDUFin-AR>>M3yu zdC=nqcBOrj2GQ3nNCnmlEk_mcit4Wj5UcgvLSh+YQ6YsAl1FD4QsjbhX@$~4GEK$O z28dNrA(ax6zmdYwiGP|7Dp5#gsL0Vrq3OuemwvO431T&HRr%jKR#VXstF+(*B(fqY zL1Q>G@rqS&EK-r9F#)LzwF-%>d7)w8z(1X-;UEmP3SyOjbl~YTFjQ{vAT=QP+p3BN z9ecZqKB#iGB5EaMO#_v^6aU%`q;17j<)yt;hOtoPn)brQ({@(`Lr00}bT?$h^rS`% zM6hstH$AnJMfU1MKRO(6y$*x!DihwrQW`b11LD-WNQma0L2gr(EJ^M=LqG|JT z;TcGY8`je8kr2UtYhCsuU_W&uz&IV}-2M44+kVT^nkZEoj8k}rJ$ycsp(g4W=@y1~*n%1y` zY7xgRs-L{7YF1i-qiEV7iU%?|B=l$=EgY%=ebPH?Ud~@Qt^wI6wM98(KnC_u)9raU z8m7&o8zA{Z9%9-&N_g6U04zB<1C&CwOK4JIUDwB@zhlh|fo++;(!N?4Xh36;d!;SR zvPej)u5^Ss1R92a??U4L*E>@2^?#}bDb)JE@6c*8e^m=N4|>Nz4=EY@-y9p1!}|x| z{wiY#83g6f5a)mI&}2{@f8iZk<4PkT(OeP!s_Ney9`lvB{(j8-uj~~Tgj5vc$_wAz zU@aJAaS;02g`sBCj|3<}09z0R{jPh?rPd(#2wl<4EF3UtUWfOC(1_ z>I=XU3`GJ^tXSdU;ZI@qn*Wi3Mrd%j_^{Uu)|xVZn8Dwu!s{}#v{_y3Wv7RXoC zsIN8FG4KjbaSI5G>|J90{&R6jatS|x62eR3OLFKxOqNoK#3}K2zH+OQ~tM1@zU_R$)aLxJ)Wsu z5btr0Dvq!Vk&Zl;ieFkc7|O(aq;wbP_FM{9n>f@1pbQ&&Q|r=cFnefbmqP)Xmd;c&}!C8 z`jqC20Q}=70TIW08vL@X@?TBEbfaJna)HN1&(&BMZz_dx(x-(ltgRl`P0$c0|Cqv z^#e2=Yr!OzxbU7O_!1kcoz|_B*D|}0gl47umi7(Y^Dx#~*!Wz18jj+##)p$~`Q7!; zCuigVso5KwC2#ey2MuGY%1WQ-CP?l+hgB`KNqyj0#dARI3`ws~il!=tQesS*?hN!0 z?LNzv$86LG7hww=ayZzAEgh!sHfNE6CF=5kpeNYP z1mJGW9k9G=klEcy{`!5|aHs`XD4hqf4w>yR&3QIkU(3 zt8@1YY_e(%HnnEb&R4Hi7AfL?4ZS}{cZL#7)hkNl0Nm51Y5Opu=ReiE3k{~{<@5&H zMJ@vzvz1%M(q9>*d*#UYRG9itl@p_J0c#{#kyZuqcbBJr+M5M7Q73cy8icH7f;j$s zFFwFZv}^nsSFRg%q&%!R@P1Dw>X*!$p8m!GzWTAFFeSFV9PRTb+N%SNrodQ4fV{uOCR+URc^JKOC~%qmo4djV2K;%93CW zA78SsZ^hsErJKJQ4I-2CAEdzx)oNh5_r{ZtBE;P8*)Qpv>Y!0dbKHRxxD?@Ih4Y^c zJWA|FD4p9Eyf(o^yu1@J`SsdUZIEk-uf#(AEpUN$Oj$On$w+`t82ycTv{_AtH+dCh#nX{vgp$Wi@Z z;~-ah5&wC#vBYDH(Z?AFm`e}Au_1c`NT9S7#6b{hg19)_0tc~dFOh)eW4XBxp8|hz zk(Cu52DGk(IAobsGWb<}$^$Wy6lI5H=<4>z1C8^0Hl9O=VQsj=JBaH(UMR8!zCTA8 zrWdo0F``VlU}1Ot(>s#D0Ex@Aj|4-=uquCn1PKj)>rgr3h(ao*RK~J z)6=o#U+Vpd3;OQr%Z1PVoJE$NTuMTPE~ik*`x+w;4&RUYj`=a<*c;k+Cq=c5n7-#$ zkNbW&zPxcCfv%rA7h>G>R7*&=A^VyqS-Z8l>E@KN%Ys$W)q@RB-o|P43qQfW@7KWLdJc)-qlJ47+3^F% z>$4YS9OJMcPG;ph#NQ*GABjq@%4?65TmhSRKhHu**&-#830L6ab!g^%UQ>w$jMH2nLHJU39ux>*90x&ao*iKJx3==J8uuB zMDY}FmBz8p?^X>=)ymlpc-1(E^8t`{BcwTGkTb;CuT7OdHf8iqX8YykMJBU=;L)$$ z#uW}21u9?fv%yJmG2{KyqhlQH-8QF{X06{d^+tkk&$c?s3?r&`q~X|B&oNQv0fY-f z00EQhuhKawxb*)PG0%LHyPxxI-Pkv6GT2fS-?co)Y1Lt>^oG&lN#}aS_R|wB z{4v=5e$=DF^PxbSUfY>PF-q6d;+rW|3;bsN9T!0lb^_Th+%`I3{scaW&675mO=`MB zv!1r%D({wCyd;-@xvhm!0*2HP2l@7nGmG|~KW|O-5_IwzJVR9?5g5ib)6r6?BXDkn zeliXgD!_>YP%0d#BY0@t}LCf#k802|hVYXDT68%>Pi7GQkqpM%7aU}2Hm<$K-(bx+- zJO6$(v772Ht-~|2`_QN!_Y;ZhSq>|1kWw&1QPfWziLruG+0y^ml0|m zcwtBqWUkh%7S?1mo6cBIXYyyx35x~%RMQx@(-zRz$YvBZm0`;jDKe>fG~zY0{xs$2 z0Wl7ns7uUz0>?47n7$4>`2u;!SKb#P{cM@Pjxa$-CQs)1*Y|a&9vwFVH=Cpl zXcCsPB1&I^o1KI=`>FM?NI)7sOwVjcz#;IAKNFw?5j!D4SbR^eb-3>BfAJQvoimzM zXlIXr0CFyyRv2eOT@)dRlrUiTAZR2UX6N`PW{Lc;T*<{ z>ImN_B-^70Qz)#$ZFg~z9FL~OgLZNGK8U<(Jjt2hr_U7kpg0aRV|pZB`!rIfXi zJum%zK;!$tz{CKQ5jQ@FU#r8qsm^ZKC~CXc@4B)VtBTiGeYA0 z*dw3o;x|D8H&;h}w|xo=jPt=z?gOf4k#K-h?*nx7f+Y0Bl~5U2;7Qu^7q?#4W7m(v zb}#%EMy>rDhg&o5UwCnI+;EHSI;u3DU;li^zDfscrf2tLpjvL8G~w3Fh_T9Ygcn6T zv9XUjxtOSj?_M#!tXfD={w-YQCWqEPXQu`oivC#wlSFS}a^qA%&sXbO?;S}FbWcAsYRooGvky_3rnD;jW=ua#N< zDz=sl_0TfDoVoiAm&TK6geTsepZg%7Mttv12^ZMd3UjPRcbpuYi~D9-;sqkLeth|r zGb{ZCmJNVpDx1m35zwaH-}I95KH$ffbqzxpz>YOp2vB4QADtxZ4h-1mrTuDVBZD>@W3Q;9MZWKMMpn*LamD^sA!j*$41 z?4_QPkDlFs%xO!iZ45Yc5l8#-mw`l1WtG*hShHKTih4&4d?}&1RTp+KQ9m8mvb0)T|fJuim){5-<>1QTj zGX-1?oZHUmG%96Y!V=={d&tRQAZX-vT{Mw>AJ;E`;7%=ZOAZR!)kw?XcTwG%6LecP z;MgXBp+sM|w=Wh5oPJ{t144I>os2@0Ww_d5pZ&h5#^|Zh#y^WGwq08#ZmL(<$Q|cK zwi{!&#D*}3)hU6Sv%gvVu4ifsipN>UdQ`x%{}gJeNw6xm9{l>P`G@4GcG%{e2C zHoGxWs?MpOW|KtrmrTZ;WOu2rH&r^>{*?PcV0k;OP5WmV?1$MOj5;~u$$j%_ndR5Z zg0}@*Ew?4EuS~&F9^&Ta5*4A-MkL|uCxt5NIz5&|*Rb*oUkbMc4~ad1fq+!Drv-tz zu4(O?6Hfz}bIHZTzdV;?hFvtof3=H$abW_+s=%zqEQT}evyUDn;F=f;ufX&l!5>qF zf%x(Yl9T{@?{=j<7BBCgaCurNdQ-(U(0a^_1v7{A3lxFd=d2N}k914SIlNf@Smr>pa|5mpkD z^qM;}*F3OXS8`QUC;-Z);mclCK%+pidw-*y%6x!*PkB#7Y}*-G5spa!iF)|%!)+Tf zj!#tv-wd0zAGYQ6PpH!g+~%@WTsE6tf%X9u$bPn724EpHH)p(Dx?*cL3d;iF0VQlE z3%tATvpJ`GrHhSaIM?ruO=EWK)IFd=|OM8QJh2lemQ97qRx*&6 zYz3bu+3w)~Aiy96spX3%vg6ly=)>#d^C=Le--s`2tJ0)`4;f}H9S!a2by>1!Dr}1# z)^&9BUzhy3q%MRajJJZai@skdDcT;g+;AkX=8^OI&?R?!F9OLzTRzp*Zc!Lu@rpZyr?@A#g!ZF7ABN6 z#&TeGkzt)(BDeFCF+}C?sh#(U+>oRQA)=jBj|u>NUDS~0glA}F z`8xQTq=^&)Pl|HrPw@bvw(HPJ@MJCKaE>d`8mPfIDJ`3OujeW0X|zcmh@o+`OGC9% z@U`H$#K+Xx?1thxKfiolhoU|)HjcRsG)n56q#)&v#Eb>8invQ3LC^d?)l9`c5wT4@FM3?(eOwrGElXRLj{r~PzQ<1o>Z zDq9~@%huJC@|7KF@H>7>AK&bY^->s2vzLk8xzXSci3oSsz>}PdTt!rCi7sXsF2!Y})Yah2d64e@NxIAHrL@|a*21MwcG zT#wX=9>fR1YKPx>z!9g0fcpTu7T2uyMv(Vq!zs%zH~a6qs+Zl(y{vb-(upZ<$jaP@ zeGe96snOXF>3lTMM3F6Hrzlgt4-iF3RiP%11toYDiCHR)#ss;GGjg`t65%r1NjB)b zx|8jN@^KmZ(_Gm`qoIT7fWTf^&XD}NNf|hWkI!YY^|BhC+tzx>M_)w8RwVAbn;To! z&oy;fjw!13Eo9DE-rvySOcg@?XbZ8Te3YO-+eIWvVYl0Hn)ZqJ3H3?h^VT1xRSLPr zcS<>5V%92%K3y2x!4WS@F~f#t+|*UmI>xbE&4JpmcAM>nkGR{%n#lqfrzuMP)d}%0 z)N#OuF7TJcVW{T^9g8C5fcAmlJSF1PUKIoNNE|#zub$lYUWf}A>e##aL7rs&Zt$CF zgIk_T77rc}YFN0&GD|$fKlvkUg)G$Sdz{q7l80g=+x~iYhq<0%s$Lw4DbEaTIdn+C zpQZV7yl+G%Iuz%+SN+V#%{m_yW#^{#R}^$?E^4r>ltJW$uJK*MWqDb;9?EPy-ubNj zCfO<<1lS#x{G&hS&~U z{Fod$ll1)Yyq6y%0Au-Q48M9SPp~05u;nC5<=*mcD?G_C-L?__sL*bcN(`6?mUzHD zsC{4?uj4(8WLXmq(6rdVz7VzzC)~WD3O^fA=Pc(Pxi_3T{9T88PHg!P^z$bT)~D?kd}({uN&96)^clp_|tR}4z!~{ zifrXHGBU|lC+H#Dz>?X&b zPC3;zZieKWBjZH%4EhR+^p2fa%<$8)pf#--iQulF;SfkQVT zQIohNjx}q_&7{N!&t#KACF6i(}fgDHJXA(K0E1|&nc3`_?SWMzdi#r0U zAI?;kS6)1~-dlUOiB>LZcDgRB{;-@v>9?@*eY)pZm+S*`M_4kK86vq=F3Cd>-lR z?aALA)=C6O@o|O8RXsaR=hHKYAKEyoSHL4e_TF~Kg=r=y36~2u6b1T@H%D?I*si4- zyba*YBz(5icJvAT6UC1ku_8yAC=!B_s6e(j9fp&jw%0Fok>+N%p~-bs@^)Z{?PyhB zL_prOIZH$Uj0_;8Iz+)X;H0{;6VK&7lW-E84kAKO{!$dN*a9101kQJ{QC94zh!2xv zYk42%_V%%aX*+zdLP}WfwK}rRVea-!eol=MjWlnqn^&(f(I@DD=d19T!QfN(js4g8 zs5ZiNmwO#?0afa!`R|?N6mZe!V1R871vJ|IcvW2D02l`GlYFVFUL_o8u&zA+Pbi&LC0;b1o0%W!)n)0 zanGxLcG|Wje~dUQ<3@EN7qilA6`mQ+?i{pn3!gm}TFHBx15K~1Kg0lNq_BKKjyR-n1siS6V9Hx6}wi$+IwQ zcT2Uw@-%=0n+Bd@Ia~a`t}_gkaQIkB+*?>UCAScbDm1yT zfY(=BmV6{svD%P!uj=OnN5@C#kXwW8;o0wQl2!lKSJMb@uML)W4LVVe?_OF69L?x# zYU&qGkJcN#i)SZR)B9jJz3ly4dHeHmiE zl0s$TU+mprzxF;Z880!{v|174%7wz22vRU1jA4dJG8Y9vYr!@qFnj!xh8DI-Q0{{P zDZ_f+?fJ|HzF~_aX#>h5BpsUYrJ=*5)U9w=bGw1xE$@SL$WWLtbgeB%Qg_y2;1`8! z3Fu7qr~CV?cktK&7LXZeJDFRdr+)1#&%s(!Z^I$9?3W&QF;0FIHgQ*^0WOp*D`L8k zE>a=MpR+<=#1qh+WKFzco$}P@iz64sp|Cvl46IJ(6@5%9ul!1vPs4}4mO-`die}#7 z*)?Orv8e|IG-GulOp{WVDsD9bGBfQ-p$s59X`d|AuPI>J`prVq$9rW5M8G7xmvM@B zW7zwGtZygaW4h`D*BGx5w4k%Zdj#mrtS}*D(Qev>BXQ$4vcKT zhsgl-8%YFiZGxUcjvbQ$h3W5KUJ*P!vBubSL1A!Q^HsZ9l6e!~pTAx^qOzYQpy}eS ztWJfq6%i>Mlah|-RTF0)tmn<)506_+WVz6KiHr;px&3+RYw;sC50iQmI0~LWsuY_ zC2gr+Pu_Cbc_j-gdO8dw_Xa%qT=mF==3ryY#<7z{wsJs2aTXPDC*8o0aw(}jDl0`p+zQ4wNv7|v zK{hmKE?nWxp@H8VqpFfD4$ihJdNS#ukOBZ2Xx~GH^qF+6+ad3&BzICDMJ{t{z#SEp zb9fhng8A#z_cjLpj@fHXn^1o}9e2LwGD*s;a zoeZ`@wA8e_uk(wH7&3=jK+0gyjE)d#_@~kr^TwIUtk`ryX(9NsxfjR2x@@V zhwboFP56g3XoWPD*{PQvpNex$Y;%lo0gH`{<%}}Lx5uFLSNud`xyyNC+@<);eDi1nErQ}#m>e3+-HwY&5XS1fg7Cf zTU7IT#T*HAs6v~-Mz~X3_?+}V;f~g;QG!_<|PG`5pA<-=w?x4_COgU#dG<$s|A!||T=e^&s@NFM3_(8S+p1e; zU(K?3G;o5@YMi$GKc`a;L~;5{ao>JxWLLx`%n7;|Q%G70g=J971*ZKOPN9Y4<7?aA zarup$LE=k6+_a{2N+}N~7P9oU1)o&GfgS(t`DxvheF2Y!s6UB{!#<5+PmEN5-Um^S>072SYuK!cZzGrO(Cd23+?l7UCW*b;OXr3Sqh% z-gef07w(*Hk`PPl@aO`2m4~(kp0RlK5e(P{#WVSkJiQ9pLn?< z^RYoL<8eMdI0WD+(ih%`wd73%fT!g#XhKnD;sH&*HEz0H5l9`3B-}60hLahm=SCKq zkKktKIsk8u;6`691rjNTj_Y_p#+4sh7O(n=Z{KLPi>X8}UBN}%r^m7c11LcI2;=aT zy}?JUv0BH!+Ffu?m&-XU)gykT&#Vc^u5Pak+i2-dPC>t2$dy!<&#jN8UsycUlwWbS zi*A}30s2zYZ-jlt_m`Ka`K@tZ$}ra?F;;0S3X`aooBOFb>0?-hNAm!azc! zd*}GPK^4l(?N2ev1IhJ%8~BYqqe>uE3@@+q;96lR5oUeAN*f+?cH;(>#jKX1LE(V) z`%UL3*8n%9$$6vK&&+K(;6nB+skdYGIFJb2B)yZ7V8P$_t9E^6!dl36IS{a(ob}kr zIPd7MPGt9APg#ju`VRK#>Y6(JFsex!S6Znj;8L+{`H==6Zk-RB>gC_6;SxMJQXBDFn>_T_s z3rQdotDCNc-^Q1XI%9x9MZ^)o8hRK*gn=)u7I#gEl+oHU&5QyNBFadK%mx>wF{6N`n% z&h=z{?)h8WxbXT2{e!ayj|iailfW*Z_mmnqzMPsrFZXbM^7E{6bzy-*5Eq}mK0n8` zrHs;S)0W*S>?@&0I=f{8zo17Q5z$Q#`?b~=*%Xc+70H%ue-H8YdFai{?Djm)Io4I$ z_DYl2CV=*3v>Bqj%z_1cSKI!WPuMXrYqX;@A^#DgcoiaCn#u-@c=uc20Huv(fyF9e z29u;q0nHddSYQ%{Red>zG2PO1V9PjXBKhZ-g$9^DLg7U4kIHr^kRX(e;+gTD6SZO| zx@^I(8Tb88w3(0GPo?^ zURuHvIq`55*RheksAYxhDDZ5mrqq~Wu%}SfSmzQ()%qzSr*M+4Pu>0Tmz9XuchVF% z-h>p&4qzNmmFPjz&|BR}G$o~RtNI;q+>2iXn-19IhrluuLGu^5nJO@|R7DMfpJ8xR zo+TeH8P^6KE$G1NMhd*%f#I9g5=Gag7bAE zmZ6z6e#|NjXp0(@9^3>%Z@isTBrG1Z`=md!^6_<(u_pgzOOgamO8ofgIemH57}K-%5= zSO72z7$F2nu4h&O1m?;O;-%LB3-NuvlTSZd)Ags+fshw1Vj6P;AD{^{*-(Y~;g)yg z35Rkq->uMvXuV2kxcizvTU*wE01F?K!1YyVnY53cvJ}el*rM3+OZFV8^E>bTIA_cl z%Yq~XnrBA*ZY_=OZ^nM0lO)YrlF=l1%o6^~ZQO%sy z+nO(b4|-gHi%8tM>FSUG0~Fe`M2`%Mm9HVvnaXx1a<(K#OR}}2ofylQl@Jvha2*3D zL`;^wz`$(kJIpJU5`t{cRFnm~8WTt-Es}Lv3g>Am1C>ga*%&p8kP5AI#(A+{sW;oc{Ew!j#`?Lw^Em>0%<{ zVZI$6zja7Zr>eI`vZTV%eOQ5!5W&NFQOu;cb{r~nyMiuVEf|fHo0#nvs_|H< z$zU4`WPTz?&@6RBb1O;%yPb&@R^DN_^cC_S41BpTET+b_=v<{%PdBbNm%T3}4;?!v zM7KY7vLsAi3uVA9c1LE`mlS$9az>&N`;Q7g9avv0A=B>lMgPQ|Vt&%(oftl)fCWuy z;CcP{AbNzsni*|fwcwqtk7HY@HA`Gp31aW6O;Re|mt3Fv$?W=gKYslh-SSL^ND2g1 z5b6wjqZ->`Zz?KZ=@PGx+}KfPKejRbAaBLj7*kT&Y4c`*{eI>HQjy8!h}SdHsA#5@ zhhe{!2;c~OdT@+O_e+?#Iaa{;-F=(e(D4@SoiI~Uj^jHTCnLNqZ$4#fJSe26w*}AM zErxI}rNx8t4&75qXUc0!0W`oNf?HY&^w5>m<)X}Nx^iI@2&L>C&-XhoHl-LZg}+Df z2HZGV!LDGv$mg$#6ixnBqf4t3k^(GC=Yrf{mZ_h2#_(nZQ_>0s%?#<&hNG0v4tQ@5 z+z|yp2xmX4yUyesYl~&cv5v2z_l-+TG(~mv{j||^9E(@sGeU12`Js88Z$2eq_??fH z?Yb)C=t9+0l9CEsblTnX{q3A^g(V3Rpdk3!da�Uh3URV%s7bHS+PQQm$(}OL}eg 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/license.txt b/Resources/Audio/Voice/Human/license.txt new file mode 100644 index 0000000000..51c969fd0e --- /dev/null +++ b/Resources/Audio/Voice/Human/license.txt @@ -0,0 +1,16 @@ +All below sounds taken from https://github.com/tgstation/tgstation/commit/3d049e69fe71a0be2133005e65ea469135d648c8 +femalescream_1 +femalescream_2 +femalescream_3 +femalescream_4 +femalescream_5 +malescream_1 +malescream_2 +malescream_3 +malescream_4 +malescream_5 +malescream_6 +manlaugh_1 +manlaugh_2 +wilhelm_scream +womanlaugh diff --git a/Resources/Audio/Voice/Human/malescream_1.ogg b/Resources/Audio/Voice/Human/malescream_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ee9005b89222ebcec012d8cb81216a7e532e7efe GIT binary patch literal 16926 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

!uTVu15!+dCO=H_23C6Q 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 N0@}-noGuvT{{t_kp925@ literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/malescream_4.ogg b/Resources/Audio/Voice/Human/malescream_4.ogg new file mode 100644 index 0000000000000000000000000000000000000000..62f787d4a7b5b290f805e8f8b17cd48269128558 GIT binary patch literal 18682 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 TN-d)r*tdz`2_&9e129Jg+(TKs literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/malescream_5.ogg b/Resources/Audio/Voice/Human/malescream_5.ogg new file mode 100644 index 0000000000000000000000000000000000000000..2aec2c7149cfd57b48d97d3d7412ede840ee15bd GIT binary patch literal 23909 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/manlaugh1.ogg b/Resources/Audio/Voice/Human/manlaugh1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..957d7a652910d3a272b3c758570ad94f51e0d886 GIT binary patch literal 55970 zcmagG1z1(V*Dt&e-6bL^2kGuqT9EGUZjcVCql9#KcXuO-NOwp#NP~m|((rBc|GxMA z?)N?SZsyE0d)A&cGi&|Unl-b}LDAAu6@UZ&a}|pGD_FUD%0Zq(+}}EzSi3w{L0(t< zqW~U|zl9cv;$zGIz8+gXQfS#F5O7{T{J*XN_bojf0I7N~dV=VD0E^?qcrXO8>|kg7~i~y_mQL1V906*yW%-u<;mm0KfqNBPx2V zcnfLz!h~EpuapF-$5IblXi7q8520xUd+&c`P%g8l0Du6%=uo5bx20`H`7H@)qnva2 ztrhr+p~zv%Yy7C4=QO4cMGZx^4x{wcD45!a04Ns`DT*L`mB-8^gd&&_%s!Gj_fr&S zIHNI?08Xp))spmyu(i)-bL5f~4=`GRnV`@t+L! z?>wO3Sj3Qs%y7g|9@&S`7o|9Zto}_43c$c=0&imRrEBqrYDq^Xs1$!QDL>(!;FM5R zlUD~XH+@Za^I3PdS$ALUlpx&}U+tD4-MJwBiy&jd;Qypc-_?u9^mpn|2#`a`pR_@r z_kuX@2Y()XU~mN-VD=~yC~Djb`nY`QA`6=;>+~k8@+SMCI;NpIjK7b7Iz@OK4Ul7< zR`mZ#7TQVX|Mw(f)z1KkfU@j!BJFdcl2D=Qb7n&RE8%_s)TxLHQ;##(8yBuVmsenV zCCO^A~@?JKMRWDQ6m56>-}H6fD@s} zv`Dgnp>W0+{}l@iWJ{QfB98H|C-8xg+)ZfOc-rX*KUUgSPAPu+PSb0Ch<}nA($*I-ce-Dvgm=_^;%JmyTRn`W>tz+R4D`B{Bw|v;LljTsp4>w$&a_ z+b(Iv2TP#|>9mclYXA8C2NtCU8Kd9$*TEK{3+cA4sQO^($}Hn(1og4*e}|6gDBESD;NDci!v86C#L1Qi+j z!haS8DivxLBZ@@y*GPg>oCoQPL96(miaVhi!zG*eA8G6tsWKilm?k)wRXGLK)zmfJ z?X{EL7Mp|g=iF9jeOG5A4W9-7@4)(R%K<>63H)o52__NjeHj4?VyOQV{9l&igx?oS zIuJ`GTSq1TooVDZm*Oec#1jc+E_v0b`V&vxCJD?{dGsf_%_r5(r`^rxo6L2aeYKnZ zV=({BW_8x>e^}0=iM-&C+K`Gv_+OTjLl^afFN#t+j!HX@#w*D(G_5o>Yp*m9?SHcz ztFYpfu;P%goscNH&?M{7w6fMbyTPi1Pyg5S-nnF>sxBAT}|-+Y%w5pnoE9?3lwZ301yEH zC8*>;oG|AYF$X~{6!vW@#9V%)+-}0aVjNBke#{rHF`~4BEphx^7^iD0bWdN)1w>Nw zMnN$B0STL zjQsD?0|g0$vSFD8c&4G1U?Tt-{GjKK$2^6I-~)gOh6);eP=d-R{dSDXAo~`niVS~o zhUzFk3R&PFJ5pu<4t*i1>L`65Y2dKvRz|?MC>;PmJ0RdkjE>7p0>GgK)*PwE;<;q; zsb!x?jNzNlK9QeI^&a$kkVz92dyvptXq*j%b z)iIToF_qU<+>|jLG?kQnsvNAVNv|qDXgWskVmkQbSXNb4Uib5n`l<4u*XiIBn@Yy{!D8&VjM3;y_GU-B&-+ ze3e&U`erZIFtN9s5j%iXAsNDv(v2j2&~Yj;>sz8;%b8-%(XIYS`LT_JW;L7ZS?K z)HV{>I;pc@5tu4>C&G-LBf(6twwNpq2@oS9OI1PP}0U#8Q%kN<1t zCN|8yGbWXfoio%X#(|x0FX%+@Cww;nS;eVDfGu?GT8olwKth^Ic;ohhiyil$DV>VP zK4ifa!X>)(Tm$qvFnB-&hH9Sk=t3UNRy0IqRN5NHd6=I8Rcx4_w$xcpjGkV7SQLf2 zag?8)zGsFXg}RVHmVvoP!NoCPx&clB)D2pXm~D)1V#;$A4|caVq*2?NFn z3-C>GUKA37dMU&=1!YU zA2Gr(!C()Dx~g$7;0DUc(3ZXjC0gkQLZPr{SjVQaVNL|4GH8ZFmKubDMP+x24j94y z1PAoZQ24XSvW&~h~p*#hL0j^cU{9uM71`@z{ zFE;?p3KPCR5>#aNpkN(+q3a_dK?Mx}7#zWfs7Be$Wl%(u1DjMkf8Gu3PQt<+Z_jlRj-b?Ol6OjZ52h~j7bIeEC}Mp?KE2% zkYGE~3II!p$UqG|i3ATVAu0ox+22XPpr9Bd*n{y+!q|5*#8K#bQotCei10D=02kTE z_#*-&fVaIMfq#eeG0Sj?`d@*`|0<#W|4gD3jI(exz}32!kObvlR4$44_tw$fUZcs4!3$COx{2DIFPRkT)`Q;f#t6*oy#4VZ+p8 zS979L^o27z;mn}P$$+;e5!}Hl;3GYACKc=j-&H{SBs8r64M@ip9J(ztedCNtL?bAF z&_m3agmcg6paW|TjsQ%m#wj?lpuTfr?XRvWz+hYKue2XG1}dN~avsq_@mdB%)gwkI z0zozWs|$?z?>ZcToqx~*0k!k*xdbJ#ztBR-1?%X^K#Em4Cd)PDioUoZyZfuURi z$oXGg0v_1M-|#L$Yk=X((dK4CxDQ6K_6kW=59g$A}Hl!oYh{_CC)<0MODi>PLoQL`F7*qwjKbAnGr;9n2IgmPz59kvHopxgvxBPE|-0|ig`Ei5%*v&yjF#!Q9 z8wbzK?8@BK;_}k|;m@^+nVG4D5j(^$k<4O|SbaXzh;?f&mkVxE!Rl>!v|5fqXxc+1 z6~b)daG(4aE`@2SZxI3OQfQI5nWD-Xk+xDFk5?R%Zp!yLZ#!woorC)(4e zG202h?|>yM+@rr1x+}-svwT)*KUX=3;K+^wgz)%M@T!bZxi4qD?PVPsRUEA~y}k^l zLJZtK%rhYf{iDkcx6nrlFrWG7Fk+KlLV$5>xlZRGLBj!Q0!T=|_XT;M9!2cJT-McH zSw(t}Evls{*Kw<>Pm=pZyLiKRf1sr>b=jQtx(3H<58 zl!r3w`GWmx@0XV{_um9k{f6Hr%S%Vq;1EnuNB&6arjOY$g}pj+^jn(#X(8GLdwY-4{962XyijUUE~(+FQsc?xq!>65_+ z>cVmH1Yz-%<^sQ|Iezw6h^$lB)r6mk3qV$QmiVUfci7UFSg&L7j_P-7)it=}QXo>3 z?=g+eywP*$I0jz8_R@{_ZIM|`2frNdeT~A*zGO%xPO^WN?GWRLE?FX>H*6+4Revbt zw$^e>R#uGhjp7r0OB+d|Z?XXv&bMY8>}`%HSRcVny7&kA!e+duhb&IdfSMWa@GlnJ zxEvnK0&{4+`uKgKUy&RcFP@Wg1$!7nV#nsuW8-w(S-nU1LQ>Pnz95-3&psV@BsfOc z4mz%$LC0)Z51F=wT6nCzi6P!yf*^8wNp~-;c#@PIqvFmp!-?RLBKh$wQDw$u$G#pd zKlNK*y_Np+#D{cztq>svfkX`y5>Xp?O(*OR2)E=y{yeSGfJR*Pdu}?3I zqjkY7ZJjYVVI}Up1DwEl7fU^#X{noE3#TO-*0=b@&2yiYgWeAtY!M0%_Q?oMQuGro96+I+K@^saG8zo>G4SnnTZ5%MjeQrh>Mu_!gv zSxYO4OICF)SKGNg>-2mAuWr&0A92k*1I@vTTYS;pspzg+mW5QMPh1da@cM(|S~?5D zDh>%?z_avPFYmzA)#RWOT|lO2`6W3AOP8 zs1(TJJ#F-;_@i4t31>1CORLJKFtfr0L@0}PkuEKsDKjnQRM&ZKWf z&4aIUw8Mbx!0oW&k6Q1=uep?ocCzqK37v~w8fd=?KNXXQc1&TLVki8z&=kD68~m8_ z9UK**_V&@r@r1T6GM?;*!-t2b@83!nzW+6-?X4%#n@TzPoBtX+17d)6>Y?~8Gg+Rr zC##L}>D#s2<~YQ7MgGe$yVQ#to9BxFa9({PqyD@v4-p{s!OriK%tZA+%T1 zUaXP#W#VR=dbjDQ=kpc4X`P@rh1JtKi+jmchvO$@Q(dah=@UGB{(3fiQfz2!tX3k# z2+@jUj8^s2Y z8C-0Q%&iKZY!~y+f)TL?{G4W^{8ACg2D4wtr$i6S(ip(`p?4_>j`|TM;H%kx^-``spKpFgYbh(=Eg&6YYh!&wCU9*v$A#bf63lwK*2hDK zhKOmB->tp`?j@E`rUts-9Oymtzn3df%JI_I-nN zkX_yRnM@s({OXxxz9?4d(1oGB7B4{$Pf)LEnv{jix2-b7o1*s}SzfrwrI3+F2rGb4QZva(DKF|Ltvi8`;UR6upqwrr^)FU(4($0JbT+pD5B`{0BfSp%n|o zSa`9AIb0Jhe&=~TNDW_D_6^>-kytIZ{|?IPa?}4R{V*r#2?e9&3Wb3#;p`PbSLoIQ+XMY!ZR3cd=ryx){f|jM ze35+ojgs*|FY@(PDKSAn2qTnnp1g6i=%QzG??Tt<_@N2E2HgYJU6E4IAqmgcc)|fBq_6E; zA?9+FnqS}T-tJb4r>5NNIyk#s=|EJ@i0YWrTWuzkJl31M3Q?L_hUL*RGdy^8l>j47 z*_6gHsL&zSqM{;DDnWWE))Rap*hp4@g8%vLdjukWX`9w_8!CrZ1OI^dBqRW1Xo+dn zX#=`Ka z1VnzL_zQ5CGJYv54ooDk2Fr{>HWC7m#;2ggtMk1Jf#+Q6_BjsmSI zYHH|ra@Z|)(JSA(-7j`4MaQ%r9?RDRqdgt|zAvUbwF813)5dJhyI@aM9F40m@HE>ApG( zcVxEs@4hV2PC0c<;mCy}3eoLnkNog{H=udqGzB4amENzI=>#5e%cGJ==~B#?suqj$ zvn3&BsnB}W_k*{|2h|$ypTu3NI`rBi*)0R$gu3TUYy*q&j=1LR8H6$d?}W|a7EX{t z5?R=*;Zf=1RlXyy4c@mN9^c<@Ecy7e3i+FGX7};6>oIO}O~jc?{m{?MgW?ZlN}sVk z#ol&kreD-Y{;iYoQp?$UWWr3<@mvM>%QK(wZ8t@z^Cq8O zMuIX{9^z!`?X;7D3x&!uw-{EvRBs8Mr|-uRV9+bO z;J6}250wKGTz_3e0QTeO=Aru+U-$=u6WX6r%hPfF{u35DTSrNbj#n`vK@%!zMq|tR z^Yvuue!c?Ja({jyV)!e2yCVD=r1zBj!1y*IE`a8xQ$q@yqbmS9l1nw`J@S!&Z$JCD z3VZH#{q`ByEPzjl9iN3n%|@IFAWK)4iFfFR|S^lwt`-)iF{nY)@8h#q3=WIupAOE!fqq3fWxQ>eYxC!8AE`#QP(!ihO`?%kC7p1rGGN1P{U4xbV?Luhe49&{fy6;dw!E;WHS zJl~oZQ66@W0*l;Q1AtqL%u`XbZ|!Hu9e={V=o@5KyKDWpXTSMa9ATYbWXw6E*%Q#@ zx*a5aL+HW*5H=-yGKdz+H8~6+44SmsUJ2#4(%h7jvJAyqx*IMD#ybtuD?#2gb0P!e z$Z!A9ILibq`iJL_{@^O0g$Dv$vggt_0tz7s@ek!A2nul>jGG%u1_?VZ8ikOKO$)>{qKIPtnF`YT@tGL@ zIS;9W4-z@S6{Ja(gq#fB-uMbMM z;?gYTHPy^lCFO}T!)m!7-W{$M=5u-5I(Y9wxvc3MKD(p|;}f?tU`UB|kh(x6EDvXQ z({y+t9Y@AjuAWJ+H6ff#lLYgKtiKJ&0-qtxYEeRMy2OZ)OzuXNk7qk^Tmw! z1H#8)CbjZ?Nc;q9@DSyJoNmd&PMah$HsT?91d=}QuMFRd0ysaaXeo?MZ3Y)TIN(30 z3(267g4g5e0=NVMzeB_+@F&KH$A>2RMh8bHrbfrWzoCI11D;{3?1%sfkCrtNlNR0| z$+QA;Nq~Lwc93)38S>gmiEHEoE z9nTDlTH_2%AcNt4jZW1pSt&ZK*GtIqNoi~k86I8;f1W)5P8)O`6R0<=-61k=f&gZB zSW&BkTO#qN2V9IyuM|xA#pVgXG#g)m#w`$#kE- zLwvfeyEHt0cwT!%a#jOe?2m!Dpf_dpt)U7FPhrY5z1A<`!F4UPKsjeOslWs%R?R_|_i(^I~0mUb|8??YO(P=9mh zh{H!HVoGU*t>y2_b*b@5#ed`B)ktdaX=J?U^_KQg)|gUN{e~zO38CJpFk?eC!BbRN zF+wflkHm)Lkl6`BCw&F+p>q^w_V|&i&;y1|y@l}i#O_rqA6YvlC1O?yYM$LcEr@CS z(z#5wh{srHQrOM|7d{_uC_`ai?!g_2qUanFQ98y?`-)uFTHxWXt7*sC@_1zn9$b0d|STDkAeQ{1m4An zZ@aW5&n^G$rwWre74@!fucgfVM&Ia}$#Tiw3g~X^AD-K<561wE%Uh&sb7%|!_C;*X z&tAgArjoR_9lxT($PWKB5#^Rjjw)XnI$>3pl;gR(D9Y)6Ph+DLC69f3#-FjTs>v&hq%Ar#%g-$N6-pT*H#0PL4wh_| zQ9Z5vB4^8`I)2(_5zVU?R1vc|oTn(PURx~AK@$Z|-k^N49GWJXKBhdwV=&9O4q!s-hrCVq>ie2zDhnxq{ zRp39R0XO``GY-V}u{Zcb`Oe_JwYeEOfvW5BvSE-~Frq6f!A(5~8%GcG)c`Ic)a6=e zQ_zzNX7{V=n|kfN{@VzBf}BJ2YVm6ijr?3 zZLqBhawBfZR9o}~PIoqeI1f)7Zv0yldG|o8?T&WTP%RJRunNhOfx-etW+$btPQI3$&KYu}Zsb z>}=}^ytCO_b2rk?g6XtK%HQh0?^m2VA3sD$fHUAxiy!6DS9p7sj2XkqykE-!m(pbY z#4J+|sSagr;7#YpZppW-)q@`&Rtsq_I4f$&=F)C_W&0XKUPjNmZeg!{2YPSSUEw+w zw8Jo#%9$AkXJgZF8h2(S;7}6JU1P>-@9hm|$qyNHw-JDx=%+RsM$fEEa)=|oo2b9< z4v4HXdxi~B_*^nL($T_^BL^K0IzkzsMQHWCe*LSO`D-43{8s4PCN57~VZMq8yOPq! zx4tCVpMOx}FvZD`d5>+`P_zrdA(FDu@qGk?p~*8Q-?w5l54zw5Hv z)lVch`keN&W-r!_?D7*t69{2`jEq7#rD`c6RcAI{3O8j{;>Xf(x^T3;j5%zjU=j}_ zRKKUdT%3RQ^~JO6<6O=mdvCn1%?*9;-{-frUp+JgLg*MMjLm&(drHd^(B8PCWz_F7 zIb5^r%q0{*{bu%Bo}`K!g16hJ{igMxQfqEJ*=bPnDUMFgWt z+S4jNlAN^o8gk!*Z)tFkrvF8emIC)2Ff&!qcW5G0t06=W?m@U8;b3?HCr zOFcV&vSax9X6L4>FT3fRx3eGCr2Qx<6E}p6ch!w~ae8+n z3F~R^Qzy)02fXWiJ!1HA*sIiuVi9`BJc) z4KEVs!t#|In&+MbQ|?7J^1hjW|8qYEtpnWy2UDRf;9;E$62TeCsRds*OA6I`@8NU! z8DD1!7x?@XD`<}x0xmM7+p=+re`G2Y^c1^D!1AbaTXe1C^w^DHtSAhs7UzvXf~`L2LWj*Z$Zto|fiGaG9F73hFkI;moGrF<@= z5ioYR{JQApFf1ohDUF)P9DwtjM@?mzhWjShTa)=yOah{wrV51= zGl6c}Ti7MOsc?RAbl}$a%;q|0*e4)Ddi!#N&G0q6JU;Wiw=^@;Gh|42#j6X$c$tP6 zDwX(YGQ|G(RQXqbIJKWhDoqw5IP|E~m#hwL8TzvixO|yt4Hr{iO70>F`@sJ@(f+RX z0NW^TlEc>3hHD3nVt}S@O%Ji=4(V(8i1@@h;TG-Zb_}knMQ>4?XM`z7=n)l}H!q#B zTV!L3)%I3Q-^QdcZeKk)hP&rha|<)3Cr^4wiHoJUP$#+=8~_iXpiPwsGHzA?yF?c< zcR@>Sr9Z@u_ARG|R*%aR%`um)Cg>Ea3Xxj9Q%|*(2g@6i`0`Jt4P})CD-((3ApCxt z1)8a;Zh_Xc&r&ovEx566lqpBDuIU6(mTuntTyM?(crMbllnf95=~eF^`r{ASJO0!; z#RXCVUYZ;{{oNya9Vm%Soae782;DPZ-e}BsADpnFwneONgx>WxOAca*X=8~-y+i%H zXlxYJKON`UkIue0>Wr=n2l(-9epZ}G`?KCmh6&fFCPa+X{hD;$2q<;x?2SKh0phUr zq@`UJnKyb4)9s?~^|!o@%Jipl*+agus_arcTX}APk@hmzTJt;C=2j>nM+8w#A9Q;&F{$DNFsyHvw~ZVx%m z5>XM=OYyr3p7^rUh`sz;D3rHmV7>bTS4*Tb`Q?u}24E06Nv>+a)&YyqBS4n)wozle zB&6cQfBF7xwefK)B%-y*f3VdCmPKGSMDSTm{S`p4_fBt) zcv2;O-&tQkm!0QQTII+jMf>$Qg%^kC;K^<8d281Zvk}nZqQJG5-qN{inTbla>zDfC zgA{p`g&^Q~tMY>Y8uRg)iTqA4v5vZfGyYk_K1xO+&!;QdOk_n*B0o8iwe0}eabRpJrAM`)kbjq)MDcO zyYJw;%4oYE^`s|8`Nve=0lDW=cQxiia4Z`$YtIqmveIoL)K5ki(s~1F=C9HV!ITi;;_2KoBIz% z>x>!;5QCDwnjfFR9nP@r6I+&3-mj84dTYWA)wV^xKm)u;%q(I8ZZ{KL$H%s=XV1s? zBG5^6$rqwWw!=(FR)w!bkKTn%8*#ghTV$@QOuw)Fp6xS$mMy>gbfg#KX|_l84q4PC z3mF2RyA|i+>d%fK?z#qUSFzSuui0Np=FE5cmuxUfm|VK>>d;13^&g)_L@ll2-JjB< zK|+n~jPc7-!0`I(PfIlJ9tMZYe_(4LVQ%;)sQvshm$t|WyX>-&o}W*8_7p6n`n-9& zn#cHcI#{r*aBNy;W9_shXa7s0|pnC|2XDd7HGJ4TpCq0ZC< zD>W&Xbw;v?SgNn7g__Cxhe3ro?#RJTYN7}R(0J(XStE4R49K#u~+G z{n;;`A`fch#nB;st+tog5kN!!uQqxOnYOn*B_X-;-uxcug76L1Asa|{nktdyjF@>; z(ZuLH{cOHGCuXzAIR$fdEQxt|^4|sz0`Y%3;U#-R>#3NNe14B4{^#gGWD|@6!Rztp z;PK5CQS;;=$pkGc$Lh%N;NWo2=olRf8wUsb#P^};;nChP0v@XPRcAxJnG|vP@u1#W z!)Qu=!G56>Yz>&f7>P@kY5{nTaOXahY;tOKLBIG}*_&yb!K>84o_{FWm6K?|T==Dx zox>qa;r3EIs|~VtwCi`5M!fbmc*nuOjVOpROLU4ON7N=(i!GppVgy%6SZH(*>YwaN zV_ifx)2$jZ(iE`kr{u(?n#^gPA;pQ85t6NGw7)yApBv=$s~=c{pWrO;+bbjM$uv$r*}hI(!fl&jfR+G=m)fUI1KsbIBNxRxJ&X6S zaRE6Wf7J24Ha~@v+t}l1$if@U`V9{@f1_5_K-EwL1%Cjx-iTz@;RTFal0h*$m;eDR zGwMS4oIZB9_EVTE$N48!NGZ8GqT8GPA&(+2AA~rD!qB{%kfo*}cr164@6KPhX!Ud| z9YxV8zhEk=3kFRGN&c;=7aepq_;zE$r0I60taKw1`O3S+Ko z2W4ap7?Jo`N21X6v6$E;%=hZb#X5XJJUN7Xg>Auxp+~A2aq3&6s-ta+pUj@-<9pe7H?IL7DbtG+{)fu6#+vq}v;xlb00)Kzk1Njlz5Wx} z%hdAgsW+$s`3Qk<@l`jGPfmFDdX>)feElQm^c{45Upy=cVKEGI9uD%)vSdF+9Y?E} zhKr8!N7bpS=2)L2{uAJ}G8GJO{eEsw7HQYA!8f?gy5PZLgr+aEoib!LtSE>~&n(fv zxGUm!hYTTkIHt>nOH_7(~%7_etIh1S+H{s z(IQl$tCMM2cuTV{_`vf;V4eNdrSv5^V!vMBtP70Y@BmmiI2?Od0&tTlFju`tl}(EP z==>|XyP%MI#+B?dv`%vkZcnEex#cco>~HBhc>Q+eKjTa&HOzdNc&(z#?l719dib}e z#+PV{Y2q^CCenzK_S}N%_XOg&MiZPB)9_u?5?6w@(ct;QMTya4mJG>q7&4Mr&7@8> zM7$6kE3VKTuB&MPV1-+)6;J#};ekMpyys~$#QhbN=$Zx4f@6Y|p>p@e7-#^P3MPNt zR!4m5^O&V3Oww%|3ou>Et`nu+e4$B45K8efMOb@T_w5~Isg-$7gXHj;qEY;lCsscv zA|gLD^P}MeB1*^ctNn5EnXsL4Fc6&0evl-2|J4*_;K0U;Kh)YjI=rJ~+I~MIUZBoJ z6~69^>s|k$TKw8sANjlXtaaFUoHuGC(i&>Fw+CaiiE_^~>v(HZw3w?OUj-*Il8ME{ z@F1_xJ%{#m5J6j^Qi{M0|G0XypGP4a&Fk|-oQlh6;q;W}m|k`p(5s*S!{X*~eJ_A= z&{e(j#WUF?KB#o)4M9Wl_e;3)b|<_L)cjtaD@HSTJPXy=c3q3Z6Xf2o3yr3S27{#h>m7&or&<$hm-64*S%vhuTIw+ zX0E&_?aE9otiP?f52w{P_c&4A1svdwm`1dHh-obu1QwOPQYK1OO%2{}dSFczna{DveL}}8U?Zke z*TeZ}{KdW>Y^-A06inA<)Df z$gWSH*2h|HKJlk;igcpwPpFAiP|Sn_0uzMlBO{FtKSww)=(1r|nmQoJokDcFw)3+B z;(RHi>M|W4TH)bxF;CDPF`khk^sIki?$kfpbg7|#YE)~qtPo0FoZ4vpb~$oIR4k!W zCJhNMbV%Hg=B4>cQKrat9VAwl)os2ZW}GCn>FJ?$4TXtxIs3^#%)1Xmh!2b``@a-8 zVq=wTEn_r_KX?aeSmoK-H@wOt; zBfd@yEe%C+JRF9hS*5ADc|CDVGs*1SaO|bx3Eu{s^l03EC&4M@ngF8ra*QO!r)NW= zzBLt^z6u1FBWZs1qYoO%;vRNu7Os)VTr%L8R@F|u*OSn3)CkQU8YU;-y|Py{tS(8L zC5ZP>Z0`9b#X%F^Cm?RU&LYGOd8bL1)hbPPG+_?0Ox+8;a*X_#d%snm_~op{AK)rj zcejyL(V`#&c4H5MA2yc#?>mpxvwTz5`&Kw^fyR@Q#*p;dK?}!8< zJ-unyBjadRV`+6y$^GH3L62zicl7>A81^N`JEgN*8QqJ*6zr@XEFJYJ--zK~8z0}- zzIfY8CfC6wYe6=AW*iOoo0Fw+MPtQ$spXsLLPltggpGZF2=>o&bAF`jBnZF>Yeqmr(D6Mt|SmNfIjg_m&~KW zE81F#J59?)%x}ePQFzDm+n*0A_7dSD!Cz#z5$OJuq=t}~=Ni8u(9^Yv(6wjVGB%N* zHaDuU^6U^Fs7CBIxSaT|WJ`$(h7xn$bLfE!#(8VM)D&ONcjiy>*iAfdYQ6 zr_AYw?KdZd{Vl?ocMp>p_AGnJqpI~t>vKy)`-{EsYNIOCQo=U+6Zzb+s~izN31(QD zCtN*@q)L$;a`$o>*ip~-5=j#g5v(tOIU@mKP2qZnY zG_g32jTB6u4%$R6taXZaI=Hiwm{8?-V~y8v{O&75#^z=9#+*URvNGwE-Dpzbtb9Q< zi9b*6?aa8o<^=KU*`%}s@u~O)vuRvlRM8MZen%{g(0{+}r|< zDSTcuv6&+hO^7uX6Vn1O$2}7ZwXacX&z*r2l{vpOe_`r>bH!xH>bNr@84P{*>L1b?I0Q~!U z3UtL3Hhw;9*XmjmXjp3tj{%Pv(|InQ_K0BEa}Qr4-Gg0rAh3%)do!fhivn1zJn+BC z>mA$VVgGJ$10}_L2>gf&$6P8ke~`l9jG89$o14ksj>9UG)9c8kWLQ@Li3nR`bTy!o z9Y+ACMzsYOxw|a2@{3ge7I{_lwH3sy&(o(CbMFNo;$j&T@%khpS-9oJ{IpSQP!W_l z%DmZxVbo^YW#7vqLg!gQSAW`Ds@EmF`Eos3pN|n22v^-8^Z8-iu^u8e#<@-oOB4WM zmfv}-3>7fLjnJN*OviWzM=9&)Re=i6TV7fZu}>ehskYDA%nXCJy{cWX= zc`WEjNoc()S)FQLSdPp2117D0I&#>%qhg%l(jN`@GCGYM*jGq;zhv9UgpL7=pUx1Wo2b! zVOyRW=Son@VD#4B!epgz0-vd9im^*S847`nl9%XBi-K0 zn6fdT+Od3rv^-~k!?6D8<{1=$_xTJp%+oz}`j^qk74z#C{UvaAzs_c&7`O;;ViBSO zqHlDR8zO$ZC5I+mV}!rE_<+GWgcXIWs*d>T>TKx=3o|wIO*;(IR_!kL zk2-+}vdQ%lm(N)p0}N~pcQsRvDOl%sZ(2Kk)wnxxk4AQ-w{N(bvhWLK>-KDn=)O}| zZ4d%5;0|jh++HfP^+ohXCOS&`YGCu;XJ9y3+kgRqisnnP zuk+ihOYZ6BXIkjl9*Y~L)w~H`8h=7ZANpoX9d5a;$LZo2-(_NE54Y+;Fa+FG*{pLL zN_=g}MeqV}GAgDN7Pi{-cQ_d4duqt*Ww&&e-%)nP=r_5&_$f72Pf5k$8*bS})!pa1 z+3XH$fbuAQ4amYwO>Bb@YsA2r}#`Z&#>wI{6>c=MOe&+%gjI0Fk!JNUSnir~7boE;$+SzQA@G^88}h-b}Ak z$md!;Fk#szC#v}=d_@Z;Dlpmb_B&T=H)kMx4BhjTd|SoHMxT~5Cx#|sJ@Zmpv&l86 zxV)!7O7HWoh~_y@wwm~xTQ)sOrnri!XTxh&-!rg;FD3zz>KIDA*=r`GnEuMD)*$^R zq%w>h^@r4s(6SGqhZn}0n-I1P^cr{88mIaZ^bG0XTdpG3Yvw=;cTW~0k}BtJW~5IK z%P|(7t=}-Q$~C=%?}$-Os0I3jUuiExoKz=U?JM(!Vu<=Lsg+Cmd~{GRui-hDAmG9Y z{>+5iKuD%1nc4^QyDDtU1-^r8DI&C(>E#~0 zvxFthP8*?|QA|+AXO@)cn0sv)enqmpawG~m-L^((qW1L{Dgeq$DSXwq3a^r)IPE>k z-?GTuT|qZGpldv2k7q!=)oLY0wG=gLUXj9P$tZjKhfA(agXv-MCvLh%cV$_7LGX2K zc|P3QJo3(m;a(NB4tniV9hm)BXZf&MLxaG#AI4E{WydQX_pY6IsnjglxiV`baaF2W zGsyoBQRf&{=@;+uldZ`$*>1wgwwqj&ZQC{3lWp6!IoY;rvg@Azy6dj{?yU3fS^GKr z?ES^($9q!rqr+Jef#L&fWxd_9If3C6Ws~T;%h|v-o71-AZ8gy%OGE$tx7{5b)Rny4 z`>_fF=I+WtLpqp(?_o?k+M!q6AJYen&rDVGNeT$+)a1vZPdrYA1V|&R-HZGs*sW4%k0YfEI1L4{NsRz|zkuwI>rS(cVN5~K z>-ef0Kw)yCTa*UYfKNby5s%aWvXhY16CcR!C9jH?rNmM{i<<=JhjZ+P3F1Nkq=KoK z%LXY0Qh$JrUj4jWzAb34%jfdtOu59u5`9jpd|_Kkscr6@w#rY5f~%XS?pcZmA)lH- zeAYD-xrUEl{cu)J@|YD3p*+Mff4A?9MTEJ$@ouw$YHw9B=dh_IA7!dmTHrSD-r$N* zIZ+gz-dCgxE&G*otV6U`*4wnQYS53}KtfMsOOj}}#5Dd={;{VLl!OuLN>t(>0i0CI zZ29AO6NWsGJ~k`OrUU8VUdRRt?vW=FO69f$=^nW^_-~#zBOkCs2;$ieOB)w~OzYl5 zY_Z3)suYW-+mp*`v(J22E2)v+aZRwEnm|B-8mjRJy zt}x^5!eGRTFkV@BxiNA|(`D}0yQpugY39D|OR&(kEj`o#<3nQzvYVzWuYHNggxxuQ$lDmC1BDCyn_ zz0oFJ1uEl1PyWGe{-HIUKYwj>lk!dL%ViVY$DeZc_jo$n+3Axc zHwJg=DJ8$p6S2lJm88G1FQrGt4!RgW8dq9%deT;&U)U@+iHJNosqG!>@jL(5ntxIh z*aj71s9F|bp2@Gn;o4R*Lt4YcB?;Uq_|5v)nb03e1>ZznBg@`LjjFYJPI<{;xXWO1 z0opf>2VmAvb9zJ}u-_!T-`DRv;A$2ZWgAp`wJ@0ru6k6i;Jjz2!2y*wEcb)*VrX&X z>fqepCzs*9%p$(I@SwE}16RDieM8A-6nkQR3vPUnY=punf!grqh#T4@w`?!v=HiVJ z@F+UpLpUY-63b0wDEA)nNDyTEr~eBZz((13B=qOQ#7j{dtT4FZ#fh)FY+~rQm`&bJ z-RNJ!Qr1(Rn$3s;xTV>#-a1bS zVSmMMY@|aSFd<_C6$aZ;vZLQQiHc7}_AfdVGEs`Snv^V*ybA`V? z+$<{VSAefU6MRp<;`%xf@WB}Oh1NTZJ#I4Ei}-iq2}hin=8u$niQw$BWCh?ApD(y` zvGKV@&0f4OJ6$%3IbRz5Gg;@NW~cn?f^s@a5g8HU*+PC=iq3%=?*2C|_74&Hp-1U9fXF3Ob5{kOWUn2HLMH?yTS)%7D&%rQcv)%%CXiILgFh086`(_WC-_O*eos>yP5KgCI5nWTLtS66Q_idLVwb|h%R3*Mk= z&3T+?fn>A>Mr-!+UNsS*0SkVxZ%-W&BOxGp7Zl9$rxq&Z`R{R2S*6Lz2bM|?e#OK; zEz=EH1Qc*}8vx+LucXx5!P71f1iu30zV_pZdHd!;mxqAs?Vr4rlsJ?F1h*#Y4;fJn zvHcYP!Dj-#q2(~>+Cu|oEpBy;!2w&QXt?rlYmL2=8Z#dk20Fh z3|;I6BXmrqJhQo~bbiH+{a=Q8v!4g}ZqH%IBzaFd;30@OzptfE*#d`Z`rl<6gVqz$s130C|3uc8 zK2}}VMepagH!}ymu9ONMVtlg*2&$NCkn4Unizwov!i3ASB3)#@@|e)KT8z>LbA68g zLj8k}f&6e0!V+eNWckzS51QXUnk)YT zLyyPZ+GB>Y@^yx5QG^ikE%-oO8^N{Zy3mTNaen0mxHL>?zgRX(-@e6A zXPCR0<4tXWTC_qs2A|H07SgV!Rnxu9H5T|W!YeV00dZIYZ?;3X%GFI(h@{I7X@FQf zS(+z&$OHf|5+o9vK{?(li= zyVgwihil(cDDrg&^CO|aGWh!HkFyhU=Ncf0XWCDN27tP!#;vZB%7P_PBQoQF0&N@= zP@h`PGi2<$KNSj-*Duo#qfT#kzq`wY03p4DVE6%-ZdKDIt1<{$PXgRFHGIRlLGz%r zXG*>;?FU4}YVEu#|JZ=MwbK3I0!-Lwk7cS$coCqiXbkH8QwfK1^=0OaI1i*yA)1j-;Ob!N6m!Xh zR7XpPl1v!>bJu%oEX-}grUe8iBmf9BZuE^aB|h)Z84JvT_hA{NSs_Jq(wC)s4G;hC zwAcE|_}qpaznP92aO&pR?N@g9+{ujdgf-@!-`)wf0uJ4>nMuJgCcUsy6X(o^^NCul zYb-56*v?=TCT~+wMi{=mXnyq_OpdqfEs^2RF!khf4f%ebFraf z5P@30c5OoWQlwMgJZ%2A*&yip|3U(2g#croILOpgv@}$VKR`+X3(Lk3D55YwK}AJH z3Hp|anVE%_`N!h;_|(wo!i4OHqHO_Q-D=eCG@EhF|+8PmL(QT?@3?f^*q;8Y%@ zPROgCW(IfmT;V{mPxB|gvEX)+z2Gxx9+1X~Q7o(q6C(f|K#7n$fq?_BN&S(_0Bg3! zg~l|PpE&+Y3aPaWQ+ZAS(oRm6`U zFIf$D=;FauT5tQDpX3FCIdkt@8k-$XID2k?8wGHmY%BD)|8<64O0`7jBVQAPtR}L) zydT1v+-S)0(q}1q1@q<~gVGQexLcaIhBfaM*3-AXQ(c?Vo1lG5PJHHCt&^JXVxQz+ z-8!NRj9<2bW}|&6_F#Q{2uX6ph4D8BpoFjo1OJYS?^_GT&(e>Y9#ZyAt-F5=(0|d> zJg@;BDKfm5aD~#U&!@>EdIo9OtVBAlL zA(tg6Bk}xIRUvDhQRFhb)981Ee5YLlfQgD;MCyK@ReXSf+MjGFW{n3})^hXjtOrz) z=Pwl;uSUVUp3d{Tr>W>a)rHtj$j5r{6Dp-5=h^wXw_D=~TsbZp{0c~+$D8Yf^73#hIg{sf2m-n4`cvi8UNXX4?`n3a3r0&!#Zx*hda_6CQ%x+;E!d; z-yf0FkBdUM+iu*hkt5~Ej|5J{C+f)!G`Z zHQHdC-w=&e@iBBWVjiIHoO^1t7&isr(0`A(B3d=00F<`SVr-fbQrSZ42&x6dGPSou zHSHaZUzaYv3v1DkwGn8U2<>W~U8=ZFh16fVI#H9!pWC}j7T0yH57D~fBTR1Tg>Eu! zlRtLV$4PNpcYNGi!H=p!B$Uo!z91GmAtB7rq-v3@ox*XVq25Kfa|P#uKrz4|ZMnih zLxL{)zI`OKu5{Cx03HwwvH39bR`L{rxuSpAUOT&!r&-#FiLX*uyM@04wco;51W~i@ zvn5HiH$lq?C75n!Q~&lSB3a2D5=_i=Y0DFQ!@y^{wjs?-VVpVm>Fe~rtLDAsbrG8d zv@A@oD#QYJeEAqR^DIVUe<}w|$w?+QEnV%|#j?ZV+_s;%UD-f5$766yG5+)4W2Z{% zyPaF@Mm9)JhwIan`=UI?#&j-@4m#UZiQ-(GpXe;FND5o*i4h7q(3g4Jk(0Nka7@2z zJ!!GF{V4?Fv@zutxFZ3L8KP{e_+qN|@m(@X4o4GwJXP&IshrB8+ ztLCz`T*N__;f5I7*)o--Wn_v;#BmH{Cm4Mke)&)>@>&@$?%{>}7_!V@>2PX4SDGMm zDJFNShD{Czvq(~^Gwo>Ji8b9-JqE~)S667nAI5Shoe^d_?Nl6y;>Fv|AKS!4u?}2Q z%I`d(QBeEUdUD+%*xb!0{MVRHB=`DdTvzmnC=n+hw%7}EjSbxMltq)hGiv#G^|=Wu z7`C|*!~iTGHb$&xju$I05Btp`U8FS|zHhA^8gS7r#raRHZ_Ge@i0|M!xfwrbguw5r z^M_#J8lL8n(~pisP~r>8u5#rfy|2au#lO*5btSS|EZqfYXTDo%gFW3`!c@sOc0}%Q zZHU`pW&b+%QT;6b<2p`n`LpBRHF*;H^6#~&hYFamMP2)H>S3H~0CncpE{aGLz9;Xb zwS!M-Fdt!VP9s)y!!jMrxL>XZ4MlLbJSl>#B?d=+8^0~cuE*fkO$Ag8MTMq_ETnNw z8WmT$=%*)NPXG-BA_2%N!w*?C*Gn~rx*dLpM4HHjT;0EH%_tL+=c^!?$aOtD^w7qA zDlFDhWq;u^_p`sPPd$v|za-IZMbPCKcI$$Ni5PVRhAUTcjNL>KDU-JOJ=Qm(XoP#MKN!q1y80^Nwn z-oLLweDOTaqp$MEVqPV|zi)s=z^#k^8&`UejJ(bwc*+26k- zwE(cun?fm-HR`{71KjYOMVYT<17r06yPrH_2S|JwoU{UjT7-}aaU!3P+G$HiBk)ki zB($=b*=fWw#*kFXDm1zQ8b3&eka=v<&>15aina zdA`q4P(dub*vDgF*z?xFtpJd{l=6jR!Nr#=|3)W#cDl)^|RZkTJL3}h{rKT#tsf6g^YJKV7 z^4rK3yGKLz=GNaiX}%uI8zP(aE%_|x!@Dk*zNaKSN#ri)3e`H~#Cn8rO4N%o6}m5#A-xSPD{b>qY{(BDCb~7Z+|F zD1RK=2|usCkL|;9VE)Wkqew2Vlqgi1%KDRB3$xiOvF-+G+y@WoCBvC@_1vSWf1_%~ z;m4g-8LjrZY*_$SD^4|x4KB+)cBJXSY?}V~ioEFDdnxk5IG#8GKeo zQu7r+zkSP?ABDL5&@xSYCjH$d??~@G<;a)z4-Su#?m(!=(`)e}#X#nlh99h+qbf-x z;J7Z3-f`H6+(-+*^i5U1vYC*D5Tyeg(EBcecGNzaZN`lbYKFBYrT%)kB^vl;0LV3o zw{Yy|ilz3u&LCjE7Gik2qU0_LC0&^IYX;gHB>sHpFo0S4P!;{)OUsGZW= zawS8sKH(~0uZ5aJUL84~@`d`J3=K1U)ZEt=G*@QXe<%ndZn`+lpD-7zhA;U_3cmsl zt~KG>)aN!|eXh6sDjshX>J8F5EgwTNe2UmoI%dEB9`$Zj_syS`mJm32co1~^0C+O< zE_HKuNy?arxe zQ9yBTAGL@_%B12qSu~8!4a$GyI}2v8waFtO$3Zn3ATkR-K%?3nW?XZ1!_7m%1B`I1 zKrrs|AR3I-&+2l6dWS)lGO7CX!sm;$rf}*w8ZxvX%2XkkfM@p-WhB72=dZbi)&AD= z5NJRAaQQf;ltOY75ECxVw;B4_dH}id0PJ?cc2$D7%h%xDuZN_vfNl^sm}16j*RdJjIi6Xp9El4OP~JfE!q< zO}7#nrDg~-MnQ`b89)!J0`i>rV!hWfi93xiC5^pFOsq_f3Yg?UZl#U*_(IIpARzlM zb_5tdNRTeJ$GfLBmRdecHi+FpD{)jc{AK_OpImI)aL#OZ*KXY>*K@E^8yAmM4i~&` zAr_bxIh%x*Uq!!-tIlv!^b4U47(q_LD{g*Da}s#(vCPqE23E`j8XY&FO$5GA^tFBcxB}+8WKZ43a z*!hDE-@^Q&_WRU7L(gakg(b3t(jejKdUn}-2YLULQd$f2SensauKyf&S+6O(wbNtk z{{E<(hi-Z`-Hvm2LhphJtJfWT6+WIr)434977rxj10Kz$?LD>I2$tPFT;LZ6`jR0#&zMHl(w05n@I3q@$qDgVB@I4>$fEbS$V$OSNO zpP{BAPelS4gJ8NnBj{%Ad_+(!`J<&EVH=cb^r8Tf$lgYj}T07Bs4Y`Q(Qe0A|b3id?`giG5@mh^5E3wr2 z+nRWvVb15J4pbO&pt0HIn`Y9t0L;AA=*-bu_)s-3`;Bi8-RSX98pyvXKQn;s=`_j( zE?&#J69HKyz8UT>^vafT?XY^n~H8AfNLSv(#@i+Zg;bzrmRkd)m?k$(RG z-!?$zwpsO#EINRJ?$OdZD{C^YBgbvrx+P#u5tb_bMBNSt=>`8%;TdagB@xl#ZkDV4Xn&7zvSX>aew5}Et)&q6FZ$ggO%jl{^nfh(`(9#51~x2({=JCc z7;?Uf0xH+ytNd$D%Rb@W^g6DTS$QfDe^+_vf(-9P8u}#TeLn#2k+q>X$@t zv*oukP`47YU2Ml6x!(UgF-LmA|BUvJH1CKg=>91{hiLsL$1HT)uCt!wt)Khywzg%k zxXZzZhFm99pKttK!+26po$6| zD)4pjRcead@LJK1SQ5DyNI#jXU_`7$pU(L-ht%@_aLsNUrll+>J_Oo{BWQhH_|QcAAovYa&{^^=z!msw)qDcAGouEQC+N<>ec8zc;q%SO zv+BYg-9_jBp6V+ddS$D{Q2PLoQQkLm2>50BdvY4MIuKhXz9z&Q4YPnw_%5rSpX$CL zv7OWsm*)|V^rRS zAIv83@hvm^$Aq7**8&?5_8~xQD58u&9#(4?!;rQ;=60RSlMo15DQ2$3f6z&dJ98*E zmHErLw=wWtR81`bL~FXGC?*<@zT4>NIz5lIW+&_%`#cKZaoqa7|5Mu*j3Letfc5*5 z(%zc6|11g!BRVBMcY2~XWLu0L^%hn$+`+qa9n{4M$dOhiLr_nlX$*DjumS?9;`3!Z zr-V;vT`HCxWGKac&bpr8&36ZIElxRBf)8X$3G>?}9hj#uN$^+f)v>E1x!57J7StFH zJLje4;@!W{M+yOV?-%V3ciL}ToFTG2|AHhM6^H2fC>d$yUT271vu)VYzfTQ#*a_pL z{>#qnW2xQQ>CYc)H&!)1o*&O%X#!ac3LGfu-}G+PdSZ&UQW<~DmY4+ob+$!bjgXOa z4xVI|KQMB2cD8-WRB<)`N>zwK%IweObz9iLw*LK)$7BR;Hx&=wAZ3fUZyOP+s0_Z( z{}E7UOd=$=e)v&m1_tS8&87|3Aotlo&Pf>XQW|`-2VUXycHFXu@iPMLpJ|hQMibsd zHO*aX{x>5~;-q26@pX>so*8t7`QByfLdKfrA}*gGF-7f$%M zNfG-NZ}o1=$|Zhw-qT+rIH8^<_bUwY#hJ)Kw+bbeC9YcUn6pulayQ@#A3YZ>1(L{6 zYZ+zM>%a$RAIc0^KrSf)lVC)HlE+bY)?-4uZHI!^%^N2V^%@S&CHgo4yccQd^wBMi z@0WvS{y3g@GHx@S<9TF&mGx8*AL6?{-zYee(bWs$#>`nIjQ?06El;>8l^I&^eu&Om zirxq7ThhfJk>4zLJzewBbJu4Qwe4uY_i7^fc>jzt_N+%sq zG@fMJMrR5QYoc$;R!Na8AmiOF-28)l@8@e8KpkX`#zHB3a}?3hR3emg40~i}5TG2g zG*v}K^b`evSB?Rj*?^i9<%Suv5D+UXpo{T!O44P$%b1cmP5Aj8( zDEnCzoDltGc3+b7D1cnH!CWOi%uRZ7{eV=Jhdo<#gzU!53C(-KqT(-Wvbn$A=f*R&(u$(@obFjjd(&A6RU zCuatkt1g*LipC*6ZYFL=+msUhL7U6*#wuICmK4t`NO&K3Lq`Fh@9J~kX5NO$@FdBT zQP^YnOI|Cwuls+$oHbatbAN-qmhwD(qHekawc{R&ut3j|z{1Tt986ezC^&gJw$(@* zUKIQtJnm@O+tErz*W2@-&ep{jvX95dsXA^(g|Bm(rt`CQc9aTO2h9ob5|ibkMfY|X zd(o^KDXPGay5lfKtc$Hgu(>Q+b*3UT0L^1_(_O#1ogCD#_w3?zs z7+kIXS<`TMuY!v~;1bqR06J4rw5^29q9*jmm2N#|QoMq)Voy%J-5Ke!xVES^{tKmw zHoofKgED9ScLSDtO6T8mLip!ynz3Mv-90CQ7}~xv1pgJqNnP4~CKVW%sO4L15{A5U z6+zRDd@(yv{cDaO@!2x2=Q9s-W3SEh9(w$unI)#`-*KXKOy87;1hqbn=a{E{)aa_( z>6I<^pAHFl6J&G|ONdBdTIl3tidtR#t>7SUG8=0uc~Iaa^O4*if^^8p=s2jg_yb)J zkZCr|<(Gx)5>B@(arPQ&Su&Mxzjqlh-on9NYS`16qmocC|hJKmC8A7uE%yzP$a`&bqgWYv2Buib-xB;)_||5L@a zS2psR(6K@)YxWRgzNlrs{8Ua^Z7bO_244&8iUGR8LS^PAxG_tpEb4VEaiQHRO7;-6 z9v5y=#U$6CoAuda6)&OzkGS3*hV$!f`_DnOpzInYuWPJ@wE)4V+ezG$aUZ^l|r{P~pVm>%W1R6^F5Z2E$ ztN+%-(jYz&TV8^p5#qi)?fv-~@B1OM04joZ9MX9#d}k?w zUfc7bm*O+Kto>4rJ8Qpg=U4ahA8Z_qxLos;PK8X3!3GaiE|np{Pflp6Z~gEyv<;!Y zxsL$oINc@HV7NhxfyQW<85LRx^1Opb^s{vRZ4=kyWg1e%0Jk+o)%PzO6H`wx$Ey!2 zcMs~fk^H}?CNoYhH$2rH%!nwdZVA(wFUG=0x)T0Y-H0!Uc7_I36oqfR+$=c#Xencw zDQ`j9Z{p<8eg(#1cj^%Zt7@<6|7+mCE>0FUs1xQbsjlUsBah+UARYM9|F^#YC14N| zmT0@h+=x*T++*$R&Ocn=2Yc90$-&<@LRsX!PG8zUG){=20x^sfxn>A{c?ixcNAd(Q z7!0ioM2w1@WO7oycW>Jrog&X2F2B{Pq5zRp8W?@JOvsa80v46`|9YmjFm!M=THURt zhPj4AByp82zQpTQ9?JK}5C7INUIm=Qca9Kcu<7I$B|XKr-t2>MtKjPJ%UqPRv-0hX%pN=fnA>G${Z1C1GZ4WVM@2*Ix9m*~~ z4Ny9{=CGY>v4kfyckPP2qgPuK@21m)6*Je!7Pbr8x1@K&X?>vCK-12kHcyb_++SB4 zgAQtWvK^dFUsZio<%tn`NJf05SA~zU94)jET_%X9g`~Vl)i^XJa*s}NSoZ1R9v&wiE2B5g++GTt*rihdc6jj8o)0}um z!et2|JB|-o66E}jTxc9;$V3NldOn{C_;ST%a@wtElp$q*IIMajQfIQhV>#AI-aWkI zFz-VlJWsPuQ@$W#*XInYys6-@_2Mazb`T42e6mf&8RotwkivzM{R1J9uvDxviF~BU z?3bJ^?=F_JwQo3fQx<_5}y8d+R|TI6{kXOXv0CF^;;O|mwc&WfH^BxgRrL4hEZVOo~Y{p}DXg^i&mQ6+P zBVQ3ltLFOo#*L9P(seEQb$y$dZnNv!QmMJ0p~u;J+z9{I<-i382^{}Ri1=@?1$e#Q zGv3hnz|`{6%=p~o*zDBgSpOUqH6;c050>$<|1J;VlNmQG(yr+=9-Wwj^gqCM>&okr zRgBdDZ#7#M>)9Fe=$h-Dj*tJ=9>-f0vWwKxtCR-@?e|%(9+n3WVtl(_e!vZ-ru7x) z;m24mUF>H}4D7@Ummnq@KMtX0?-Iqk3gXb)TiJxkcmk0(4KGoLH9g{ak;h@qkd&LUC`Oa zUHb&j0ZZJON926^v&x`=9D8FQjz#3y0`9;-5sn=JFYj($JUm>1EcnjQ^kcG`FR!!H zn^pa(OF?LeSDTBY$q!Jx=!oIb^Is5E{&BCz+}u&`QVp-sO@3kXI)?%YI6W@fCmSz^n1pXehp~F~2!sjF z6NKlf`7rq>kWas*e{m(rVR_HQ4sN!)eVw4mo$3J=fKuo))DXEgcv6Z2r;oOlXmX&&EfUlR>WkdF*QkRKi2`G*)Jl zN?%x?1*7N{5I-l43cuRRs8r2&G=D;Fr2%r8YLRqT2zoDqJ$jQwzW1a;GT8x9R zA1o-)H1xx=C*OCufk|v&`{3yPa`(I=zF1(HW1(xA`L?Y8?1uG$XBQDu;%CFg65E!Y zz$9yinc@u6 z{0S_v2{{=@cI-bzr+OMm(D5TXle2edV5)wm1_RfRQzvg1#*R5Z)ab6;J#L~Syc6E_ z(?r_=k62entFtZQZ(~&_&0=BAVQe zZ4Atu(-8-8*sS@iKLj1#AV}~V20y~<_QcO)>0COB& zKkR)__D+Xq+8vTFp~S;{H@}x-R61XVd7wp-Gl9_$=iz6d%~v}VxkQbkT>7~w3po*9 z;K2}+j@juI7?3rI98Fyd4L&0zp$=)>4jOOZ<`e0(E00(51*>`&${5R@0gnURIEkg- z?Vqb6NV4e5c4>((;@_#ehs}2!NS6sQKXacMJ0Fu&XXwq3|2_P~Bk&YtTW8-gzHham zrCvc_gvbVxZ)9piveGpvL+DfwPEsS!RKTkk7j)8t*LJucAG&6F9yXQrDCE@q zRT%3*;m^4^OpbheQratY?2$aVRJlrf!iB`pe9P9CM+%aC;5)9m^HXY9GJh{92!FZx zN(Lz3d3t~9(COM$F?<3{luAu$ZB^!v#QOO5(83DbklSX3PBe`B@}>@xlu?CngrNBb z@LmGz%@7ei^zY`XIdfJU#A& zrG|H$SZ^q!?`A(Nj`7z=im}~qb zg-p|O$@kCtx7+1(BfB)^7h8I-hxNA2jsep0SMJ8bcrK}Mw~)#a7gEIrd#ohg1QdrsO-W(gSyteECAJZKx`6U0i?ANzdo)|n+E4ecjVthN}T)5^JsoW zWhhm?f&Kzj&WhGd_U)@-vmpctrVGR8!!;vn}}_9q#usC8-9&P@Eayw>)!5tu?Kdt zora_q;AAxhn=_Td7C}%nmPBv-tlv)0-O;}DX$sx8N4>(ciEez7 z1p5RA)lsodHNfjog(_E1DBp50l2hbbYDvX32BwMulE)sfEv_y%v)?{-yF*z@DxC;X zsI2LUhJ^v=^+e@Xr6{If12VLX9WpSySi zw6NhBw2M#@dHV@7>i#&A*>o2EK|Jyi+xz*;#|H*+d^b&kn$h~rAK$dwZ79V3fxLmO z!lI$n-^b^tP{OFgxL`*pk$Jh6$RZY|*cIn{PNG}2qjUqoYD@N5?>c`QG!ukuFQ2$X z9ssyKUyYD9-5mHP_Q5}#o)StgrebWOv$!UOr)7}!!EI*;EkguA{eRH5HiTm?PBnHl zsWmoWbJb{xf>LM%ojYx}qFxWWh;mv#x^{o<4&3&0Wp@jB|G7CWVfa3|#lta#{^8+% zjLi`rKoVrieH?fmX|qjXOU$L*jUT(ml|uZS*pVgZd&L!363R5@O>pY&EAT2}`<7}z zrYGNl?qAk)zeH}>_C1-GWc5=roU z%y*4N*mQ0sPei6%E{1aH^RI)~e0j7qvVk0TFhl{!S`{b|6=a)_XQBE6$&Diug(j3L z=UDX(`j$1^z2Yng}3hsG|3lrm*?0o(bf~#(2N_rjef)%VZ#e;zxHw2 z(q}@pDGkf*3s+26+Zi%&eqsiLnJQjxL}|qx^*VX`VAHZ>INmYIadH0!-0%8&!Si;W zLsb-dNk<>hM?@4U8N+>cms&HJ&%!$`JX8u&7Kvi}DRo&cj3HBBKE2=^y&dPepu3sx zH~De4UTq!@`xF(i($JfiZhhA)T~t$hajx{{0JNrqFwrHMfdD`@5YkpQzAqt>)ihso zu8PH!Nf7nw4#JTvld2y4*~x`7u4z@L&B)_T(ccx&PQt#NtoueZ#t1L-k& zg7nzBp_%~6oxZ6wL*T_@F<8E7Tl7ii4L^JF&hfT2bkrD$-!sB5Zn?W`-z52461F5v z!s+{1l#f4`@NYVk1y% zw+nimA_=avw{LC}x7k>PlTk}hpee1kykrU3zWBsFJ20*~NHl-lu|5BdK)j4-x(`*s z%N4E|Eq)A^q06vP)TN}hle2<9%mUmN@Is=4!YU8(qBHtnv7iZTL6G8#sy659dvs=w zfDpfzBVCV=NPR&$XrrTN0HO*k8O`m5qeNs`JH`rpaVn*n#!rCd&U($Rme zrIC2ah1pBhOe2MM4#Es2-lb8t*E&US2lxk6zM-9p<+#5sC?)|r>&&#u1 z2CyAEE5Hv+{*vfRY+dqr&SxGwwG7(JgZ079cb{OdT4VCMh6ol{#-YfO{HkFGZ$vD? zlHJSn>1P64{u9su05$TBySp&OVhQVa%zE?~U(bUCgCP6y$!XOVex6qXUM0KkmI)bK z_Or1qSf+Ab8=Dk#kK`RF$;U_WrP(bfPDu)w=eM~KdS1$}_<+u?L&&u8$mwQv3j!oEBMl6|+S6VBA=tAM4Ewo2xL0j^dftF!ypT50_y?#R z%y{3xhCDWXKgFzWFCaks!x@>n4kP(4-GASpC{u^QqO4i@GjChIL5UCHT%|%f+r$F` z4ut7Pr758d!&^=rheRYCwH4|#CjX>)$M3QM&^2+Y%R_4Ixq z-orkm8>FzYe82RA&xOw>v>dMdd*HljW)(>zATVtcQ1!Avq_~bVkD(zP!1*Z!4sZsm zgDD*u)c7}hvK|}pXJ=-2_4+ON$8&!@lm?u zMQ#1A2Y;tJO3t0)6AFScWRQk__Sg}rlD|tI=)Y?6sD%VJZt7!Kaxi?dT z%UBm*Go=-3f7C_*eDl(|B0Fo78Z(aO9A)X4ZwpQlV?oJSMdHpI0g%fxNMO&k!|(}@ zJ|`7J{cica8raJzqD<{=gR5bq*hSK8NX(koiy@2m#Oo$|tH?3&WM27zD~CeOglKb< zvaJV+*)sFK$E8uLN27S0}3wtYlwAUq!|a($td zE?B@XqVcVoBWqrkZ-h1MeVa{2qZQ2dWP(T8sJNBn|ChBe_{4*KtQRc?9kEQQoas%7!2n&%~Va+gXDKgh8Lz2t(x; zv*??~4RosmEE-aLtesTWjk?}`ZcgurqjNco9Du_$5+Q>GCTG&21XhE-#Y3b8ZW@3O zE>{lquh@lhLwg}U7<-Dt6S8Xv)^yHIBJG z9+!~eq_P8NzPe|3XZUpKJ&ay{xw)Ig(M~D&c13|wPLiu%4a7L89Y?i;0b{NNA-YoJ zRnUC;t3u9FiHG49U6fTUToNZL*#-L&pkr0ck4@L_JtGkjrbHTr1Xbt2AB36gn<2>F zvWrU{?3?MrmZl{nKgk~H$ zZOmvxh+}|@Nw9@1!qsK`)Y%(-dNbjhhoa?+qV((_`aXjn@gjnt>~h*zioki{j{m$E zs!-PI_X1We&uIRFCz)+~+1d-}m{*ILxC*WNwiqe}ke4ggSVqUnn9|10M_(5TMRO@k z(Cdy)27B>FcO10z2!6Z>2}+%TCSFT9OP|S70#HDP1}UZBywmOpe#Izg65LYp; z-ao5m&o#CWMH}f(z1!Q>ZpkSmGmoKB413ant6;J~zrV*2jHn|kvrd$&<}97*1pc@P zhvI_wzIrn}fM~lJBC#(LMf@{G6iTONs3GF*!5HU2M=p{|4X5m%yBc#fos5m$U1Cl_ zF=JMkL#;Sg_gXSG;)2=^KoaS&mZQ89k*-$k<5!D7|y5M)G=!gOG z>f>LWq+mecy(qnFf4c!39Hor=-ki(O`n<#U5eE&(MqGcOg=9(UB-o4@^6gRguT%Av z%X5$J=q;kr_m?>ss`oMZWG$0<%522PRF3ibZ{Xf$;r~O_IYw8~h1+`X*h$B>Z9AQG zoOEn=Y}@JBcG4ZIW81cE+s@tJxaW-Xum09ptE%2LYtCn?R6u0HTv$toaK6D(5 ze+G$LZlzLFN~|%=_OAXfgNw_HedNFzE3>mFufA^YH7XO*BSTp{x?<8kK6xt)#bbjV zhQgzZ0-<=r9GV^L_<+-<5DMF@4zPr138a*Zp;~o>jNkRQR=u*bqNDPI!$%Ar?yKCP zLc3T*u|IZ25M`$at&8igaRC>vEwG%(nN+^7cbBA-el`3#jzl>JoP#>dZ+pC)K3U5G zJg??x2*AOL?Wo-xZ?$b%`vGG~BX1}vN{YfLU#jlnF5qGmr|Y`n-yc3Vo-!JS3ofcz zp93p6Fa3ba6!3%;t2mNv$=5<2y5GF6>H22>p z7$Z1XZRi+u>tlD}@pSdGD^nUMNIRQ7y- zI?Eg^zZICHHC;Dmi9J^C{sCiWSYnhUi=BzEg8%)&)g~`_mS-gO@@~zx2EOWfW*9+x znrba7M+SGkx^F=v(R}nrv}9^b1Rkv!FRQGVh)ADIZ>%*26o3 zU{={=e25->8XpKylB&){q5EwlD(wfzK!OVVIGI44`n-?byuaPD$RvZFoaoyz2Ne^@ zD*v+0NrhsR%ZlGpD%-yV)6;y)YZx88V~QE?F|0DE-IZUhJ|o?AewiGGhPtmXvt_ht zGcso7!KcUwPK}!)lg`8ZiJxa>r%x_MaM)^sYYJ!G0a-26DvGDsTHAfm*E;jcnBl`{ z{32Hf?UGm`E+5cXY!n-Lj!0NvfK4>rsT5r?3f76ajX$vWsR^O;@!s=0xzBKg9$#l@ zYADnVb5Y>IU+REA8?!ts2d!h0?+O(hSnzTTMXbBM?7kLUPJ9JdZgByDC9<=YO2nvM zEwXq!1oN-97diMvkq$~2%59T!+kLp@&;pF6wW|Yao*#~UfU|b2950uY5S5&t0R3Z> z3-U-!@zhWsY^YCY=t+B0gWrsRx5$iFS%2TCX6^>5V8}HL=nU*o6^mXH$2KDshROr5 z-CfNggle9kN8ZTD`z_&%cggHoR+hXG4Jot6OThs#HA>*q|q#nh1h8(d_l0w`wYTm#1*(l7; zJzFZfDpw<&6he{~6_c+ry0a(0-q-np1Lj}&S7OE|BQr9exKKoJZ?}q*l#=N`{&h1t zaGOFpaW81*WX}0>5C4}xO{z5;8}d$IJwx1xQNI5)9tJ?LNf466@*xV zEHYd=i}PzdKq4;1JO(@^)lDTgSZnI#b1K}}>Y*c3bUkv?13nRxYMi<%ssZm%__*P< zVk@6CV85^GAN^~sgIE<@IRp|9+-n&%O_W2{;JhKd&{+!}m7d;dp_b6PgK$CQsu?Ji z<1@g*kt+E#P3YfZY_mor)lK@S-JtRK8x!uz1>-e4xIC!)4`B*Ne}p?0AhRT_L31}X zL#;TAVKoBSW}PGBn_(7#cb}ep#@i=u{Y}^mv#c=JCSEsjP)ez$tx@6Td;v|MMk*~U zr|j#={CU3lRRGZX!C7r2dJAh}{T~oHC}*jDGVZ%IN^6dFajdNt8WHj!TCeres9~IckvX89PD9H*sGJYQQr;` zqMzK*ep`8P?lK7NY$AwKFk5`R2yOTzSF1@C<)ht^zrgo(y!jrI9L@#fxJCJ!|Dg|& zN>6Ngd;azp zwINVU`m38K=Z0T_YG%j3V^%ObPy|G`YCmbO>j|NKKXJ$f4vE>;{?Sl#ta-q7gKHKV zvM&rYisJ24?~c4F3l@~(G6MOdsS#H?1vKD8UA(Z73KFscuG3@*1@NaKG zK$e8?{xRVJ08oJLSUY_}hnM+?iy}h+z<@@!NK4)AX0nZEPrd9j z^RIcAWS@^m1_0>fhM4SM3x!vOoBi>G=8pp{TihBRewCPg4-Lp3*qXHJo%kNiwlN23 zr5QrxbaC*8(;36ESmo8Qd6D52UHBIU5nfE|;72>wLHxWhdRK`%rz2IwIUo6&5|-n6 zMT$sg$xuwMw2$l6!9GIq<%DSl1*=)b_=R2x#Z~mLINfjSwTuRSUb@|4v)8{cOT1_S z^p(}f9uAGKx>218j8Mz}7=RW8c#U`N8b8|p;^Jw613JZ#vVrsz#0gQpzHsQhiVRDO z2Jda)dR(BQv9d_L@NdO0JFbX;kG;Y#4Bz6klCYK28Gr8 zrOsN5$kIr2@z~{}qt9S8cP%-yL8kAb&;v+=K2XfH&G4uQ_4c1vS_SC{2vHUt)TE`t zyxOU+ldUAO3}y6+4-$btubhzUMEyGncHNej@2&HS%%=VtR?>R za@>#?=xdLnPGaa_=P(BqSV+{^XisH3=!ZCl!GcLK8_cA%KpCm%e`sfiML3&^;>R^N zQ^~{TSF#F?Ux!p?Nhu8`MODY%CJTxxKAx^hY3b8noG-?rPvik`-rn@<@Sb8@4JtNj zb0zAb44u2s^#RR#*C$~CG9=vmtU~H`C`d*S@IV0cI~aoB-L=qYBBN3XAQ)z92*2)RpnQX7rO@J zNA)=(%OaBIyGAPiam46^NU>{zQX@M5%)YaJLFo1j7ZBe$9O}SFd4qDx4qCk6pj@QD zg1B!HO$)vXY@$upFy5|v15Mfa?J?Bf~l;J4o2=h?+7%Vk`k;!H8?RJH~f06 zT%3VZr1Bb4M8LJT{2@N<}wq!-{R6g8rFpB!D$%*65ny{YhKkHCQ({M4IblnI_ZMAV4Df z0sFVv;=#A1O0xTXNn0a+hU&!YzeGw+Nm^+*Q2(QJ_?P{k((zyKuEx2C2j$H8)X@0Y z`1AxdB`rN2JqsHfD+enZ`}E-O@W?nd6%8FN9UU8sAm=y>j!9=yh9T>qsVbBG#!b+I_E zt?;C*o1Jfae<{Ho)gvW|fiwV==g6 zaAK&bTjr~mP(X+Y5DcBSbJ;|I>c8m$CpCJ)>I;311afL+hh@h?!A3~8hAc_9AzlGG zq_UEtEBHa#nZ1RP{m4(UcAx7DWWVNS7olim!noT~8wcgZc~_k}<*c7Q5(N-`agc__ zk;qPGQAjf)Z>Kb^A)+{XTQgn}Otj3e?07F;FzegyACvNVT|z*Z8N;5p+fDa%KttM} zu}@+|diUa2xD=)zldIC6xE4pUN7J9CyHgAP0H~?43W{XrE2mZx}QUlK5PDAf;X( zC8+aaYLmis=(A|NtKH{VjFYL$JL>Po@m!NW>`zAo4k`)kx>f5qOX|z=_^~~vqHwgr z->*zi^tFYH9Z?A*Sg!0lwpDmT3?E{Eh+$=K zoFxLtNN0B;n!X)9+fF|Wr4Jl>C4VJlM{Eh$y26Jy{1Uu!SI5RhiJKc0qkN8@`M2{c zf15bn@&~WA zdA@`vDg8f-8^qgTYE+~U<|%rd^?%{tL}n{{JPbr90;=}Zq0d<^WY*-hrxHMEjfF%V zZ%@O$!@GQSKkHV0`)jyIIo4DUYG`b{CrxjI@Otb*V)6KQ$e><=44)SLLxKK06458E*{aRJDeVk#D_8+<# zcc+Ek;LmDj1o5bIO1Z(5{*$}EfEQJhV#3;pvdXT|o1GS-*V~<0Q1c%s!@g6Q(ELg- zt{$*dbYBN%k?raxNw^^YF9y*DQ#G>*Df_zpFf_Bp+u))WQ<9_~pmUkLRKKVgiI?vy zuDSN>B|F|5Tz#_*2t~RCy|N0X)PJ6^*`(Q48WxJ!aqBPY zU%ia*@G){X&jQb!rTv=rYM$(**SaEl+`satz*@>1yUA*Wl!W?%S2eq{K1i9~9#bmu z&juMAn9XziCTuX-27vwQ$yGj@m7b{MF@vBYt2TTKum6xf7E)*p8-5ggn%NC=s|*@| zv;JB{o%fjzvN9v^pBec{YupFsVB-*TZ_cv#;z$jl=Gn2Pm`PC<2vk^Q`$uMoD3Y2$!^LC)X>Whn$j!d-2=TI*TG;FdN~8{mNzhLseI;N zxbJB_8jV!Re8j-N#|_%_dr7yg5#dzYR+|Ibmj;UBR+egt6rO2>T$&X*V{CH90T~aS zYuJL=Pck@E)ka+f%a95Ns%2`H%WKUi;OW4F`lPyh>&c7V>0;%X6pg$E+gabSYDXPu z#2O*Q9FF?*@T-Z=dn|D9@h2l(#TlS69}ZoO^rIMHIPLi-PE$WqRdGkB)zETA!v^^X zJl45aBc*Wn3fsFrEscn^J?A!W7ui;=Oo;qBAFpw~*&5+%45KidSh~teFLHxYHmD>q zPUw3jQhLiFm0f+}Ot^=xQH0F_`98fj+2l{II1whI3koGwxvaub_CF~)W z@zhZOFgaDC-@e<8l0J$m#B+8LvYO%iIM88=_pkX^MvnTJf$wiwy=-hwaJn(eo3B%j zGn<8%GDh>VH8k&RA$Zk^F>oVvaEQQMyn_WvkT%3emhL4+1?!4q46TPm@W96oekeAgJ zh;;%#tX)M0jlIODhb28%6stXUDh4>O#0L?hJ|co9E6tD8h@2Jy1%$jTQq%*)@6D;1 z-zy@nFU*IBIK$%R3t{<)0N+t7QHek;gZ-DL*|A{$NH~WrqS-+f2B~Fq58Z?2nnm>A zH*Pwt%mO-@0a4N;JDiN;x`moO;8{d$+r!!F9yvKIJt_W=3>HS!M0-u0-$Lj zNq&bMgop~YmF*czT%sZd@A+{WRFH6;Z!Sfd>;)QJ{!H{BFBw<|qTRA*BpnwY5+m==V_4b6wH;E}k%_>`P4qs5d!~lVNUY=lytUsG4FEvLz|xNO+JvvwL;kfc zDk!5cgKoaPRm_#KoKt|_R|KzcNnz;g8%r4cYhN_0;n)bkwQCIp1$PjTXBHX9s-@F z7p@x(+x9mvgDL3eB$!<@A^!Z`e#(~jnm5Alx3C$b17>j53(MH0y5cl&!(<|PdGq?_p`{!4Mv+z;<)`*)=y} z&M?956QAV1o^aZj!A0}*i$ec!*1(rteH<~cghN|~h>hJ13vxro^h1uO$EYhQHiYR* zSHRg9fPhPv0-7>}nCKoj*RHlVDR?UAxo1sAzZ<$=H|?bNQ;a`59}a_01~vpv6|2BN z@eX=9Eh8lDU*XhU^fW+y7z(!A-KJ6?x4@mv0Y7gDzy-O{**Uy2v*brK4Z&~hrBlj_ z_6?PM8-AOk)A#@^pYL{z6LHoKq+ACtT{3sN6JGbUO$Dp^jn_Gb)=)mssx^~29Gemo z!te3pr7`!AR^C_!$r62I(#a-A(_msNFzDUt2_Y>FbVCoqS1$&jm$cwYk>nSqJ0<2* zuAJ+warB_-L{K3Tv7Tl1{3)*s^%LA&rO zZB-h$0Vm5JGSAnp-%+${$nYOV_RGOOIrlXz$`ru)faGhZ}9U*``bJO)3#g{swOA{kdqcml7A3O$H7LcMqE0CFyIgux`^=|6^ zDUTs_iHl_dO~wcL>RP{CIU)&Dng5TUoMavAXjZr!ZGRL`$G1V50->ZSemn5=TQ)8_ zzJ(zH+V~&okF|1DPj&pKHIlL=sVJ?a;*d1MGQkdsynSUd0vEhNi&?vB0a23DUA6xn z<^Q2!{E#+4L>%9G&z4FiFHEo8_pEH(dzFXHJ3Q*X!wG@wR_QjTei4-Z^=`VE0aghS z5$vbWg%0=0*{@Z!WWr=*pf;WWn!O@e49?%LOMVSF!gR0NW(h>e};5$ocRY zmUP;g%RV{Q{u3eVzmn`Z_k=~xJuzAtDt`BKCWI}w(M9J)uIWUx_12qsV`mD9`;09pbJD*hHDu8)?N(lAR3An7bnnUrAJc9wa zav(UvwGxL|-nO_mwUcgKSh*u)N`WV`Sd6(#M^*qBsfroqe!>%BeX;V>3ECCfRW$mH zU&xX$4=BRbMR&4Ek%X^H*$9y+s4Yd@NO%Ig;)sKHjmz^f9iLoYUmgVO{F=S)7-AJn zcs_zC*_ifthKJHq`>B?;)uX2&GA8<<-f<}WJYJkHBMszbMCnv%aR>*`{{;aX(ai>x zVg0cf-?@qX7~kL8m*mjO31Bj#<@(Ne2-?`LqT+4Eo4@dL@`{7>7fc=p7Z(5~4-sU; z#C`1z89*pxrzTcztwfDZ%{IK(C?IrgW&g_2Y@^yI8WFhcL)A%Ci+Sm~#}*Vb_w&~; zM)?uudO3tp8sFohZ}>Bj+=AQ6c3=g-F-kUF?AW-?mtt6K@@-D!)IuuK7y;(-T&lhI zP_)@iYNYIkWEemJP$pCw^YDNZp-~M7`3G)Hu9T64b~%><`{E2mbCnbQuCa+NcDdS&0>acvsq8Q5XtsOb-JUB#~5(%(b-{sM^JzbR=O2 z64_aXyF?%8UenADmL&hOn>UnV4*44UAp@z1+3B$`eA1#M=$)Kb zg>~hir*7Ll-eObk-#oye1@RJ%{J)Wn*ZJ&V1d#GRKJ#V+g2BpA|_{-86KWk`8S_NfkD2Mf}*k0LjR&y+^%>GI3Z8R z7QB&uVrVlT#>c=n<)^Q_DpAJ~lt@+bv z!_cIIhuy0aT!(NuK^|#6qtx=evPQ2oRjRViPo%D`4!V~??GOTPOcq{>b}}fX zTD|@3F=Cl^x(!;3*9U31MlSK|joH8Z3Tg}GZ5TP?IT^Mr6%`m-h$pJX(Qn)_#6T7-EXe%JvxjT_ugV zCVt@cYh_qCi6LhaI0r=+6)+72he=xm`7O*Wk7TArDj%(sUK_3~TAwHR7=u}$U5Cdx z|BK@3pALsIgpuU&hs|oM_!RRAjz|7J*V4o2JgU_wQILgKQx`l5IjS7nu@?s%dMBga zD18>S0h$GbCtBh<(Vs3HS)SdI1e~ofs2BhaKyWMH25YG$urKE;WOxorkgI@|gDr!q z&IJ4yvKiJMJ)qvW(1VF09mJ|gh$PD8aUWq;x@|PS9r8BaEk8G~l^r5!N~cr|7EiHXxup1`vM99PBl^P zttETX;lv-<%*)i!@q;m+(tH}#PhE%-^{?N)a5etVu431TJGVKZ0_h}Rd_02K?`CAO zM%`K(-@|p+vp%@fIal{4)Y{+ma9}DTYnw3t>`*1*-LfL7AUZjtW()Y;A2B=N5!t%E zU0V$)30s%t@vvAIQw0HwCl4ER7MoB$%gsV4euelewl$e7wyzA9nIbx#8p}I6mI#uM zt!Z-(KHPH^H*T!$bnl$$tA9^G)FPXZjOWYX{8YXsT2f&(_wc;Qae6ZXa{gqntbG$j zk#?@a2j~=k(xERcvB@2%QRa_p_~60RxetVe!Ky10p!ES?(J|lY^+8oX#6VYOLX90gf^D9c=%eCY-T{ zIH`}&r9e7PcbB*8!|oFD#>Roy?dpl=XUo4%#E%sd%c`T5DH-Y-H2DV4kvDLFMdW+> z&3D}6&I)&Cin_vGO@|h9aUO#?Qd?hw3Q;Ef6x#q(_=iddORu%IIlZ?;NA{XGv71*@ zR+Mdj?|7s30}wVuO$UJ&|K8>zu{#%WG2(pcV{|c#V5FMvsHm!Kze*fU6K&W&{RJDx zUMM@T6W)h!`v$!sETW|9={giO@p@$o!hAA(7I7U|^~c@I zWb(w3TU*il`a7$><&~FLr%>mIQmm?rv9{Y-dS`lXs3x?3c9;LP?Y{TkVgU!q$aCFX zV-4+j6FlYDFI={1JAQ=1aR>h|%umVu{00REGMdOdH+-&*(Pdi69)7{koi2PUQCx2r&`RZf%S8?~G)o zr8h9loU>H2i>HFJM{Vy${ zWwQ#$ipR@3rrSg&T05_Pwa^#giHZQ)i3Leh$7QU)6Vc6&+%(lPI8>%4Y*y?sD;ohinE)* z>N{5Pj86Qu?Lp-q(`KuG-A?+X|7i~g^3@WGrG6n%-qPe{Fm55>=NbGH$s5Qg`s6UF z`W}*)S4Sbkn{DhKWPpD_eiNkzN}73FIC1>b){o>8V)XNS5XRl>Ai8WBQ#doSTn)m~ zO_8^|2a9)*Og57AK9V8iS-a3qP!0^EwDA>war`#uqP}f5pTdBy*vDc_vL4U?+1FuA zD{daQ(O}y-#vynuGtzu$5^S{ex_E+tid2Hw#boN`abw_Wyeng}r^FIq!>|hX%ELINW9Fqh+#5$`)8k|>m5_wKH+2lwej`cB zCnS(9vohYTpfvp^ysGaPYGI4W`|9&jUg;*Nx8C-`GvZYT3g&0FV9&lgOw`>v=-IQA zl3HhP@688GuvvFi36R!Oy+$Id=l&VgC5t?&is@-)V{3#9$P(8fouY;*`Sa`kOGVFZIgd*kc;v{FQXt^yY!l)nKGj@W5mkIW zUE8~qR-7f6B5Coigg0Vw2*;++TWn51UKdrBg;u7!`Y{9AQb|SMvCaCxc)AhyCscs4 zgN?IB#$lQeaRS3b4|K{;CJ&5rhbN0$UqW4LF{5vFZGsl*8m-0&tnI8r!gP(8msklC zs;~1@t5ODStBp&my@oL?TIlo2p3@4<_9wn8wZr7g(?D+e=%G*WH^|kU9Gu-PMfhG68OVW*r>+^ZK3@!o{WaE zmYap#qfqOZbs4-SqR-9uOHQf1i^hfVqnFCv3F=oi`_?580CAEh3}I+Ep5}h|5Z#NL zaS7i_uoUM!%>ce5(>pcV8?cq)bWGKHaYF`Oks0-Fq%9Z)=xhgn8Pt9^ajgzS&VnGd z9=Ag1uVSIA8B%(pqd^_E(g z<35G%O;;;MtT5qZ8jHL4+F!v9Fqqp!BK|k>491+XG#G%XFC-Y&}e8zm}p%BjNMAYxC$5a&xx8mGGgGQNr==@4r=#?}S$+9(0f7*lK<*Y-Q zk1JdrMnP>tNfZO9AY9<4?bh3xpgZXILO+7T3xmvcN0)lk>z5y|#EO;YbQ^gEHU+f4SGC`0fA?D6E>**r0WwF^= zfP3a{AGX;H0WRhBv)y{Q1@^kwtf9@iu6QKUN+CDdn3Ta{t1auwlh~CUcnttDkCN%z zA5ap8qo5WFVOlK3xo!Xk)VH!qD^{;&2FX0)XH~OBJ<(R(_TligqrL=CxH!-1#?TzKIU_S|a;ZI+uY6$Q|*?QP3%Y z0$P;yh~=UpL!N@2+l~k=FHH!=F54EL!?%^Xj}K7FF1|VEccC4=J~8_Jn~_=LwX!02bltHOQG2o6rp*((Y+wc>uI+12y4Uu?PsSU7KWpEs{n6}aviLlrMpj!S z1bU-|@l83d44k*jcZ8s|J=FgokQgkIcZ?s4{wfJEfW>dA_Ksb6RjK_F`ZGB~ec&uC zu>W}5L-3lJUsqk~=i!*WterNHfPCYrSS z(O(C6z%!FvQ%~n{th1A_L^^0M?eF93{8Y&f$O7LQ`PMPrQ-C;@jD03~f-LO);XiJn zEViX__5RhP&Q?76L^zBir+bMIL&w6)X$nMA+k!PuL8QEu58$7N`$*+cfwLFEm1uYk zWM>vRsS@QtNT{qNND|Uxe*?G%#(Qd{{pHgg()vp%!~^)2Ad8ILNBtA2Bl|Q(kL~z= z_Uio-O>Z1P&{XG*L-&4n)_ zw=e$Yt6WJB3@tnU-Iq}X+F zf>2ac|CpHpOGLOKbXsu%o%51qd^|`!WF{c}xy-AiWwv%?+x_h?YxVPkjeDzVuo+B$ zc=Bc5!Z_q_!=iyf>a?vXjl$;jX;mat+8XG;KQi`G>j||hR`m^$ZZ9vmsM!ANZTlXJ z4pml`Je<%adt9%xjZFo}pv`{Had3q3FHwk)FfpXMC0)tj`74zk&=Q!=eVm5!&BVj| zn39YY@!#K?p=_65YzN#K?^DI^2AVK_MdNKv_T{PYI+#K6j-$j3uM;jr0i&CJL&L+f{lj`779jj$z~W98_`0+{*u1|pYMAf5RwTav zt3U=3qTW=~LqMOOk7f~w{*$A7U8)kjVV54zxwWggw)YYEqZ7^}rI+QEi~6F_#!73W zRaR`_?o9x>UMWk#8S#FPU;6xbqYS#+Q>yy`9ffuyFm&fQdaaM>Zafx$y^Xf$Qf#(b zZm3_Jt?#*6f)`B7Z2KKj^2LYs)we~}Csw{EC3SlE_JuTT8eJmVqtWJ=3EaH38ZnXS;RZ??( z^BGxR?N3kl=Fx--a8csZ-g50>8L-P&pEw8wm)~&2n#3JzkhJiy)+Y@UPd!S8yvXsm!&Q*;Pm4DpBBUN0XgVkD%H2nhwTw`PS`2XsGGjcsU%Me*^;kv z=qhUdS4fT#e)AW)DFV6yq(ajw`tJq;WZlfy18HqC9{{U-BLCYnnfYrT){EJO!kz6S zv{m%SWzizGplNHf!|c&{;fn9ijURT*W_B<2H7+GvhZU)`RvfZ6d==Fmn1PB>@|v{P zrGwjjL~age%MBxJ%rl!LE#@YzPb4l%yt2-V%zGYB1=$=NZj-w|ORFr0z3|}&h|x`- z=2+toPpP@%j=|kY>FQ*MCoPu!sGy2AksdJz2Y=UZ#b(N48TJXQ^ud?>S)*ue{uuLJws=mtm1kkhXc?X3TgJ> z(gP`RRYE!S0_x`8IGaRDs?_l7eo9!x7zSz$C0>}~br(Qi5EbNc19gBdF_x+Yi+T+? zGXCFxZ-kn6IM<^lYh86j*Hg6< zM~lcNJ_-6gN~0HhInf(r5@ok8mDvER90gU@DE@>xoqPA9hFQm zn-X1{75zERRkdAz=brDAMHIHojJ;ZUkLO&%VJFNuUQY}-UvwJsnuejX|k$~OUx^Rlr z`lzSeb{pUBSLxlJY`Khu-%s$rvS?d&pA?KT*y+O9U9^kpvEVwAg}31`LUTk!Zd%60 z8j`fZ@8D=gF=O1GdJXT;n@Xjp(}-nPVt4qk3%9(bYcQvK+GJw9G_WgcQeOPo*(!Eu z2*4Pk+Q|m=L(y>gTxgx%hV^Mimco@ZY=p!3_!`~JGR|BO{5Zc!@n#v$T+u!i5eeQe zJ}4RxN?`H1<~jTv(P%n;qXW68$Yqf!g_k=xG^Z~SB{P$qrU8SE3_{^CAQl|=>%gca zku!z${-@7${Tm0mTxobdHVmDxHCQrUWOs%;3Z+=A|Elfvhsc1JHl;DP7C+9q49vai zw_N6*4leg$@{zbTHjr(VW^JW@o2=%IvVy)NKZ-No<0$RgPQ1zzOk!lw-9r>k-m~-1 zcGQG=tu6Je{?Z5*2kZ10fX_TEN&}vAduHGl=;jsAtjBDiqYqSkW1Iz8ZruLVLnNMb zT#= zOx7SxC@&A0l(bATtm(VtO@@m$0a#S()NmFJc&8!bjr2=+%GopRaA1(|2Te*%nfGeCXG|Mct~#9|6zVM+P4_zf(A$ zWbr@Z+kbWKmgu_J;>WjvCar?jVnga{VVt%y|jBH8}Srw;=GfU)4v+>=n z)+?j-{Ib(Ac|r_{ppl>8l3rFdm@qP13}3eDtcQ#^M)r)hweLf){WcodqUA zVr)WW(cmF&9iX!s4v0D!Twe$y49(L`VL|o)6{Si+#Y?O73RuSXt&@3uspVn2aQd}M&Ph|m_Joy`>bk1D$E-E%%#A$>>lOuT#Q&mv8p%%_GS?~{~YdFg1Wua zYj5W`prw77cS|5K@S2i)6BI#D+6{|*$iNzAt@@b?TW-0b)`K4Bvv(GKJy6yi$L2o3bTcH>y0lZmWgFYqlD+xA)B{XCx+ za=j?m7oRXBaps(K8;1K31(At+)5pF*6!-n|ZSikULO@^6B;En3 z)sQmL82h#A_&4i5;2uI=*`IG(Ad-lmsh(AVCC7Uj@cXG^w2g|1$`CH~%aa^1Vw(34zF(q?7q zo=fj_-{}2WI{fG7pf@tgtyE%Sr}3|!ZV5K0Msok+2|FSa7f#g34;gB^?Cj&VU9FKQ~vDZ$)RG(FVLte-NNEV z3l8sd z%N1S$M_Q8a)8-sULH4t4BPdEOdU5rej}kVb$FhRLVbLu=TdC9vJ70p>L4|hD1%jTJ znn94Fm2_iI_h*Jucx~{ze7iYl2+_8wvuUwDb zct&{qJ+Za8t)e1XU+-3KdU=UroDP?`xsASTCMCJ1Y5Vc0jTLuHdfiE;(U6S&@9T#7 zq(Tv;k|aD%y61^>(qXWf)IvjMwAlLzuY-Jt;ze_AZYo~e}> zXrrp?A@2d}|Ni4*Q@^XRw&P<)4Jp0tDTQxjD3s00g#$&I5f684&`q6;)7x3tdM83! zvp$HC@Ls$A;3bM7@WGD2s9br)PSo+XB*&QiP-|t|^l!~UFNf*Qxu_3pOk8?JO>E>J zNphu%AYo*oRPZfFMq{*q5WDN|M3kh z?(x#$21QzVu{twGf~$By`Id$-n~!mwKY_^A`*vhF8@)@x)+AJOm~sJy!qsb7nwU` zOOni-aIr%4q%ds^v9>AlxT{ykk^HAIBl~1C@b=q@sK6ufl*a<4xfGL zc4{;!JsF|8X}iJFnqrNu+5Ty8^&fYKz4JdZe~P^}B+n(dzpIu7j=~~{!O&e1yHZz~ z>bpY8)9PnZv_G`v_x0hzIoL;ZG`ZiK@S+_WzJu){Fnh1(m+xX~kG4%BS`;-!o%K&~ zw5Um|NuoV%;t3q#q;a+50@x%Z`JKb5trGqWjVrx5B zaJ*uh)j-Q#P2J4Q9-&>2>u2iY%U!Z zASY$H%qKP1UO{YfnACjVH9sbXZbdtega#ngk+9lGs|rWyKgA=m*7Mr-o+6hx^xOXU66y#)n2WInV{6(7KPu-F0kd zBMT4H)P8x9D7b?tV-8f(haX#e`DW@>6#6y#(E)x$BgUCTuA)6xMp?hs9x+o=QrKvm z;prl##SOc1YP26lX>2>E#RF*VjuOD6viQQ!Y&hvrL;NTf?(}kY8J_HazKJMH?(%#4 zB(b8!@k($#ai=@e4fi-7?3t|)O$5Xv$!B|129JpEs{1lyZ)s19)w(SA_GLVgh2o6V z2xfT>UV%r8UA)TolcDX^jyU1(Q()D!bAGE;h!qD8|VVhgf+IoIVDtCG5LHNh2b32BO{J0^D+}CL2| z7$VaMJUlhH!8D$H4W128ktyi!Gxe;d4JtgH`6;$O22=bsre-_Nze$8GKA7l5L*R>5 z!_F6`OLDQBq3!-i;MEA1{YSq@s`~yb55{xA0pxV16qXyE@|r{gbuJlrUe>RBcDgYL zJRJPKh-6q<`z)XmB6oFiRfLw-_+Xf?vH8l^-Y}~YSC5l`FTyikeC-=Yq)FdC_&Mx( z>4S)l$5Ha=MB?ydYXC93`pmzd8t+D*B0F5yQy}T}Y(~Ir6vqI5-33ll7k7_0@$`3a z6icFw4bs*p+d;EDl@;GHB0)2W)?FQt8)2~(6B+ogy1|8adHZM_>-+cWVyo1;&+o;W zXw*!)F`&OdNu88^IMu?K&R!^x!~{cSY1oDT5s0q@&i-Ej858F0`{P!dFE(W!jO|&{ zcxsnM#%HgxSw#D;Y_(B7nIyB8?MlrMq0&uU6=mC76#$E)Cqy=rySTJY?qZ~}lgW)m zpcQ$9dWF+%1a`xy?;Ilk6J42JN=Kd!PN^ewYsB;Jwl94aMwr+y+Eq5Fme<<~w<&CT zLd(oTGw%&J5tS}A8}*V&i}|sYnkcY$1;r=H!~Zder4)Zi&;yYoW^t9peZ3*5mQ&Q{pY^c}ucLXJn}XT!TPz(vW0(`P;Fy-#ctEXU@}hWHMVg=u9$Nt8VCDnoGFY> z0svk$BtZ=Xpoc8sO;|z(5;~ykD>-_y(Zmn{lYkXBjX1+Sjz8p0IqyUJ`H;&t>!g{3 z-57S&&J|q3bbg>OTFqNXQcFajOu1Xq0$~d+AeI@$v&ZNb_7LJn%|$2L#9rr{x?0xS z052#v+MMO@r>Sa<1U#s@%ILcu+V4w0C%7}NG1*Ar;nnfTwpV>wS|aQCi-kF5=Qb1c z7;S*U3~3u{T^K1Zf$xIJNQ~*v$N+H1Hs+6Qfo)4YrhTW_O{D!$Ym>vh~nzy!BzGaR`g z9f?a*oz80{7bv537Z)*_{@jyjh}oO2aUtSl3D>|c02tc;N#nX1rfBP`zSjXhHVjdZU@0%@S)F#IhQm#! zp8$H*juS_EMj`=Vo*m%*oK06&*5mi4H~pS%#G&QKuaj00i**x_0GHsgm2F%^PQgm*R}q___un;WNk zVFrmj$*rMK(4@2Pfh?9R#LzpUa5unN^iMxXFb1jcd-N-MCNO7~Ab9K8vsb5sS9%CZ z_jqUx$9p1#Su^ym+%lK#NJrCu)v(lzr}fOTK>?#$F*pRb*lnYtRP25*AA8Oy;><3qoI!u$U%%kxWN{#?2}KzBaUBOxTN;s)Eb2_1zpmk`NxN zn$}}BAprTqi!Uwf)Tf(mHvS*nXWL}XCT}BO#@(gnQ}NM9Mg3jINjR1*17+X7=|%?= z5pxv{nkQ^RA1aiQ$0UAOQRbKef%HagLbUrcq5)=q#!7H8{+s=l@R6;&Z-EHD|B>ta zBuzr@WjMt?G|?II(6`-<`&n|E%EAt$Lyv?FZz0lHU-<6JhN6JGw@3cotR~zBjt`G} z!&Gi5<(L&N47pf`Ve3AJI}?(vQ{eyr-Zr#=jGN1Th$*9-13xa1>Ohkk!qwBE$|+%_ z4*;9r!=EmQ*^KwSca!Ay({_4N1NKIm&!Wnj>1x80eYb6z(Rs7&%Muf97Fv*Yyex#? zDdOz|bol78FYe}RLXk>?{Kc1n#!(RW2LZ5WwkdOquUaw(;J}jM<}Dye8vVU z%L<2;8#LOhP}aT#UN#)ye2PnWphCv*_^4b&92&yIbOtc>lWG%E&I$lI;{#Zt3a5se zFF5N#IGcU2rntG=qbE-4s(NikI=GL=36 zi8lvfxUoK4Uo3c;OpnIp^A&@s^f-Jn$&6E#*l|^La7Rl^0>Sd)4=z!hYAYj0>mV~V z#k^ZT&lCj>UmP27qp#{|z2D446qvkNKi?Fg=bD*qsFc;WQj$^;wlj zAAq`keF%;@G?rb{V3>RR(`IAL7;MfTsH4&oG97y=9qX z92y;c+2UA$v#CAN1%1Ep_qJ8mC&(?UxxxK}HKP{;T7BLJQF5Q;8h5cmKVPcBaJLFS z02L;KSMq!s7DU9*#s(qM_XX7j=Eg((a#sL;H&mpF$R-V~!hAa2{t6&GR>e4KvZN0{ zoPT8rd9S799-d^_eu%IA^1lZwx6)6W{4#gF|H;oJ)9gzkR00-udT`Y+LD>Md1Pef} z=;yJ3u4r9l-}>~i1DHb(NWh7az}NZ=n^lVmD`iT38clW?NvlF@`#yLPKFs8x3T-{D zj4chuKkCMLyxl`+IZ;5ndMnF`#AqD^}u{jI7{=X_8Gw}r8=LTGu>NqM@JT;HAFKG zi)x#gb=z!aCW5azDY|Yq^~UiU4uBDotl+ddEk?4O3i|*C{Uu06k0eA>uacNxtmjOW zFB`@n%e9a)3%c?iMjoQG2?iPF;7fPY}OdKwG8+az$j1# z%7p(Z#cO&Z4H1e4|6waCi)B$RUT2jI5&%9n%tDhwKNTa+-8$ky|GN*RHI}V2jii*~7?Wt?ahV_!l;H1^@T`v>zc5oo+sqEf$#21Qe_fH54j0CV ztB!*@6?b}T*-CA?f#I`zuY11}HqDG;DdD-U%m?)LKy*q-t~t{ntjOyWdxI zcYDyf{PzyPY`V^EE0Adu-UXp-Yk>*=HOw;~lTAQ2AY;Bgj03&;KB|L!fpcDSGa=jvW=mD5w6HI4P3a1n)-Swz7Qgt#ZS1p6kzcgaacHBt9g zo)!i&;{KK|&L*p2*s1oy$JOe9>`8-eczd>8F|5MW2eA11a1K|K79wO6^Ec%cJYPaZ zm{~V^*4}W;hrn%CG{Vy7L+$c0_t%#8uEVz$JW0o|utkI(%=*ZN5!MCkwneNjd-O-?350tW!THOvq?x;c~!RcNxLTMFY2U^+=nOj^anECTpveH+`< z`uXGh(~&2STb89BoPA;LQ9n8zXGcs_Gf?4nZKJr1xLG?Um2)7XGJ$ysB4a6yIiCw3 z$8Oi<{}jC$TMpQ-#`e4)09L?|3CBm+4CuL0I@f-i#?MEZWXASd2};4=sl1`FAw~>A zVC|&v+}5nm$s|bv?aVJLFHu@hN(r4Tlpx5C#g)99!+}2O7_kSzU{;)0si8m-Ccfy; zy!cyl-l{_S`T#yQ446;khxt%S2QH7)bEc`ov`dpRc}vr{rf7~F=28*<7h&ahdlBT=f~*~G6Hb>YjM_4bdn1o9yS zcT-Sd4&pUIQ@i{HH7VBZcR#8NBuT}sPNPPvu%Z?j%dnulx)dxzA`1eXK~7>-ON+Zo z2eCzPFHRRM1$IN^?CkfGt=C zn2-QZXJ=CY1ZB1Z000000Hr_x01f~E01%9p)&|7N&Bx8cJ~rf`N#x7^Ab}j{hHt@e z9h)IM4J5G=X0id0X3O2THfSRn2R=Pdi31xJj4iIe-*wZdiDeZIXB5}7npM~Y>XzZr zHpG5Efd~^O%@c`zhQ(JnfqIndK6M&^nWp(m?QM1e>VtdmBh+oR07o)s!5ok-a~rM? z7$CbbL1XEV%wu7(bRf&PG;tUD@0*YaD3~0;pA+AUgQZyON%^_pu+GOoHopjY`kh~n zoQpd4avdLRq^=SnnPmq^y@(prV@&`6J~kv+iswoBq+}Jcr;=!^&%@NzZl(T zC+VnMNZ~bM$`MC$_nOK}*1=yn+gh{0% z?%_gPDLQZVZA>7$oU+(FW>}>K%X9~+d^4%znV(hO05~r+4Hgq3F7T3MCKif7bF82y7=e-|YEG*dB+rR7p}1X0!?nDt2KfVr@&;N!p&)09JNnZBZ9C=un4+SBih%ITt~|LRQSWU zj9L2f<95|Rp%3d&q7+9tjjVnc{tkFC<+%MQj?`hnOjnN=0@&abKkRe_mNDEMFrT$v zTHFA>Wt4G!j7VlPCk=E8%vk_dAJj07(<-f$(1Zi)xSvub!^ys_v?0w93@9E*bg3Qmu%I3PlhvyHvJnZs)p=wr!03h)*5 zx6lBRe{K1{j@Op2l$DvPa@6-P|F2^Z@*g8QFkRiu)|64+-h|lFOkedcdtymqR%RAf zW>#i4VrqFSYcpGWBL^dEN19jOAn1QhX@o^oK>!@MhDC2un#?tp+88N#dCICPI-qi5n*}IbFV>~7}RN?j+JZ5s- z`NXgx3hO-ZUDuQb)_FB~7S>}l6mUoy&;VF3f)XVD7>ci%eh^6j4mb>zYdq`a~A^e7+W=cwiWmeUotQAJv-C>MpFXcZO{NHg9 zgZm;3gKLN?4EM@Dh$b)59?a_Bu!sR~@Gt@KC=AI;jNwZB(MfXo3wi}~j!8CAC1qI^ z@ZqGb=4>?Y>@@G}p^@mX)!?Dg;IH+|U;Ebo3r@g)(jSkt+t>7W=)@o(gN!GBizbWo zUDhT~7NlQ52?SvHsuE)OXi}Q!Y{@)h^D?vKI@97htKlm8;VQ(xmw;^w_1YUC!z?N9 z|C5Y0;*I|ANzinF77zsMvfmEB-;P{Vk-XoY9`>(>2LP~51r_Og?b*d0*!vy$z)9Tk z<6x}J=#>o0e;MJ`>;NFhg5PV0KM2-_BEz&jhnfTbyo2mKSQW1t`9B{%fAIny2uZ4O zyty|VTjZC&YViiM#mR&dLHs8Pd_hp=HgVEK(&Z=*a?(ylAx83aXRvZIRbydd@?Q&D zj^S>BZAmlqvp3`CM3TdpBw|wTzmh9jGHhw#6u1s|F9o>|_Y3$sv#BK5!Uc73TaAgN z-GW98a49%8nX0x?`5(Xkz@ktmWvr8D1Kc8bG1;OKUK?DxGfX)hg@3L4-{B(&w%eCq zIDM(#i9Nz$^f5^CNMZ;bBamY6yP+;%Amx?ucEA? z=4_>r;Ivflul>tuZQf&T{)aAh!2b@c|K&LV*lGO!`edwr7)yVOubeRaKLh`l=h$KN zN8t}fkxN&R%TCdcUa-qwvQMImDzM8cVQNoeI!$94DRF8~a~Msl7|l8xEz}ul)q7~v z{l{Vcxy{0rSZ0ee#w zz3k<`G4fA>73wI8{XY!=03DHt(tqa>1x1EgMUGiT1{G!g|Fg${sk7{|)9hfy#sL5> z0FZ%=?1vg+A1Q3juMWquOAejM1C!Z<KG|R#orLk(~Wq!u1JmfQO5TN z1y?vRQdiz*$sKMu@U#a7C;)&r%K%Fcj{TVAV8+KW7WmZI0UoN{m?=pbhIkw{8j3tL zgRPY8DapZ{SRCn))EqQ};8JiS02cgvlQ|KI2@=Er0DVM71RDQX#W9-QNW~$R9ehP8 zp8OQ0F&;PqzabWwR9{q@TzsW5ntpu05uu$F-w7dV03hxFfq%l(?1rKMDiyG9OFkaM zE{#DUjV?NlVKk2}J0HhB%FdyntTK$QGOMhzge9v|MGX zBMoI04QC^bTNSlA4{0z}Np=BKMWs$<;o3?i-ebd2MO)2i;Z{Wh%TsIKf8)ku1Lu|V z{Hzn0`en}OOTE(?wONvxc~(JD5_M5gVNsn`QCX>0)_ze|(NL91QCZ1QRe5olCvPeAraj)MS0sWH(Z0g`Zt?*hV|tRWVd`cGSdF<3ysM;EGN0|kN#6eD~7eDrs zlsQT43~e8=a=7F`N(`EmDNXE@B5Y>vCn<}}*f~Xu%Dfr<6XF~!DKm!HDNXajz8TH1 zw+5)JdpU+Gd5sx&TIaU>}z@uOQ#~pv&7DhI>7leSM0S|lxdvPBzc;11* z&BgJZz{$ZdKqbw^0ox@P!vL&X90z1!T3mxMA!-`GPg1lr{-{!t6uIC47Xcp}&sk2y zs^fz@^lu)~L)1rP2_SY4juoUM<{?uSU?ab8!VT{1XX{8XCI#(JTv0^A=CDy_#z9#VS>8>zE_R# zfHPEKFaaFzW%`1%+}NpCf}+#`F}RK<*YTAQtB3#qw6@@gs6xTe;d@jDc+P|XTwmu* zDIVC&STUMD;@D_KusHCL3hfcWy6aVaCB$kT6N8~)$>EF&hE<=Yh*W8>xZJN z#>vBZ4L!g&+1L0Z3`_u@_JIjJd-$(eibME+2PXerg8%;+L?JlNdaDY)TleAM!TpQM z#nAqq8d&}(NKgEC`afd!|Bl}OPbDpjQZVHHX9ftiBftVLuz0HC+%)gr(7%om6&W1p zbti&*3`5`-K(n9Y2R2Wph!hVs1sEkXG+_9EJvvrX>ZgOEA}vL3{Oi;)qbbDz<_$}c zJEv$4mck2{TQl=2>X(oNP41j#C6ftNOeuo>6UU$g>_D0p z;I3OR(A3W9ht-1h51t|B^g}u3G~WR0*0zARM1@^ITuyb@_wTycPr`qp1(yk~qagr;jQVeh z0$hUs7r^}mqaPYLl#2y({#O@^2A1(Ryo*(QMI=}?uY|u){ab=3c~#fni%I^qeT@ad z5QTi5g~|5Gvxi76**Cf%Kba0l0p5rJVE4=Xh-PkpxHrWwUfM6x9#x<5tzrm|c%_j9 zZZFPkEK4qilA^_aihh`ae5!#IOLs}EsuXq9_bL05TIUh)6D2Vmz>G@5<|7Egz!?Aw z@`IuQGAcSISUy8RZKJUwAR_epIZT;B0;qlih*3f6h-m0VK3V#_fcj^>0a$OuA3thB z-|Vw?Kx;2M5&IECkp%UEFe1Hu(XCxJk>ndX;dw=Kk^emag8)JRh(;nNB~9W9M~*;= zM2&hIjTVC*3x+QMtb@E!0B>^Wpr9b*@a*m_Nb-+{emOncP!xa3uNfTNzeg3QzvX`q z$bxeJ$gdB`uVP-RKMrD2&5w`F_RWn=EKZD!j!%sa3=fX?^WC)|sb@$`{CN*r|BcG; zs1auPk#**zqw|jBo$?zxS+b0{5zI2kCJtr&HJFfo8}s8Fa{#4$IDfAV-N`0(SOvjU zsj$d2HGT+Ty}hvh1)`lQreNFITqS(F>jjpP9F5%2KvKQ-Hv5D~1lsWdB*Wy?N}=p* z%q4rn0QW|2dk|HPy~U#lLHruAV)E)9){wSo|L$I5*f0G1BN2V3t$EAO0vs#8@l{-5 z4^Hy54|aS4q#dPri<0^jNlgZ>bd&z(_a8Dz^c`XGP)ZkDqqQ|WU^7?WdL4x*bZL6w zgQ|uw3ZkJn9m`{9@z6W^1K)Bo=Rs`Bu~21F62St`oPNT7C%tD_siCW(mf<}B41_@( zzS3Hh4zl2nujR;W4s}cpoNR$FTtmBZ&X(JlsNY;Gl^U(o9t`Q;haj|y`-OM}_EV$DkgN7+; zaf@ILDCWD%mzNLIe#>`xbmgrrJQt5H&&{xh*U4XgBya}Z^x6ErE4A93N~eO!Ha(v{>A=;xry~A_%kS8W>0;DvFAvu76)gh@h3WC@h{Q zcGl7VWiP(OZtm1+e-2>-TbxmyNQT+g*^L=LSjqxh&PrMJ#I_eW|o;mR|NCgOo-Y^MmPr#axjL`go%-fK< zC@imudIkzke*Sa`z+=H;f_@aflo^8jW|GJ;*En67x^HisE33unpIzNRF)-4AJ&@O^ zz&Q1;r??7g)Piftt-U&+9+4cg$ML)VOrJ^zbs%PQXZTIvl<2o)vpt&A8t8V_*_Myi zu?tW0lW%`zc8vsr>X^oVU<7&hw7OFL(HwO%^lV|UAsQVM3zXwqLkVf8w1q#RozYAu zq7|ebIT!VLFi%h7{#C5mF zp~v+c|D56_$-NGN6;>zy%{yl+Lz?&V=$rd{mOP=rmylh!lk*4IDpkCYC^-a=Re56_ zOMuaPE<6U4m(G5?fv8Ec8E_-(T!>kiS>?os;BLFy=wcd>T&o-R*2tzPm@HSHboKFs z02)Fu-hU4gqja@6*+|6ua?{oNcsHj+d#4s0%}w@THJ+Y}se&W0XuczVoO{Dnzd_94 z`L3LtKsRHP0F~1^bxf5Y!(H(DD%R^`C1*qG1=H#Qb^KWm5qv9la1l2$H<8Czl&*1d zi43P|;mmn*D3|zBF^lA(382) z`pxm07v@rHMT&9hR-b?kixio^Oeoh#lh%WZ)*uA|f`Bg58vTBASl)3n_-X@!A;Ev+ zxNMOIDggOyQA%GPR+Y-?5t@HxmZ66qD3Y)Sfgp_&pIC2mb5qo1BhsO~+gk_trquYu zbJOfYC}6$vS>t5|FCgw@uFzrcsQD|d3t6LCHGd(t_Khe0BqXcvI*)e>w5ehdX!kAC zq9afCx%cwWSJKd9lxdD1NYW19*+Q!?`yD->{Z$)!O-wF~qNNOn22+B zYS|Iv?x3$5=&^mW-iA14&2%jmarOOoRzx!#Z;!x#`dJXBHA=Fl6Z&l(4A3RnMy|z z%J+-J8Z~&wXny7U?z+F?m;!sv&_6fk z`lX|rOae5sS+^vedoSn!^4$2cJ3*;!W}YzV*XU>c!z`lieVgEpaFvJGHka>i<779h+ET4zFz7)aii&Q1Z)xZh zvBwwtg=6@h>tI{l72dTZy2d}C!c4|>hBoX|f9FB}^Vrx{+91%zfgfP%c)3AZy1s#Y zbi|iqr@3)ai`jMs;=X_Wo{4>u-@Ci};C#{5J9_L9y&Q#_6kuhyIW{pgTq(Uh(L{ki zP)1kWNw+ic-ZV@Lw~;b!ddP46R0xNdL(zV@$(qX?Dr1HAj7?GY9l5O;!`W4;qwSw! zT7o_r9H{qR0o6HG3G7NJ)Gqi?eLlaAKm8^yE@m#$c94^?ec)HiR4cxX*l$M|kXM;k zK~$ob(tO{#w=We8g4Vt9)J7$1xnt>N`MpJkn)==Zq5|mh4Dze zFYP<#fTbGuY)plF0AIl6sEGkvt+`R$8ipzz<(tu0w){2A&}nJ5qZ?PgrOp1#=rN3P zf6MQv%Y=5L^C`bDk^acVbrm)jRI26eez-@Z7EWCp{YkUvwni@#e{r7F>8{hLxIt+H z`LX!e#AqN>xfRKt`Mo*eTeKN$A4=`17R=Pty7wsvls-KkzJI8=E+F5071{}PBHf2j z?Pg}zdTcRkNLaLDGQgpfTB|+a1@G{Ur*b?q42fi?iryUK`W=O~NGW_i#cy8@KgS@33(b3YSps5FG2L%(>O z5p9zu-6~Yid_d~>16`I8_mdFbEVTaU^sX5pr1Rx7B%B*PgaDUvW%}aH;hyg&J15r| zMR)|glIUzAB=lSq!PB_VEgyq&{_~4Bmx|6!!}c~7zxdW7QhSY2t3{CZjMCrBT8xSt zcn(rh9I)7#|7MC?c%eos4rcA~fPmK!`YFp!2RV0%FfDGqFei3M7NTbAfujm5N)c4B zjib{GNkSv`A-v~K7>(2G&&|O+hLJ+>6X8@ev6KP}Q8YTQ~Kin#(vY_k|=Z?@oBs9@Dc|N>B1McuG z`wmB)l&cff=F#95v+w(VhZdao4W+PM;|R_jpb1M*H^IsHB-;z4Mab6c z*s#nUpZnA>M_$`vu^b^KImHhOnotYVUI`~{CIg`elbB}#+mlZmD*0!f@y{FNz`0f16If&JZ*<8l1-ZUXLg%%GANrmKq@1V{zo@Vz! zm#`yc^-g7_)97hO_czMND+uso8^*V=5ybSx{o~lGiHCQL3`Y=@bl$po(FNLApq$n8 zr;K{5SPcLVsFL<7*52`Hs#dmdn!SaHsTNI1;WEqgP0remZYA#LomJ~?n z1-!~}n6RSI_gK+qWQ%?V$mk-&>6aA#7W$lYwF5R-Ju!wKUF3$b4i!#i$gC`O3tP)oe8#h1-9L=P7<(tT# zd^_xPe(!daNmP7wx1sjlBAjy2ke~(z(ToQB0N`BOs+RH_VMtQ-ID_z-(&=oSM5c!c zgkXwVfPL^n3Z-Q~%r*1LxNbh&&N*9Mu|3(cYoLnXe*eZk0s07$A8JK{#OEpgXGca_ z##$PGuHV(Vp2@>|1ldOh*r?3<-ACvaTCTm${zICOR7O*HYdg;7+k3|Do30rPSU^-? zIDwoR8DiCK741Ju4>A9}^Z*GiuAh{DV;Z5RWMX5cpr)o`Vq>9VW1HOj zrWK1E0wixyA@{%*rc-k^ETbVFBXj%siRq^rS|Te1BWZH_vAP5Zn%MgNgOlGQbww)$ zI!3>8GErhnijsH1m3sre9L|x3ySD#ZsxY4ZUvgDu9!Gv63-gvx{)Z7?yd`86ucp+9 zMoNQNsTA(yY>BfXd_@4D=eojGTu{v$idsnL4ADeZuGcUiC>ZUzk6UT}hSw%AwBGQ3 zdDd|Z<%x@?kz!D~PecUPX)IR4^=Y~#QGFX_<=;6$Q1{AlG=AMKFLoftOdF|Mr-LAW zY&qK*9ho(~oijhS^&#eOpyO*xXyIVN&&YRJ-dpQ?qrECptu$l$rc0*Ld+gZ1uZVXz zLy=A+AZo6!*a{4x2FLh0^sXi*6p3+(HgBD=(dDcUkc)4yMj`eMde$SpK+7Pg4VR71 z2%z=pk^xK`e`fa)n5EHBcpMx(W;djYM|-`{{MoaQWze=H@5iFz@J_vCeCXl)dt(iI zWF}r*hpYlyrZ<~HOuKE+m3Eie zm{P5cMr-Zb3f^N8jgu(suyHjWXBXYVP7PaL)!4=66RjV-zQ$)F+BT%oxbt zHtd0wJ-D>=15yTI@=c{54Wnbl>fu}9AY_V<(M*}uMide&ThMkF`pX~PE0gZ=YI(4 zqB^c4-1!L>>0)xPF^Njzy^#9^!~2AcT){uM8qNLd49^VYrT)o^6h1+F_g$hNtL*Z% zVqIHH*4Tx9E`3zntLNB{LE(-N#xFbTwcQwL$w?rvu-F&~s)+5NS;Ae^B)zu%!km6$3>@E9Sj8CXPO%lFO@%+ie zLqOCC`e^4Y-i50Ct1U@@jG+G!U(zWI8aq9aanYyv23NPF?MIo^@`~Xqa;BjWS8Q1Q ziabt$bSBHDn?O1@^W1d0MoQ5st%t>}o0IP3*VsgpByF**xw@&U*8n4&A63 zz{N#$2gsSDrv&D0#(rn_aFg2yFoaj32tFyDP%s9>GI;>(_;*7(I$sTz4EMsy39MYK zFD3_^yBkK2vwkiYAX&s=tIdqq`yah|)>MxLNzQ<_3$F3ow^%gFD!6SK`c@skx=&VJ zml!Cg+i6#wQ@sI&jN!Y*Z3oN}f-vAu9u>?(d%0SI_6vIMCouannzI(aW+sF?a;u)O z^bX|XtcD%s)-aA>#P&7w9OqqqGva$I%>7_r$8rXeU`WX>BnxmBv&p)?NOWigpgzN! zL6VQlt;%XZ!wJ)NV0%=nNaffM*{+BAFG`3uiZgyB(EM&r5q6yj!3^i}M+xF>PG*}O zxuYD_U2Sk4TeW)jX+}nUei!m`>mnBn;CNS|XiVCzSoEzrX&nSK>x~Xer3h=fD+PV~ zM9vb(x{~G>e}{D(LI;w$W2d0;4lp~l2KZPXk1<#&DDH8wVyW!&SfOQGFq)L-9CpV? z@ga5$d%S6*3(L`_x_sU^T&&w>H`DQ=>o*(L$UNrDiLvHB$3VCm*HxMDVSd0rzB4`d8#lrJf7}zaY4Qi$3 zn{;BGEa*VEz`3Vf8V^4_gTXbsn&m0e<;NaaI47^&gPvU~N!1iOYhF}GsV|rAx8!bZ z73~Y<7Czy$e~_@3EU+q{o!&VN)C|O7ke9YT$dx*D$RRa_pM?zHf?pscQ6rtaJ0c>d7tpUCq=9FT%UAbuP*1Ps|#Z}UTS0q&3c#lIC^17+*wa|$4AZq={Y|@47pDef6#`6dEqF5UKpPs=jSr_h>VN<_b_@9kDE`EWxnnN zF|M_tQwh)YZNyzhC3zdI^j}4_=V!>k3k~5YWfLLF7rh9bE}{rwnHr3)3F)U$8pv!%-sD17^t@*~(DmSt z)VbdX{cT{KRQJsoJ}_c|O3ftN@?&;esl^>=OcO_$UBg)Bl$7F+B&SD{&R2|fv1w0wHHXT>ny}!+;SEXgvbw7DXJ4bRQJT4fegh8 z+2zF&m?0oW#CHl8Y?k-Z(r9tN!6#Gnk@IZS)bJ*wG)2@8GZG`Fr>X8p#M}SaMxUKP-jas1D>_9I9R``{j-m!Ri zgX9W!Zm9dZ5{!=cGMsMK6KY1cn)<#k5Afj-kk^&3`aDKWMe+nXhdnK-aQ$u_;rb`W z%-X{#j6Y>9=i$cF#Y>41;`fKa3u*E=I>BK#7XlCevuIs+EW6Y(1qAQZZcg4-FB#d2 ze2M{p_8s@ukx287kZ1nN4J>RF+|64U^cD%Q!G#GAe(_Wnxm;k}>UB8jfjL(N6 zm+ZJlPvcys{zpAY2l zL%rjY^RT7+uweXOL7Az2FwGI5WD$#K!rdMttdqQt^DJ2vY6o{nsU36noEh`lZ;#+IaU|zq({4&Xq zHRk5B>;a>#%&X2k>*AUKm2l92PloyKQ)V&jSJAqc`EbYBsB(5jv; zl^Co)Ne?1Kwp{NePE4 z^DS7VE{a7Ok@A`;7t^(8md1&aIG}Y2UrIwxQ6$6C&pB{1=ycxlvL?pIEn{rv{er`T zH0Fhf`z8C+nUCVjFP{|?M+^lBphU-xLYcK>7yF|@2-<{X!%uco^kVw#M34S{*%dDz z`oMQyjeAQ|B0!G02RaL!#h+?u?I#%|!x7Ndunc9);MI1g=o{u0=p593JKj{yBLVq` zXa!hoMPUNm?QcTeI8@V|SS`xwbl$Y;X`BtSr20>su#BcB(mb1rxv4Z?MSY#y(rMD) zqR0IJ9e=OI#93kT6x#nlDujvA4kfddlsxiq7!=`nJ;AykA0s6C^=AX_G)r+|CG;60 zXvDeIElXKU$I4P&^44{j+rx)d_m7No)^~>uEB+?xopnoSy&dbq!{JYNcG3V;XeWeU zyp4{Ksjh6n_i9~VlmV+??W4i1ieiZaU0nmp`){OQBC!yw%czm*NHIRCyn*-Kd)zf=dP4s=4#quUSGXC#qjC_M%O576!k zhB33APttlmI_f!b$(>i`B6y;_i_T0_ghjz=%*BW%*fQBM?-<2y@Yyv^p&(IqEldsH zy1zIH0?)2cCD$wBIL=QpKdTce)^aoNdM{Mml1eVkw>u+(*HKa~5> z;FvMnef@@>-ptUv+Lrwb>x}0JTB=_o#28Yxs1W%A>n|VShnDa$_xjFFbSUp{->bZj z!b_iIViKsC(Aqvb>Ot&It@P>8S#J2TL1JSekiI}tt1Jzj)o)uH2*f!L)n*#EppCf! zI@~p1q~ULL0DI>DQCes$GSgN!s^KT zBp;eRyWwHR+J29fr}Dku;GL3nx$2>b3emxQ5_Qgl;ZlIuaXaW}Fo1Zi zCsFt)JDugjhqelkGCg_q(e_!k_g=QzxCVG}N|-5tYxJv~h`Fb|5N?;0 zG`tu7drjl@GYeg30zRtod1h87*3I>UvC(l#S~~C_3llRd%fjlx$lxLsEiDyzg@c)e z`I!q*LGrpg<0iG13)WM;AuqTFMGxP0qPQYRb#RKvn_aQ2w!}mYx{I~LaW>ORRYW&W z-TH=5D=RQfF!5seW>pHKK{Bbn*XeG(fPK>VfqtiURQFc@fJBncsDqBkYmDV)}EGIz1yb;1npLERSkqyh)Q`eIC>Ds0Ryh za@SNaZH5gTqU}IDhKRX1Y0syhhTSKe%{9Sfl-3}<8UN-d=>|vP$neMo@6z0RcS$fiAP#w5)Y@I=b}E9IS9au=DF3GXLa2V zF~n9}2X&MtXaCqEcc`7bC9sDUQxj$VR$`Y3-|Tqwo?cve2QTWk7N-V-5Y&l9pip_t zxiUjmSWqvU&1gXi)CuGXf)P$#jaaH{45l&cyOB*8jzt#;;By_m3gj+L?wI4^86;V9 zdjb4S5D17>og~NfeDiZjiw6NP>YPbh?2bA{3~YAjWi^4*Iy`CNm`=J}hDp@2d9!pE z;SHqd*FtA$?T9xlloxbC6s_A1jp)QICi)ikp%Togjp(k~(K={uaE1m1mp2{r_do$V-& z@$b69w?imm$WtJDiJNVV1-3YSHz2k$_)O%50>9@GpYdulqi^K1ZLL<2?7Ibmyzr;g zmmgJAC}9r2VS#hX4LrRmD`e9y!h>Y+yF}*kGl|O@+Yl>u3{GZ(@pU$<2;-papRf};(cs6#*bp?!w4_0GJ z`{qc?)dz=xjtUw^=ZCGYZSH~M+nP7znQwl>N^Xjd1!<^iJ)od91@U0>#JOua3gi>u zh&Wm@wOUdCxG=Ib<6-9z(qT$>?Buwr6L4)}v=Nztbq9aOhf_+B#}b?`B>_eIJZ)W- z-+3T6y}C=_%?AKFcLY@riO-FcxQ};`KuQG28xw5Yn{v zNRm=gGS7v_#&SH{Gxlyi54(A(>X}rX5<}P)`#z)TyejOsS9xKsw$hN(DnGJe15m46Vzv| zx#K2D+6W8WnE$9ZEZd50f4>Buufr!GyS?IRPv>RqP+c0P+7{ac`P3CoPUx6w-$(C4diHdofGx)F%bx2=>ebOc}TB{#RJ>R$DTQ}#k>6SG)HtJ zxrFrajjhzHwcyh~0Dk~Lw#-@CC6ho zr<}})!CAqa;w$Wh#{dc9pGMJm^{Grq8&~Qq;jadM9jcG^{i4yD{v&YX`KX+*A3TA! z-F@tcSa|-NJPQJ3H$lUFA>8T9NBe3|IXc_jU=|)e0hN9~R$<NB9nx`lZ4k3&MIdls9U8^VM1A%N#E z<;FzJ7j7RMQ3K173*RNdJt9PJr+%1E(%v~WB@+7JR}v+y94O|4o_bNMeI75jA`6Cm z5ijH>32t@5pGn6XC^_N|SK?F$y;B2O0p%jrE)<+Y6T=b@+e!bn6lX)wWM_K4==GuBlhU*tlLMs#sLd>D)kjW?) zf#J($R!1NV6h$-qJOx%O&swck0qF}NW~HporZdeAkI2|Baykzgrl7q7Nf26AFV*zc7HvTTx0bnlpKz8!{?2{1eW2K+Mj}em?qgc~8S4&TVa5_=t zV}U~CRcg9sbIPl0aro5&1p*~;NX6S&_*6}t*Ip&A(8-t^t;Z)_ zwb>@Fgrvtiy=q&o8T#w;;g1?cdEIK`no915f^~>yg43Iue4Lb(FPs$xlD`(%plg0) z*5Y;Sw?*D3;g99oshl*WBl!I+PKe6S0h)>pp_>95j~}ho{uHCa?c4p7hXg*OX~atE z^R?_;X%T;i<-8um(Z{4#pDi(6Ab~Is<~UE>(=uuSlo2Y?NKzedbM*Dy`@Od^+a`@R zhZIPhB>Uza-PCrU81&cmdtsaSRzU2}e#WX)eV-%v^>%+kyyfuTt&oY#+;Px#@U%QR zD6FU%m*O^9Et^aSN5jxgQegqV<8oA=S_=R~){!#sHaD!`Rha&Xbn{d8ac2?VDUYG+ z2Vwx~nNJQmll5V_=phq9FA@#Dh2v7?$H3}ucL?-pZat#S6%mBG8PY^@4-g`RlUO~Y zl#MbxW4jIH{y8e=iMz{ND}_FSisPh+f|ZaJqa>R?g{3Y)M%X{+(^>LD`-(irClJuP zOCn1Z6MXh_Lk)(wStCwN*TYe_$x=IcaJ>-UJ+OWZGDlG;;q6zmQ-c>17*=D5{xi#; zILg)qEp%|_-9cCj@U>5GwX(gczDV!LX(d_h^%!S^EEi9@Emm1UAjUUsVvW4m7ujov zbZ?4>akD)>liIwf65)tksNP;mtAMxMjaW+>Bd9sVgutG(?FnLSKSNbfDa|(Db^5E% zdbYS~?r$kMzW7EWrhFw6NPYPPjPtT{YcRxsQho?4D=WJX+Rygg3jUa6 zQfhyp|9~W9{}aK1DBOK=!vz4|1+7e!-EO^%LLUVGZVuZd0`%ahA(5o{99wBw-x>;n zKhG5aKWX+y5YaGevv)@(J4jCb`GPAruN1HD8{rZQHH{$ZrgQC{zIWFePZ!UC<{9j2 z+#wUK@o?NdPKIliVE4ylJB)YYs9uLUFnN4zk^vDw1ibAAcoCVBL$>Nq`tp*Y#~nyP zfy5`aBw|#&u7s+vMWH)z>3i1sk-k*Zzd8;iIJFA7l&o4ff{1RgI7k5U~LOfkXnry0zdJ7sO3oMB06^pPMLv;4kmK zXgNh`Z$omipAsXaK*<*7J=2eCuJpN1FO#CPRU0%S&nT?YdNnQnr(3ze%mHq`^Ef98 zc<}e$!@G}D^hsa2Bdex&DBvW}1s__7aJ~xBN|Bt3Ows;+q*w?zEK(7%pv0@8bkwfr z)&3b07;MFBTcbkwLC#Ho7s0Jdl>yz+7!z_asWRglKpe2GM-?M zH8c@)j#qJz&Oj9fd(c>eKJ6)#%u#Dt&{Zj6Zu6RNuDsn&968`(0v=uCN{~nR?em_n zyE2(Hqmnzf4&A}+3l=`FyTHeK@{lNziY$v_}<(3YtWK=>F8o^WTShb z@`E-rGl>=SKUrGbUX1uNm)ust^icpSN z3@)4H--Ex%n$7mv2+4a*WF+^S37K~T(D}}cE5At{;m)51Wxs;D)36mG_ZGt|qq3R4 zj8GN4yVDx|Em71|1Hyr}#n!k2sh}dA^Fjt)3g@G3dB7XVOzrgC>!^d@c|N~j&f4sK z#ek&~wIlt_x{3;%BLTpwS?%nd>->jur5?|?Nn9$AL{GUCt$tNM2~#hk{mCY-G-Iwym`Y2kA2lHyUyNW z{?@fAde<@#-7}O6l!1fs+hW#lAiFG`g4^O{BHXCg+iSXR1q)H5kcH~!o_)38b{3hc zZv&5x%E=&MiJB)34p0_RP_>qb#xBnE(>q?BZzd|1553aEf z1|Q^yggXgRO0Vauja#YrRJ$rVeDj$iqSfeGPjbc%TVjwIG^UC8rb`-M_r!dho+a|}095>L zB@n>Lg%9O$<0^QRSkf_#?(t(Rhq0X=I^ValzQ-?zPw_!g74Yyy5_|6^D^#p-pT}t8 z7V*r>9@F~lSshgyPMg?@3GYx(1N{kPDZ?hqYyWtuV9BnWu0Y$!rfvtXCEB9NcRUiy ze~d%Txi-cQ(tyuMyq&HRhE@_iQ6e6i{;ghHH$FpfDD5ktHMT7wOgn(dGOvgec#OD{ zm2D%96LM56meR*}i&RswbDO2VJBWTeUd#O*HOi+~v1EFZhyy`Y?C}`l@GW=51cv{b z2X3ss*bVTe?oB%+fOwD4OjcFWS{JJOmbe0HE0?7xjGRHmq3f38%E+FO#UnRKii!v* zc`92GaZEDyYH(=-z8UI`eD<6o{$``eg=qAwl^xuciK(vFH2z1Y6~KSyi=q9Vzgq7! zQ%{SUgCC&g(CaL@#JT|a$ty`1-=oa>g80rj-4+AjL-3bzv;BQNqVEiaW7wc=l4vbC zO&-$k8+svSCE4}tjZtIz1Pk!Nm`VHnQFnG0;D;vtMoq<$A4FBi_l6Szf5G8KRTw5w zBzI!+7LxtmFM}-Q=H}SF2`WtFjOqgh62@#|Usz9teGhB0_D$I$&Zj?a6fArQl_1~x z7E;@y_IU3tlET{5aO4043gOi@tR0lgtZu!Lk4DX~+FsuPWr13xKMP52-TJ9GU4&Xok*8sd3=k<8qwgLW60KjJ+;a1A!r3rd! zS|;}Kv7w3KvGL8}@xgu?@b?l_%xr8l;4daB){PCX2B1@S4VX#$_m%#Bq@KOp|Z)ERPZK46od?q24x%t zV+RhJq_NSQKyk|R-oq!S0-n=FX3P{7XtOFch%tN%G-`>jje0J<+x1*)dik=mSAVQ_>ToLe zW5eC(XJTxkN-zl#N|KHO8_N9n%#R;N7T~>E65s4+`-4m3C26%IeZP6!)pY){9Qd;K zAiU#IQY4B5w7mQrC(1^^^|_%o1opAI-mkQ+ALSy;3S`9OnsfF*1ZQDgEaL`~ z@L^SaO)KV3msnW5w@!c_Yx^oHbmL$UepF82A4wJHzW4+k&KE%7Xxn zZt%>M_vRL{y3%EnsEUK@`m<|RId^#Z&VI~0VA>H{;{Net1rvdH=}Kr@1hvPX^RmzD z4mX=-VwV0&5{F3oa~Z*SK<+-)e_LIPO^f{7nTR}z zUl+&iX==wKOtGv&haouAF@7t-?ymdH^q~aLDLb;3ZmkC|8yDDi_GgsmtuK`6JUgTW zD4E@j!U}bK(z_ac+RF9B>d!@0aTp3VljCuKnQ#2)6K-0U-?=}Cd28XYZr_pTQ;Fp3 z(4MCe-Uhz3e_HhXJbP%l&-v}}6cYfpt^K7q=4Mt?w!0ghSpg*V1|nGM%9~b<_ep`9AMn=#JT6(Gy(Rq( z%0nb#tKons{{FHwWJ2=fCbBBG+tVjQONNKzMSn|7b?$QB2Ud)XxA3X|PXH7w>(d+L z+JR;4+*&6g&z4W!cLe#=ya+W44C)@Poiq5}U@?H2CFi#Oh^bs;MVp25BIV$vCX>gM z8b9D_0Q|(B_sTT@#E8ue;3Ly2c)5@PSOk%hQPQa{-EW~)7rffSWJxVNK{1g=jt=12 zk_A`>P=uq?HVRty4=@dsjOAu+7>9>K$IB$sIzm_jYDIn<#U1&rxB@w-+!2WQ#3uWPr%2@Ews%6I1 zVerx8-16NU(I5wz+0Hrp##&h#dH1uyU03a|43G9Dwaqhdr$RH@k^6QAmV5@Lx(tb- zP)0IvgN=Cz#wiOTc-*jHx$^mb4 zSK_*uM++JrKX?Dm{ zOlA=P81$RjwHw30PnS!djt->W{^C)({&?D+rXE?e+)y|jv(?`37|@cMpLl^=yqPu{ znHh9CaZ0$z6Mv~1g(Wn2^#1mV+B?s7kbwwCBu!wiK!Vq;q(19lDk8q?Yc_ru#y*}2 zwCrEikGKlc8~cDY2n6#61+$cn#rvJLv5GswS0nwn2Dii?jPB`V?iTPu0!L+uC^xb#Ufejtt zvGUU1$7c5Wy>sgp3cniw7jb8_$0{tK+Zkl}NnX#!fqVaLEbN3%J`t+yHr2%5NLO z|JClkxdQ=2e&2cS0^TUiseOB%|ITC6wP3hH50D}L005{1lW_(NumY?Ap69WbMF8-o z6m=6!Wq#%M?^B*8Z8tW^GzGvzA$zCsVf*JH`#9(Vg8`NVKw_a}Dy&!E_CjK6fy&6+ zwv|p@cxbPBrBkELmU?T;(v!;b_y`SPxS~1obFLC~Fa+3n^LUXDUZLs$Aom^h!7zMu zAJk!E01nUwc+9i@+-j`(hv{Vm!zF% z31b~h(qKSVb`qT;vj_mNrnFr?le@d2d}ZaoU(7zek{+kcT{1a2903?kk}4u$^7XxG zW=E|He!t{g7b+m%%+DK9t0z?a%i<=RHRRY)`>k9~!)t&=G{LlDVYZEX0DxS{mntfe zxv!%z!#uJHv#H%}pTK%AR07~dt0h9wvF&zn)hy%%K@)h-DEr-S$ZzXa+MEDC5h0LflH zWSuM}{Q-b|E&l8Br7lnxU3$~H`X1)KV_Lg$V>A-;ZPFyUHe{Ffwiuq&81pP&;I0zo zMRq(+Fw+d4B}L=R+TVA!Qe`@+cb!}B%U~EGk&&GuDx>yicGkyZ0t&hn^MP9*CApgW zE{^#a92G5(|1Z~el!GnsLM-gHn1Jd?-3EU&fcko*cyUmOWCrNkR!DXhDVb3@X>bp1 zjUQ7`*VFiPa33E*G_YVm1EzdN7GRBR5xzI%Xs`Rzy6>b`yz?=%!7(#{b_|;&@wD}% zKLA`fB`rTs{n`0)t)9P{=1Io&$>G~aKkM{_mqrgI@y|DD2x0c zYC#hnkV%tdxrV1sN zvj+b?{=0YM>5;^RwC&Be7=XrHYUoEKz`=3Q*%P%O8sFAe4On*(;IOJ&)RYaHH_M@a z>5N%aN(0KzT?2-cQc8qPd7fjM_Ma^bS$~gSV#KO9{?e9 ztDU}BKl42otMqYYchi(zMt0|0x4uaA1=(0-;_2EA=9cNcrCR?EbcW#O^b)`EZP2|* znWbs0z$vlyP|%H?@M5CEKm-Cqag<{B_6oNn=v|oiV>)q#BvjoPMJd)gQnr;1x`ci) zRmm94k;N)b{8#eQlfI&(QIA8KJaa~at|D6CNM_t;i_D%(bR~V9?q&dX- zXi2;Bc?iRqOCXU7z+WF_X;*q**8M%O)=}>1jMW_}9pz|+8^)Oh%>Bgd3WWDsSJ|dj2|;jSIGNU@f6gO-UIou_VbPbV zUG-yUZkRhnMx^R+iXY8C9{?o&l2x}PYrQqS?%{yo{wt*oD}X>_|gG?`$ZBvX1|g@8i< zJTAo-8BceoBZCk5Qx%{B2ryu(3J_65g9F>$`ZKmMKA<_@tchaYu91?E(A zGUX|1sOdC1l+8Q^5H8_i&CC_Y5_A=3>^BQ>8%b|!8(oi!>FWXmWIFk&lhWZv2G`!7 z3Lt9XlEcwGyQa-*Ttz%yy8%Np*g+8D_CjEBWX-TCCeS!8n}z55_yeXHC$~k^f9jQo0Zaz*h*nBVCG|?pM+xZhF!R6`mG_M6V~BdXjy*2t zi-LNk|BdBZ_&osp^>8$?s>qaT{J#=#JggOai=iq#KLfI4Pn1e{$zbmbSsbn_QaA|w z1@+wu>C@Vo-TZ3?TshqCu3O6%{V8q_-L6o#9w^$*gR}#$tDwyQ9Qvr)3RIz;wwR9t z(Bt7a%sOZTOGw+^{LS&c={@NLO7Jma)^Q#HR3`bVo@Sj(t?In1Pp~?&;Q5NLsdcYd z|2of8%r(bLHFnuBfd1?jB*k*-%|uf@&;Uk}m~X1X5_eg6LYcj~&XVR!;7K!v@; zC7zr}AAqZNW)e=u5k_yE-)Se)|GakX>c)s4nw7;!B&HG}kr)y`3>FzW!8iCE*i2fV zdvkpv$S8l}^)j_j0s(|eJL;I8ta!Mks>qgQJ|Kr$XPZRnvF4ujV^uF-`avrxfbn2ErN3d0a)X?3#O~Z2K2@R z5h6TMS`cf3H{V4`y=St)fFDyo6NUK~R6Qm?cM@jQQ?S=fmVhID!_GdRMi~HoXWsE-2f#G;Cl|I|&%AT+pC4`b_#}MW$${0TjVrOw$JEhrKetzN-lI3Es?mG&jr}Be zCrD%3iOqNok2tHt*Hz^=*-IHjYl6*=2xaK!;|RfTrvJtz0c*586KaIrwWS+?k9L00 zO7g!^2y*g8r~6&1*#&)!-ks$|fVCz0Np+jAo7!pj;7n6xxQ60g9v$EyT-eA}(Ee7L zIs=}|l6*TkeN&x{%2jVBGcA9$0PS{+$xqxk1>*K?3kDwtppzWFj6usMr&-z9PFigb4bCK=Ffm$0+o z8)MRasG1@wwHWb>ojbc#sM!0;ilMg{c3)~(_*uP zykg!cqefxNA=$OrfnoU&1qQr8(MeJK1T+`Bt|k5In+QKOlEwK&g4wo>MlzEy$iiw< zM%(^U0jiS1;SoLeGplJFcS4_1X9;8u1t=;~W`@_{Ks-bZ`nM5{-AACdpwPYHbwmfy zd+R#!9Yz8GzBUvoPK{!~0$IYbm`;Cs|C2bqnpyiFXKAWdiKFjwTL7z zTulJJHWW1_2ozMvk{|So6bbM!R-d1gtUV!p0Jy9D{&@RcP>UVPUP^cWz z8>*%`R8%ABzD^W%R4MdOE2>j}+_uByQP`(Bra&T)%^~t3E!?Q;9MK@FzQb~qrA~CJK-Xyue(%1#6o^Ix zax>*-FA{+sSfPCLyI`ErFMiqutOL0HYl>ZYTU~m+(evC_Z4(X6Q2^dHG_#MO01I&4 z!YGme`g-At}ulgIGo)Qtm5PU%e63GnD(cou_6f_Lg^h%S`e6q z{iC&J4i`M5MmwF(QvA71#W&BCsR!Qab-3zAM)IQ+)^)10bcXzDXN_G`lJ z2>2Q{bj0_P${`*Q-Q^plo@OO<>qQTi9-5-b7(^R$hdeMBETeN~T#Regp*UP3wadlq zp%ke>qB0FU2OO=PeR4L++}8(s$tt){-&OR}H7Ikh+Xw)jHZQvw!nd7G$d7y#7h zSFBRjWD&pvGs|>cJw833H?-Uesi}MG?fK(yYP#+ucF|(&B)&L`!d=dp7pwqxBK8~? zc^QetAgkuKsnp0kbYLvO5Ll?E)MD7`d-9bO*0n%qN4uW6(3agk=$Y9o%D$q1T3I@| zo9{jh16o%oA~XG_=+w!SNMk!wax5J``9&=O4>h$Pd@Jqy9_$V6kq}Gl{XBhmirKx% zSx=oruxv?2Q!ln*dU=9f36#C+$8Sgh{x!rtMo={91i-^BhYX4d)lMsko?iOG_RI>D3|jotw`xzlqiKC!?i%t4T$dPdsxT-{@>Qi2~89<(={a~ zD#^?rf8!FKH3TjO0?EDvs-E%D3#@XrsDl=on0Vp*oq-iY;m;GSN%7;lKec}Jntg)x z`#^A^6@Z|XgkPA9;&2+)3=3a%GsJQ;c?I?C+;tNsdWqON4%bI{|4CS-xHfx3lCJ?i zHk8pPYyvuPc^!(9!+~*(&&mP z+r75E*C$NgmXi{s1C_t9wEt08?H#br(sTdo2PPGDj&phok!68j(8B1yVLjZ#x~gN? zR&9dsx~&z#g-jzl;9*IA9M2a;eRD+lp~dOmgW+b}XhH-Hn$8Cto*-TgfY<#*-ZjL| z2MPr}xV%Za_7WJU>FAg5DLDk-e~UF-yD}&5igC8Qw>7_F?v>P!);YKGX8Hn6FSTDl zY30tcOcIEiNrIUx@ql4)GqL+Dvv*WHQuPI+2zW!>lK zBqcx=cUM}N)xvr~Qg%0ve-7AG<*!N@q=&opawGjt5##lSHJD7^Hgvs3;uKhw$${=S zL2WbvO;uH5obo~dtSRD}cjWG;jj((@dE2`AKN7I<>Qls5_D!YNkUmEka!0-M+$i)L zoZ0!gzDFkZX=&KY@SLwr;hfxs9m!Zad}WVbZNq~roR4pC-Od#Q6C`X-gX8O9*68MC zBy^U*TC5_F6yHt$YiD>{qrW@M*Bz+QKZ;sgOw+1h%f{<>3rq-Vze5;1p+N7LD-NH` z_3TojtX09Isnj~CCs9VrLCQ??RivII#Lp@L9yd&>1f0>Mhb-Y_mAUc&-B_{87)xda z(2llo+jKj6dS+%U*X!+zi=ND8E&th9^`wjwq&MG1WiB1;^ON!lr}Z*=PZ8b&`ow!D zPe}o)GTmN>HecD#yINT+HaG8P%!n|%W9TxPL+wu=5K5O=;x`>4bynsh;%Sx&M4RXc z96I+{bz04I<$}8M4o%B%NotzASPr9_Jzy1Sj*jB?@H6_t>LK)==h`;t@kQ|*G-A)A zN=cO|2m4OAR`Sc$_yz*k@Fi|$0bYRdz9W4tY&dV_v|H7HG2c2Ky%}1uA69}cch?hB7A8N=45}hgLWt$;w=5`M&Gdp~A^e2GoK5nOa{o@5R1KmPv{<9yB8o z>Zf3!H2^1)oQ1VuR|TMIZ~B>)03J7#WsGYuh^05A=LA^?V3Q_^oR9tfKmK(p!bnvG&IXG4esn4GYJG+s27f8c zXO7uc4kov)r-&5yxgR~eSlGb5iPXCF@7FUzI3{WVmm5d419LE^PQCH_)4^0pps5w9 z`4+&+fzi4u<&_MWw{8jiGbpyYcYPo*CTXuxx`MX4e(KaPcvV;8FjO&8**JVu1`339 zM9z0BK$Z6#U3-8XaaOL#$2XogY-5cu3A6<~+zt5IXbpj`s^s#tmZd)c{M^6V=Dd1s zZ?##?+O0m2-L!FGlHGoJQzWzZ5j+YDC)7R@`-I+ml^a+QsZ2TQM~(B7htJ-eJj1bI zpyED8-1~tD7stL_AB? znhk75EQHyBkik}qCRJ@vmBoy#M$}j14ksHU$u|8IBM$3I_d^BK@TGdvQGVnqkg&QG z;lzor3+YVO*NFZH_UUIB$9Qe!;PFjnakpK&^R4N1sw+c2Qgn$9G6EF*$Z1qy0n@ZD z9W(}102pgeXJ=CY1m(E_000000NOwR00#g702<>A`h6-wqjLcoX#SEjf^KZSM#0z44?1vE~Qi)?k>lTjSF{;8KdOd zSiHhB>cnCXWL_)f#w{YrRz|Px;l&aiRYevkRsGL#E%${b+elq%MGc0^LObRy>3f@d ztQ}SLkXRgQqm9bF5}BCW_85iYik)Qv@t#4)?K+eE5kdC+8pH12hqdE~>OhO^=158L z!`v$zV&PB;Z|22lX|)C133;h@yk{$1h7#mwY4qyc_5q^2iu z26T`EJ(vb8EVDz}tJK$?)>`@iq`6!DPy8?*WQI11vyrQV#`|l0>4co zY9aqz38ZDn5&W?@i+JC6NqF7ewQguiP0Gye+)wGe)-aNVb&0G@J>Kmd@qPS?s$jFU zUa4X|lCY&CLzjS77MfLQ9@2c+e%J_?{PHIH!Z&DQU*GnN1I-&vDhMavK}OXmEf=_? zh1op#hu-MsrwCA0F;9e|KwUXSO2Ft8<|}Ag@n`ve01s-!cQu&-o;IYVMM)ZT;Ni3- zRPaFCR#HYO_Ob{dsoAf$ymDh2!*r`ptzI{>+YgPtzqVMWhk9q#__yVr)Vf>J=5vl4 z_kHZOJECyY_t?&2O)xiPStwdMJ(>Q<&i}-@q2uk}%&L~;ITYyK89{5unou$|i9mI7;Z!oRrnxr_Er^Y$5golXsSAc)Rx3P=nc?OR@DZ0Jwt28E)s5yN%w~0D zNm&99hsnd8uiYt2ZP21islD+|T^c?&luM7n*iaz{y)zCW6<{2z%Gbu}ECM)R|MR#! zG${9v^>Rwzw@Y4>=f|`zUyUi#x26m#&kKub0YDnqTAWOb1;2SU*2WcMJ!L;T1JNl+ z8ml~I4DLGJG({VQ^aDNDn0e6|>%Y{iNHiTk=O=BQvUJE90P*~+>P|2TESZ%|O7-U? zDd{I!ZHK3wAMW0Eu(z{3c_)t{-}d}bNXcgQ08=6nrp>e0kW-s0wcKu zEqJ$>)>+_&l;@Ve?tL!;9ybJf3>4*oP5~b7y+tbkrqfAUi6Dh!=?_5CGLw?;ll`;% zSEenWUbSRiygK;jI(aka>1KCB%lm1JxScvS(mHvw*1B?2Q)|*Ji$^~a{dD-$|M_|H zVDCw-_<|t-g4PM+Y`_2CKF`^a7$iiqe>|%0^IS4P!-9@?sc~eglkk!NKCo`yKY-m| zd;bw2A&h9@yY664CSk%nAJs-{?t`{++@&L+a=uhKaA5!^`lqg{{SZpeFlfWQT>`l> zB|VH~19VRHYKiHDG@W{5o}6_PO#%Kkv|x<5xReK@!{_1dl7+LvfoZs^JlPte^Z|(9 zsjnY+8uK1HJN&AlcmDrj_pW{Xe%j8;Or8?sA}P7o#wO8pkevW7;s}F8j_TNjE)K%x z*BJtq*|9j<06!q0ev{cMtsh82#zb)X_ynUvqSklo&q7aV3yDPM3=>3RZv zYKR884xUEdPq~jy(Htim^(OM}fk9UbMw@6(u%kt{P01AhK-czc>vs(FWOV|Xdn@i8 zvm^Qqh&CxlU@I_%!|Xr4FBiWsq3#zWW_e)nO^)Q(sJ6Vx=VvOeN$VJyI)Z#CZ5K}y(=kq4ve8!G&6ON?uW7P9bvz;G`p6eLKRo}(tshX>Fg=r z+?`A~iX>LIUFw6)RPmS5=wP6cmxvyl^E*VGN!I+qXfdsB^0)3JE)~;D>O*JU>3IHg zGsr5w*dei4C^NLAcp;nL%ZkOXN5#~)I)~G7T>tC;_xE>{tUqL==%`B#3jhzH^P78T z+zgtVjK{DIHn|9(CdRqg@8>!s31a01hT&wUzpQVrA(A z0P`u05$yfb(#+1AK05S^86%8T%z!TaKP}zU8};8Z4nvPC<0m;xEYm>Y&S>7oYA@$ zR)tO%8jeC)JD22j&fM|1D<>rOhGXp0FHy`kvhMiy~MCVO!ywL`~Wv|QU!UOL9 z)TI+d?Bjz6-Z)gLh>y-#X%DDL&)jaGws^o8uBwpr zv@Cr9YPYsqPuGDLAHH|vj$8X*|7#sHCwbPHMzJPhfr+J0PA?c??{(c&O?4ZB^%CAz z`b+)qvVyLo8!J*dvoW*hmb}kB;!b-D3NYTp+<0tVMm<+M;CFsqR#jJBqV?1O1SIxE z_KvTsmok2(Skz>&L-~iWmLdgVbAD7M52tZqa9e1`4;(1N2Pw*DuyXkMoEuj;l!Y}~ zNv>HsTc5p4P1+>qJ_A{KcX}qqkz`v^OOOsL*z*aH#hi?V zKFAKcyfg0f;+1|u5myh3MAxzvF{u~o5sNx=rtL)i^V>ebxHQ%;a!*9RLubk80IjRl zYfp@tPSlr;chmH!Q!Pm)dN`_O0E&PpsR&BVS6kZzpUgFe z=IvJVIZ3gwu;yYq5S8f~F1ix#X&mM}5V}|@2Rq(G58Ulxq!15hqLo)Lw?w(ZS~onc z@MyQVPCQ6Xj!`hO6};8*YYDo>e&Kv&pl1g;lFS5@Gqf<01{b`6f_5Ge_SwFi5E#YJ zIITb}g|h5RZ)KoW_4cFH&erNhA>=(iHyj37`qnAGNEZ|&|Dg*=v>gL_Recj3wU+^q zJ^+w2llQ;N>Q?(AoNvVa-)$k-P4Wn4-fW9`8bVSnZc=vcJy;eF?QNKlUR7D_X|!Xs zdro-gd1g*4|SbU6=$GGT4os7)T2dxLmbxeQNd&WHXI^E+GQ|bw1;4_`1^=gHZ_2;tE%#& zB}yNFetXdCX|JyCo6j%i{rBW8dGK_z&pS)=_|LNmt2MApQ@iuHphK8?8vklsx*e#7 zg#voa7p|7BM{s{Wmo;x4f{ne*;X?*LPwfe(?JRN&6J6DOxu$#Dp5YmX_q0V41zbK% z;`t`s4947bVUgn)Mofdcgpl`unbL9@nSHyG7nw-MX1C*7E1_DIFwzMkXg=sFeot-L zGew{j>j1~QT^QH+ew-bvh_9@%iWoEi{x%E&jbB@Q%5635lx5F0iMh1;fHB-o%9EWk z(g)!A#_nj$uG^rwmn_$=UP=CbXSd!~FSeW|G-W?0E<#<0P(Os-+~C;}k#TshKI>I` zfX?PnR`Vv7c2CWzNOEpSQFduz`>9=!xu6b`_J&0?HX%X=R9I@C<&K?c8i**OIuRH^Y1#-n?H}Sq3dAa@K&Nc-;6(B8S(y3W%;IBE<6n zjWv@18S6rNiP_20H`?%xpGMV~TtpK9J~s@YNxOKAPsR#4&>3NYP6A`N#^_g6zVrb& zJe+N&49=Nj{s+=zzc-mNGeg4m2Okq-x@lt>hdNT6LWhk!b=ZdcuavS6edB@dHjY7h z?8kAUphiw-l>!&Jr?F%|?;I}DGQD4BVdpgPM1$MSJ&kR&g4bOje5C{!$dm3AT~&i% zb2V@L$q1lanj0M*8}0*UtgECT2kFRo_> zZ@%#-TcdUV;Jw%Td)tdCx9?x7;0HGuo2}fkP@BWLkWef)uI$5O7`zHi&Jezr&#Fg# z9QE8(T@x5pe>%Ned)9OU-lX%*CcV~n#p}_;=?bvx+S2!%*y1O}Sp@Yg46>nQW^93` zNW3uPw^Vz}QY}#%LU=fQTxXS)4NDt$*v1;a-13kVdDkk$w|(;uZ5;qw>9c-HufVW3 z9NoF54VOGZ1NuwE`wr^>9%qDNPFy>*Pv!z1p1lE)fju3BCCAFgkan^Kz`ZF(^vw9X zYK>u^U{|JIwlU^$(CmaZH+7OCC0>t9dHDL==-WgaRRVs9oGoKHBP(N6Ok5TcGH7SI z5jM2$?aJZt0N{(6q;4JgwYx&|4;>x)H|3mAq zUY~z)B}AvuV)Q_2TU-Tnvy3|C{zB|^YinChGA-|3IJ|_ BTuJ}{ literal 0 HcmV?d00001 diff --git a/Resources/Audio/Voice/Human/wilhelm_scream.ogg b/Resources/Audio/Voice/Human/wilhelm_scream.ogg new file mode 100644 index 0000000000000000000000000000000000000000..9a81c47f3797552626dd5fc1abe9478a058fd628 GIT binary patch literal 13984 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 literal 0 HcmV?d00001 diff --git a/Resources/Prototypes/Actions/actions.yml b/Resources/Prototypes/Actions/actions.yml new file mode 100644 index 0000000000..c30aef5800 --- /dev/null +++ b/Resources/Prototypes/Actions/actions.yml @@ -0,0 +1,91 @@ +- type: action + actionType: HumanScream + icon: Interface/Actions/scream.png + name: "Scream" + filters: + - human + behaviorType: Instant + behavior: !type:ScreamAction + cooldown: 10 + male: + - /Audio/Voice/Human/malescream_1.ogg + - /Audio/Voice/Human/malescream_2.ogg + - /Audio/Voice/Human/malescream_3.ogg + - /Audio/Voice/Human/malescream_4.ogg + - /Audio/Voice/Human/malescream_5.ogg + - /Audio/Voice/Human/malescream_6.ogg + female: + - /Audio/Voice/Human/femalescream_1.ogg + - /Audio/Voice/Human/femalescream_2.ogg + - /Audio/Voice/Human/femalescream_3.ogg + - /Audio/Voice/Human/femalescream_4.ogg + - /Audio/Voice/Human/femalescream_5.ogg + wilhelm: /Audio/Voice/Human/wilhelm_scream.ogg + +- type: action + actionType: DebugInstant + icon: Interface/Alerts/Human/human1.png + name: "[color=red]Debug Instant[/color]" + description: "This is a [color=red]debug message[/color]." + requires: "Requires blah blah" + filters: + - debug + behaviorType: Instant + behavior: !type:DebugInstant + message: Instant action was used! + +- type: action + actionType: DebugToggle + icon: Interface/Alerts/Human/human3.png + name: "[color=red]Debug Toggle[/color]" + description: "This is a [color=red]debug message[/color]." + requires: "Requires blah blah" + filters: + - debug + behaviorType: Toggle + behavior: !type:DebugToggle + messageOn: Toggled on! + messageOff: Toggled off! + +- type: action + actionType: DebugTargetPoint + icon: Interface/Alerts/Human/human4.png + name: "[color=red]Debug Target Position[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetPoint + behavior: !type:DebugTargetPoint { } + +- type: action + actionType: DebugTargetPointRepeat + icon: Interface/Alerts/Human/human2.png + name: "[color=red]Repeating Debug Target Position[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetPoint + repeat: true + behavior: !type:DebugTargetPoint { } + +- type: action + actionType: DebugTargetEntity + icon: Interface/Alerts/Human/human6.png + name: "[color=red]Debug Target Entity[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetEntity + behavior: !type:DebugTargetEntity { } + +- type: action + actionType: DebugTargetEntityRepeat + icon: Interface/Alerts/Human/human5.png + name: "[color=red]Repeating Debug Target Entity[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetEntity + repeat: true + behavior: !type:DebugTargetEntity { } + diff --git a/Resources/Prototypes/Actions/item_actions.yml b/Resources/Prototypes/Actions/item_actions.yml new file mode 100644 index 0000000000..f5ea9a4766 --- /dev/null +++ b/Resources/Prototypes/Actions/item_actions.yml @@ -0,0 +1,121 @@ +- type: itemAction + actionType: ToggleInternals + icon: Interface/Actions/internal0.png + iconOn: Interface/Actions/internal1.png + name: "Toggle Internals" + description: "Breathe from the equipped gas tank." + requires: "Requires equipped breath mask and gas tank" + filters: + - common + - atmos + keywords: + - gas + - tank + - breath + behaviorType: Toggle + behavior: !type:ToggleInternalsAction { } + +- type: itemAction + actionType: ToggleLight + icon: Objects/Tools/flashlight.rsi/lantern_off.png + iconOn: Objects/Tools/flashlight.rsi/lantern_on.png + name: "Toggle Light" + description: "Turn the light on." + filters: + - tools + keywords: + - lantern + - lamp + behaviorType: Toggle + behavior: !type:ToggleLightAction { } + +- type: itemAction + actionType: DebugInstant + icon: Interface/Alerts/Human/human1.png + iconStyle: BigAction + name: "[color=red]Debug Item Instant[/color]" + description: "This is a [color=red]debug message[/color]." + requires: "Requires blah blah" + filters: + - debug + behaviorType: Instant + behavior: !type:DebugInstant + message: Instant action was used! + cooldown: 10 + +- type: itemAction + actionType: DebugToggle + iconStyle: BigItem + icon: Interface/Alerts/Human/human3.png + name: "[color=red]Debug Item Toggle[/color]" + description: "This is a [color=red]debug message[/color]." + requires: "Requires blah blah" + filters: + - debug + behaviorType: Toggle + behavior: !type:DebugToggle + messageOn: Toggled on! + messageOff: Toggled off! + +- type: itemAction + actionType: DebugTargetPoint + iconStyle: NoItem + icon: Interface/Alerts/Human/human4.png + name: "[color=red]Debug Item Target Position[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetPoint + behavior: !type:DebugTargetPoint { } + +- type: itemAction + actionType: DebugTargetPointRepeat + iconStyle: BigAction + icon: Interface/Alerts/Human/human2.png + name: "[color=red]Repeating Debug Item Target Position[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetPoint + repeat: true + behavior: !type:DebugTargetPoint { } + +- type: itemAction + actionType: DebugTargetEntity + iconStyle: BigAction + icon: Interface/Alerts/Human/human6.png + name: "[color=red]Debug Item Target Entity[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetEntity + behavior: !type:DebugTargetEntity { } + +- type: itemAction + actionType: DebugTargetEntityRepeat + icon: Interface/Alerts/Human/human5.png + name: "[color=red]Repeating Debug Item Target Entity[/color]" + description: "This is a [color=red]debug message[/color]." + filters: + - debug + behaviorType: TargetEntity + repeat: true + behavior: !type:DebugTargetEntity { } + +- type: entity + name: item action example + parent: BaseItem + id: ItemActionExample + description: for testing item actions + components: + - type: Sprite + sprite: Objects/Fun/bikehorn.rsi + state: icon + - type: ItemActions + actions: + - actionType: DebugInstant + - actionType: DebugToggle + - actionType: DebugTargetPoint + - actionType: DebugTargetPointRepeat + - actionType: DebugTargetEntity + - actionType: DebugTargetEntityRepeat diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 52e0d5a655..326ef60494 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -36,6 +36,7 @@ - type: alert alertType: Fire icon: /Textures/Interface/Alerts/Fire/fire.png + onClick: !type:ResistFire { } name: "[color=red]On Fire[/color]" description: "You're [color=red]on fire[/color]. Click the alert to stop, drop and roll to put the fire out or move to a vacuum area." @@ -80,6 +81,7 @@ - type: alert alertType: Buckled category: Buckled + onClick: !type:Unbuckle { } icon: /Textures/Interface/Alerts/Buckle/buckled.png name: "[color=yellow]Buckled[/color]" description: "You've been [color=yellow]buckled[/color] to something. Click the alert to unbuckle unless you're [color=yellow]handcuffed.[/color]" @@ -110,6 +112,7 @@ - type: alert alertType: PilotingShuttle category: Piloting + onClick: !type:StopPiloting { } icon: /Textures/Interface/Alerts/Buckle/buckled.png name: Piloting Shuttle description: You are piloting a shuttle. Click the alert to stop. @@ -165,6 +168,7 @@ - type: alert alertType: Pulling icon: /Textures/Interface/Alerts/Pull/pulling.png + onClick: !type:StopPulling { } name: Pulling description: You're pulling something. Click the alert to stop. diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml index 236cfac568..e8e403ae9b 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml @@ -20,6 +20,9 @@ visuals: - type: FlashLightVisualizer - type: HandheldLight + - type: ItemActions + actions: + - actionType: ToggleLight - type: PowerCellSlot - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 671a69b4db..e692fa9830 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -10,7 +10,10 @@ show_examine_info: true - type: Input context: "human" - - type: AlertsUI + - type: Alerts + - type: Actions + innateActions: + - HumanScream - type: OverlayEffectsUI - type: Eye zoom: 0.5, 0.5 diff --git a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml index 41169aa299..279f3bde1a 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml @@ -6,6 +6,9 @@ abstract: true components: - type: HandheldLight + - type: ItemActions + actions: + - actionType: ToggleLight - type: Sprite sprite: Objects/Misc/lights.rsi - type: Item diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml index 7fa4d153b3..cd3dd41e3e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml @@ -5,6 +5,9 @@ description: They light the way to freedom. components: - type: HandheldLight + - type: ItemActions + actions: + - actionType: ToggleLight - type: PowerCellSlot - type: Sprite sprite: Objects/Tools/flashlight.rsi diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 7f1b12ea5f..26e3cc5788 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -19,6 +19,9 @@ - Back - Belt - type: GasTank + - type: ItemActions + actions: + - actionType: ToggleInternals - type: entity id: OxygenTank @@ -281,7 +284,7 @@ sprite: Objects/Tanks/phoron.rsi Slots: - Belt - + - type: entity id: PhoronTankFilled parent: PhoronTank diff --git a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml index 5fbbbe13f1..a8f62030fd 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml @@ -5,6 +5,9 @@ description: The holy light guides the way. components: - type: HandheldLight + - type: ItemActions + actions: + - actionType: ToggleLight - type: Sprite sprite: Objects/Tools/lantern.rsi layers: diff --git a/Resources/Textures/Interface/Actions/internal0.png b/Resources/Textures/Interface/Actions/internal0.png new file mode 100644 index 0000000000000000000000000000000000000000..c136016c6473c6036b1db55b7d3a8979424517dc GIT binary patch literal 850 zcmV-Y1FigtP)i$6Gc@7~?}e1Cu5ANX(o*c3k> zsj4dL`-Wlo`p=Jb`T-{r2}-3B0D(XNK(3V#&k0mjl|l#r^0^$_`ubdLUDsDFqNCYh zAP``B@olYL^`7a)x3%_at)Y5VRb?WP;PU5ZbWg~_m6hc^_?pP z^|`m;4JWUT2_amo#DXi`L6p$o($gJu34#U@uqG2iaChf70K(T_V~Nsrox<2b07m9E zqw6|gUlA6Zo_>CJZDSzx6C})K4P?1!i-czib`1=$YtNqAyV==U0AjIN?Oi6FCX>xJ zg1!_vRN|ebPaBrCZ=iDgfxPWj*iOnyC2&<2au38y8x(E z>^^&LMAeC73=9r1hbM}7k611B2^}q5|zJGMZe8uHs6{WTA zOQn))19BFZJ{bfcc6G=Gm*par2PbUIrW=uE`CJZ7)6g`Hd@g4jv*7wJx@|t-Y@F! z3Wi}Q&VEa3&2O)r_HxzKy{_xhFbuALe9ZdcqkOu0mp%FK0KC#Ski0U;{79CITMuBG z=CY#TYhY^MCe{xhCHmnD0Hx7~0Cb!^48Y>HcIHR2q@I8FHINvKx{BBCU-UfZNkM(? z6}*|$oDjluN)$X2*NFDiev!E5F$7H_pk5|~;BMDf05*@lM47TIi@CFV0T`U#h-F!T z`$Q;sq!X%Jev(?~24E^bO|>jtk?>8yj_z)D?A+;$<#IUyGMS7sR+yZmP%H+*l~N1c z%zT+<@rBz!(^HVo=e0tyNMUl)f!nr?ZQG8KX7Hh*A$k76WzWtf@NQiZfL~hG#R{xm zUDstIk-)ZXR|?cvvz=d?9;K_RlS}$Jrq&fvNnbO&mc{o~s%6RNBh}y(G@1}XIOo1n zsdz4+#^N&LwYoUbn+b$Ni;GBUwp7A2O-$2dwp4P>DYzN89Pr8B44`rS&aFUrCpwcK50HXa%Mt_OT6h0h zj)BgDTu7ba(CqbJK*fz)lbO4oht=Kzz-gYYuw|J3v86d-!z1T zL&zfZEb44N9_`Wo2@WkH5G124Tp^G4Xo1!4r@#oPI;fYFlIjd>Ka_I9zEn80xQH4H zF`Z7!W4qpPEp+-}oPr=yOn3hZ?NhXtl@E|pd#y_2cY&N@M n{|A<2Agt;`-l_&x_22ayngy}?iV}4800000NkvXXu0mjfLid{T literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/meta.json b/Resources/Textures/Interface/Actions/meta.json new file mode 100644 index 0000000000..6ccd7cf002 --- /dev/null +++ b/Resources/Textures/Interface/Actions/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/3d049e69fe71a0be2133005e65ea469135d648c8", + "states": [ + { + "name": "internal0", + "directions": 1 + }, + { + "name": "internal1", + "directions": 1 + }, + { + "name": "scream", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Interface/Actions/scream.png b/Resources/Textures/Interface/Actions/scream.png new file mode 100644 index 0000000000000000000000000000000000000000..af3494368a6d940785f4c26f2dd2f0461fda9550 GIT binary patch literal 265 zcmV+k0rvihP)GX zAP@vi2X-VE5acwFRnmYFyyLh7OX~uVeC-E5a7Oth{BAVIP1pVJ4MQ_qdm_dHwpZZd z09=(Yxf!@u#jM$Q^lBp;Y9|3SU?&d+#&D2(eAOcqK859snSyvoRKU@LHN@PU_t>zM zkkPsD2Fj6Rs0WIS1R P00000NkvXXu0mjfF{W^B literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/black_panel_dark_thin_border.png b/Resources/Textures/Interface/Nano/black_panel_dark_thin_border.png new file mode 100644 index 0000000000000000000000000000000000000000..b95f047938482155f7f4f7605f64e0fef29efc26 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=&H|6fVg?31We{epSZZGe6l5>) z^mS!_!YwMIr#9L6(O005hNp{T2*>s0FP}dBI^Vz+Apr#2a&NPVfB5t%DIw_q(*~|h d2ev3MGpxPGdeXb-ybVwfgQu&X%Q~loCIE~OC%XUu literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/black_panel_light_thin_border.png b/Resources/Textures/Interface/Nano/black_panel_light_thin_border.png new file mode 100644 index 0000000000000000000000000000000000000000..d1a9f279ba3c47ee4f23c3a8d2a39120762c0cd7 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=&H|6fVg?31We{epSZZGe6l5>) z^mS!_!YwMIs}NW#!VDDB^mK6y;kceW$FBBQJ)6maLkA8Vm@soDr}zxJS}SH@X0e9Q e39QRlC4p8=W}m*}_Ua;_CI(MeKbLh*2~7aT)*`C_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/black_panel_red_thin_border.png b/Resources/Textures/Interface/Nano/black_panel_red_thin_border.png new file mode 100644 index 0000000000000000000000000000000000000000..9d3960ccfa5155071b3771aea67994f698882a62 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=&H|6fVg?31We{epSZZGe6l5>) z^mS!_!YwLdsCa<4@&r&w&C|s(gyVX$&4B|S<}>ncFaQDz1x}d+AV@mEw1I2Wfh`Km Z4D6qndoAp9#eq5)JYD@<);T3K0RURnAi)3t literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/gear.svg b/Resources/Textures/Interface/Nano/gear.svg new file mode 100644 index 0000000000..ccb451d706 --- /dev/null +++ b/Resources/Textures/Interface/Nano/gear.svg @@ -0,0 +1,3 @@ + + + diff --git a/Resources/Textures/Interface/Nano/gear.svg.png b/Resources/Textures/Interface/Nano/gear.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..522472042c4e42786f1402c634bfb7b34c9c3f0c GIT binary patch literal 612 zcmV-q0-ODbP)4b1fayT4AW;P(D~NJVBJRe?WPA!$0jUb24lGv?bzr&zYsS9FHVHp0 zcV&)E`((_J1hD`f@PJk7nS_Rnd0xgXQ3*AuM|iY@zA>lNH%2l=|E#wGN5?`a}Z(b;F#A9-v}h-9FJ=SEoRXdoeW=6|ci^ zB|p2SjTN^k)U=5R@K1Bye)se|;qR8`uDrU}i?GOF*a5e%j=8ws2n*k;V`-~K)-}3TA zS_`0zVLSyNUu@4r>wW*7Z44rls9Ogs20*Y$#c_bC%4!5rXBZSx)IWRF&sK@-8!X*B z-#5xm)E#dNd@4LEv^Mc}w46x0GL%+jJBt(*%ol6^Fery-G6@JT_(>{mWr z*aG}vdq?)DA^Imo4<^Y=t=ujFaOVB?dqhN^*y|t{XvcH2)J@x-!KuFL2)E?>7+K(* z)AxyX;NS%X(`)S0OQM_wmAS%Hq}u+alq)g`gYYdDGda>}?Z6+3kO{v2JJgMg+E$6S yVJblG*ZVPn8WtJ#%3l=(9sskDe*kQT0&W3)neCmX&TGd20000 + + diff --git a/Resources/Textures/Interface/Nano/left_arrow.svg.png b/Resources/Textures/Interface/Nano/left_arrow.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..94535028325521eb9044996d0e879e9e52028a09 GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7Bet#3xhBt!>l*8o|0J>k`KLWy978G?YXc7QHU;qH3SKP13m=DGQiE{6k@zC|pO4bbV>7SA1#=i?D!`ixoWeQ8CG ziV5ed^O9kKKQG^K^jPa?a4_JmSHeS=Ls>m+@8g<&1}3Vn;!p&7i^0>?&t;ucLK6TW CP;pZL literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/light_panel_background_bordered.png b/Resources/Textures/Interface/Nano/light_panel_background_bordered.png new file mode 100644 index 0000000000000000000000000000000000000000..95aab33ba3763ced04d78fde85d39357d9448e8b GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=wj^(N7l!{JxM1({$v_d#0*}aI z1_nK45N51cYF`EvWH0gbb!C6T!^@(|&d#b`02EU2ba4#fxSnjX{mb9;4Qw+cHZbf^ lkhbPu_F(<|_z&6a45CUb$ELPVuLG)O@O1TaS?83{1OTBFCk+4q literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/lock.svg b/Resources/Textures/Interface/Nano/lock.svg new file mode 100644 index 0000000000..54d5211aba --- /dev/null +++ b/Resources/Textures/Interface/Nano/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/Resources/Textures/Interface/Nano/lock.svg.png b/Resources/Textures/Interface/Nano/lock.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..148cc20f5eb2004a815e20c143646362e6804f7b GIT binary patch literal 446 zcmV;v0YUzWP)ISl0BtEtYX2A3jlHhJpotH6F5i6#wwL0ZBI>&=XqGy`5SlGkrg!8(|PIpr3F zx0?mNg&~6%3^ESLOTaea?3}qTVgA3$(rPbaeifmK>BC=@+MMg{0gM40jF)=4x17p< z#tOqM+hSEQUj&+DALE?+5px3D5T_6I?gr`pTZp9`>G|DO?j!X8+KC(BG%=~jpaYob oF+B{KZ73JhZz?3PS>8|gU#tm?A;vW8DgXcg07*qoM6N<$f|l*G%m4rY literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/lock_open.svg b/Resources/Textures/Interface/Nano/lock_open.svg new file mode 100644 index 0000000000..e4f0449523 --- /dev/null +++ b/Resources/Textures/Interface/Nano/lock_open.svg @@ -0,0 +1,64 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Resources/Textures/Interface/Nano/lock_open.svg.png b/Resources/Textures/Interface/Nano/lock_open.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..d08584bc170a585aab6bc4c413bdee43a2ce30de GIT binary patch literal 557 zcmV+|0@D47P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10lG;< zK~y-6jg-4<6j2n$f9K9P!Zt#Pl_3a0K`et!1e*m5A%(SRCQ)LMPDs$~hRg2kN_s8A zMpnolU=cydqw&%B?gX1OTKNDQ!OiYD7MsBAI+MxQ-S2$A`*H7uD`aSCY3)=HotQ@C zjG$k;f4fgtSLc5Z=VaKZ`1UnZ+WrbP?mEIHnsu-Z`+h@2#}o3$|rJxCq3Dc{czXQ@~#0XLr(mvlefI z>|u5}?GH3JYP08t?;y4HH(+2c0N$^aIz;D#o&-QHL<|B{Jc9{pLqQ z9s)lQ9yERYDJz$I=4LHk02cD*xy{E_=s*onw7k61K3fE+=>!N4G{ng()b1d1AFQ}e v`d@&-OM(9Hf!Y*D+(AlR#a=gSbI<+(e+in41SQmE00000NkvXXu0mjfhuriS literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/right_arrow.svg b/Resources/Textures/Interface/Nano/right_arrow.svg new file mode 100644 index 0000000000..eff5fdff21 --- /dev/null +++ b/Resources/Textures/Interface/Nano/right_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/Resources/Textures/Interface/Nano/right_arrow.svg.png b/Resources/Textures/Interface/Nano/right_arrow.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed4cb62fa00d06a4297276d779616df52051f94 GIT binary patch literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRQ!3HGLSWEVS1PVMNiy0WWg+Z8+Vb&Z8pde#$ zkh>GZx^prwfgF}}M_)$E)e-c@N{8OGTjv*C{wf%;CO#uQf<@;5lidbwL!!Jlz zFf)ggm59avP+&|}4GU{9XY^NcKX8oc!}@@Xj;`QbvwS1t4I70Ok7V-HA5pomXi?&+ zwI^P-PT0V)?Nx3(1qJpXAM=D=r9ZlcUmdKI;Vst02+yK A-~a#s literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/square.png b/Resources/Textures/Interface/Nano/square.png new file mode 100644 index 0000000000000000000000000000000000000000..468260b1e6c852a90babc0cacccb280d95bd5221 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1UlJzX3_ zJUZW=HDqL9;5ck>VE^+(rhOp}mpy~}&pwv{ssMrq>sZBgS#+L#mAwMudb;|#taD0e F0suH3E_MI_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/square.svg b/Resources/Textures/Interface/Nano/square.svg new file mode 100644 index 0000000000..55ba05166b --- /dev/null +++ b/Resources/Textures/Interface/Nano/square.svg @@ -0,0 +1,70 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/Interface/Nano/transparent_window_background_bordered.png b/Resources/Textures/Interface/Nano/transparent_window_background_bordered.png index 987ad4cba61570fa3677a329b451e88e2a9f4554..b9cc0a9ce186989990098254d60a5d45372a73f3 100644 GIT binary patch delta 73 zcmbQh*vvS=(MeQ9-=uQlqCX4_3`(9Zjv*Y^lTEgN`F*~DZHB}Kh8+)XUoYoR;Na@k dvVL%tkD*G0^?3B8D~v$Z44$rjF6*2UngH268g&2w delta 86 zcmZo>oWMB2(Vv6GK)`HnN+SaUgTAMWV+hCf&;yevwJvpg-l7#J87JY5_^IIbs~7#03L t-@rCQVgtjD2g~{W4OxG(Ecuwt&fqqgWvhV1^_M`k44$rjF6*2UngEd=A0+?) literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=&H|6fVg?31We{epSZZGe6l5>) z^mS!_!p+3Uz*bUubPZ6b#M8wwghQ4wlVRF_Mj)TT+|utqhz5a^$2T!DA`3u-85o#g njJL1v!k7p_VNsy1P=M9Ut%f{DBbQ#908-=W>gTe~DWM4f4^bsK diff --git a/Resources/Textures/Objects/Tools/flashlight.rsi/lantern_on.png b/Resources/Textures/Objects/Tools/flashlight.rsi/lantern_on.png new file mode 100644 index 0000000000000000000000000000000000000000..ff8752b672b07a36f8a1d0d073abf126047ae305 GIT binary patch literal 293 zcmV+=0owkFP)2mZ0rLXFM%w%qg@z=Kfelu4Ge+0ehi3cPrm>d@pHyb0MqFlZGeu5 zN6*ajbcs7{!R*D=A*c4Yl+vANqKC08tG=t?sW4{}^AcBGQmP>NgiKMyoYU@5KNS&E rqSW4RnE}eXXuscn6Iifw2)>yY=