using Content.Client.Rotation;
-using Content.Shared.ActionBlocker;
using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
using Content.Shared.Vehicle.Components;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
-namespace Content.Client.Buckle
-{
- internal sealed class BuckleSystem : SharedBuckleSystem
- {
- [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
- [Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
- [Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
+namespace Content.Client.Buckle;
- public override void Initialize()
- {
- base.Initialize();
+internal sealed class BuckleSystem : SharedBuckleSystem
+{
+ [Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
- SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnBuckleHandleState);
- SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
- }
+ public override void Initialize()
+ {
+ base.Initialize();
- private void OnBuckleHandleState(EntityUid uid, BuckleComponent buckle, ref ComponentHandleState args)
- {
- if (args.Current is not BuckleComponentState state)
- return;
+ SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnBuckleHandleState);
+ SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
+ }
- buckle.Buckled = state.Buckled;
- buckle.LastEntityBuckledTo = state.LastEntityBuckledTo;
- buckle.DontCollide = state.DontCollide;
+ private void OnBuckleHandleState(EntityUid uid, BuckleComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not BuckleComponentState state)
+ return;
- _actionBlocker.UpdateCanMove(uid);
+ component.Buckled = state.Buckled;
+ component.BuckledTo = state.BuckledTo;
+ component.LastEntityBuckledTo = state.LastEntityBuckledTo;
+ component.DontCollide = state.DontCollide;
- if (!TryComp(uid, out SpriteComponent? ownerSprite))
- return;
+ ActionBlockerSystem.UpdateCanMove(uid);
- if (HasComp<VehicleComponent>(buckle.LastEntityBuckledTo))
- return;
+ if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
+ 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 (HasComp<VehicleComponent>(component.LastEntityBuckledTo))
+ 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;
- }
+ // Adjust draw depth when the chair faces north so that the seat back is drawn over the player.
+ // Reset the draw depth when rotated in any other direction.
+ // TODO when ECSing, make this a visualizer
+ // This code was written before rotatable viewports were introduced, so hard-coding Direction.North
+ // and comparing it against LocalRotation now breaks this in other rotations. This is a FIXME, but
+ // better to get it working for most people before we look at a more permanent solution.
+ if (component is { Buckled: true, LastEntityBuckledTo: { } } &&
+ Transform(component.LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North &&
+ TryComp<SpriteComponent>(component.LastEntityBuckledTo, out var buckledSprite))
+ {
+ component.OriginalDrawDepth ??= ownerSprite.DrawDepth;
+ ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
+ return;
}
- private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
+ // If here, we're not turning north and should restore the saved draw depth.
+ if (component.OriginalDrawDepth.HasValue)
{
- if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
- return;
+ ownerSprite.DrawDepth = component.OriginalDrawDepth.Value;
+ component.OriginalDrawDepth = null;
+ }
+ }
- 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;
- }
+ private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
+ {
+ if (!TryComp<RotationVisualsComponent>(uid, out var 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);
+ 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);
}
}
using Robust.Client.GameObjects;
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()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
+ SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
+ SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
+ SubscribeLocalEvent<VehicleComponent, AppearanceChangeEvent>(OnVehicleAppearanceChange);
+ }
+
+ private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
+ {
+ // Center the player's eye on the vehicle
+ if (TryComp(uid, out EyeComponent? eyeComp))
+ eyeComp.Target ??= component.Vehicle;
+ }
+
+ private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
+ {
+ // reset the riders eye centering.
+ if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
+ eyeComp.Target = null;
+ }
+
+ private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
{
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
- SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
- SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
- }
-
- private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
- {
- // Center the player's eye on the vehicle
- if (TryComp(uid, out EyeComponent? eyeComp))
- eyeComp.Target ??= component.Vehicle;
- }
-
- private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
- {
- // reset the riders eye centering.
- if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
- eyeComp.Target = null;
- }
-
- private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not RiderComponentState state)
- return;
-
- if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
- eyeComp.Target = state.Entity;
-
- component.Vehicle = state.Entity;
- }
+ if (args.Current is not RiderComponentState state)
+ return;
+
+ 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,
}
+++ /dev/null
-namespace Content.Client.Vehicle;
-
-/// <summary>
-/// Controls visuals for vehicles
-/// </summary>
-[RegisterComponent]
-public sealed class VehicleVisualsComponent : Component
-{
- public int DrawDepth = 0;
-}
+++ /dev/null
-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,
-}
using System.Threading.Tasks;
using Content.Server.Body.Systems;
-using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle;
using Content.Shared.ActionBlocker;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
var coordinates = testMap.GridCoords;
var entityManager = server.ResolveDependency<IEntityManager>();
var actionBlocker = entityManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
- var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<BuckleSystem>();
+ var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<SharedBuckleSystem>();
var standingState = entityManager.EntitySysManager.GetEntitySystem<StandingStateSystem>();
EntityUid human = default;
Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle));
// 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.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
Assert.True(buckle.Buckled);
Assert.True(buckle.Buckled);
// Unbuckle
- Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+ Assert.True(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
Assert.Null(buckle.BuckledTo);
Assert.False(buckle.Buckled);
Assert.True(actionBlocker.CanMove(human));
Assert.Zero(strap.OccupiedSize);
// 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);
// On cooldown
- Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+ Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
Assert.True(buckle.Buckled);
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
Assert.True(buckle.Buckled);
Assert.True(buckle.Buckled);
// Unbuckle
- Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+ Assert.True(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
Assert.False(buckle.Buckled);
// Move away from the chair
entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1000, 1000);
// Out of range
- Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
- Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+ Assert.False(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
+ Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
// Move near the chair
entityManager.GetComponent<TransformComponent>(chair).WorldPosition + (0.5f, 0);
// 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.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+ Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
Assert.True(buckle.Buckled);
Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
Assert.True(buckle.Buckled);
// 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.True(actionBlocker.CanMove(human));
Assert.True(actionBlocker.CanChangeDirection(human));
Assert.True(standingState.Down(human));
// 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
entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1, 0);
var entityManager = server.ResolveDependency<IEntityManager>();
var handsSys = entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>();
- var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<BuckleSystem>();
+ var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<SharedBuckleSystem>();
await server.WaitAssertion(() =>
{
Assert.True(entityManager.TryGetComponent(human, out body));
// Buckle
- Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+ Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
Assert.NotNull(buckle.BuckledTo);
Assert.True(buckle.Buckled);
Assert.Null(hand.HeldEntity);
}
- buckleSystem.TryUnbuckle(human, human, true, buckle: buckle);
+ buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle);
});
await pairTracker.CleanReturnAsync();
var testMap = await PoolManager.CreateTestMap(pairTracker);
var coordinates = testMap.GridCoords;
var entityManager = server.ResolveDependency<IEntityManager>();
- var buckleSystem = entityManager.System<BuckleSystem>();
+ var buckleSystem = entityManager.System<SharedBuckleSystem>();
EntityUid human = default;
EntityUid chair = default;
Assert.True(entityManager.HasComponent<StrapComponent>(chair));
// Buckle
- Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+ Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
Assert.NotNull(buckle.BuckledTo);
Assert.True(buckle.Buckled);
entityManager.GetComponent<TransformComponent>(human).WorldPosition -= (100, 0);
// Buckle
- Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+ Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
Assert.NotNull(buckle.BuckledTo);
Assert.True(buckle.Buckled);
});
-using Content.Server.Buckle.Systems;
using Content.Shared.Alert;
+using Content.Shared.Buckle;
using JetBrains.Annotations;
namespace Content.Server.Alert.Click
{
public void AlertClicked(EntityUid player)
{
- IoCManager.Resolve<IEntityManager>().System<BuckleSystem>().TryUnbuckle(player, player);
+ IoCManager.Resolve<IEntityManager>().System<SharedBuckleSystem>().TryUnbuckle(player, player);
}
}
}
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);
if (args.Buckling)
_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
// So don't worry about that
+++ /dev/null
-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);
- }
-}
+++ /dev/null
-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);
- }
-}
-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.Mobs.Systems;
-using JetBrains.Annotations;
-using Robust.Server.Containers;
-using Robust.Server.GameObjects;
-using Robust.Shared.Timing;
namespace Content.Server.Buckle.Systems;
-[UsedImplicitly]
-public sealed partial class BuckleSystem : SharedBuckleSystem
+public sealed 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();
- }
}
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)
return;
public override bool Cure(DiseaseEffectArgs args)
{
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;
var ticks = 1;
using System.Linq;
-using Content.Server.Buckle.Systems;
using Content.Server.Storage.Components;
+using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
using Content.Shared.Foldable;
using Content.Shared.Verbs;
[UsedImplicitly]
public sealed class FoldableSystem : SharedFoldableSystem
{
- [Dependency] private readonly BuckleSystem _buckle = default!;
+ [Dependency] private readonly SharedBuckleSystem _buckle = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
+++ /dev/null
-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();
- }
-}
using Content.Server.Mind.Components;
using Content.Shared.Actions;
using Content.Shared.Light;
+using Content.Shared.Light.Component;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
-using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle;
namespace Content.Server.NPC.HTN.Preconditions;
/// </summary>
public sealed class BuckledPrecondition : HTNPrecondition
{
- private BuckleSystem _buckle = default!;
+ private SharedBuckleSystem _buckle = default!;
[ViewVariables(VVAccess.ReadWrite)] [DataField("isBuckled")] public bool IsBuckled = true;
public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
- _buckle = sysManager.GetEntitySystem<BuckleSystem>();
+ _buckle = sysManager.GetEntitySystem<SharedBuckleSystem>();
}
public override bool IsMet(NPCBlackboard blackboard)
using Robust.Shared.Map;
using Content.Server.Mind.Components;
using Content.Server.Traitor;
+using Content.Shared.Light.Component;
namespace Content.Server.PDA
{
using Content.Server.Actions;
-using Content.Server.Buckle.Systems;
using Content.Server.Humanoid;
using Content.Server.Inventory;
using Content.Server.Mind.Commands;
using Content.Server.Polymorph.Components;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
+using Content.Shared.Buckle;
using Content.Shared.Damage;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ActionsSystem _actions = 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 DamageableSystem _damageable = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
using Content.Server.Body.Systems;
-using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle;
using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Buckle.Components;
-using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
SubscribeLocalEvent<ToiletComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ToiletComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(BuckleSystem)});
+ SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(SharedBuckleSystem)});
SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
+++ /dev/null
-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);
- }
- }
-}
-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.Components;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Player;
-namespace Content.Server.Vehicle
-{
- 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;
+namespace Content.Server.Vehicle;
- // 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;
- }
- }
+public sealed class VehicleSystem : SharedVehicleSystem
+{
}
namespace Content.Shared.Buckle.Components;
[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedBuckleSystem))]
public sealed class BuckleComponent : Component
{
/// <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>
[DataField("range")]
+ [ViewVariables(VVAccess.ReadWrite)]
public float Range = SharedInteractionSystem.InteractionRange / 1.4f;
/// <summary>
- /// True if the entity is buckled, false otherwise.
+ /// True if the entity is buckled, false otherwise.
/// </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>
+ /// Whether or not collisions should be possible with the entity we are strapped to
+ /// </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.
+ /// The amount of time that must pass for this entity to
+ /// be able to unbuckle after recently buckling.
/// </summary>
[DataField("delay")]
+ [ViewVariables(VVAccess.ReadWrite)]
public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f);
/// <summary>
- /// The time that this entity buckled at.
+ /// The time that this entity buckled at.
/// </summary>
- [ViewVariables] public TimeSpan BuckleTime;
+ [ViewVariables]
+ public TimeSpan BuckleTime;
/// <summary>
- /// The strap that this component is buckled to.
+ /// The strap that this component is buckled to.
/// </summary>
[ViewVariables]
- public StrapComponent? BuckledTo { get; set; }
+ public EntityUid? BuckledTo;
/// <summary>
- /// The amount of space that this entity occupies in a
- /// <see cref="StrapComponent"/>.
+ /// The amount of space that this entity occupies in a
+ /// <see cref="StrapComponent"/>.
/// </summary>
[DataField("size")]
+ [ViewVariables(VVAccess.ReadWrite)]
public int Size = 100;
/// <summary>
/// Used for client rendering
/// </summary>
- public int? OriginalDrawDepth { get; set; }
+ [ViewVariables]
+ public int? OriginalDrawDepth;
}
[Serializable, NetSerializable]
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;
+ BuckledTo = buckledTo;
LastEntityBuckledTo = lastEntityBuckledTo;
DontCollide = dontCollide;
}
- public bool Buckled { get; }
- public EntityUid? LastEntityBuckledTo { get; }
- public bool DontCollide { get; }
+ public readonly bool Buckled;
+ public readonly EntityUid? BuckledTo;
+ public readonly EntityUid? LastEntityBuckledTo;
+ public readonly bool DontCollide;
}
-public sealed class BuckleChangeEvent : EntityEventArgs
-{
- public EntityUid Strap;
+[ByRefEvent]
+public readonly record struct BuckleAttemptEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling, bool Cancelled = false);
- public EntityUid BuckledEntity;
- public bool Buckling;
-}
+[ByRefEvent]
+public readonly record struct BuckleChangeEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling);
[Serializable, NetSerializable]
public enum BuckleVisuals
using Content.Shared.Alert;
+using Content.Shared.Vehicle;
+using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Buckle.Components;
-public enum StrapPosition
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedBuckleSystem), typeof(SharedVehicleSystem))]
+public sealed class StrapComponent : Component
{
/// <summary>
- /// (Default) Makes no change to the buckled mob
+ /// The entities that are currently buckled
/// </summary>
- None = 0,
+ [ViewVariables]
+ public readonly HashSet<EntityUid> BuckledEntities = new();
/// <summary>
- /// Makes the mob stand up
+ /// Entities that this strap accepts and can buckle
+ /// If null it accepts any entity
/// </summary>
- Stand,
+ [DataField("allowedEntities")]
+ [ViewVariables]
+ public EntityWhitelist? AllowedEntities;
- /// <summary>
- /// Makes the mob lie down
- /// </summary>
- 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();
+ [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)]
- public float MaxBuckleDistance = 0.1f;
+ [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,
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
/// </summary>
[DataField("buckleOffset", required: false)]
- [Access(Other = AccessPermissions.ReadWrite)]
+ [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")]
- public int Rotation { get; set; }
+ [ViewVariables(VVAccess.ReadWrite)]
+ public int Rotation;
/// <summary>
/// The size of the strap which is compared against when buckling entities
/// </summary>
[DataField("size")]
- public int Size { get; set; } = 100;
+ [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>
- public bool Enabled { get; set; } = true;
+ [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")]
- public SoundSpecifier BuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
+ [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")]
- public SoundSpecifier UnbuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
+ [ViewVariables(VVAccess.ReadWrite)]
+ public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
/// <summary>
/// ID of the alert to show when buckled
/// </summary>
[DataField("buckledAlertType")]
- public AlertType BuckledAlertType { get; } = AlertType.Buckled;
+ [ViewVariables(VVAccess.ReadWrite)]
+ public AlertType BuckledAlertType = AlertType.Buckled;
/// <summary>
/// The sum of the sizes of all the buckled entities in this strap
/// </summary>
- public int OccupiedSize { get; set; }
+ [ViewVariables]
+ public int OccupiedSize;
}
[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)
+ 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
+{
+ /// <summary>
+ /// (Default) Makes no change to the buckled mob
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Makes the mob stand up
+ /// </summary>
+ Stand,
+
+ /// <summary>
+ /// Makes the mob lie down
+ /// </summary>
+ Down
+}
+
[Serializable, NetSerializable]
public enum StrapVisuals : byte
{
-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.Mobs.Components;
using Content.Shared.Movement.Events;
+using Content.Shared.Popups;
+using Content.Shared.Pulling.Components;
using Content.Shared.Standing;
+using Content.Shared.Storage.Components;
+using Content.Shared.Stunnable;
using Content.Shared.Throwing;
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.Utility;
namespace Content.Shared.Buckle;
{
private void InitializeBuckle()
{
- SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(PreventCollision);
- SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(HandleDown);
- SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(HandleStand);
- SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(HandleThrowPushback);
- SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(HandleMove);
+ SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleComponentStartup);
+ SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleComponentShutdown);
+ SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnBuckleComponentGetState);
+ SubscribeLocalEvent<BuckleComponent, MoveEvent>(OnBuckleMove);
+ 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);
}
- private void PreventCollision(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
+ private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
+ {
+ 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.LastEntityBuckledTo)
+ if (args.BodyB.Owner != component.BuckledTo)
return;
if (component.Buckled || component.DontCollide)
args.Cancelled = true;
}
- private void HandleDown(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
+ private void OnBuckleDownAttempt(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
{
if (component.Buckled)
args.Cancel();
}
- private void HandleStand(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
+ private void OnBuckleStandAttempt(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
{
if (component.Buckled)
args.Cancel();
}
- private void HandleThrowPushback(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
+ private void OnBuckleThrowPushbackAttempt(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
{
if (component.Buckled)
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)
return;
if (component.Buckled &&
- !HasComp<VehicleComponent>(Transform(uid).ParentUid)) // buckle+vehicle shitcode
+ !HasComp<VehicleComponent>(component.BuckledTo)) // buckle+vehicle shitcode
args.Cancel();
}
}
/// <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>
- /// <param name="buckleId">The entity to reattach.</param>
- /// <param name="strap">The strap to reattach to.</param>
- /// <param name="buckle">The buckle component of the entity to reattach.</param>
- public void ReAttach(EntityUid buckleId, StrapComponent strap, BuckleComponent? buckle = null)
+ /// <param name="uid"> Entity that we want to show the alert </param>
+ /// <param name="buckleComp"> buckle component of the entity </param>
+ /// <param name="strapComp"> strap component of the thing we are strapping to </param>
+ private void UpdateBuckleStatus(EntityUid uid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
{
- if (!Resolve(buckleId, ref buckle, false))
- return;
+ if (buckleComp.BuckledTo != null)
+ {
+ if (!Resolve(buckleComp.BuckledTo.Value, ref strapComp))
+ return;
- var ownTransform = Transform(buckleId);
- var strapTransform = Transform(strap.Owner);
+ var alertType = strapComp.BuckledAlertType;
+ _alertsSystem.ShowAlert(uid, alertType);
+ }
+ else
+ {
+ _alertsSystem.ClearAlertCategory(uid, AlertCategory.Buckled);
+ }
+ }
- ownTransform.Coordinates = new EntityCoordinates(strapTransform.Owner, strap.BuckleOffset);
+ /// <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;
- // Buckle subscribes to move for <reasons> so this might fail.
- // TODO: Make buckle not do that.
- if (ownTransform.ParentUid != strapTransform.Owner)
- return;
+ if (strapUid == null)
+ {
+ buckleComp.Buckled = false;
+ }
+ else
+ {
+ buckleComp.LastEntityBuckledTo = strapUid;
+ buckleComp.DontCollide = true;
+ buckleComp.Buckled = true;
+ buckleComp.BuckleTime = _gameTiming.CurTime;
+ }
- ownTransform.LocalRotation = Angle.Zero;
+ ActionBlockerSystem.UpdateCanMove(buckleUid);
+ UpdateBuckleStatus(buckleUid, buckleComp, strapComp);
+ Dirty(buckleComp);
+ }
- switch (strap.Position)
+ /// <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))
{
- case StrapPosition.None:
- break;
- case StrapPosition.Stand:
- _standing.Stand(buckleId);
- break;
- case StrapPosition.Down:
- _standing.Down(buckleId, false, false);
- break;
+ 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);
+ }
+
}
}
-using Content.Shared.Buckle.Components;
+using System.Linq;
+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.Shared.Containers;
using Robust.Shared.GameStates;
namespace Content.Shared.Buckle;
public abstract partial class SharedBuckleSystem
{
- [Dependency] private readonly SharedInteractionSystem _interactions = default!;
-
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, 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)
component.BuckledEntities.Clear();
component.BuckledEntities.UnionWith(state.BuckledEntities);
component.MaxBuckleDistance = state.MaxBuckleDistance;
+ component.OccupiedSize = state.OccupiedSize;
+ }
+
+ 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 OnStrapRotate(EntityUid uid, StrapComponent component, ref MoveEvent args)
+ 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.
// On rotation of a strap, reattach all buckled entities.
// One option is to just never trigger re-buckles during state application.
// another is to.. just not do this? Like wtf is this code. But I CBF with buckle atm.
- if (GameTiming.ApplyingState || args.NewRotation == args.OldRotation)
+ if (_gameTiming.ApplyingState || args.NewRotation == args.OldRotation)
return;
foreach (var buckledEntity in component.BuckledEntities)
{
- if (!EntityManager.TryGetComponent(buckledEntity, out BuckleComponent? buckled))
- {
+ if (!TryComp<BuckleComponent>(buckledEntity, out var buckled))
continue;
- }
if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
{
continue;
}
- ReAttach(buckledEntity, component, buckle: buckled);
+ ReAttach(buckledEntity, uid, buckled, component);
Dirty(buckled);
}
}
- protected bool StrapCanDragDropOn(
- EntityUid strapId,
- EntityUid user,
- EntityUid target,
- EntityUid buckleId,
- StrapComponent? strap = null,
- BuckleComponent? buckle = null)
+ private bool StrapCanDragDropOn(
+ EntityUid strapUid,
+ EntityUid userUid,
+ EntityUid targetUid,
+ EntityUid buckleUid,
+ StrapComponent? strapComp = null,
+ BuckleComponent? buckleComp = null)
{
- if (!Resolve(strapId, ref strap, false) ||
- !Resolve(buckleId, ref buckle, false))
+ if (!Resolve(strapUid, ref strapComp, false) ||
+ !Resolve(buckleUid, ref buckleComp, 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);
- args.Handled = true;
+ foreach (var entity in strapComp.BuckledEntities.ToArray())
+ {
+ 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);
}
}
+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 Robust.Shared.Containers;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Content.Shared.Buckle;
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()
{
base.Initialize();
+ UpdatesAfter.Add(typeof(SharedInteractionSystem));
+ UpdatesAfter.Add(typeof(SharedInputSystem));
+
InitializeBuckle();
InitializeStrap();
}
+
+ /// <summary>
+ /// Reattaches this entity to the strap, modifying its position and rotation.
+ /// </summary>
+ /// <param name="buckleUid">The entity to reattach.</param>
+ /// <param name="strapUid">The entity to reattach the buckleUid entity to.</param>
+ private void ReAttach(
+ EntityUid buckleUid,
+ EntityUid strapUid,
+ BuckleComponent? buckleComp = null,
+ StrapComponent? strapComp = null)
+ {
+ if (!Resolve(strapUid, ref strapComp, false)
+ || !Resolve(buckleUid, ref buckleComp, false))
+ return;
+
+ 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;
+ }
+ }
}
--- /dev/null
+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();
+}
if (EntityManager.TryGetComponent<BuckleComponent?>(puller, out var buckle))
{
// 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;
}
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>
- /// Added to people when they are riding in a vehicle
- /// used mostly to keep track of them for entityquery.
+ /// The vehicle this rider is currently riding.
/// </summary>
- [RegisterComponent, NetworkedComponent]
- public sealed class RiderComponent : Component
- {
- /// <summary>
- /// The vehicle this rider is currently riding.
- /// </summary>
- [ViewVariables] public EntityUid? Vehicle;
+ [ViewVariables] public EntityUid? Vehicle;
+
+ public override bool SendOnlyToOwner => true;
+}
- public override bool SendOnlyToOwner => true;
- }
+[Serializable, NetSerializable]
+public sealed class RiderComponentState : ComponentState
+{
+ public EntityUid? Entity;
}
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
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>
- /// 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.
+ /// The entity currently riding the vehicle.
+ /// </summary>
+ [ViewVariables]
+ [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>
- [RegisterComponent]
- public sealed class VehicleComponent : Component
+ [DataField("hornSound")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public SoundSpecifier? HornSound = new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg")
{
- /// <summary>
- /// Whether someone is currently riding the vehicle
- /// </summary>
- public bool HasRider => Rider != null;
-
- /// <summary>
- /// The entity currently riding the vehicle.
- /// </summary>
- [ViewVariables]
- public EntityUid? Rider;
-
- /// <summary>
- /// The base offset for the vehicle (when facing east)
- /// </summary>
- public Vector2 BaseBuckleOffset = Vector2.Zero;
-
- /// <summary>
- /// The sound that the horn makes
- /// </summary>
- [DataField("hornSound")] public SoundSpecifier? HornSound =
- new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg")
- {
- Params = AudioParams.Default.WithVolume(-3f)
- };
-
- public IPlayingAudioStream? HonkPlayingStream;
-
- /// Use ambient sound component for the idle sound.
-
- /// <summary>
- /// The action for the horn (if any)
- /// </summary>
- [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>
- /// Whether the vehicle has a key currently inside it or not.
- /// </summary>
- [DataField("hasKey")]
- public bool HasKey = false;
-
- /// <summary>
- /// Determines from which side the vehicle will be displayed on top of the player.
- /// </summary>
-
- [DataField("southOver")]
- public bool SouthOver = false;
-
- [DataField("northOver")]
- public bool NorthOver = false;
-
- [DataField("westOver")]
- public bool WestOver = false;
-
- [DataField("eastOver")]
- public bool EastOver = false;
-
- /// <summary>
- /// What the y buckle offset should be in north / south
- /// </summary>
- [DataField("northOverride")]
- public float NorthOverride = 0f;
-
- /// <summary>
- /// What the y buckle offset should be in north / south
- /// </summary>
- [DataField("southOverride")]
- public float SouthOverride = 0f;
- }
+ Params = AudioParams.Default.WithVolume(-3f)
+ };
+
+ [ViewVariables]
+ public IPlayingAudioStream? HonkPlayingStream;
+
+ /// Use ambient sound component for the idle sound.
+
+ /// <summary>
+ /// The action for the horn (if any)
+ /// </summary>
+ [DataField("hornAction")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ 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>
+ /// Whether the vehicle has a key currently inside it or not.
+ /// </summary>
+ [DataField("hasKey")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool HasKey;
+
+ /// <summary>
+ /// Determines from which side the vehicle will be displayed on top of the player.
+ /// </summary>
+
+ [DataField("southOver")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool SouthOver;
+
+ [DataField("northOver")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool NorthOver;
+
+ [DataField("westOver")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool WestOver;
+
+ [DataField("eastOver")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool EastOver;
+
+ /// <summary>
+ /// What the y buckle offset should be in north / south
+ /// </summary>
+ [DataField("northOverride")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float NorthOverride;
+
+ /// <summary>
+ /// What the y buckle offset should be in north / south
+ /// </summary>
+ [DataField("southOverride")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float SouthOverride;
+
+ [DataField("autoAnimate")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool AutoAnimate = true;
+
+ [DataField("hideRider")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool HideRider;
}
+using Content.Shared.Hands;
using Content.Shared.Physics.Pull;
using Content.Shared.Vehicle.Components;
-using Robust.Shared.Serialization;
+using Robust.Shared.GameStates;
namespace Content.Shared.Vehicle;
public abstract partial class SharedVehicleSystem
{
- [Serializable, NetSerializable]
- protected sealed class RiderComponentState : ComponentState
+ private void InitializeRider()
{
- 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)
args.Cancelled = true;
using Content.Shared.Item;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
-using Content.Shared.Physics.Pull;
using Robust.Shared.Serialization;
using Robust.Shared.Containers;
using Content.Shared.Tag;
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;
/// </summary>
public abstract partial class SharedVehicleSystem : EntitySystem
{
+ [Dependency] private readonly INetManager _netManager = default!;
+
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _modifier = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tagSystem = 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";
+ /// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<InVehicleComponent, GettingPickedUpAttemptEvent>(OnPickupAttempt);
- SubscribeLocalEvent<RiderComponent, PullAttemptEvent>(OnRiderPull);
- SubscribeLocalEvent<VehicleComponent, RefreshMovementSpeedModifiersEvent>(OnVehicleModifier);
+ InitializeRider();
+
SubscribeLocalEvent<VehicleComponent, ComponentStartup>(OnVehicleStartup);
- SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnVehicleRotate);
+ SubscribeLocalEvent<VehicleComponent, BuckleChangeEvent>(OnBuckleChange);
+ SubscribeLocalEvent<VehicleComponent, HonkActionEvent>(OnHonkAction);
SubscribeLocalEvent<VehicleComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<VehicleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
+ SubscribeLocalEvent<VehicleComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
+ SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnMoveEvent);
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>
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
_ambientSound.SetAmbience(uid, true);
_tagSystem.AddTag(uid, "DoorBumpOpener");
_modifier.RefreshMovementSpeedModifiers(uid);
}
- private void OnVehicleModifier(EntityUid uid, VehicleComponent component, RefreshMovementSpeedModifiersEvent args)
+ private void OnRefreshMovementSpeedModifiers(EntityUid uid, VehicleComponent component, RefreshMovementSpeedModifiersEvent args)
{
if (!component.HasKey)
{
}
}
- 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.
- 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)
return;
// This first check is just for safety
- if (!HasComp<InputMoverComponent>(uid))
+ if (component.AutoAnimate && !HasComp<InputMoverComponent>(uid))
{
UpdateAutoAnimate(uid, false);
return;
}
- UpdateBuckleOffset(args.Component, component);
+ UpdateBuckleOffset(uid, args.Component, component);
if (TryComp<InputMoverComponent>(uid, out var mover))
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);
-
- // 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);
+ if (component.Vehicle == null || component.Vehicle.Rider != null && component.Vehicle.Rider != args.User)
+ args.Cancel();
}
/// <summary>
/// change its draw depth. Vehicles can choose between special drawdetph
/// when facing north or south. East and west are easy.
/// </summary>
- protected int GetDrawDepth(TransformComponent xform, VehicleComponent component, Angle cameraAngle)
+ private int GetDrawDepth(TransformComponent xform, VehicleComponent component, Angle cameraAngle)
{
var itemDirection = cameraAngle.GetDir() switch
{
/// teleport any buckled entities to it. This is the most crucial part of making
/// buckled vehicles work.
/// </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;
// TODO: Strap should handle this but buckle E/C moment.
/// <summary>
/// Set the draw depth for the sprite.
/// </summary>
- protected void UpdateDrawDepth(EntityUid uid, int drawDepth)
+ private void UpdateDrawDepth(EntityUid uid, int drawDepth)
{
Appearance.SetData(uid, VehicleVisuals.DrawDepth, drawDepth);
}
/// <summary>
/// Set whether the vehicle's base layer is animating or not.
/// </summary>
- protected void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
+ private void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
{
Appearance.SetData(uid, VehicleVisuals.AutoAnimate, autoAnimate);
}
/// <summary>
/// Whether the wheels should be turning
/// </summary>
- AutoAnimate
+ AutoAnimate,
+ HideRider
}
+
/// <summary>
/// Raised when someone honks a vehicle horn
/// </summary>
-public sealed class HonkActionEvent : InstantActionEvent { }
-
+public sealed class HonkActionEvent : InstantActionEvent
+{
+}
- MobMask
layer:
- TableLayer
- - type: VehicleVisuals
- type: Appearance
- type: Repairable
fuelcost: 20