Decouple standing state and drop item behavior (#41566)

* removed DropHandsItemEvent from standing state system, added DropHandsItemEvent calls in slippery system and shared stun system

* added DropHandItemsEvent calls in mobstate system subscribers

* Added DropHandItemsEvent call in SharedBodySystem.Parts

* Add a standingState check in RemoveLeg so removing the legs of a downed person won't cause them to drop items

* new method for downing + dropping held items in mobstatesystem

* mild cleanup

* Bugfix

* update BuckleTest to reflect new item dropping behavior when removing legs

* light cleanup

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
This commit is contained in:
alexalexmax
2025-12-16 10:32:56 -08:00
committed by GitHub
parent b0b88b216d
commit 2a596d283c
6 changed files with 45 additions and 24 deletions

View File

@@ -315,10 +315,10 @@ namespace Content.IntegrationTests.Tests.Buckle
// Still buckled
Assert.That(buckle.Buckled);
// Now with no item in any hand
// Still with items in hand
foreach (var hand in hands.Hands.Keys)
{
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Null);
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Not.Null);
}
buckleSystem.Unbuckle(human, human);

View File

@@ -7,6 +7,7 @@ using Content.Shared.Body.Part;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Movement.Components;
using Content.Shared.Standing;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -156,17 +157,23 @@ public partial class SharedBodySystem
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
return;
if (legEnt.Comp.PartType == BodyPartType.Leg)
{
bodyEnt.Comp.LegEntities.Remove(legEnt);
UpdateMovementSpeed(bodyEnt);
Dirty(bodyEnt, bodyEnt.Comp);
if (legEnt.Comp.PartType != BodyPartType.Leg)
return;
if (!bodyEnt.Comp.LegEntities.Any())
{
Standing.Down(bodyEnt);
}
}
bodyEnt.Comp.LegEntities.Remove(legEnt);
UpdateMovementSpeed(bodyEnt);
Dirty(bodyEnt, bodyEnt.Comp);
if (bodyEnt.Comp.LegEntities.Count != 0)
return;
if (!TryComp<StandingStateComponent>(bodyEnt, out var standingState)
|| !standingState.Standing
|| !Standing.Down(bodyEnt, standingState: standingState))
return;
var ev = new DropHandItemsEvent();
RaiseLocalEvent(bodyEnt, ref ev);
}
private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)

View File

@@ -59,6 +59,13 @@ public partial class MobStateSystem
args.Cancelled = true;
}
private void Down(EntityUid target)
{
_standing.Down(target);
var ev = new DropHandItemsEvent();
RaiseLocalEvent(target, ref ev);
}
private void CheckConcious(Entity<MobStateComponent> ent, ref ConsciousAttemptEvent args)
{
switch (ent.Comp.CurrentState)
@@ -103,23 +110,33 @@ public partial class MobStateSystem
switch (state)
{
case MobState.Alive:
{
_standing.Stand(target);
_appearance.SetData(target, MobStateVisuals.State, MobState.Alive);
break;
}
case MobState.Critical:
_standing.Down(target);
{
Down(target);
_appearance.SetData(target, MobStateVisuals.State, MobState.Critical);
break;
}
case MobState.Dead:
{
EnsureComp<CollisionWakeComponent>(target);
_standing.Down(target);
Down(target);
_appearance.SetData(target, MobStateVisuals.State, MobState.Dead);
break;
}
case MobState.Invalid:
{
//unused;
break;
}
default:
{
throw new NotImplementedException();
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Shared.Database;
using Content.Shared.Inventory;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Content.Shared.StatusEffectNew;
using Content.Shared.StepTrigger.Systems;
using Content.Shared.Stunnable;
@@ -131,6 +132,9 @@ public sealed class SlipperySystem : EntitySystem
// Preventing from playing the slip sound and stunning when you are already knocked down.
if (!knockedDown)
{
var evDropHands = new DropHandItemsEvent();
RaiseLocalEvent(uid, ref evDropHands);
// Status effects should handle a TimeSpan of 0 properly...
_stun.TryUpdateStunDuration(other, component.SlipData.StunTime);

View File

@@ -101,16 +101,6 @@ public sealed class StandingStateSystem : EntitySystem
if (!standingState.Standing)
return true;
// This is just to avoid most callers doing this manually saving boilerplate
// 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to.
// We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway
// and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent.
if (dropHeldItems && hands != null)
{
var ev = new DropHandItemsEvent();
RaiseLocalEvent(uid, ref ev, false);
}
if (!force)
{
var msg = new DownAttemptEvent();

View File

@@ -150,6 +150,9 @@ public abstract partial class SharedStunSystem : EntitySystem
var ev = new StunnedEvent(); // todo: rename event or change how it is raised - this event is raised each time duration of stun was externally changed
RaiseLocalEvent(uid, ref ev);
var evDropHands = new DropHandItemsEvent();
RaiseLocalEvent(uid, ref evDropHands);
var timeForLogs = duration.HasValue
? duration.Value.Seconds.ToString()
: "Infinite";