From d32eb158d4ce6cd077efa939d946bfa787335d97 Mon Sep 17 00:00:00 2001
From: Spatison <137375981+Spatison@users.noreply.github.com>
Date: Thu, 18 Jul 2024 01:33:28 +0300
Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BF?=
=?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=BC=D0=B5=D1=82=D1=8B=20=D0=BC=D0=BE=D0=B6?=
=?UTF-8?q?=D0=BD=D0=BE=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D0=B2=D0=B0?=
=?UTF-8?q?=D1=82=D1=8C=20=D0=B8=D0=B7=20=D1=80=D1=83=D0=BA=20=D0=B2=20?=
=?UTF-8?q?=D1=80=D1=83=D0=BA=D0=B8=20(#462)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* add: Теперь предеты можно передовать из рук в руки
* Кудато не туда уехало
---
Content.Client/Input/ContentContexts.cs | 1 +
.../Options/UI/Tabs/KeyRebindTab.xaml.cs | 1 +
Content.Client/Options/UI/Tabs/MiscTab.xaml | 1 +
.../Options/UI/Tabs/MiscTab.xaml.cs | 6 +
.../OfferItem/OfferItemIndicatorsOverlay.cs | 72 ++++++++
.../_White/OfferItem/OfferItemSystem.cs | 52 ++++++
.../_White/Alert/Click/AcceptOffer.cs | 24 +++
.../_White/OfferItem/OfferItemSystem.cs | 83 +++++++++
Content.Shared/Alert/AlertType.cs | 3 +-
Content.Shared/Input/ContentKeyFunctions.cs | 1 +
.../_White/OfferItem/OfferItemComponent.cs | 26 +++
.../SharedOfferItemSystem.Interactions.cs | 74 ++++++++
.../_White/OfferItem/SharedOfferItemSystem.cs | 158 ++++++++++++++++++
Content.Shared/_White/WhiteCVars.cs | 7 +
Resources/Locale/en-US/alerts/alerts.ftl | 3 +
.../en-US/escape-menu/ui/options-menu.ftl | 2 +
.../en-US/interaction/offer-item-system.ftl | 9 +
Resources/Locale/ru-RU/alerts/alerts.ftl | 3 +
.../ru-RU/escape-menu/ui/options-menu.ftl | 2 +
.../ru-RU/interaction/offer-item-system.ftl | 9 +
Resources/Prototypes/Alerts/alerts.yml | 11 ++
.../Prototypes/Entities/Mobs/Species/base.yml | 1 +
.../Interface/Alerts/offer_item.rsi/meta.json | 14 ++
.../Alerts/offer_item.rsi/offer_item.png | Bin 0 -> 556 bytes
.../Interface/give_item.rsi/give_item.png | Bin 0 -> 7276 bytes
.../White/Interface/give_item.rsi/meta.json | 14 ++
Resources/keybinds.yml | 3 +
27 files changed, 579 insertions(+), 1 deletion(-)
create mode 100644 Content.Client/_White/OfferItem/OfferItemIndicatorsOverlay.cs
create mode 100644 Content.Client/_White/OfferItem/OfferItemSystem.cs
create mode 100644 Content.Server/_White/Alert/Click/AcceptOffer.cs
create mode 100644 Content.Server/_White/OfferItem/OfferItemSystem.cs
create mode 100644 Content.Shared/_White/OfferItem/OfferItemComponent.cs
create mode 100644 Content.Shared/_White/OfferItem/SharedOfferItemSystem.Interactions.cs
create mode 100644 Content.Shared/_White/OfferItem/SharedOfferItemSystem.cs
create mode 100644 Resources/Locale/en-US/interaction/offer-item-system.ftl
create mode 100644 Resources/Locale/ru-RU/interaction/offer-item-system.ftl
create mode 100644 Resources/Textures/White/Interface/Alerts/offer_item.rsi/meta.json
create mode 100644 Resources/Textures/White/Interface/Alerts/offer_item.rsi/offer_item.png
create mode 100644 Resources/Textures/White/Interface/give_item.rsi/give_item.png
create mode 100644 Resources/Textures/White/Interface/give_item.rsi/meta.json
diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index 8753654fc3..fd6eee0e7b 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -81,6 +81,7 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.Arcade2);
human.AddFunction(ContentKeyFunctions.Arcade3);
human.AddFunction(ContentKeyFunctions.LieDown); // WD EDIT
+ human.AddFunction(ContentKeyFunctions.OfferItem); // WD EDIT
// actions should be common (for ghosts, mobs, etc)
common.AddFunction(ContentKeyFunctions.OpenActionsMenu);
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index b5449c99d2..7cd48d338f 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -191,6 +191,7 @@ namespace Content.Client.Options.UI.Tabs
AddButton(ContentKeyFunctions.MoveStoredItem);
AddButton(ContentKeyFunctions.RotateStoredItem);
AddButton(ContentKeyFunctions.SaveItemLocation);
+ AddButton(ContentKeyFunctions.OfferItem); // WD EDIT
AddButton(ContentKeyFunctions.LieDown); // WD EDIT
AddCheckBox("ui-options-function-auto-get-up", _cfg.GetCVar(WhiteCVars.AutoGetUp), HandleToggleAutoGetUp); // WD EDIT
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml b/Content.Client/Options/UI/Tabs/MiscTab.xaml
index 3810cddbb9..815254b788 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml
@@ -50,6 +50,7 @@
StyleClasses="LabelKeyText"/>
+
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
index b1724753ce..29ba230d1e 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Client.UserInterface.Screens;
+using Content.Shared._White;
using Content.Shared.CCVar;
using Content.Shared.HUD;
using Robust.Client.AutoGenerated;
@@ -69,6 +70,7 @@ namespace Content.Client.Options.UI.Tabs
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
+ ShowOfferModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled; // WD EDIT
OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
@@ -85,6 +87,7 @@ namespace Content.Client.Options.UI.Tabs
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
+ ShowOfferModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(WhiteCVars.OfferModeIndicatorsPointShow); // WD EDIT
OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
@@ -130,6 +133,7 @@ namespace Content.Client.Options.UI.Tabs
_cfg.SetCVar(CVars.DiscordEnabled, DiscordRich.Pressed);
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
+ _cfg.SetCVar(WhiteCVars.OfferModeIndicatorsPointShow, ShowOfferModeIndicatorsCheckBox.Pressed); // WD EDIT
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
_cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
@@ -158,6 +162,7 @@ namespace Content.Client.Options.UI.Tabs
var isDiscordSame = DiscordRich.Pressed == _cfg.GetCVar(CVars.DiscordEnabled);
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
+ var isOfferModeIndicatorsSame = ShowOfferModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(WhiteCVars.OfferModeIndicatorsPointShow); // WD EDIT
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
@@ -175,6 +180,7 @@ namespace Content.Client.Options.UI.Tabs
isDiscordSame &&
isShowHeldItemSame &&
isCombatModeIndicatorsSame &&
+ isOfferModeIndicatorsSame && // WD EDIT
isOpaqueStorageWindow &&
isOocPatronColorShowSame &&
isLoocShowSame &&
diff --git a/Content.Client/_White/OfferItem/OfferItemIndicatorsOverlay.cs b/Content.Client/_White/OfferItem/OfferItemIndicatorsOverlay.cs
new file mode 100644
index 0000000000..b5d8137ff8
--- /dev/null
+++ b/Content.Client/_White/OfferItem/OfferItemIndicatorsOverlay.cs
@@ -0,0 +1,72 @@
+using System.Numerics;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.UserInterface;
+using Robust.Shared.Enums;
+using Robust.Shared.Utility;
+
+namespace Content.Client._White.OfferItem;
+
+public sealed class OfferItemIndicatorsOverlay : Overlay
+{
+ private readonly IInputManager _inputManager;
+ private readonly IEntityManager _entMan;
+ private readonly IEyeManager _eye;
+ private readonly OfferItemSystem _offer;
+
+ private readonly Texture _sight;
+
+ public override OverlaySpace Space => OverlaySpace.ScreenSpace;
+
+ private readonly Color _mainColor = Color.White.WithAlpha(0.3f);
+ private readonly Color _strokeColor = Color.Black.WithAlpha(0.5f);
+ private readonly float _scale = 0.6f; // 1 is a little big
+
+ public OfferItemIndicatorsOverlay(IInputManager input, IEntityManager entMan,
+ IEyeManager eye, OfferItemSystem offerSys)
+ {
+ _inputManager = input;
+ _entMan = entMan;
+ _eye = eye;
+ _offer = offerSys;
+
+ var spriteSys = _entMan.EntitySysManager.GetEntitySystem();
+ _sight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/White/Interface/give_item.rsi"),
+ "give_item"));
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ if (!_offer.IsInOfferMode())
+ return false;
+
+ return base.BeforeDraw(in args);
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ var mouseScreenPosition = _inputManager.MouseScreenPosition;
+ var mousePosMap = _eye.PixelToMap(mouseScreenPosition);
+ if (mousePosMap.MapId != args.MapId)
+ return;
+
+
+ var mousePos = mouseScreenPosition.Position;
+ var uiScale = (args.ViewportControl as Control)?.UIScale ?? 1f;
+ var limitedScale = uiScale > 1.25f ? 1.25f : uiScale;
+
+ DrawSight(_sight, args.ScreenHandle, mousePos, limitedScale * _scale);
+ }
+
+ private void DrawSight(Texture sight, DrawingHandleScreen screen, Vector2 centerPos, float scale)
+ {
+ var sightSize = sight.Size * scale;
+ var expandedSize = sightSize + new Vector2(7f, 7f);
+
+ screen.DrawTextureRect(sight,
+ UIBox2.FromDimensions(centerPos - sightSize * 0.5f, sightSize), _strokeColor);
+ screen.DrawTextureRect(sight,
+ UIBox2.FromDimensions(centerPos - expandedSize * 0.5f, expandedSize), _mainColor);
+ }
+}
diff --git a/Content.Client/_White/OfferItem/OfferItemSystem.cs b/Content.Client/_White/OfferItem/OfferItemSystem.cs
new file mode 100644
index 0000000000..dd2fbc163a
--- /dev/null
+++ b/Content.Client/_White/OfferItem/OfferItemSystem.cs
@@ -0,0 +1,52 @@
+using Content.Shared._White;
+using Content.Shared._White.OfferItem;
+using Content.Shared.CCVar;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.Player;
+using Robust.Shared.Configuration;
+
+namespace Content.Client._White.OfferItem;
+
+public sealed class OfferItemSystem : SharedOfferItemSystem
+{
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IInputManager _inputManager = default!;
+ [Dependency] private readonly IEyeManager _eye = default!;
+
+ public override void Initialize()
+ {
+ Subs.CVar(_cfg, WhiteCVars.OfferModeIndicatorsPointShow, OnShowOfferIndicatorsChanged, true);
+ }
+ public override void Shutdown()
+ {
+ _overlayManager.RemoveOverlay();
+
+ base.Shutdown();
+ }
+
+ public bool IsInOfferMode()
+ {
+ var entity = _playerManager.LocalEntity;
+
+ if (entity == null)
+ return false;
+
+ return IsInOfferMode(entity.Value);
+ }
+ private void OnShowOfferIndicatorsChanged(bool isShow)
+ {
+ if (isShow)
+ {
+ _overlayManager.AddOverlay(new OfferItemIndicatorsOverlay(
+ _inputManager,
+ EntityManager,
+ _eye,
+ this));
+ }
+ else
+ _overlayManager.RemoveOverlay();
+ }
+}
diff --git a/Content.Server/_White/Alert/Click/AcceptOffer.cs b/Content.Server/_White/Alert/Click/AcceptOffer.cs
new file mode 100644
index 0000000000..2d38173c6e
--- /dev/null
+++ b/Content.Server/_White/Alert/Click/AcceptOffer.cs
@@ -0,0 +1,24 @@
+using Content.Server._White.OfferItem;
+using Content.Shared._White.OfferItem;
+using Content.Shared.Alert;
+using JetBrains.Annotations;
+
+namespace Content.Server._White.Alert.Click;
+
+///
+/// Accepting the offer and receive item
+///
+[UsedImplicitly]
+[DataDefinition]
+public sealed partial class AcceptOffer : IAlertClick
+{
+ public void AlertClicked(EntityUid player)
+ {
+ var entManager = IoCManager.Resolve();
+
+ if (entManager.TryGetComponent(player, out OfferItemComponent? offerItem))
+ {
+ entManager.System().Receive(player, offerItem);
+ }
+ }
+}
diff --git a/Content.Server/_White/OfferItem/OfferItemSystem.cs b/Content.Server/_White/OfferItem/OfferItemSystem.cs
new file mode 100644
index 0000000000..23df303bea
--- /dev/null
+++ b/Content.Server/_White/OfferItem/OfferItemSystem.cs
@@ -0,0 +1,83 @@
+using Content.Server.Popups;
+using Content.Shared.Hands.Components;
+using Content.Shared.Alert;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared._White.OfferItem;
+using Content.Shared.IdentityManagement;
+using Robust.Shared.Player;
+
+namespace Content.Server._White.OfferItem;
+
+public sealed class OfferItemSystem : SharedOfferItemSystem
+{
+ [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var offerItem))
+ {
+ if (!TryComp(uid, out var hands) || hands.ActiveHand == null)
+ continue;
+
+ if (offerItem.Hand != null &&
+ hands.Hands[offerItem.Hand].HeldEntity == null)
+ {
+ if (offerItem.Target != null)
+ {
+ UnReceive(offerItem.Target.Value, offerItem: offerItem);
+ offerItem.IsInOfferMode = false;
+ Dirty(uid, offerItem);
+ }
+ else
+ UnOffer(uid, offerItem);
+ }
+
+ if (!offerItem.IsInReceiveMode)
+ {
+ _alertsSystem.ClearAlert(uid, AlertType.Offer);
+ continue;
+ }
+
+ _alertsSystem.ShowAlert(uid, AlertType.Offer);
+ }
+ }
+
+ ///
+ /// Accepting the offer and receive item
+ ///
+ public void Receive(EntityUid uid, OfferItemComponent? component = null)
+ {
+ if (!Resolve(uid, ref component) ||
+ !TryComp(component.Target, out var offerItem) ||
+ offerItem.Hand == null ||
+ component.Target == null ||
+ !TryComp(uid, out var hands))
+ return;
+
+ if (offerItem.Item != null)
+ {
+ if (!_hands.TryPickup(uid, offerItem.Item.Value, handsComp: hands))
+ {
+ _popup.PopupEntity(Loc.GetString("offer-item-full-hand"), uid, uid);
+ return;
+ }
+
+ _popup.PopupEntity(Loc.GetString("offer-item-give",
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(uid, EntityManager))), component.Target.Value, component.Target.Value);
+ _popup.PopupEntity(Loc.GetString("offer-item-give-other",
+ ("user", Identity.Entity(component.Target.Value, EntityManager)),
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(uid, EntityManager)))
+ , component.Target.Value, Filter.PvsExcept(component.Target.Value, entityManager: EntityManager), true);
+ }
+
+ offerItem.Item = null;
+ UnReceive(uid, component, offerItem);
+ }
+}
diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs
index 76d31a2168..51c038d136 100644
--- a/Content.Shared/Alert/AlertType.cs
+++ b/Content.Shared/Alert/AlertType.cs
@@ -68,7 +68,8 @@ namespace Content.Shared.Alert
SuitPower,
BorgHealth,
BorgCrit,
- BorgDead
+ BorgDead,
+ Offer // WD EDITS
}
}
diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs
index 59c784cd43..56c0299c82 100644
--- a/Content.Shared/Input/ContentKeyFunctions.cs
+++ b/Content.Shared/Input/ContentKeyFunctions.cs
@@ -58,6 +58,7 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction ZoomIn = "ZoomIn";
public static readonly BoundKeyFunction ResetZoom = "ResetZoom";
public static readonly BoundKeyFunction LieDown = "LieDown"; // WD EDIT
+ public static readonly BoundKeyFunction OfferItem = "OfferItem"; // WD EDIT
public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp";
public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown";
diff --git a/Content.Shared/_White/OfferItem/OfferItemComponent.cs b/Content.Shared/_White/OfferItem/OfferItemComponent.cs
new file mode 100644
index 0000000000..fe1c455b6f
--- /dev/null
+++ b/Content.Shared/_White/OfferItem/OfferItemComponent.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._White.OfferItem;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+[Access(typeof(SharedOfferItemSystem))]
+public sealed partial class OfferItemComponent : Component
+{
+ [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ public bool IsInOfferMode;
+
+ [DataField, AutoNetworkedField]
+ public bool IsInReceiveMode;
+
+ [DataField, AutoNetworkedField]
+ public string? Hand;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? Item;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? Target;
+
+ [DataField]
+ public float MaxOfferDistance = 2f;
+}
diff --git a/Content.Shared/_White/OfferItem/SharedOfferItemSystem.Interactions.cs b/Content.Shared/_White/OfferItem/SharedOfferItemSystem.Interactions.cs
new file mode 100644
index 0000000000..39367951c7
--- /dev/null
+++ b/Content.Shared/_White/OfferItem/SharedOfferItemSystem.Interactions.cs
@@ -0,0 +1,74 @@
+using Content.Shared.Popups;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Input;
+using Content.Shared.Hands.Components;
+using Robust.Shared.Input.Binding;
+using Robust.Shared.Player;
+
+namespace Content.Shared._White.OfferItem;
+
+public abstract partial class SharedOfferItemSystem
+{
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ private void InitializeInteractions()
+ {
+ CommandBinds.Builder
+ .Bind(ContentKeyFunctions.OfferItem, InputCmdHandler.FromDelegate(SetInOfferMode, handle: false, outsidePrediction: false))
+ .Register();
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+
+ CommandBinds.Unregister();
+ }
+
+ private void SetInOfferMode(ICommonSession? session)
+ {
+ if (session is not { } playerSession)
+ return;
+
+ if ((playerSession.AttachedEntity is not { Valid: true } uid || !Exists(uid)) ||
+ !_actionBlocker.CanInteract(uid, null))
+ return;
+
+ if (!TryComp(uid, out var offerItem))
+ return;
+
+ if (!TryComp(uid, out var hands) || hands.ActiveHand == null)
+ return;
+
+ offerItem.Item = hands.ActiveHand.HeldEntity;
+
+ if (offerItem.IsInOfferMode == false)
+ {
+ if (offerItem.Item == null)
+ {
+ _popup.PopupEntity(Loc.GetString("offer-item-empty-hand"), uid, uid);
+ return;
+ }
+
+ if (offerItem.Hand == null || offerItem.Target == null)
+ {
+ offerItem.IsInOfferMode = true;
+ offerItem.Hand = hands.ActiveHand.Name;
+
+ Dirty(uid, offerItem);
+ return;
+ }
+ }
+
+ if (offerItem.Target != null)
+ {
+ UnReceive(offerItem.Target.Value, offerItem: offerItem);
+ offerItem.IsInOfferMode = false;
+ Dirty(uid, offerItem);
+ return;
+ }
+
+ UnOffer(uid, offerItem);
+ }
+}
diff --git a/Content.Shared/_White/OfferItem/SharedOfferItemSystem.cs b/Content.Shared/_White/OfferItem/SharedOfferItemSystem.cs
new file mode 100644
index 0000000000..4a471028cc
--- /dev/null
+++ b/Content.Shared/_White/OfferItem/SharedOfferItemSystem.cs
@@ -0,0 +1,158 @@
+using Content.Shared.Interaction;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Hands.Components;
+
+namespace Content.Shared._White.OfferItem;
+
+public abstract partial class SharedOfferItemSystem : EntitySystem
+{
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(SetInReceiveMode);
+ SubscribeLocalEvent(OnMove);
+
+ InitializeInteractions();
+ }
+
+
+ private void SetInReceiveMode(EntityUid uid, OfferItemComponent component, InteractUsingEvent args)
+ {
+ if (!TryComp(args.User, out var offerItem))
+ return;
+
+ if (args.User == uid || component.IsInReceiveMode || !offerItem.IsInOfferMode ||
+ (offerItem.IsInReceiveMode && offerItem.Target != uid))
+ return;
+
+ component.IsInReceiveMode = true;
+ component.Target = args.User;
+
+ Dirty(uid, component);
+
+ offerItem.Target = uid;
+ offerItem.IsInOfferMode = false;
+
+ Dirty(args.User, offerItem);
+
+ if (offerItem.Item == null)
+ return;
+
+ _popup.PopupEntity(Loc.GetString("offer-item-try-give",
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(uid, EntityManager))), component.Target.Value, component.Target.Value);
+ _popup.PopupEntity(Loc.GetString("offer-item-try-give-target",
+ ("user", Identity.Entity(component.Target.Value, EntityManager)),
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager))), component.Target.Value, uid);
+
+ args.Handled = true;
+ }
+
+ private void OnMove(EntityUid uid, OfferItemComponent component, MoveEvent args)
+ {
+ if (component.Target == null ||
+ args.NewPosition.InRange(EntityManager, _transform,
+ Transform(component.Target.Value).Coordinates, component.MaxOfferDistance))
+ return;
+
+ UnOffer(uid, component);
+ }
+
+ ///
+ /// Resets the of the user and the target
+ ///
+ protected void UnOffer(EntityUid uid, OfferItemComponent component)
+ {
+ if (!TryComp(uid, out var hands) || hands.ActiveHand == null)
+ return;
+
+
+ if (TryComp(component.Target, out var offerItem) && component.Target != null)
+ {
+
+ if (component.Item != null)
+ {
+ _popup.PopupEntity(Loc.GetString("offer-item-no-give",
+ ("item", Identity.Entity(component.Item.Value, EntityManager)),
+ ("target", Identity.Entity(component.Target.Value, EntityManager))), uid, uid);
+ _popup.PopupEntity(Loc.GetString("offer-item-no-give-target",
+ ("user", Identity.Entity(uid, EntityManager)),
+ ("item", Identity.Entity(component.Item.Value, EntityManager))), uid, component.Target.Value);
+ }
+
+ else if (offerItem.Item != null)
+ {
+ _popup.PopupEntity(Loc.GetString("offer-item-no-give",
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(uid, EntityManager))), component.Target.Value, component.Target.Value);
+ _popup.PopupEntity(Loc.GetString("offer-item-no-give-target",
+ ("user", Identity.Entity(component.Target.Value, EntityManager)),
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager))), component.Target.Value, uid);
+ }
+
+ offerItem.IsInOfferMode = false;
+ offerItem.IsInReceiveMode = false;
+ offerItem.Hand = null;
+ offerItem.Target = null;
+ offerItem.Item = null;
+
+ Dirty(component.Target.Value, offerItem);
+ }
+
+ component.IsInOfferMode = false;
+ component.IsInReceiveMode = false;
+ component.Hand = null;
+ component.Target = null;
+ component.Item = null;
+
+ Dirty(uid, component);
+ }
+
+
+ ///
+ /// Cancels the transfer of the item
+ ///
+ protected void UnReceive(EntityUid uid, OfferItemComponent? component = null, OfferItemComponent? offerItem = null)
+ {
+ if (component == null && !TryComp(uid, out component))
+ return;
+
+ if (offerItem == null && !TryComp(component.Target, out offerItem))
+ return;
+
+ if (!TryComp(uid, out var hands) || hands.ActiveHand == null ||
+ component.Target == null)
+ return;
+
+ if (offerItem.Item != null)
+ {
+ _popup.PopupEntity(Loc.GetString("offer-item-no-give",
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(uid, EntityManager))), component.Target.Value, component.Target.Value);
+ _popup.PopupEntity(Loc.GetString("offer-item-no-give-target",
+ ("user", Identity.Entity(component.Target.Value, EntityManager)),
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager))), component.Target.Value, uid);
+ }
+
+ if (!offerItem.IsInReceiveMode)
+ {
+ offerItem.Target = null;
+ component.Target = null;
+ }
+
+ offerItem.Item = null;
+ offerItem.Hand = null;
+ component.IsInReceiveMode = false;
+
+ Dirty(uid, component);
+ }
+
+ ///
+ /// Returns true if = true
+ ///
+ protected bool IsInOfferMode(EntityUid? entity, OfferItemComponent? component = null)
+ {
+ return entity != null && Resolve(entity.Value, ref component, false) && component.IsInOfferMode;
+ }
+}
diff --git a/Content.Shared/_White/WhiteCVars.cs b/Content.Shared/_White/WhiteCVars.cs
index da434e771b..d2a3b360d8 100644
--- a/Content.Shared/_White/WhiteCVars.cs
+++ b/Content.Shared/_White/WhiteCVars.cs
@@ -19,6 +19,13 @@ public sealed class WhiteCVars
public static readonly CVarDef ShowTrails =
CVarDef.Create("white.show_trails", true, CVar.CLIENTONLY | CVar.ARCHIVE);
+ /*
+ * Offer Indicator
+ */
+
+ public static readonly CVarDef OfferModeIndicatorsPointShow =
+ CVarDef.Create("white.offer_mode_indicators_point_show", true, CVar.ARCHIVE | CVar.CLIENTONLY);
+
/*
* Wiki rules
*/
diff --git a/Resources/Locale/en-US/alerts/alerts.ftl b/Resources/Locale/en-US/alerts/alerts.ftl
index db4a58275f..fb72664f04 100644
--- a/Resources/Locale/en-US/alerts/alerts.ftl
+++ b/Resources/Locale/en-US/alerts/alerts.ftl
@@ -113,3 +113,6 @@ alerts-revenant-corporeal-desc = You have manifested physically. People around y
alerts-changeling-chemicals-name = Chemicals
alerts-changeling-chemicals-desc = Our chemicals.
+
+alerts-offer-name = Offer
+alerts-offer-desc = Someone offers you an item.
diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
index 19afd1efdb..743b2e785c 100644
--- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
@@ -41,6 +41,7 @@ ui-options-volume-percent = { TOSTRING($volume, "P0") }
ui-options-show-held-item = Show held item next to cursor
ui-options-show-combat-mode-indicators = Show combat mode indicators with cursor
+ui-options-show-offer-mode-indicators = Show offer mode indicators with cursor
ui-options-opaque-storage-window = Opaque storage window
ui-options-show-ooc-patron-color = Show OOC Patreon color
ui-options-show-looc-on-head = Show LOOC chat above characters head
@@ -136,6 +137,7 @@ ui-options-function-examine-entity = Examine
ui-options-function-swap-hands = Swap hands
ui-options-function-move-stored-item = Move stored item
ui-options-function-rotate-stored-item = Rotate stored item
+ui-options-function-offer-item = Offer something
ui-options-function-lie-down = Lie down/Get up
ui-options-function-auto-get-up = Automatically get up when falling
ui-options-function-save-item-location = Save item location
diff --git a/Resources/Locale/en-US/interaction/offer-item-system.ftl b/Resources/Locale/en-US/interaction/offer-item-system.ftl
new file mode 100644
index 0000000000..128a196dc0
--- /dev/null
+++ b/Resources/Locale/en-US/interaction/offer-item-system.ftl
@@ -0,0 +1,9 @@
+offer-item-empty-hand = You don't have anything in your hand to give!
+offer-item-try-give = You offer {THE($item)} to {$target}
+offer-item-try-give-target = {$user} offers you {THE($item)}
+offer-item-try-give-target = {$user} offers you a {THE($item)}
+offer-item-give = You handed {THE($item)} to {$target}
+offer-item-give-other = {$user} handed {THE($item)} to {$target}
+offer-item-give-target = {$user} handed you {THE($item)}
+offer-item-no-give = You stop offering {THE($item)} to {$target}
+offer-item-no-give-target = {$user} is no longer offering {THE($item)} to you
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/alerts/alerts.ftl b/Resources/Locale/ru-RU/alerts/alerts.ftl
index 274bc07519..eae57111eb 100644
--- a/Resources/Locale/ru-RU/alerts/alerts.ftl
+++ b/Resources/Locale/ru-RU/alerts/alerts.ftl
@@ -113,3 +113,6 @@ alerts-changeling-chemicals-desc = Наши химикаты.
alerts-cult-buff-name = Усиление
alerts-cult-buff-desc = Подготовка заклинаний крови занимает гораздо меньше времени, и вы не теряете столько крови при этом. Также вы неуязвимы к низкому давлению.
+
+alerts-offer-name = Предложение
+alerts-offer-desc = Кто-то предлагает вам предмет.
diff --git a/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl b/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl
index f87208afd3..16a8697fd0 100644
--- a/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl
@@ -41,6 +41,7 @@ ui-options-volume-percent = { TOSTRING($volume, "P0") }
ui-options-show-held-item = Показать удерживаемый элемент рядом с курсором?
ui-options-show-combat-mode-indicators = Показать индикатор боевого режима рядом с курсором
+ui-options-show-offer-mode-indicators = Показывать индикаторы передачи предмета рядом с курсором
ui-options-opaque-storage-window = Непрозрачность окна хранилища
ui-options-show-looc-on-head = Показывать LOOC-чат над головами персонажей
ui-options-fancy-speech = Показывать имена в облачках с текстом
@@ -142,6 +143,7 @@ ui-options-function-lie-down = Лечь/встать
ui-options-function-auto-get-up = Автоматически вставать при падении
ui-options-function-save-item-location = Сохранить позицию предмета
ui-options-static-storage-ui = Закрепить интерфейс хранилища на хотбаре
+ui-options-function-offer-item = Передать что-то
ui-options-function-smart-equip-backpack = Умная экипировка в рюкзак
ui-options-function-smart-equip-belt = Умная экипировка на пояс
diff --git a/Resources/Locale/ru-RU/interaction/offer-item-system.ftl b/Resources/Locale/ru-RU/interaction/offer-item-system.ftl
new file mode 100644
index 0000000000..737cff1539
--- /dev/null
+++ b/Resources/Locale/ru-RU/interaction/offer-item-system.ftl
@@ -0,0 +1,9 @@
+offer-item-empty-hand = У тебя в руках нет ничего, что ты мог бы отдать!
+offer-item-full-hand = Ваша рука занята
+offer-item-try-give = Вы предлагаете {$item} {$target}
+offer-item-try-give-target = {$user} предлагает вам {$item}
+offer-item-give = Вы передали {THE($item)} {$target}
+offer-item-give-other = {$user} передал {$item} {$target}
+offer-item-give-target = {$user} передал вам ($item)
+offer-item-no-give = Вы прекращаете предлагать {$item} {$target}
+offer-item-no-give-target = {$user} прекращает предлагать вам ($item)
\ No newline at end of file
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index 8a0892bba2..a8db1c0f19 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -27,6 +27,7 @@
- category: Thirst
- alertType: Magboots
- alertType: Pacified
+ - alertType: Offer
- type: entity
id: AlertSpriteView
@@ -490,3 +491,13 @@
icons: [ /Textures/Interface/Alerts/bleeding.png ]
name: alerts-bleeding-name
description: alerts-bleeding-desc
+
+# WD-EDIT
+- type: alert
+ id: Offer
+ onClick: !type:AcceptOffer { }
+ icons:
+ - sprite: /Textures/White/Interface/Alerts/offer_item.rsi
+ state: offer_item
+ name: alerts-offer-name
+ description: alerts-offer-desc
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index 82615b6f43..980c4d8865 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -260,6 +260,7 @@
- type: CharacterInformation
- type: Penetrated
- type: Mood
+ - type: OfferItem # WD-EDIT
- type: entity
save: false
diff --git a/Resources/Textures/White/Interface/Alerts/offer_item.rsi/meta.json b/Resources/Textures/White/Interface/Alerts/offer_item.rsi/meta.json
new file mode 100644
index 0000000000..c5710a3a19
--- /dev/null
+++ b/Resources/Textures/White/Interface/Alerts/offer_item.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/ss220-space/Paradise/blob/master220/icons/mob/screen_alert.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "offer_item"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/White/Interface/Alerts/offer_item.rsi/offer_item.png b/Resources/Textures/White/Interface/Alerts/offer_item.rsi/offer_item.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce39fa92299f4d487ff88a18b1f7d2b92819b8f2
GIT binary patch
literal 556
zcmV+{0@MA8P)H&9zhXm&JASTjpiDk(2`i=8<*J{1)i6tN-C00001bW%=J06^y0
zW&i*IN=ZaPR5*=|lh=ZRFc3vyNe~+rgjg^d3z%_P|NrlHQVd1qUPXv
zlW91!YmZ06pzSbUEGaRFSvZ|UnCDxltl}hLrT%g;ZxSe3Z??NMWoBuXr17dw!16xk
zoU>Fj@cwSQSvMUHAwLS+U@4a4Jt8Ux|8%}w1;=|?mM2^sd3b0#+!84SP?~2MJ}<79
z^Qm%B>)t^y33z~}9E6a`ts?xYw+B{8B+xw1DM&2eqo~};KeZ1~fCOcO0a<8TNr=?$
z>4NOBIs{n3BDoVS%aV)Q~@Zd6QcUJz)PhBvG)X(C_KU+y
u46NbMJs76&=pGA7AMSUf_hEQHjsHI;nkOQ+Rm@!b
literal 0
HcmV?d00001
diff --git a/Resources/Textures/White/Interface/give_item.rsi/give_item.png b/Resources/Textures/White/Interface/give_item.rsi/give_item.png
new file mode 100644
index 0000000000000000000000000000000000000000..26bc88fb2294b62811e09e41055bbe34b2268882
GIT binary patch
literal 7276
zcmeHMc{J4P{~v@RJ6%~ahEN)_Gc#GTkIGJVHOmKMnPFzE6{W1DE6Q3*6xB%Ck|l(Y
zEJ=Rh_~N>AzcJMR@Xf0b{gmTvh2;Sb)C6E
z8=^7_kAE?Ws^$fQUR@uQnx4_>ApLlm8mG#U_
z|G?v0=Td8;#5T@#w4M)Tguh1U*M_!05OezujHt)JrYeJAHMf%7jHt*j6U8SuL4
z5up&Wj}q8kc|U>O7_PWYzj{ib(nVStaZ;Eh8lxAsP}V$E@$g8wSX=!)u=XG!Dk)Uz~d2CAc2WZ99?iCx%H$Z$dtRS~Ud9}@SP(A!}
zQhnkU+d5SGr;4F-%-(4
zLeb2u!Qa(z{hG!SEykDn&dMW&(1r9s454Otd`X(1()~@1buQ?OQ27?XM}wj+)Ok|xbnRn{ff6w{0$Qi%i!inrlp!M4nADw+Ny2;KDMUUsrYcvp93
zo=JF$rlyqSe)|*i1G>hBX9pKL*}enO*7=4xtXRXqNancAh=!-KLnwp0@7PsSSd>(K_^5f~oAM2%-nh{IH0KMZ
zAD;L)7Ff`Z9X_1)-gHw3yj1EgM%K+OO?j|M4Myf`ukSI6?F!nLVkOseEd=qblp1k!
zI`0#1{0?+)$ldc*2enyS7EZUj7FqY$vf5f+vZuETq?tB24_=xHcy^hp>~ihW#FKYE
z&jgp=`^c5aeVTK%7D#H{(~G#LOgLh?bbald@@>&$Rh7jiv(Hb8+ota!Z@pxIs5!B~
zyHKclR$O+CX*=K2lb7@5L8s&&U9VUWVZTxM%Aa@pK?Jv2`}t{JKkL^1AVlZ=%mcG6
zr_D2}Re7o!#2yB}+^mpAGJYB#HyRXzjspCWF_yeGI@P}0++Yb%>0=(;WWTw6(6m9_
zdWN8we?r489iSaCa#YfoN6Et}MR0#}-}xo(o$iz%d3S6!tK7r7=|&hI-PDwv^b+Qgg@ZB0avU}t64*Lvy+lVE@=+_;+6~?vgxPNFIq6S^4))D+M&-rI&kE;I(`I8YQJeYva^zAxao+6hE*l-&tDXZ*
zGoxC~%zR}X!-mc6={s5QjYi2`2?81o`Tg8*Nddd$>uV0NKHpK*RXa-vcYe5*x7dPY
zTq;rx_*dMmJ=WPN(LQeQ^pbSsG;*NN=BCNOsjf=XIADbcmr31p#D&%zxB+iBaE1uJu|}U
zyDYw?Z*Fx^qrY8Hok$w#^3{D(@QK^uRz;FsYd@*z&|TyivcITeyv`O-k_sokSg4dZ
z@;o`WNNhH{YixPrn52hWj%7=#;O0CekEGtin~$P>l=-#^E(pL?0r3eJ9gVj6>J!o>
zzUXy(u&MduhR=_*X3Do+8h$uGI+-bW%lc-ntYxu$(go=~(!}?&rpCUB?@OwNUYr%$
zx+Ptj`-BsmB6HdIq(NcrsDpuAGyS@~9xXE}DRoDws(Ti}WJ0Z~=9Egl^@TLEgyu2*
z()zE2smS=8t68r@QO^7h$peW34XP;8!QuFHYD!5>lx(?Az9Vf3it4b&j@wffhrK^8
zGzY2wy#_-J>mrVRhTW<02Db?_)4dr)B119vPwYdlQq5Ux=GE#YC#q#`sVZCsf@H46^ptzWv~1-Zd7Ge<~tr
zD?{y;G`*x%d~!JMYitROTS_{4=Y(ovbF+2rg)qGjiHBlzBU@!mU#Dp*h2;jsxrrxm
zjJ=V+A3c|q5J5cG<0bo9d41H0t#xg@F6J{2aL>LKcB*%H9MXqMVGjDc_139{fNXfPvCmVkA@@s3
z;qz`TuZ;L8%y^FStkNruHTmUyqH095L$RWgy;D-zr0DZWOE9OM21zL=m(=f$`wUEK
zp074HccpYRXK=86quk>f`UUe!Ds>02bs^NRCiS69nap*v2cbfI7xNE?ap^}5C)1%j
zZKYo1ec@d)LBKkm&}%aa;w6)_(q4O9JaIf_`IX`HmPSX7L9g_W#?jX+T(*y?w|Wfb
z%F{}Qy_0j5nC2Z<^Ns;rb1%^jIrTk={mcG_{9%(a=23Gp7q95MH7`Ecnx%Dm?(Nr>
zhs>^=j>rgCxsHeoFNVTSJ=2C=<>tZ~kIV$b;~!sa=4O9;5Z{^7y_VEvR^pa)oA+$a
z)dY*kxEMQHtLCnw?PBN@C0xmaV7OmwS&w1C=8YvgT<%=i87nX904
zq+_h@%P+`CXvX7dg;pC(OcgFBnqIdV5Go>O)Vw$M=}FG7#0y>H7mV6)RaC39idZQW
zJuDVqdL;G2u~T7t?R+FgV_Lr8=W-hNFYp)@H93?9DA?fINp~+$)T&_uo(~S|udk0|
zBxFYDmM6!0AAD-B^vL6=clg3L7rmXGv_saaIY1RzqFBnB=}Me9I3ljOQ|K+>ZHD1*
zs*z(=)ARBZ(T%)a>o-o0DFnqkZ!5b#bcgFx*ks>CHhrYAx3!WR_-urmwv`)Xy>w@v
zm77)2*~dtVvx&mG_+;A#ft}aIb3Ig=O;S4ZHZ3W)oP(zCz9xcX-OpPulQJr}eB^m4
z?p>-B!wxA_R@e8jWvzH0|CHn4l77X^;=17|foo-#>fHIf^eu|D%il)^u0
zDu;Lk&T-n3722$KwN4yfz${+c)*U6xDXL=oof&=4Lwd+fV;nkSysvCWzu%_0S^u+x
zb$Uskzd@kMtk|5|Wh)3o)R7L}hMX;T6RFGqEi#Qs0kpyb0>Rr71fpvY7D%T00URg=
z@TN2LU~|=VFesg-2Xn$%A}s@r0UtUkf(6(|SUFH5{HO#P%s^jAH;f1Z1OOZ|G%UcM
z!6t_3!B%mJ;PZ+Y0fVlZaQyUO&XzV%VKY7lP`tXkJ7+6SE%>
z;7AYV!{G!H5s1*xP_0mm7L(QDc`V3z-CIy5Q*@CO1wQ#Lp&>Mv88TUgrs
z^jM+5n;sCj>IIVh7fTM^>rb-&65EPqHJx7r0o{M%{>A!_+*gf3D@#kF36mPU5}t*L
z9&BZPB8^F<(}=5&NHn1Bg~p-bcp424$6~#_-~>Ea7&Jhj&~RiOJQU*P??sNHPwIqtP&M3JObxW63lu9FO!O!!bxam5io&;W1dkFA&x&I@pzD|6ik8
zfuey>6u=9G)&{)bcnl7VhD;{FDR>G24p1;u3YI`aVriIFC>oV$#$*MM!E(|A$ld@V
zkm0?mSRtHfXk(!VLu(=bl-T%_IbNUxxCZD98Z(srr^%mrn
zg06V}R9k`zibm#;O~@Po1Vy5;M5Hzmg?B(AiAXdNht@=5h{#{;nKZgr`2T0UvV5Sr
z--}M7v%&epS4H2~lsypiee`|kPhVY3Q0VHiAd;!yQ(%)r0NQGtAl7#k)rZXR2EhH}
zN5lRhr~j8`K+&|dv3M#Dj==(Gu%E#7O`%b};5ZD0ibN8~C?xQ`f*{x`UBfR=>=q5#PFlw!Pg+jiF}pw8X_H{*{y-