});
}
- 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.
foreach (var (ent, shootable) in ammo)
{
+ if (throwItems)
+ {
+ Recoil(user, direction);
+ if (ent!.Value.IsClientSide())
+ Del(ent.Value);
+ else
+ RemComp<AmmoComponent>(ent.Value);
+ continue;
+ }
+
switch (shootable)
{
case CartridgeAmmoComponent cartridge:
if (!TryComp<GasTankComponent>(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();
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<StatusEffectsComponent>(args.User, out var status)
("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);
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
foreach (var (ent, shootable) in ammo)
{
+ // pneumatic cannon doesn't shoot bullets it just throws them, ignore ammo handling
+ if (throwItems)
+ {
+ if (!HasComp<ProjectileComponent>(ent!.Value))
+ {
+ RemComp<AmmoComponent>(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
/// </summary>
[DataField("baseProjectileSpeed")]
public float BaseProjectileSpeed = 20f;
+
+ /// <summary>
+ /// If true, will throw ammo rather than shoot it.
+ /// </summary>
+ [DataField("throwItems"), ViewVariables(VVAccess.ReadWrite)]
+ public bool ThrowItems = true;
}
/// <summary>
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
return;
}
-
var curTime = Timing.CurTime;
// Need to do this to play the clicking sound for empty automatic weapons
}
// 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
EntityUid ammo,
EntityCoordinates fromCoordinates,
EntityCoordinates toCoordinates,
- EntityUid? user = null)
+ EntityUid? user = null,
+ bool throwItems = false)
{
var shootable = EnsureComp<AmmoComponent>(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(
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);
/// </remarks>
/// <param name="User">The user that attempted to fire this gun.</param>
/// <param name="Cancelled">Set this to true if the shot should be cancelled.</param>
+/// <param name="ThrowItems">Set this to true if the ammo shouldn't actually be fired, just thrown.</param>
[ByRefEvent]
-public record struct AttemptShootEvent(EntityUid User, bool Cancelled=false);
+public record struct AttemptShootEvent(EntityUid User, bool Cancelled = false, bool ThrowItems = false);
/// <summary>
/// Raised directed on the gun after firing.
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