diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index 639f326f7f4..d90b0b5bda3 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -84,6 +84,7 @@ public static void SetupContexts(IInputContextContainer contexts)
human.AddFunction(ContentKeyFunctions.Arcade1);
human.AddFunction(ContentKeyFunctions.Arcade2);
human.AddFunction(ContentKeyFunctions.Arcade3);
+ human.AddFunction(ContentKeyFunctions.OfferItem); // Corvax-Next-Offer
// 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 24be904e061..fe5a7adafbb 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -184,7 +184,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action
+
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
index 79000af58c6..835c250c6d3 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._CorvaxNext.NextVars; // Corvax-Next-Offer
using Content.Shared.CCVar;
using Content.Shared.HUD;
using Robust.Client.AutoGenerated;
@@ -52,7 +53,7 @@ public MiscTab()
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
-
+ Control.AddOptionCheckBox(NextVars.OfferModeIndicatorsPointShow, ShowOfferModeIndicatorsCheckBox); // Corvax-Next-Offer
Control.Initialize();
}
}
diff --git a/Content.Client/_CorvaxNext/OfferItem/OfferItemIndicatorsOverlay.cs b/Content.Client/_CorvaxNext/OfferItem/OfferItemIndicatorsOverlay.cs
new file mode 100644
index 00000000000..a233ec833ec
--- /dev/null
+++ b/Content.Client/_CorvaxNext/OfferItem/OfferItemIndicatorsOverlay.cs
@@ -0,0 +1,65 @@
+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._CorvaxNext.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/_CorvaxNext/Misc/give_item.rsi"), "give_item"));
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ return _offer.IsInOfferMode() && 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 = Math.Min(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(7);
+
+ 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/_CorvaxNext/OfferItem/OfferItemSystem.cs b/Content.Client/_CorvaxNext/OfferItem/OfferItemSystem.cs
new file mode 100644
index 00000000000..949ce1addbc
--- /dev/null
+++ b/Content.Client/_CorvaxNext/OfferItem/OfferItemSystem.cs
@@ -0,0 +1,44 @@
+using Content.Shared._CorvaxNext.OfferItem;
+using Content.Shared._CorvaxNext.NextVars;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.Player;
+using Robust.Shared.Configuration;
+
+namespace Content.Client._CorvaxNext.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()
+ {
+ base.Initialize();
+ Subs.CVar(_cfg, NextVars.OfferModeIndicatorsPointShow, OnShowOfferIndicatorsChanged, true);
+ }
+
+ public override void Shutdown()
+ {
+ _overlayManager.RemoveOverlay();
+ base.Shutdown();
+ }
+
+ public bool IsInOfferMode()
+ {
+ var entity = _playerManager.LocalEntity;
+
+ return entity is not null && 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/_CorvaxNext/OfferItemSystem.cs b/Content.Server/_CorvaxNext/OfferItemSystem.cs
new file mode 100644
index 00000000000..b21512ec065
--- /dev/null
+++ b/Content.Server/_CorvaxNext/OfferItemSystem.cs
@@ -0,0 +1,48 @@
+using Content.Shared.Alert;
+using Content.Shared._CorvaxNext.OfferItem;
+using Content.Shared.Hands.Components;
+
+namespace Content.Server._CorvaxNext.OfferItem;
+
+public sealed class OfferItemSystem : SharedOfferItemSystem
+{
+ [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+
+ private float _offerAcc = 0;
+ private const float OfferAccMax = 3f;
+
+ public override void Update(float frameTime)
+ {
+ _offerAcc += frameTime;
+
+ if (_offerAcc >= OfferAccMax)
+ _offerAcc -= OfferAccMax;
+ else
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var offerItem, out var hands))
+ {
+ if (hands.ActiveHand is null)
+ continue;
+
+ if (offerItem.Hand is not null && hands.Hands[offerItem.Hand].HeldEntity is null)
+ if (offerItem.Target is not null)
+ {
+ UnReceive(offerItem.Target.Value, offerItem: offerItem);
+ offerItem.IsInOfferMode = false;
+ Dirty(uid, offerItem);
+ }
+ else
+ UnOffer(uid, offerItem);
+
+ if (!offerItem.IsInReceiveMode)
+ {
+ _alertsSystem.ClearAlert(uid, OfferAlert);
+ continue;
+ }
+
+ _alertsSystem.ShowAlert(uid, OfferAlert);
+ }
+ }
+}
diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs
index 863d9da970f..b34fdab30f2 100644
--- a/Content.Shared/Input/ContentKeyFunctions.cs
+++ b/Content.Shared/Input/ContentKeyFunctions.cs
@@ -56,6 +56,7 @@ public static class ContentKeyFunctions
public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI";
public static readonly BoundKeyFunction ToggleFullscreen = "ToggleFullscreen";
public static readonly BoundKeyFunction Point = "Point";
+ public static readonly BoundKeyFunction OfferItem = "OfferItem"; // Corvax-Next-Offer
public static readonly BoundKeyFunction ZoomOut = "ZoomOut";
public static readonly BoundKeyFunction ZoomIn = "ZoomIn";
public static readonly BoundKeyFunction ResetZoom = "ResetZoom";
diff --git a/Content.Shared/_CorvaxNext/Alert/Click/AcceptingOffer.cs b/Content.Shared/_CorvaxNext/Alert/Click/AcceptingOffer.cs
new file mode 100644
index 00000000000..0b7be4ccb08
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Alert/Click/AcceptingOffer.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Alert;
+
+namespace Content.Shared._CorvaxNext.Alert.Click;
+
+///
+/// Accepting the offer and receive item
+///
+public sealed partial class AcceptOfferAlertEvent : BaseAlertEvent;
diff --git a/Content.Shared/_CorvaxNext/NextVars.cs b/Content.Shared/_CorvaxNext/NextVars.cs
new file mode 100644
index 00000000000..5d232025ae7
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/NextVars.cs
@@ -0,0 +1,17 @@
+using Robust.Shared.Configuration;
+
+namespace Content.Shared._CorvaxNext.NextVars;
+
+///
+/// Corvax modules console variables
+///
+[CVarDefs]
+// ReSharper disable once InconsistentNaming
+public sealed class NextVars
+{
+ ///
+ /// Offer item.
+ ///
+ public static readonly CVarDef OfferModeIndicatorsPointShow =
+ CVarDef.Create("hud.offer_mode_indicators_point_show", true, CVar.ARCHIVE | CVar.CLIENTONLY);
+}
diff --git a/Content.Shared/_CorvaxNext/OfferItem/OfferItemComponent.cs b/Content.Shared/_CorvaxNext/OfferItem/OfferItemComponent.cs
new file mode 100644
index 00000000000..3389b947f90
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/OfferItem/OfferItemComponent.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._CorvaxNext.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/_CorvaxNext/OfferItem/SharedOfferItemSystem.Interactions.cs b/Content.Shared/_CorvaxNext/OfferItem/SharedOfferItemSystem.Interactions.cs
new file mode 100644
index 00000000000..2c54c0aa606
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/OfferItem/SharedOfferItemSystem.Interactions.cs
@@ -0,0 +1,74 @@
+using Content.Shared.ActionBlocker;
+using Content.Shared.Hands.Components;
+using Content.Shared.Input;
+using Robust.Shared.Input.Binding;
+using Robust.Shared.Player;
+
+namespace Content.Shared._CorvaxNext.OfferItem;
+
+public abstract partial class SharedOfferItemSystem
+{
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = 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 (!_timing.IsFirstTimePredicted)
+ return;
+
+ if (session is null)
+ return;
+
+ if (session.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 is null)
+ return;
+
+ offerItem.Item = hands.ActiveHand.HeldEntity;
+
+ if (!offerItem.IsInOfferMode)
+ {
+ if (offerItem.Item is null)
+ {
+ _popup.PopupEntity(Loc.GetString("offer-item-empty-hand"), uid, uid);
+ return;
+ }
+
+ if (offerItem.Hand is null || offerItem.Target is null)
+ {
+ offerItem.IsInOfferMode = true;
+ offerItem.Hand = hands.ActiveHand.Name;
+
+ Dirty(uid, offerItem);
+ return;
+ }
+ }
+
+ if (offerItem.Target is not null)
+ {
+ UnReceive(offerItem.Target.Value, offerItem: offerItem);
+ offerItem.IsInOfferMode = false;
+ Dirty(uid, offerItem);
+ return;
+ }
+
+ UnOffer(uid, offerItem);
+ }
+}
diff --git a/Content.Shared/_CorvaxNext/OfferItem/SharedOfferItemSystem.cs b/Content.Shared/_CorvaxNext/OfferItem/SharedOfferItemSystem.cs
new file mode 100644
index 00000000000..ddcf774aef1
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/OfferItem/SharedOfferItemSystem.cs
@@ -0,0 +1,232 @@
+using Content.Shared.Alert;
+using Content.Shared._CorvaxNext.Alert.Click;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Popups;
+using Robust.Shared.Timing;
+
+namespace Content.Shared._CorvaxNext.OfferItem;
+
+public abstract partial class SharedOfferItemSystem : EntitySystem
+{
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ [ValidatePrototypeId]
+ protected const string OfferAlert = "Offer";
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(SetInReceiveMode);
+ SubscribeLocalEvent(OnMove);
+
+ InitializeInteractions();
+
+ SubscribeLocalEvent(OnClickAlertEvent);
+ }
+
+ private void OnClickAlertEvent(Entity ent, ref AcceptOfferAlertEvent ev)
+ {
+ if (ev.Handled)
+ return;
+
+ if (ev.AlertId != OfferAlert)
+ return;
+
+ ev.Handled = true;
+
+ Receive(ent!);
+ }
+ ///
+ /// Accepting the offer and receive item
+ ///
+ public void Receive(Entity ent)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ if (!TryComp(ent.Comp.Target, out var offerItem))
+ return;
+
+ if (offerItem.Hand is null)
+ return;
+
+ if (ent.Comp.Target is null)
+ return;
+
+ if (!TryComp(ent, out var hands))
+ return;
+
+ if (offerItem.Item is not null)
+ {
+ if (!_hands.TryPickup(ent, offerItem.Item.Value, handsComp: hands))
+ {
+ _popup.PopupClient(Loc.GetString("offer-item-full-hand"), ent, ent);
+ return;
+ }
+
+ _popup.PopupClient(Loc.GetString("offer-item-give",
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(ent, EntityManager))), ent.Comp.Target.Value, ent.Comp.Target.Value);
+
+ _popup.PopupPredicted(Loc.GetString("offer-item-give-other",
+ ("user", Identity.Entity(ent.Comp.Target.Value, EntityManager)),
+ ("item", Identity.Entity(offerItem.Item.Value, EntityManager)),
+ ("target", Identity.Entity(ent, EntityManager)))
+ , ent.Comp.Target.Value, ent);
+ }
+
+ offerItem.Item = null;
+ Dirty(ent);
+ UnReceive(ent, ent, offerItem);
+ }
+
+ 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.PopupPredicted(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.PopupClient(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 is null ||
+ _transform.InRange(args.NewPosition,
+ 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 is null)
+ return;
+
+ if (TryComp(component.Target, out var offerItem) && component.Target is not null)
+ {
+ if (component.Item is not null)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ {
+ _popup.PopupClient(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 is not null)
+ if (!_timing.IsFirstTimePredicted)
+ {
+ _popup.PopupClient(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 is null && !TryComp(uid, out component))
+ return;
+
+ if (offerItem is null && !TryComp(component.Target, out offerItem))
+ return;
+
+ if (!TryComp(uid, out var hands) || hands.ActiveHand is null ||
+ component.Target is null)
+ return;
+
+ if (offerItem.Item is not null)
+ {
+ _popup.PopupClient(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 is not null && Resolve(entity.Value, ref component, false) && component.IsInOfferMode;
+ }
+}
diff --git a/Resources/Locale/ru-RU/_corvaxnext/interaction/offer-item-system.ftl b/Resources/Locale/ru-RU/_corvaxnext/interaction/offer-item-system.ftl
new file mode 100644
index 00000000000..94dbe640ef8
--- /dev/null
+++ b/Resources/Locale/ru-RU/_corvaxnext/interaction/offer-item-system.ftl
@@ -0,0 +1,13 @@
+offer-item-empty-hand = У вас в руках ничего нет!
+
+offer-item-full-hand = Ваши руки заняты!
+
+offer-item-try-give = Вы предлагаете {THE($item)} {$target}
+offer-item-try-give-target = {$user} предлагает вам {THE($item)}
+
+offer-item-give = Вы отдали {THE($item)} {$target}
+offer-item-give-other = {$user} отдал {THE($item)} {$target}
+offer-item-give-target = {$user} отдал вам {THE($item)}
+
+offer-item-no-give = Вы перестаёте предлагать {THE($item)} {$target}
+offer-item-no-give-target = {$user} больше не предлагает вам {THE($item)}
diff --git a/Resources/Locale/ru-RU/alerts/alerts.ftl b/Resources/Locale/ru-RU/alerts/alerts.ftl
index f73606a7b93..e77dd9c14a0 100644
--- a/Resources/Locale/ru-RU/alerts/alerts.ftl
+++ b/Resources/Locale/ru-RU/alerts/alerts.ftl
@@ -74,3 +74,5 @@ alerts-revenant-essence-name = Эссенция
alerts-revenant-essence-desc = Сила душ. Поддерживает вас и используется при использовании способностей. Медленно восстанавливается с течением времени.
alerts-revenant-corporeal-name = Материальность
alerts-revenant-corporeal-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 33a779e7cc1..68984b39c6d 100644
--- a/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl
@@ -263,3 +263,5 @@ ui-options-colorblind-friendly = Режим для дальтоников
ui-options-reduced-motion = Снижение интенсивности визуальных эффектов
ui-options-chat-window-opacity = Прозрачность окна чата
ui-options-screen-shake-intensity = Интенсивность дрожания экрана
+ui-options-show-offer-mode-indicators = Показывать индикатор передачи предмета
+ui-options-function-offer-item = Передать что-либо
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index 80fcc44a559..91d876bf93b 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -24,6 +24,7 @@
- category: Thirst
- alertType: Magboots
- alertType: Pacified
+ - alertType: Offer # Corvax-Next-Offer
- type: entity
id: AlertSpriteView
@@ -91,6 +92,15 @@
name: alerts-on-fire-name
description: alerts-on-fire-desc
+- type: alert # Corvax-Next-Offer
+ id: Offer
+ clickEvent: !type:AcceptOfferAlertEvent
+ icons:
+ - sprite: /Textures/_CorvaxNext/Alerts/offer_item.rsi
+ state: offer_item
+ name: alerts-offer-name
+ description: alerts-offer-desc
+
- type: alert
id: Cold
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index 100a0ced842..9e8a070559d 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -223,6 +223,7 @@
id: BaseMobSpeciesOrganic
abstract: true
components:
+ - type: OfferItem # Corvax-Next-Offer
- type: Barotrauma
damage:
types:
diff --git a/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/meta.json b/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/meta.json
new file mode 100644
index 00000000000..0513aeb3e06
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Original icon taken from https://github.com/ss220-space/Paradise/blob/master220/icons/mob/screen_alert.dmi, modified by Mocho, resprited by AwareFoxy",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "offer_item"
+ }
+ ]
+}
diff --git a/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/offer_item.png b/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/offer_item.png
new file mode 100644
index 00000000000..6215d2e53ff
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/offer_item.png differ
diff --git a/Resources/Textures/_CorvaxNext/Misc/give_item.rsi/give_item.png b/Resources/Textures/_CorvaxNext/Misc/give_item.rsi/give_item.png
new file mode 100644
index 00000000000..26bc88fb229
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Misc/give_item.rsi/give_item.png differ
diff --git a/Resources/Textures/_CorvaxNext/Misc/give_item.rsi/meta.json b/Resources/Textures/_CorvaxNext/Misc/give_item.rsi/meta.json
new file mode 100644
index 00000000000..94cd6fa2e1e
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Misc/give_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/misc/mouse_icons/give_item.dmi",
+ "size": {
+ "x": 64,
+ "y": 64
+ },
+ "states": [
+ {
+ "name": "give_item"
+ }
+ ]
+}
diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml
index 3b8158b7c7a..153d7c35b61 100644
--- a/Resources/keybinds.yml
+++ b/Resources/keybinds.yml
@@ -584,3 +584,6 @@ binds:
type: State
key: MouseRight
canFocus: true
+- function: OfferItem # Corvax-Next-Offer
+ type: State
+ key: F