Gun Code Modularization + Magazine Fed Shotgun (#751)
* Adds shotgun YAML files * Adds shotgu ammo and magazine enum value, modularizes some projectile weapon methods * Fixes guns consuming two bullets on fire * Finishes shotgun behavior in Projectile Weapon Components * Gun and ammo modularization * Updates BallisticMagazineWeapons to be compatible with new AmmoWeapon * Fixes shotgun spread angles, Fixes shogun YAML * Gun documentation and ViewVariable specifications * Removes todo messsage in gun code * Default gun evenspread fix * AmmoComponent Name fix * Fixes Component name * Projectile refactor code review fixes
This commit is contained in:
@@ -1,32 +1,106 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
/// <summary>
|
||||
/// Passes information about the projectiles to be fired by AmmoWeapons
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BallisticBulletComponent : Component
|
||||
{
|
||||
public override string Name => "BallisticBullet";
|
||||
|
||||
private BallisticCaliber _caliber;
|
||||
private string _projectileType;
|
||||
private bool _spent;
|
||||
/// <summary>
|
||||
/// Cartridge calibre, restricts what AmmoWeapons this ammo can be fired from.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public BallisticCaliber Caliber { get => _caliber; set => _caliber = value; }
|
||||
|
||||
public string ProjectileType => _projectileType;
|
||||
public BallisticCaliber Caliber => _caliber;
|
||||
public bool Spent
|
||||
{
|
||||
get => _spent;
|
||||
set => _spent = value;
|
||||
}
|
||||
private string _projectileID;
|
||||
/// <summary>
|
||||
/// YAML ID of the projectiles to be created when firing this ammo.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string ProjectileID { get => _projectileID; set => _projectileID = value; }
|
||||
|
||||
private int _projectilesFired;
|
||||
/// <summary>
|
||||
/// How many copies of the projectile are shot.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int ProjectilesFired { get => _projectilesFired; set => _projectilesFired = value; }
|
||||
|
||||
private float _spreadStdDev_Ammo;
|
||||
/// <summary>
|
||||
/// Weapons that fire projectiles from ammo types.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SpreadStdDev_Ammo { get => _spreadStdDev_Ammo; set => _spreadStdDev_Ammo = value; }
|
||||
|
||||
private float _evenSpreadAngle_Ammo;
|
||||
/// <summary>
|
||||
/// Arc angle of shotgun pellet spreads, only used if multiple projectiles are being fired.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float EvenSpreadAngle_Ammo { get => _evenSpreadAngle_Ammo; set => _evenSpreadAngle_Ammo = value; }
|
||||
|
||||
private float _velocity_Ammo;
|
||||
/// <summary>
|
||||
/// Adds additional velocity to the projectile, on top of what it already has.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Velocity_Ammo { get => _velocity_Ammo; set => _velocity_Ammo = value; }
|
||||
|
||||
private bool _spent;
|
||||
/// <summary>
|
||||
/// If the ammo cartridge has been shot already.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Spent { get => _spent; set => _spent = value; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||
serializer.DataField(ref _projectileType, "projectile", null);
|
||||
serializer.DataField(ref _projectileID, "projectile", null);
|
||||
serializer.DataField(ref _spent, "spent", false);
|
||||
serializer.DataField(ref _projectilesFired, "projectilesfired", 1);
|
||||
serializer.DataField(ref _spreadStdDev_Ammo, "ammostddev", 0);
|
||||
serializer.DataField(ref _evenSpreadAngle_Ammo, "ammospread", 0);
|
||||
serializer.DataField(ref _velocity_Ammo, "ammovelocity", 0);
|
||||
}
|
||||
}
|
||||
public enum BallisticCaliber
|
||||
{
|
||||
Unspecified = 0,
|
||||
// .32
|
||||
A32,
|
||||
// .357
|
||||
A357,
|
||||
// .44
|
||||
A44,
|
||||
// .45mm
|
||||
A45mm,
|
||||
// .50 cal
|
||||
A50,
|
||||
// 5.56mm
|
||||
A556mm,
|
||||
// 6.5mm
|
||||
A65mm,
|
||||
// 7.62mm
|
||||
A762mm,
|
||||
// 9mm
|
||||
A9mm,
|
||||
// 10mm
|
||||
A10mm,
|
||||
// 20mm
|
||||
A20mm,
|
||||
// 24mm
|
||||
A24mm,
|
||||
// 12g
|
||||
A12g,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
}
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
|
||||
OnAmmoCountChanged?.Invoke();
|
||||
_appearance.SetData(BallisticMagazineVisuals.AmmoCapacity, Capacity);
|
||||
@@ -99,7 +99,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
|
||||
_bulletContainer.Insert(bullet);
|
||||
_loadedBullets.Push(bullet);
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
OnAmmoCountChanged?.Invoke();
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
_bulletContainer.Remove(bullet);
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
OnAmmoCountChanged?.Invoke();
|
||||
return bullet;
|
||||
}
|
||||
@@ -224,7 +224,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
return false;
|
||||
}
|
||||
|
||||
private void _updateAppearance()
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearance.SetData(BallisticMagazineVisuals.AmmoLeft, CountLoaded);
|
||||
}
|
||||
@@ -278,5 +278,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
|
||||
// 24mm
|
||||
A24mm,
|
||||
|
||||
// 12g
|
||||
A12g,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
/// <summary>
|
||||
/// Guns that have a magazine.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BallisticMagazineWeaponComponent : BallisticWeaponComponent, IUse, IAttackBy, IMapInit
|
||||
{
|
||||
@@ -46,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
[ViewVariables] private bool _autoEjectMagazine;
|
||||
[ViewVariables] private AppearanceComponent _appearance;
|
||||
|
||||
private static readonly Direction[] _randomBulletDirs =
|
||||
private static readonly Direction[] RandomBulletDirs =
|
||||
{
|
||||
Direction.North,
|
||||
Direction.East,
|
||||
@@ -54,12 +57,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
Direction.West
|
||||
};
|
||||
|
||||
protected override int ChamberCount => 1;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _magazineTypes, "magazines",
|
||||
new List<BallisticMagazineType> {BallisticMagazineType.Unspecified});
|
||||
serializer.DataField(ref _defaultMagazine, "default_magazine", null);
|
||||
@@ -72,7 +72,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
||||
}
|
||||
|
||||
@@ -80,57 +79,44 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
_magazineSlot = ContainerManagerComponent.Ensure<ContainerSlot>("ballistic_gun_magazine", Owner);
|
||||
|
||||
if (Magazine != null)
|
||||
{
|
||||
// Already got magazine from loading a container.
|
||||
Magazine.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged += _magazineAmmoCountChanged;
|
||||
Magazine.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged += MagazineAmmoCountChanged;
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public bool InsertMagazine(IEntity magazine, bool playSound = true)
|
||||
{
|
||||
if (!magazine.TryGetComponent(out BallisticMagazineComponent component))
|
||||
if (!magazine.TryGetComponent(out BallisticMagazineComponent magazinetype))
|
||||
{
|
||||
throw new ArgumentException("Not a magazine", nameof(magazine));
|
||||
}
|
||||
|
||||
if (!MagazineTypes.Contains(component.MagazineType))
|
||||
if (!MagazineTypes.Contains(magazinetype.MagazineType))
|
||||
{
|
||||
throw new ArgumentException("Wrong magazine type", nameof(magazine));
|
||||
}
|
||||
|
||||
if (component.Caliber != Caliber)
|
||||
{
|
||||
throw new ArgumentException("Wrong caliber", nameof(magazine));
|
||||
}
|
||||
|
||||
if (!_magazineSlot.Insert(magazine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_magInSound != null)
|
||||
{
|
||||
Owner.GetComponent<SoundComponent>().Play(_magInSound);
|
||||
}
|
||||
|
||||
component.OnAmmoCountChanged += _magazineAmmoCountChanged;
|
||||
magazinetype.OnAmmoCountChanged += MagazineAmmoCountChanged;
|
||||
if (GetChambered(0) == null)
|
||||
{
|
||||
// No bullet in chamber, load one from magazine.
|
||||
var bullet = component.TakeBullet();
|
||||
var bullet = magazinetype.TakeBullet();
|
||||
if (bullet != null)
|
||||
{
|
||||
LoadIntoChamber(0, bullet);
|
||||
}
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
Dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -142,7 +128,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_magazineSlot.Remove(entity))
|
||||
{
|
||||
entity.Transform.GridPosition = Owner.Transform.GridPosition;
|
||||
@@ -150,14 +135,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
Owner.GetComponent<SoundComponent>().Play(_magOutSound, AudioParams.Default.WithVolume(20));
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
Dirty();
|
||||
entity.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged -= _magazineAmmoCountChanged;
|
||||
entity.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged -= MagazineAmmoCountChanged;
|
||||
return true;
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
Dirty();
|
||||
return false;
|
||||
}
|
||||
@@ -168,9 +151,13 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
|
||||
// Eject chambered bullet.
|
||||
var entity = RemoveFromChamber(chamber);
|
||||
if (entity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var offsetPos = (CalcBulletOffset(), CalcBulletOffset());
|
||||
entity.Transform.GridPosition = Owner.Transform.GridPosition.Offset(offsetPos);
|
||||
entity.Transform.LocalRotation = _bulletDropRandom.Pick(_randomBulletDirs).ToAngle();
|
||||
entity.Transform.LocalRotation = _bulletDropRandom.Pick(RandomBulletDirs).ToAngle();
|
||||
var effect = $"/Audio/Guns/Casings/casingfall{_bulletDropRandom.Next(1, 4)}.ogg";
|
||||
Owner.GetComponent<SoundComponent>().Play(effect, AudioParams.Default.WithVolume(-3));
|
||||
|
||||
@@ -188,9 +175,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
DoAutoEject();
|
||||
}
|
||||
}
|
||||
|
||||
Dirty();
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private float CalcBulletOffset()
|
||||
@@ -206,7 +192,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
Owner.GetComponent<SoundComponent>().Play(_autoEjectSound, AudioParams.Default.WithVolume(-5));
|
||||
}
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
@@ -221,7 +206,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, "No magazine");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -231,29 +215,26 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Magazine != null)
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, "Already got a magazine.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MagazineTypes.Contains(component.MagazineType) || component.Caliber != Caliber)
|
||||
if (!MagazineTypes.Contains(component.MagazineType))
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, "Magazine doesn't fit.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return InsertMagazine(eventArgs.AttackWith);
|
||||
}
|
||||
|
||||
private void _magazineAmmoCountChanged()
|
||||
private void MagazineAmmoCountChanged()
|
||||
{
|
||||
Dirty();
|
||||
_updateAppearance();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private void _updateAppearance()
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (Magazine != null)
|
||||
{
|
||||
@@ -273,15 +254,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
var chambered = GetChambered(0) != null;
|
||||
|
||||
(int, int)? count = null;
|
||||
|
||||
if (Magazine != null)
|
||||
{
|
||||
var magComponent = Magazine.GetComponent<BallisticMagazineComponent>();
|
||||
count = (magComponent.CountLoaded, magComponent.Capacity);
|
||||
}
|
||||
|
||||
return new BallisticMagazineWeaponComponentState(chambered, count);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles firing projectiles from a contained <see cref="BallisticBulletComponent" />.
|
||||
/// </summary>
|
||||
public abstract class BallisticWeaponComponent : BaseProjectileWeaponComponent
|
||||
{
|
||||
private Chamber[] _chambers;
|
||||
|
||||
/// <summary>
|
||||
/// Number of chambers created during initialization.
|
||||
/// </summary>
|
||||
private int _chamberCount;
|
||||
|
||||
[ViewVariables]
|
||||
private BallisticCaliber _caliber ;
|
||||
/// <summary>
|
||||
/// What type of ammo this gun can fire.
|
||||
/// </summary>
|
||||
|
||||
private string _soundGunEmpty;
|
||||
/// <summary>
|
||||
/// Sound played when trying to shoot if there is no ammo available.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string SoundGunEmpty { get => _soundGunEmpty; set => _soundGunEmpty = value; }
|
||||
|
||||
private float _spreadStdDevGun;
|
||||
/// <summary>
|
||||
/// Increases the standard deviation of the ammo being fired.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SpreadStdDevGun { get => _spreadStdDevGun; set => _spreadStdDevGun = value; }
|
||||
|
||||
private float _evenSpreadAngleGun;
|
||||
/// <summary>
|
||||
/// Increases the evenspread of the ammo being fired.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float EvenSpreadAngleGun { get => _evenSpreadAngleGun; set => _evenSpreadAngleGun = value; }
|
||||
|
||||
private float _velocityGun;
|
||||
/// <summary>
|
||||
/// Increases the velocity of the ammo being fired.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float VelocityGun { get => _velocityGun; set => _velocityGun = value; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _soundGunEmpty, "sound_empty", "/Audio/Guns/Empty/empty.ogg");
|
||||
serializer.DataField(ref _spreadStdDevGun, "spreadstddev", 0);
|
||||
serializer.DataField(ref _evenSpreadAngleGun, "evenspread", 0);
|
||||
serializer.DataField(ref _velocityGun, "gunvelocity", 0);
|
||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||
serializer.DataField(ref _chamberCount, "chambers", 1);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.GetComponent<RangedWeaponComponent>().FireHandler = TryShoot;
|
||||
_chambers = new Chamber[_chamberCount];
|
||||
for (var i = 0; i < _chambers.Length; i++)
|
||||
{
|
||||
var container = ContainerManagerComponent.Ensure<ContainerSlot>($"ballistics_chamber_{i}", Owner);
|
||||
_chambers[i] = new Chamber(container);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires projectiles based on loaded ammo from entity to a coordinate.
|
||||
/// </summary>
|
||||
protected void TryShoot(IEntity user, GridCoordinates clickLocation)
|
||||
{
|
||||
var ammo = GetChambered(FirstChamber)?.GetComponent<BallisticBulletComponent>();
|
||||
CycleChamberedBullet(FirstChamber);
|
||||
if (ammo == null || ammo?.Spent == true || ammo?.Caliber != _caliber)
|
||||
{
|
||||
PlayEmptySound();
|
||||
return;
|
||||
}
|
||||
ammo.Spent = true;
|
||||
var total_stdev = _spreadStdDevGun + ammo.SpreadStdDev_Ammo;
|
||||
var final_evenspread = _evenSpreadAngleGun + ammo.EvenSpreadAngle_Ammo;
|
||||
var final_velocity = _velocityGun + ammo.Velocity_Ammo;
|
||||
FireAtCoord(user, clickLocation, ammo.ProjectileID, total_stdev, ammo.ProjectilesFired, final_evenspread, final_velocity);
|
||||
}
|
||||
|
||||
protected IEntity GetChambered(int chamber) => _chambers[chamber].Slot.ContainedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the next ammo casing into the chamber.
|
||||
/// </summary>
|
||||
protected virtual void CycleChamberedBullet(int chamber) { }
|
||||
|
||||
public IEntity RemoveFromChamber(int chamber)
|
||||
{
|
||||
var c = _chambers[chamber];
|
||||
var loaded = c.Slot.ContainedEntity;
|
||||
if (loaded != null)
|
||||
{
|
||||
c.Slot.Remove(loaded);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
protected bool LoadIntoChamber(int chamber, IEntity bullet)
|
||||
{
|
||||
if (!bullet.TryGetComponent(out BallisticBulletComponent component))
|
||||
{
|
||||
throw new ArgumentException("entity isn't a bullet.", nameof(bullet));
|
||||
}
|
||||
if (component.Caliber != _caliber)
|
||||
{
|
||||
throw new ArgumentException("entity is of the wrong caliber.", nameof(bullet));
|
||||
}
|
||||
if (GetChambered(chamber) != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_chambers[chamber].Slot.Insert(bullet);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PlayEmptySound() => Owner.GetComponent<SoundComponent>().Play(_soundGunEmpty);
|
||||
|
||||
protected sealed class Chamber
|
||||
{
|
||||
public Chamber(ContainerSlot slot)
|
||||
{
|
||||
Slot = slot;
|
||||
}
|
||||
|
||||
public ContainerSlot Slot { get; }
|
||||
}
|
||||
|
||||
private const int FirstChamber = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public abstract class BallisticWeaponComponent : ProjectileWeaponComponent
|
||||
{
|
||||
private BallisticCaliber _caliber;
|
||||
private Chamber[] _chambers;
|
||||
|
||||
public BallisticCaliber Caliber => _caliber;
|
||||
protected abstract int ChamberCount { get; }
|
||||
|
||||
private string _soundGunEmpty;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||
serializer.DataField(ref _soundGunEmpty, "sound_empty", null);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_chambers = new Chamber[ChamberCount];
|
||||
for (var i = 0; i < _chambers.Length; i++)
|
||||
{
|
||||
var container = ContainerManagerComponent.Ensure<ContainerSlot>($"ballistics_chamber_{i}", Owner);
|
||||
_chambers[i] = new Chamber(container);
|
||||
}
|
||||
}
|
||||
|
||||
public IEntity GetChambered(int chamber) => _chambers[chamber].Slot.ContainedEntity;
|
||||
|
||||
public bool LoadIntoChamber(int chamber, IEntity bullet)
|
||||
{
|
||||
if (!bullet.TryGetComponent(out BallisticBulletComponent component))
|
||||
{
|
||||
throw new ArgumentException("entity isn't a bullet.", nameof(bullet));
|
||||
}
|
||||
|
||||
if (component.Caliber != Caliber)
|
||||
{
|
||||
throw new ArgumentException("entity is of the wrong caliber.", nameof(bullet));
|
||||
}
|
||||
|
||||
if (GetChambered(chamber) != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_chambers[chamber].Slot.Insert(bullet);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected sealed override IEntity GetFiredProjectile()
|
||||
{
|
||||
void PlayEmpty()
|
||||
{
|
||||
if (_soundGunEmpty != null)
|
||||
{
|
||||
Owner.GetComponent<SoundComponent>().Play(_soundGunEmpty);
|
||||
}
|
||||
}
|
||||
var chambered = GetChambered(0);
|
||||
if (chambered != null)
|
||||
{
|
||||
var bullet = chambered.GetComponent<BallisticBulletComponent>();
|
||||
if (bullet.Spent)
|
||||
{
|
||||
PlayEmpty();
|
||||
return null;
|
||||
}
|
||||
|
||||
var projectile = Owner.EntityManager.SpawnEntity(bullet.ProjectileType, Owner.Transform.GridPosition);
|
||||
bullet.Spent = true;
|
||||
|
||||
CycleChamberedBullet(0);
|
||||
|
||||
// Load a new bullet into the chamber from magazine.
|
||||
return projectile;
|
||||
}
|
||||
|
||||
PlayEmpty();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual void CycleChamberedBullet(int chamber)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IEntity RemoveFromChamber(int chamber)
|
||||
{
|
||||
var c = _chambers[chamber];
|
||||
var loaded = c.Slot.ContainedEntity;
|
||||
if (loaded != null)
|
||||
{
|
||||
c.Slot.Remove(loaded);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private sealed class Chamber
|
||||
{
|
||||
public Chamber(ContainerSlot slot)
|
||||
{
|
||||
Slot = slot;
|
||||
}
|
||||
|
||||
public ContainerSlot Slot { get; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Projectiles;
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods to shoot projectiles.
|
||||
/// </summary>
|
||||
public abstract class BaseProjectileWeaponComponent : Component
|
||||
{
|
||||
private string _soundGunshot;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string SoundGunshot
|
||||
{ get => _soundGunshot; set => _soundGunshot = value; }
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private IRobustRandom _spreadRandom;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _soundGunshot, "sound_gunshot", "/Audio/Guns/Gunshots/smg.ogg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires projectile from an entity at a coordinate.
|
||||
/// </summary>
|
||||
protected void FireAtCoord(IEntity source, GridCoordinates coord, string projectileType, double spreadStdDev, int projectilesFired = 1, double evenSpreadAngle = 0, float velocity = 0)
|
||||
{
|
||||
var angle = GetAngleFromClickLocation(source, coord);
|
||||
FireAtAngle(source, angle, projectileType, spreadStdDev, projectilesFired, evenSpreadAngle, velocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires projectile in the direction of an angle.
|
||||
/// </summary>
|
||||
protected void FireAtAngle(IEntity source, Angle angle, string projectileType = null, double spreadStdDev = 0, int projectilesFired = 1, double evenSpreadAngle = 0, float velocity = 0)
|
||||
{
|
||||
List<Angle> sprayanglechange = null;
|
||||
if (evenSpreadAngle != 0 & projectilesFired > 1)
|
||||
{
|
||||
sprayanglechange = Linspace(-evenSpreadAngle/2, evenSpreadAngle/2, projectilesFired);
|
||||
}
|
||||
for (var i = 1; i <= projectilesFired; i++)
|
||||
{
|
||||
Angle finalangle = angle + Angle.FromDegrees(_spreadRandom.NextGaussian(0, spreadStdDev)) + (sprayanglechange != null ? sprayanglechange[i - 1] : 0);
|
||||
var projectile = Owner.EntityManager.SpawnEntity(projectileType, Owner.Transform.GridPosition);
|
||||
projectile.Transform.GridPosition = source.Transform.GridPosition; //move projectile to entity it is being fired from
|
||||
projectile.GetComponent<ProjectileComponent>().IgnoreEntity(source);//make sure it doesn't hit the source entity
|
||||
var finalvelocity = projectile.GetComponent<ProjectileComponent>().Velocity + velocity;//add velocity
|
||||
projectile.GetComponent<PhysicsComponent>().LinearVelocity = finalangle.ToVec() * finalvelocity;//Rotate the bullets sprite to the correct direction
|
||||
projectile.Transform.LocalRotation = finalangle.Theta;
|
||||
}
|
||||
PlayFireSound();
|
||||
if (source.TryGetComponent(out CameraRecoilComponent recoil))
|
||||
{
|
||||
var recoilVec = angle.ToVec() * -0.15f;
|
||||
recoil.Kick(recoilVec);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayFireSound() => Owner.GetComponent<SoundComponent>().Play(_soundGunshot);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the angle from an entity to a coordinate.
|
||||
/// </summary>
|
||||
protected Angle GetAngleFromClickLocation(IEntity source, GridCoordinates clickLocation) => new Angle(clickLocation.Position - source.Transform.GridPosition.Position);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of numbers that form a set of equal intervals between the start and end value. Used to calculate shotgun spread angles.
|
||||
/// </summary>
|
||||
protected List<Angle> Linspace(double start, double end, int intervals)
|
||||
{
|
||||
var linspace = new List<Angle> { };
|
||||
for (var i = 0; i <= intervals - 1; i++)
|
||||
{
|
||||
linspace.Add(Angle.FromDegrees(start + (end - start) * i / (intervals - 1)));
|
||||
}
|
||||
return linspace;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Projectiles;
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public abstract class ProjectileWeaponComponent : Component
|
||||
{
|
||||
private float _spreadStdDev = 3;
|
||||
private bool _spread = true;
|
||||
private string _soundGunshot;
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private IRobustRandom _spreadRandom;
|
||||
#pragma warning restore 649
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Spread
|
||||
{
|
||||
get => _spread;
|
||||
set => _spread = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SpreadStdDev
|
||||
{
|
||||
get => _spreadStdDev;
|
||||
set => _spreadStdDev = value;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var rangedWeapon = Owner.GetComponent<RangedWeaponComponent>();
|
||||
rangedWeapon.FireHandler = Fire;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _spread, "spread", true);
|
||||
serializer.DataField(ref _spreadStdDev, "spreadstddev", 3);
|
||||
serializer.DataField(ref _soundGunshot, "sound_gunshot", "/Audio/Guns/Gunshots/smg.ogg");
|
||||
}
|
||||
|
||||
private void Fire(IEntity user, GridCoordinates clickLocation)
|
||||
{
|
||||
var projectile = GetFiredProjectile();
|
||||
if (projectile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userPosition = user.Transform.GridPosition; //Remember world positions are ephemeral and can only be used instantaneously
|
||||
var angle = new Angle(clickLocation.Position - userPosition.Position);
|
||||
|
||||
if (user.TryGetComponent(out CameraRecoilComponent recoil))
|
||||
{
|
||||
var recoilVec = angle.ToVec() * -0.15f;
|
||||
recoil.Kick(recoilVec);
|
||||
}
|
||||
|
||||
if (Spread)
|
||||
{
|
||||
angle += Angle.FromDegrees(_spreadRandom.NextGaussian(0, SpreadStdDev));
|
||||
}
|
||||
|
||||
projectile.Transform.GridPosition = userPosition;
|
||||
|
||||
//Give it the velocity we fire from this weapon, and make sure it doesn't shoot our character
|
||||
projectile.GetComponent<ProjectileComponent>().IgnoreEntity(user);
|
||||
var velocity = projectile.GetComponent<ProjectileComponent>().Velocity;
|
||||
|
||||
//Give it the velocity this weapon gives to things it fires from itself
|
||||
projectile.GetComponent<PhysicsComponent>().LinearVelocity = angle.ToVec() * velocity;
|
||||
|
||||
//Rotate the bullets sprite to the correct direction, from north facing I guess
|
||||
projectile.Transform.LocalRotation = angle.Theta;
|
||||
|
||||
// Sound!
|
||||
Owner.GetComponent<SoundComponent>().Play(_soundGunshot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get a projectile for firing. If null, nothing will be fired.
|
||||
/// </summary>
|
||||
protected abstract IEntity GetFiredProjectile();
|
||||
}
|
||||
|
||||
public enum BallisticCaliber
|
||||
{
|
||||
Unspecified = 0,
|
||||
// .32
|
||||
A32,
|
||||
// .357
|
||||
A357,
|
||||
// .44
|
||||
A44,
|
||||
// .45mm
|
||||
A45mm,
|
||||
// .50 cal
|
||||
A50,
|
||||
// 5.56mm
|
||||
A556mm,
|
||||
// 6.5mm
|
||||
A65mm,
|
||||
// 7.62mm
|
||||
A762mm,
|
||||
// 9mm
|
||||
A9mm,
|
||||
// 10mm
|
||||
A10mm,
|
||||
// 20mm
|
||||
A20mm,
|
||||
// 24mm
|
||||
A24mm,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Empty mags
|
||||
- type: entity
|
||||
id: magazine_12g_empty
|
||||
name: "12g magazine - empty"
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: BallisticMagazine
|
||||
caliber: A12g
|
||||
magazine: A12g
|
||||
capacity: 8
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
#Magazines
|
||||
- type: entity
|
||||
id: magazine_12g_shotgun
|
||||
name: "12g Magazine"
|
||||
parent: magazine_12g_empty
|
||||
components:
|
||||
- type: BallisticMagazine
|
||||
fill: ammo_casing_12g
|
||||
caliber: A12g
|
||||
magazine: A12g
|
||||
capacity: 20
|
||||
- type: Icon
|
||||
sprite: Objects/Guns/Ammunition/Magazine/10mm/12mml.rsi
|
||||
state: 12mml-1
|
||||
- type: Sprite
|
||||
sprite: Objects/Guns/Ammunition/Magazine/10mm/12mml.rsi
|
||||
state: 12mml-1
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: BallisticMagazineVisualizer2D
|
||||
base_state: 12mml
|
||||
steps: 2
|
||||
|
||||
# Casings
|
||||
- type: entity
|
||||
id: ammo_casing_12g
|
||||
name: "12g casing"
|
||||
parent: BaseItem
|
||||
components:
|
||||
- type: BallisticBullet
|
||||
caliber: A12g
|
||||
projectile: pellet_12g
|
||||
projectilesfired : 6
|
||||
- type: Sprite
|
||||
sprite: Objects/Guns/Ammunition/ammo_casing.rsi
|
||||
state: s-casing
|
||||
drawdepth: FloorObjects
|
||||
- type: Icon
|
||||
sprite: Objects/Guns/Ammunition/ammo_casing.rsi
|
||||
state: s-casing
|
||||
@@ -0,0 +1,34 @@
|
||||
# Empty boxes
|
||||
- type: entity
|
||||
id: box_12g_empty
|
||||
name: "12g box - empty"
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: AmmoBox
|
||||
caliber: A12g
|
||||
capacity: 30
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
# Ammo boxes
|
||||
- type: entity
|
||||
id: box_12g
|
||||
name: "12g box"
|
||||
parent: box_12g_empty
|
||||
components:
|
||||
- type: AmmoBox
|
||||
fill: ammo_casing_12g
|
||||
caliber: A12g
|
||||
capacity: 30
|
||||
- type: Icon
|
||||
sprite: Objects/Guns/Ammunition/Boxes/10mm/box10mm.rsi
|
||||
state: box10mm-1
|
||||
- type: Sprite
|
||||
sprite: Objects/Guns/Ammunition/Boxes/10mm/box10mm.rsi
|
||||
state: box10mm-1
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: BallisticMagazineVisualizer2D
|
||||
base_state: box10mm
|
||||
steps: 2
|
||||
@@ -0,0 +1,10 @@
|
||||
- type: entity
|
||||
id: pellet_12g
|
||||
name: 12g Pellet
|
||||
parent: bullet_base
|
||||
description: If you can see this you're dead!
|
||||
components:
|
||||
- type: Projectile
|
||||
velocity: 20
|
||||
damages:
|
||||
Brute: 10
|
||||
47
Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml
Normal file
47
Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
- type: entity
|
||||
name: Shotgun
|
||||
parent: BaseItem
|
||||
id: BaseShotgun
|
||||
description: A rooty tooty point and shooty.
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sound
|
||||
- type: RangedWeapon
|
||||
automatic: false
|
||||
firerate: 20
|
||||
- type: BallisticMagazineWeapon
|
||||
caliber: A12g
|
||||
evenspread : 40
|
||||
magazines:
|
||||
- A12g
|
||||
default_magazine: magazine_12g_shotgun
|
||||
auto_eject_magazine: false
|
||||
sound_auto_eject: /Audio/Guns/EmptyAlarm/smg_empty_alarm.ogg
|
||||
sound_magazine_in: /Audio/Guns/MagIn/smg_magin.ogg
|
||||
sound_magazine_out: /Audio/Guns/MagOut/smg_magout.ogg
|
||||
sound_empty: /Audio/Guns/Empty/empty.ogg
|
||||
sound_gunshot: /Audio/Guns/Gunshots/smg.ogg
|
||||
|
||||
- type: entity
|
||||
name: Magazine Fed Shotgun
|
||||
parent: BaseShotgun
|
||||
id: MagazineFedShotgun
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
sprite: Objects/Guns/SMGs/c20r.rsi
|
||||
state: c20r-5
|
||||
- type: Icon
|
||||
sprite: Objects/Guns/SMGs/c20r.rsi
|
||||
state: c20r-5
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: BallisticMagazineWeaponVisualizer2D
|
||||
base_state: c20r
|
||||
steps: 6
|
||||
- type: Item
|
||||
Size: 24
|
||||
sprite: Objects/Guns/SMGs/c20r.rsi
|
||||
- type: Item
|
||||
Size: 24
|
||||
sprite: Objects/Guns/SMGs/wt550.rsi
|
||||
Reference in New Issue
Block a user