Moves buckling and vehicles to shared, some cleanup (#15923)
This commit is contained in:
@@ -1,86 +1,82 @@
|
|||||||
using Content.Client.Rotation;
|
using Content.Client.Rotation;
|
||||||
using Content.Shared.ActionBlocker;
|
|
||||||
using Content.Shared.Buckle;
|
using Content.Shared.Buckle;
|
||||||
using Content.Shared.Buckle.Components;
|
using Content.Shared.Buckle.Components;
|
||||||
using Content.Shared.Vehicle.Components;
|
using Content.Shared.Vehicle.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Client.Buckle
|
namespace Content.Client.Buckle;
|
||||||
|
|
||||||
|
internal sealed class BuckleSystem : SharedBuckleSystem
|
||||||
{
|
{
|
||||||
internal sealed class BuckleSystem : SharedBuckleSystem
|
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
base.Initialize();
|
||||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
|
||||||
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnBuckleHandleState);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuckleHandleState(EntityUid uid, BuckleComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not BuckleComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Buckled = state.Buckled;
|
||||||
|
component.BuckledTo = state.BuckledTo;
|
||||||
|
component.LastEntityBuckledTo = state.LastEntityBuckledTo;
|
||||||
|
component.DontCollide = state.DontCollide;
|
||||||
|
|
||||||
|
ActionBlockerSystem.UpdateCanMove(uid);
|
||||||
|
|
||||||
|
if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (HasComp<VehicleComponent>(component.LastEntityBuckledTo))
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
base.Initialize();
|
component.OriginalDrawDepth ??= ownerSprite.DrawDepth;
|
||||||
|
ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
|
||||||
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnBuckleHandleState);
|
return;
|
||||||
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBuckleHandleState(EntityUid uid, BuckleComponent buckle, ref ComponentHandleState args)
|
// If here, we're not turning north and should restore the saved draw depth.
|
||||||
|
if (component.OriginalDrawDepth.HasValue)
|
||||||
{
|
{
|
||||||
if (args.Current is not BuckleComponentState state)
|
ownerSprite.DrawDepth = component.OriginalDrawDepth.Value;
|
||||||
return;
|
component.OriginalDrawDepth = null;
|
||||||
|
|
||||||
buckle.Buckled = state.Buckled;
|
|
||||||
buckle.LastEntityBuckledTo = state.LastEntityBuckledTo;
|
|
||||||
buckle.DontCollide = state.DontCollide;
|
|
||||||
|
|
||||||
_actionBlocker.UpdateCanMove(uid);
|
|
||||||
|
|
||||||
if (!TryComp(uid, out SpriteComponent? ownerSprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (HasComp<VehicleComponent>(buckle.LastEntityBuckledTo))
|
|
||||||
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 (buckle.Buckled &&
|
|
||||||
buckle.LastEntityBuckledTo != null &&
|
|
||||||
Transform(buckle.LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North &&
|
|
||||||
TryComp<SpriteComponent>(buckle.LastEntityBuckledTo, out var buckledSprite))
|
|
||||||
{
|
|
||||||
buckle.OriginalDrawDepth ??= ownerSprite.DrawDepth;
|
|
||||||
ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If here, we're not turning north and should restore the saved draw depth.
|
|
||||||
if (buckle.OriginalDrawDepth.HasValue)
|
|
||||||
{
|
|
||||||
ownerSprite.DrawDepth = buckle.OriginalDrawDepth.Value;
|
|
||||||
buckle.OriginalDrawDepth = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_appearanceSystem.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
|
|
||||||
!_appearanceSystem.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
|
|
||||||
!buckled ||
|
|
||||||
args.Sprite == null)
|
|
||||||
{
|
|
||||||
_rotationVisualizerSystem.SetHorizontalAngle(uid, RotationVisualsComponent.DefaultRotation, rotVisuals);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animate strapping yourself to something at a given angle
|
|
||||||
_rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals);
|
|
||||||
// TODO: Dump this when buckle is better
|
|
||||||
_rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!AppearanceSystem.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
|
||||||
|
!AppearanceSystem.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
|
||||||
|
!buckled ||
|
||||||
|
args.Sprite == null)
|
||||||
|
{
|
||||||
|
_rotationVisualizerSystem.SetHorizontalAngle(uid, RotationVisualsComponent.DefaultRotation, rotVisuals);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate strapping yourself to something at a given angle
|
||||||
|
_rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals);
|
||||||
|
// TODO: Dump this when buckle is better
|
||||||
|
_rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,41 +3,68 @@ using Content.Shared.Vehicle.Components;
|
|||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Client.Vehicle
|
namespace Content.Client.Vehicle;
|
||||||
|
|
||||||
|
public sealed class VehicleSystem : SharedVehicleSystem
|
||||||
{
|
{
|
||||||
public sealed class VehicleSystem : SharedVehicleSystem
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
base.Initialize();
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
|
|
||||||
SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
|
|
||||||
SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
|
SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
|
||||||
{
|
SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
|
||||||
// Center the player's eye on the vehicle
|
SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
|
||||||
if (TryComp(uid, out EyeComponent? eyeComp))
|
SubscribeLocalEvent<VehicleComponent, AppearanceChangeEvent>(OnVehicleAppearanceChange);
|
||||||
eyeComp.Target ??= component.Vehicle;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
|
private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
|
||||||
{
|
{
|
||||||
// reset the riders eye centering.
|
// Center the player's eye on the vehicle
|
||||||
if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
|
if (TryComp(uid, out EyeComponent? eyeComp))
|
||||||
eyeComp.Target = null;
|
eyeComp.Target ??= component.Vehicle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
|
private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
|
||||||
{
|
{
|
||||||
if (args.Current is not RiderComponentState state)
|
// reset the riders eye centering.
|
||||||
return;
|
if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
|
||||||
|
eyeComp.Target = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
|
private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
|
||||||
eyeComp.Target = state.Entity;
|
{
|
||||||
|
if (args.Current is not RiderComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
component.Vehicle = state.Entity;
|
if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
|
||||||
}
|
eyeComp.Target = state.Entity;
|
||||||
|
|
||||||
|
component.Vehicle = state.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVehicleAppearanceChange(EntityUid uid, VehicleComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (component.HideRider
|
||||||
|
&& Appearance.TryGetData<bool>(uid, VehicleVisuals.HideRider, out var hide, args.Component)
|
||||||
|
&& TryComp<SpriteComponent>(component.LastRider, out var riderSprite))
|
||||||
|
riderSprite.Visible = !hide;
|
||||||
|
|
||||||
|
// First check is for the sprite itself
|
||||||
|
if (Appearance.TryGetData<int>(uid, VehicleVisuals.DrawDepth, out var drawDepth, args.Component))
|
||||||
|
args.Sprite.DrawDepth = drawDepth;
|
||||||
|
|
||||||
|
// Set vehicle layer to animated or not (i.e. are the wheels turning or not)
|
||||||
|
if (component.AutoAnimate
|
||||||
|
&& Appearance.TryGetData<bool>(uid, VehicleVisuals.AutoAnimate, out var autoAnimate, args.Component))
|
||||||
|
args.Sprite.LayerSetAutoAnimated(VehicleVisualLayers.AutoAnimate, autoAnimate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum VehicleVisualLayers : byte
|
||||||
|
{
|
||||||
|
/// Layer for the vehicle's wheels
|
||||||
|
AutoAnimate,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Content.Client.Vehicle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Controls visuals for vehicles
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class VehicleVisualsComponent : Component
|
|
||||||
{
|
|
||||||
public int DrawDepth = 0;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using Robust.Client.GameObjects;
|
|
||||||
using Content.Shared.Vehicle;
|
|
||||||
|
|
||||||
namespace Content.Client.Vehicle
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Controls client-side visuals for
|
|
||||||
/// vehicles
|
|
||||||
/// </summary>
|
|
||||||
public sealed class VehicleVisualsSystem : VisualizerSystem<VehicleVisualsComponent>
|
|
||||||
{
|
|
||||||
protected override void OnAppearanceChange(EntityUid uid, VehicleVisualsComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
if (args.Sprite == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// First check is for the sprite itself
|
|
||||||
if (AppearanceSystem.TryGetData<int>(uid, VehicleVisuals.DrawDepth, out var drawDepth, args.Component))
|
|
||||||
{
|
|
||||||
args.Sprite.DrawDepth = drawDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set vehicle layer to animated or not (i.e. are the wheels turning or not)
|
|
||||||
if (AppearanceSystem.TryGetData<bool>(uid, VehicleVisuals.AutoAnimate, out var autoAnimate, args.Component))
|
|
||||||
{
|
|
||||||
args.Sprite.LayerSetAutoAnimated(VehicleVisualLayers.AutoAnimate, autoAnimate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public enum VehicleVisualLayers : byte
|
|
||||||
{
|
|
||||||
/// Layer for the vehicle's wheels
|
|
||||||
AutoAnimate,
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Buckle.Systems;
|
using Content.Shared.Buckle;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
@@ -57,7 +57,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
var coordinates = testMap.GridCoords;
|
var coordinates = testMap.GridCoords;
|
||||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||||
var actionBlocker = entityManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
|
var actionBlocker = entityManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
|
||||||
var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<BuckleSystem>();
|
var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<SharedBuckleSystem>();
|
||||||
var standingState = entityManager.EntitySysManager.GetEntitySystem<StandingStateSystem>();
|
var standingState = entityManager.EntitySysManager.GetEntitySystem<StandingStateSystem>();
|
||||||
|
|
||||||
EntityUid human = default;
|
EntityUid human = default;
|
||||||
@@ -108,7 +108,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle));
|
Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle));
|
||||||
|
|
||||||
// Trying to unbuckle too quickly fails
|
// Trying to unbuckle too quickly fails
|
||||||
Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
|
Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
@@ -123,7 +123,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
|
|
||||||
// Unbuckle
|
// Unbuckle
|
||||||
Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
|
Assert.True(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
|
||||||
Assert.Null(buckle.BuckledTo);
|
Assert.Null(buckle.BuckledTo);
|
||||||
Assert.False(buckle.Buckled);
|
Assert.False(buckle.Buckled);
|
||||||
Assert.True(actionBlocker.CanMove(human));
|
Assert.True(actionBlocker.CanMove(human));
|
||||||
@@ -135,11 +135,11 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.Zero(strap.OccupiedSize);
|
Assert.Zero(strap.OccupiedSize);
|
||||||
|
|
||||||
// Re-buckling has no cooldown
|
// Re-buckling has no cooldown
|
||||||
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
|
|
||||||
// On cooldown
|
// On cooldown
|
||||||
Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
|
Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
@@ -156,15 +156,15 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
|
|
||||||
// Unbuckle
|
// Unbuckle
|
||||||
Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
|
Assert.True(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
|
||||||
Assert.False(buckle.Buckled);
|
Assert.False(buckle.Buckled);
|
||||||
|
|
||||||
// Move away from the chair
|
// Move away from the chair
|
||||||
entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1000, 1000);
|
entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1000, 1000);
|
||||||
|
|
||||||
// Out of range
|
// Out of range
|
||||||
Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.False(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
|
Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
|
||||||
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
||||||
|
|
||||||
// Move near the chair
|
// Move near the chair
|
||||||
@@ -172,22 +172,22 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
entityManager.GetComponent<TransformComponent>(chair).WorldPosition + (0.5f, 0);
|
entityManager.GetComponent<TransformComponent>(chair).WorldPosition + (0.5f, 0);
|
||||||
|
|
||||||
// In range
|
// In range
|
||||||
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
|
Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
|
|
||||||
// Force unbuckle
|
// Force unbuckle
|
||||||
Assert.True(buckleSystem.TryUnbuckle(human, human, true, buckle: buckle));
|
Assert.True(buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle));
|
||||||
Assert.False(buckle.Buckled);
|
Assert.False(buckle.Buckled);
|
||||||
Assert.True(actionBlocker.CanMove(human));
|
Assert.True(actionBlocker.CanMove(human));
|
||||||
Assert.True(actionBlocker.CanChangeDirection(human));
|
Assert.True(actionBlocker.CanChangeDirection(human));
|
||||||
Assert.True(standingState.Down(human));
|
Assert.True(standingState.Down(human));
|
||||||
|
|
||||||
// Re-buckle
|
// Re-buckle
|
||||||
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
|
|
||||||
// Move away from the chair
|
// Move away from the chair
|
||||||
entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1, 0);
|
entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1, 0);
|
||||||
@@ -225,7 +225,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
|
|
||||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||||
var handsSys = entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>();
|
var handsSys = entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>();
|
||||||
var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<BuckleSystem>();
|
var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<SharedBuckleSystem>();
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
@@ -239,7 +239,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.True(entityManager.TryGetComponent(human, out body));
|
Assert.True(entityManager.TryGetComponent(human, out body));
|
||||||
|
|
||||||
// Buckle
|
// Buckle
|
||||||
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
Assert.NotNull(buckle.BuckledTo);
|
Assert.NotNull(buckle.BuckledTo);
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.Null(hand.HeldEntity);
|
Assert.Null(hand.HeldEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
buckleSystem.TryUnbuckle(human, human, true, buckle: buckle);
|
buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle);
|
||||||
});
|
});
|
||||||
|
|
||||||
await pairTracker.CleanReturnAsync();
|
await pairTracker.CleanReturnAsync();
|
||||||
@@ -304,7 +304,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
var testMap = await PoolManager.CreateTestMap(pairTracker);
|
var testMap = await PoolManager.CreateTestMap(pairTracker);
|
||||||
var coordinates = testMap.GridCoords;
|
var coordinates = testMap.GridCoords;
|
||||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||||
var buckleSystem = entityManager.System<BuckleSystem>();
|
var buckleSystem = entityManager.System<SharedBuckleSystem>();
|
||||||
|
|
||||||
EntityUid human = default;
|
EntityUid human = default;
|
||||||
EntityUid chair = default;
|
EntityUid chair = default;
|
||||||
@@ -320,7 +320,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
Assert.True(entityManager.HasComponent<StrapComponent>(chair));
|
Assert.True(entityManager.HasComponent<StrapComponent>(chair));
|
||||||
|
|
||||||
// Buckle
|
// Buckle
|
||||||
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
Assert.NotNull(buckle.BuckledTo);
|
Assert.NotNull(buckle.BuckledTo);
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
|
|
||||||
@@ -338,7 +338,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
|||||||
entityManager.GetComponent<TransformComponent>(human).WorldPosition -= (100, 0);
|
entityManager.GetComponent<TransformComponent>(human).WorldPosition -= (100, 0);
|
||||||
|
|
||||||
// Buckle
|
// Buckle
|
||||||
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
|
Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
|
||||||
Assert.NotNull(buckle.BuckledTo);
|
Assert.NotNull(buckle.BuckledTo);
|
||||||
Assert.True(buckle.Buckled);
|
Assert.True(buckle.Buckled);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Content.Server.Buckle.Systems;
|
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Buckle;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Content.Server.Alert.Click
|
namespace Content.Server.Alert.Click
|
||||||
@@ -13,7 +13,7 @@ namespace Content.Server.Alert.Click
|
|||||||
{
|
{
|
||||||
public void AlertClicked(EntityUid player)
|
public void AlertClicked(EntityUid player)
|
||||||
{
|
{
|
||||||
IoCManager.Resolve<IEntityManager>().System<BuckleSystem>().TryUnbuckle(player, player);
|
IoCManager.Resolve<IEntityManager>().System<SharedBuckleSystem>().TryUnbuckle(player, player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Content.Server.Bed
|
|||||||
SubscribeLocalEvent<StasisBedComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
SubscribeLocalEvent<StasisBedComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, BuckleChangeEvent args)
|
private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args)
|
||||||
{
|
{
|
||||||
_prototypeManager.TryIndex<InstantActionPrototype>("Sleep", out var sleepAction);
|
_prototypeManager.TryIndex<InstantActionPrototype>("Sleep", out var sleepAction);
|
||||||
if (args.Buckling)
|
if (args.Buckling)
|
||||||
@@ -92,7 +92,7 @@ namespace Content.Server.Bed
|
|||||||
_appearance.SetData(uid, StasisBedVisuals.IsOn, isOn);
|
_appearance.SetData(uid, StasisBedVisuals.IsOn, isOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBuckleChange(EntityUid uid, StasisBedComponent component, BuckleChangeEvent args)
|
private void OnBuckleChange(EntityUid uid, StasisBedComponent component, ref BuckleChangeEvent args)
|
||||||
{
|
{
|
||||||
// In testing this also received an unbuckle event when the bed is destroyed
|
// In testing this also received an unbuckle event when the bed is destroyed
|
||||||
// So don't worry about that
|
// So don't worry about that
|
||||||
|
|||||||
@@ -1,419 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Administration.Logs;
|
|
||||||
using Content.Shared.Alert;
|
|
||||||
using Content.Shared.Bed.Sleep;
|
|
||||||
using Content.Shared.Buckle.Components;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.DragDrop;
|
|
||||||
using Content.Shared.Hands.Components;
|
|
||||||
using Content.Shared.IdentityManagement;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Content.Shared.Pulling.Components;
|
|
||||||
using Content.Shared.Storage.Components;
|
|
||||||
using Content.Shared.Stunnable;
|
|
||||||
using Content.Shared.Vehicle.Components;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Server.Buckle.Systems;
|
|
||||||
|
|
||||||
public sealed partial class BuckleSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
|
||||||
|
|
||||||
private void InitializeBuckle()
|
|
||||||
{
|
|
||||||
SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleStartup);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleShutdown);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnBuckleGetState);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, MoveEvent>(MoveEvent);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(HandleInteractHand);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, CanDropDraggedEvent>(OnBuckleCanDrop);
|
|
||||||
SubscribeLocalEvent<BuckleComponent, DragDropDraggedEvent>(OnBuckleDragDrop);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, buckle: 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuckleStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
|
|
||||||
{
|
|
||||||
UpdateBuckleStatus(uid, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuckleShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args)
|
|
||||||
{
|
|
||||||
TryUnbuckle(uid, uid, true, component);
|
|
||||||
|
|
||||||
component.BuckleTime = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuckleGetState(EntityUid uid, BuckleComponent component, ref ComponentGetState args)
|
|
||||||
{
|
|
||||||
args.State = new BuckleComponentState(component.Buckled, component.LastEntityBuckledTo, component.DontCollide);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
|
|
||||||
{
|
|
||||||
if (TryUnbuckle(uid, args.User, buckle: component))
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MoveEvent(EntityUid uid, BuckleComponent buckle, ref MoveEvent ev)
|
|
||||||
{
|
|
||||||
var strap = buckle.BuckledTo;
|
|
||||||
|
|
||||||
if (strap == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var strapPosition = Transform(strap.Owner).Coordinates;
|
|
||||||
|
|
||||||
if (ev.NewPosition.InRange(EntityManager, strapPosition, strap.MaxBuckleDistance))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TryUnbuckle(uid, buckle.Owner, true, buckle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEntityStorageInsertAttempt(EntityUid uid, BuckleComponent comp, ref InsertIntoEntityStorageAttemptEvent args)
|
|
||||||
{
|
|
||||||
if (comp.Buckled)
|
|
||||||
args.Cancelled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuckleCanDrop(EntityUid uid, BuckleComponent component, ref CanDropDraggedEvent args)
|
|
||||||
{
|
|
||||||
args.Handled = HasComp<StrapComponent>(args.Target);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuckleDragDrop(EntityUid uid, BuckleComponent component, ref DragDropDraggedEvent args)
|
|
||||||
{
|
|
||||||
args.Handled = TryBuckle(uid, args.User, args.Target, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shows or hides the buckled status effect depending on if the
|
|
||||||
/// entity is buckled or not.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateBuckleStatus(EntityUid uid, BuckleComponent component)
|
|
||||||
{
|
|
||||||
if (component.Buckled)
|
|
||||||
{
|
|
||||||
var alertType = component.BuckledTo?.BuckledAlertType ?? AlertType.Buckled;
|
|
||||||
_alerts.ShowAlert(uid, alertType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_alerts.ClearAlertCategory(uid, AlertCategory.Buckled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetBuckledTo(BuckleComponent buckle, StrapComponent? strap)
|
|
||||||
{
|
|
||||||
buckle.BuckledTo = strap;
|
|
||||||
buckle.LastEntityBuckledTo = strap?.Owner;
|
|
||||||
|
|
||||||
if (strap == null)
|
|
||||||
{
|
|
||||||
buckle.Buckled = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buckle.DontCollide = true;
|
|
||||||
buckle.Buckled = true;
|
|
||||||
buckle.BuckleTime = _gameTiming.CurTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
_actionBlocker.UpdateCanMove(buckle.Owner);
|
|
||||||
UpdateBuckleStatus(buckle.Owner, buckle);
|
|
||||||
Dirty(buckle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanBuckle(
|
|
||||||
EntityUid buckleId,
|
|
||||||
EntityUid user,
|
|
||||||
EntityUid to,
|
|
||||||
[NotNullWhen(true)] out StrapComponent? strap,
|
|
||||||
BuckleComponent? buckle = null)
|
|
||||||
{
|
|
||||||
strap = null;
|
|
||||||
|
|
||||||
if (user == to ||
|
|
||||||
!Resolve(buckleId, ref buckle, false) ||
|
|
||||||
!Resolve(to, ref strap, false))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var strapUid = strap.Owner;
|
|
||||||
bool Ignored(EntityUid entity) => entity == buckleId || entity == user || entity == strapUid;
|
|
||||||
|
|
||||||
if (!_interactions.InRangeUnobstructed(buckleId, strapUid, buckle.Range, predicate: Ignored, popup: true))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If in a container
|
|
||||||
if (_containers.TryGetContainingContainer(buckleId, out var ownerContainer))
|
|
||||||
{
|
|
||||||
// And not in the same container as the strap
|
|
||||||
if (!_containers.TryGetContainingContainer(strap.Owner, out var strapContainer) ||
|
|
||||||
ownerContainer != strapContainer)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HasComp<HandsComponent>(user))
|
|
||||||
{
|
|
||||||
_popups.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), user, user);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buckle.Buckled)
|
|
||||||
{
|
|
||||||
var message = Loc.GetString(buckleId == user
|
|
||||||
? "buckle-component-already-buckled-message"
|
|
||||||
: "buckle-component-other-already-buckled-message",
|
|
||||||
("owner", Identity.Entity(buckleId, EntityManager)));
|
|
||||||
_popups.PopupEntity(message, user, user);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parent = Transform(to).ParentUid;
|
|
||||||
while (parent.IsValid())
|
|
||||||
{
|
|
||||||
if (parent == user)
|
|
||||||
{
|
|
||||||
var message = Loc.GetString(buckleId == user
|
|
||||||
? "buckle-component-cannot-buckle-message"
|
|
||||||
: "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleId, EntityManager)));
|
|
||||||
_popups.PopupEntity(message, user, user);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = Transform(parent).ParentUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!StrapHasSpace(to, buckle, strap))
|
|
||||||
{
|
|
||||||
var message = Loc.GetString(buckleId == user
|
|
||||||
? "buckle-component-cannot-fit-message"
|
|
||||||
: "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(buckleId, EntityManager)));
|
|
||||||
_popups.PopupEntity(message, user, user);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryBuckle(EntityUid buckleId, EntityUid user, EntityUid to, BuckleComponent? buckle = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(buckleId, ref buckle, false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!CanBuckle(buckleId, user, to, out var strap, buckle))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_audio.PlayPvs(strap.BuckleSound, buckleId);
|
|
||||||
|
|
||||||
if (!StrapTryAdd(to, buckle, strap: strap))
|
|
||||||
{
|
|
||||||
var message = Loc.GetString(buckleId == user
|
|
||||||
? "buckle-component-cannot-buckle-message"
|
|
||||||
: "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleId, EntityManager)));
|
|
||||||
_popups.PopupEntity(message, user, user);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryComp<AppearanceComponent>(buckleId, out var appearance))
|
|
||||||
_appearance.SetData(buckleId, BuckleVisuals.Buckled, true, appearance);
|
|
||||||
|
|
||||||
ReAttach(buckleId, strap, buckle);
|
|
||||||
SetBuckledTo(buckle, strap);
|
|
||||||
|
|
||||||
var ev = new BuckleChangeEvent { Buckling = true, Strap = strap.Owner, BuckledEntity = buckleId };
|
|
||||||
RaiseLocalEvent(ev.BuckledEntity, ev);
|
|
||||||
RaiseLocalEvent(ev.Strap, ev);
|
|
||||||
|
|
||||||
if (TryComp(buckleId, out SharedPullableComponent? ownerPullable))
|
|
||||||
{
|
|
||||||
if (ownerPullable.Puller != null)
|
|
||||||
{
|
|
||||||
_pulling.TryStopPull(ownerPullable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryComp(to, out SharedPullableComponent? toPullable))
|
|
||||||
{
|
|
||||||
if (toPullable.Puller == buckleId)
|
|
||||||
{
|
|
||||||
// can't pull it and buckle to it at the same time
|
|
||||||
_pulling.TryStopPull(toPullable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
if (user != buckleId)
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled {ToPrettyString(buckleId)} to {ToPrettyString(to)}");
|
|
||||||
else
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled themselves to {ToPrettyString(to)}");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to unbuckle the Owner of this component from its current strap.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buckleId">The entity to unbuckle.</param>
|
|
||||||
/// <param name="user">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="buckle">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 buckleId, EntityUid user, bool force = false, BuckleComponent? buckle = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(buckleId, ref buckle, false) ||
|
|
||||||
buckle.BuckledTo is not { } oldBuckledTo)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!force)
|
|
||||||
{
|
|
||||||
if (_gameTiming.CurTime < buckle.BuckleTime + buckle.UnbuckleDelay)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!_interactions.InRangeUnobstructed(user, oldBuckledTo.Owner, buckle.Range, popup: true))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (HasComp<SleepingComponent>(buckleId) && buckleId == user)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// If the strap is a vehicle and the rider is not the person unbuckling, return.
|
|
||||||
if (TryComp(oldBuckledTo.Owner, out VehicleComponent? vehicle) &&
|
|
||||||
vehicle.Rider != user)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
if (user != buckleId)
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} unbuckled {ToPrettyString(buckleId)} from {ToPrettyString(oldBuckledTo.Owner)}");
|
|
||||||
else
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} unbuckled themselves from {ToPrettyString(oldBuckledTo.Owner)}");
|
|
||||||
|
|
||||||
SetBuckledTo(buckle, null);
|
|
||||||
|
|
||||||
var xform = Transform(buckleId);
|
|
||||||
var oldBuckledXform = Transform(oldBuckledTo.Owner);
|
|
||||||
|
|
||||||
if (xform.ParentUid == oldBuckledXform.Owner && !Terminating(xform.ParentUid))
|
|
||||||
{
|
|
||||||
_containers.AttachParentToContainerOrGrid(xform);
|
|
||||||
xform.WorldRotation = oldBuckledXform.WorldRotation;
|
|
||||||
|
|
||||||
if (oldBuckledTo.UnbuckleOffset != Vector2.Zero)
|
|
||||||
xform.Coordinates = oldBuckledXform.Coordinates.Offset(oldBuckledTo.UnbuckleOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryComp(buckleId, out AppearanceComponent? appearance))
|
|
||||||
_appearance.SetData(buckleId, BuckleVisuals.Buckled, false, appearance);
|
|
||||||
|
|
||||||
if ((TryComp<MobStateComponent>(buckleId, out var mobState) && _mobState.IsIncapacitated(buckleId, mobState)) ||
|
|
||||||
HasComp<KnockedDownComponent>(buckleId))
|
|
||||||
{
|
|
||||||
_standing.Down(buckleId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_standing.Stand(buckleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_mobState.IsIncapacitated(buckleId, mobState))
|
|
||||||
{
|
|
||||||
_standing.Down(buckleId);
|
|
||||||
}
|
|
||||||
// Sync StrapComponent data
|
|
||||||
_appearance.SetData(oldBuckledTo.Owner, StrapVisuals.State, false);
|
|
||||||
if (oldBuckledTo.BuckledEntities.Remove(buckleId))
|
|
||||||
{
|
|
||||||
oldBuckledTo.OccupiedSize -= buckle.Size;
|
|
||||||
Dirty(oldBuckledTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
_audio.PlayPvs(oldBuckledTo.UnbuckleSound, buckleId);
|
|
||||||
|
|
||||||
var ev = new BuckleChangeEvent { Buckling = false, Strap = oldBuckledTo.Owner, BuckledEntity = buckleId };
|
|
||||||
RaiseLocalEvent(buckleId, ev);
|
|
||||||
RaiseLocalEvent(oldBuckledTo.Owner, ev);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Makes an entity toggle the buckling status of the owner to a
|
|
||||||
/// specific entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buckleId">The entity to buckle/unbuckle from <see cref="to"/>.</param>
|
|
||||||
/// <param name="user">The entity doing the buckling/unbuckling.</param>
|
|
||||||
/// <param name="to">
|
|
||||||
/// 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 buckleId,
|
|
||||||
EntityUid user,
|
|
||||||
EntityUid to,
|
|
||||||
bool force = false,
|
|
||||||
BuckleComponent? buckle = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(buckleId, ref buckle, false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (buckle.BuckledTo?.Owner == to)
|
|
||||||
{
|
|
||||||
return TryUnbuckle(buckleId, user, force, buckle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryBuckle(buckleId, user, to, buckle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.Construction.Completions;
|
|
||||||
using Content.Shared.Buckle.Components;
|
|
||||||
using Content.Shared.Destructible;
|
|
||||||
using Content.Shared.DragDrop;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Storage;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Containers;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
|
|
||||||
namespace Content.Server.Buckle.Systems;
|
|
||||||
|
|
||||||
public sealed partial class BuckleSystem
|
|
||||||
{
|
|
||||||
private void InitializeStrap()
|
|
||||||
{
|
|
||||||
SubscribeLocalEvent<StrapComponent, ComponentShutdown>(OnStrapShutdown);
|
|
||||||
SubscribeLocalEvent<StrapComponent, ComponentRemove>((_, c, _) => StrapRemoveAll(c));
|
|
||||||
SubscribeLocalEvent<StrapComponent, ComponentGetState>(OnStrapGetState);
|
|
||||||
SubscribeLocalEvent<StrapComponent, EntInsertedIntoContainerMessage>(ContainerModifiedStrap);
|
|
||||||
SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(ContainerModifiedStrap);
|
|
||||||
SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
|
|
||||||
SubscribeLocalEvent<StrapComponent, ContainerGettingInsertedAttemptEvent>(OnStrapInsertAttempt);
|
|
||||||
SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
|
|
||||||
SubscribeLocalEvent<StrapComponent, DestructionEventArgs>((_,c,_) => StrapRemoveAll(c));
|
|
||||||
SubscribeLocalEvent<StrapComponent, BreakageEventArgs>((_, c, _) => StrapRemoveAll(c));
|
|
||||||
SubscribeLocalEvent<StrapComponent, ConstructionBeforeDeleteEvent>((_, c, _) => StrapRemoveAll(c));
|
|
||||||
SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDrop);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args)
|
|
||||||
{
|
|
||||||
args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ContainerModifiedStrap(EntityUid uid, StrapComponent strap, ContainerModifiedMessage message)
|
|
||||||
{
|
|
||||||
if (GameTiming.ApplyingState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var buckledEntity in strap.BuckledEntities)
|
|
||||||
{
|
|
||||||
if (!TryComp(buckledEntity, out BuckleComponent? buckled))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainerModifiedReAttach(buckledEntity, strap.Owner, buckled, strap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ContainerModifiedReAttach(EntityUid buckleId, EntityUid strapId, BuckleComponent? buckle = null, StrapComponent? strap = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(buckleId, ref buckle, false) ||
|
|
||||||
!Resolve(strapId, ref strap, false))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var contained = _containers.TryGetContainingContainer(buckleId, out var ownContainer);
|
|
||||||
var strapContained = _containers.TryGetContainingContainer(strapId, out var strapContainer);
|
|
||||||
|
|
||||||
if (contained != strapContained || ownContainer != strapContainer)
|
|
||||||
{
|
|
||||||
TryUnbuckle(buckleId, buckle.Owner, true, buckle);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contained)
|
|
||||||
{
|
|
||||||
ReAttach(buckleId, strap, buckle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStrapShutdown(EntityUid uid, StrapComponent component, ComponentShutdown args)
|
|
||||||
{
|
|
||||||
if (LifeStage(uid) > EntityLifeStage.MapInitialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
StrapRemoveAll(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStrapInsertAttempt(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<SharedStorageComponent>(args.Container.Owner) && component.BuckledEntities.Count != 0)
|
|
||||||
args.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
|
|
||||||
{
|
|
||||||
if (args.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ToggleBuckle(args.User, args.User, uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddStrapVerbs(EntityUid uid, StrapComponent strap, GetVerbsEvent<InteractionVerb> args)
|
|
||||||
{
|
|
||||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract || !strap.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 strap.BuckledEntities)
|
|
||||||
{
|
|
||||||
var buckledComp = Comp<BuckleComponent>(entity);
|
|
||||||
|
|
||||||
if (!_interactions.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
InteractionVerb verb = new()
|
|
||||||
{
|
|
||||||
Act = () => TryUnbuckle(entity, args.User, buckle: buckledComp),
|
|
||||||
Category = VerbCategory.Unbuckle
|
|
||||||
};
|
|
||||||
|
|
||||||
if (entity == args.User)
|
|
||||||
verb.Text = Loc.GetString("verb-self-target-pronoun");
|
|
||||||
else
|
|
||||||
verb.Text = 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(args.User, out BuckleComponent? buckle) &&
|
|
||||||
buckle.BuckledTo != strap &&
|
|
||||||
args.User != strap.Owner &&
|
|
||||||
StrapHasSpace(uid, buckle, strap) &&
|
|
||||||
_interactions.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(@using, out BuckleComponent? usingBuckle) &&
|
|
||||||
StrapHasSpace(uid, usingBuckle, strap) &&
|
|
||||||
_interactions.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 (!_interactions.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
|
|
||||||
return;
|
|
||||||
|
|
||||||
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 = HasComp<ActorComponent>(@using) ? 1 : -1
|
|
||||||
};
|
|
||||||
|
|
||||||
args.Verbs.Add(verb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StrapRemoveAll(StrapComponent strap)
|
|
||||||
{
|
|
||||||
foreach (var entity in strap.BuckledEntities.ToArray())
|
|
||||||
{
|
|
||||||
TryUnbuckle(entity, entity, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
strap.BuckledEntities.Clear();
|
|
||||||
strap.OccupiedSize = 0;
|
|
||||||
Dirty(strap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStrapDragDrop(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 bool StrapHasSpace(EntityUid strapId, BuckleComponent buckle, StrapComponent? strap = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(strapId, ref strap, false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return strap.OccupiedSize + buckle.Size <= strap.Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool StrapTryAdd(EntityUid strapId, BuckleComponent buckle, bool force = false, StrapComponent? strap = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(strapId, ref strap, false) ||
|
|
||||||
!strap.Enabled)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!force && !StrapHasSpace(strapId, buckle, strap))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!strap.BuckledEntities.Add(buckle.Owner))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
strap.OccupiedSize += buckle.Size;
|
|
||||||
|
|
||||||
_appearance.SetData(buckle.Owner, StrapVisuals.RotationAngle, strap.Rotation);
|
|
||||||
|
|
||||||
_appearance.SetData(strap.Owner, StrapVisuals.State, true);
|
|
||||||
|
|
||||||
Dirty(strap);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StrapSetEnabled(EntityUid strapId, bool enabled, StrapComponent? strap = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(strapId, ref strap, false) ||
|
|
||||||
strap.Enabled == enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
strap.Enabled = enabled;
|
|
||||||
|
|
||||||
if (!enabled)
|
|
||||||
StrapRemoveAll(strap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +1,7 @@
|
|||||||
using Content.Server.Interaction;
|
|
||||||
using Content.Server.Popups;
|
|
||||||
using Content.Server.Pulling;
|
|
||||||
using Content.Shared.ActionBlocker;
|
|
||||||
using Content.Shared.Alert;
|
|
||||||
using Content.Shared.Buckle;
|
using Content.Shared.Buckle;
|
||||||
using Content.Shared.Mobs.Systems;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.Containers;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Server.Buckle.Systems;
|
namespace Content.Server.Buckle.Systems;
|
||||||
|
|
||||||
[UsedImplicitly]
|
public sealed class BuckleSystem : SharedBuckleSystem
|
||||||
public sealed partial class BuckleSystem : SharedBuckleSystem
|
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
||||||
|
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
|
||||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
|
||||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
||||||
[Dependency] private readonly ContainerSystem _containers = default!;
|
|
||||||
[Dependency] private readonly InteractionSystem _interactions = default!;
|
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
|
||||||
[Dependency] private readonly PopupSystem _popups = default!;
|
|
||||||
[Dependency] private readonly PullingSystem _pulling = default!;
|
|
||||||
[Dependency] private readonly Shared.Standing.StandingStateSystem _standing = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
UpdatesAfter.Add(typeof(InteractionSystem));
|
|
||||||
UpdatesAfter.Add(typeof(InputSystem));
|
|
||||||
|
|
||||||
InitializeBuckle();
|
|
||||||
InitializeStrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ public sealed class ClimbSystem : SharedClimbSystem
|
|||||||
Climb(uid, uid, uid, climbable, true, component);
|
Climb(uid, uid, uid, climbable, true, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBuckleChange(EntityUid uid, ClimbingComponent component, BuckleChangeEvent args)
|
private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args)
|
||||||
{
|
{
|
||||||
if (!args.Buckling)
|
if (!args.Buckling)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Content.Server.Disease.Cures
|
|||||||
public override bool Cure(DiseaseEffectArgs args)
|
public override bool Cure(DiseaseEffectArgs args)
|
||||||
{
|
{
|
||||||
if (!args.EntityManager.TryGetComponent<BuckleComponent>(args.DiseasedEntity, out var buckle) ||
|
if (!args.EntityManager.TryGetComponent<BuckleComponent>(args.DiseasedEntity, out var buckle) ||
|
||||||
!args.EntityManager.HasComponent<HealOnBuckleComponent>(buckle.BuckledTo?.Owner))
|
!args.EntityManager.HasComponent<HealOnBuckleComponent>(buckle.BuckledTo))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var ticks = 1;
|
var ticks = 1;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Buckle.Systems;
|
|
||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
|
using Content.Shared.Buckle;
|
||||||
using Content.Shared.Buckle.Components;
|
using Content.Shared.Buckle.Components;
|
||||||
using Content.Shared.Foldable;
|
using Content.Shared.Foldable;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
@@ -13,7 +13,7 @@ namespace Content.Server.Foldable
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class FoldableSystem : SharedFoldableSystem
|
public sealed class FoldableSystem : SharedFoldableSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
[Dependency] private readonly SharedBuckleSystem _buckle = default!;
|
||||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using Content.Shared.Actions.ActionTypes;
|
|
||||||
using Content.Shared.Light;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
|
|
||||||
namespace Content.Server.Light.Components
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This is simplified version of <see cref="HandheldLightComponent"/>.
|
|
||||||
/// It doesn't consume any power and can be toggle only by verb.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class UnpoweredFlashlightComponent : Component
|
|
||||||
{
|
|
||||||
[DataField("toggleFlashlightSound")]
|
|
||||||
public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
|
|
||||||
|
|
||||||
[ViewVariables] public bool LightOn = false;
|
|
||||||
|
|
||||||
[DataField("toggleAction", required: true)]
|
|
||||||
public InstantAction ToggleAction = new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ using Content.Server.Light.Events;
|
|||||||
using Content.Server.Mind.Components;
|
using Content.Server.Mind.Components;
|
||||||
using Content.Shared.Actions;
|
using Content.Shared.Actions;
|
||||||
using Content.Shared.Light;
|
using Content.Shared.Light;
|
||||||
|
using Content.Shared.Light.Component;
|
||||||
using Content.Shared.Toggleable;
|
using Content.Shared.Toggleable;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Buckle.Systems;
|
using Content.Shared.Buckle;
|
||||||
|
|
||||||
namespace Content.Server.NPC.HTN.Preconditions;
|
namespace Content.Server.NPC.HTN.Preconditions;
|
||||||
|
|
||||||
@@ -7,14 +7,14 @@ namespace Content.Server.NPC.HTN.Preconditions;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class BuckledPrecondition : HTNPrecondition
|
public sealed class BuckledPrecondition : HTNPrecondition
|
||||||
{
|
{
|
||||||
private BuckleSystem _buckle = default!;
|
private SharedBuckleSystem _buckle = default!;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("isBuckled")] public bool IsBuckled = true;
|
[ViewVariables(VVAccess.ReadWrite)] [DataField("isBuckled")] public bool IsBuckled = true;
|
||||||
|
|
||||||
public override void Initialize(IEntitySystemManager sysManager)
|
public override void Initialize(IEntitySystemManager sysManager)
|
||||||
{
|
{
|
||||||
base.Initialize(sysManager);
|
base.Initialize(sysManager);
|
||||||
_buckle = sysManager.GetEntitySystem<BuckleSystem>();
|
_buckle = sysManager.GetEntitySystem<SharedBuckleSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsMet(NPCBlackboard blackboard)
|
public override bool IsMet(NPCBlackboard blackboard)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Robust.Shared.Containers;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Content.Server.Mind.Components;
|
using Content.Server.Mind.Components;
|
||||||
using Content.Server.Traitor;
|
using Content.Server.Traitor;
|
||||||
|
using Content.Shared.Light.Component;
|
||||||
|
|
||||||
namespace Content.Server.PDA
|
namespace Content.Server.PDA
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Content.Server.Actions;
|
using Content.Server.Actions;
|
||||||
using Content.Server.Buckle.Systems;
|
|
||||||
using Content.Server.Humanoid;
|
using Content.Server.Humanoid;
|
||||||
using Content.Server.Inventory;
|
using Content.Server.Inventory;
|
||||||
using Content.Server.Mind.Commands;
|
using Content.Server.Mind.Commands;
|
||||||
@@ -7,6 +6,7 @@ using Content.Server.Mind.Components;
|
|||||||
using Content.Server.Polymorph.Components;
|
using Content.Server.Polymorph.Components;
|
||||||
using Content.Shared.Actions;
|
using Content.Shared.Actions;
|
||||||
using Content.Shared.Actions.ActionTypes;
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Content.Shared.Buckle;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Hands.EntitySystems;
|
using Content.Shared.Hands.EntitySystems;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
@@ -30,7 +30,7 @@ namespace Content.Server.Polymorph.Systems
|
|||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||||
[Dependency] private readonly AudioSystem _audio = default!;
|
[Dependency] private readonly AudioSystem _audio = default!;
|
||||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
[Dependency] private readonly SharedBuckleSystem _buckle = default!;
|
||||||
[Dependency] private readonly ContainerSystem _container = default!;
|
[Dependency] private readonly ContainerSystem _container = default!;
|
||||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Buckle.Systems;
|
using Content.Shared.Buckle;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Server.Storage.EntitySystems;
|
using Content.Server.Storage.EntitySystems;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
using Content.Shared.Buckle.Components;
|
using Content.Shared.Buckle.Components;
|
||||||
using Content.Shared.DoAfter;
|
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
@@ -37,7 +36,7 @@ namespace Content.Server.Toilet
|
|||||||
SubscribeLocalEvent<ToiletComponent, ComponentInit>(OnInit);
|
SubscribeLocalEvent<ToiletComponent, ComponentInit>(OnInit);
|
||||||
SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<ToiletComponent, InteractUsingEvent>(OnInteractUsing);
|
SubscribeLocalEvent<ToiletComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(BuckleSystem)});
|
SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(SharedBuckleSystem)});
|
||||||
SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
|
SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
|
||||||
SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
|
SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
|
||||||
SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
|
SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
using Content.Server.Standing;
|
|
||||||
using Content.Shared.Hands;
|
|
||||||
using Content.Shared.Mobs;
|
|
||||||
using Content.Shared.Vehicle.Components;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
|
|
||||||
namespace Content.Server.Vehicle
|
|
||||||
{
|
|
||||||
public sealed partial class VehicleSystem
|
|
||||||
{
|
|
||||||
private void InitializeRider()
|
|
||||||
{
|
|
||||||
SubscribeLocalEvent<RiderComponent, ComponentGetState>(OnRiderGetState);
|
|
||||||
SubscribeLocalEvent<RiderComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
|
|
||||||
SubscribeLocalEvent<RiderComponent, FellDownEvent>(OnFallDown);
|
|
||||||
SubscribeLocalEvent<RiderComponent, MobStateChangedEvent>(OnMobStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRiderGetState(EntityUid uid, RiderComponent component, ref ComponentGetState args)
|
|
||||||
{
|
|
||||||
args.State = new RiderComponentState()
|
|
||||||
{
|
|
||||||
Entity = component.Vehicle,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kick the rider off the vehicle if they press q / drop the virtual item
|
|
||||||
/// </summary>
|
|
||||||
private void OnVirtualItemDeleted(EntityUid uid, RiderComponent component, VirtualItemDeletedEvent args)
|
|
||||||
{
|
|
||||||
if (args.BlockingEntity == component.Vehicle)
|
|
||||||
{
|
|
||||||
UnbuckleFromVehicle(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kick the rider off the vehicle if they get stunned
|
|
||||||
/// </summary>
|
|
||||||
private void OnFallDown(EntityUid uid, RiderComponent rider, FellDownEvent args)
|
|
||||||
{
|
|
||||||
UnbuckleFromVehicle(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kick the rider off the vehicle if they go into crit or die.
|
|
||||||
/// </summary>
|
|
||||||
private void OnMobStateChanged(EntityUid uid, RiderComponent rider, MobStateChangedEvent args)
|
|
||||||
{
|
|
||||||
if (args.NewMobState is MobState.Critical or MobState.Dead)
|
|
||||||
{
|
|
||||||
UnbuckleFromVehicle(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnbuckleFromVehicle(EntityUid uid)
|
|
||||||
{
|
|
||||||
_buckle.TryUnbuckle(uid, uid, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +1,7 @@
|
|||||||
using Content.Server.Buckle.Systems;
|
|
||||||
using Content.Server.Hands.Systems;
|
|
||||||
using Content.Server.Light.Components;
|
|
||||||
using Content.Shared.Actions;
|
|
||||||
using Content.Shared.Buckle.Components;
|
|
||||||
using Content.Shared.Movement.Components;
|
|
||||||
using Content.Shared.Movement.Systems;
|
|
||||||
using Content.Shared.Vehicle;
|
using Content.Shared.Vehicle;
|
||||||
using Content.Shared.Vehicle.Components;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Physics.Systems;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
|
|
||||||
namespace Content.Server.Vehicle
|
namespace Content.Server.Vehicle;
|
||||||
|
|
||||||
|
public sealed class VehicleSystem : SharedVehicleSystem
|
||||||
{
|
{
|
||||||
public sealed partial class VehicleSystem : SharedVehicleSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
|
||||||
[Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!;
|
|
||||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
|
||||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
|
||||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
InitializeRider();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<VehicleComponent, HonkActionEvent>(OnHonk);
|
|
||||||
SubscribeLocalEvent<VehicleComponent, BuckleChangeEvent>(OnBuckleChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This fires when the rider presses the honk action
|
|
||||||
/// </summary>
|
|
||||||
private void OnHonk(EntityUid uid, VehicleComponent vehicle, HonkActionEvent args)
|
|
||||||
{
|
|
||||||
if (args.Handled || vehicle.HornSound == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: Need audio refactor maybe, just some way to null it when the stream is over.
|
|
||||||
// For now better to just not loop to keep the code much cleaner.
|
|
||||||
vehicle.HonkPlayingStream?.Stop();
|
|
||||||
vehicle.HonkPlayingStream = SoundSystem.Play(vehicle.HornSound.GetSound(), Filter.Pvs(uid), uid, vehicle.HornSound.Params);
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This just controls whether the wheels are turning.
|
|
||||||
/// </summary>
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
foreach (var (vehicle, mover) in EntityQuery<VehicleComponent, InputMoverComponent>())
|
|
||||||
{
|
|
||||||
if (_mover.GetVelocityInput(mover).Sprinting == Vector2.Zero)
|
|
||||||
{
|
|
||||||
UpdateAutoAnimate(vehicle.Owner, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
UpdateAutoAnimate(vehicle.Owner, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Give the user the rider component if they're buckling to the vehicle,
|
|
||||||
/// otherwise remove it.
|
|
||||||
/// </summary>
|
|
||||||
private void OnBuckleChange(EntityUid uid, VehicleComponent component, BuckleChangeEvent args)
|
|
||||||
{
|
|
||||||
// Add Rider
|
|
||||||
if (args.Buckling)
|
|
||||||
{
|
|
||||||
// Add a virtual item to rider's hand, unbuckle if we can't.
|
|
||||||
if (!_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.BuckledEntity))
|
|
||||||
{
|
|
||||||
UnbuckleFromVehicle(args.BuckledEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the rider and vehicle with each other
|
|
||||||
EnsureComp<InputMoverComponent>(uid);
|
|
||||||
var rider = EnsureComp<RiderComponent>(args.BuckledEntity);
|
|
||||||
component.Rider = args.BuckledEntity;
|
|
||||||
|
|
||||||
var relay = EnsureComp<RelayInputMoverComponent>(args.BuckledEntity);
|
|
||||||
_mover.SetRelay(args.BuckledEntity, uid, relay);
|
|
||||||
rider.Vehicle = uid;
|
|
||||||
|
|
||||||
// Update appearance stuff, add actions
|
|
||||||
UpdateBuckleOffset(Transform(uid), component);
|
|
||||||
if (TryComp<InputMoverComponent>(uid, out var mover))
|
|
||||||
UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component, mover.RelativeRotation.Degrees));
|
|
||||||
|
|
||||||
if (TryComp<ActionsComponent>(args.BuckledEntity, out var actions) && TryComp<UnpoweredFlashlightComponent>(uid, out var flashlight))
|
|
||||||
{
|
|
||||||
_actionsSystem.AddAction(args.BuckledEntity, flashlight.ToggleAction, uid, actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.HornSound != null)
|
|
||||||
{
|
|
||||||
_actionsSystem.AddAction(args.BuckledEntity, component.HornAction, uid, actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
_joints.ClearJoints(args.BuckledEntity);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove rider
|
|
||||||
|
|
||||||
// Clean up actions and virtual items
|
|
||||||
_actionsSystem.RemoveProvidedActions(args.BuckledEntity, uid);
|
|
||||||
_virtualItemSystem.DeleteInHandsMatching(args.BuckledEntity, uid);
|
|
||||||
|
|
||||||
// Entity is no longer riding
|
|
||||||
RemComp<RiderComponent>(args.BuckledEntity);
|
|
||||||
RemComp<RelayInputMoverComponent>(args.BuckledEntity);
|
|
||||||
|
|
||||||
// Reset component
|
|
||||||
component.Rider = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,76 +5,99 @@ using Robust.Shared.Serialization;
|
|||||||
namespace Content.Shared.Buckle.Components;
|
namespace Content.Shared.Buckle.Components;
|
||||||
|
|
||||||
[RegisterComponent, NetworkedComponent]
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[Access(typeof(SharedBuckleSystem))]
|
||||||
public sealed class BuckleComponent : Component
|
public sealed class BuckleComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The range from which this entity can buckle to a <see cref="StrapComponent"/>.
|
/// The range from which this entity can buckle to a <see cref="StrapComponent"/>.
|
||||||
|
/// Separated from normal interaction range to fix the "someone buckled to a strap
|
||||||
|
/// across a table two tiles away" problem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("range")]
|
[DataField("range")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float Range = SharedInteractionSystem.InteractionRange / 1.4f;
|
public float Range = SharedInteractionSystem.InteractionRange / 1.4f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the entity is buckled, false otherwise.
|
/// True if the entity is buckled, false otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Buckled { get; set; }
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool Buckled;
|
||||||
|
|
||||||
public EntityUid? LastEntityBuckledTo { get; set; }
|
[ViewVariables]
|
||||||
|
public EntityUid? LastEntityBuckledTo;
|
||||||
public bool DontCollide { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of time that must pass for this entity to
|
/// Whether or not collisions should be possible with the entity we are strapped to
|
||||||
/// be able to unbuckle after recently buckling.
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("dontCollide")]
|
||||||
|
public bool DontCollide;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not we should be allowed to pull the entity we are strapped to
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("pullStrap")]
|
||||||
|
public bool PullStrap;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time that must pass for this entity to
|
||||||
|
/// be able to unbuckle after recently buckling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("delay")]
|
[DataField("delay")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f);
|
public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time that this entity buckled at.
|
/// The time that this entity buckled at.
|
||||||
/// </summary>
|
|
||||||
[ViewVariables] public TimeSpan BuckleTime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The strap that this component is buckled to.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public StrapComponent? BuckledTo { get; set; }
|
public TimeSpan BuckleTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of space that this entity occupies in a
|
/// The strap that this component is buckled to.
|
||||||
/// <see cref="StrapComponent"/>.
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityUid? BuckledTo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of space that this entity occupies in a
|
||||||
|
/// <see cref="StrapComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("size")]
|
[DataField("size")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public int Size = 100;
|
public int Size = 100;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for client rendering
|
/// Used for client rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? OriginalDrawDepth { get; set; }
|
[ViewVariables]
|
||||||
|
public int? OriginalDrawDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class BuckleComponentState : ComponentState
|
public sealed class BuckleComponentState : ComponentState
|
||||||
{
|
{
|
||||||
public BuckleComponentState(bool buckled, EntityUid? lastEntityBuckledTo, bool dontCollide)
|
public BuckleComponentState(bool buckled, EntityUid? buckledTo, EntityUid? lastEntityBuckledTo,
|
||||||
|
bool dontCollide)
|
||||||
{
|
{
|
||||||
Buckled = buckled;
|
Buckled = buckled;
|
||||||
|
BuckledTo = buckledTo;
|
||||||
LastEntityBuckledTo = lastEntityBuckledTo;
|
LastEntityBuckledTo = lastEntityBuckledTo;
|
||||||
DontCollide = dontCollide;
|
DontCollide = dontCollide;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Buckled { get; }
|
public readonly bool Buckled;
|
||||||
public EntityUid? LastEntityBuckledTo { get; }
|
public readonly EntityUid? BuckledTo;
|
||||||
public bool DontCollide { get; }
|
public readonly EntityUid? LastEntityBuckledTo;
|
||||||
|
public readonly bool DontCollide;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BuckleChangeEvent : EntityEventArgs
|
[ByRefEvent]
|
||||||
{
|
public readonly record struct BuckleAttemptEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling, bool Cancelled = false);
|
||||||
public EntityUid Strap;
|
|
||||||
|
|
||||||
public EntityUid BuckledEntity;
|
[ByRefEvent]
|
||||||
public bool Buckling;
|
public readonly record struct BuckleChangeEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling);
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum BuckleVisuals
|
public enum BuckleVisuals
|
||||||
|
|||||||
@@ -1,10 +1,143 @@
|
|||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Vehicle;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Buckle.Components;
|
namespace Content.Shared.Buckle.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[Access(typeof(SharedBuckleSystem), typeof(SharedVehicleSystem))]
|
||||||
|
public sealed class StrapComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The entities that are currently buckled
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public readonly HashSet<EntityUid> BuckledEntities = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entities that this strap accepts and can buckle
|
||||||
|
/// If null it accepts any entity
|
||||||
|
/// </summary>
|
||||||
|
[DataField("allowedEntities")]
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityWhitelist? AllowedEntities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The change in position to the strapped mob
|
||||||
|
/// </summary>
|
||||||
|
[DataField("position")]
|
||||||
|
[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("maxBuckleDistance", required: false)]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxBuckleDistance = 0.2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets and clamps the buckle offset to MaxBuckleDistance
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Vector2 BuckleOffset => Vector2.Clamp(
|
||||||
|
BuckleOffsetUnclamped,
|
||||||
|
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("buckleOffset", required: false)]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The angle in degrees to rotate the player by when they get strapped
|
||||||
|
/// </summary>
|
||||||
|
[DataField("rotation")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int Rotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the strap which is compared against when buckling entities
|
||||||
|
/// </summary>
|
||||||
|
[DataField("size")]
|
||||||
|
[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]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// You can specify the offset the entity will have after unbuckling.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("unbuckleOffset", required: false)]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public Vector2 UnbuckleOffset = Vector2.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound to be played when a mob is buckled
|
||||||
|
/// </summary>
|
||||||
|
[DataField("buckleSound")]
|
||||||
|
[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("unbuckleSound")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the alert to show when buckled
|
||||||
|
/// </summary>
|
||||||
|
[DataField("buckledAlertType")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public AlertType BuckledAlertType = AlertType.Buckled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sum of the sizes of all the buckled entities in this strap
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int OccupiedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class StrapComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public readonly StrapPosition Position;
|
||||||
|
public readonly float MaxBuckleDistance;
|
||||||
|
public readonly Vector2 BuckleOffsetClamped;
|
||||||
|
public readonly HashSet<EntityUid> BuckledEntities;
|
||||||
|
public readonly int OccupiedSize;
|
||||||
|
|
||||||
|
public StrapComponentState(StrapPosition position, Vector2 offset, HashSet<EntityUid> buckled,
|
||||||
|
float maxBuckleDistance, int occupiedSize)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
BuckleOffsetClamped = offset;
|
||||||
|
BuckledEntities = buckled;
|
||||||
|
MaxBuckleDistance = maxBuckleDistance;
|
||||||
|
OccupiedSize = occupiedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum StrapPosition
|
public enum StrapPosition
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -23,111 +156,6 @@ public enum StrapPosition
|
|||||||
Down
|
Down
|
||||||
}
|
}
|
||||||
|
|
||||||
[RegisterComponent, NetworkedComponent]
|
|
||||||
public sealed class StrapComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The change in position to the strapped mob
|
|
||||||
/// </summary>
|
|
||||||
[DataField("position")]
|
|
||||||
public StrapPosition Position { get; set; } = StrapPosition.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The entity that is currently buckled here
|
|
||||||
/// </summary>
|
|
||||||
public readonly HashSet<EntityUid> BuckledEntities = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The distance above which a buckled entity will be automatically unbuckled.
|
|
||||||
/// Don't change it unless you really have to
|
|
||||||
/// </summary>
|
|
||||||
[DataField("maxBuckleDistance", required: false)]
|
|
||||||
public float MaxBuckleDistance = 0.1f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets and clamps the buckle offset to MaxBuckleDistance
|
|
||||||
/// </summary>
|
|
||||||
public Vector2 BuckleOffset => Vector2.Clamp(
|
|
||||||
BuckleOffsetUnclamped,
|
|
||||||
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("buckleOffset", required: false)]
|
|
||||||
[Access(Other = AccessPermissions.ReadWrite)]
|
|
||||||
public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The angle in degrees to rotate the player by when they get strapped
|
|
||||||
/// </summary>
|
|
||||||
[DataField("rotation")]
|
|
||||||
public int Rotation { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The size of the strap which is compared against when buckling entities
|
|
||||||
/// </summary>
|
|
||||||
[DataField("size")]
|
|
||||||
public int Size { get; set; } = 100;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled
|
|
||||||
/// </summary>
|
|
||||||
public bool Enabled { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// You can specify the offset the entity will have after unbuckling.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("unbuckleOffset", required: false)]
|
|
||||||
public Vector2 UnbuckleOffset = Vector2.Zero;
|
|
||||||
/// <summary>
|
|
||||||
/// The sound to be played when a mob is buckled
|
|
||||||
/// </summary>
|
|
||||||
[DataField("buckleSound")]
|
|
||||||
public SoundSpecifier BuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The sound to be played when a mob is unbuckled
|
|
||||||
/// </summary>
|
|
||||||
[DataField("unbuckleSound")]
|
|
||||||
public SoundSpecifier UnbuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ID of the alert to show when buckled
|
|
||||||
/// </summary>
|
|
||||||
[DataField("buckledAlertType")]
|
|
||||||
public AlertType BuckledAlertType { get; } = AlertType.Buckled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The sum of the sizes of all the buckled entities in this strap
|
|
||||||
/// </summary>
|
|
||||||
public int OccupiedSize { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public sealed class StrapComponentState : ComponentState
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The change in position that this strap makes to the strapped mob
|
|
||||||
/// </summary>
|
|
||||||
public StrapPosition Position;
|
|
||||||
|
|
||||||
public float MaxBuckleDistance;
|
|
||||||
public Vector2 BuckleOffsetClamped;
|
|
||||||
public HashSet<EntityUid> BuckledEntities;
|
|
||||||
|
|
||||||
public StrapComponentState(StrapPosition position, Vector2 offset, HashSet<EntityUid> buckled, float maxBuckleDistance)
|
|
||||||
{
|
|
||||||
Position = position;
|
|
||||||
BuckleOffsetClamped = offset;
|
|
||||||
BuckledEntities = buckled;
|
|
||||||
MaxBuckleDistance = maxBuckleDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum StrapVisuals : byte
|
public enum StrapVisuals : byte
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
using Content.Shared.Buckle.Components;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
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.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Pulling.Components;
|
||||||
using Content.Shared.Standing;
|
using Content.Shared.Standing;
|
||||||
|
using Content.Shared.Storage.Components;
|
||||||
|
using Content.Shared.Stunnable;
|
||||||
using Content.Shared.Throwing;
|
using Content.Shared.Throwing;
|
||||||
using Content.Shared.Vehicle.Components;
|
using Content.Shared.Vehicle.Components;
|
||||||
using Robust.Shared.Map;
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Physics.Events;
|
using Robust.Shared.Physics.Events;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Buckle;
|
namespace Content.Shared.Buckle;
|
||||||
|
|
||||||
@@ -13,48 +27,125 @@ public abstract partial class SharedBuckleSystem
|
|||||||
{
|
{
|
||||||
private void InitializeBuckle()
|
private void InitializeBuckle()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(PreventCollision);
|
SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleComponentStartup);
|
||||||
SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(HandleDown);
|
SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleComponentShutdown);
|
||||||
SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(HandleStand);
|
SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnBuckleComponentGetState);
|
||||||
SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(HandleThrowPushback);
|
SubscribeLocalEvent<BuckleComponent, MoveEvent>(OnBuckleMove);
|
||||||
SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(HandleMove);
|
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(OnBuckleInteractHand);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnBuckleInsertIntoEntityStorageAttempt);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(OnBucklePreventCollide);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(OnBuckleDownAttempt);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(OnBuckleStandAttempt);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(OnBuckleThrowPushbackAttempt);
|
||||||
|
SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(OnBuckleUpdateCanMove);
|
||||||
SubscribeLocalEvent<BuckleComponent, ChangeDirectionAttemptEvent>(OnBuckleChangeDirectionAttempt);
|
SubscribeLocalEvent<BuckleComponent, ChangeDirectionAttemptEvent>(OnBuckleChangeDirectionAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PreventCollision(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
|
private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
|
||||||
{
|
{
|
||||||
if (args.BodyB.Owner != component.LastEntityBuckledTo)
|
UpdateBuckleStatus(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuckleComponentShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
TryUnbuckle(uid, uid, true, component);
|
||||||
|
|
||||||
|
component.BuckleTime = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuckleComponentGetState(EntityUid uid, BuckleComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new BuckleComponentState(component.Buckled, component.BuckledTo, component.LastEntityBuckledTo, component.DontCollide);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuckleMove(EntityUid uid, BuckleComponent component, ref MoveEvent ev)
|
||||||
|
{
|
||||||
|
if (component.BuckledTo is not {} strapUid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<StrapComponent>(strapUid, out var strapComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var strapPosition = Transform(strapUid).Coordinates;
|
||||||
|
if (ev.NewPosition.InRange(EntityManager, _transformSystem, strapPosition, strapComp.MaxBuckleDistance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TryUnbuckle(uid, uid, true, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuckleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
|
||||||
|
{
|
||||||
|
if (!component.Buckled)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuckleInsertIntoEntityStorageAttempt(EntityUid uid, BuckleComponent component, ref InsertIntoEntityStorageAttemptEvent args)
|
||||||
|
{
|
||||||
|
if (component.Buckled)
|
||||||
|
args.Cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBucklePreventCollide(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
|
||||||
|
{
|
||||||
|
if (args.BodyB.Owner != component.BuckledTo)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.Buckled || component.DontCollide)
|
if (component.Buckled || component.DontCollide)
|
||||||
args.Cancelled = true;
|
args.Cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleDown(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
|
private void OnBuckleDownAttempt(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (component.Buckled)
|
if (component.Buckled)
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleStand(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
|
private void OnBuckleStandAttempt(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (component.Buckled)
|
if (component.Buckled)
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleThrowPushback(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
|
private void OnBuckleThrowPushbackAttempt(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (component.Buckled)
|
if (component.Buckled)
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
|
private void OnBuckleUpdateCanMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
|
||||||
{
|
{
|
||||||
if (component.LifeStage > ComponentLifeStage.Running)
|
if (component.LifeStage > ComponentLifeStage.Running)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.Buckled &&
|
if (component.Buckled &&
|
||||||
!HasComp<VehicleComponent>(Transform(uid).ParentUid)) // buckle+vehicle shitcode
|
!HasComp<VehicleComponent>(component.BuckledTo)) // buckle+vehicle shitcode
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,38 +161,369 @@ public abstract partial class SharedBuckleSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reattaches this entity to the strap, modifying its position and rotation.
|
/// Shows or hides the buckled status effect depending on if the
|
||||||
|
/// entity is buckled or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buckleId">The entity to reattach.</param>
|
/// <param name="uid"> Entity that we want to show the alert </param>
|
||||||
/// <param name="strap">The strap to reattach to.</param>
|
/// <param name="buckleComp"> buckle component of the entity </param>
|
||||||
/// <param name="buckle">The buckle component of the entity to reattach.</param>
|
/// <param name="strapComp"> strap component of the thing we are strapping to </param>
|
||||||
public void ReAttach(EntityUid buckleId, StrapComponent strap, BuckleComponent? buckle = null)
|
private void UpdateBuckleStatus(EntityUid uid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(buckleId, ref buckle, false))
|
if (buckleComp.BuckledTo != null)
|
||||||
return;
|
|
||||||
|
|
||||||
var ownTransform = Transform(buckleId);
|
|
||||||
var strapTransform = Transform(strap.Owner);
|
|
||||||
|
|
||||||
ownTransform.Coordinates = new EntityCoordinates(strapTransform.Owner, strap.BuckleOffset);
|
|
||||||
|
|
||||||
// Buckle subscribes to move for <reasons> so this might fail.
|
|
||||||
// TODO: Make buckle not do that.
|
|
||||||
if (ownTransform.ParentUid != strapTransform.Owner)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ownTransform.LocalRotation = Angle.Zero;
|
|
||||||
|
|
||||||
switch (strap.Position)
|
|
||||||
{
|
{
|
||||||
case StrapPosition.None:
|
if (!Resolve(buckleComp.BuckledTo.Value, ref strapComp))
|
||||||
break;
|
return;
|
||||||
case StrapPosition.Stand:
|
|
||||||
_standing.Stand(buckleId);
|
var alertType = strapComp.BuckledAlertType;
|
||||||
break;
|
_alertsSystem.ShowAlert(uid, alertType);
|
||||||
case StrapPosition.Down:
|
}
|
||||||
_standing.Down(buckleId, false, false);
|
else
|
||||||
break;
|
{
|
||||||
|
_alertsSystem.ClearAlertCategory(uid, AlertCategory.Buckled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionBlockerSystem.UpdateCanMove(buckleUid);
|
||||||
|
UpdateBuckleStatus(buckleUid, buckleComp, strapComp);
|
||||||
|
Dirty(buckleComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether or not buckling is possible
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
|
||||||
|
/// <param name="userUid">
|
||||||
|
/// 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,
|
||||||
|
EntityUid strapUid,
|
||||||
|
[NotNullWhen(true)] out StrapComponent? strapComp,
|
||||||
|
BuckleComponent? buckleComp = null)
|
||||||
|
{
|
||||||
|
strapComp = null;
|
||||||
|
|
||||||
|
if (userUid == strapUid ||
|
||||||
|
!Resolve(buckleUid, ref buckleComp, false) ||
|
||||||
|
!Resolve(strapUid, ref strapComp, false))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does it pass the Whitelist
|
||||||
|
if (strapComp.AllowedEntities != null &&
|
||||||
|
!strapComp.AllowedEntities.IsValid(userUid, EntityManager))
|
||||||
|
{
|
||||||
|
if (_netManager.IsServer)
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it within range
|
||||||
|
bool Ignored(EntityUid entity) => entity == buckleUid || entity == userUid || entity == strapUid;
|
||||||
|
|
||||||
|
if (!_interactionSystem.InRangeUnobstructed(buckleUid, strapUid, buckleComp.Range, predicate: Ignored,
|
||||||
|
popup: true))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If in a container
|
||||||
|
if (_containerSystem.TryGetContainingContainer(buckleUid, out var ownerContainer))
|
||||||
|
{
|
||||||
|
// And not in the same container as the strap
|
||||||
|
if (!_containerSystem.TryGetContainingContainer(strapUid, out var strapContainer) ||
|
||||||
|
ownerContainer != strapContainer)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HasComp<HandsComponent>(userUid))
|
||||||
|
{
|
||||||
|
// PopupPredicted when
|
||||||
|
if (_netManager.IsServer)
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), userUid, userUid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buckleComp.Buckled)
|
||||||
|
{
|
||||||
|
var message = Loc.GetString(buckleUid == userUid
|
||||||
|
? "buckle-component-already-buckled-message"
|
||||||
|
: "buckle-component-other-already-buckled-message",
|
||||||
|
("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||||
|
if (_netManager.IsServer)
|
||||||
|
_popupSystem.PopupEntity(message, userUid, userUid);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = Transform(strapUid).ParentUid;
|
||||||
|
while (parent.IsValid())
|
||||||
|
{
|
||||||
|
if (parent == userUid)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
_popupSystem.PopupEntity(message, userUid, userUid);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = Transform(parent).ParentUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrapHasSpace(strapUid, buckleComp, strapComp))
|
||||||
|
{
|
||||||
|
var message = Loc.GetString(buckleUid == userUid
|
||||||
|
? "buckle-component-cannot-fit-message"
|
||||||
|
: "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
|
||||||
|
if (_netManager.IsServer)
|
||||||
|
_popupSystem.PopupEntity(message, userUid, userUid);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to buckle an entity to a strap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
|
||||||
|
/// <param name="userUid">
|
||||||
|
/// 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)
|
||||||
|
{
|
||||||
|
if (!Resolve(buckleUid, ref buckleComp, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CanBuckle(buckleUid, userUid, strapUid, out var strapComp, buckleComp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, true);
|
||||||
|
RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
|
||||||
|
RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
|
||||||
|
if (attemptEvent.Cancelled)
|
||||||
|
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)
|
||||||
|
_popupSystem.PopupEntity(message, userUid, userUid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<AppearanceComponent>(buckleUid, out var appearance))
|
||||||
|
AppearanceSystem.SetData(buckleUid, BuckleVisuals.Buckled, true, appearance);
|
||||||
|
|
||||||
|
ReAttach(buckleUid, strapUid, buckleComp, strapComp);
|
||||||
|
SetBuckledTo(buckleUid,strapUid, strapComp, buckleComp);
|
||||||
|
_audioSystem.PlayPredicted(strapComp.BuckleSound, strapUid, buckleUid);
|
||||||
|
|
||||||
|
var ev = new BuckleChangeEvent(strapUid, buckleUid, true);
|
||||||
|
RaiseLocalEvent(ev.BuckledEntity, ref ev);
|
||||||
|
RaiseLocalEvent(ev.StrapEntity, ref ev);
|
||||||
|
|
||||||
|
if (TryComp<SharedPullableComponent>(buckleUid, out var ownerPullable))
|
||||||
|
{
|
||||||
|
if (ownerPullable.Puller != null)
|
||||||
|
{
|
||||||
|
_pullingSystem.TryStopPull(ownerPullable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buckleComp.PullStrap && TryComp<SharedPullableComponent>(strapUid, out var toPullable))
|
||||||
|
{
|
||||||
|
if (toPullable.Puller == buckleUid)
|
||||||
|
{
|
||||||
|
// can't pull it and buckle to it at the same time
|
||||||
|
_pullingSystem.TryStopPull(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)}");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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="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)
|
||||||
|
{
|
||||||
|
if (!Resolve(buckleUid, ref buckleComp, false) ||
|
||||||
|
buckleComp.BuckledTo is not { } strapUid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, false);
|
||||||
|
RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
|
||||||
|
RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
|
||||||
|
if (attemptEvent.Cancelled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Logger.Debug($"{force}");
|
||||||
|
if (!force)
|
||||||
|
{
|
||||||
|
if (_gameTiming.CurTime < buckleComp.BuckleTime + buckleComp.UnbuckleDelay)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_interactionSystem.InRangeUnobstructed(userUid, strapUid, buckleComp.Range, popup: true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (HasComp<SleepingComponent>(buckleUid) && buckleUid == userUid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the strap is a vehicle and the rider is not the person unbuckling, return.
|
||||||
|
if (TryComp<VehicleComponent>(strapUid, out var vehicle) &&
|
||||||
|
vehicle.Rider != userUid)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var buckleXform = Transform(buckleUid);
|
||||||
|
var oldBuckledXform = Transform(strapUid);
|
||||||
|
|
||||||
|
if (buckleXform.ParentUid == strapUid && !Terminating(buckleXform.ParentUid))
|
||||||
|
{
|
||||||
|
_containerSystem.AttachParentToContainerOrGrid(buckleXform);
|
||||||
|
|
||||||
|
var oldBuckledToWorldRot = _transformSystem.GetWorldRotation(strapUid);
|
||||||
|
_transformSystem.SetWorldRotation(buckleXform, oldBuckledToWorldRot);
|
||||||
|
|
||||||
|
if (strapComp.UnbuckleOffset != Vector2.Zero)
|
||||||
|
buckleXform.Coordinates = oldBuckledXform.Coordinates.Offset(strapComp.UnbuckleOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp(buckleUid, out AppearanceComponent? appearance))
|
||||||
|
AppearanceSystem.SetData(buckleUid, BuckleVisuals.Buckled, false, appearance);
|
||||||
|
|
||||||
|
if (TryComp<MobStateComponent>(buckleUid, out var mobState)
|
||||||
|
&& _mobStateSystem.IsIncapacitated(buckleUid, mobState)
|
||||||
|
|| HasComp<KnockedDownComponent>(buckleUid))
|
||||||
|
{
|
||||||
|
_standingSystem.Down(buckleUid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_standingSystem.Stand(buckleUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mobStateSystem.IsIncapacitated(buckleUid, mobState))
|
||||||
|
{
|
||||||
|
_standingSystem.Down(buckleUid);
|
||||||
|
}
|
||||||
|
// Sync StrapComponent data
|
||||||
|
AppearanceSystem.SetData(strapUid, StrapVisuals.State, false);
|
||||||
|
if (strapComp.BuckledEntities.Remove(buckleUid))
|
||||||
|
{
|
||||||
|
strapComp.OccupiedSize -= buckleComp.Size;
|
||||||
|
//Dirty(strapUid);
|
||||||
|
Dirty(strapComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_audioSystem.PlayPredicted(strapComp.UnbuckleSound, strapUid, buckleUid);
|
||||||
|
|
||||||
|
var ev = new BuckleChangeEvent(strapUid, buckleUid, false);
|
||||||
|
RaiseLocalEvent(buckleUid, ref ev);
|
||||||
|
RaiseLocalEvent(strapUid, ref ev);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!Resolve(buckleUid, ref buckle, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!buckle.Buckled)
|
||||||
|
{
|
||||||
|
return TryBuckle(buckleUid, userUid, strapUid, buckle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TryUnbuckle(buckleUid, userUid, force, buckle);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,50 @@
|
|||||||
using Content.Shared.Buckle.Components;
|
using System.Linq;
|
||||||
|
using Content.Shared.Buckle.Components;
|
||||||
|
using Content.Shared.Destructible;
|
||||||
using Content.Shared.DragDrop;
|
using Content.Shared.DragDrop;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Storage;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Shared.Buckle;
|
namespace Content.Shared.Buckle;
|
||||||
|
|
||||||
public abstract partial class SharedBuckleSystem
|
public abstract partial class SharedBuckleSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedInteractionSystem _interactions = default!;
|
|
||||||
|
|
||||||
private void InitializeStrap()
|
private void InitializeStrap()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapRotate);
|
SubscribeLocalEvent<StrapComponent, ComponentShutdown>(OnStrapShutdown);
|
||||||
|
SubscribeLocalEvent<StrapComponent, ComponentRemove>((_, c, _) => StrapRemoveAll(c));
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StrapComponent, ComponentGetState>(OnStrapGetState);
|
||||||
SubscribeLocalEvent<StrapComponent, ComponentHandleState>(OnStrapHandleState);
|
SubscribeLocalEvent<StrapComponent, ComponentHandleState>(OnStrapHandleState);
|
||||||
SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnStrapCanDropOn);
|
|
||||||
|
SubscribeLocalEvent<StrapComponent, EntInsertedIntoContainerMessage>(OnStrapEntModifiedFromContainer);
|
||||||
|
SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(OnStrapEntModifiedFromContainer);
|
||||||
|
SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
|
||||||
|
SubscribeLocalEvent<StrapComponent, ContainerGettingInsertedAttemptEvent>(OnStrapContainerGettingInsertedAttempt);
|
||||||
|
SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
|
||||||
|
SubscribeLocalEvent<StrapComponent, DestructionEventArgs>((_,c,_) => StrapRemoveAll(c));
|
||||||
|
SubscribeLocalEvent<StrapComponent, BreakageEventArgs>((_, c, _) => StrapRemoveAll(c));
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDropTarget);
|
||||||
|
SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnCanDropTarget);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStrapShutdown(EntityUid uid, StrapComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (LifeStage(uid) > EntityLifeStage.MapInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StrapRemoveAll(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance, component.OccupiedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStrapHandleState(EntityUid uid, StrapComponent component, ref ComponentHandleState args)
|
private void OnStrapHandleState(EntityUid uid, StrapComponent component, ref ComponentHandleState args)
|
||||||
@@ -26,9 +57,151 @@ public abstract partial class SharedBuckleSystem
|
|||||||
component.BuckledEntities.Clear();
|
component.BuckledEntities.Clear();
|
||||||
component.BuckledEntities.UnionWith(state.BuckledEntities);
|
component.BuckledEntities.UnionWith(state.BuckledEntities);
|
||||||
component.MaxBuckleDistance = state.MaxBuckleDistance;
|
component.MaxBuckleDistance = state.MaxBuckleDistance;
|
||||||
|
component.OccupiedSize = state.OccupiedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStrapRotate(EntityUid uid, StrapComponent component, ref MoveEvent args)
|
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 = _containerSystem.TryGetContainingContainer(buckleUid, out var ownContainer);
|
||||||
|
var strapContained = _containerSystem.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<SharedStorageComponent>(args.Container.Owner) && component.BuckledEntities.Count != 0)
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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 (!_interactionSystem.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) &&
|
||||||
|
_interactionSystem.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) &&
|
||||||
|
_interactionSystem.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 (!_interactionSystem.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 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.
|
// TODO: This looks dirty af.
|
||||||
// On rotation of a strap, reattach all buckled entities.
|
// On rotation of a strap, reattach all buckled entities.
|
||||||
@@ -45,15 +218,13 @@ public abstract partial class SharedBuckleSystem
|
|||||||
// One option is to just never trigger re-buckles during state application.
|
// 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.
|
// 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)
|
if (_gameTiming.ApplyingState || args.NewRotation == args.OldRotation)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var buckledEntity in component.BuckledEntities)
|
foreach (var buckledEntity in component.BuckledEntities)
|
||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent(buckledEntity, out BuckleComponent? buckled))
|
if (!TryComp<BuckleComponent>(buckledEntity, out var buckled))
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
|
if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
|
||||||
{
|
{
|
||||||
@@ -61,33 +232,90 @@ public abstract partial class SharedBuckleSystem
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReAttach(buckledEntity, component, buckle: buckled);
|
ReAttach(buckledEntity, uid, buckled, component);
|
||||||
Dirty(buckled);
|
Dirty(buckled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool StrapCanDragDropOn(
|
private bool StrapCanDragDropOn(
|
||||||
EntityUid strapId,
|
EntityUid strapUid,
|
||||||
EntityUid user,
|
EntityUid userUid,
|
||||||
EntityUid target,
|
EntityUid targetUid,
|
||||||
EntityUid buckleId,
|
EntityUid buckleUid,
|
||||||
StrapComponent? strap = null,
|
StrapComponent? strapComp = null,
|
||||||
BuckleComponent? buckle = null)
|
BuckleComponent? buckleComp = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(strapId, ref strap, false) ||
|
if (!Resolve(strapUid, ref strapComp, false) ||
|
||||||
!Resolve(buckleId, ref buckle, false))
|
!Resolve(buckleUid, ref buckleComp, false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ignored(EntityUid entity) => entity == user || entity == buckleId || entity == target;
|
bool Ignored(EntityUid entity) => entity == userUid || entity == buckleUid || entity == targetUid;
|
||||||
|
|
||||||
return _interactions.InRangeUnobstructed(target, buckleId, buckle.Range, predicate: Ignored);
|
return _interactionSystem.InRangeUnobstructed(targetUid, buckleUid, buckleComp.Range, predicate: Ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStrapCanDropOn(EntityUid uid, StrapComponent strap, ref CanDropTargetEvent args)
|
/// <summary>
|
||||||
|
/// Remove everything attached to the strap
|
||||||
|
/// </summary>
|
||||||
|
private void StrapRemoveAll(StrapComponent strapComp)
|
||||||
{
|
{
|
||||||
args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, strap);
|
foreach (var entity in strapComp.BuckledEntities.ToArray())
|
||||||
args.Handled = true;
|
{
|
||||||
|
TryUnbuckle(entity, entity, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
strapComp.BuckledEntities.Clear();
|
||||||
|
strapComp.OccupiedSize = 0;
|
||||||
|
Dirty(strapComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool StrapHasSpace(EntityUid strapUid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(strapUid, ref strapComp, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return strapComp.OccupiedSize + buckleComp.Size <= strapComp.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
AppearanceSystem.SetData(buckleUid, StrapVisuals.RotationAngle, strapComp.Rotation);
|
||||||
|
|
||||||
|
AppearanceSystem.SetData(strapUid, StrapVisuals.State, true);
|
||||||
|
|
||||||
|
Dirty(strapComp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the enabled field in the strap component to a value
|
||||||
|
/// </summary>
|
||||||
|
public void StrapSetEnabled(EntityUid strapUid, bool enabled, StrapComponent? strapComp = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(strapUid, ref strapComp, false) ||
|
||||||
|
strapComp.Enabled == enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
strapComp.Enabled = enabled;
|
||||||
|
|
||||||
|
if (!enabled)
|
||||||
|
StrapRemoveAll(strapComp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,87 @@
|
|||||||
|
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.Standing;
|
using Content.Shared.Standing;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Buckle;
|
namespace Content.Shared.Buckle;
|
||||||
|
|
||||||
public abstract partial class SharedBuckleSystem : EntitySystem
|
public abstract partial class SharedBuckleSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
[Dependency] private readonly INetManager _netManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
[Dependency] private readonly StandingStateSystem _standing = default!;
|
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
|
||||||
|
[Dependency] protected readonly ActionBlockerSystem ActionBlockerSystem = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPullingSystem _pullingSystem = default!;
|
||||||
|
[Dependency] private readonly StandingStateSystem _standingSystem = default!;
|
||||||
|
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||||
|
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
UpdatesAfter.Add(typeof(SharedInteractionSystem));
|
||||||
|
UpdatesAfter.Add(typeof(SharedInputSystem));
|
||||||
|
|
||||||
InitializeBuckle();
|
InitializeBuckle();
|
||||||
InitializeStrap();
|
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;
|
||||||
|
|
||||||
|
var buckleTransform = Transform(buckleUid);
|
||||||
|
|
||||||
|
buckleTransform.Coordinates = new EntityCoordinates(strapUid, strapComp.BuckleOffset);
|
||||||
|
|
||||||
|
// Buckle subscribes to move for <reasons> so this might fail.
|
||||||
|
// TODO: Make buckle not do that.
|
||||||
|
if (buckleTransform.ParentUid != strapUid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_transformSystem.SetLocalRotation(buckleUid, Angle.Zero, buckleTransform);
|
||||||
|
|
||||||
|
switch (strapComp.Position)
|
||||||
|
{
|
||||||
|
case StrapPosition.None:
|
||||||
|
break;
|
||||||
|
case StrapPosition.Stand:
|
||||||
|
_standingSystem.Stand(buckleUid);
|
||||||
|
break;
|
||||||
|
case StrapPosition.Down:
|
||||||
|
_standingSystem.Down(buckleUid, false, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
|
namespace Content.Shared.Light.Component;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is simplified version of <see cref="HandheldLightComponent"/>.
|
||||||
|
/// It doesn't consume any power and can be toggle only by verb.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class UnpoweredFlashlightComponent : Robust.Shared.GameObjects.Component
|
||||||
|
{
|
||||||
|
[DataField("toggleFlashlightSound")]
|
||||||
|
public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
|
||||||
|
|
||||||
|
[ViewVariables] public bool LightOn = false;
|
||||||
|
|
||||||
|
[DataField("toggleAction", required: true)]
|
||||||
|
public InstantAction ToggleAction = new();
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ namespace Content.Shared.Pulling
|
|||||||
if (EntityManager.TryGetComponent<BuckleComponent?>(puller, out var buckle))
|
if (EntityManager.TryGetComponent<BuckleComponent?>(puller, out var buckle))
|
||||||
{
|
{
|
||||||
// Prevent people pulling the chair they're on, etc.
|
// Prevent people pulling the chair they're on, etc.
|
||||||
if (buckle.Buckled && (buckle.LastEntityBuckledTo == pulled))
|
if (buckle is { PullStrap: false, Buckled: true } && (buckle.LastEntityBuckledTo == pulled))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Vehicle.Components
|
namespace Content.Shared.Vehicle.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Added to people when they are riding in a vehicle
|
||||||
|
/// used mostly to keep track of them for entityquery.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
public sealed class RiderComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Added to people when they are riding in a vehicle
|
/// The vehicle this rider is currently riding.
|
||||||
/// used mostly to keep track of them for entityquery.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, NetworkedComponent]
|
[ViewVariables] public EntityUid? Vehicle;
|
||||||
public sealed class RiderComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The vehicle this rider is currently riding.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables] public EntityUid? Vehicle;
|
|
||||||
|
|
||||||
public override bool SendOnlyToOwner => true;
|
public override bool SendOnlyToOwner => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class RiderComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public EntityUid? Entity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +1,113 @@
|
|||||||
using Content.Shared.Actions.ActionTypes;
|
using Content.Shared.Actions.ActionTypes;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Vehicle.Components
|
namespace Content.Shared.Vehicle.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is particularly for vehicles that use
|
||||||
|
/// buckle. Stuff like clown cars may need a different
|
||||||
|
/// component at some point.
|
||||||
|
/// All vehicles should have Physics, Strap, and SharedPlayerInputMover components.
|
||||||
|
/// </summary>
|
||||||
|
[AutoGenerateComponentState]
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[Access(typeof(SharedVehicleSystem))]
|
||||||
|
public sealed partial class VehicleComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is particularly for vehicles that use
|
/// The entity currently riding the vehicle.
|
||||||
/// buckle. Stuff like clown cars may need a different
|
|
||||||
/// component at some point.
|
|
||||||
/// All vehicles should have Physics, Strap, and SharedPlayerInputMover components.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[ViewVariables]
|
||||||
public sealed class VehicleComponent : Component
|
[AutoNetworkedField]
|
||||||
|
public EntityUid? Rider;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
[AutoNetworkedField]
|
||||||
|
public EntityUid? LastRider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base offset for the vehicle (when facing east)
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Vector2 BaseBuckleOffset = Vector2.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound that the horn makes
|
||||||
|
/// </summary>
|
||||||
|
[DataField("hornSound")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public SoundSpecifier? HornSound = new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg")
|
||||||
{
|
{
|
||||||
/// <summary>
|
Params = AudioParams.Default.WithVolume(-3f)
|
||||||
/// Whether someone is currently riding the vehicle
|
};
|
||||||
/// </summary>
|
|
||||||
public bool HasRider => Rider != null;
|
|
||||||
|
|
||||||
/// <summary>
|
[ViewVariables]
|
||||||
/// The entity currently riding the vehicle.
|
public IPlayingAudioStream? HonkPlayingStream;
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public EntityUid? Rider;
|
|
||||||
|
|
||||||
/// <summary>
|
/// Use ambient sound component for the idle sound.
|
||||||
/// The base offset for the vehicle (when facing east)
|
|
||||||
/// </summary>
|
|
||||||
public Vector2 BaseBuckleOffset = Vector2.Zero;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sound that the horn makes
|
/// The action for the horn (if any)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("hornSound")] public SoundSpecifier? HornSound =
|
[DataField("hornAction")]
|
||||||
new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg")
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
{
|
public InstantAction HornAction = new()
|
||||||
Params = AudioParams.Default.WithVolume(-3f)
|
{
|
||||||
};
|
UseDelay = TimeSpan.FromSeconds(3.4),
|
||||||
|
Icon = new SpriteSpecifier.Texture(new("Objects/Fun/bikehorn.rsi/icon.png")),
|
||||||
|
DisplayName = "action-name-honk",
|
||||||
|
Description = "action-desc-honk",
|
||||||
|
Event = new HonkActionEvent(),
|
||||||
|
};
|
||||||
|
|
||||||
public IPlayingAudioStream? HonkPlayingStream;
|
/// <summary>
|
||||||
|
/// Whether the vehicle has a key currently inside it or not.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("hasKey")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool HasKey;
|
||||||
|
|
||||||
/// Use ambient sound component for the idle sound.
|
/// <summary>
|
||||||
|
/// Determines from which side the vehicle will be displayed on top of the player.
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
/// <summary>
|
[DataField("southOver")]
|
||||||
/// The action for the horn (if any)
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
/// </summary>
|
public bool SouthOver;
|
||||||
[DataField("hornAction")]
|
|
||||||
public InstantAction HornAction = new()
|
|
||||||
{
|
|
||||||
UseDelay = TimeSpan.FromSeconds(3.4),
|
|
||||||
Icon = new SpriteSpecifier.Texture(new("Objects/Fun/bikehorn.rsi/icon.png")),
|
|
||||||
DisplayName = "action-name-honk",
|
|
||||||
Description = "action-desc-honk",
|
|
||||||
Event = new HonkActionEvent(),
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
[DataField("northOver")]
|
||||||
/// Whether the vehicle has a key currently inside it or not.
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
/// </summary>
|
public bool NorthOver;
|
||||||
[DataField("hasKey")]
|
|
||||||
public bool HasKey = false;
|
|
||||||
|
|
||||||
/// <summary>
|
[DataField("westOver")]
|
||||||
/// Determines from which side the vehicle will be displayed on top of the player.
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
/// </summary>
|
public bool WestOver;
|
||||||
|
|
||||||
[DataField("southOver")]
|
[DataField("eastOver")]
|
||||||
public bool SouthOver = false;
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool EastOver;
|
||||||
|
|
||||||
[DataField("northOver")]
|
/// <summary>
|
||||||
public bool NorthOver = false;
|
/// What the y buckle offset should be in north / south
|
||||||
|
/// </summary>
|
||||||
|
[DataField("northOverride")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float NorthOverride;
|
||||||
|
|
||||||
[DataField("westOver")]
|
/// <summary>
|
||||||
public bool WestOver = false;
|
/// What the y buckle offset should be in north / south
|
||||||
|
/// </summary>
|
||||||
|
[DataField("southOverride")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float SouthOverride;
|
||||||
|
|
||||||
[DataField("eastOver")]
|
[DataField("autoAnimate")]
|
||||||
public bool EastOver = false;
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool AutoAnimate = true;
|
||||||
|
|
||||||
/// <summary>
|
[DataField("hideRider")]
|
||||||
/// What the y buckle offset should be in north / south
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
/// </summary>
|
public bool HideRider;
|
||||||
[DataField("northOverride")]
|
|
||||||
public float NorthOverride = 0f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What the y buckle offset should be in north / south
|
|
||||||
/// </summary>
|
|
||||||
[DataField("southOverride")]
|
|
||||||
public float SouthOverride = 0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,39 @@
|
|||||||
|
using Content.Shared.Hands;
|
||||||
using Content.Shared.Physics.Pull;
|
using Content.Shared.Physics.Pull;
|
||||||
using Content.Shared.Vehicle.Components;
|
using Content.Shared.Vehicle.Components;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Shared.Vehicle;
|
namespace Content.Shared.Vehicle;
|
||||||
|
|
||||||
public abstract partial class SharedVehicleSystem
|
public abstract partial class SharedVehicleSystem
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
private void InitializeRider()
|
||||||
protected sealed class RiderComponentState : ComponentState
|
|
||||||
{
|
{
|
||||||
public EntityUid? Entity;
|
SubscribeLocalEvent<RiderComponent, ComponentGetState>(OnRiderGetState);
|
||||||
|
SubscribeLocalEvent<RiderComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
|
||||||
|
SubscribeLocalEvent<RiderComponent, PullAttemptEvent>(OnPullAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRiderPull(EntityUid uid, RiderComponent component, PullAttemptEvent args)
|
private void OnRiderGetState(EntityUid uid, RiderComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new RiderComponentState()
|
||||||
|
{
|
||||||
|
Entity = component.Vehicle,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kick the rider off the vehicle if they press q / drop the virtual item
|
||||||
|
/// </summary>
|
||||||
|
private void OnVirtualItemDeleted(EntityUid uid, RiderComponent component, VirtualItemDeletedEvent args)
|
||||||
|
{
|
||||||
|
if (args.BlockingEntity == component.Vehicle)
|
||||||
|
{
|
||||||
|
_buckle.TryUnbuckle(uid, uid, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPullAttempt(EntityUid uid, RiderComponent component, PullAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (component.Vehicle != null)
|
if (component.Vehicle != null)
|
||||||
args.Cancelled = true;
|
args.Cancelled = true;
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ using Content.Shared.Buckle.Components;
|
|||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
using Content.Shared.Physics.Pull;
|
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
using Serilog;
|
using Content.Shared.Buckle;
|
||||||
|
using Content.Shared.Hands;
|
||||||
|
using Content.Shared.Light.Component;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Physics.Systems;
|
||||||
|
|
||||||
namespace Content.Shared.Vehicle;
|
namespace Content.Shared.Vehicle;
|
||||||
|
|
||||||
@@ -22,26 +26,155 @@ namespace Content.Shared.Vehicle;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class SharedVehicleSystem : EntitySystem
|
public abstract partial class SharedVehicleSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly INetManager _netManager = default!;
|
||||||
|
|
||||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
[Dependency] private readonly MovementSpeedModifierSystem _modifier = default!;
|
[Dependency] private readonly MovementSpeedModifierSystem _modifier = default!;
|
||||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
|
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly SharedHandVirtualItemSystem _virtualItemSystem = default!;
|
||||||
|
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||||
|
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||||
|
[Dependency] private readonly SharedBuckleSystem _buckle = default!;
|
||||||
|
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||||
|
|
||||||
private const string KeySlot = "key_slot";
|
private const string KeySlot = "key_slot";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<InVehicleComponent, GettingPickedUpAttemptEvent>(OnPickupAttempt);
|
InitializeRider();
|
||||||
SubscribeLocalEvent<RiderComponent, PullAttemptEvent>(OnRiderPull);
|
|
||||||
SubscribeLocalEvent<VehicleComponent, RefreshMovementSpeedModifiersEvent>(OnVehicleModifier);
|
|
||||||
SubscribeLocalEvent<VehicleComponent, ComponentStartup>(OnVehicleStartup);
|
SubscribeLocalEvent<VehicleComponent, ComponentStartup>(OnVehicleStartup);
|
||||||
SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnVehicleRotate);
|
SubscribeLocalEvent<VehicleComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||||
|
SubscribeLocalEvent<VehicleComponent, HonkActionEvent>(OnHonkAction);
|
||||||
SubscribeLocalEvent<VehicleComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
SubscribeLocalEvent<VehicleComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||||
SubscribeLocalEvent<VehicleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
SubscribeLocalEvent<VehicleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||||
|
SubscribeLocalEvent<VehicleComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
|
||||||
|
SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnMoveEvent);
|
||||||
SubscribeLocalEvent<VehicleComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
|
SubscribeLocalEvent<VehicleComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<InVehicleComponent, GettingPickedUpAttemptEvent>(OnGettingPickedUpAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This just controls whether the wheels are turning.
|
||||||
|
/// </summary>
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
var vehicleQuery = EntityQueryEnumerator<VehicleComponent, InputMoverComponent>();
|
||||||
|
while (vehicleQuery.MoveNext(out var uid, out var vehicle, out var mover))
|
||||||
|
{
|
||||||
|
if (!vehicle.AutoAnimate)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (_mover.GetVelocityInput(mover).Sprinting == Vector2.Zero)
|
||||||
|
{
|
||||||
|
UpdateAutoAnimate(uid, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAutoAnimate(uid, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVehicleStartup(EntityUid uid, VehicleComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
UpdateDrawDepth(uid, 2);
|
||||||
|
|
||||||
|
// This code should be purged anyway but with that being said this doesn't handle components being changed.
|
||||||
|
if (TryComp<StrapComponent>(uid, out var strap))
|
||||||
|
{
|
||||||
|
component.BaseBuckleOffset = strap.BuckleOffset;
|
||||||
|
strap.BuckleOffsetUnclamped = Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
_modifier.RefreshMovementSpeedModifiers(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Give the user the rider component if they're buckling to the vehicle,
|
||||||
|
/// otherwise remove it.
|
||||||
|
/// </summary>
|
||||||
|
private void OnBuckleChange(EntityUid uid, VehicleComponent component, ref BuckleChangeEvent args)
|
||||||
|
{
|
||||||
|
// Add Rider
|
||||||
|
if (args.Buckling)
|
||||||
|
{
|
||||||
|
// Add a virtual item to rider's hand, unbuckle if we can't.
|
||||||
|
if (!_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.BuckledEntity))
|
||||||
|
{
|
||||||
|
_buckle.TryUnbuckle(uid, uid, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the rider and vehicle with each other
|
||||||
|
EnsureComp<InputMoverComponent>(uid);
|
||||||
|
var rider = EnsureComp<RiderComponent>(args.BuckledEntity);
|
||||||
|
component.Rider = args.BuckledEntity;
|
||||||
|
component.LastRider = component.Rider;
|
||||||
|
Dirty(component);
|
||||||
|
Appearance.SetData(uid, VehicleVisuals.HideRider, true);
|
||||||
|
|
||||||
|
var relay = EnsureComp<RelayInputMoverComponent>(args.BuckledEntity);
|
||||||
|
_mover.SetRelay(args.BuckledEntity, uid, relay);
|
||||||
|
rider.Vehicle = uid;
|
||||||
|
|
||||||
|
// Update appearance stuff, add actions
|
||||||
|
UpdateBuckleOffset(uid, Transform(uid), component);
|
||||||
|
if (TryComp<InputMoverComponent>(uid, out var mover))
|
||||||
|
UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component, mover.RelativeRotation.Degrees));
|
||||||
|
|
||||||
|
if (TryComp<ActionsComponent>(args.BuckledEntity, out var actions) && TryComp<UnpoweredFlashlightComponent>(uid, out var flashlight))
|
||||||
|
{
|
||||||
|
_actionsSystem.AddAction(args.BuckledEntity, flashlight.ToggleAction, uid, actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.HornSound != null)
|
||||||
|
{
|
||||||
|
_actionsSystem.AddAction(args.BuckledEntity, component.HornAction, uid, actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
_joints.ClearJoints(args.BuckledEntity);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove rider
|
||||||
|
|
||||||
|
// Clean up actions and virtual items
|
||||||
|
_actionsSystem.RemoveProvidedActions(args.BuckledEntity, uid);
|
||||||
|
_virtualItemSystem.DeleteInHandsMatching(args.BuckledEntity, uid);
|
||||||
|
|
||||||
|
|
||||||
|
// Entity is no longer riding
|
||||||
|
RemComp<RiderComponent>(args.BuckledEntity);
|
||||||
|
RemComp<RelayInputMoverComponent>(args.BuckledEntity);
|
||||||
|
|
||||||
|
Appearance.SetData(uid, VehicleVisuals.HideRider, false);
|
||||||
|
// Reset component
|
||||||
|
component.Rider = null;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This fires when the rider presses the honk action
|
||||||
|
/// </summary>
|
||||||
|
private void OnHonkAction(EntityUid uid, VehicleComponent vehicle, HonkActionEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || vehicle.HornSound == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: Need audio refactor maybe, just some way to null it when the stream is over.
|
||||||
|
// For now better to just not loop to keep the code much cleaner.
|
||||||
|
vehicle.HonkPlayingStream?.Stop();
|
||||||
|
vehicle.HonkPlayingStream = _audioSystem.PlayPredicted(vehicle.HornSound, uid, uid);
|
||||||
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -60,6 +193,11 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
|
|
||||||
component.HasKey = true;
|
component.HasKey = true;
|
||||||
|
|
||||||
|
var msg = Loc.GetString("vehicle-use-key",
|
||||||
|
("keys", args.Entity), ("vehicle", uid));
|
||||||
|
if (_netManager.IsServer)
|
||||||
|
_popupSystem.PopupEntity(msg, uid, args.OldParent, PopupType.Medium);
|
||||||
|
|
||||||
// Audiovisual feedback
|
// Audiovisual feedback
|
||||||
_ambientSound.SetAmbience(uid, true);
|
_ambientSound.SetAmbience(uid, true);
|
||||||
_tagSystem.AddTag(uid, "DoorBumpOpener");
|
_tagSystem.AddTag(uid, "DoorBumpOpener");
|
||||||
@@ -81,7 +219,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
_modifier.RefreshMovementSpeedModifiers(uid);
|
_modifier.RefreshMovementSpeedModifiers(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnVehicleModifier(EntityUid uid, VehicleComponent component, RefreshMovementSpeedModifiersEvent args)
|
private void OnRefreshMovementSpeedModifiers(EntityUid uid, VehicleComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||||
{
|
{
|
||||||
if (!component.HasKey)
|
if (!component.HasKey)
|
||||||
{
|
{
|
||||||
@@ -89,42 +227,28 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPickupAttempt(EntityUid uid, InVehicleComponent component, GettingPickedUpAttemptEvent args)
|
|
||||||
{
|
|
||||||
if (component.Vehicle == null || component.Vehicle.Rider != null && component.Vehicle.Rider != args.User)
|
|
||||||
args.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Shitcode, needs to use sprites instead of actual offsets.
|
// TODO: Shitcode, needs to use sprites instead of actual offsets.
|
||||||
private void OnVehicleRotate(EntityUid uid, VehicleComponent component, ref MoveEvent args)
|
private void OnMoveEvent(EntityUid uid, VehicleComponent component, ref MoveEvent args)
|
||||||
{
|
{
|
||||||
if (args.NewRotation == args.OldRotation)
|
if (args.NewRotation == args.OldRotation)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// This first check is just for safety
|
// This first check is just for safety
|
||||||
if (!HasComp<InputMoverComponent>(uid))
|
if (component.AutoAnimate && !HasComp<InputMoverComponent>(uid))
|
||||||
{
|
{
|
||||||
UpdateAutoAnimate(uid, false);
|
UpdateAutoAnimate(uid, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateBuckleOffset(args.Component, component);
|
UpdateBuckleOffset(uid, args.Component, component);
|
||||||
if (TryComp<InputMoverComponent>(uid, out var mover))
|
if (TryComp<InputMoverComponent>(uid, out var mover))
|
||||||
UpdateDrawDepth(uid, GetDrawDepth(args.Component, component, mover.RelativeRotation));
|
UpdateDrawDepth(uid, GetDrawDepth(args.Component, component, mover.RelativeRotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnVehicleStartup(EntityUid uid, VehicleComponent component, ComponentStartup args)
|
private void OnGettingPickedUpAttempt(EntityUid uid, InVehicleComponent component, GettingPickedUpAttemptEvent args)
|
||||||
{
|
{
|
||||||
UpdateDrawDepth(uid, 2);
|
if (component.Vehicle == null || component.Vehicle.Rider != null && component.Vehicle.Rider != args.User)
|
||||||
|
args.Cancel();
|
||||||
// This code should be purged anyway but with that being said this doesn't handle components being changed.
|
|
||||||
if (TryComp<StrapComponent>(uid, out var strap))
|
|
||||||
{
|
|
||||||
component.BaseBuckleOffset = strap.BuckleOffset;
|
|
||||||
strap.BuckleOffsetUnclamped = Vector2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
_modifier.RefreshMovementSpeedModifiers(uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -132,7 +256,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
/// change its draw depth. Vehicles can choose between special drawdetph
|
/// change its draw depth. Vehicles can choose between special drawdetph
|
||||||
/// when facing north or south. East and west are easy.
|
/// when facing north or south. East and west are easy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected int GetDrawDepth(TransformComponent xform, VehicleComponent component, Angle cameraAngle)
|
private int GetDrawDepth(TransformComponent xform, VehicleComponent component, Angle cameraAngle)
|
||||||
{
|
{
|
||||||
var itemDirection = cameraAngle.GetDir() switch
|
var itemDirection = cameraAngle.GetDir() switch
|
||||||
{
|
{
|
||||||
@@ -167,9 +291,9 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
/// teleport any buckled entities to it. This is the most crucial part of making
|
/// teleport any buckled entities to it. This is the most crucial part of making
|
||||||
/// buckled vehicles work.
|
/// buckled vehicles work.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void UpdateBuckleOffset(TransformComponent xform, VehicleComponent component)
|
private void UpdateBuckleOffset(EntityUid uid, TransformComponent xform, VehicleComponent component)
|
||||||
{
|
{
|
||||||
if (!TryComp<StrapComponent>(component.Owner, out var strap))
|
if (!TryComp<StrapComponent>(uid, out var strap))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: Strap should handle this but buckle E/C moment.
|
// TODO: Strap should handle this but buckle E/C moment.
|
||||||
@@ -208,7 +332,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the draw depth for the sprite.
|
/// Set the draw depth for the sprite.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void UpdateDrawDepth(EntityUid uid, int drawDepth)
|
private void UpdateDrawDepth(EntityUid uid, int drawDepth)
|
||||||
{
|
{
|
||||||
Appearance.SetData(uid, VehicleVisuals.DrawDepth, drawDepth);
|
Appearance.SetData(uid, VehicleVisuals.DrawDepth, drawDepth);
|
||||||
}
|
}
|
||||||
@@ -216,7 +340,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set whether the vehicle's base layer is animating or not.
|
/// Set whether the vehicle's base layer is animating or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
|
private void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
|
||||||
{
|
{
|
||||||
Appearance.SetData(uid, VehicleVisuals.AutoAnimate, autoAnimate);
|
Appearance.SetData(uid, VehicleVisuals.AutoAnimate, autoAnimate);
|
||||||
}
|
}
|
||||||
@@ -235,10 +359,13 @@ public enum VehicleVisuals : byte
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the wheels should be turning
|
/// Whether the wheels should be turning
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAnimate
|
AutoAnimate,
|
||||||
|
HideRider
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised when someone honks a vehicle horn
|
/// Raised when someone honks a vehicle horn
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HonkActionEvent : InstantActionEvent { }
|
public sealed class HonkActionEvent : InstantActionEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@
|
|||||||
- MobMask
|
- MobMask
|
||||||
layer:
|
layer:
|
||||||
- TableLayer
|
- TableLayer
|
||||||
- type: VehicleVisuals
|
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
- type: Repairable
|
- type: Repairable
|
||||||
fuelcost: 20
|
fuelcost: 20
|
||||||
|
|||||||
Reference in New Issue
Block a user