]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Adds force-gun (#16561)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Fri, 19 May 2023 07:10:31 +0000 (17:10 +1000)
committerGitHub <noreply@github.com>
Fri, 19 May 2023 07:10:31 +0000 (17:10 +1000)
26 files changed:
Content.Client/Weapons/Misc/TetherGunOverlay.cs
Content.Client/Weapons/Misc/TetherGunSystem.cs
Content.Server/Weapons/Misc/TetherGunSystem.cs
Content.Shared/Sound/SharedEmitSoundSystem.cs
Content.Shared/Throwing/LandEvent.cs
Content.Shared/Throwing/ThrowingSystem.cs
Content.Shared/Throwing/ThrownItemSystem.cs
Content.Shared/Weapons/Misc/BaseForceGunComponent.cs [new file with mode: 0644]
Content.Shared/Weapons/Misc/ForceGunComponent.cs [new file with mode: 0644]
Content.Shared/Weapons/Misc/SharedTetherGunSystem.Force.cs [new file with mode: 0644]
Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs
Content.Shared/Weapons/Misc/TetherGunComponent.cs
Resources/Audio/Weapons/licenses.txt
Resources/Audio/Weapons/soup.ogg [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/Recipes/Lathes/devices.yml
Resources/Prototypes/Research/experimental.yml
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base-unshaded.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left-unshaded.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right-unshaded.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Guns/Launchers/tether_gun.rsi/inhand-right.png

index 215589c38dd5ee2c1a6643efcb4f7de3c2bf3245..142d9da8232f4c78b6b458abae9f89cb572cd877 100644 (file)
@@ -19,6 +19,8 @@ public sealed class TetherGunOverlay : Overlay
     {
         var query = _entManager.EntityQueryEnumerator<TetheredComponent>();
         var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
+        var tetherQuery = _entManager.GetEntityQuery<TetherGunComponent>();
+        var forceQuery = _entManager.GetEntityQuery<ForceGunComponent>();
         var worldHandle = args.WorldHandle;
         var xformSystem = _entManager.System<SharedTransformSystem>();
 
@@ -46,7 +48,18 @@ public sealed class TetherGunOverlay : Overlay
             var box = new Box2(-Width, -length, Width, length);
             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));
         }
     }
 }
index 1219fe135754baa411163d214d214e4ca122e05b..9ceda9291d4f5c462b3eff07517758f979882211 100644 (file)
@@ -22,16 +22,26 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
         base.Initialize();
         SubscribeLocalEvent<TetheredComponent, ComponentStartup>(OnTetheredStartup);
         SubscribeLocalEvent<TetheredComponent, ComponentShutdown>(OnTetheredShutdown);
+        SubscribeLocalEvent<TetherGunComponent, AfterAutoHandleStateEvent>(OnAfterState);
+        SubscribeLocalEvent<ForceGunComponent, AfterAutoHandleStateEvent>(OnAfterState);
         _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()
     {
         base.Shutdown();
         _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 :<
         return false;
@@ -88,9 +98,18 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
     private void OnTetheredStartup(EntityUid uid, TetheredComponent component, ComponentStartup args)
     {
         if (!TryComp<SpriteComponent>(uid, out var sprite))
+        {
             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)
index 3b62e3f8ca4998c0a2663b7799ef926b2976f1d0..44d0c49e3f8b4f582b7cc1b7c358692429cb318d 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.PowerCell;
-using Content.Shared.PowerCell.Components;
 using Content.Shared.Weapons.Misc;
 using Robust.Shared.Physics.Components;
 
@@ -13,14 +12,15 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
     {
         base.Initialize();
         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);
     }
 
-    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))
             return false;
@@ -31,16 +31,16 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
         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)
     {
         base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
         _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);
     }
 }
index 3dc1cb793f33b99cf29b3a0638d0dface81754c8..a2fa038101f0ae74fbe9b704e25eb21f63f62271 100644 (file)
@@ -51,9 +51,12 @@ public abstract class SharedEmitSoundSystem : EntitySystem
 
     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))
+        {
             return;
+        }
 
         var tile = grid.GetTileRef(xform.Coordinates);
 
index def0278816c9e3b8c7f0e993eb2d3bd33b755517..1bf1f20371608078cb1387721ae7821ef5f5ca35 100644 (file)
@@ -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.
     /// </summary>
     [ByRefEvent]
-    public readonly record struct LandEvent(EntityUid? User);
+    public readonly record struct LandEvent(EntityUid? User, bool PlaySound);
 
     /// <summary>
     /// Raised when a thrown entity is no longer moving.
index 44cfdc33fb71f4b079000478a6e7842fa5dab46b..4368dac6b7d72140ca964af19a1bc6c5086ae0c8 100644 (file)
@@ -3,6 +3,7 @@ using Content.Shared.Interaction;
 using Content.Shared.Movement.Components;
 using Content.Shared.Projectiles;
 using Content.Shared.Tag;
+using Robust.Shared.Map;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Systems;
@@ -25,9 +26,27 @@ public sealed class ThrowingSystem : EntitySystem
     [Dependency] private readonly SharedGravitySystem _gravity = default!;
     [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
     [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
     [Dependency] private readonly ThrownItemSystem _thrownSystem = 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>
     ///     Tries to throw the entity if it has a physics component, otherwise does nothing.
     /// </summary>
@@ -39,7 +58,8 @@ public sealed class ThrowingSystem : EntitySystem
         Vector2 direction,
         float strength = 1.0f,
         EntityUid? user = null,
-        float pushbackRatio = PushbackDefault)
+        float pushbackRatio = PushbackDefault,
+        bool playSound = true)
     {
         var physicsQuery = GetEntityQuery<PhysicsComponent>();
         if (!physicsQuery.TryGetComponent(uid, out var physics))
@@ -57,7 +77,8 @@ public sealed class ThrowingSystem : EntitySystem
             tagQuery,
             strength,
             user,
-            pushbackRatio);
+            pushbackRatio,
+            playSound);
     }
 
     /// <summary>
@@ -75,7 +96,8 @@ public sealed class ThrowingSystem : EntitySystem
         EntityQuery<TagComponent> tagQuery,
         float strength = 1.0f,
         EntityUid? user = null,
-        float pushbackRatio = PushbackDefault)
+        float pushbackRatio = PushbackDefault,
+        bool playSound = true)
     {
         if (strength <= 0 || direction == Vector2.Infinity || direction == Vector2.NaN || direction == Vector2.Zero)
             return;
@@ -105,11 +127,11 @@ public sealed class ThrowingSystem : EntitySystem
         _physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
 
         // 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)
         {
-            _thrownSystem.LandComponent(uid, comp, physics);
+            _thrownSystem.LandComponent(uid, comp, physics, playSound);
         }
         else
         {
@@ -120,7 +142,7 @@ public sealed class ThrowingSystem : EntitySystem
                 if (physics.Deleted)
                     return;
 
-                _thrownSystem.LandComponent(uid, comp, physics);
+                _thrownSystem.LandComponent(uid, comp, physics, playSound);
             });
         }
 
index 6467ea2d77540443f70b5205001aa95bccd32154..39e357a32c2778751975aaa90894a20d9dc89905 100644 (file)
@@ -116,7 +116,7 @@ namespace Content.Shared.Throwing
             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);
 
@@ -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.");
 
             _broadphase.RegenerateContacts(physics);
-            var landEvent = new LandEvent(thrownItem.Thrower);
+            var landEvent = new LandEvent(thrownItem.Thrower, playSound);
             RaiseLocalEvent(landing, ref landEvent);
         }
 
diff --git a/Content.Shared/Weapons/Misc/BaseForceGunComponent.cs b/Content.Shared/Weapons/Misc/BaseForceGunComponent.cs
new file mode 100644 (file)
index 0000000..45c81fa
--- /dev/null
@@ -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;
+}
diff --git a/Content.Shared/Weapons/Misc/ForceGunComponent.cs b/Content.Shared/Weapons/Misc/ForceGunComponent.cs
new file mode 100644 (file)
index 0000000..f3aca0d
--- /dev/null
@@ -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),
+    };
+}
diff --git a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.Force.cs b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.Force.cs
new file mode 100644 (file)
index 0000000..9b5665f
--- /dev/null
@@ -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;
+    }
+}
index f6d65c862de513abcb6a9a2001223a3b6b1c30a1..1e4e8902240ccb4d485bdbc9b2e74f6f937c1eaa 100644 (file)
@@ -7,6 +7,7 @@ using Content.Shared.Mobs.Systems;
 using Content.Shared.Movement.Events;
 using Content.Shared.Throwing;
 using Content.Shared.Toggleable;
+using Robust.Shared.Containers;
 using Robust.Shared.Map;
 using Robust.Shared.Network;
 using Robust.Shared.Physics;
@@ -16,16 +17,18 @@ using Robust.Shared.Serialization;
 
 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 ActionBlockerSystem _blocker = 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 SharedContainerSystem _container = default!;
     [Dependency] private   readonly SharedJointSystem _joints = default!;
     [Dependency] private   readonly SharedPhysicsSystem _physics = default!;
     [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
+    [Dependency] private   readonly ThrowingSystem _throwing = default!;
     [Dependency] private   readonly ThrownItemSystem _thrown = default!;
 
     private const string TetherJoint = "tether";
@@ -42,6 +45,8 @@ public abstract class SharedTetherGunSystem : EntitySystem
 
         SubscribeLocalEvent<TetheredComponent, BuckleAttemptEvent>(OnTetheredBuckleAttempt);
         SubscribeLocalEvent<TetheredComponent, UpdateCanMoveEvent>(OnTetheredUpdateCanMove);
+
+        InitializeForce();
     }
 
     private void OnTetheredBuckleAttempt(EntityUid uid, TetheredComponent component, ref BuckleAttemptEvent args)
@@ -115,7 +120,8 @@ public abstract class SharedTetherGunSystem : EntitySystem
         gun = null;
 
         if (!TryComp<HandsComponent>(user, out var hands) ||
-            !TryComp(hands.ActiveHandEntity, out gun))
+            !TryComp(hands.ActiveHandEntity, out gun) ||
+            _container.IsEntityInContainer(user))
         {
             return false;
         }
@@ -129,18 +135,19 @@ public abstract class SharedTetherGunSystem : EntitySystem
         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))
-            return;
+            return false;
 
         if (!CanTether(gun, component, target, user))
-            return;
+            return false;
 
         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))
             return false;
@@ -160,7 +167,7 @@ public abstract class SharedTetherGunSystem : EntitySystem
         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)
     {
         if (!Resolve(target, ref targetPhysics, ref targetXform))
@@ -212,7 +219,7 @@ public abstract class SharedTetherGunSystem : EntitySystem
         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)
             return;
@@ -229,8 +236,11 @@ public abstract class SharedTetherGunSystem : EntitySystem
 
         if (TryComp<PhysicsComponent>(component.Tethered, out var targetPhysics))
         {
-            var thrown = EnsureComp<ThrownItemComponent>(component.Tethered.Value);
-            _thrown.LandComponent(component.Tethered.Value, thrown, targetPhysics);
+            if (land)
+            {
+                var thrown = EnsureComp<ThrownItemComponent>(component.Tethered.Value);
+                _thrown.LandComponent(component.Tethered.Value, thrown, targetPhysics, true);
+            }
 
             _physics.SetBodyStatus(targetPhysics, BodyStatus.OnGround);
             _physics.SetSleepingAllowed(component.Tethered.Value, targetPhysics, true);
index 7feaf7f518355788768908bafd3747134adc7e53..06e7da6df40cb39fdedb3c18f64156b95d79fe06 100644 (file)
@@ -1,58 +1,16 @@
-using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 
 namespace Content.Shared.Weapons.Misc;
 
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-public sealed partial class TetherGunComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+public sealed partial class TetherGunComponent : BaseForceGunComponent
 {
     [ViewVariables(VVAccess.ReadWrite), DataField("maxDistance"), AutoNetworkedField]
     public float MaxDistance = 10f;
 
-    /// <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 EntityUid? Tethered;
-
-    /// <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;
+    public override EntityUid? Tethered { get; set; }
 }
index 7b077ffc7114c5f8b27c33c653e279742454709d..4dbd19401ad94429a9276fb134bcc39226caa322 100644 (file)
@@ -16,4 +16,8 @@ pierce.ogg taken from: https://github.com/tgstation/tgstation/commit/106cd26fc00
 
 - files: ["weoweo.ogg"]
   license: "SONNISS #GAMEAUDIOGDC BUNDLE LICENSING"
-  copyright: "Taken from Sonniss.com - GDC 2023 - Systematic Sound - TonalElements Obscurum - Dark Drones"
\ No newline at end of file
+  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"
\ No newline at end of file
diff --git a/Resources/Audio/Weapons/soup.ogg b/Resources/Audio/Weapons/soup.ogg
new file mode 100644 (file)
index 0000000..b3dfe57
Binary files /dev/null and b/Resources/Audio/Weapons/soup.ogg differ
index 4356efa474f2d04191b9ac8291b355629bc424c0..0c0940b85e62c7fd7f2c7fb89f65aa64b5aa2fa7 100644 (file)
             True: { visible: true }
             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
 - type: entity
   name: tether gun
             True: { visible: true }
             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
   name: meteor launcher
   parent: WeaponLauncherMultipleRocket
index 888e37bd864ec618f46cc9f99bec614545f83694..7c58249ef0673bcda148b86689cbb3b3efab89fd 100644 (file)
       - ClothingShoesBootsMag
       - NodeScanner
       - HolofanProjector
+      - WeaponForceGun
       - WeaponTetherGun
       - ClothingBackpackHolding
       - ClothingBackpackSatchelHolding
index b0cb852142eb2ba394758b8839f6fa1f4ac9c57c..cf9954b48b3344d97bf2ab6e2aa6d1ab1d112045 100644 (file)
     Plastic: 750
     Plasma: 1000
 
+- type: latheRecipe
+  id: WeaponForceGun
+  result: WeaponForceGun
+  completetime: 5
+  materials:
+    Steel: 500
+    Glass: 400
+    Silver: 200
+
 - type: latheRecipe
   id: WeaponTetherGun
   result: WeaponTetherGun
index 5bc6c623c76d3de272af8cb74297ceb7d7d25277..c2e96fd0a77aebd86a7a37fca7ec4ded11bce354 100644 (file)
   tier: 3
   cost: 10000
   recipeUnlocks:
+    - WeaponForceGun
     - WeaponTetherGun
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base-unshaded.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base-unshaded.png
new file mode 100644 (file)
index 0000000..99a23fa
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base-unshaded.png differ
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base.png
new file mode 100644 (file)
index 0000000..5ccd70e
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/base.png differ
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left-unshaded.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left-unshaded.png
new file mode 100644 (file)
index 0000000..9396c9d
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left-unshaded.png differ
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left.png
new file mode 100644 (file)
index 0000000..5d314fb
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right-unshaded.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right-unshaded.png
new file mode 100644 (file)
index 0000000..e494ca3
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right-unshaded.png differ
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right.png
new file mode 100644 (file)
index 0000000..519a10b
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Launchers/force_gun.rsi/meta.json
new file mode 100644 (file)
index 0000000..66511d0
--- /dev/null
@@ -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
+        }
+    ]
+}
\ No newline at end of file
index 913d55638007ba109bda3464fd4209b5d334ee9d..519a10b6345a499f8b6a0dd1951c035f2d44c617 100644 (file)
Binary files a/Resources/Textures/Objects/Weapons/Guns/Launchers/tether_gun.rsi/inhand-right.png and b/Resources/Textures/Objects/Weapons/Guns/Launchers/tether_gun.rsi/inhand-right.png differ