diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index f41c50fc90..0a7d62970f 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -169,7 +169,8 @@ public sealed partial class GunSystem : SharedGunSystem }); } - public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null) + public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo, + EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null, bool throwItems = false) { // Rather than splitting client / server for every ammo provider it's easier // to just delete the spawned entities. This is for programmer sanity despite the wasted perf. @@ -178,6 +179,16 @@ public sealed partial class GunSystem : SharedGunSystem foreach (var (ent, shootable) in ammo) { + if (throwItems) + { + Recoil(user, direction); + if (ent!.Value.IsClientSide()) + Del(ent.Value); + else + RemComp(ent.Value); + continue; + } + switch (shootable) { case CartridgeAmmoComponent cartridge: diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index b7c3ad609b..cbfc263018 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -63,7 +63,8 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem if (!TryComp(args.EntityUid, out var gas)) return; - if (gas.Air.TotalMoles >= component.GasUsage) + // only accept tanks if it uses gas + if (gas.Air.TotalMoles >= component.GasUsage && component.GasUsage > 0f) return; args.Cancel(); @@ -71,7 +72,9 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem private void OnShoot(EntityUid uid, PneumaticCannonComponent component, ref GunShotEvent args) { - if (GetGas(uid) is not { } gas) + // require a gas tank if it uses gas + var gas = GetGas(uid); + if (gas == null && component.GasUsage > 0f) return; if(TryComp(args.User, out var status) @@ -82,6 +85,10 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem ("cannon", component.Owner)), uid, args.User); } + // ignore gas stuff if the cannon doesn't use any + if (gas == null) + return; + // this should always be possible, as we'll eject the gas tank when it no longer is var environment = _atmos.GetContainingMixture(component.Owner, false, true); var removed = _gasTank.RemoveAir(gas, component.GasUsage); diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index cb67880a00..6b1a154a83 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -62,7 +62,8 @@ public sealed partial class GunSystem : SharedGunSystem args.Price += price * component.UnspawnedCount; } - public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null) + public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo, + EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null, bool throwItems = false) { // Try a clumsy roll // TODO: Who put this here @@ -110,6 +111,21 @@ public sealed partial class GunSystem : SharedGunSystem foreach (var (ent, shootable) in ammo) { + // pneumatic cannon doesn't shoot bullets it just throws them, ignore ammo handling + if (throwItems) + { + if (!HasComp(ent!.Value)) + { + RemComp(ent.Value); + // TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack. + ThrowingSystem.TryThrow(ent.Value, mapDirection, gun.ProjectileSpeed, user); + continue; + } + + ShootProjectile(ent.Value, mapDirection, gunVelocity, user, gun.ProjectileSpeed); + continue; + } + switch (shootable) { // Cartridge shoots something else diff --git a/Content.Shared/PneumaticCannon/PneumaticCannonComponent.cs b/Content.Shared/PneumaticCannon/PneumaticCannonComponent.cs index 11407f1d3f..45f6c077eb 100644 --- a/Content.Shared/PneumaticCannon/PneumaticCannonComponent.cs +++ b/Content.Shared/PneumaticCannon/PneumaticCannonComponent.cs @@ -37,6 +37,12 @@ public sealed class PneumaticCannonComponent : Component /// [DataField("baseProjectileSpeed")] public float BaseProjectileSpeed = 20f; + + /// + /// If true, will throw ammo rather than shoot it. + /// + [DataField("throwItems"), ViewVariables(VVAccess.ReadWrite)] + public bool ThrowItems = true; } /// diff --git a/Content.Shared/PneumaticCannon/SharedPneumaticCannonSystem.cs b/Content.Shared/PneumaticCannon/SharedPneumaticCannonSystem.cs index 7071676b2c..27384f8d55 100644 --- a/Content.Shared/PneumaticCannon/SharedPneumaticCannonSystem.cs +++ b/Content.Shared/PneumaticCannon/SharedPneumaticCannonSystem.cs @@ -20,6 +20,13 @@ public abstract class SharedPneumaticCannonSystem : EntitySystem private void OnAttemptShoot(EntityUid uid, PneumaticCannonComponent component, ref AttemptShootEvent args) { + // if the cannon doesn't need gas then it will always predict firing + if (component.GasUsage == 0f) + return; + + // pneumatic cannon usually doesn't shoot bullets + args.ThrowItems = component.ThrowItems; + // we don't have atmos on shared, so just predict by the existence of a slot item // server will handle auto ejecting/not adding the slot item if it doesnt have enough gas, // so this won't mispredict diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 84f5922d69..4a0a5ec69a 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -238,7 +238,6 @@ public abstract partial class SharedGunSystem : EntitySystem return; } - var curTime = Timing.CurTime; // Need to do this to play the clicking sound for empty automatic weapons @@ -313,7 +312,7 @@ public abstract partial class SharedGunSystem : EntitySystem } // Shoot confirmed - sounds also played here in case it's invalid (e.g. cartridge already spent). - Shoot(gunUid, gun, ev.Ammo, fromCoordinates, toCoordinates.Value, user); + Shoot(gunUid, gun, ev.Ammo, fromCoordinates, toCoordinates.Value, user, throwItems: attemptEv.ThrowItems); var shotEv = new GunShotEvent(user); RaiseLocalEvent(gunUid, ref shotEv); // Projectiles cause impulses especially important in non gravity environments @@ -331,10 +330,11 @@ public abstract partial class SharedGunSystem : EntitySystem EntityUid ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, - EntityUid? user = null) + EntityUid? user = null, + bool throwItems = false) { var shootable = EnsureComp(ammo); - Shoot(gunUid, gun, new List<(EntityUid? Entity, IShootable Shootable)>(1) { (ammo, shootable) }, fromCoordinates, toCoordinates, user); + Shoot(gunUid, gun, new List<(EntityUid? Entity, IShootable Shootable)>(1) { (ammo, shootable) }, fromCoordinates, toCoordinates, user, throwItems); } public abstract void Shoot( @@ -343,7 +343,8 @@ public abstract partial class SharedGunSystem : EntitySystem List<(EntityUid? Entity, IShootable Shootable)> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, - EntityUid? user = null); + EntityUid? user = null, + bool throwItems = false); protected abstract void Popup(string message, EntityUid? uid, EntityUid? user); @@ -438,8 +439,9 @@ public abstract partial class SharedGunSystem : EntitySystem /// /// The user that attempted to fire this gun. /// Set this to true if the shot should be cancelled. +/// Set this to true if the ammo shouldn't actually be fired, just thrown. [ByRefEvent] -public record struct AttemptShootEvent(EntityUid User, bool Cancelled=false); +public record struct AttemptShootEvent(EntityUid User, bool Cancelled = false, bool ThrowItems = false); /// /// Raised directed on the gun after firing. diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml index 01a6ff8db7..d689776d32 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml @@ -99,3 +99,36 @@ containers: storagebase: !type:Container ents: [] + +# shoots bullets instead of throwing them, no other changes +- type: entity + parent: WeaponImprovisedPneumaticCannon + id: WeaponImprovisedPneumaticCannonGun + suffix: Gun + components: + - type: PneumaticCannon + throwItems: false + +# doesn't need gas, extra capacity +- type: entity + parent: WeaponImprovisedPneumaticCannonGun + id: WeaponImprovisedPneumaticCannonAdmeme + suffix: Admeme + components: + - type: Item + size: 9999 + - type: Storage + capacity: 9999 + - type: PneumaticCannon + gasUsage: 0 + throwItems: false + - type: Gun + fireRate: 10 + selectedMode: FullAuto + availableModes: + - SemiAuto + - FullAuto + soundGunshot: + path: /Audio/Effects/thunk.ogg + soundEmpty: + path: /Audio/Items/hiss.ogg