Vehicles (#7336)
@@ -1,8 +1,7 @@
|
|||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Buckle.Components;
|
using Content.Shared.Buckle.Components;
|
||||||
|
using Content.Shared.Vehicle.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
|
|
||||||
namespace Content.Client.Buckle
|
namespace Content.Client.Buckle
|
||||||
{
|
{
|
||||||
@@ -42,6 +41,11 @@ namespace Content.Client.Buckle
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_entMan.TryGetComponent(Owner, out RiderComponent? rider))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_buckled && buckle.DrawDepth.HasValue)
|
if (_buckled && buckle.DrawDepth.HasValue)
|
||||||
{
|
{
|
||||||
_originalDrawDepth ??= ownerSprite.DrawDepth;
|
_originalDrawDepth ??= ownerSprite.DrawDepth;
|
||||||
|
|||||||
32
Content.Client/Vehicle/VehicleSystem.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Content.Shared.Vehicle;
|
||||||
|
|
||||||
|
namespace Content.Client.Vehicle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls client-side visuals for
|
||||||
|
/// vehicles
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VehicleSystem : VisualizerSystem<VehicleVisualsComponent>
|
||||||
|
{
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, VehicleVisualsComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
/// First check is for the sprite itself
|
||||||
|
if (TryComp(uid, out SpriteComponent? sprite)
|
||||||
|
&& args.Component.TryGetData(VehicleVisuals.DrawDepth, out int drawDepth) && sprite != null)
|
||||||
|
{
|
||||||
|
sprite.DrawDepth = drawDepth;
|
||||||
|
}
|
||||||
|
/// Set vehicle layer to animated or not (i.e. are the wheels turning or not)
|
||||||
|
if (args.Component.TryGetData(VehicleVisuals.AutoAnimate, out bool autoAnimate))
|
||||||
|
{
|
||||||
|
sprite?.LayerSetAutoAnimated(VehicleVisualLayers.AutoAnimate, autoAnimate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum VehicleVisualLayers : byte
|
||||||
|
{
|
||||||
|
/// Layer for the vehicle's wheels
|
||||||
|
AutoAnimate,
|
||||||
|
}
|
||||||
10
Content.Client/Vehicle/VehicleVisualsComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Content.Client.Vehicle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls visuals for vehicles
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class VehicleVisualsComponent : Component
|
||||||
|
{
|
||||||
|
public int DrawDepth = 0;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
using Content.Server.Pulling;
|
using Content.Server.Pulling;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.Vehicle.Components;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Buckle.Components;
|
using Content.Shared.Buckle.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
@@ -288,6 +289,10 @@ namespace Content.Server.Buckle.Components
|
|||||||
{
|
{
|
||||||
return false;
|
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;
|
BuckledTo = null;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Content.Server.Buckle.Components
|
|||||||
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
|
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("buckleOffset", required: false)]
|
[DataField("buckleOffset", required: false)]
|
||||||
private Vector2 _buckleOffset = Vector2.Zero;
|
public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
|
||||||
|
|
||||||
private bool _enabled = true;
|
private bool _enabled = true;
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ namespace Content.Server.Buckle.Components
|
|||||||
/// Don't change it unless you really have to
|
/// Don't change it unless you really have to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("maxBuckleDistance", required: false)]
|
[DataField("maxBuckleDistance", required: false)]
|
||||||
public float MaxBuckleDistance = 0.5f;
|
public float MaxBuckleDistance = 0.1f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// You can specify the offset the entity will have after unbuckling.
|
/// 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
|
/// Gets and clamps the buckle offset to MaxBuckleDistance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 BuckleOffset => Vector2.Clamp(
|
public Vector2 BuckleOffset => Vector2.Clamp(
|
||||||
_buckleOffset,
|
BuckleOffsetUnclamped,
|
||||||
Vector2.One * -MaxBuckleDistance,
|
Vector2.One * -MaxBuckleDistance,
|
||||||
Vector2.One * MaxBuckleDistance);
|
Vector2.One * MaxBuckleDistance);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Content.Server.Entry
|
|||||||
"MeleeWeaponArcAnimation",
|
"MeleeWeaponArcAnimation",
|
||||||
"AnimationsTest",
|
"AnimationsTest",
|
||||||
"ItemStatus",
|
"ItemStatus",
|
||||||
|
"VehicleVisuals",
|
||||||
"Marker",
|
"Marker",
|
||||||
"Clickable",
|
"Clickable",
|
||||||
"Icon",
|
"Icon",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Shuttles.Components;
|
using Content.Server.Shuttles.Components;
|
||||||
using Content.Server.Shuttles.EntitySystems;
|
using Content.Server.Shuttles.EntitySystems;
|
||||||
|
using Content.Shared.Vehicle.Components;
|
||||||
using Content.Shared.Movement;
|
using Content.Shared.Movement;
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
using Content.Shared.Shuttles.Components;
|
using Content.Shared.Shuttles.Components;
|
||||||
@@ -14,7 +15,12 @@ namespace Content.Server.Physics.Controllers
|
|||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||||
[Dependency] private readonly ThrusterSystem _thruster = 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 HashSet<EntityUid> _excludedMobs = new();
|
||||||
private Dictionary<ShuttleComponent, List<(PilotComponent, IMoverComponent)>> _shuttlePilots = new();
|
private Dictionary<ShuttleComponent, List<(PilotComponent, IMoverComponent)>> _shuttlePilots = new();
|
||||||
|
|
||||||
@@ -40,6 +46,7 @@ namespace Content.Server.Physics.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
HandleShuttleMovement(frameTime);
|
HandleShuttleMovement(frameTime);
|
||||||
|
HandleVehicleMovement(frameTime);
|
||||||
|
|
||||||
foreach (var (mover, physics) in EntityManager.EntityQuery<IMoverComponent, PhysicsComponent>(true))
|
foreach (var (mover, physics) in EntityManager.EntityQuery<IMoverComponent, PhysicsComponent>(true))
|
||||||
{
|
{
|
||||||
@@ -60,7 +67,7 @@ namespace Content.Server.Physics.Controllers
|
|||||||
_excludedMobs.Add(mover.Owner);
|
_excludedMobs.Add(mover.Owner);
|
||||||
|
|
||||||
var gridId = xform.GridID;
|
var gridId = xform.GridID;
|
||||||
|
// This tries to see if the grid is a shuttle
|
||||||
if (!_mapManager.TryGetGrid(gridId, out var grid) ||
|
if (!_mapManager.TryGetGrid(gridId, out var grid) ||
|
||||||
!EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent)) continue;
|
!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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ public sealed class StandingStateSystem : EntitySystem
|
|||||||
var direction = EntityManager.TryGetComponent(uid, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero;
|
var direction = EntityManager.TryGetComponent(uid, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero;
|
||||||
var dropAngle = _random.NextFloat(0.8f, 1.2f);
|
var dropAngle = _random.NextFloat(0.8f, 1.2f);
|
||||||
|
|
||||||
|
var fellEvent = new FellDownEvent(uid);
|
||||||
|
RaiseLocalEvent(uid, fellEvent, false);
|
||||||
|
|
||||||
if (!TryComp(uid, out SharedHandsComponent? handsComp))
|
if (!TryComp(uid, out SharedHandsComponent? handsComp))
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
57
Content.Server/Vehicle/HonkSystem.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Content.Server/Vehicle/RiderSystem.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
256
Content.Server/Vehicle/VehicleSystem.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -336,10 +336,16 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
{
|
{
|
||||||
item = null;
|
item = null;
|
||||||
|
|
||||||
|
/// This handles logic with the slot itself
|
||||||
if (!CanEject(slot))
|
if (!CanEject(slot))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
item = slot.Item;
|
item = slot.Item;
|
||||||
|
|
||||||
|
/// This handles user logic
|
||||||
|
if (user != null && item != null && !_actionBlockerSystem.CanPickup(user.Value, item.Value))
|
||||||
|
return false;
|
||||||
|
|
||||||
Eject(uid, slot, item!.Value, user, excludeUserAudio);
|
Eject(uid, slot, item!.Value, user, excludeUserAudio);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Shared.MobState.Components;
|
using Content.Shared.MobState.Components;
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
|
using Content.Shared.Vehicle.Components;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Input.Binding;
|
using Robust.Shared.Input.Binding;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
namespace Content.Shared.Movement.EntitySystems
|
namespace Content.Shared.Movement.EntitySystems
|
||||||
@@ -60,6 +58,14 @@ namespace Content.Shared.Movement.EntitySystems
|
|||||||
var relayMoveEvent = new RelayMovementEntityEvent(owner.Value);
|
var relayMoveEvent = new RelayMovementEntityEvent(owner.Value);
|
||||||
EntityManager.EventBus.RaiseLocalEvent(EntityManager.GetComponent<TransformComponent>(owner.Value).ParentUid, relayMoveEvent);
|
EntityManager.EventBus.RaiseLocalEvent(EntityManager.GetComponent<TransformComponent>(owner.Value).ParentUid, relayMoveEvent);
|
||||||
}
|
}
|
||||||
|
// Pass the rider's inputs to the vehicle (the rider itself is on the ignored list in C.S/MoverController.cs)
|
||||||
|
if (TryComp<RiderComponent>(owner.Value, out var rider) && rider.Vehicle != null && rider.Vehicle.HasKey)
|
||||||
|
{
|
||||||
|
if (TryComp<IMoverComponent>(rider.Vehicle.Owner, out var vehicleMover))
|
||||||
|
{
|
||||||
|
vehicleMover.SetVelocityDirection(dir, subTick, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moverComp.SetVelocityDirection(dir, subTick, state);
|
moverComp.SetVelocityDirection(dir, subTick, state);
|
||||||
|
|||||||
16
Content.Shared/Vehicle/Components/InVehicleComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Content.Shared.Vehicle.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Added to objects inside a vehicle to stop people besides the rider from
|
||||||
|
/// removing them.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class InVehicleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The vehicle this rider is currently riding.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public VehicleComponent Vehicle = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Content.Shared/Vehicle/Components/RiderComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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]
|
||||||
|
public sealed class RiderComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The vehicle this rider is currently riding.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public VehicleComponent Vehicle = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Content.Shared/Vehicle/Components/VehicleComponent.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Content.Shared.Sound;
|
||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
|
|
||||||
|
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>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class VehicleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether someone is currently riding the vehicle
|
||||||
|
/// </summary
|
||||||
|
public bool HasRider = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entity currently riding the vehicle.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityUid? Rider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the vehicle should treat north as it's unique direction in its visualizer
|
||||||
|
/// </summary>
|
||||||
|
[DataField("northOnly")]
|
||||||
|
public bool NorthOnly = 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;
|
||||||
|
|
||||||
|
/// <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");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the horn is a siren or not.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("hornIsSiren")]
|
||||||
|
public bool HornIsLooping = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If this vehicle has a siren currently playing.
|
||||||
|
/// </summary>
|
||||||
|
public bool LoopingHornIsPlaying = false;
|
||||||
|
|
||||||
|
public IPlayingAudioStream? SirenPlayingStream;
|
||||||
|
|
||||||
|
/// 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 ResourcePath("Objects/Fun/bikehorn.rsi/icon.png")),
|
||||||
|
Name = "action-name-honk",
|
||||||
|
Description = "action-desc-honk",
|
||||||
|
Event = new HonkActionEvent(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The prototype ID of the key that was inserted so it can be
|
||||||
|
/// spawned when the key is removed.
|
||||||
|
/// </summary>
|
||||||
|
public ItemSlot KeySlot = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the vehicle has a key currently inside it or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasKey = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Content.Shared/Vehicle/SharedVehicleSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Content.Shared.Vehicle.Components;
|
||||||
|
using Content.Shared.Actions;
|
||||||
|
using Content.Shared.Item;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the VehicleVisuals and shared event
|
||||||
|
/// Nothing for a system but these need to be put somewhere in
|
||||||
|
/// Content.Shared
|
||||||
|
/// </summary>
|
||||||
|
namespace Content.Shared.Vehicle
|
||||||
|
{
|
||||||
|
public sealed class SharedVehicleSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<InVehicleComponent, GettingPickedUpAttemptEvent>(OnPickupAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPickupAttempt(EntityUid uid, InVehicleComponent component, GettingPickedUpAttemptEvent args)
|
||||||
|
{
|
||||||
|
if (component.Vehicle == null || !component.Vehicle.HasRider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (component.Vehicle.Rider != args.User)
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the vehicle's draw depth mostly
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum VehicleVisuals : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// What layer the vehicle should draw on (assumed integer)
|
||||||
|
/// </summary>
|
||||||
|
DrawDepth,
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the wheels should be turning
|
||||||
|
/// </summary>
|
||||||
|
AutoAnimate
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when someone honks a vehicle horn
|
||||||
|
/// </summary>
|
||||||
|
public sealed class HonkActionEvent : InstantActionEvent { }
|
||||||
|
}
|
||||||
BIN
Resources/Audio/Effects/Vehicle/carhorn.ogg
Normal file
5
Resources/Audio/Effects/Vehicle/license.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
vehiclestartup.ogg and vehicleengineidle.ogg taken from user InspectorJ at https://freesound.org/people/InspectorJ/sounds/338954/
|
||||||
|
|
||||||
|
carhorn.ogg taken from /tg/station at commit https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a
|
||||||
|
|
||||||
|
policesiren.ogg taken from freesound user blukotek at https://freesound.org/people/blukotek/sounds/431651/
|
||||||
BIN
Resources/Audio/Effects/Vehicle/policesiren.ogg
Normal file
BIN
Resources/Audio/Effects/Vehicle/vehicleengineidle.ogg
Normal file
BIN
Resources/Audio/Effects/Vehicle/vehiclestartup.ogg
Normal file
@@ -1 +1,2 @@
|
|||||||
pill.ogg contains audio from '437480__ruanza__swallowing.wav' by user RuanZA, used under CC BY-NC 3.0 (https://freesound.org/people/RuanZA/sounds/437480/), as well as part of '330657__diniunicorn__popping-pills.wav' by user diniunicorn, used under CC0 1.0 (https://freesound.org/people/diniunicorn/sounds/330657/). ring.ogg used udner CC-BY-SA-3.0, taken from /tg/station commit https://github.com/tgstation/tgstation/commit/c61c452d78425d89920b41ed5f95fd190e733a3c.
|
pill.ogg contains audio from '437480__ruanza__swallowing.wav' by user RuanZA, used under CC BY-NC 3.0 (https://freesound.org/people/RuanZA/sounds/437480/), as well as part of '330657__diniunicorn__popping-pills.wav' by user diniunicorn, used under CC0 1.0 (https://freesound.org/people/diniunicorn/sounds/330657/). ring.ogg used udner CC-BY-SA-3.0, taken from /tg/station commit https://github.com/tgstation/tgstation/commit/c61c452d78425d89920b41ed5f95fd190e733a3c.
|
||||||
|
|
||||||
|
|||||||
4
Resources/Locale/en-US/actions/actions/vehicle.ftl
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
action-name-honk = Honk
|
||||||
|
action-desc-honk = Honk!
|
||||||
|
action-name-siren = Toggle Siren
|
||||||
|
action-desc-siren = Wee-woo.
|
||||||
1
Resources/Locale/en-US/vehicle/vehicle.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
vehicle-use-key = You use {THE($keys)} to start {THE($vehicle)}.
|
||||||
@@ -57,3 +57,15 @@
|
|||||||
cost: 1500
|
cost: 1500
|
||||||
category: Fun
|
category: Fun
|
||||||
group: market
|
group: market
|
||||||
|
|
||||||
|
- type: cargoProduct
|
||||||
|
name: "ATV crate"
|
||||||
|
id: FunATV
|
||||||
|
description: "An Absolutely Taxable Vehicle to help cargo with hauling."
|
||||||
|
icon:
|
||||||
|
sprite: Objects/Vehicles/atv.rsi
|
||||||
|
state: vehicle
|
||||||
|
product: CrateFunATV
|
||||||
|
cost: 2500
|
||||||
|
category: Fun
|
||||||
|
group: market
|
||||||
|
|||||||
@@ -111,3 +111,15 @@
|
|||||||
amount: 1
|
amount: 1
|
||||||
- id: d6Dice
|
- id: d6Dice
|
||||||
amount: 4
|
amount: 4
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: CrateFunATV
|
||||||
|
name: ATV crate
|
||||||
|
parent: CrateLivestock
|
||||||
|
components:
|
||||||
|
- type: StorageFill
|
||||||
|
contents:
|
||||||
|
- id: VehicleATV
|
||||||
|
amount: 1
|
||||||
|
- id: VehicleKeyATV
|
||||||
|
amount: 1
|
||||||
|
|||||||
@@ -145,6 +145,7 @@
|
|||||||
- FunArtSupplies
|
- FunArtSupplies
|
||||||
- FunInstruments
|
- FunInstruments
|
||||||
- FunBoardGames
|
- FunBoardGames
|
||||||
|
- FunATV
|
||||||
- MaterialSteel
|
- MaterialSteel
|
||||||
- MaterialGlass
|
- MaterialGlass
|
||||||
- MaterialPlastic
|
- MaterialPlastic
|
||||||
|
|||||||
@@ -162,8 +162,8 @@
|
|||||||
event: !type:ToggleActionEvent
|
event: !type:ToggleActionEvent
|
||||||
- type: PointLight
|
- type: PointLight
|
||||||
enabled: false
|
enabled: false
|
||||||
radius: 2.5
|
radius: 3.5
|
||||||
softness: 5
|
softness: 1
|
||||||
mask: /Textures/Effects/LightMasks/cone.png
|
mask: /Textures/Effects/LightMasks/cone.png
|
||||||
autoRot: true
|
autoRot: true
|
||||||
- type: Tag
|
- type: Tag
|
||||||
|
|||||||
257
Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
- type: entity
|
||||||
|
id: VehicleBase
|
||||||
|
save: false
|
||||||
|
abstract: true
|
||||||
|
name: Vehicle
|
||||||
|
components:
|
||||||
|
- type: Strap
|
||||||
|
buckleOffset: "0.10, 0.36"
|
||||||
|
maxBuckleDistance: 1
|
||||||
|
- type: AmbientSound
|
||||||
|
sound: "/Audio/Effects/Vehicle/vehicleengineidle.ogg"
|
||||||
|
range: 10
|
||||||
|
volume: -1
|
||||||
|
- type: PlayerInputMover
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Vehicle
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
baseWalkSpeed : 7
|
||||||
|
baseSprintSpeed : 7
|
||||||
|
- type: Tag
|
||||||
|
- type: Pullable
|
||||||
|
- type: Physics
|
||||||
|
bodyType: KinematicController
|
||||||
|
- type: Clickable
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.4
|
||||||
|
mass: 180
|
||||||
|
restitution: 0.0
|
||||||
|
mask:
|
||||||
|
- Impassable
|
||||||
|
- VaultImpassable
|
||||||
|
layer:
|
||||||
|
- VaultImpassable
|
||||||
|
- type: VehicleVisuals
|
||||||
|
- type: Appearance
|
||||||
|
- type: Repairable
|
||||||
|
fuelcost: 20
|
||||||
|
doAfterDelay: 20
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 300
|
||||||
|
behaviors:
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: ["Destruction"]
|
||||||
|
- !type:PlaySoundBehavior
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/metalbreak.ogg
|
||||||
|
- !type:ExplodeBehavior
|
||||||
|
- type: ItemSlots
|
||||||
|
slots:
|
||||||
|
key_slot: #this slot name is important
|
||||||
|
name: Keys
|
||||||
|
whitelist:
|
||||||
|
requireAll: true
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: VehiclePussyWagon
|
||||||
|
parent: VehicleBase
|
||||||
|
name: janicart
|
||||||
|
description: The janitor's trusty steed.
|
||||||
|
components:
|
||||||
|
- type: Vehicle
|
||||||
|
northOverride: -0.15
|
||||||
|
southOverride: 0.22
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/pussywagon.rsi
|
||||||
|
layers:
|
||||||
|
- state: vehicle
|
||||||
|
map: ["enum.VehicleVisualLayers.AutoAnimate"]
|
||||||
|
netsync: false
|
||||||
|
noRot: true
|
||||||
|
- type: Access
|
||||||
|
tags:
|
||||||
|
- Maintenance
|
||||||
|
- Janitor
|
||||||
|
- type: UnpoweredFlashlight
|
||||||
|
toggleAction:
|
||||||
|
name: action-name-toggle-light
|
||||||
|
description: action-description-toggle-light
|
||||||
|
icon: Objects/Tools/flashlight.rsi/flashlight.png
|
||||||
|
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
|
||||||
|
event: !type:ToggleActionEvent
|
||||||
|
- type: PointLight
|
||||||
|
enabled: false
|
||||||
|
radius: 3.5
|
||||||
|
softness: 2
|
||||||
|
mask: /Textures/Effects/LightMasks/cone.png
|
||||||
|
autoRot: true
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 250
|
||||||
|
behaviors:
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: ["Destruction"]
|
||||||
|
- !type:PlaySoundBehavior
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/metalbreak.ogg
|
||||||
|
- !type:ExplodeBehavior
|
||||||
|
- !type:SpawnEntitiesBehavior # in future should also emit a cloud of hot gas
|
||||||
|
spawn:
|
||||||
|
VehiclePussyWagonDestroyed:
|
||||||
|
min: 1
|
||||||
|
max: 1
|
||||||
|
- type: ItemSlots
|
||||||
|
slots:
|
||||||
|
key_slot:
|
||||||
|
name: Keys
|
||||||
|
whitelist:
|
||||||
|
requireAll: true
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- PussyWagonKeys
|
||||||
|
insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg
|
||||||
|
trashbag_slot:
|
||||||
|
name: Bag
|
||||||
|
whitelist:
|
||||||
|
tags:
|
||||||
|
- TrashBag
|
||||||
|
- type: ItemMapper
|
||||||
|
mapLayers:
|
||||||
|
storage:
|
||||||
|
whitelist:
|
||||||
|
tags:
|
||||||
|
- TrashBag
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: MappedItemVisualizer
|
||||||
|
sprite: Objects/Vehicles/pussywagon.rsi
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: VehiclePussyWagonDestroyed
|
||||||
|
parent: MachineFrameDestroyed
|
||||||
|
name: destroyed janicart
|
||||||
|
description: R.I.P. (Rest In Pussy)
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/pussywagon.rsi
|
||||||
|
state: destroyed
|
||||||
|
netsync: false
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: VehicleSecway
|
||||||
|
parent: VehicleBase
|
||||||
|
name: secway
|
||||||
|
description: The future of transportation. Popularized by St. James, the patron saint of security officers and internet forum moderators.
|
||||||
|
components:
|
||||||
|
- type: Vehicle
|
||||||
|
northOnly: true
|
||||||
|
northOverride: -0.1
|
||||||
|
southOverride: 0.1
|
||||||
|
hornSound:
|
||||||
|
path: /Audio/Effects/Vehicle/policesiren.ogg
|
||||||
|
hornAction:
|
||||||
|
name: action-name-siren
|
||||||
|
description: action-desc-siren
|
||||||
|
icon: Objects/Fun/bikehorn.rsi/icon.png
|
||||||
|
iconOn: Objects/Fun/bikehorn.rsi/icon.png
|
||||||
|
event: !type:ToggleActionEvent
|
||||||
|
hornIsSiren: true
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/secway.rsi
|
||||||
|
layers:
|
||||||
|
- state: vehicle
|
||||||
|
map: ["enum.VehicleVisualLayers.AutoAnimate"]
|
||||||
|
netsync: false
|
||||||
|
noRot: true
|
||||||
|
- type: Access
|
||||||
|
tags:
|
||||||
|
- Security
|
||||||
|
- Brig
|
||||||
|
- Maintenance
|
||||||
|
- Service
|
||||||
|
- type: Strap
|
||||||
|
buckleOffset: "0.15, -0.05"
|
||||||
|
maxBuckleDistance: 1
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
baseWalkSpeed : 8
|
||||||
|
baseSprintSpeed : 8
|
||||||
|
- type: Armor
|
||||||
|
modifiers:
|
||||||
|
coeffecients:
|
||||||
|
Blunt: 0.8
|
||||||
|
Slash: 0.6
|
||||||
|
Piercing: 0.85
|
||||||
|
- type: ItemSlots
|
||||||
|
slots:
|
||||||
|
key_slot:
|
||||||
|
name: Keys
|
||||||
|
whitelist:
|
||||||
|
requireAll: true
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- SecwayKeys
|
||||||
|
insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: VehicleBase
|
||||||
|
id: VehicleATV
|
||||||
|
name: ATV
|
||||||
|
description: All-Tile Vehicle.
|
||||||
|
components:
|
||||||
|
- type: Vehicle
|
||||||
|
northOverride: -0.1
|
||||||
|
southOverride: 0.1
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/atv.rsi
|
||||||
|
layers:
|
||||||
|
- state: vehicle
|
||||||
|
map: ["enum.VehicleVisualLayers.AutoAnimate"]
|
||||||
|
netsync: false
|
||||||
|
noRot: true
|
||||||
|
- type: Access
|
||||||
|
tags:
|
||||||
|
- Cargo
|
||||||
|
- Maintenance
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
baseWalkSpeed : 8
|
||||||
|
baseSprintSpeed : 8
|
||||||
|
- type: Strap
|
||||||
|
buckleOffset: "0.1, -0.05"
|
||||||
|
maxBuckleDistance: 1
|
||||||
|
- type: UnpoweredFlashlight
|
||||||
|
toggleAction:
|
||||||
|
name: action-name-toggle-light
|
||||||
|
description: action-description-toggle-light
|
||||||
|
icon: Objects/Tools/flashlight.rsi/flashlight.png
|
||||||
|
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
|
||||||
|
event: !type:ToggleActionEvent
|
||||||
|
- type: PointLight
|
||||||
|
enabled: false
|
||||||
|
radius: 3.5
|
||||||
|
softness: 2
|
||||||
|
mask: /Textures/Effects/LightMasks/cone.png
|
||||||
|
autoRot: true
|
||||||
|
- type: ItemSlots
|
||||||
|
slots:
|
||||||
|
key_slot:
|
||||||
|
name: Keys
|
||||||
|
whitelist:
|
||||||
|
requireAll: true
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- ATVKeys
|
||||||
|
insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg
|
||||||
59
Resources/Prototypes/Entities/Objects/Vehicles/keys.yml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: VehicleKeyPussyWagon
|
||||||
|
name: janicart keys
|
||||||
|
description: Interesting design.
|
||||||
|
components:
|
||||||
|
- type: Item
|
||||||
|
size: 2
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- PussyWagonKeys
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/pussywagon.rsi
|
||||||
|
state: keys
|
||||||
|
netsync: false
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: VehicleKeyPussyWagon
|
||||||
|
id: VehicleKeySecway
|
||||||
|
name: secway keys
|
||||||
|
description: The keys to the future.
|
||||||
|
components:
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- SecwayKeys
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/secway.rsi
|
||||||
|
state: keys
|
||||||
|
netsync: false
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: VehicleKeyPussyWagon
|
||||||
|
id: VehicleKeyATV
|
||||||
|
name: ATV keys
|
||||||
|
description: Think this looks like just one key? ATV keys means "actually two vehicle keys."
|
||||||
|
components:
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- ATVKeys
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Vehicles/atv.rsi
|
||||||
|
state: keys
|
||||||
|
netsync: false
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: VehicleKeyATV
|
||||||
|
id: VehicleKeySkeleton
|
||||||
|
name: vehicle skeleton keys
|
||||||
|
description: Unlock any vehicle.
|
||||||
|
components:
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- VehicleKey
|
||||||
|
- PussyWagonKeys
|
||||||
|
- SecwayKeys
|
||||||
|
- ATVKeys
|
||||||
@@ -395,6 +395,7 @@
|
|||||||
- FunInstruments
|
- FunInstruments
|
||||||
- FunBrass
|
- FunBrass
|
||||||
- FunBoardGames
|
- FunBoardGames
|
||||||
|
- FunATV
|
||||||
- MaterialSteel
|
- MaterialSteel
|
||||||
- MaterialGlass
|
- MaterialGlass
|
||||||
- MaterialPlastic
|
- MaterialPlastic
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: AirAlarmElectronics
|
id: AirAlarmElectronics
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: ATVKeys
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: Baguette
|
id: Baguette
|
||||||
|
|
||||||
@@ -123,6 +126,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Donut
|
id: Donut
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: Drone
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: DroneUsable
|
id: DroneUsable
|
||||||
|
|
||||||
@@ -264,6 +270,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Powerdrill
|
id: Powerdrill
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: PussyWagonKeys
|
||||||
|
|
||||||
# Give this to something that doesn't need any special recycler behavior and just needs deleting.
|
# Give this to something that doesn't need any special recycler behavior and just needs deleting.
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: Recyclable
|
id: Recyclable
|
||||||
@@ -280,6 +289,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Screwdriver
|
id: Screwdriver
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: SecwayKeys
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: Sheet
|
id: Sheet
|
||||||
|
|
||||||
@@ -310,6 +322,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: TrashBag
|
id: TrashBag
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: VehicleKey
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: WetFloorSign
|
id: WetFloorSign
|
||||||
|
|
||||||
@@ -336,6 +351,3 @@
|
|||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: Write
|
id: Write
|
||||||
|
|
||||||
- type: Tag
|
|
||||||
id: Drone
|
|
||||||
|
|||||||
BIN
Resources/Textures/Objects/Vehicles/atv.rsi/keys.png
Normal file
|
After Width: | Height: | Size: 282 B |
44
Resources/Textures/Objects/Vehicles/atv.rsi/meta.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "vehicle",
|
||||||
|
"directions": 4,
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keys"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Objects/Vehicles/atv.rsi/vehicle.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Resources/Textures/Objects/Vehicles/pussywagon.rsi/destroyed.png
Normal file
|
After Width: | Height: | Size: 870 B |
BIN
Resources/Textures/Objects/Vehicles/pussywagon.rsi/keys.png
Normal file
|
After Width: | Height: | Size: 557 B |
47
Resources/Textures/Objects/Vehicles/pussywagon.rsi/meta.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "vehicle",
|
||||||
|
"directions": 4,
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "storage",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "destroyed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keys"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Objects/Vehicles/pussywagon.rsi/storage.png
Normal file
|
After Width: | Height: | Size: 546 B |
BIN
Resources/Textures/Objects/Vehicles/pussywagon.rsi/vehicle.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Resources/Textures/Objects/Vehicles/secway.rsi/keys.png
Normal file
|
After Width: | Height: | Size: 529 B |
40
Resources/Textures/Objects/Vehicles/secway.rsi/meta.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "vehicle",
|
||||||
|
"directions": 4,
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keys"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Objects/Vehicles/secway.rsi/vehicle.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |