Adds force-gun (#16561)
@@ -19,6 +19,8 @@ public sealed class TetherGunOverlay : Overlay
|
|||||||
{
|
{
|
||||||
var query = _entManager.EntityQueryEnumerator<TetheredComponent>();
|
var query = _entManager.EntityQueryEnumerator<TetheredComponent>();
|
||||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||||
|
var tetherQuery = _entManager.GetEntityQuery<TetherGunComponent>();
|
||||||
|
var forceQuery = _entManager.GetEntityQuery<ForceGunComponent>();
|
||||||
var worldHandle = args.WorldHandle;
|
var worldHandle = args.WorldHandle;
|
||||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||||
|
|
||||||
@@ -46,7 +48,18 @@ public sealed class TetherGunOverlay : Overlay
|
|||||||
var box = new Box2(-Width, -length, Width, length);
|
var box = new Box2(-Width, -length, Width, length);
|
||||||
var rotated = new Box2Rotated(box.Translated(midPoint), angle, midPoint);
|
var rotated = new Box2Rotated(box.Translated(midPoint), angle, midPoint);
|
||||||
|
|
||||||
worldHandle.DrawRect(rotated, Color.Orange.WithAlpha(0.3f));
|
var color = Color.Red;
|
||||||
|
|
||||||
|
if (forceQuery.TryGetComponent(tethered.Tetherer, out var force))
|
||||||
|
{
|
||||||
|
color = force.LineColor;
|
||||||
|
}
|
||||||
|
else if (tetherQuery.TryGetComponent(tethered.Tetherer, out var tether))
|
||||||
|
{
|
||||||
|
color = tether.LineColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
worldHandle.DrawRect(rotated, color.WithAlpha(0.3f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,16 +22,26 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<TetheredComponent, ComponentStartup>(OnTetheredStartup);
|
SubscribeLocalEvent<TetheredComponent, ComponentStartup>(OnTetheredStartup);
|
||||||
SubscribeLocalEvent<TetheredComponent, ComponentShutdown>(OnTetheredShutdown);
|
SubscribeLocalEvent<TetheredComponent, ComponentShutdown>(OnTetheredShutdown);
|
||||||
|
SubscribeLocalEvent<TetherGunComponent, AfterAutoHandleStateEvent>(OnAfterState);
|
||||||
|
SubscribeLocalEvent<ForceGunComponent, AfterAutoHandleStateEvent>(OnAfterState);
|
||||||
_overlay.AddOverlay(new TetherGunOverlay(EntityManager));
|
_overlay.AddOverlay(new TetherGunOverlay(EntityManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAfterState(EntityUid uid, BaseForceGunComponent component, ref AfterAutoHandleStateEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<SpriteComponent>(component.Tethered, out var sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
sprite.Color = component.LineColor;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Shutdown();
|
||||||
_overlay.RemoveOverlay<TetherGunOverlay>();
|
_overlay.RemoveOverlay<TetherGunOverlay>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanTether(EntityUid uid, TetherGunComponent component, EntityUid target, EntityUid? user)
|
protected override bool CanTether(EntityUid uid, BaseForceGunComponent component, EntityUid target, EntityUid? user)
|
||||||
{
|
{
|
||||||
// Need powercells predicted sadly :<
|
// Need powercells predicted sadly :<
|
||||||
return false;
|
return false;
|
||||||
@@ -88,9 +98,18 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
|||||||
private void OnTetheredStartup(EntityUid uid, TetheredComponent component, ComponentStartup args)
|
private void OnTetheredStartup(EntityUid uid, TetheredComponent component, ComponentStartup args)
|
||||||
{
|
{
|
||||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sprite.Color = Color.Orange;
|
if (TryComp<ForceGunComponent>(component.Tetherer, out var force))
|
||||||
|
{
|
||||||
|
sprite.Color = force.LineColor;
|
||||||
|
}
|
||||||
|
else if (TryComp<TetherGunComponent>(component.Tetherer, out var tether))
|
||||||
|
{
|
||||||
|
sprite.Color = tether.LineColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTetheredShutdown(EntityUid uid, TetheredComponent component, ComponentShutdown args)
|
private void OnTetheredShutdown(EntityUid uid, TetheredComponent component, ComponentShutdown args)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Content.Server.PowerCell;
|
using Content.Server.PowerCell;
|
||||||
using Content.Shared.PowerCell.Components;
|
|
||||||
using Content.Shared.Weapons.Misc;
|
using Content.Shared.Weapons.Misc;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
|
|
||||||
@@ -13,14 +12,15 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<TetherGunComponent, PowerCellSlotEmptyEvent>(OnGunEmpty);
|
SubscribeLocalEvent<TetherGunComponent, PowerCellSlotEmptyEvent>(OnGunEmpty);
|
||||||
|
SubscribeLocalEvent<ForceGunComponent, PowerCellSlotEmptyEvent>(OnGunEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGunEmpty(EntityUid uid, TetherGunComponent component, ref PowerCellSlotEmptyEvent args)
|
private void OnGunEmpty(EntityUid uid, BaseForceGunComponent component, ref PowerCellSlotEmptyEvent args)
|
||||||
{
|
{
|
||||||
StopTether(uid, component);
|
StopTether(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanTether(EntityUid uid, TetherGunComponent component, EntityUid target, EntityUid? user)
|
protected override bool CanTether(EntityUid uid, BaseForceGunComponent component, EntityUid target, EntityUid? user)
|
||||||
{
|
{
|
||||||
if (!base.CanTether(uid, component, target, user))
|
if (!base.CanTether(uid, component, target, user))
|
||||||
return false;
|
return false;
|
||||||
@@ -31,16 +31,16 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void StartTether(EntityUid gunUid, TetherGunComponent component, EntityUid target, EntityUid? user,
|
protected override void StartTether(EntityUid gunUid, BaseForceGunComponent component, EntityUid target, EntityUid? user,
|
||||||
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
|
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
|
||||||
{
|
{
|
||||||
base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
|
base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
|
||||||
_cell.SetPowerCellDrawEnabled(gunUid, true);
|
_cell.SetPowerCellDrawEnabled(gunUid, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void StopTether(EntityUid gunUid, TetherGunComponent component, bool transfer = false)
|
protected override void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
|
||||||
{
|
{
|
||||||
base.StopTether(gunUid, component, transfer);
|
base.StopTether(gunUid, component, land, transfer);
|
||||||
_cell.SetPowerCellDrawEnabled(gunUid, false);
|
_cell.SetPowerCellDrawEnabled(gunUid, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,12 @@ public abstract class SharedEmitSoundSystem : EntitySystem
|
|||||||
|
|
||||||
private void OnEmitSoundOnLand(EntityUid uid, BaseEmitSoundComponent component, ref LandEvent args)
|
private void OnEmitSoundOnLand(EntityUid uid, BaseEmitSoundComponent component, ref LandEvent args)
|
||||||
{
|
{
|
||||||
if (!TryComp<TransformComponent>(uid, out var xform) ||
|
if (!args.PlaySound ||
|
||||||
|
!TryComp<TransformComponent>(uid, out var xform) ||
|
||||||
!_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
!_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tile = grid.GetTileRef(xform.Coordinates);
|
var tile = grid.GetTileRef(xform.Coordinates);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Content.Shared.Throwing
|
|||||||
/// Raised when an entity that was thrown lands. This occurs before they stop moving and is when their tile-friction is reapplied.
|
/// Raised when an entity that was thrown lands. This occurs before they stop moving and is when their tile-friction is reapplied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct LandEvent(EntityUid? User);
|
public readonly record struct LandEvent(EntityUid? User, bool PlaySound);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised when a thrown entity is no longer moving.
|
/// Raised when a thrown entity is no longer moving.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Content.Shared.Interaction;
|
|||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
using Content.Shared.Projectiles;
|
using Content.Shared.Projectiles;
|
||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Physics.Systems;
|
using Robust.Shared.Physics.Systems;
|
||||||
@@ -25,9 +26,27 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly ThrownItemSystem _thrownSystem = default!;
|
[Dependency] private readonly ThrownItemSystem _thrownSystem = default!;
|
||||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||||
|
|
||||||
|
public void TryThrow(
|
||||||
|
EntityUid uid,
|
||||||
|
EntityCoordinates coordinates,
|
||||||
|
float strength = 1.0f,
|
||||||
|
EntityUid? user = null,
|
||||||
|
float pushbackRatio = PushbackDefault,
|
||||||
|
bool playSound = true)
|
||||||
|
{
|
||||||
|
var thrownPos = Transform(uid).MapPosition;
|
||||||
|
var mapPos = coordinates.ToMap(EntityManager, _transform);
|
||||||
|
|
||||||
|
if (mapPos.MapId != thrownPos.MapId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, playSound);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to throw the entity if it has a physics component, otherwise does nothing.
|
/// Tries to throw the entity if it has a physics component, otherwise does nothing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -39,7 +58,8 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
Vector2 direction,
|
Vector2 direction,
|
||||||
float strength = 1.0f,
|
float strength = 1.0f,
|
||||||
EntityUid? user = null,
|
EntityUid? user = null,
|
||||||
float pushbackRatio = PushbackDefault)
|
float pushbackRatio = PushbackDefault,
|
||||||
|
bool playSound = true)
|
||||||
{
|
{
|
||||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
if (!physicsQuery.TryGetComponent(uid, out var physics))
|
if (!physicsQuery.TryGetComponent(uid, out var physics))
|
||||||
@@ -57,7 +77,8 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
tagQuery,
|
tagQuery,
|
||||||
strength,
|
strength,
|
||||||
user,
|
user,
|
||||||
pushbackRatio);
|
pushbackRatio,
|
||||||
|
playSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -75,7 +96,8 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
EntityQuery<TagComponent> tagQuery,
|
EntityQuery<TagComponent> tagQuery,
|
||||||
float strength = 1.0f,
|
float strength = 1.0f,
|
||||||
EntityUid? user = null,
|
EntityUid? user = null,
|
||||||
float pushbackRatio = PushbackDefault)
|
float pushbackRatio = PushbackDefault,
|
||||||
|
bool playSound = true)
|
||||||
{
|
{
|
||||||
if (strength <= 0 || direction == Vector2.Infinity || direction == Vector2.NaN || direction == Vector2.Zero)
|
if (strength <= 0 || direction == Vector2.Infinity || direction == Vector2.NaN || direction == Vector2.Zero)
|
||||||
return;
|
return;
|
||||||
@@ -105,11 +127,11 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
_physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
|
_physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
|
||||||
|
|
||||||
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
|
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
|
||||||
var time = (direction / strength).Length;
|
var time = direction.Length / strength;
|
||||||
|
|
||||||
if (time < FlyTime)
|
if (time < FlyTime)
|
||||||
{
|
{
|
||||||
_thrownSystem.LandComponent(uid, comp, physics);
|
_thrownSystem.LandComponent(uid, comp, physics, playSound);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -120,7 +142,7 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
if (physics.Deleted)
|
if (physics.Deleted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_thrownSystem.LandComponent(uid, comp, physics);
|
_thrownSystem.LandComponent(uid, comp, physics, playSound);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ namespace Content.Shared.Throwing
|
|||||||
EntityManager.RemoveComponent<ThrownItemComponent>(uid);
|
EntityManager.RemoveComponent<ThrownItemComponent>(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LandComponent(EntityUid uid, ThrownItemComponent thrownItem, PhysicsComponent physics)
|
public void LandComponent(EntityUid uid, ThrownItemComponent thrownItem, PhysicsComponent physics, bool playSound)
|
||||||
{
|
{
|
||||||
_physics.SetBodyStatus(physics, BodyStatus.OnGround);
|
_physics.SetBodyStatus(physics, BodyStatus.OnGround);
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ namespace Content.Shared.Throwing
|
|||||||
_adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(landing):entity} thrown by {ToPrettyString(thrownItem.Thrower.Value):thrower} landed.");
|
_adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(landing):entity} thrown by {ToPrettyString(thrownItem.Thrower.Value):thrower} landed.");
|
||||||
|
|
||||||
_broadphase.RegenerateContacts(physics);
|
_broadphase.RegenerateContacts(physics);
|
||||||
var landEvent = new LandEvent(thrownItem.Thrower);
|
var landEvent = new LandEvent(thrownItem.Thrower, playSound);
|
||||||
RaiseLocalEvent(landing, ref landEvent);
|
RaiseLocalEvent(landing, ref landEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
56
Content.Shared/Weapons/Misc/BaseForceGunComponent.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
|
namespace Content.Shared.Weapons.Misc;
|
||||||
|
|
||||||
|
public abstract class BaseForceGunComponent : Component
|
||||||
|
{
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("lineColor"), AutoNetworkedField]
|
||||||
|
public Color LineColor = Color.Orange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entity the tethered target has a joint to.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("tetherEntity"), AutoNetworkedField]
|
||||||
|
public EntityUid? TetherEntity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entity currently tethered.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("tethered"), AutoNetworkedField]
|
||||||
|
public virtual EntityUid? Tethered { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can the tethergun unanchor entities.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("canUnanchor"), AutoNetworkedField]
|
||||||
|
public bool CanUnanchor = false;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("canTetherAlive"), AutoNetworkedField]
|
||||||
|
public bool CanTetherAlive = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max force between the tether entity and the tethered target.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("maxForce"), AutoNetworkedField]
|
||||||
|
public float MaxForce = 200f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("frequency"), AutoNetworkedField]
|
||||||
|
public float Frequency = 10f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("dampingRatio"), AutoNetworkedField]
|
||||||
|
public float DampingRatio = 2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum amount of mass a tethered entity can have.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("massLimit"), AutoNetworkedField]
|
||||||
|
public float MassLimit = 100f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("sound"), AutoNetworkedField]
|
||||||
|
public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/weoweo.ogg")
|
||||||
|
{
|
||||||
|
Params = AudioParams.Default.WithLoop(true).WithVolume(-8f),
|
||||||
|
};
|
||||||
|
|
||||||
|
public IPlayingAudioStream? Stream;
|
||||||
|
}
|
||||||
29
Content.Shared/Weapons/Misc/ForceGunComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Weapons.Misc;
|
||||||
|
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||||
|
public sealed partial class ForceGunComponent : BaseForceGunComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum distance to throw entities.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("throwDistance"), AutoNetworkedField]
|
||||||
|
public float ThrowDistance = 15f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("throwForce"), AutoNetworkedField]
|
||||||
|
public float ThrowForce = 30f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entity currently tethered.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("tethered"), AutoNetworkedField]
|
||||||
|
public override EntityUid? Tethered { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("soundLaunch")]
|
||||||
|
public SoundSpecifier? LaunchSound = new SoundPathSpecifier("/Audio/Weapons/soup.ogg")
|
||||||
|
{
|
||||||
|
Params = AudioParams.Default.WithVolume(5f),
|
||||||
|
};
|
||||||
|
}
|
||||||
54
Content.Shared/Weapons/Misc/SharedTetherGunSystem.Force.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Shared.Weapons.Misc;
|
||||||
|
|
||||||
|
public abstract partial class SharedTetherGunSystem
|
||||||
|
{
|
||||||
|
private void InitializeForce()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<ForceGunComponent, AfterInteractEvent>(OnForceRanged);
|
||||||
|
SubscribeLocalEvent<ForceGunComponent, ActivateInWorldEvent>(OnForceActivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnForceActivate(EntityUid uid, ForceGunComponent component, ActivateInWorldEvent args)
|
||||||
|
{
|
||||||
|
StopTether(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnForceRanged(EntityUid uid, ForceGunComponent component, AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (IsTethered(component))
|
||||||
|
{
|
||||||
|
if (!args.ClickLocation.TryDistance(EntityManager, TransformSystem, Transform(uid).Coordinates,
|
||||||
|
out var distance) ||
|
||||||
|
distance > component.ThrowDistance)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// URGH, soon
|
||||||
|
// Need auto states to be nicer + powercelldraw to be nicer
|
||||||
|
if (!_netManager.IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Launch
|
||||||
|
var tethered = component.Tethered;
|
||||||
|
StopTether(uid, component, land: false);
|
||||||
|
_throwing.TryThrow(tethered!.Value, args.ClickLocation, component.ThrowForce, playSound: false);
|
||||||
|
|
||||||
|
_audio.PlayPredicted(component.LaunchSound, uid, null);
|
||||||
|
}
|
||||||
|
else if (args.Target != null)
|
||||||
|
{
|
||||||
|
// Pickup
|
||||||
|
if (TryTether(uid, args.Target.Value, args.User, component))
|
||||||
|
TransformSystem.SetCoordinates(component.TetherEntity!.Value, new EntityCoordinates(uid, new Vector2(0.0f, -0.8f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTethered(ForceGunComponent component)
|
||||||
|
{
|
||||||
|
return component.Tethered != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ using Content.Shared.Mobs.Systems;
|
|||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
using Content.Shared.Throwing;
|
using Content.Shared.Throwing;
|
||||||
using Content.Shared.Toggleable;
|
using Content.Shared.Toggleable;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
@@ -16,16 +17,18 @@ using Robust.Shared.Serialization;
|
|||||||
|
|
||||||
namespace Content.Shared.Weapons.Misc;
|
namespace Content.Shared.Weapons.Misc;
|
||||||
|
|
||||||
public abstract class SharedTetherGunSystem : EntitySystem
|
public abstract partial class SharedTetherGunSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly INetManager _netManager = default!;
|
[Dependency] private readonly INetManager _netManager = default!;
|
||||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mob = default!;
|
[Dependency] private readonly MobStateSystem _mob = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
||||||
|
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||||
[Dependency] private readonly ThrownItemSystem _thrown = default!;
|
[Dependency] private readonly ThrownItemSystem _thrown = default!;
|
||||||
|
|
||||||
private const string TetherJoint = "tether";
|
private const string TetherJoint = "tether";
|
||||||
@@ -42,6 +45,8 @@ public abstract class SharedTetherGunSystem : EntitySystem
|
|||||||
|
|
||||||
SubscribeLocalEvent<TetheredComponent, BuckleAttemptEvent>(OnTetheredBuckleAttempt);
|
SubscribeLocalEvent<TetheredComponent, BuckleAttemptEvent>(OnTetheredBuckleAttempt);
|
||||||
SubscribeLocalEvent<TetheredComponent, UpdateCanMoveEvent>(OnTetheredUpdateCanMove);
|
SubscribeLocalEvent<TetheredComponent, UpdateCanMoveEvent>(OnTetheredUpdateCanMove);
|
||||||
|
|
||||||
|
InitializeForce();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTetheredBuckleAttempt(EntityUid uid, TetheredComponent component, ref BuckleAttemptEvent args)
|
private void OnTetheredBuckleAttempt(EntityUid uid, TetheredComponent component, ref BuckleAttemptEvent args)
|
||||||
@@ -115,7 +120,8 @@ public abstract class SharedTetherGunSystem : EntitySystem
|
|||||||
gun = null;
|
gun = null;
|
||||||
|
|
||||||
if (!TryComp<HandsComponent>(user, out var hands) ||
|
if (!TryComp<HandsComponent>(user, out var hands) ||
|
||||||
!TryComp(hands.ActiveHandEntity, out gun))
|
!TryComp(hands.ActiveHandEntity, out gun) ||
|
||||||
|
_container.IsEntityInContainer(user))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -129,18 +135,19 @@ public abstract class SharedTetherGunSystem : EntitySystem
|
|||||||
StopTether(uid, component);
|
StopTether(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TryTether(EntityUid gun, EntityUid target, EntityUid? user, TetherGunComponent? component = null)
|
public bool TryTether(EntityUid gun, EntityUid target, EntityUid? user, BaseForceGunComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(gun, ref component))
|
if (!Resolve(gun, ref component))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
if (!CanTether(gun, component, target, user))
|
if (!CanTether(gun, component, target, user))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
StartTether(gun, component, target, user);
|
StartTether(gun, component, target, user);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool CanTether(EntityUid uid, TetherGunComponent component, EntityUid target, EntityUid? user)
|
protected virtual bool CanTether(EntityUid uid, BaseForceGunComponent component, EntityUid target, EntityUid? user)
|
||||||
{
|
{
|
||||||
if (HasComp<TetheredComponent>(target) || !TryComp<PhysicsComponent>(target, out var physics))
|
if (HasComp<TetheredComponent>(target) || !TryComp<PhysicsComponent>(target, out var physics))
|
||||||
return false;
|
return false;
|
||||||
@@ -160,7 +167,7 @@ public abstract class SharedTetherGunSystem : EntitySystem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void StartTether(EntityUid gunUid, TetherGunComponent component, EntityUid target, EntityUid? user,
|
protected virtual void StartTether(EntityUid gunUid, BaseForceGunComponent component, EntityUid target, EntityUid? user,
|
||||||
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
|
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(target, ref targetPhysics, ref targetXform))
|
if (!Resolve(target, ref targetPhysics, ref targetXform))
|
||||||
@@ -212,7 +219,7 @@ public abstract class SharedTetherGunSystem : EntitySystem
|
|||||||
Dirty(component);
|
Dirty(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void StopTether(EntityUid gunUid, TetherGunComponent component, bool transfer = false)
|
protected virtual void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
|
||||||
{
|
{
|
||||||
if (component.Tethered == null)
|
if (component.Tethered == null)
|
||||||
return;
|
return;
|
||||||
@@ -228,9 +235,12 @@ public abstract class SharedTetherGunSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp<PhysicsComponent>(component.Tethered, out var targetPhysics))
|
if (TryComp<PhysicsComponent>(component.Tethered, out var targetPhysics))
|
||||||
|
{
|
||||||
|
if (land)
|
||||||
{
|
{
|
||||||
var thrown = EnsureComp<ThrownItemComponent>(component.Tethered.Value);
|
var thrown = EnsureComp<ThrownItemComponent>(component.Tethered.Value);
|
||||||
_thrown.LandComponent(component.Tethered.Value, thrown, targetPhysics);
|
_thrown.LandComponent(component.Tethered.Value, thrown, targetPhysics, true);
|
||||||
|
}
|
||||||
|
|
||||||
_physics.SetBodyStatus(targetPhysics, BodyStatus.OnGround);
|
_physics.SetBodyStatus(targetPhysics, BodyStatus.OnGround);
|
||||||
_physics.SetSleepingAllowed(component.Tethered.Value, targetPhysics, true);
|
_physics.SetSleepingAllowed(component.Tethered.Value, targetPhysics, true);
|
||||||
|
|||||||
@@ -1,58 +1,16 @@
|
|||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Shared.Weapons.Misc;
|
namespace Content.Shared.Weapons.Misc;
|
||||||
|
|
||||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||||
public sealed partial class TetherGunComponent : Component
|
public sealed partial class TetherGunComponent : BaseForceGunComponent
|
||||||
{
|
{
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxDistance"), AutoNetworkedField]
|
[ViewVariables(VVAccess.ReadWrite), DataField("maxDistance"), AutoNetworkedField]
|
||||||
public float MaxDistance = 10f;
|
public float MaxDistance = 10f;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The entity the tethered target has a joint to.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("tetherEntity"), AutoNetworkedField]
|
|
||||||
public EntityUid? TetherEntity;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity currently tethered.
|
/// The entity currently tethered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("tethered"), AutoNetworkedField]
|
[ViewVariables(VVAccess.ReadWrite), DataField("tethered"), AutoNetworkedField]
|
||||||
public EntityUid? Tethered;
|
public override EntityUid? Tethered { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Can the tethergun unanchor entities.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("canUnanchor"), AutoNetworkedField]
|
|
||||||
public bool CanUnanchor = false;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("canTetherAlive"), AutoNetworkedField]
|
|
||||||
public bool CanTetherAlive = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Max force between the tether entity and the tethered target.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxForce"), AutoNetworkedField]
|
|
||||||
public float MaxForce = 200f;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("frequency"), AutoNetworkedField]
|
|
||||||
public float Frequency = 10f;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("dampingRatio"), AutoNetworkedField]
|
|
||||||
public float DampingRatio = 2f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum amount of mass a tethered entity can have.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("massLimit"), AutoNetworkedField]
|
|
||||||
public float MassLimit = 100f;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("sound"), AutoNetworkedField]
|
|
||||||
public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/weoweo.ogg")
|
|
||||||
{
|
|
||||||
Params = AudioParams.Default.WithLoop(true).WithVolume(-8f),
|
|
||||||
};
|
|
||||||
|
|
||||||
public IPlayingAudioStream? Stream;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,3 +17,7 @@ pierce.ogg taken from: https://github.com/tgstation/tgstation/commit/106cd26fc00
|
|||||||
- files: ["weoweo.ogg"]
|
- files: ["weoweo.ogg"]
|
||||||
license: "SONNISS #GAMEAUDIOGDC BUNDLE LICENSING"
|
license: "SONNISS #GAMEAUDIOGDC BUNDLE LICENSING"
|
||||||
copyright: "Taken from Sonniss.com - GDC 2023 - Systematic Sound - TonalElements Obscurum - Dark Drones"
|
copyright: "Taken from Sonniss.com - GDC 2023 - Systematic Sound - TonalElements Obscurum - Dark Drones"
|
||||||
|
|
||||||
|
- files: ["soup.ogg"]
|
||||||
|
license: "SONNISS #GAMEAUDIOGDC BUNDLE LICENSING"
|
||||||
|
copyright: "Taken from Sonniss.com - GDC 2023 - 344 AUdio - Epic Impacts Vol. 1"
|
||||||
BIN
Resources/Audio/Weapons/soup.ogg
Normal file
@@ -186,6 +186,49 @@
|
|||||||
True: { visible: true }
|
True: { visible: true }
|
||||||
False: { visible: false }
|
False: { visible: false }
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: force gun
|
||||||
|
parent:
|
||||||
|
- BaseItem
|
||||||
|
- PowerCellSlotMediumItem
|
||||||
|
id: WeaponForceGun
|
||||||
|
description: Manipulates gravity around objects to fling them at high velocities.
|
||||||
|
components:
|
||||||
|
- type: ForceGun
|
||||||
|
frequency: 15
|
||||||
|
dampingRatio: 4
|
||||||
|
massLimit: 50
|
||||||
|
lineColor: "#18a2d5"
|
||||||
|
soundLaunch:
|
||||||
|
path: /Audio/Weapons/soup.ogg
|
||||||
|
params:
|
||||||
|
volume: 2
|
||||||
|
- type: PowerCellDraw
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Weapons/Guns/Launchers/force_gun.rsi
|
||||||
|
layers:
|
||||||
|
- state: base
|
||||||
|
- state: base-unshaded
|
||||||
|
map: [ "unshaded" ]
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
|
- type: ToggleableLightVisuals
|
||||||
|
spriteLayer: unshaded
|
||||||
|
inhandVisuals:
|
||||||
|
left:
|
||||||
|
- state: inhand-left-unshaded
|
||||||
|
shader: unshaded
|
||||||
|
right:
|
||||||
|
- state: inhand-right-unshaded
|
||||||
|
shader: unshaded
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.TetherVisualsStatus.Key:
|
||||||
|
unshaded:
|
||||||
|
True: { visible: true }
|
||||||
|
False: { visible: false }
|
||||||
|
|
||||||
# Admeme
|
# Admeme
|
||||||
- type: entity
|
- type: entity
|
||||||
name: tether gun
|
name: tether gun
|
||||||
@@ -226,6 +269,48 @@
|
|||||||
True: { visible: true }
|
True: { visible: true }
|
||||||
False: { visible: false }
|
False: { visible: false }
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: force gun
|
||||||
|
parent: BaseItem
|
||||||
|
id: WeaponForceGunAdmin
|
||||||
|
suffix: Admin
|
||||||
|
description: Manipulates gravity around objects to fling them at high velocities.
|
||||||
|
components:
|
||||||
|
- type: ForceGun
|
||||||
|
canTetherAlive: true
|
||||||
|
canUnanchor: true
|
||||||
|
maxForce: 10000
|
||||||
|
massLimit: 10000
|
||||||
|
frequency: 15
|
||||||
|
dampingRatio: 4
|
||||||
|
throwForce: 50
|
||||||
|
throwDistance: 100
|
||||||
|
lineColor: "#18a2d5"
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Weapons/Guns/Launchers/force_gun.rsi
|
||||||
|
layers:
|
||||||
|
- state: base
|
||||||
|
- state: base-unshaded
|
||||||
|
map: [ "unshaded" ]
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
|
- type: ToggleableLightVisuals
|
||||||
|
spriteLayer: unshaded
|
||||||
|
inhandVisuals:
|
||||||
|
left:
|
||||||
|
- state: inhand-left-unshaded
|
||||||
|
shader: unshaded
|
||||||
|
right:
|
||||||
|
- state: inhand-right-unshaded
|
||||||
|
shader: unshaded
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.TetherVisualsStatus.Key:
|
||||||
|
unshaded:
|
||||||
|
True: { visible: true }
|
||||||
|
False: { visible: false }
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: meteor launcher
|
name: meteor launcher
|
||||||
parent: WeaponLauncherMultipleRocket
|
parent: WeaponLauncherMultipleRocket
|
||||||
|
|||||||
@@ -239,6 +239,7 @@
|
|||||||
- ClothingShoesBootsMag
|
- ClothingShoesBootsMag
|
||||||
- NodeScanner
|
- NodeScanner
|
||||||
- HolofanProjector
|
- HolofanProjector
|
||||||
|
- WeaponForceGun
|
||||||
- WeaponTetherGun
|
- WeaponTetherGun
|
||||||
- ClothingBackpackHolding
|
- ClothingBackpackHolding
|
||||||
- ClothingBackpackSatchelHolding
|
- ClothingBackpackSatchelHolding
|
||||||
|
|||||||
@@ -100,6 +100,15 @@
|
|||||||
Plastic: 750
|
Plastic: 750
|
||||||
Plasma: 1000
|
Plasma: 1000
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: WeaponForceGun
|
||||||
|
result: WeaponForceGun
|
||||||
|
completetime: 5
|
||||||
|
materials:
|
||||||
|
Steel: 500
|
||||||
|
Glass: 400
|
||||||
|
Silver: 200
|
||||||
|
|
||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: WeaponTetherGun
|
id: WeaponTetherGun
|
||||||
result: WeaponTetherGun
|
result: WeaponTetherGun
|
||||||
|
|||||||
@@ -171,4 +171,5 @@
|
|||||||
tier: 3
|
tier: 3
|
||||||
cost: 10000
|
cost: 10000
|
||||||
recipeUnlocks:
|
recipeUnlocks:
|
||||||
|
- WeaponForceGun
|
||||||
- WeaponTetherGun
|
- WeaponTetherGun
|
||||||
|
|||||||
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Sprited by discord Kheprep#7153, modified by metalgearsloth",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base-unshaded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left-unshaded",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right-unshaded",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.7 KiB |