From f51d4aef47782e5ac271c754a99f2d81e086f586 Mon Sep 17 00:00:00 2001
From: Babaev <129369024+babaevlsdd@users.noreply.github.com>
Date: Sun, 9 Jun 2024 20:01:25 +0600
Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?=
=?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20?=
=?UTF-8?q?=D0=BB=D1=8E=D0=B4=D0=B5=D0=B9=20=D0=BD=D0=B0=20=D1=80=D1=83?=
=?UTF-8?q?=D0=BA=D0=B8=20(#31)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* carry
* fix
* carriable update
* Removed carriable from vox, slime
---------
Co-authored-by: Vigers Ray <60344369+VigersRay@users.noreply.github.com>
---
.../Resist/EscapeInventorySystem.cs | 2 +-
.../Carrying/BeingCarriedComponent.cs | 11 +
.../_Sunrise/Carrying/CarriableComponent.cs | 17 +
.../_Sunrise/Carrying/CarryingComponent.cs | 11 +
.../_Sunrise/Carrying/CarryingSystem.cs | 405 ++++++++++++++++++
.../PseudoItem/AllowsSleepInsideComponent.cs | 9 +
.../Item/PseudoItem/PseudoItemSystem.cs | 213 +++++++++
.../_Sunrise/Carrying/CarryDoAfterEvent.cs | 9 +
.../_Sunrise/Carrying/CarryingDoAfterEvent.cs | 12 +
.../Carrying/CarryingSlowdownComponent.cs | 28 ++
.../Carrying/CarryingSlowdownSystem.cs | 47 ++
.../_Sunrise/Cloning/ITransferredByCloning.cs | 9 +
.../Item/Components/PseudoItemComponent.cs | 19 +
.../Item/PseudoItemInsertDoAfterEvent.cs | 10 +
Resources/Changelog/ChangelogSunrise.yml | 10 +
.../Locale/ru-RU/_sunrise/carrying/carry.ftl | 2 +
.../Prototypes/Entities/Mobs/Species/base.yml | 1 +
17 files changed, 814 insertions(+), 1 deletion(-)
create mode 100644 Content.Server/_Sunrise/Carrying/BeingCarriedComponent.cs
create mode 100644 Content.Server/_Sunrise/Carrying/CarriableComponent.cs
create mode 100644 Content.Server/_Sunrise/Carrying/CarryingComponent.cs
create mode 100644 Content.Server/_Sunrise/Carrying/CarryingSystem.cs
create mode 100644 Content.Server/_Sunrise/Item/PseudoItem/AllowsSleepInsideComponent.cs
create mode 100644 Content.Server/_Sunrise/Item/PseudoItem/PseudoItemSystem.cs
create mode 100644 Content.Shared/_Sunrise/Carrying/CarryDoAfterEvent.cs
create mode 100644 Content.Shared/_Sunrise/Carrying/CarryingDoAfterEvent.cs
create mode 100644 Content.Shared/_Sunrise/Carrying/CarryingSlowdownComponent.cs
create mode 100644 Content.Shared/_Sunrise/Carrying/CarryingSlowdownSystem.cs
create mode 100644 Content.Shared/_Sunrise/Cloning/ITransferredByCloning.cs
create mode 100644 Content.Shared/_Sunrise/Item/Components/PseudoItemComponent.cs
create mode 100644 Content.Shared/_Sunrise/Item/PseudoItemInsertDoAfterEvent.cs
create mode 100644 Resources/Locale/ru-RU/_sunrise/carrying/carry.ftl
diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs
index 35c7d0bc65b..679af0d4e7b 100644
--- a/Content.Server/Resist/EscapeInventorySystem.cs
+++ b/Content.Server/Resist/EscapeInventorySystem.cs
@@ -56,7 +56,7 @@ private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent componen
AttemptEscape(uid, container.Owner, component);
}
- private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component, float multiplier = 1f)
+ public void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component, float multiplier = 1f)
{
if (component.IsEscaping)
return;
diff --git a/Content.Server/_Sunrise/Carrying/BeingCarriedComponent.cs b/Content.Server/_Sunrise/Carrying/BeingCarriedComponent.cs
new file mode 100644
index 00000000000..afc78978c86
--- /dev/null
+++ b/Content.Server/_Sunrise/Carrying/BeingCarriedComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Server.Carrying
+{
+ ///
+ /// Stores the carrier of an entity being carried.
+ ///
+ [RegisterComponent]
+ public sealed partial class BeingCarriedComponent : Component
+ {
+ public EntityUid Carrier = default!;
+ }
+}
diff --git a/Content.Server/_Sunrise/Carrying/CarriableComponent.cs b/Content.Server/_Sunrise/Carrying/CarriableComponent.cs
new file mode 100644
index 00000000000..0dabdabbce0
--- /dev/null
+++ b/Content.Server/_Sunrise/Carrying/CarriableComponent.cs
@@ -0,0 +1,17 @@
+using System.Threading;
+
+namespace Content.Server.Carrying
+{
+ [RegisterComponent]
+ public sealed partial class CarriableComponent : Component
+ {
+ ///
+ /// необходимое количество свободных рук
+ /// что-бы взять сущность
+ ///
+ [DataField("freeHandsRequired")]
+ public int FreeHandsRequired = 2;
+
+ public CancellationTokenSource? CancelToken;
+ }
+}
diff --git a/Content.Server/_Sunrise/Carrying/CarryingComponent.cs b/Content.Server/_Sunrise/Carrying/CarryingComponent.cs
new file mode 100644
index 00000000000..e79460595b9
--- /dev/null
+++ b/Content.Server/_Sunrise/Carrying/CarryingComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Server.Carrying
+{
+ ///
+ /// Added to an entity when they are carrying somebody.
+ ///
+ [RegisterComponent]
+ public sealed partial class CarryingComponent : Component
+ {
+ public EntityUid Carried = default!;
+ }
+}
diff --git a/Content.Server/_Sunrise/Carrying/CarryingSystem.cs b/Content.Server/_Sunrise/Carrying/CarryingSystem.cs
new file mode 100644
index 00000000000..89a33385144
--- /dev/null
+++ b/Content.Server/_Sunrise/Carrying/CarryingSystem.cs
@@ -0,0 +1,405 @@
+using System.Numerics;
+using System.Threading;
+using Content.Server.DoAfter;
+using Content.Server.Inventory;
+using Content.Server.Resist;
+using Content.Server.Popups;
+using Content.Server.Item.PseudoItem;
+using Content.Shared.Climbing;
+using Content.Shared.Mobs;
+using Content.Shared.DoAfter;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands;
+using Content.Shared.Stunnable;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Verbs;
+using Content.Shared.Climbing.Events;
+using Content.Shared.Carrying;
+using Content.Shared.Movement.Events;
+using Content.Shared.Movement.Systems;
+using Content.Shared.Pulling;
+using Content.Shared.Standing;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Inventory.VirtualItem;
+using Content.Shared.Item;
+using Content.Shared.Item.PseudoItem;
+using Content.Shared.Mind.Components;
+using Content.Shared.Throwing;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Movement.Pulling.Components;
+using Content.Shared.Movement.Pulling.Events;
+using Content.Shared.Movement.Pulling.Systems;
+using Content.Shared.Popups;
+using Content.Shared.Storage;
+using Robust.Shared.Map.Components;
+
+namespace Content.Server.Carrying
+{
+ public sealed class CarryingSystem : EntitySystem
+ {
+ [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!;
+ [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly StandingStateSystem _standingState = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
+ [Dependency] private readonly PullingSystem _pulling = default!;
+ [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+ [Dependency] private readonly EscapeInventorySystem _escapeInventorySystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+ [Dependency] private readonly PseudoItemSystem _pseudoItem = default!;
+ [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent>(AddCarryVerb);
+ SubscribeLocalEvent(OnVirtualItemDeleted);
+ SubscribeLocalEvent(OnThrow);
+ SubscribeLocalEvent(OnParentChanged);
+ SubscribeLocalEvent(OnMobStateChanged);
+ SubscribeLocalEvent(OnInteractionAttempt);
+ SubscribeLocalEvent(OnMoveInput);
+ SubscribeLocalEvent(OnMoveAttempt);
+ SubscribeLocalEvent(OnStandAttempt);
+ SubscribeLocalEvent(OnInteractedWith);
+ SubscribeLocalEvent(OnPullAttempt);
+ SubscribeLocalEvent(OnStartClimb);
+ SubscribeLocalEvent(OnBuckleChange);
+ SubscribeLocalEvent(OnDoAfter);
+ SubscribeLocalEvent>(AddInsertCarriedVerb);
+ }
+
+
+ private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ if (!CanCarry(args.User, uid, component))
+ return;
+
+ if (HasComp(args.User))
+ return;
+
+ if (HasComp(args.User) || HasComp(args.Target))
+ return;
+
+ if (!_mobStateSystem.IsAlive(args.User))
+ return;
+
+ if (args.User == args.Target)
+ return;
+
+ AlternativeVerb verb = new()
+ {
+ Act = () =>
+ {
+ StartCarryDoAfter(args.User, uid, component);
+ },
+ Text = Loc.GetString("carry-verb"),
+ Priority = 2
+ };
+ args.Verbs.Add(verb);
+ }
+
+ private void OnVirtualItemDeleted(EntityUid uid, CarryingComponent component, VirtualItemDeletedEvent args)
+ {
+ if (!HasComp(args.BlockingEntity))
+ return;
+
+ DropCarried(uid, args.BlockingEntity);
+ }
+
+ private void OnThrow(EntityUid uid, CarryingComponent component, BeforeThrowEvent args)
+ {
+ if (!TryComp(args.ItemUid, out var virtItem) || !HasComp(virtItem.BlockingEntity))
+ return;
+
+ args.ItemUid = virtItem.BlockingEntity;
+
+ // var multiplier = _contests.MassContest(uid, virtItem.BlockingEntity);
+ // args.ThrowStrength = 5f * multiplier;
+ }
+
+ private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args)
+ {
+ var xform = Transform(uid);
+
+ if (xform.ParentUid == args.OldParent)
+ return;
+
+ // Do not drop the carried entity if the new parent is a grid
+ if (xform.ParentUid == xform.GridUid)
+ return;
+
+ DropCarried(uid, component.Carried);
+ }
+
+ private void OnMobStateChanged(EntityUid uid, CarryingComponent component, MobStateChangedEvent args)
+ {
+ DropCarried(uid, component.Carried);
+ }
+
+ ///
+ /// позволяет человеку, которого несут, взаимодействовать только с тем, кто его несет, и с вещами, находящимися у него
+ ///
+ private void OnInteractionAttempt(EntityUid uid, BeingCarriedComponent component, InteractionAttemptEvent args)
+ {
+ if (args.Target == null)
+ return;
+
+ var targetParent = Transform(args.Target.Value).ParentUid;
+
+ if (args.Target.Value != component.Carrier && targetParent != component.Carrier && targetParent != uid)
+ args.Cancel();
+ }
+
+ ///
+ /// Try to escape via the escape inventory system.
+ ///
+ private void OnMoveInput(EntityUid uid, BeingCarriedComponent component, ref MoveInputEvent args)
+ {
+ if (!TryComp(uid, out var escape))
+ return;
+
+ if (args.OldMovement == MoveButtons.None || args.OldMovement == MoveButtons.Walk)
+ return;
+
+ if (_actionBlockerSystem.CanInteract(uid, component.Carrier))
+ {
+ _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape);
+ }
+ }
+
+ private void OnMoveAttempt(EntityUid uid, BeingCarriedComponent component, UpdateCanMoveEvent args)
+ {
+ args.Cancel();
+ }
+
+ private void OnStandAttempt(EntityUid uid, BeingCarriedComponent component, StandAttemptEvent args)
+ {
+ args.Cancel();
+ }
+
+ private void OnInteractedWith(EntityUid uid, BeingCarriedComponent component, GettingInteractedWithAttemptEvent args)
+ {
+ if (args.Uid != component.Carrier)
+ args.Cancel();
+ }
+
+ private void OnPullAttempt(EntityUid uid, BeingCarriedComponent component, PullAttemptEvent args)
+ {
+ args.Cancelled = true;
+ }
+
+ private void OnStartClimb(EntityUid uid, BeingCarriedComponent component, ref StartClimbEvent args)
+ {
+ DropCarried(component.Carrier, uid);
+ }
+
+ private void OnBuckleChange(EntityUid uid, BeingCarriedComponent component, ref BuckleChangeEvent args)
+ {
+ DropCarried(component.Carrier, uid);
+ }
+
+ private void OnDoAfter(EntityUid uid, CarriableComponent component, CarryDoAfterEvent args)
+ {
+ component.CancelToken = null;
+ if (args.Handled || args.Cancelled)
+ return;
+
+ if (!CanCarry(args.Args.User, uid, component))
+ return;
+
+ Carry(args.Args.User, uid);
+ args.Handled = true;
+ }
+
+ private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableComponent component)
+ {
+ var length = GetPickupDuration(carrier, carried);
+ if (length >= TimeSpan.FromSeconds(9))
+ {
+ _popupSystem.PopupEntity(Loc.GetString("carry-too-heavy"), carried, carrier, Shared.Popups.PopupType.SmallCaution);
+ return;
+ }
+
+ if (!HasComp(carried))
+ length *= 2f;
+
+ component.CancelToken = new CancellationTokenSource();
+
+ var ev = new CarryDoAfterEvent();
+ var args = new DoAfterArgs(EntityManager, carrier, length, ev, carried, target: carried)
+ {
+ BreakOnMove = true,
+ NeedHand = true
+ };
+ _doAfterSystem.TryStartDoAfter(args);
+
+ _popupSystem.PopupEntity(Loc.GetString("carry-started", ("carrier", carrier)), carried, carried);
+ }
+
+ private void Carry(EntityUid carrier, EntityUid carried)
+ {
+ if (TryComp(carried, out var pullable))
+ {
+ if (pullable.Puller != null)
+ {
+ _pulling.TryStopPull(carried, pullable);
+ }
+ }
+
+
+ if (TryComp(carried, out var carryComp))
+ DropCarried(carried, carryComp.Carried);
+
+ Transform(carrier).AttachToGridOrMap();
+ Transform(carried).AttachToGridOrMap();
+ Transform(carried).Coordinates = Transform(carrier).Coordinates;
+ Transform(carried).AttachParent(Transform(carrier));
+ var carryingComp = EnsureComp(carrier);
+ ApplyCarrySlowdown(carrier, carried);
+ var carriedComp = EnsureComp(carried);
+ EnsureComp(carried);
+
+ _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier);
+ _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier);
+
+ carryingComp.Carried = carried;
+ carriedComp.Carrier = carrier;
+
+ _actionBlockerSystem.UpdateCanMove(carried);
+ }
+
+ public void DropCarried(EntityUid carrier, EntityUid carried)
+ {
+ RemComp(carrier);
+ RemComp(carrier);
+ RemComp(carried);
+ RemComp(carried);
+ _actionBlockerSystem.UpdateCanMove(carried);
+ Transform(carried).AttachToGridOrMap();
+ _standingState.Stand(carried);
+ _movementSpeed.RefreshMovementSpeedModifiers(carrier);
+ _virtualItemSystem.DeleteInHandsMatching(carrier, carried);
+ }
+
+ private void ApplyCarrySlowdown(EntityUid carrier, EntityUid carried)
+ {
+ // Carrying slowdown made static as a part of removing mass contests
+ // var massRatio = _contests.MassContest(carrier, carried);
+ // if (massRatio == 0)
+ // massRatio = 1;
+ // var massRatioSq = Math.Pow(massRatio, 2);
+ // var modifier = (1 - (0.15 / massRatioSq));
+ // modifier = Math.Max(0.1, modifier);
+ var modifier = 0.7f; // 30% slowdown while carrying
+ var slowdownComp = EnsureComp(carrier);
+ _slowdown.SetModifier(carrier, (float) modifier, (float) modifier, slowdownComp);
+ }
+
+ public bool CanCarry(EntityUid carrier, EntityUid carried, CarriableComponent? carriedComp = null)
+ {
+ if (!Resolve(carried, ref carriedComp, false))
+ return false;
+
+ if (carriedComp.CancelToken != null)
+ return false;
+
+ if (!HasComp(Transform(carrier).ParentUid))
+ return false;
+
+ if (HasComp(carrier) || HasComp(carried))
+ return false;
+
+ // if (_respirator.IsReceivingCPR(carried))
+ // return false;
+
+ if (!TryComp(carrier, out var hands))
+ return false;
+
+ if (hands.CountFreeHands() < carriedComp.FreeHandsRequired)
+ return false;
+
+ return true;
+ }
+
+ public override void Update(float frameTime)
+ {
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var carried, out var comp))
+ {
+ var carrier = comp.Carrier;
+ if (carrier is not { Valid: true } || carried is not { Valid: true })
+ continue;
+
+ if (Transform(carried).ParentUid != carrier)
+ {
+ DropCarried(carrier, carried);
+ continue;
+ }
+
+ var xform = Transform(carried);
+ if (!xform.LocalPosition.Equals(Vector2.Zero))
+ {
+ xform.LocalPosition = Vector2.Zero;
+ }
+ }
+ query.Dispose();
+ }
+
+ public TimeSpan GetPickupDuration(EntityUid carrier, EntityUid carried)
+ {
+ TimeSpan length = TimeSpan.FromSeconds(2.5);
+
+ // var mod = _contests.MassContest(carrier, carried);
+ // if (mod != 0)
+ // length /= mod;
+
+ return length;
+ }
+
+ public bool TryCarry(EntityUid carrier, EntityUid toCarry, CarriableComponent? carriedComp = null)
+ {
+ if (!Resolve(toCarry, ref carriedComp, false))
+ return false;
+
+ if (!CanCarry(carrier, toCarry, carriedComp))
+ return false;
+
+ if (HasComp(carrier) || HasComp(carrier))
+ return false;
+
+ if (GetPickupDuration(carrier, toCarry) > TimeSpan.FromSeconds(9))
+ return false;
+
+ Carry(carrier, toCarry);
+
+ return true;
+ }
+
+ private void AddInsertCarriedVerb(EntityUid uid, CarryingComponent component, GetVerbsEvent args)
+ {
+ var toInsert = args.Using;
+ if (toInsert is not { Valid: true } || !args.CanAccess || !TryComp(toInsert, out var pseudoItem))
+ return;
+
+ if (!HasComp(args.Target))
+ return;
+
+ InnateVerb verb = new()
+ {
+ Act = () =>
+ {
+ DropCarried(uid, toInsert.Value);
+ _pseudoItem.TryInsert(args.Target, toInsert.Value, args.User, pseudoItem);
+ },
+ Text = Loc.GetString("action-name-insert-other", ("target", toInsert)),
+ Priority = 2
+ };
+ args.Verbs.Add(verb);
+ }
+ }
+}
diff --git a/Content.Server/_Sunrise/Item/PseudoItem/AllowsSleepInsideComponent.cs b/Content.Server/_Sunrise/Item/PseudoItem/AllowsSleepInsideComponent.cs
new file mode 100644
index 00000000000..3f023d6cdce
--- /dev/null
+++ b/Content.Server/_Sunrise/Item/PseudoItem/AllowsSleepInsideComponent.cs
@@ -0,0 +1,9 @@
+namespace Content.Server.Item.PseudoItem;
+
+///
+/// Signifies that pseudo-item creatures can sleep inside the container to which this component was added.
+///
+[RegisterComponent]
+public sealed partial class AllowsSleepInsideComponent : Component
+{
+}
diff --git a/Content.Server/_Sunrise/Item/PseudoItem/PseudoItemSystem.cs b/Content.Server/_Sunrise/Item/PseudoItem/PseudoItemSystem.cs
new file mode 100644
index 00000000000..af7af93eab2
--- /dev/null
+++ b/Content.Server/_Sunrise/Item/PseudoItem/PseudoItemSystem.cs
@@ -0,0 +1,213 @@
+using Content.Server.Actions;
+using Content.Server.Bed.Sleep;
+using Content.Server.Carrying;
+using Content.Server.DoAfter;
+using Content.Server.Popups;
+using Content.Server.Storage.EntitySystems;
+using Content.Shared.Bed.Sleep;
+using Content.Shared.DoAfter;
+using Content.Shared.Hands;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Item;
+using Content.Shared.Item.PseudoItem;
+using Content.Shared.Storage;
+using Content.Shared.Tag;
+using Content.Shared.Verbs;
+using Robust.Shared.Containers;
+
+namespace Content.Server.Item.PseudoItem;
+
+public sealed class PseudoItemSystem : EntitySystem
+{
+ [Dependency] private readonly StorageSystem _storageSystem = default!;
+ [Dependency] private readonly ItemSystem _itemSystem = default!;
+ [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly TagSystem _tagSystem = default!;
+ [Dependency] private readonly CarryingSystem _carrying = default!; // Frontier
+ [Dependency] private readonly ActionsSystem _actions = default!; // Frontier
+ [Dependency] private readonly PopupSystem _popup = default!; // Frontier
+
+ [ValidatePrototypeId]
+ private const string PreventTag = "PreventLabel";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent>(AddInsertVerb);
+ SubscribeLocalEvent>(AddInsertAltVerb);
+ SubscribeLocalEvent(OnEntRemoved);
+ SubscribeLocalEvent(OnGettingPickedUpAttempt);
+ SubscribeLocalEvent(OnDropAttempt);
+ SubscribeLocalEvent(OnDoAfter);
+ SubscribeLocalEvent(OnInsertAttempt);
+ SubscribeLocalEvent(OnTrySleeping); // Frontier
+ }
+
+ private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ if (component.Active)
+ return;
+
+ if (!TryComp(args.Target, out var targetStorage))
+ return;
+
+ if (Transform(args.Target).ParentUid == uid)
+ return;
+
+ InnateVerb verb = new()
+ {
+ Act = () =>
+ {
+ TryInsert(args.Target, uid, args.User, component, targetStorage);
+ },
+ Text = Loc.GetString("action-name-insert-self"),
+ Priority = 2
+ };
+ args.Verbs.Add(verb);
+ }
+
+ private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ if (args.User == args.Target)
+ return;
+
+ if (component.Active)
+ return;
+
+ if (args.Hands == null)
+ return;
+
+ if (!TryComp(args.Hands.ActiveHandEntity, out var targetStorage))
+ return;
+
+ AlternativeVerb verb = new()
+ {
+ Act = () =>
+ {
+ StartInsertDoAfter(args.User, uid, args.Hands.ActiveHandEntity.Value, component);
+ },
+ Text = Loc.GetString("action-name-insert-other", ("target", Identity.Entity(args.Target, EntityManager))),
+ Priority = 2
+ };
+ args.Verbs.Add(verb);
+ }
+
+ private void OnEntRemoved(EntityUid uid, PseudoItemComponent component, EntGotRemovedFromContainerMessage args)
+ {
+ if (!component.Active)
+ return;
+
+ RemComp(uid);
+ component.Active = false;
+
+ // Frontier
+ if (component.SleepAction is { Valid: true })
+ _actions.RemoveAction(uid, component.SleepAction);
+ }
+
+ private void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
+ GettingPickedUpAttemptEvent args)
+ {
+ if (args.User == args.Item)
+ return;
+
+ // Frontier: prevent people from pushing each other from a bag
+ if (HasComp(args.User))
+ {
+ args.Cancel();
+ return;
+ }
+
+ // Frontier: try to carry the person when taking them out of a bag.
+ if (_carrying.TryCarry(args.User, uid))
+ {
+ args.Cancel();
+ return;
+ }
+
+ Transform(uid).AttachToGridOrMap();
+ args.Cancel();
+ }
+
+ private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
+ {
+ if (component.Active)
+ args.Cancel();
+ }
+
+ private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled || args.Args.Used == null)
+ return;
+
+ args.Handled = TryInsert(args.Args.Used.Value, uid, args.User, component);
+ }
+
+ public bool TryInsert(EntityUid storageUid, EntityUid toInsert, EntityUid userUid, PseudoItemComponent component,
+ StorageComponent? storage = null)
+ {
+ if (!Resolve(storageUid, ref storage))
+ return false;
+
+ var item = EnsureComp(toInsert);
+ _tagSystem.TryAddTag(toInsert, PreventTag);
+ _itemSystem.SetSize(toInsert, component.Size, item);
+ _itemSystem.VisualsChanged(toInsert);
+
+ if (!_storageSystem.CanInsert(storageUid, toInsert, out _) ||
+ !_storageSystem.PlayerInsertEntityInWorld(storageUid, userUid, toInsert))
+ {
+ component.Active = false;
+ RemComp(toInsert);
+ return false;
+ }
+ _storageSystem.UpdateUI(storageUid);
+ _storageSystem.UpdateAppearance(storageUid);
+
+ // Frontier
+ if (HasComp(storageUid))
+ _actions.AddAction(toInsert, ref component.SleepAction, SleepingSystem.SleepActionId, toInsert);
+
+ component.Active = true;
+ return true;
+ }
+
+ private void StartInsertDoAfter(EntityUid inserter, EntityUid toInsert, EntityUid storageEntity,
+ PseudoItemComponent? pseudoItem = null)
+ {
+ if (!Resolve(toInsert, ref pseudoItem))
+ return;
+
+ var ev = new PseudoItemInsertDoAfterEvent();
+ var args = new DoAfterArgs(EntityManager, inserter, 5f, ev, toInsert, toInsert, storageEntity)
+ {
+ BreakOnMove = true,
+ NeedHand = true
+ };
+
+ _doAfter.TryStartDoAfter(args);
+ }
+
+ private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component,
+ ContainerGettingInsertedAttemptEvent args)
+ {
+ if (!component.Active)
+ return;
+ // This hopefully shouldn't trigger, but this is a failsafe just in case so we dont bluespace them cats
+ args.Cancel();
+ }
+
+ // Frontier - show a popup when a pseudo-item falls asleep inside a bag.
+ private void OnTrySleeping(EntityUid uid, PseudoItemComponent component, TryingToSleepEvent args)
+ {
+ var parent = Transform(uid).ParentUid;
+ if (!HasComp(uid) && parent is { Valid: true } && HasComp(parent))
+ _popup.PopupEntity(Loc.GetString("popup-sleep-in-bag", ("entity", uid)), uid);
+ }
+}
diff --git a/Content.Shared/_Sunrise/Carrying/CarryDoAfterEvent.cs b/Content.Shared/_Sunrise/Carrying/CarryDoAfterEvent.cs
new file mode 100644
index 00000000000..940135bfa99
--- /dev/null
+++ b/Content.Shared/_Sunrise/Carrying/CarryDoAfterEvent.cs
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Carrying;
+
+[Serializable, NetSerializable]
+public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/_Sunrise/Carrying/CarryingDoAfterEvent.cs b/Content.Shared/_Sunrise/Carrying/CarryingDoAfterEvent.cs
new file mode 100644
index 00000000000..abf12ab4de7
--- /dev/null
+++ b/Content.Shared/_Sunrise/Carrying/CarryingDoAfterEvent.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.Serialization;
+using Content.Shared.DoAfter;
+
+namespace Content.Shared.Carrying
+{
+}
+
+[Serializable, NetSerializable]
+public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
diff --git a/Content.Shared/_Sunrise/Carrying/CarryingSlowdownComponent.cs b/Content.Shared/_Sunrise/Carrying/CarryingSlowdownComponent.cs
new file mode 100644
index 00000000000..a4b0cc1781f
--- /dev/null
+++ b/Content.Shared/_Sunrise/Carrying/CarryingSlowdownComponent.cs
@@ -0,0 +1,28 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Carrying
+{
+ [RegisterComponent, NetworkedComponent, Access(typeof(CarryingSlowdownSystem))]
+
+ public sealed partial class CarryingSlowdownComponent : Component
+ {
+ [DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+ public float WalkModifier = 1.0f;
+
+ [DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+ public float SprintModifier = 1.0f;
+ }
+
+ [Serializable, NetSerializable]
+ public sealed partial class CarryingSlowdownComponentState : ComponentState
+ {
+ public float WalkModifier;
+ public float SprintModifier;
+ public CarryingSlowdownComponentState(float walkModifier, float sprintModifier)
+ {
+ WalkModifier = walkModifier;
+ SprintModifier = sprintModifier;
+ }
+ }
+}
diff --git a/Content.Shared/_Sunrise/Carrying/CarryingSlowdownSystem.cs b/Content.Shared/_Sunrise/Carrying/CarryingSlowdownSystem.cs
new file mode 100644
index 00000000000..9b9c8cec10f
--- /dev/null
+++ b/Content.Shared/_Sunrise/Carrying/CarryingSlowdownSystem.cs
@@ -0,0 +1,47 @@
+using Content.Shared.Movement.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Carrying
+{
+ public sealed class CarryingSlowdownSystem : EntitySystem
+ {
+ [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnGetState);
+ SubscribeLocalEvent(OnHandleState);
+ SubscribeLocalEvent(OnRefreshMoveSpeed);
+ }
+
+ public void SetModifier(EntityUid uid, float walkSpeedModifier, float sprintSpeedModifier, CarryingSlowdownComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.WalkModifier = walkSpeedModifier;
+ component.SprintModifier = sprintSpeedModifier;
+ _movementSpeed.RefreshMovementSpeedModifiers(uid);
+ }
+ private void OnGetState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentGetState args)
+ {
+ args.State = new CarryingSlowdownComponentState(component.WalkModifier, component.SprintModifier);
+ }
+
+ private void OnHandleState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is CarryingSlowdownComponentState state)
+ {
+ component.WalkModifier = state.WalkModifier;
+ component.SprintModifier = state.SprintModifier;
+
+ _movementSpeed.RefreshMovementSpeedModifiers(uid);
+ }
+ }
+ private void OnRefreshMoveSpeed(EntityUid uid, CarryingSlowdownComponent component, RefreshMovementSpeedModifiersEvent args)
+ {
+ args.ModifySpeed(component.WalkModifier, component.SprintModifier);
+ }
+ }
+}
diff --git a/Content.Shared/_Sunrise/Cloning/ITransferredByCloning.cs b/Content.Shared/_Sunrise/Cloning/ITransferredByCloning.cs
new file mode 100644
index 00000000000..a58c50ab347
--- /dev/null
+++ b/Content.Shared/_Sunrise/Cloning/ITransferredByCloning.cs
@@ -0,0 +1,9 @@
+namespace Content.Shared._Sunrise.Cloning;
+
+///
+/// Indicates that this Component should be transferred to the new entity when the entity is cloned (for example, using a cloner)
+///
+public interface ITransferredByCloning
+{
+}
+
diff --git a/Content.Shared/_Sunrise/Item/Components/PseudoItemComponent.cs b/Content.Shared/_Sunrise/Item/Components/PseudoItemComponent.cs
new file mode 100644
index 00000000000..41e2b660e9f
--- /dev/null
+++ b/Content.Shared/_Sunrise/Item/Components/PseudoItemComponent.cs
@@ -0,0 +1,19 @@
+using Content.Shared._Sunrise.Cloning;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Item.PseudoItem;
+///
+/// For entities that behave like an item under certain conditions,
+/// but not under most conditions.
+///
+[RegisterComponent]
+public sealed partial class PseudoItemComponent : Component, ITransferredByCloning
+{
+ [DataField("size", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string Size = "Huge";
+
+ public bool Active = false;
+
+ [DataField]
+ public EntityUid? SleepAction;
+}
diff --git a/Content.Shared/_Sunrise/Item/PseudoItemInsertDoAfterEvent.cs b/Content.Shared/_Sunrise/Item/PseudoItemInsertDoAfterEvent.cs
new file mode 100644
index 00000000000..4b34118f377
--- /dev/null
+++ b/Content.Shared/_Sunrise/Item/PseudoItemInsertDoAfterEvent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.Serialization;
+using Content.Shared.DoAfter;
+
+namespace Content.Shared.Item.PseudoItem;
+
+
+[Serializable, NetSerializable]
+public sealed partial class PseudoItemInsertDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Resources/Changelog/ChangelogSunrise.yml b/Resources/Changelog/ChangelogSunrise.yml
index 5124bb65177..4a9c6e3083f 100644
--- a/Resources/Changelog/ChangelogSunrise.yml
+++ b/Resources/Changelog/ChangelogSunrise.yml
@@ -420,3 +420,13 @@ Entries:
type: Tweak
id: 34
time: '2024-06-09T13:38:09.611579+00:00'
+- author: Babaev
+ changes:
+ - message: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u0432\u043E\u0437\
+ \u043C\u043E\u0436\u043D\u043E\u0441\u0442\u044C \u043F\u0435\u0440\u0435\u0442\
+ \u0430\u0441\u043A\u0438\u0432\u0430\u0442\u044C \u0434\u0440\u0443\u0433\u0438\
+ \u0445 \u043B\u044E\u0434\u0435\u0439 \u043D\u0430 \u0440\u0443\u043A\u0430\u0445\
+ ."
+ type: Add
+ id: 35
+ time: '2024-06-06T13:07:39.013718+00:00'
diff --git a/Resources/Locale/ru-RU/_sunrise/carrying/carry.ftl b/Resources/Locale/ru-RU/_sunrise/carrying/carry.ftl
new file mode 100644
index 00000000000..8417df65f09
--- /dev/null
+++ b/Resources/Locale/ru-RU/_sunrise/carrying/carry.ftl
@@ -0,0 +1,2 @@
+carry-verb = Нести на руках
+carry-too-heavy = Ты недостаточно силен.
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index 05cbd0ee306..d3924701f84 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -92,6 +92,7 @@
horizontalRotation: 90
- type: HumanoidAppearance
species: Human
+ - type: Carriable
- type: SlowOnDamage
speedModifierThresholds:
60: 0.7