This commit is contained in:
Zekins3366
2025-08-29 12:59:01 +03:00
parent 4fb5dac7e2
commit fe76f4f7d9
19 changed files with 454 additions and 1 deletions

View File

@@ -65,6 +65,7 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.OpenEmotesMenu);
human.AddFunction(ContentKeyFunctions.OpenInteractionMenu); /// Corvax-Wega
human.AddFunction(ContentKeyFunctions.Strangle); /// Corvax-Wega
human.AddFunction(ContentKeyFunctions.OfferItem); /// Corvax-Wega-Offer
human.AddFunction(ContentKeyFunctions.ActivateItemInWorld);
human.AddFunction(ContentKeyFunctions.ThrowItemInHand);
human.AddFunction(ContentKeyFunctions.AltActivateItemInWorld);

View File

@@ -186,6 +186,7 @@ namespace Content.Client.Options.UI.Tabs
AddButton(ContentKeyFunctions.MoveStoredItem);
AddButton(ContentKeyFunctions.RotateStoredItem);
AddButton(ContentKeyFunctions.SaveItemLocation);
AddButton(ContentKeyFunctions.OfferItem); /// Corvax-Wega-Offer
AddHeader("ui-options-header-interaction-adv");
AddButton(ContentKeyFunctions.SmartEquipBackpack);

View File

@@ -0,0 +1,38 @@
using Content.Client.Resources;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
namespace Content.Client.Offer;
public sealed class OfferItemIndicatorsOverlay : Overlay
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
private readonly Texture _indicatorTexture;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public OfferItemIndicatorsOverlay()
{
IoCManager.InjectDependencies(this);
var resourceCache = IoCManager.Resolve<IResourceCache>();
_indicatorTexture = resourceCache.GetTexture("/Textures/_Wega/Interface/Misc/give_item.rsi/give_item.png");
}
protected override void Draw(in OverlayDrawArgs args)
{
var player = _player.LocalEntity;
if (player == null)
return;
var screen = args.ScreenHandle;
var mousePos = _inputManager.MouseScreenPosition.Position;
screen.DrawTexture(_indicatorTexture, mousePos - _indicatorTexture.Size / 2, Color.White);
}
}

View File

@@ -0,0 +1,73 @@
using Content.Shared.Offer;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
namespace Content.Client.Offer;
public sealed class OfferItemSystem : SharedOfferItemSystem
{
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IPlayerManager _player = default!;
private OfferItemIndicatorsOverlay? _overlayInstance;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OfferGiverComponent, ComponentStartup>(OnGiverStartup);
SubscribeLocalEvent<OfferGiverComponent, ComponentShutdown>(OnGiverShutdown);
SubscribeLocalEvent<OfferGiverComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<OfferGiverComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<OfferGiverComponent, AfterAutoHandleStateEvent>(OnComponentStateUpdated);
}
private void OnGiverStartup(EntityUid uid, OfferGiverComponent component, ComponentStartup args)
{
UpdateOverlay(uid, component);
}
private void OnGiverShutdown(EntityUid uid, OfferGiverComponent component, ComponentShutdown args)
{
UpdateOverlay(uid, component);
}
private void OnPlayerAttached(EntityUid uid, OfferGiverComponent component, LocalPlayerAttachedEvent args)
{
UpdateOverlay(uid, component);
}
private void OnPlayerDetached(EntityUid uid, OfferGiverComponent component, LocalPlayerDetachedEvent args)
{
UpdateOverlay(uid, component);
}
private void OnComponentStateUpdated(EntityUid uid, OfferGiverComponent component, ref AfterAutoHandleStateEvent args)
{
UpdateOverlay(uid, component);
}
private void UpdateOverlay(EntityUid uid, OfferGiverComponent component)
{
if (uid != _player.LocalEntity)
return;
if (component.IsOffering)
{
if (_overlayInstance == null)
{
_overlayInstance = new();
_overlay.AddOverlay(_overlayInstance);
}
}
else
{
if (_overlayInstance != null)
{
_overlay.RemoveOverlay(_overlayInstance);
_overlayInstance = null;
}
}
}
}

View File

@@ -0,0 +1,106 @@
using Content.Shared.Alert;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Offer;
using Content.Shared.Popups;
namespace Content.Server.Offer;
public sealed class OfferItemSystem : SharedOfferItemSystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<RequestToggleOfferEvent>(OnRequestToggleOffer);
SubscribeLocalEvent<OfferGiverComponent, InteractUsingEvent>(OnGiverInteractUsing);
SubscribeLocalEvent<OfferReceiverComponent, AcceptOfferAlertEvent>(OnReceiverAlertAcceptOffer);
}
private void OnRequestToggleOffer(RequestToggleOfferEvent msg)
{
TryToggleOfferMode(GetEntity(msg.Player));
}
private void OnGiverInteractUsing(Entity<OfferGiverComponent> ent, ref InteractUsingEvent args)
{
if (!TryComp<OfferGiverComponent>(args.User, out var offer))
return;
if (!offer.IsOffering || args.User == args.Target || !HasComp<HandsComponent>(args.Target))
return;
if (!_transform.InRange(args.User, args.Target, offer.MaxOfferDistance))
return;
offer.Target = args.Target;
offer.IsOffering = false;
Dirty(args.User, offer);
var receiver = EnsureComp<OfferReceiverComponent>(args.Target);
receiver.Offerer = args.User;
receiver.Item = offer.Item;
Dirty(args.Target, receiver);
_alerts.ShowAlert(args.Target, receiver.Alert);
if (offer.Item is { } item)
{
_popup.PopupEntity(Loc.GetString("offer-item-try-give",
("item", Name(item)), ("target", Identity.Name(args.Target, EntityManager, args.User))), args.User, args.User);
_popup.PopupEntity(Loc.GetString("offer-item-try-give-target",
("user", Identity.Name(args.User, EntityManager, args.Target)), ("item", Name(item))), args.Target, args.Target);
}
}
private void OnReceiverAlertAcceptOffer(EntityUid uid, OfferReceiverComponent component, AcceptOfferAlertEvent args)
{
if (args.AlertId != component.Alert || component.Offerer is not { } offerer)
return;
TryAcceptOffer(uid, offerer);
}
public bool TryAcceptOffer(EntityUid acceptor, EntityUid offerer)
{
if (!TryComp<OfferGiverComponent>(offerer, out var offerComp) || !HasComp<HandsComponent>(acceptor))
return false;
if (_hands.GetEmptyHandCount(acceptor) == 0)
{
_popup.PopupEntity(Loc.GetString("offer-item-full-hand"), acceptor, acceptor);
CancelOffer(offerer, offerComp);
return false;
}
if (offerComp.Item is { } item)
{
if (_hands.TryPickupAnyHand(acceptor, item))
{
_popup.PopupEntity(Loc.GetString("offer-item-give",
("item", Name(item)), ("target", Identity.Name(acceptor, EntityManager, offerer))), offerer, offerer);
_popup.PopupEntity(Loc.GetString("offer-item-give-target",
("item", Name(item)), ("target", Identity.Name(offerer, EntityManager, acceptor))), acceptor, acceptor);
}
else
{
_popup.PopupEntity(Loc.GetString("offer-item-no-give",
("item", Name(item)), ("target", Identity.Name(acceptor, EntityManager, offerer))), offerer, offerer);
}
}
CancelOffer(offerer, offerComp);
return true;
}
}

View File

@@ -29,6 +29,7 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction OpenEmotesMenu = "OpenEmotesMenu";
public static readonly BoundKeyFunction OpenInteractionMenu = "OpenInteractionMenu"; /// Corvax-Wega
public static readonly BoundKeyFunction Strangle = "Strangle"; /// Corvax-Wega
public static readonly BoundKeyFunction OfferItem = "OfferItem"; /// Corvax-Wega-Offer
public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu";
public static readonly BoundKeyFunction OpenGuidebook = "OpenGuidebook";
public static readonly BoundKeyFunction OpenInventoryMenu = "OpenInventoryMenu";

View File

@@ -0,0 +1,20 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Offer;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedOfferItemSystem))]
public sealed partial class OfferGiverComponent : Component
{
[DataField, AutoNetworkedField]
public bool IsOffering = false;
[DataField, AutoNetworkedField]
public EntityUid? Item;
[DataField, AutoNetworkedField]
public EntityUid? Target;
[DataField, AutoNetworkedField]
public float MaxOfferDistance = 2f;
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Alert;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Offer;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedOfferItemSystem))]
public sealed partial class OfferReceiverComponent : Component
{
[DataField]
public EntityUid? Offerer;
[DataField]
public EntityUid? Item;
public ProtoId<AlertPrototype> Alert = "Offer";
}

View File

@@ -0,0 +1,145 @@
using Content.Shared.Alert;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Interaction.Components;
using Content.Shared.Popups;
using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
namespace Content.Shared.Offer;
public abstract partial class SharedOfferItemSystem : EntitySystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OfferGiverComponent, MoveEvent>(OnGiverMoved);
SubscribeLocalEvent<OfferGiverComponent, DidUnequipHandEvent>(OnGiverItemUnequipped);
SubscribeLocalEvent<OfferGiverComponent, EntityTerminatingEvent>(OnGiverTerminating);
SubscribeLocalEvent<OfferReceiverComponent, MoveEvent>(OnReceiverMoved);
SubscribeLocalEvent<OfferReceiverComponent, EntityTerminatingEvent>(OnReceiverTerminating);
SubscribeLocalEvent<OfferReceiverComponent, ComponentShutdown>(OnReceiverShutdown);
CommandBinds.Builder
.Bind(ContentKeyFunctions.OfferItem, InputCmdHandler.FromDelegate(OnOfferItemCommand))
.Register<SharedOfferItemSystem>();
}
private void OnOfferItemCommand(ICommonSession? session)
{
if (session?.AttachedEntity is not { } player)
return;
RaiseNetworkEvent(new RequestToggleOfferEvent(GetNetEntity(player)));
}
public bool TryToggleOfferMode(EntityUid uid, OfferGiverComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return false;
if (component.IsOffering || component.Target != null)
{
_popup.PopupEntity(Loc.GetString("offer-cancel-offer"), uid, uid);
CancelOffer(uid, component);
return true;
}
if (!_hands.TryGetActiveItem(uid, out var item))
{
_popup.PopupEntity(Loc.GetString("offer-item-empty-hand"), uid, uid);
return false;
}
// You will not be able to transfer such items.
if (HasComp<UnremoveableComponent>(item) || HasComp<DeleteOnDropComponent>(item))
return false;
component.IsOffering = true;
component.Item = item;
Dirty(uid, component);
return true;
}
public void CancelOffer(EntityUid uid, OfferGiverComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return;
component.IsOffering = false;
component.Item = null;
if (component.Target is { } target && HasComp<OfferReceiverComponent>(target))
RemoveReceiverComponent(target, component);
Dirty(uid, component);
}
private void RemoveReceiverComponent(EntityUid uid, OfferGiverComponent component)
{
component.Target = null;
RemCompDeferred<OfferReceiverComponent>(uid);
}
// TODO: Плохо что оно постоянно вызывается, возможно стоит переделеать в будущем.
private void OnGiverMoved(EntityUid uid, OfferGiverComponent component, MoveEvent args)
{
if (component.Target != null && !_transform.InRange(uid, component.Target.Value, component.MaxOfferDistance))
CancelOffer(uid, component);
}
private void OnGiverItemUnequipped(EntityUid uid, OfferGiverComponent component, DidUnequipHandEvent args)
{
if (args.Unequipped == component.Item)
CancelOffer(uid, component);
}
private void OnGiverTerminating(EntityUid uid, OfferGiverComponent component, ref EntityTerminatingEvent args)
{
CancelOffer(uid, component);
}
private void OnReceiverMoved(EntityUid uid, OfferReceiverComponent component, MoveEvent args)
{
if (component.Offerer != null && TryComp<OfferGiverComponent>(component.Offerer, out var giver)
&& !_transform.InRange(uid, component.Offerer.Value, giver.MaxOfferDistance))
CancelOffer(component.Offerer.Value, giver);
}
private void OnReceiverTerminating(EntityUid uid, OfferReceiverComponent component, ref EntityTerminatingEvent args)
{
if (component.Offerer is { } offerer && TryComp<OfferGiverComponent>(offerer, out var giver))
CancelOffer(offerer, giver);
}
private void OnReceiverShutdown(EntityUid uid, OfferReceiverComponent component, ref ComponentShutdown args)
{
if (component.Offerer is { } offerer && TryComp<OfferGiverComponent>(offerer, out var giver))
CancelOffer(offerer, giver);
_alerts.ClearAlert(uid, component.Alert);
}
}
[Serializable, NetSerializable]
public sealed class RequestToggleOfferEvent : EntityEventArgs
{
public NetEntity Player { get; }
public RequestToggleOfferEvent(NetEntity player)
{
Player = player;
}
}
public sealed partial class AcceptOfferAlertEvent : BaseAlertEvent;

View File

@@ -2,3 +2,5 @@ alerts-vampire-blood-name = Кровь
alerts-vampire-blood-desc = Накопленная жизненная сила вампира позволяющая ему использовать сверх естественные способности.
alerts-strangle-name = Душат
alerts-strangle-desc = Вас [color=red]ДУШАТ[/color]. Щёлкните по иконке, чтобы попытаться выбраться
alerts-offer-name = Предложение
alerts-offer-desc = Кто-то предлагает вам взять предмет.

View File

@@ -1,3 +1,3 @@
ui-options-function-open-interaction-menu = Открыть меню взаимодействия
ui-options-function-toggle-crawling = Ползать/Встать
ui-options-function-strangle = Душить
ui-options-function-offer-item = Переключить режим передачи предмета

View File

@@ -0,0 +1,8 @@
offer-item-empty-hand = Чтобы предложить предмет, нужно держать его в руке!
offer-cancel-offer = Вы отменили передачу предмета.
offer-item-try-give = Вы предложили {$item} {$target}.
offer-item-try-give-target = {$user} предлагает вам {$item}.
offer-item-give = Вы передали {$item} {$target}.
offer-item-give-target = {$target} передал вам {$item}.
offer-item-no-give = Не удалось передать {$item} {$target}.
offer-item-full-hand = Ваши руки заняты.

View File

@@ -175,6 +175,7 @@
- type: SSDIndicator
- type: StandingState
- type: SpeechSynthesis # Corvax-Wega-Barks
- type: OfferGiver # Corvax-Wega-Offer
# - type: Dna # Corvax-Wega
- type: MindContainer
showExamineInfo: true

View File

@@ -0,0 +1,8 @@
- type: alert
id: Offer
clickEvent: !type:AcceptOfferAlertEvent
icons:
- sprite: /Textures/_Wega/Interface/Alerts/offer.rsi
state: offer
name: alerts-offer-name
description: alerts-offer-desc

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken https://github.com/ss220-space/Paradise/blob/master220/icons/misc/mouse_icons/give_item.dmi and resprite by zekins3366",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "offer"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Sprite taken from https://github.com/ss220-space/Paradise/blob/master220/icons/misc/mouse_icons/give_item.dmi",
"size": {
"x": 64,
"y": 64
},
"states": [
{
"name": "give_item"
}
]
}

View File

@@ -201,6 +201,9 @@ binds:
- function: Strangle
type: State
key: J
- function: OfferItem
type: State
key: F
# Corvax-Wega-end
- function: TextCursorSelect
# TextCursorSelect HAS to be above ExamineEntity