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:
py01
2020-02-29 11:09:41 -06:00
committed by GitHub
parent 4b1fd4cfd1
commit 196950fb7e
11 changed files with 506 additions and 313 deletions

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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,
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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