Partial buckling refactor (#29031)
* partial buckling refactor * git mv test * change test namespace * git mv test * Update test namespace * Add pulling test * Network BuckleTime * Add two more tests * smelly
This commit is contained in:
@@ -3,6 +3,7 @@ using Content.Shared.Buckle;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Rotation;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Buckle;
|
||||
|
||||
@@ -14,38 +15,61 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, AfterAutoHandleStateEvent>(OnBuckleAfterAutoHandleState);
|
||||
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
|
||||
}
|
||||
|
||||
private void OnBuckleAfterAutoHandleState(EntityUid uid, BuckleComponent component, ref AfterAutoHandleStateEvent args)
|
||||
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
|
||||
{
|
||||
ActionBlocker.UpdateCanMove(uid);
|
||||
// I'm moving this to the client-side system, but for the sake of posterity let's keep this comment:
|
||||
// > This is mega cursed. Please somebody save me from Mr Buckle's wild ride
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
|
||||
// The nice thing is its still true, this is quite cursed, though maybe not omega cursed anymore.
|
||||
// This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking
|
||||
// sprite rendering for entity layers & direction dependent sorting.
|
||||
|
||||
if (args.NewRotation == args.OldRotation)
|
||||
return;
|
||||
|
||||
// Adjust draw depth when the chair faces north so that the seat back is drawn over the player.
|
||||
// Reset the draw depth when rotated in any other direction.
|
||||
// TODO when ECSing, make this a visualizer
|
||||
// This code was written before rotatable viewports were introduced, so hard-coding Direction.North
|
||||
// and comparing it against LocalRotation now breaks this in other rotations. This is a FIXME, but
|
||||
// better to get it working for most people before we look at a more permanent solution.
|
||||
if (component is { Buckled: true, LastEntityBuckledTo: { } } &&
|
||||
Transform(component.LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North &&
|
||||
TryComp<SpriteComponent>(component.LastEntityBuckledTo, out var buckledSprite))
|
||||
{
|
||||
component.OriginalDrawDepth ??= ownerSprite.DrawDepth;
|
||||
ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
|
||||
if (!TryComp<SpriteComponent>(uid, out var strapSprite))
|
||||
return;
|
||||
|
||||
var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North;
|
||||
foreach (var buckledEntity in component.BuckledEntities)
|
||||
{
|
||||
if (!TryComp<BuckleComponent>(buckledEntity, out var buckle))
|
||||
continue;
|
||||
|
||||
if (!TryComp<SpriteComponent>(buckledEntity, out var buckledSprite))
|
||||
continue;
|
||||
|
||||
if (isNorth)
|
||||
{
|
||||
buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth;
|
||||
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
|
||||
}
|
||||
else if (buckle.OriginalDrawDepth.HasValue)
|
||||
{
|
||||
buckledSprite.DrawDepth = buckle.OriginalDrawDepth.Value;
|
||||
buckle.OriginalDrawDepth = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If here, we're not turning north and should restore the saved draw depth.
|
||||
if (component.OriginalDrawDepth.HasValue)
|
||||
private void OnHandleState(Entity<BuckleComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
ownerSprite.DrawDepth = component.OriginalDrawDepth.Value;
|
||||
component.OriginalDrawDepth = null;
|
||||
}
|
||||
if (args.Current is not BuckleState state)
|
||||
return;
|
||||
|
||||
ent.Comp.DontCollide = state.DontCollide;
|
||||
ent.Comp.BuckleTime = state.BuckleTime;
|
||||
var strapUid = EnsureEntity<BuckleComponent>(state.BuckledTo, ent);
|
||||
|
||||
SetBuckledTo(ent, strapUid == null ? null : new (strapUid.Value, null));
|
||||
|
||||
var (uid, component) = ent;
|
||||
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
|
||||
|
||||
56
Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs
Normal file
56
Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Buckle;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Buckle;
|
||||
|
||||
public sealed class BuckleDragTest : InteractionTest
|
||||
{
|
||||
// Check that dragging a buckled player unbuckles them.
|
||||
[Test]
|
||||
public async Task BucklePullTest()
|
||||
{
|
||||
var urist = await SpawnTarget("MobHuman");
|
||||
var sUrist = ToServer(urist);
|
||||
await SpawnTarget("Chair");
|
||||
|
||||
var buckle = Comp<BuckleComponent>(urist);
|
||||
var strap = Comp<StrapComponent>(Target);
|
||||
var puller = Comp<PullerComponent>(Player);
|
||||
var pullable = Comp<PullableComponent>(urist);
|
||||
|
||||
#pragma warning disable RA0002
|
||||
buckle.Delay = TimeSpan.Zero;
|
||||
#pragma warning restore RA0002
|
||||
|
||||
// Initially not buckled to the chair and not pulling anything
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
Assert.That(buckle.BuckledTo, Is.Null);
|
||||
Assert.That(strap.BuckledEntities, Is.Empty);
|
||||
Assert.That(puller.Pulling, Is.Null);
|
||||
Assert.That(pullable.Puller, Is.Null);
|
||||
Assert.That(pullable.BeingPulled, Is.False);
|
||||
|
||||
// Strap the human to the chair
|
||||
Assert.That(Server.System<SharedBuckleSystem>().TryBuckle(sUrist, SPlayer, STarget.Value));
|
||||
await RunTicks(5);
|
||||
Assert.That(buckle.Buckled, Is.True);
|
||||
Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
|
||||
Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{sUrist}));
|
||||
Assert.That(puller.Pulling, Is.Null);
|
||||
Assert.That(pullable.Puller, Is.Null);
|
||||
Assert.That(pullable.BeingPulled, Is.False);
|
||||
|
||||
// Start pulling, and thus unbuckle them
|
||||
await PressKey(ContentKeyFunctions.TryPullObject, cursorEntity:urist);
|
||||
await RunTicks(5);
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
Assert.That(buckle.BuckledTo, Is.Null);
|
||||
Assert.That(strap.BuckledEntities, Is.Empty);
|
||||
Assert.That(puller.Pulling, Is.EqualTo(sUrist));
|
||||
Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
|
||||
Assert.That(pullable.BeingPulled, Is.True);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
{
|
||||
Assert.That(strap, Is.Not.Null);
|
||||
Assert.That(strap.BuckledEntities, Is.Empty);
|
||||
Assert.That(strap.OccupiedSize, Is.Zero);
|
||||
});
|
||||
|
||||
// Side effects of buckling
|
||||
@@ -111,8 +110,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
|
||||
// Side effects of buckling for the strap
|
||||
Assert.That(strap.BuckledEntities, Does.Contain(human));
|
||||
Assert.That(strap.OccupiedSize, Is.EqualTo(buckle.Size));
|
||||
Assert.That(strap.OccupiedSize, Is.Positive);
|
||||
});
|
||||
|
||||
#pragma warning disable NUnit2045 // Interdependent asserts.
|
||||
@@ -122,7 +119,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
// Trying to unbuckle too quickly fails
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
@@ -149,7 +146,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
|
||||
// Unbuckle, strap
|
||||
Assert.That(strap.BuckledEntities, Is.Empty);
|
||||
Assert.That(strap.OccupiedSize, Is.Zero);
|
||||
});
|
||||
|
||||
#pragma warning disable NUnit2045 // Interdependent asserts.
|
||||
@@ -160,9 +156,9 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
// On cooldown
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
@@ -189,7 +185,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
#pragma warning disable NUnit2045 // Interdependent asserts.
|
||||
Assert.That(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle), Is.False);
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
|
||||
Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
// Move near the chair
|
||||
@@ -202,12 +197,10 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
Assert.That(buckle.Buckled);
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
|
||||
Assert.That(buckle.Buckled);
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
// Force unbuckle
|
||||
Assert.That(buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle));
|
||||
buckleSystem.Unbuckle(human, human);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
@@ -311,7 +304,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
// Break our guy's kneecaps
|
||||
foreach (var leg in legs)
|
||||
{
|
||||
xformSystem.DetachParentToNull(leg.Id, entityManager.GetComponent<TransformComponent>(leg.Id));
|
||||
entityManager.DeleteEntity(leg.Id);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -328,7 +321,8 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
Assert.That(hand.HeldEntity, Is.Null);
|
||||
}
|
||||
|
||||
buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle);
|
||||
buckleSystem.Unbuckle(human, human);
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.IntegrationTests.Tests.Movement;
|
||||
using Robust.Shared.Maths;
|
||||
using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
|
||||
using ClimbSystem = Content.Shared.Climbing.Systems.ClimbSystem;
|
||||
|
||||
@@ -59,11 +59,6 @@ public sealed class CraftingTests : InteractionTest
|
||||
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
|
||||
}
|
||||
|
||||
// The following is wrapped in an if DEBUG. This is because of cursed state handling bugs. Tests don't (de)serialize
|
||||
// net messages and just copy objects by reference. This means that the server will directly modify cached server
|
||||
// states on the client's end. Crude fix at the moment is to used modified state handling while in debug mode
|
||||
// Otherwise, this test cannot work.
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Cancel crafting a complex recipe.
|
||||
/// </summary>
|
||||
@@ -93,8 +88,6 @@ public sealed class CraftingTests : InteractionTest
|
||||
await RunTicks(1);
|
||||
|
||||
// DoAfter is in progress. Entity not spawned, stacks have been split and someingredients are in a container.
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
|
||||
Assert.That(sys.IsEntityInContainer(shard), Is.True);
|
||||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||
@@ -103,18 +96,14 @@ public sealed class CraftingTests : InteractionTest
|
||||
Assert.That(wireStack, Has.Count.EqualTo(7));
|
||||
|
||||
await FindEntity(Spear, shouldSucceed: false);
|
||||
});
|
||||
|
||||
// Cancel the DoAfter. Should drop ingredients to the floor.
|
||||
await CancelDoAfters();
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
||||
Assert.That(sys.IsEntityInContainer(shard), Is.False);
|
||||
await FindEntity(Spear, shouldSucceed: false);
|
||||
await AssertEntityLookup((Rod, 10), (Cable, 10), (ShardGlass, 1));
|
||||
});
|
||||
|
||||
// Re-attempt the do-after
|
||||
#pragma warning disable CS4014 // Legacy construction code uses DoAfterAwait. See above.
|
||||
@@ -123,24 +112,17 @@ public sealed class CraftingTests : InteractionTest
|
||||
await RunTicks(1);
|
||||
|
||||
// DoAfter is in progress. Entity not spawned, ingredients are in a container.
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
|
||||
Assert.That(sys.IsEntityInContainer(shard), Is.True);
|
||||
await FindEntity(Spear, shouldSucceed: false);
|
||||
});
|
||||
|
||||
// Finish the DoAfter
|
||||
await AwaitDoAfters();
|
||||
|
||||
// Spear has been crafted. Rods and wires are no longer contained. Glass has been consumed.
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
await FindEntity(Spear);
|
||||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
||||
Assert.That(SEntMan.Deleted(shard));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -84,8 +84,9 @@ public abstract partial class InteractionTest
|
||||
/// <summary>
|
||||
/// Spawn an entity entity and set it as the target.
|
||||
/// </summary>
|
||||
[MemberNotNull(nameof(Target))]
|
||||
protected async Task SpawnTarget(string prototype)
|
||||
[MemberNotNull(nameof(Target), nameof(STarget), nameof(CTarget))]
|
||||
#pragma warning disable CS8774 // Member must have a non-null value when exiting.
|
||||
protected async Task<NetEntity> SpawnTarget(string prototype)
|
||||
{
|
||||
Target = NetEntity.Invalid;
|
||||
await Server.WaitPost(() =>
|
||||
@@ -95,7 +96,9 @@ public abstract partial class InteractionTest
|
||||
|
||||
await RunTicks(5);
|
||||
AssertPrototype(prototype);
|
||||
return Target!.Value;
|
||||
}
|
||||
#pragma warning restore CS8774 // Member must have a non-null value when exiting.
|
||||
|
||||
/// <summary>
|
||||
/// Spawn an entity in preparation for deconstruction
|
||||
@@ -1170,14 +1173,17 @@ public abstract partial class InteractionTest
|
||||
|
||||
#region Inputs
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Make the client press and then release a key. This assumes the key is currently released.
|
||||
/// This will default to using the <see cref="Target"/> entity and <see cref="TargetCoords"/> coordinates.
|
||||
/// </summary>
|
||||
protected async Task PressKey(
|
||||
BoundKeyFunction key,
|
||||
int ticks = 1,
|
||||
NetCoordinates? coordinates = null,
|
||||
NetEntity cursorEntity = default)
|
||||
NetEntity? cursorEntity = null)
|
||||
{
|
||||
await SetKey(key, BoundKeyState.Down, coordinates, cursorEntity);
|
||||
await RunTicks(ticks);
|
||||
@@ -1186,15 +1192,17 @@ public abstract partial class InteractionTest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make the client press or release a key
|
||||
/// Make the client press or release a key.
|
||||
/// This will default to using the <see cref="Target"/> entity and <see cref="TargetCoords"/> coordinates.
|
||||
/// </summary>
|
||||
protected async Task SetKey(
|
||||
BoundKeyFunction key,
|
||||
BoundKeyState state,
|
||||
NetCoordinates? coordinates = null,
|
||||
NetEntity cursorEntity = default)
|
||||
NetEntity? cursorEntity = null)
|
||||
{
|
||||
var coords = coordinates ?? TargetCoords;
|
||||
var target = cursorEntity ?? Target ?? default;
|
||||
ScreenCoordinates screen = default;
|
||||
|
||||
var funcId = InputManager.NetworkBindMap.KeyFunctionID(key);
|
||||
@@ -1203,7 +1211,7 @@ public abstract partial class InteractionTest
|
||||
State = state,
|
||||
Coordinates = CEntMan.GetCoordinates(coords),
|
||||
ScreenCoordinates = screen,
|
||||
Uid = CEntMan.GetEntity(cursorEntity),
|
||||
Uid = CEntMan.GetEntity(target),
|
||||
};
|
||||
|
||||
await Client.WaitPost(() => InputSystem.HandleInputCommand(ClientSession, key, message));
|
||||
|
||||
@@ -84,6 +84,7 @@ public abstract partial class InteractionTest
|
||||
protected NetEntity? Target;
|
||||
|
||||
protected EntityUid? STarget => ToServer(Target);
|
||||
|
||||
protected EntityUid? CTarget => ToClient(Target);
|
||||
|
||||
/// <summary>
|
||||
@@ -128,7 +129,6 @@ public abstract partial class InteractionTest
|
||||
|
||||
public float TickPeriod => (float) STiming.TickPeriod.TotalSeconds;
|
||||
|
||||
|
||||
// Simple mob that has one hand and can perform misc interactions.
|
||||
[TestPrototypes]
|
||||
private const string TestPrototypes = @"
|
||||
@@ -142,6 +142,8 @@ public abstract partial class InteractionTest
|
||||
- type: ComplexInteraction
|
||||
- type: MindContainer
|
||||
- type: Stripping
|
||||
- type: Puller
|
||||
- type: Physics
|
||||
- type: Tag
|
||||
tags:
|
||||
- CanPilot
|
||||
@@ -207,8 +209,8 @@ public abstract partial class InteractionTest
|
||||
SEntMan.System<SharedMindSystem>().WipeMind(ServerSession.ContentData()?.Mind);
|
||||
|
||||
old = cPlayerMan.LocalEntity;
|
||||
Player = SEntMan.GetNetEntity(SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords)));
|
||||
SPlayer = SEntMan.GetEntity(Player);
|
||||
SPlayer = SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords));
|
||||
Player = SEntMan.GetNetEntity(SPlayer);
|
||||
Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer);
|
||||
Hands = SEntMan.GetComponent<HandsComponent>(SPlayer);
|
||||
DoAfters = SEntMan.GetComponent<DoAfterComponent>(SPlayer);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Movement;
|
||||
|
||||
public sealed class BuckleMovementTest : MovementTest
|
||||
{
|
||||
// Check that interacting with a chair straps you to it and prevents movement.
|
||||
[Test]
|
||||
public async Task ChairTest()
|
||||
{
|
||||
await SpawnTarget("Chair");
|
||||
|
||||
var cAlert = Client.System<AlertsSystem>();
|
||||
var sAlert = Server.System<AlertsSystem>();
|
||||
var buckle = Comp<BuckleComponent>(Player);
|
||||
var strap = Comp<StrapComponent>(Target);
|
||||
|
||||
#pragma warning disable RA0002
|
||||
buckle.Delay = TimeSpan.Zero;
|
||||
#pragma warning restore RA0002
|
||||
|
||||
// Initially not buckled to the chair, and standing off to the side
|
||||
Assert.That(Delta(), Is.InRange(0.9f, 1.1f));
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
Assert.That(buckle.BuckledTo, Is.Null);
|
||||
Assert.That(strap.BuckledEntities, Is.Empty);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.False);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.False);
|
||||
|
||||
// Interact results in being buckled to the chair
|
||||
await Interact();
|
||||
Assert.That(Delta(), Is.InRange(-0.01f, 0.01f));
|
||||
Assert.That(buckle.Buckled, Is.True);
|
||||
Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
|
||||
Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{SPlayer}));
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.True);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.True);
|
||||
|
||||
// Attempting to walk away does nothing
|
||||
await Move(DirectionFlag.East, 1);
|
||||
Assert.That(Delta(), Is.InRange(-0.01f, 0.01f));
|
||||
Assert.That(buckle.Buckled, Is.True);
|
||||
Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
|
||||
Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{SPlayer}));
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.True);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.True);
|
||||
|
||||
// Interacting again will unbuckle the player
|
||||
await Interact();
|
||||
Assert.That(Delta(), Is.InRange(-0.5f, 0.5f));
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
Assert.That(buckle.BuckledTo, Is.Null);
|
||||
Assert.That(strap.BuckledEntities, Is.Empty);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.False);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.False);
|
||||
|
||||
// And now they can move away
|
||||
await Move(DirectionFlag.SouthEast, 1);
|
||||
Assert.That(Delta(), Is.LessThan(-1));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
#nullable enable
|
||||
using System.Numerics;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Interaction;
|
||||
namespace Content.IntegrationTests.Tests.Movement;
|
||||
|
||||
/// <summary>
|
||||
/// This is a variation of <see cref="InteractionTest"/> that sets up the player with a normal human entity and a simple
|
||||
73
Content.IntegrationTests/Tests/Movement/PullingTest.cs
Normal file
73
Content.IntegrationTests/Tests/Movement/PullingTest.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
#nullable enable
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Movement;
|
||||
|
||||
public sealed class PullingTest : MovementTest
|
||||
{
|
||||
protected override int Tiles => 4;
|
||||
|
||||
[Test]
|
||||
public async Task PullTest()
|
||||
{
|
||||
var cAlert = Client.System<AlertsSystem>();
|
||||
var sAlert = Server.System<AlertsSystem>();
|
||||
await SpawnTarget("MobHuman");
|
||||
|
||||
var puller = Comp<PullerComponent>(Player);
|
||||
var pullable = Comp<PullableComponent>(Target);
|
||||
|
||||
// Player is initially to the left of the target and not pulling anything
|
||||
Assert.That(Delta(), Is.InRange(0.9f, 1.1f));
|
||||
Assert.That(puller.Pulling, Is.Null);
|
||||
Assert.That(pullable.Puller, Is.Null);
|
||||
Assert.That(pullable.BeingPulled, Is.False);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.False);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.False);
|
||||
|
||||
// Start pulling
|
||||
await PressKey(ContentKeyFunctions.TryPullObject);
|
||||
await RunTicks(5);
|
||||
Assert.That(puller.Pulling, Is.EqualTo(STarget));
|
||||
Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
|
||||
Assert.That(pullable.BeingPulled, Is.True);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.True);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.True);
|
||||
|
||||
// Move to the left and check that the target moves with the player and is still being pulled.
|
||||
await Move(DirectionFlag.West, 1);
|
||||
Assert.That(Delta(), Is.InRange(0.9f, 1.3f));
|
||||
Assert.That(puller.Pulling, Is.EqualTo(STarget));
|
||||
Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
|
||||
Assert.That(pullable.BeingPulled, Is.True);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.True);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.True);
|
||||
|
||||
// Move in the other direction
|
||||
await Move(DirectionFlag.East, 2);
|
||||
Assert.That(Delta(), Is.InRange(-1.3f, -0.9f));
|
||||
Assert.That(puller.Pulling, Is.EqualTo(STarget));
|
||||
Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
|
||||
Assert.That(pullable.BeingPulled, Is.True);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.True);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.True);
|
||||
|
||||
// Stop pulling
|
||||
await PressKey(ContentKeyFunctions.ReleasePulledObject);
|
||||
await RunTicks(5);
|
||||
Assert.That(Delta(), Is.InRange(-1.3f, -0.9f));
|
||||
Assert.That(puller.Pulling, Is.Null);
|
||||
Assert.That(pullable.Puller, Is.Null);
|
||||
Assert.That(pullable.BeingPulled, Is.False);
|
||||
Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.False);
|
||||
Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.False);
|
||||
|
||||
// Move back to the left and ensure the target is no longer following us.
|
||||
await Move(DirectionFlag.West, 2);
|
||||
Assert.That(Delta(), Is.GreaterThan(2f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Slipping;
|
||||
namespace Content.IntegrationTests.Tests.Movement;
|
||||
|
||||
public sealed class SlippingTest : MovementTest
|
||||
{
|
||||
@@ -36,18 +36,14 @@ public sealed class SlippingTest : MovementTest
|
||||
Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed.");
|
||||
|
||||
// Player is to the left of the banana peel and has not slipped.
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(Delta(), Is.GreaterThan(0.5f));
|
||||
Assert.That(sys.Slipped, Does.Not.Contain(SEntMan.GetEntity(Player)));
|
||||
#pragma warning restore NUnit2045
|
||||
|
||||
// Walking over the banana slowly does not trigger a slip.
|
||||
await SetKey(EngineKeyFunctions.Walk, BoundKeyState.Down);
|
||||
await Move(DirectionFlag.East, 1f);
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(Delta(), Is.LessThan(0.5f));
|
||||
Assert.That(sys.Slipped, Does.Not.Contain(SEntMan.GetEntity(Player)));
|
||||
#pragma warning restore NUnit2045
|
||||
AssertComp<KnockedDownComponent>(false, Player);
|
||||
|
||||
// Moving at normal speeds does trigger a slip.
|
||||
@@ -11,6 +11,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Bed
|
||||
{
|
||||
@@ -26,25 +27,29 @@ namespace Content.Server.Bed
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HealOnBuckleComponent, BuckleChangeEvent>(ManageUpdateList);
|
||||
SubscribeLocalEvent<StasisBedComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
SubscribeLocalEvent<HealOnBuckleComponent, StrappedEvent>(OnStrapped);
|
||||
SubscribeLocalEvent<HealOnBuckleComponent, UnstrappedEvent>(OnUnstrapped);
|
||||
SubscribeLocalEvent<StasisBedComponent, StrappedEvent>(OnStasisStrapped);
|
||||
SubscribeLocalEvent<StasisBedComponent, UnstrappedEvent>(OnStasisUnstrapped);
|
||||
SubscribeLocalEvent<StasisBedComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<StasisBedComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args)
|
||||
private void OnStrapped(Entity<HealOnBuckleComponent> bed, ref StrappedEvent args)
|
||||
{
|
||||
if (args.Buckling)
|
||||
{
|
||||
AddComp<HealOnBuckleHealingComponent>(uid);
|
||||
component.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(component.HealTime);
|
||||
_actionsSystem.AddAction(args.BuckledEntity, ref component.SleepAction, SleepingSystem.SleepActionId, uid);
|
||||
return;
|
||||
EnsureComp<HealOnBuckleHealingComponent>(bed);
|
||||
bed.Comp.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(bed.Comp.HealTime);
|
||||
_actionsSystem.AddAction(args.Buckle, ref bed.Comp.SleepAction, SleepingSystem.SleepActionId, bed);
|
||||
|
||||
// Single action entity, cannot strap multiple entities to the same bed.
|
||||
DebugTools.AssertEqual(args.Strap.Comp.BuckledEntities.Count, 1);
|
||||
}
|
||||
|
||||
_actionsSystem.RemoveAction(args.BuckledEntity, component.SleepAction);
|
||||
_sleepingSystem.TryWaking(args.BuckledEntity);
|
||||
RemComp<HealOnBuckleHealingComponent>(uid);
|
||||
private void OnUnstrapped(Entity<HealOnBuckleComponent> bed, ref UnstrappedEvent args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(args.Buckle, bed.Comp.SleepAction);
|
||||
_sleepingSystem.TryWaking(args.Buckle.Owner);
|
||||
RemComp<HealOnBuckleHealingComponent>(bed);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -82,18 +87,22 @@ namespace Content.Server.Bed
|
||||
_appearance.SetData(uid, StasisBedVisuals.IsOn, isOn);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, StasisBedComponent component, ref BuckleChangeEvent args)
|
||||
private void OnStasisStrapped(Entity<StasisBedComponent> bed, ref StrappedEvent args)
|
||||
{
|
||||
// In testing this also received an unbuckle event when the bed is destroyed
|
||||
// So don't worry about that
|
||||
if (!HasComp<BodyComponent>(args.BuckledEntity))
|
||||
if (!HasComp<BodyComponent>(args.Buckle) || !this.IsPowered(bed, EntityManager))
|
||||
return;
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.Buckle, bed.Comp.Multiplier, true);
|
||||
RaiseLocalEvent(args.Buckle, ref metabolicEvent);
|
||||
}
|
||||
|
||||
private void OnStasisUnstrapped(Entity<StasisBedComponent> bed, ref UnstrappedEvent args)
|
||||
{
|
||||
if (!HasComp<BodyComponent>(args.Buckle) || !this.IsPowered(bed, EntityManager))
|
||||
return;
|
||||
|
||||
var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.BuckledEntity, component.Multiplier, args.Buckling);
|
||||
RaiseLocalEvent(args.BuckledEntity, ref metabolicEvent);
|
||||
var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.Buckle, bed.Comp.Multiplier, false);
|
||||
RaiseLocalEvent(args.Buckle, ref metabolicEvent);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, StasisBedComponent component, ref PowerChangedEvent args)
|
||||
|
||||
@@ -5,19 +5,26 @@ namespace Content.Server.Bed.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class HealOnBuckleComponent : Component
|
||||
{
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
/// <summary>
|
||||
/// Damage to apply to entities that are strapped to this entity.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField("healTime", required: false)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float HealTime = 1f; // How often the bed applies the damage
|
||||
/// <summary>
|
||||
/// How frequently the damage should be applied, in seconds.
|
||||
/// </summary>
|
||||
[DataField(required: false)]
|
||||
public float HealTime = 1f;
|
||||
|
||||
[DataField("sleepMultiplier")]
|
||||
/// <summary>
|
||||
/// Damage multiplier that gets applied if the entity is sleeping.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SleepMultiplier = 3f;
|
||||
|
||||
public TimeSpan NextHealTime = TimeSpan.Zero; //Next heal
|
||||
|
||||
[DataField("sleepAction")] public EntityUid? SleepAction;
|
||||
[DataField] public EntityUid? SleepAction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Content.Server.Bed.Components
|
||||
{
|
||||
// TODO rename this component
|
||||
[RegisterComponent]
|
||||
public sealed partial class HealOnBuckleHealingComponent : Component
|
||||
{}
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Content.Server.Bed.Components
|
||||
/// <summary>
|
||||
/// What the metabolic update rate will be multiplied by (higher = slower metabolism)
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[ViewVariables(VVAccess.ReadOnly)] // Writing is is not supported. ApplyMetabolicMultiplierEvent needs to be refactored first
|
||||
[DataField]
|
||||
public float Multiplier = 10f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,9 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
Entity<BloodstreamComponent> ent,
|
||||
ref ApplyMetabolicMultiplierEvent args)
|
||||
{
|
||||
// TODO REFACTOR THIS
|
||||
// This will slowly drift over time due to floating point errors.
|
||||
// Instead, raise an event with the base rates and allow modifiers to get applied to it.
|
||||
if (args.Apply)
|
||||
{
|
||||
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace Content.Server.Body.Systems
|
||||
Entity<MetabolizerComponent> ent,
|
||||
ref ApplyMetabolicMultiplierEvent args)
|
||||
{
|
||||
// TODO REFACTOR THIS
|
||||
// This will slowly drift over time due to floating point errors.
|
||||
// Instead, raise an event with the base rates and allow modifiers to get applied to it.
|
||||
if (args.Apply)
|
||||
{
|
||||
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||
@@ -229,6 +232,9 @@ namespace Content.Server.Body.Systems
|
||||
}
|
||||
}
|
||||
|
||||
// TODO REFACTOR THIS
|
||||
// This will cause rates to slowly drift over time due to floating point errors.
|
||||
// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
|
||||
[ByRefEvent]
|
||||
public readonly record struct ApplyMetabolicMultiplierEvent(
|
||||
EntityUid Uid,
|
||||
|
||||
@@ -326,6 +326,9 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
Entity<RespiratorComponent> ent,
|
||||
ref ApplyMetabolicMultiplierEvent args)
|
||||
{
|
||||
// TODO REFACTOR THIS
|
||||
// This will slowly drift over time due to floating point errors.
|
||||
// Instead, raise an event with the base rates and allow modifiers to get applied to it.
|
||||
if (args.Apply)
|
||||
{
|
||||
ent.Comp.UpdateInterval *= args.Multiplier;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Content.Server.Buckle.Systems;
|
||||
using Content.Shared.Buckle.Components;
|
||||
|
||||
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
|
||||
|
||||
public sealed partial class UnbuckleOperator : HTNOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private BuckleSystem _buckle = default!;
|
||||
|
||||
[DataField("shutdownState")]
|
||||
@@ -21,10 +19,7 @@ public sealed partial class UnbuckleOperator : HTNOperator
|
||||
{
|
||||
base.Startup(blackboard);
|
||||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
if (!_entManager.TryGetComponent<BuckleComponent>(owner, out var buckle) || !buckle.Buckled)
|
||||
return;
|
||||
|
||||
_buckle.TryUnbuckle(owner, owner, true, buckle);
|
||||
_buckle.Unbuckle(owner, null);
|
||||
}
|
||||
|
||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Examine;
|
||||
@@ -59,6 +60,15 @@ public sealed partial class SleepingSystem : EntitySystem
|
||||
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
|
||||
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
|
||||
}
|
||||
|
||||
private void OnUnbuckleAttempt(Entity<SleepingComponent> ent, ref UnbuckleAttemptEvent args)
|
||||
{
|
||||
// TODO is this necessary?
|
||||
// Shouldn't the interaction have already been blocked by a general interaction check?
|
||||
if (ent.Owner == args.User)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnBedSleepAction(Entity<ActionsContainerComponent> ent, ref SleepActionEvent args)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Buckle.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
/// <summary>
|
||||
/// This component allows an entity to be buckled to an entity with a <see cref="StrapComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedBuckleSystem))]
|
||||
public sealed partial class BuckleComponent : Component
|
||||
{
|
||||
@@ -14,31 +19,23 @@ public sealed partial class BuckleComponent : Component
|
||||
/// across a table two tiles away" problem.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Range = SharedInteractionSystem.InteractionRange / 1.4f;
|
||||
|
||||
/// <summary>
|
||||
/// True if the entity is buckled, false otherwise.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public bool Buckled;
|
||||
|
||||
[ViewVariables]
|
||||
[AutoNetworkedField]
|
||||
public EntityUid? LastEntityBuckledTo;
|
||||
[MemberNotNullWhen(true, nameof(BuckledTo))]
|
||||
public bool Buckled => BuckledTo != null;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not collisions should be possible with the entity we are strapped to
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
[DataField]
|
||||
public bool DontCollide;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not we should be allowed to pull the entity we are strapped to
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool PullStrap;
|
||||
|
||||
@@ -47,20 +44,18 @@ public sealed partial class BuckleComponent : Component
|
||||
/// be able to unbuckle after recently buckling.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(0.25f);
|
||||
|
||||
/// <summary>
|
||||
/// The time that this entity buckled at.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan BuckleTime;
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? BuckleTime;
|
||||
|
||||
/// <summary>
|
||||
/// The strap that this component is buckled to.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[AutoNetworkedField]
|
||||
[DataField]
|
||||
public EntityUid? BuckledTo;
|
||||
|
||||
/// <summary>
|
||||
@@ -68,7 +63,6 @@ public sealed partial class BuckleComponent : Component
|
||||
/// <see cref="StrapComponent"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Size = 100;
|
||||
|
||||
/// <summary>
|
||||
@@ -77,11 +71,90 @@ public sealed partial class BuckleComponent : Component
|
||||
[ViewVariables] public int? OriginalDrawDepth;
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct BuckleAttemptEvent(EntityUid StrapEntity, EntityUid BuckledEntity, EntityUid UserEntity, bool Buckling, bool Cancelled = false);
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BuckleState(NetEntity? buckledTo, bool dontCollide, TimeSpan? buckleTime) : ComponentState
|
||||
{
|
||||
public readonly NetEntity? BuckledTo = buckledTo;
|
||||
public readonly bool DontCollide = dontCollide;
|
||||
public readonly TimeSpan? BuckleTime = buckleTime;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a strap entity before some entity gets buckled to it.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BuckleChangeEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling);
|
||||
public record struct StrapAttemptEvent(
|
||||
Entity<StrapComponent> Strap,
|
||||
Entity<BuckleComponent> Buckle,
|
||||
EntityUid? User,
|
||||
bool Popup)
|
||||
{
|
||||
public bool Cancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a buckle entity before it gets buckled to some strap entity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct BuckleAttemptEvent(
|
||||
Entity<StrapComponent> Strap,
|
||||
Entity<BuckleComponent> Buckle,
|
||||
EntityUid? User,
|
||||
bool Popup)
|
||||
{
|
||||
public bool Cancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a strap entity before some entity gets unbuckled from it.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct UnstrapAttemptEvent(
|
||||
Entity<StrapComponent> Strap,
|
||||
Entity<BuckleComponent> Buckle,
|
||||
EntityUid? User,
|
||||
bool Popup)
|
||||
{
|
||||
public bool Cancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a buckle entity before it gets unbuckled.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct UnbuckleAttemptEvent(
|
||||
Entity<StrapComponent> Strap,
|
||||
Entity<BuckleComponent> Buckle,
|
||||
EntityUid? User,
|
||||
bool Popup)
|
||||
{
|
||||
public bool Cancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a strap entity after something has been buckled to it.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct StrappedEvent(Entity<StrapComponent> Strap, Entity<BuckleComponent> Buckle);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a buckle entity after it has been buckled.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BuckledEvent(Entity<StrapComponent> Strap, Entity<BuckleComponent> Buckle);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a strap entity after something has been unbuckled from it.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct UnstrappedEvent(Entity<StrapComponent> Strap, Entity<BuckleComponent> Buckle);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised directed at a buckle entity after it has been unbuckled from some strap entity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct UnbuckledEvent(Entity<StrapComponent> Strap, Entity<BuckleComponent> Buckle);
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BuckleVisuals
|
||||
|
||||
@@ -13,117 +13,77 @@ namespace Content.Shared.Buckle.Components;
|
||||
public sealed partial class StrapComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entities that are currently buckled
|
||||
/// The entities that are currently buckled to this strap.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[ViewVariables] // TODO serialization
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> BuckledEntities = new();
|
||||
|
||||
/// <summary>
|
||||
/// Entities that this strap accepts and can buckle
|
||||
/// If null it accepts any entity
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Entities that this strap does not accept and cannot buckle.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// The change in position to the strapped mob
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public StrapPosition Position = StrapPosition.None;
|
||||
|
||||
/// <summary>
|
||||
/// The distance above which a buckled entity will be automatically unbuckled.
|
||||
/// Don't change it unless you really have to
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Dont set this below 0.2 because that causes audio issues with <see cref="SharedBuckleSystem.OnBuckleMove"/>
|
||||
/// My guess after testing is that the client sets BuckledTo to the strap in *some* ticks for some reason
|
||||
/// whereas the server doesnt, thus the client tries to unbuckle like 15 times because it passes the strap null check
|
||||
/// This is why this needs to be above 0.1 to make the InRange check fail in both client and server.
|
||||
/// </remarks>
|
||||
[DataField, AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxBuckleDistance = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets and clamps the buckle offset to MaxBuckleDistance
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Vector2 BuckleOffsetClamped => Vector2.Clamp(
|
||||
BuckleOffset,
|
||||
Vector2.One * -MaxBuckleDistance,
|
||||
Vector2.One * MaxBuckleDistance);
|
||||
|
||||
/// <summary>
|
||||
/// The buckled entity will be offset by this amount from the center of the strap object.
|
||||
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 BuckleOffset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The angle to rotate the player by when they get strapped
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle Rotation;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the strap which is compared against when buckling entities
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Size = 100;
|
||||
|
||||
/// <summary>
|
||||
/// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// You can specify the offset the entity will have after unbuckling.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 UnbuckleOffset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The sound to be played when a mob is buckled
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier BuckleSound = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound to be played when a mob is unbuckled
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// ID of the alert to show when buckled
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ProtoId<AlertPrototype> BuckledAlertType = "Buckled";
|
||||
|
||||
/// <summary>
|
||||
/// The sum of the sizes of all the buckled entities in this strap
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[ViewVariables]
|
||||
public int OccupiedSize;
|
||||
}
|
||||
|
||||
public enum StrapPosition
|
||||
|
||||
@@ -1,39 +1,46 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Pulling.Events;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent;
|
||||
|
||||
namespace Content.Shared.Buckle;
|
||||
|
||||
public abstract partial class SharedBuckleSystem
|
||||
{
|
||||
public static ProtoId<AlertCategoryPrototype> BuckledAlertCategory = "Buckled";
|
||||
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
|
||||
private void InitializeBuckle()
|
||||
{
|
||||
SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleComponentStartup);
|
||||
SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleComponentShutdown);
|
||||
SubscribeLocalEvent<BuckleComponent, MoveEvent>(OnBuckleMove);
|
||||
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(OnBuckleInteractHand);
|
||||
SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
|
||||
SubscribeLocalEvent<BuckleComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<BuckleComponent, EntGotInsertedIntoContainerMessage>(OnInserted);
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, StartPullAttemptEvent>(OnPullAttempt);
|
||||
SubscribeLocalEvent<BuckleComponent, BeingPulledAttemptEvent>(OnBeingPulledAttempt);
|
||||
SubscribeLocalEvent<BuckleComponent, PullStartedMessage>(OnPullStarted);
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnBuckleInsertIntoEntityStorageAttempt);
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(OnBucklePreventCollide);
|
||||
@@ -41,68 +48,92 @@ public abstract partial class SharedBuckleSystem
|
||||
SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(OnBuckleStandAttempt);
|
||||
SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(OnBuckleThrowPushbackAttempt);
|
||||
SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(OnBuckleUpdateCanMove);
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnGetState);
|
||||
}
|
||||
|
||||
[ValidatePrototypeId<AlertCategoryPrototype>]
|
||||
public const string BuckledAlertCategory = "Buckled";
|
||||
|
||||
private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
|
||||
private void OnGetState(Entity<BuckleComponent> ent, ref ComponentGetState args)
|
||||
{
|
||||
UpdateBuckleStatus(uid, component);
|
||||
args.State = new BuckleState(GetNetEntity(ent.Comp.BuckledTo), ent.Comp.DontCollide, ent.Comp.BuckleTime);
|
||||
}
|
||||
|
||||
private void OnBuckleComponentShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args)
|
||||
private void OnBuckleComponentShutdown(Entity<BuckleComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
TryUnbuckle(uid, uid, true, component);
|
||||
|
||||
component.BuckleTime = default;
|
||||
Unbuckle(ent!, null);
|
||||
}
|
||||
|
||||
private void OnBuckleMove(EntityUid uid, BuckleComponent component, ref MoveEvent ev)
|
||||
#region Pulling
|
||||
|
||||
private void OnPullAttempt(Entity<BuckleComponent> ent, ref StartPullAttemptEvent args)
|
||||
{
|
||||
if (component.BuckledTo is not { } strapUid)
|
||||
// Prevent people pulling the chair they're on, etc.
|
||||
if (ent.Comp.BuckledTo == args.Pulled && !ent.Comp.PullStrap)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnBeingPulledAttempt(Entity<BuckleComponent> ent, ref BeingPulledAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled || !ent.Comp.Buckled)
|
||||
return;
|
||||
|
||||
if (!CanUnbuckle(ent!, args.Puller, false))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPullStarted(Entity<BuckleComponent> ent, ref PullStartedMessage args)
|
||||
{
|
||||
Unbuckle(ent!, args.PullerUid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transform
|
||||
|
||||
private void OnParentChanged(Entity<BuckleComponent> ent, ref EntParentChangedMessage args)
|
||||
{
|
||||
BuckleTransformCheck(ent, args.Transform);
|
||||
}
|
||||
|
||||
private void OnInserted(Entity<BuckleComponent> ent, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
BuckleTransformCheck(ent, Transform(ent));
|
||||
}
|
||||
|
||||
private void OnBuckleMove(Entity<BuckleComponent> ent, ref MoveEvent ev)
|
||||
{
|
||||
BuckleTransformCheck(ent, ev.Component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the entity should get unbuckled as a result of transform or container changes.
|
||||
/// </summary>
|
||||
private void BuckleTransformCheck(Entity<BuckleComponent> buckle, TransformComponent xform)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
if (buckle.Comp.BuckledTo is not { } strapUid)
|
||||
return;
|
||||
|
||||
if (!TryComp<StrapComponent>(strapUid, out var strapComp))
|
||||
{
|
||||
Log.Error($"Encountered buckle entity {ToPrettyString(buckle)} without a valid strap entity {ToPrettyString(strapUid)}");
|
||||
SetBuckledTo(buckle, null);
|
||||
return;
|
||||
|
||||
var strapPosition = Transform(strapUid).Coordinates;
|
||||
if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance))
|
||||
return;
|
||||
|
||||
TryUnbuckle(uid, uid, true, component);
|
||||
}
|
||||
|
||||
private void OnBuckleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
|
||||
if (xform.ParentUid != strapUid || _container.IsEntityInContainer(buckle))
|
||||
{
|
||||
if (!component.Buckled)
|
||||
Unbuckle(buckle, (strapUid, strapComp), null);
|
||||
return;
|
||||
|
||||
if (TryUnbuckle(uid, args.User, buckleComp: component))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !component.Buckled)
|
||||
return;
|
||||
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () => TryUnbuckle(uid, args.User, buckleComp: component),
|
||||
Text = Loc.GetString("verb-categories-unbuckle"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"))
|
||||
};
|
||||
|
||||
if (args.Target == args.User && args.Using == null)
|
||||
{
|
||||
// A user is left clicking themselves with an empty hand, while buckled.
|
||||
// It is very likely they are trying to unbuckle themselves.
|
||||
verb.Priority = 1;
|
||||
var delta = (xform.LocalPosition - strapComp.BuckleOffset).LengthSquared();
|
||||
if (delta > 1e-5)
|
||||
Unbuckle(buckle, (strapUid, strapComp), null);
|
||||
}
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void OnBuckleInsertIntoEntityStorageAttempt(EntityUid uid, BuckleComponent component, ref InsertIntoEntityStorageAttemptEvent args)
|
||||
{
|
||||
@@ -112,10 +143,7 @@ public abstract partial class SharedBuckleSystem
|
||||
|
||||
private void OnBucklePreventCollide(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
|
||||
{
|
||||
if (args.OtherEntity != component.BuckledTo)
|
||||
return;
|
||||
|
||||
if (component.Buckled || component.DontCollide)
|
||||
if (args.OtherEntity == component.BuckledTo && component.DontCollide)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
@@ -139,10 +167,7 @@ public abstract partial class SharedBuckleSystem
|
||||
|
||||
private void OnBuckleUpdateCanMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
|
||||
{
|
||||
if (component.LifeStage > ComponentLifeStage.Running)
|
||||
return;
|
||||
|
||||
if (component.Buckled) // buckle shitcode
|
||||
if (component.Buckled)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
@@ -151,162 +176,139 @@ public abstract partial class SharedBuckleSystem
|
||||
return Resolve(uid, ref component, false) && component.Buckled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows or hides the buckled status effect depending on if the
|
||||
/// entity is buckled or not.
|
||||
/// </summary>
|
||||
/// <param name="uid"> Entity that we want to show the alert </param>
|
||||
/// <param name="buckleComp"> buckle component of the entity </param>
|
||||
/// <param name="strapComp"> strap component of the thing we are strapping to </param>
|
||||
private void UpdateBuckleStatus(EntityUid uid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
|
||||
protected void SetBuckledTo(Entity<BuckleComponent> buckle, Entity<StrapComponent?>? strap)
|
||||
{
|
||||
Appearance.SetData(uid, StrapVisuals.State, buckleComp.Buckled);
|
||||
if (buckleComp.BuckledTo != null)
|
||||
{
|
||||
if (!Resolve(buckleComp.BuckledTo.Value, ref strapComp))
|
||||
return;
|
||||
if (TryComp(buckle.Comp.BuckledTo, out StrapComponent? old))
|
||||
old.BuckledEntities.Remove(buckle);
|
||||
|
||||
var alertType = strapComp.BuckledAlertType;
|
||||
_alerts.ShowAlert(uid, alertType);
|
||||
if (strap is {} strapEnt && Resolve(strapEnt.Owner, ref strapEnt.Comp))
|
||||
{
|
||||
strapEnt.Comp.BuckledEntities.Add(buckle);
|
||||
_alerts.ShowAlert(buckle, strapEnt.Comp.BuckledAlertType);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, BuckledAlertCategory);
|
||||
}
|
||||
_alerts.ClearAlertCategory(buckle, BuckledAlertCategory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="BuckleComponent.BuckledTo"/> field in the component to a value
|
||||
/// </summary>
|
||||
/// <param name="strapUid"> Value tat with be assigned to the field </param>
|
||||
private void SetBuckledTo(EntityUid buckleUid, EntityUid? strapUid, StrapComponent? strapComp, BuckleComponent buckleComp)
|
||||
{
|
||||
buckleComp.BuckledTo = strapUid;
|
||||
|
||||
if (strapUid == null)
|
||||
{
|
||||
buckleComp.Buckled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
buckleComp.LastEntityBuckledTo = strapUid;
|
||||
buckleComp.DontCollide = true;
|
||||
buckleComp.Buckled = true;
|
||||
buckleComp.BuckleTime = _gameTiming.CurTime;
|
||||
}
|
||||
|
||||
ActionBlocker.UpdateCanMove(buckleUid);
|
||||
UpdateBuckleStatus(buckleUid, buckleComp, strapComp);
|
||||
Dirty(buckleUid, buckleComp);
|
||||
buckle.Comp.BuckledTo = strap;
|
||||
buckle.Comp.BuckleTime = _gameTiming.CurTime;
|
||||
ActionBlocker.UpdateCanMove(buckle);
|
||||
Appearance.SetData(buckle, StrapVisuals.State, buckle.Comp.Buckled);
|
||||
Dirty(buckle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not buckling is possible
|
||||
/// </summary>
|
||||
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
|
||||
/// <param name="userUid">
|
||||
/// <param name="user">
|
||||
/// Uid of a third party entity,
|
||||
/// i.e, the uid of someone else you are dragging to a chair.
|
||||
/// Can equal buckleUid sometimes
|
||||
/// </param>
|
||||
/// <param name="strapUid"> Uid of the owner of strap component </param>
|
||||
private bool CanBuckle(
|
||||
EntityUid buckleUid,
|
||||
EntityUid userUid,
|
||||
/// <param name="strapComp"></param>
|
||||
/// <param name="buckleComp"></param>
|
||||
private bool CanBuckle(EntityUid buckleUid,
|
||||
EntityUid? user,
|
||||
EntityUid strapUid,
|
||||
bool popup,
|
||||
[NotNullWhen(true)] out StrapComponent? strapComp,
|
||||
BuckleComponent? buckleComp = null)
|
||||
BuckleComponent buckleComp)
|
||||
{
|
||||
strapComp = null;
|
||||
|
||||
if (userUid == strapUid ||
|
||||
!Resolve(buckleUid, ref buckleComp, false) ||
|
||||
!Resolve(strapUid, ref strapComp, false))
|
||||
{
|
||||
if (!Resolve(strapUid, ref strapComp, false))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does it pass the Whitelist
|
||||
if (_whitelistSystem.IsWhitelistFail(strapComp.Whitelist, buckleUid) ||
|
||||
_whitelistSystem.IsBlacklistPass(strapComp.Blacklist, buckleUid))
|
||||
{
|
||||
if (_netManager.IsServer)
|
||||
_popup.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium);
|
||||
if (_netManager.IsServer && popup && user != null)
|
||||
_popup.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), user.Value, user.Value, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is it within range
|
||||
bool Ignored(EntityUid entity) => entity == buckleUid || entity == userUid || entity == strapUid;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(buckleUid, strapUid, buckleComp.Range, predicate: Ignored,
|
||||
if (!_interaction.InRangeUnobstructed(buckleUid,
|
||||
strapUid,
|
||||
buckleComp.Range,
|
||||
predicate: entity => entity == buckleUid || entity == user || entity == strapUid,
|
||||
popup: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If in a container
|
||||
if (_container.TryGetContainingContainer(buckleUid, out var ownerContainer))
|
||||
{
|
||||
// And not in the same container as the strap
|
||||
if (!_container.TryGetContainingContainer(strapUid, out var strapContainer) ||
|
||||
ownerContainer != strapContainer)
|
||||
{
|
||||
if (!_container.IsInSameOrNoContainer((buckleUid, null, null), (strapUid, null, null)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasComp<HandsComponent>(userUid))
|
||||
if (user != null && !HasComp<HandsComponent>(user))
|
||||
{
|
||||
// PopupPredicted when
|
||||
if (_netManager.IsServer)
|
||||
_popup.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), userUid, userUid);
|
||||
if (_netManager.IsServer && popup)
|
||||
_popup.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), user.Value, user.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buckleComp.Buckled)
|
||||
{
|
||||
var message = Loc.GetString(buckleUid == userUid
|
||||
if (_netManager.IsClient || popup || user == null)
|
||||
return false;
|
||||
|
||||
var message = Loc.GetString(buckleUid == user
|
||||
? "buckle-component-already-buckled-message"
|
||||
: "buckle-component-other-already-buckled-message",
|
||||
("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||
if (_netManager.IsServer)
|
||||
_popup.PopupEntity(message, userUid, userUid);
|
||||
|
||||
_popup.PopupEntity(message, user.Value, user.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether someone is attempting to buckle something to their own child
|
||||
var parent = Transform(strapUid).ParentUid;
|
||||
while (parent.IsValid())
|
||||
{
|
||||
if (parent == userUid)
|
||||
if (parent != buckleUid)
|
||||
{
|
||||
var message = Loc.GetString(buckleUid == userUid
|
||||
? "buckle-component-cannot-buckle-message"
|
||||
: "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||
if (_netManager.IsServer)
|
||||
_popup.PopupEntity(message, userUid, userUid);
|
||||
|
||||
return false;
|
||||
parent = Transform(parent).ParentUid;
|
||||
continue;
|
||||
}
|
||||
|
||||
parent = Transform(parent).ParentUid;
|
||||
if (_netManager.IsClient || popup || user == null)
|
||||
return false;
|
||||
|
||||
var message = Loc.GetString(buckleUid == user
|
||||
? "buckle-component-cannot-buckle-message"
|
||||
: "buckle-component-other-cannot-buckle-message",
|
||||
("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||
|
||||
_popup.PopupEntity(message, user.Value, user.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StrapHasSpace(strapUid, buckleComp, strapComp))
|
||||
{
|
||||
var message = Loc.GetString(buckleUid == userUid
|
||||
if (_netManager.IsClient || popup || user == null)
|
||||
return false;
|
||||
|
||||
var message = Loc.GetString(buckleUid == user
|
||||
? "buckle-component-cannot-fit-message"
|
||||
: "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||
if (_netManager.IsServer)
|
||||
_popup.PopupEntity(message, userUid, userUid);
|
||||
: "buckle-component-other-cannot-fit-message",
|
||||
("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||
|
||||
_popup.PopupEntity(message, user.Value, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, userUid, true);
|
||||
RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
|
||||
RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
|
||||
if (attemptEvent.Cancelled)
|
||||
var buckleAttempt = new BuckleAttemptEvent((strapUid, strapComp), (buckleUid, buckleComp), user, popup);
|
||||
RaiseLocalEvent(buckleUid, ref buckleAttempt);
|
||||
if (buckleAttempt.Cancelled)
|
||||
return false;
|
||||
|
||||
var strapAttempt = new StrapAttemptEvent((strapUid, strapComp), (buckleUid, buckleComp), user, popup);
|
||||
RaiseLocalEvent(strapUid, ref strapAttempt);
|
||||
if (strapAttempt.Cancelled)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -315,216 +317,194 @@ public abstract partial class SharedBuckleSystem
|
||||
/// <summary>
|
||||
/// Attempts to buckle an entity to a strap
|
||||
/// </summary>
|
||||
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
|
||||
/// <param name="userUid">
|
||||
/// <param name="buckle"> Uid of the owner of BuckleComponent </param>
|
||||
/// <param name="user">
|
||||
/// Uid of a third party entity,
|
||||
/// i.e, the uid of someone else you are dragging to a chair.
|
||||
/// Can equal buckleUid sometimes
|
||||
/// </param>
|
||||
/// <param name="strapUid"> Uid of the owner of strap component </param>
|
||||
public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid, BuckleComponent? buckleComp = null)
|
||||
/// <param name="strap"> Uid of the owner of strap component </param>
|
||||
public bool TryBuckle(EntityUid buckle, EntityUid? user, EntityUid strap, BuckleComponent? buckleComp = null, bool popup = true)
|
||||
{
|
||||
if (!Resolve(buckleUid, ref buckleComp, false))
|
||||
if (!Resolve(buckle, ref buckleComp, false))
|
||||
return false;
|
||||
|
||||
if (!CanBuckle(buckleUid, userUid, strapUid, out var strapComp, buckleComp))
|
||||
if (!CanBuckle(buckle, user, strap, popup, out var strapComp, buckleComp))
|
||||
return false;
|
||||
|
||||
if (!StrapTryAdd(strapUid, buckleUid, buckleComp, false, strapComp))
|
||||
{
|
||||
var message = Loc.GetString(buckleUid == userUid
|
||||
? "buckle-component-cannot-buckle-message"
|
||||
: "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||
if (_netManager.IsServer)
|
||||
_popup.PopupEntity(message, userUid, userUid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<AppearanceComponent>(buckleUid, out var appearance))
|
||||
Appearance.SetData(buckleUid, BuckleVisuals.Buckled, true, appearance);
|
||||
|
||||
_rotationVisuals.SetHorizontalAngle(buckleUid, strapComp.Rotation);
|
||||
|
||||
ReAttach(buckleUid, strapUid, buckleComp, strapComp);
|
||||
SetBuckledTo(buckleUid, strapUid, strapComp, buckleComp);
|
||||
// TODO user is currently set to null because if it isn't the sound fails to play in some situations, fix that
|
||||
_audio.PlayPredicted(strapComp.BuckleSound, strapUid, userUid);
|
||||
|
||||
var ev = new BuckleChangeEvent(strapUid, buckleUid, true);
|
||||
RaiseLocalEvent(ev.BuckledEntity, ref ev);
|
||||
RaiseLocalEvent(ev.StrapEntity, ref ev);
|
||||
|
||||
if (TryComp<PullableComponent>(buckleUid, out var ownerPullable))
|
||||
{
|
||||
if (ownerPullable.Puller != null)
|
||||
{
|
||||
_pulling.TryStopPull(buckleUid, ownerPullable);
|
||||
}
|
||||
}
|
||||
|
||||
if (TryComp<PhysicsComponent>(buckleUid, out var physics))
|
||||
{
|
||||
_physics.ResetDynamics(buckleUid, physics);
|
||||
}
|
||||
|
||||
if (!buckleComp.PullStrap && TryComp<PullableComponent>(strapUid, out var toPullable))
|
||||
{
|
||||
if (toPullable.Puller == buckleUid)
|
||||
{
|
||||
// can't pull it and buckle to it at the same time
|
||||
_pulling.TryStopPull(strapUid, toPullable);
|
||||
}
|
||||
}
|
||||
|
||||
// Logging
|
||||
if (userUid != buckleUid)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} buckled {ToPrettyString(buckleUid)} to {ToPrettyString(strapUid)}");
|
||||
else
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} buckled themselves to {ToPrettyString(strapUid)}");
|
||||
|
||||
Buckle((buckle, buckleComp), (strap, strapComp), user);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Buckle(Entity<BuckleComponent> buckle, Entity<StrapComponent> strap, EntityUid? user)
|
||||
{
|
||||
if (user == buckle.Owner)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled themselves to {ToPrettyString(strap)}");
|
||||
else if (user != null)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled {ToPrettyString(buckle)} to {ToPrettyString(strap)}");
|
||||
|
||||
_audio.PlayPredicted(strap.Comp.BuckleSound, strap, user);
|
||||
|
||||
SetBuckledTo(buckle, strap!);
|
||||
Appearance.SetData(strap, StrapVisuals.State, true);
|
||||
Appearance.SetData(buckle, BuckleVisuals.Buckled, true);
|
||||
|
||||
_rotationVisuals.SetHorizontalAngle(buckle.Owner, strap.Comp.Rotation);
|
||||
|
||||
var xform = Transform(buckle);
|
||||
var coords = new EntityCoordinates(strap, strap.Comp.BuckleOffset);
|
||||
_transform.SetCoordinates(buckle, xform, coords, rotation: Angle.Zero);
|
||||
|
||||
_joints.SetRelay(buckle, strap);
|
||||
|
||||
switch (strap.Comp.Position)
|
||||
{
|
||||
case StrapPosition.Stand:
|
||||
_standing.Stand(buckle);
|
||||
break;
|
||||
case StrapPosition.Down:
|
||||
_standing.Down(buckle, false, false);
|
||||
break;
|
||||
}
|
||||
|
||||
var ev = new StrappedEvent(strap, buckle);
|
||||
RaiseLocalEvent(strap, ref ev);
|
||||
|
||||
var gotEv = new BuckledEvent(strap, buckle);
|
||||
RaiseLocalEvent(buckle, ref gotEv);
|
||||
|
||||
if (TryComp<PhysicsComponent>(buckle, out var physics))
|
||||
_physics.ResetDynamics(buckle, physics);
|
||||
|
||||
DebugTools.AssertEqual(xform.ParentUid, strap.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to unbuckle the Owner of this component from its current strap.
|
||||
/// </summary>
|
||||
/// <param name="buckleUid">The entity to unbuckle.</param>
|
||||
/// <param name="userUid">The entity doing the unbuckling.</param>
|
||||
/// <param name="force">
|
||||
/// Whether to force the unbuckling or not. Does not guarantee true to
|
||||
/// be returned, but guarantees the owner to be unbuckled afterwards.
|
||||
/// </param>
|
||||
/// <param name="user">The entity doing the unbuckling.</param>
|
||||
/// <param name="buckleComp">The buckle component of the entity to unbuckle.</param>
|
||||
/// <returns>
|
||||
/// true if the owner was unbuckled, otherwise false even if the owner
|
||||
/// was previously already unbuckled.
|
||||
/// </returns>
|
||||
public bool TryUnbuckle(EntityUid buckleUid, EntityUid userUid, bool force = false, BuckleComponent? buckleComp = null)
|
||||
public bool TryUnbuckle(EntityUid buckleUid,
|
||||
EntityUid? user,
|
||||
BuckleComponent? buckleComp = null,
|
||||
bool popup = true)
|
||||
{
|
||||
if (!Resolve(buckleUid, ref buckleComp, false) ||
|
||||
buckleComp.BuckledTo is not { } strapUid)
|
||||
return false;
|
||||
|
||||
if (!force)
|
||||
{
|
||||
var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, userUid, false);
|
||||
RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
|
||||
RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
|
||||
if (attemptEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
if (_gameTiming.CurTime < buckleComp.BuckleTime + buckleComp.Delay)
|
||||
return false;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(userUid, strapUid, buckleComp.Range, popup: true))
|
||||
return false;
|
||||
|
||||
if (HasComp<SleepingComponent>(buckleUid) && buckleUid == userUid)
|
||||
return false;
|
||||
|
||||
// If the person is crit or dead in any kind of strap, return. This prevents people from unbuckling themselves while incapacitated.
|
||||
if (_mobState.IsIncapacitated(buckleUid) && userUid == buckleUid)
|
||||
return false;
|
||||
return TryUnbuckle((buckleUid, buckleComp), user, popup);
|
||||
}
|
||||
|
||||
// Logging
|
||||
if (userUid != buckleUid)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} unbuckled {ToPrettyString(buckleUid)} from {ToPrettyString(strapUid)}");
|
||||
else
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} unbuckled themselves from {ToPrettyString(strapUid)}");
|
||||
|
||||
SetBuckledTo(buckleUid, null, null, buckleComp);
|
||||
|
||||
if (!TryComp<StrapComponent>(strapUid, out var strapComp))
|
||||
public bool TryUnbuckle(Entity<BuckleComponent?> buckle, EntityUid? user, bool popup)
|
||||
{
|
||||
if (!Resolve(buckle.Owner, ref buckle.Comp))
|
||||
return false;
|
||||
|
||||
var buckleXform = Transform(buckleUid);
|
||||
var oldBuckledXform = Transform(strapUid);
|
||||
|
||||
if (buckleXform.ParentUid == strapUid && !Terminating(buckleXform.ParentUid))
|
||||
{
|
||||
_container.AttachParentToContainerOrGrid((buckleUid, buckleXform));
|
||||
|
||||
var oldBuckledToWorldRot = _transform.GetWorldRotation(strapUid);
|
||||
_transform.SetWorldRotation(buckleXform, oldBuckledToWorldRot);
|
||||
|
||||
if (strapComp.UnbuckleOffset != Vector2.Zero)
|
||||
buckleXform.Coordinates = oldBuckledXform.Coordinates.Offset(strapComp.UnbuckleOffset);
|
||||
}
|
||||
|
||||
if (TryComp(buckleUid, out AppearanceComponent? appearance))
|
||||
Appearance.SetData(buckleUid, BuckleVisuals.Buckled, false, appearance);
|
||||
_rotationVisuals.ResetHorizontalAngle(buckleUid);
|
||||
|
||||
if (TryComp<MobStateComponent>(buckleUid, out var mobState)
|
||||
&& _mobState.IsIncapacitated(buckleUid, mobState)
|
||||
|| HasComp<KnockedDownComponent>(buckleUid))
|
||||
{
|
||||
_standing.Down(buckleUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_standing.Stand(buckleUid);
|
||||
}
|
||||
|
||||
if (_mobState.IsIncapacitated(buckleUid, mobState))
|
||||
{
|
||||
_standing.Down(buckleUid);
|
||||
}
|
||||
if (strapComp.BuckledEntities.Remove(buckleUid))
|
||||
{
|
||||
strapComp.OccupiedSize -= buckleComp.Size;
|
||||
Dirty(strapUid, strapComp);
|
||||
}
|
||||
|
||||
_joints.RefreshRelay(buckleUid);
|
||||
Appearance.SetData(strapUid, StrapVisuals.State, strapComp.BuckledEntities.Count != 0);
|
||||
|
||||
// TODO: Buckle listening to moveevents is sussy anyway.
|
||||
if (!TerminatingOrDeleted(strapUid))
|
||||
_audio.PlayPredicted(strapComp.UnbuckleSound, strapUid, userUid);
|
||||
|
||||
var ev = new BuckleChangeEvent(strapUid, buckleUid, false);
|
||||
RaiseLocalEvent(buckleUid, ref ev);
|
||||
RaiseLocalEvent(strapUid, ref ev);
|
||||
if (!CanUnbuckle(buckle, user, popup, out var strap))
|
||||
return false;
|
||||
|
||||
Unbuckle(buckle!, strap, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes an entity toggle the buckling status of the owner to a
|
||||
/// specific entity.
|
||||
/// </summary>
|
||||
/// <param name="buckleUid">The entity to buckle/unbuckle from <see cref="to"/>.</param>
|
||||
/// <param name="userUid">The entity doing the buckling/unbuckling.</param>
|
||||
/// <param name="strapUid">
|
||||
/// The entity to toggle the buckle status of the owner to.
|
||||
/// </param>
|
||||
/// <param name="force">
|
||||
/// Whether to force the unbuckling or not, if it happens. Does not
|
||||
/// guarantee true to be returned, but guarantees the owner to be
|
||||
/// unbuckled afterwards.
|
||||
/// </param>
|
||||
/// <param name="buckle">The buckle component of the entity to buckle/unbuckle from <see cref="to"/>.</param>
|
||||
/// <returns>true if the buckling status was changed, false otherwise.</returns>
|
||||
public bool ToggleBuckle(
|
||||
EntityUid buckleUid,
|
||||
EntityUid userUid,
|
||||
EntityUid strapUid,
|
||||
bool force = false,
|
||||
BuckleComponent? buckle = null)
|
||||
public void Unbuckle(Entity<BuckleComponent?> buckle, EntityUid? user)
|
||||
{
|
||||
if (!Resolve(buckleUid, ref buckle, false))
|
||||
if (!Resolve(buckle.Owner, ref buckle.Comp, false))
|
||||
return;
|
||||
|
||||
if (buckle.Comp.BuckledTo is not { } strap)
|
||||
return;
|
||||
|
||||
if (!TryComp(strap, out StrapComponent? strapComp))
|
||||
{
|
||||
Log.Error($"Encountered buckle {ToPrettyString(buckle.Owner)} with invalid strap entity {ToPrettyString(strap)}");
|
||||
SetBuckledTo(buckle!, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Unbuckle(buckle!, (strap, strapComp), user);
|
||||
}
|
||||
|
||||
private void Unbuckle(Entity<BuckleComponent> buckle, Entity<StrapComponent> strap, EntityUid? user)
|
||||
{
|
||||
if (user == buckle.Owner)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{user} unbuckled themselves from {strap}");
|
||||
else if (user != null)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{user} unbuckled {buckle} from {strap}");
|
||||
|
||||
_audio.PlayPredicted(strap.Comp.UnbuckleSound, strap, user);
|
||||
|
||||
SetBuckledTo(buckle, null);
|
||||
|
||||
var buckleXform = Transform(buckle);
|
||||
var oldBuckledXform = Transform(strap);
|
||||
|
||||
if (buckleXform.ParentUid == strap.Owner && !Terminating(buckleXform.ParentUid))
|
||||
{
|
||||
_container.AttachParentToContainerOrGrid((buckle, buckleXform));
|
||||
|
||||
var oldBuckledToWorldRot = _transform.GetWorldRotation(strap);
|
||||
_transform.SetWorldRotation(buckleXform, oldBuckledToWorldRot);
|
||||
|
||||
if (strap.Comp.UnbuckleOffset != Vector2.Zero)
|
||||
buckleXform.Coordinates = oldBuckledXform.Coordinates.Offset(strap.Comp.UnbuckleOffset);
|
||||
}
|
||||
|
||||
_rotationVisuals.ResetHorizontalAngle(buckle.Owner);
|
||||
Appearance.SetData(strap, StrapVisuals.State, strap.Comp.BuckledEntities.Count != 0);
|
||||
Appearance.SetData(buckle, BuckleVisuals.Buckled, false);
|
||||
|
||||
if (HasComp<KnockedDownComponent>(buckle) || _mobState.IsIncapacitated(buckle))
|
||||
_standing.Down(buckle);
|
||||
else
|
||||
_standing.Stand(buckle);
|
||||
|
||||
_joints.RefreshRelay(buckle);
|
||||
|
||||
var buckleEv = new UnbuckledEvent(strap, buckle);
|
||||
RaiseLocalEvent(buckle, ref buckleEv);
|
||||
|
||||
var strapEv = new UnstrappedEvent(strap, buckle);
|
||||
RaiseLocalEvent(strap, ref strapEv);
|
||||
}
|
||||
|
||||
public bool CanUnbuckle(Entity<BuckleComponent?> buckle, EntityUid user, bool popup)
|
||||
{
|
||||
return CanUnbuckle(buckle, user, popup, out _);
|
||||
}
|
||||
|
||||
private bool CanUnbuckle(Entity<BuckleComponent?> buckle, EntityUid? user, bool popup, out Entity<StrapComponent> strap)
|
||||
{
|
||||
strap = default;
|
||||
if (!Resolve(buckle.Owner, ref buckle.Comp))
|
||||
return false;
|
||||
|
||||
if (!buckle.Buckled)
|
||||
if (buckle.Comp.BuckledTo is not { } strapUid)
|
||||
return false;
|
||||
|
||||
if (!TryComp(strapUid, out StrapComponent? strapComp))
|
||||
{
|
||||
return TryBuckle(buckleUid, userUid, strapUid, buckle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TryUnbuckle(buckleUid, userUid, force, buckle);
|
||||
Log.Error($"Encountered buckle {ToPrettyString(buckle.Owner)} with invalid strap entity {ToPrettyString(strap)}");
|
||||
SetBuckledTo(buckle!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
strap = (strapUid, strapComp);
|
||||
if (_gameTiming.CurTime < buckle.Comp.BuckleTime + buckle.Comp.Delay)
|
||||
return false;
|
||||
|
||||
if (user != null && !_interaction.InRangeUnobstructed(user.Value, strap.Owner, buckle.Comp.Range, popup: popup))
|
||||
return false;
|
||||
|
||||
var unbuckleAttempt = new UnbuckleAttemptEvent(strap, buckle!, user, popup);
|
||||
RaiseLocalEvent(buckle, ref unbuckleAttempt);
|
||||
if (unbuckleAttempt.Cancelled)
|
||||
return false;
|
||||
|
||||
var unstrapAttempt = new UnstrapAttemptEvent(strap, buckle!, user, popup);
|
||||
RaiseLocalEvent(strap, ref unstrapAttempt);
|
||||
return !unstrapAttempt.Cancelled;
|
||||
}
|
||||
}
|
||||
|
||||
171
Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs
Normal file
171
Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Buckle;
|
||||
|
||||
// Partial class containing interaction & verb event handlers
|
||||
public abstract partial class SharedBuckleSystem
|
||||
{
|
||||
private void InitializeInteraction()
|
||||
{
|
||||
SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
|
||||
SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
|
||||
SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDropTarget);
|
||||
SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnCanDropTarget);
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
|
||||
}
|
||||
|
||||
private void OnCanDropTarget(EntityUid uid, StrapComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnStrapDragDropTarget(EntityUid uid, StrapComponent component, ref DragDropTargetEvent args)
|
||||
{
|
||||
if (!StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component))
|
||||
return;
|
||||
|
||||
args.Handled = TryBuckle(args.Dragged, args.User, uid, popup: false);
|
||||
}
|
||||
|
||||
private bool StrapCanDragDropOn(
|
||||
EntityUid strapUid,
|
||||
EntityUid userUid,
|
||||
EntityUid targetUid,
|
||||
EntityUid buckleUid,
|
||||
StrapComponent? strapComp = null,
|
||||
BuckleComponent? buckleComp = null)
|
||||
{
|
||||
if (!Resolve(strapUid, ref strapComp, false) ||
|
||||
!Resolve(buckleUid, ref buckleComp, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ignored(EntityUid entity) => entity == userUid || entity == buckleUid || entity == targetUid;
|
||||
|
||||
return _interaction.InRangeUnobstructed(targetUid, buckleUid, buckleComp.Range, predicate: Ignored);
|
||||
}
|
||||
|
||||
private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp(args.User, out BuckleComponent? buckle))
|
||||
return;
|
||||
|
||||
if (buckle.BuckledTo == null)
|
||||
TryBuckle(args.User, args.User, uid, buckle, popup: true);
|
||||
else if (buckle.BuckledTo == uid)
|
||||
TryUnbuckle(args.User, args.User, buckle, popup: true);
|
||||
else
|
||||
return;
|
||||
|
||||
args.Handled = true; // This generate popups on failure.
|
||||
}
|
||||
|
||||
private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled)
|
||||
return;
|
||||
|
||||
// Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this
|
||||
// range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb.
|
||||
|
||||
// Add unstrap verbs for every strapped entity.
|
||||
foreach (var entity in component.BuckledEntities)
|
||||
{
|
||||
var buckledComp = Comp<BuckleComponent>(entity);
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
|
||||
continue;
|
||||
|
||||
var verb = new InteractionVerb()
|
||||
{
|
||||
Act = () => TryUnbuckle(entity, args.User, buckleComp: buckledComp),
|
||||
Category = VerbCategory.Unbuckle,
|
||||
Text = entity == args.User
|
||||
? Loc.GetString("verb-self-target-pronoun")
|
||||
: Identity.Name(entity, EntityManager)
|
||||
};
|
||||
|
||||
// In the event that you have more than once entity with the same name strapped to the same object,
|
||||
// these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to
|
||||
// the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by
|
||||
// appending an integer to verb.Text to distinguish the verbs.
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Add a verb to buckle the user.
|
||||
if (TryComp<BuckleComponent>(args.User, out var buckle) &&
|
||||
buckle.BuckledTo != uid &&
|
||||
args.User != uid &&
|
||||
StrapHasSpace(uid, buckle, component) &&
|
||||
_interaction.InRangeUnobstructed(args.User, args.Target, range: buckle.Range))
|
||||
{
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () => TryBuckle(args.User, args.User, args.Target, buckle),
|
||||
Category = VerbCategory.Buckle,
|
||||
Text = Loc.GetString("verb-self-target-pronoun")
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// If the user is currently holding/pulling an entity that can be buckled, add a verb for that.
|
||||
if (args.Using is { Valid: true } @using &&
|
||||
TryComp<BuckleComponent>(@using, out var usingBuckle) &&
|
||||
StrapHasSpace(uid, usingBuckle, component) &&
|
||||
_interaction.InRangeUnobstructed(@using, args.Target, range: usingBuckle.Range))
|
||||
{
|
||||
// Check that the entity is unobstructed from the target (ignoring the user).
|
||||
bool Ignored(EntityUid entity) => entity == args.User || entity == args.Target || entity == @using;
|
||||
if (!_interaction.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
|
||||
return;
|
||||
|
||||
var isPlayer = _playerManager.TryGetSessionByEntity(@using, out var _);
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () => TryBuckle(@using, args.User, args.Target, usingBuckle),
|
||||
Category = VerbCategory.Buckle,
|
||||
Text = Identity.Name(@using, EntityManager),
|
||||
// just a held object, the user is probably just trying to sit down.
|
||||
// If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
|
||||
Priority = isPlayer ? 1 : -1
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !component.Buckled)
|
||||
return;
|
||||
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () => TryUnbuckle(uid, args.User, buckleComp: component),
|
||||
Text = Loc.GetString("verb-categories-unbuckle"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"))
|
||||
};
|
||||
|
||||
if (args.Target == args.User && args.Using == null)
|
||||
{
|
||||
// A user is left clicking themselves with an empty hand, while buckled.
|
||||
// It is very likely they are trying to unbuckle themselves.
|
||||
verb.Priority = 1;
|
||||
}
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,39 +2,25 @@
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Foldable;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Rotation;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Buckle;
|
||||
|
||||
public abstract partial class SharedBuckleSystem
|
||||
{
|
||||
[Dependency] private readonly SharedRotationVisualsSystem _rotationVisuals = default!;
|
||||
|
||||
private void InitializeStrap()
|
||||
{
|
||||
SubscribeLocalEvent<StrapComponent, ComponentStartup>(OnStrapStartup);
|
||||
SubscribeLocalEvent<StrapComponent, ComponentShutdown>(OnStrapShutdown);
|
||||
SubscribeLocalEvent<StrapComponent, ComponentRemove>((e, c, _) => StrapRemoveAll(e, c));
|
||||
|
||||
SubscribeLocalEvent<StrapComponent, EntInsertedIntoContainerMessage>(OnStrapEntModifiedFromContainer);
|
||||
SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(OnStrapEntModifiedFromContainer);
|
||||
SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
|
||||
SubscribeLocalEvent<StrapComponent, ContainerGettingInsertedAttemptEvent>(OnStrapContainerGettingInsertedAttempt);
|
||||
SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
|
||||
SubscribeLocalEvent<StrapComponent, DestructionEventArgs>((e, c, _) => StrapRemoveAll(e, c));
|
||||
SubscribeLocalEvent<StrapComponent, BreakageEventArgs>((e, c, _) => StrapRemoveAll(e, c));
|
||||
|
||||
SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDropTarget);
|
||||
SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnCanDropTarget);
|
||||
SubscribeLocalEvent<StrapComponent, FoldAttemptEvent>(OnAttemptFold);
|
||||
|
||||
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
|
||||
SubscribeLocalEvent<StrapComponent, MachineDeconstructedEvent>((e, c, _) => StrapRemoveAll(e, c));
|
||||
}
|
||||
|
||||
@@ -45,145 +31,17 @@ public abstract partial class SharedBuckleSystem
|
||||
|
||||
private void OnStrapShutdown(EntityUid uid, StrapComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (LifeStage(uid) > EntityLifeStage.MapInitialized)
|
||||
return;
|
||||
|
||||
if (!TerminatingOrDeleted(uid))
|
||||
StrapRemoveAll(uid, component);
|
||||
}
|
||||
|
||||
private void OnStrapEntModifiedFromContainer(EntityUid uid, StrapComponent component, ContainerModifiedMessage message)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
foreach (var buckledEntity in component.BuckledEntities)
|
||||
{
|
||||
if (!TryComp<BuckleComponent>(buckledEntity, out var buckleComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ContainerModifiedReAttach(buckledEntity, uid, buckleComp, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void ContainerModifiedReAttach(EntityUid buckleUid, EntityUid strapUid, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null)
|
||||
{
|
||||
if (!Resolve(buckleUid, ref buckleComp, false) ||
|
||||
!Resolve(strapUid, ref strapComp, false))
|
||||
return;
|
||||
|
||||
var contained = _container.TryGetContainingContainer(buckleUid, out var ownContainer);
|
||||
var strapContained = _container.TryGetContainingContainer(strapUid, out var strapContainer);
|
||||
|
||||
if (contained != strapContained || ownContainer != strapContainer)
|
||||
{
|
||||
TryUnbuckle(buckleUid, buckleUid, true, buckleComp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!contained)
|
||||
{
|
||||
ReAttach(buckleUid, strapUid, buckleComp, strapComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStrapContainerGettingInsertedAttempt(EntityUid uid, StrapComponent component, ContainerGettingInsertedAttemptEvent args)
|
||||
{
|
||||
// If someone is attempting to put this item inside of a backpack, ensure that it has no entities strapped to it.
|
||||
if (HasComp<StorageComponent>(args.Container.Owner) && component.BuckledEntities.Count != 0)
|
||||
if (args.Container.ID == StorageComponent.ContainerId && component.BuckledEntities.Count != 0)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = ToggleBuckle(args.User, args.User, uid);
|
||||
}
|
||||
|
||||
private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled)
|
||||
return;
|
||||
|
||||
// Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this
|
||||
// range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb.
|
||||
|
||||
// Add unstrap verbs for every strapped entity.
|
||||
foreach (var entity in component.BuckledEntities)
|
||||
{
|
||||
var buckledComp = Comp<BuckleComponent>(entity);
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
|
||||
continue;
|
||||
|
||||
var verb = new InteractionVerb()
|
||||
{
|
||||
Act = () => TryUnbuckle(entity, args.User, buckleComp: buckledComp),
|
||||
Category = VerbCategory.Unbuckle,
|
||||
Text = entity == args.User
|
||||
? Loc.GetString("verb-self-target-pronoun")
|
||||
: Comp<MetaDataComponent>(entity).EntityName
|
||||
};
|
||||
|
||||
// In the event that you have more than once entity with the same name strapped to the same object,
|
||||
// these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to
|
||||
// the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by
|
||||
// appending an integer to verb.Text to distinguish the verbs.
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Add a verb to buckle the user.
|
||||
if (TryComp<BuckleComponent>(args.User, out var buckle) &&
|
||||
buckle.BuckledTo != uid &&
|
||||
args.User != uid &&
|
||||
StrapHasSpace(uid, buckle, component) &&
|
||||
_interaction.InRangeUnobstructed(args.User, args.Target, range: buckle.Range))
|
||||
{
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () => TryBuckle(args.User, args.User, args.Target, buckle),
|
||||
Category = VerbCategory.Buckle,
|
||||
Text = Loc.GetString("verb-self-target-pronoun")
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// If the user is currently holding/pulling an entity that can be buckled, add a verb for that.
|
||||
if (args.Using is { Valid: true } @using &&
|
||||
TryComp<BuckleComponent>(@using, out var usingBuckle) &&
|
||||
StrapHasSpace(uid, usingBuckle, component) &&
|
||||
_interaction.InRangeUnobstructed(@using, args.Target, range: usingBuckle.Range))
|
||||
{
|
||||
// Check that the entity is unobstructed from the target (ignoring the user).
|
||||
bool Ignored(EntityUid entity) => entity == args.User || entity == args.Target || entity == @using;
|
||||
if (!_interaction.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
|
||||
return;
|
||||
|
||||
var isPlayer = _playerManager.TryGetSessionByEntity(@using, out var _);
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () => TryBuckle(@using, args.User, args.Target, usingBuckle),
|
||||
Category = VerbCategory.Buckle,
|
||||
Text = Comp<MetaDataComponent>(@using).EntityName,
|
||||
// just a held object, the user is probably just trying to sit down.
|
||||
// If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
|
||||
Priority = isPlayer ? 1 : -1
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanDropTarget(EntityUid uid, StrapComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAttemptFold(EntityUid uid, StrapComponent component, ref FoldAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
@@ -192,69 +50,6 @@ public abstract partial class SharedBuckleSystem
|
||||
args.Cancelled = component.BuckledEntities.Count != 0;
|
||||
}
|
||||
|
||||
private void OnStrapDragDropTarget(EntityUid uid, StrapComponent component, ref DragDropTargetEvent args)
|
||||
{
|
||||
if (!StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component))
|
||||
return;
|
||||
|
||||
args.Handled = TryBuckle(args.Dragged, args.User, uid);
|
||||
}
|
||||
|
||||
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
|
||||
{
|
||||
// TODO: This looks dirty af.
|
||||
// On rotation of a strap, reattach all buckled entities.
|
||||
// This fixes buckle offsets and draw depths.
|
||||
// This is mega cursed. Please somebody save me from Mr Buckle's wild ride.
|
||||
// Oh god I'm back here again. Send help.
|
||||
|
||||
// Consider a chair that has a player strapped to it. Then the client receives a new server state, showing
|
||||
// that the player entity has moved elsewhere, and the chair has rotated. If the client applies the player
|
||||
// state, then the chairs transform comp state, and then the buckle state. The transform state will
|
||||
// forcefully teleport the player back to the chair (client-side only). This causes even more issues if the
|
||||
// chair was teleporting in from nullspace after having left PVS.
|
||||
//
|
||||
// One option is to just never trigger re-buckles during state application.
|
||||
// another is to.. just not do this? Like wtf is this code. But I CBF with buckle atm.
|
||||
|
||||
if (_gameTiming.ApplyingState || args.NewRotation == args.OldRotation)
|
||||
return;
|
||||
|
||||
foreach (var buckledEntity in component.BuckledEntities)
|
||||
{
|
||||
if (!TryComp<BuckleComponent>(buckledEntity, out var buckled))
|
||||
continue;
|
||||
|
||||
if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
|
||||
{
|
||||
Log.Error($"A moving strap entity {ToPrettyString(uid)} attempted to re-parent an entity that does not 'belong' to it {ToPrettyString(buckledEntity)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
ReAttach(buckledEntity, uid, buckled, component);
|
||||
Dirty(buckledEntity, buckled);
|
||||
}
|
||||
}
|
||||
|
||||
private bool StrapCanDragDropOn(
|
||||
EntityUid strapUid,
|
||||
EntityUid userUid,
|
||||
EntityUid targetUid,
|
||||
EntityUid buckleUid,
|
||||
StrapComponent? strapComp = null,
|
||||
BuckleComponent? buckleComp = null)
|
||||
{
|
||||
if (!Resolve(strapUid, ref strapComp, false) ||
|
||||
!Resolve(buckleUid, ref buckleComp, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ignored(EntityUid entity) => entity == userUid || entity == buckleUid || entity == targetUid;
|
||||
|
||||
return _interaction.InRangeUnobstructed(targetUid, buckleUid, buckleComp.Range, predicate: Ignored);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove everything attached to the strap
|
||||
/// </summary>
|
||||
@@ -264,10 +59,6 @@ public abstract partial class SharedBuckleSystem
|
||||
{
|
||||
TryUnbuckle(entity, entity, true);
|
||||
}
|
||||
|
||||
strapComp.BuckledEntities.Clear();
|
||||
strapComp.OccupiedSize = 0;
|
||||
Dirty(uid, strapComp);
|
||||
}
|
||||
|
||||
private bool StrapHasSpace(EntityUid strapUid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
|
||||
@@ -275,30 +66,13 @@ public abstract partial class SharedBuckleSystem
|
||||
if (!Resolve(strapUid, ref strapComp, false))
|
||||
return false;
|
||||
|
||||
return strapComp.OccupiedSize + buckleComp.Size <= strapComp.Size;
|
||||
var avail = strapComp.Size;
|
||||
foreach (var buckle in strapComp.BuckledEntities)
|
||||
{
|
||||
avail -= CompOrNull<BuckleComponent>(buckle)?.Size ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to add an entity to the strap
|
||||
/// </summary>
|
||||
private bool StrapTryAdd(EntityUid strapUid, EntityUid buckleUid, BuckleComponent buckleComp, bool force = false, StrapComponent? strapComp = null)
|
||||
{
|
||||
if (!Resolve(strapUid, ref strapComp, false) ||
|
||||
!strapComp.Enabled)
|
||||
return false;
|
||||
|
||||
if (!force && !StrapHasSpace(strapUid, buckleComp, strapComp))
|
||||
return false;
|
||||
|
||||
if (!strapComp.BuckledEntities.Add(buckleUid))
|
||||
return false;
|
||||
|
||||
strapComp.OccupiedSize += buckleComp.Size;
|
||||
|
||||
Appearance.SetData(strapUid, StrapVisuals.State, true);
|
||||
|
||||
Dirty(strapUid, strapComp);
|
||||
return true;
|
||||
return avail >= buckleComp.Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,6 +85,7 @@ public abstract partial class SharedBuckleSystem
|
||||
return;
|
||||
|
||||
strapComp.Enabled = enabled;
|
||||
Dirty(strapUid, strapComp);
|
||||
|
||||
if (!enabled)
|
||||
StrapRemoveAll(strapUid, strapComp);
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Pulling;
|
||||
using Content.Shared.Rotation;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using PullingSystem = Content.Shared.Movement.Pulling.Systems.PullingSystem;
|
||||
|
||||
namespace Content.Shared.Buckle;
|
||||
|
||||
@@ -36,10 +32,10 @@ public abstract partial class SharedBuckleSystem : EntitySystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PullingSystem _pulling = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standing = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedRotationVisualsSystem _rotationVisuals = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -51,45 +47,6 @@ public abstract partial class SharedBuckleSystem : EntitySystem
|
||||
|
||||
InitializeBuckle();
|
||||
InitializeStrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reattaches this entity to the strap, modifying its position and rotation.
|
||||
/// </summary>
|
||||
/// <param name="buckleUid">The entity to reattach.</param>
|
||||
/// <param name="strapUid">The entity to reattach the buckleUid entity to.</param>
|
||||
private void ReAttach(
|
||||
EntityUid buckleUid,
|
||||
EntityUid strapUid,
|
||||
BuckleComponent? buckleComp = null,
|
||||
StrapComponent? strapComp = null)
|
||||
{
|
||||
if (!Resolve(strapUid, ref strapComp, false)
|
||||
|| !Resolve(buckleUid, ref buckleComp, false))
|
||||
return;
|
||||
|
||||
_transform.SetCoordinates(buckleUid, new EntityCoordinates(strapUid, strapComp.BuckleOffsetClamped));
|
||||
|
||||
var buckleTransform = Transform(buckleUid);
|
||||
|
||||
// Buckle subscribes to move for <reasons> so this might fail.
|
||||
// TODO: Make buckle not do that.
|
||||
if (buckleTransform.ParentUid != strapUid)
|
||||
return;
|
||||
|
||||
_transform.SetLocalRotation(buckleUid, Angle.Zero, buckleTransform);
|
||||
_joints.SetRelay(buckleUid, strapUid);
|
||||
|
||||
switch (strapComp.Position)
|
||||
{
|
||||
case StrapPosition.None:
|
||||
break;
|
||||
case StrapPosition.Stand:
|
||||
_standing.Stand(buckleUid);
|
||||
break;
|
||||
case StrapPosition.Down:
|
||||
_standing.Down(buckleUid, false, false);
|
||||
break;
|
||||
}
|
||||
InitializeInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed partial class ClimbSystem : VirtualController
|
||||
SubscribeLocalEvent<ClimbingComponent, EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<ClimbingComponent, ClimbDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
|
||||
SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
SubscribeLocalEvent<ClimbingComponent, BuckledEvent>(OnBuckled);
|
||||
|
||||
SubscribeLocalEvent<ClimbableComponent, CanDropTargetEvent>(OnCanDragDropOn);
|
||||
SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
|
||||
@@ -468,10 +468,8 @@ public sealed partial class ClimbSystem : VirtualController
|
||||
Climb(uid, uid, climbable, true, component);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args)
|
||||
private void OnBuckled(EntityUid uid, ClimbingComponent component, ref BuckledEvent args)
|
||||
{
|
||||
if (!args.Buckling)
|
||||
return;
|
||||
StopClimb(uid, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Content.Shared.Cuffs
|
||||
SubscribeLocalEvent<CuffableComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
|
||||
SubscribeLocalEvent<CuffableComponent, BeingPulledAttemptEvent>(OnBeingPulledAttempt);
|
||||
SubscribeLocalEvent<CuffableComponent, BuckleAttemptEvent>(OnBuckleAttemptEvent);
|
||||
SubscribeLocalEvent<CuffableComponent, UnbuckleAttemptEvent>(OnUnbuckleAttemptEvent);
|
||||
SubscribeLocalEvent<CuffableComponent, GetVerbsEvent<Verb>>(AddUncuffVerb);
|
||||
SubscribeLocalEvent<CuffableComponent, UnCuffDoAfterEvent>(OnCuffableDoAfter);
|
||||
SubscribeLocalEvent<CuffableComponent, PullStartedMessage>(OnPull);
|
||||
@@ -195,21 +196,33 @@ namespace Content.Shared.Cuffs
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnBuckleAttemptEvent(EntityUid uid, CuffableComponent component, ref BuckleAttemptEvent args)
|
||||
private void OnBuckleAttempt(Entity<CuffableComponent> ent, EntityUid? user, ref bool cancelled, bool buckling, bool popup)
|
||||
{
|
||||
// if someone else is doing it, let it pass.
|
||||
if (args.UserEntity != uid)
|
||||
if (cancelled || user != ent.Owner)
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(uid, out var hands) || component.CuffedHandCount != hands.Count)
|
||||
if (!TryComp<HandsComponent>(ent, out var hands) || ent.Comp.CuffedHandCount != hands.Count)
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
var message = args.Buckling
|
||||
cancelled = true;
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
var message = buckling
|
||||
? Loc.GetString("handcuff-component-cuff-interrupt-buckled-message")
|
||||
: Loc.GetString("handcuff-component-cuff-interrupt-unbuckled-message");
|
||||
|
||||
_popup.PopupClient(message, uid, args.UserEntity);
|
||||
_popup.PopupClient(message, ent, user);
|
||||
}
|
||||
|
||||
private void OnBuckleAttemptEvent(Entity<CuffableComponent> ent, ref BuckleAttemptEvent args)
|
||||
{
|
||||
OnBuckleAttempt(ent, args.User, ref args.Cancelled, true, args.Popup);
|
||||
}
|
||||
|
||||
private void OnUnbuckleAttemptEvent(Entity<CuffableComponent> ent, ref UnbuckleAttemptEvent args)
|
||||
{
|
||||
OnBuckleAttempt(ent, args.User, ref args.Cancelled, false, args.Popup);
|
||||
}
|
||||
|
||||
private void OnPull(EntityUid uid, CuffableComponent component, PullMessage args)
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class FoldableSystem : EntitySystem
|
||||
SubscribeLocalEvent<FoldableComponent, StoreMobInItemContainerAttemptEvent>(OnStoreThisAttempt);
|
||||
SubscribeLocalEvent<FoldableComponent, StorageOpenAttemptEvent>(OnFoldableOpenAttempt);
|
||||
|
||||
SubscribeLocalEvent<FoldableComponent, BuckleAttemptEvent>(OnBuckleAttempt);
|
||||
SubscribeLocalEvent<FoldableComponent, StrapAttemptEvent>(OnStrapAttempt);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, FoldableComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -53,9 +53,9 @@ public sealed class FoldableSystem : EntitySystem
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
public void OnBuckleAttempt(EntityUid uid, FoldableComponent comp, ref BuckleAttemptEvent args)
|
||||
public void OnStrapAttempt(EntityUid uid, FoldableComponent comp, ref StrapAttemptEvent args)
|
||||
{
|
||||
if (args.Buckling && comp.IsFolded)
|
||||
if (comp.IsFolded)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Rotatable;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
@@ -83,25 +82,22 @@ namespace Content.Shared.Interaction
|
||||
if (!_actionBlockerSystem.CanChangeDirection(user))
|
||||
return false;
|
||||
|
||||
if (EntityManager.TryGetComponent(user, out BuckleComponent? buckle) && buckle.Buckled)
|
||||
{
|
||||
var suid = buckle.LastEntityBuckledTo;
|
||||
if (suid != null)
|
||||
if (TryComp(user, out BuckleComponent? buckle) && buckle.BuckledTo is {} strap)
|
||||
{
|
||||
// What if a person is strapped to a borg?
|
||||
// I'm pretty sure this would allow them to be partially ratatouille'd
|
||||
|
||||
// We're buckled to another object. Is that object rotatable?
|
||||
if (TryComp<RotatableComponent>(suid.Value, out var rotatable) && rotatable.RotateWhileAnchored)
|
||||
{
|
||||
if (!TryComp<RotatableComponent>(strap, out var rotatable) || !rotatable.RotateWhileAnchored)
|
||||
return false;
|
||||
|
||||
// Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel".
|
||||
// (Since the user being buckled to it holds it down with their weight.)
|
||||
// This is logically equivalent to RotateWhileAnchored.
|
||||
// Barstools and office chairs have independent wheels, while regular chairs don't.
|
||||
_transform.SetWorldRotation(Transform(suid.Value), diffAngle);
|
||||
_transform.SetWorldRotation(Transform(strap), diffAngle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// user is not buckled in; apply to their transform
|
||||
if (!Resolve(user, ref xform))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Emoting;
|
||||
@@ -10,15 +11,12 @@ using Content.Shared.Item;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Strip.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Shared.Mobs.Systems;
|
||||
|
||||
@@ -46,6 +44,16 @@ public partial class MobStateSystem
|
||||
SubscribeLocalEvent<MobStateComponent, TryingToSleepEvent>(OnSleepAttempt);
|
||||
SubscribeLocalEvent<MobStateComponent, CombatModeShouldHandInteractEvent>(OnCombatModeShouldHandInteract);
|
||||
SubscribeLocalEvent<MobStateComponent, AttemptPacifiedAttackEvent>(OnAttemptPacifiedAttack);
|
||||
|
||||
SubscribeLocalEvent<MobStateComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
|
||||
}
|
||||
|
||||
private void OnUnbuckleAttempt(Entity<MobStateComponent> ent, ref UnbuckleAttemptEvent args)
|
||||
{
|
||||
// TODO is this necessary?
|
||||
// Shouldn't the interaction have already been blocked by a general interaction check?
|
||||
if (args.User == ent.Owner && IsIncapacitated(ent))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void CheckConcious(Entity<MobStateComponent> ent, ref ConsciousAttemptEvent args)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
namespace Content.Shared.Movement.Pulling.Events;
|
||||
|
||||
public sealed class PullStartedMessage : PullMessage
|
||||
{
|
||||
public PullStartedMessage(EntityUid pullerUid, EntityUid pullableUid) :
|
||||
base(pullerUid, pullableUid)
|
||||
{
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Event raised directed BOTH at the puller and pulled entity when a pull starts.
|
||||
/// </summary>
|
||||
public sealed class PullStartedMessage(EntityUid pullerUid, EntityUid pullableUid) : PullMessage(pullerUid, pullableUid);
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Content.Shared.Movement.Pulling.Events;
|
||||
namespace Content.Shared.Movement.Pulling.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on both puller and pullable.
|
||||
/// Event raised directed BOTH at the puller and pulled entity when a pull starts.
|
||||
/// </summary>
|
||||
public sealed class PullStoppedMessage : PullMessage
|
||||
{
|
||||
public PullStoppedMessage(EntityUid pullerUid, EntityUid pulledUid) : base(pullerUid, pulledUid)
|
||||
{
|
||||
}
|
||||
}
|
||||
public sealed class PullStoppedMessage(EntityUid pullerUid, EntityUid pulledUid) : PullMessage(pullerUid, pulledUid);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
@@ -16,11 +15,9 @@ using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
@@ -68,11 +65,26 @@ public sealed class PullingSystem : EntitySystem
|
||||
SubscribeLocalEvent<PullerComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
SubscribeLocalEvent<PullerComponent, DropHandItemsEvent>(OnDropHandItems);
|
||||
|
||||
SubscribeLocalEvent<PullableComponent, StrappedEvent>(OnBuckled);
|
||||
SubscribeLocalEvent<PullableComponent, BuckledEvent>(OnGotBuckled);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(OnReleasePulledObject, handle: false))
|
||||
.Register<PullingSystem>();
|
||||
}
|
||||
|
||||
private void OnBuckled(Entity<PullableComponent> ent, ref StrappedEvent args)
|
||||
{
|
||||
// Prevent people from pulling the entity they are buckled to
|
||||
if (ent.Comp.Puller == args.Buckle.Owner && !args.Buckle.Comp.PullStrap)
|
||||
StopPulling(ent, ent);
|
||||
}
|
||||
|
||||
private void OnGotBuckled(Entity<PullableComponent> ent, ref BuckledEvent args)
|
||||
{
|
||||
StopPulling(ent, ent);
|
||||
}
|
||||
|
||||
private void OnAfterState(Entity<PullerComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent.Comp.Pulling == null)
|
||||
@@ -94,7 +106,8 @@ public sealed class PullingSystem : EntitySystem
|
||||
|
||||
private void OnPullerContainerInsert(Entity<PullerComponent> ent, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (ent.Comp.Pulling == null) return;
|
||||
if (ent.Comp.Pulling == null)
|
||||
return;
|
||||
|
||||
if (!TryComp(ent.Comp.Pulling.Value, out PullableComponent? pulling))
|
||||
return;
|
||||
@@ -228,8 +241,18 @@ public sealed class PullingSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp)
|
||||
{
|
||||
if (pullableComp.Puller == null)
|
||||
return;
|
||||
|
||||
if (!_timing.ApplyingState)
|
||||
{
|
||||
// Joint shutdown
|
||||
if (pullableComp.PullJointId != null)
|
||||
{
|
||||
_joints.RemoveJoint(pullableUid, pullableComp.PullJointId);
|
||||
pullableComp.PullJointId = null;
|
||||
}
|
||||
|
||||
if (TryComp<PhysicsComponent>(pullableUid, out var pullablePhysics))
|
||||
{
|
||||
_physics.SetFixedRotation(pullableUid, pullableComp.PrevFixedRotation, body: pullablePhysics);
|
||||
@@ -330,15 +353,6 @@ public sealed class PullingSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EntityManager.TryGetComponent(puller, out BuckleComponent? buckle))
|
||||
{
|
||||
// Prevent people pulling the chair they're on, etc.
|
||||
if (buckle is { PullStrap: false, Buckled: true } && (buckle.LastEntityBuckledTo == pullableUid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var getPulled = new BeingPulledAttemptEvent(puller, pullableUid);
|
||||
RaiseLocalEvent(pullableUid, getPulled, true);
|
||||
var startPull = new StartPullAttemptEvent(puller, pullableUid);
|
||||
@@ -382,11 +396,8 @@ public sealed class PullingSystem : EntitySystem
|
||||
if (!CanPull(pullerUid, pullableUid))
|
||||
return false;
|
||||
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(pullerUid, out var pullerPhysics) ||
|
||||
!EntityManager.TryGetComponent<PhysicsComponent>(pullableUid, out var pullablePhysics))
|
||||
{
|
||||
if (!HasComp<PhysicsComponent>(pullerUid) || !TryComp(pullableUid, out PhysicsComponent? pullablePhysics))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the puller is not currently pulling anything.
|
||||
if (TryComp<PullableComponent>(pullerComp.Pulling, out var oldPullable)
|
||||
@@ -431,7 +442,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
{
|
||||
// Joint startup
|
||||
var union = _physics.GetHardAABB(pullerUid).Union(_physics.GetHardAABB(pullableUid, body: pullablePhysics));
|
||||
var length = Math.Max((float) union.Size.X, (float) union.Size.Y) * 0.75f;
|
||||
var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f;
|
||||
|
||||
var joint = _joints.CreateDistanceJoint(pullableUid, pullerUid, id: pullableComp.PullJointId);
|
||||
joint.CollideConnected = false;
|
||||
@@ -475,17 +486,6 @@ public sealed class PullingSystem : EntitySystem
|
||||
if (msg.Cancelled)
|
||||
return false;
|
||||
|
||||
// Stop pulling confirmed!
|
||||
if (!_timing.ApplyingState)
|
||||
{
|
||||
// Joint shutdown
|
||||
if (pullable.PullJointId != null)
|
||||
{
|
||||
_joints.RemoveJoint(pullableUid, pullable.PullJointId);
|
||||
pullable.PullJointId = null;
|
||||
}
|
||||
}
|
||||
|
||||
StopPulling(pullableUid, pullable);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public abstract class SharedWaddleAnimationSystem : EntitySystem
|
||||
// Stop moving possibilities
|
||||
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref StunnedEvent _) => StopWaddling(ent));
|
||||
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref DownedEvent _) => StopWaddling(ent));
|
||||
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref BuckleChangeEvent _) => StopWaddling(ent));
|
||||
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref BuckledEvent _) => StopWaddling(ent));
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, GravityChangedEvent>(OnGravityChanged);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ public sealed class LegsParalyzedSystem : EntitySystem
|
||||
{
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, BuckledEvent>(OnBuckled);
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, UnbuckledEvent>(OnUnbuckled);
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, ThrowPushbackAttemptEvent>(OnThrowPushbackAttempt);
|
||||
SubscribeLocalEvent<LegsParalyzedComponent, UpdateCanMoveEvent>(OnUpdateCanMoveEvent);
|
||||
}
|
||||
@@ -34,16 +35,14 @@ public sealed class LegsParalyzedSystem : EntitySystem
|
||||
_bodySystem.UpdateMovementSpeed(uid);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, LegsParalyzedComponent component, ref BuckleChangeEvent args)
|
||||
private void OnBuckled(EntityUid uid, LegsParalyzedComponent component, ref BuckledEvent args)
|
||||
{
|
||||
if (args.Buckling)
|
||||
{
|
||||
_standingSystem.Stand(args.BuckledEntity);
|
||||
_standingSystem.Stand(uid);
|
||||
}
|
||||
else
|
||||
|
||||
private void OnUnbuckled(EntityUid uid, LegsParalyzedComponent component, ref UnbuckledEvent args)
|
||||
{
|
||||
_standingSystem.Down(args.BuckledEntity);
|
||||
}
|
||||
_standingSystem.Down(uid);
|
||||
}
|
||||
|
||||
private void OnUpdateCanMoveEvent(EntityUid uid, LegsParalyzedComponent component, UpdateCanMoveEvent args)
|
||||
|
||||
@@ -357,6 +357,8 @@
|
||||
components:
|
||||
- type: Foldable
|
||||
folded: true
|
||||
- type: Strap
|
||||
enabled: False
|
||||
|
||||
- type: entity
|
||||
name: steel bench
|
||||
|
||||
@@ -79,6 +79,8 @@
|
||||
components:
|
||||
- type: Foldable
|
||||
folded: true
|
||||
- type: Strap
|
||||
enabled: False
|
||||
|
||||
- type: entity
|
||||
id: CheapRollerBed
|
||||
@@ -105,6 +107,8 @@
|
||||
components:
|
||||
- type: Foldable
|
||||
folded: true
|
||||
- type: Strap
|
||||
enabled: False
|
||||
|
||||
- type: entity
|
||||
id: EmergencyRollerBed
|
||||
@@ -131,3 +135,5 @@
|
||||
components:
|
||||
- type: Foldable
|
||||
folded: true
|
||||
- type: Strap
|
||||
enabled: False
|
||||
|
||||
Reference in New Issue
Block a user