This commit is contained in:
Rane
2022-04-23 21:05:02 -04:00
committed by GitHub
parent 18220b6488
commit 98cd4fdb58
44 changed files with 1160 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using Content.Server.Hands.Components;
using Content.Server.Pulling;
using Content.Shared.ActionBlocker;
using Content.Shared.Vehicle.Components;
using Content.Shared.Alert;
using Content.Shared.Buckle.Components;
using Content.Shared.Interaction;
@@ -288,6 +289,10 @@ namespace Content.Server.Buckle.Components
{
return false;
}
// If the strap is a vehicle and the rider is not the person unbuckling, return.
if (_entMan.TryGetComponent<VehicleComponent>(oldBuckledTo.Owner, out var vehicle) &&
vehicle.Rider != user)
return false;
}
BuckledTo = null;

View File

@@ -34,7 +34,7 @@ namespace Content.Server.Buckle.Components
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
/// </summary>
[DataField("buckleOffset", required: false)]
private Vector2 _buckleOffset = Vector2.Zero;
public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
private bool _enabled = true;
@@ -57,7 +57,7 @@ namespace Content.Server.Buckle.Components
/// Don't change it unless you really have to
/// </summary>
[DataField("maxBuckleDistance", required: false)]
public float MaxBuckleDistance = 0.5f;
public float MaxBuckleDistance = 0.1f;
/// <summary>
/// You can specify the offset the entity will have after unbuckling.
@@ -69,7 +69,7 @@ namespace Content.Server.Buckle.Components
/// Gets and clamps the buckle offset to MaxBuckleDistance
/// </summary>
public Vector2 BuckleOffset => Vector2.Clamp(
_buckleOffset,
BuckleOffsetUnclamped,
Vector2.One * -MaxBuckleDistance,
Vector2.One * MaxBuckleDistance);

View File

@@ -12,6 +12,7 @@ namespace Content.Server.Entry
"MeleeWeaponArcAnimation",
"AnimationsTest",
"ItemStatus",
"VehicleVisuals",
"Marker",
"Clickable",
"Icon",

View File

@@ -1,5 +1,6 @@
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.EntitySystems;
using Content.Shared.Vehicle.Components;
using Content.Shared.Movement;
using Content.Shared.Movement.Components;
using Content.Shared.Shuttles.Components;
@@ -14,7 +15,12 @@ namespace Content.Server.Physics.Controllers
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly ThrusterSystem _thruster = default!;
/// <summary>
/// These mobs will get skipped over when checking which mobs
/// should be moved. Prediction is handled elsewhere by
/// cancelling the movement attempt in the shared and
/// client namespace.
/// </summary>
private HashSet<EntityUid> _excludedMobs = new();
private Dictionary<ShuttleComponent, List<(PilotComponent, IMoverComponent)>> _shuttlePilots = new();
@@ -40,6 +46,7 @@ namespace Content.Server.Physics.Controllers
}
HandleShuttleMovement(frameTime);
HandleVehicleMovement(frameTime);
foreach (var (mover, physics) in EntityManager.EntityQuery<IMoverComponent, PhysicsComponent>(true))
{
@@ -60,7 +67,7 @@ namespace Content.Server.Physics.Controllers
_excludedMobs.Add(mover.Owner);
var gridId = xform.GridID;
// This tries to see if the grid is a shuttle
if (!_mapManager.TryGetGrid(gridId, out var grid) ||
!EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent)) continue;
@@ -252,5 +259,17 @@ namespace Content.Server.Physics.Controllers
}
}
}
/// <summary>
/// Add mobs riding vehicles to the list of mobs whose input
/// should be ignored.
/// </summary>
private void HandleVehicleMovement(float frameTime)
{
foreach (var (rider, mover, xform) in EntityManager.EntityQuery<RiderComponent, SharedPlayerInputMoverComponent, TransformComponent>())
{
if (rider.Vehicle == null) continue;
_excludedMobs.Add(mover.Owner);
}
}
}
}

View File

@@ -17,6 +17,9 @@ public sealed class StandingStateSystem : EntitySystem
var direction = EntityManager.TryGetComponent(uid, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero;
var dropAngle = _random.NextFloat(0.8f, 1.2f);
var fellEvent = new FellDownEvent(uid);
RaiseLocalEvent(uid, fellEvent, false);
if (!TryComp(uid, out SharedHandsComponent? handsComp))
return;
@@ -43,3 +46,15 @@ public sealed class StandingStateSystem : EntitySystem
}
}
/// <summary>
/// Raised after an entity falls down.
/// <summary>
public sealed class FellDownEvent : EntityEventArgs
{
public EntityUid Uid { get; }
public FellDownEvent(EntityUid uid)
{
Uid = uid;
}
}

View File

@@ -0,0 +1,57 @@
using Content.Shared.Vehicle.Components;
using Content.Shared.Vehicle;
using Content.Shared.Toggleable;
using Content.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Audio;
namespace Content.Server.Vehicle
{
/// <summary>
/// Controls all the vehicle horns.
/// </summary>
public sealed class HonkSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VehicleComponent, HonkActionEvent>(OnHonk);
SubscribeLocalEvent<VehicleComponent, ToggleActionEvent>(OnSirenToggle);
}
/// <summary>
/// This fires when the rider presses the honk action
/// </summary>
private void OnHonk(EntityUid uid, VehicleComponent vehicle, HonkActionEvent args)
{
if (args.Handled)
return;
if (vehicle.HornSound != null)
{
SoundSystem.Play(Filter.Pvs(uid), vehicle.HornSound.GetSound(), uid, AudioHelpers.WithVariation(0.1f).WithVolume(8f));
args.Handled = true;
}
}
/// <summary>
/// For vehicles with horn sirens (like the secway) this uses different logic that makes the siren
/// loop instead of using a normal honk.
/// </summary>
private void OnSirenToggle(EntityUid uid, VehicleComponent vehicle, ToggleActionEvent args)
{
if (args.Handled || !vehicle.HornIsLooping)
return;
if (!vehicle.LoopingHornIsPlaying)
{
vehicle.SirenPlayingStream?.Stop();
vehicle.LoopingHornIsPlaying = true;
if (vehicle.HornSound != null)
vehicle.SirenPlayingStream = SoundSystem.Play(Filter.Pvs(uid), vehicle.HornSound.GetSound(), uid, AudioParams.Default.WithLoop(true).WithVolume(1.8f));
return;
}
vehicle.SirenPlayingStream?.Stop();
vehicle.LoopingHornIsPlaying = false;
}
}
}

View File

@@ -0,0 +1,57 @@
using Content.Server.Buckle.Components;
using Content.Shared.Vehicle.Components;
using Content.Shared.MobState;
using Content.Server.Standing;
using Content.Shared.Hands;
namespace Content.Server.Vehicle
{
public sealed class RiderSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RiderComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<RiderComponent, FellDownEvent>(OnFallDown);
SubscribeLocalEvent<RiderComponent, MobStateChangedEvent>(OnMobStateChanged);
}
/// <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?.Owner)
{
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.Component.IsCritical() || args.Component.IsDead())
{
UnbuckleFromVehicle(uid);
}
}
private void UnbuckleFromVehicle(EntityUid uid)
{
if (!TryComp<BuckleComponent>(uid, out var buckle))
return;
buckle.TryUnbuckle(uid, true);
}
}
}

View File

@@ -0,0 +1,256 @@
using Content.Shared.Vehicle.Components;
using Content.Shared.Vehicle;
using Content.Shared.Buckle.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.Pulling.Components;
using Content.Server.Light.Components;
using Content.Server.Buckle.Components;
using Content.Server.Hands.Systems;
using Content.Shared.Tag;
using Robust.Shared.Random;
using Robust.Shared.Containers;
namespace Content.Server.Vehicle
{
public sealed class VehicleSystem : EntitySystem
{
[Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VehicleComponent, BuckleChangeEvent>(OnBuckleChange);
SubscribeLocalEvent<VehicleComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnMove);
SubscribeLocalEvent<VehicleComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<VehicleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
}
/// <summary>
/// This just controls whether the wheels are turning.
/// </summary>
public override void Update(float frameTime)
{
foreach (var (vehicle, mover) in EntityQuery<VehicleComponent, SharedPlayerInputMoverComponent>())
{
if (mover.VelocityDir.sprinting == Vector2.Zero)
{
UpdateAutoAnimate(vehicle.Owner, false);
continue;
}
UpdateAutoAnimate(vehicle.Owner, true);
}
}
/// <summary>
/// Sets the initial appearance / sound, then stores the initial buckle offset and resets it.
/// </summary>
private void OnComponentInit(EntityUid uid, VehicleComponent component, ComponentInit args)
{
UpdateDrawDepth(uid, 2);
_ambientSound.SetAmbience(uid, false);
if (!TryComp<StrapComponent>(uid, out var strap))
return;
component.BaseBuckleOffset = strap.BuckleOffset;
strap.BuckleOffsetUnclamped = Vector2.Zero; //You're going to align these facing east, so...
}
/// <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)
{
if (args.Buckling)
{
/// Set up the rider and vehicle with each other
EnsureComp<SharedPlayerInputMoverComponent>(uid);
var rider = EnsureComp<RiderComponent>(args.BuckledEntity);
component.Rider = args.BuckledEntity;
rider.Vehicle = component;
component.HasRider = true;
/// Handle pulling
RemComp<SharedPullableComponent>(args.BuckledEntity);
RemComp<SharedPullableComponent>(uid);
/// Add a virtual item to rider's hand
_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.BuckledEntity);
/// Let this open doors if it has the key in it
if (component.HasKey)
{
_tagSystem.AddTag(uid, "DoorBumpOpener");
}
/// Update appearance stuff, add actions
UpdateBuckleOffset(Transform(uid), component);
UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component.NorthOnly));
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);
}
_itemSlotsSystem.SetLock(uid, component.Name, true);
return;
}
// Clean up actions and virtual items
_actionsSystem.RemoveProvidedActions(args.BuckledEntity, uid);
_virtualItemSystem.DeleteInHandsMatching(args.BuckledEntity, uid);
// Go back to old pullable behavior
_tagSystem.RemoveTag(uid, "DoorBumpOpener");
EnsureComp<SharedPullableComponent>(args.BuckledEntity);
EnsureComp<SharedPullableComponent>(uid);
/// Entity is no longer riding
RemComp<RiderComponent>(args.BuckledEntity);
/// Reset component
component.HasRider = false;
component.Rider = null;
_itemSlotsSystem.SetLock(uid, component.Name, false);
}
/// <summary>
/// Every time the vehicle moves we update its visual and buckle positions.
/// Not the most beautiful thing but it works.
/// </summary>
private void OnMove(EntityUid uid, VehicleComponent component, ref MoveEvent args)
{
/// This first check is just for safety
if (!HasComp<SharedPlayerInputMoverComponent>(uid))
{
UpdateAutoAnimate(uid, false);
return;
}
/// The random check means the vehicle will stop after a few tiles without a key or without a rider
if ((!component.HasRider || !component.HasKey) && _random.Prob(0.015f))
{
RemComp<SharedPlayerInputMoverComponent>(uid);
UpdateAutoAnimate(uid, false);
}
UpdateBuckleOffset(args.Component, component);
UpdateDrawDepth(uid, GetDrawDepth(args.Component, component.NorthOnly));
}
/// <summary>
/// Handle adding keys to the ignition, give stuff the InVehicleComponent so it can't be picked
/// up by people not in the vehicle.
/// </summary>
private void OnEntInserted(EntityUid uid, VehicleComponent component, EntInsertedIntoContainerMessage args)
{
var inVehicle = AddComp<InVehicleComponent>(args.Entity);
inVehicle.Vehicle = component;
if (_tagSystem.HasTag(args.Entity, "VehicleKey"))
{
/// Return if the slot is not the key slot
/// That slot ID should be inherited from basevehicle in the .yml
if (args.Container.ID != "key_slot")
{
return;
}
/// This lets the vehicle move
EnsureComp<SharedPlayerInputMoverComponent>(uid);
/// This lets the vehicle open doors
if (component.HasRider)
_tagSystem.AddTag(uid, "DoorBumpOpener");
component.HasKey = true;
// Audiovisual feedback
_ambientSound.SetAmbience(uid, true);
}
}
/// <summary>
/// Turn off the engine when key is removed.
/// </summary>
private void OnEntRemoved(EntityUid uid, VehicleComponent component, EntRemovedFromContainerMessage args)
{
RemComp<InVehicleComponent>(args.Entity);
if (_tagSystem.HasTag(args.Entity, "VehicleKey"))
{
component.HasKey = false;
_ambientSound.SetAmbience(uid, false);
}
}
/// <summary>
/// Depending on which direction the vehicle is facing,
/// change its draw depth. Vehicles can choose between special drawdetph
/// when facing north or south. East and west are easy.
/// </summary>
private int GetDrawDepth(TransformComponent xform, bool northOnly)
{
if (northOnly)
{
return xform.LocalRotation.Degrees switch
{
< 135f => 10,
<= 225f => 2,
_ => 10
};
}
return xform.LocalRotation.Degrees switch
{
< 45f => 10,
<= 315f => 2,
_ => 10
};
}
/// <summary>
/// Change the buckle offset based on what direction the vehicle is facing and
/// teleport any buckled entities to it. This is the most crucial part of making
/// buckled vehicles work.
/// </summary>
private void UpdateBuckleOffset(TransformComponent xform, VehicleComponent component)
{
if (!TryComp<StrapComponent>(component.Owner, out var strap))
return;
strap.BuckleOffsetUnclamped = xform.LocalRotation.Degrees switch
{
< 45f => (0, component.SouthOverride),
<= 135f => component.BaseBuckleOffset,
< 225f => (0, component.NorthOverride),
<= 315f => (component.BaseBuckleOffset.X * -1, component.BaseBuckleOffset.Y),
_ => (0, component.SouthOverride)
};
foreach (var buckledEntity in strap.BuckledEntities)
{
var buckleXform = Transform(buckledEntity);
buckleXform.LocalPosition = strap.BuckleOffset;
}
}
/// <summary>
/// Set the draw depth for the sprite.
/// </summary>
private void UpdateDrawDepth(EntityUid uid, int drawDepth)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
appearance.SetData(VehicleVisuals.DrawDepth, drawDepth);
}
/// <summary>
/// Set whether the vehicle's base layer is animating or not.
/// </summmary>
private void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
appearance.SetData(VehicleVisuals.AutoAnimate, autoAnimate);
}
}
}