diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticBulletComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticBulletComponent.cs index 93176615a1..5060a28b9d 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticBulletComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticBulletComponent.cs @@ -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 { + /// + /// Passes information about the projectiles to be fired by AmmoWeapons + /// [RegisterComponent] public class BallisticBulletComponent : Component { public override string Name => "BallisticBullet"; private BallisticCaliber _caliber; - private string _projectileType; - private bool _spent; + /// + /// Cartridge calibre, restricts what AmmoWeapons this ammo can be fired from. + /// + [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; + /// + /// YAML ID of the projectiles to be created when firing this ammo. + /// + [ViewVariables(VVAccess.ReadWrite)] + public string ProjectileID { get => _projectileID; set => _projectileID = value; } + + private int _projectilesFired; + /// + /// How many copies of the projectile are shot. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int ProjectilesFired { get => _projectilesFired; set => _projectilesFired = value; } + + private float _spreadStdDev_Ammo; + /// + /// Weapons that fire projectiles from ammo types. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float SpreadStdDev_Ammo { get => _spreadStdDev_Ammo; set => _spreadStdDev_Ammo = value; } + + private float _evenSpreadAngle_Ammo; + /// + /// Arc angle of shotgun pellet spreads, only used if multiple projectiles are being fired. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float EvenSpreadAngle_Ammo { get => _evenSpreadAngle_Ammo; set => _evenSpreadAngle_Ammo = value; } + + private float _velocity_Ammo; + /// + /// Adds additional velocity to the projectile, on top of what it already has. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float Velocity_Ammo { get => _velocity_Ammo; set => _velocity_Ammo = value; } + + private bool _spent; + /// + /// If the ammo cartridge has been shot already. + /// + [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, + } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineComponent.cs index 280628b2bb..0fa594fa6b 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineComponent.cs @@ -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, } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineWeaponComponent.cs index d38bc355e6..ed93dfe541 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticMagazineWeaponComponent.cs @@ -21,6 +21,9 @@ using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile { + /// + /// Guns that have a magazine. + /// [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.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(); } @@ -80,57 +79,44 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile protected override void Startup() { base.Startup(); - _magazineSlot = ContainerManagerComponent.Ensure("ballistic_gun_magazine", Owner); - if (Magazine != null) { // Already got magazine from loading a container. - Magazine.GetComponent().OnAmmoCountChanged += _magazineAmmoCountChanged; + Magazine.GetComponent().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().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().Play(_magOutSound, AudioParams.Default.WithVolume(20)); } - - _updateAppearance(); + UpdateAppearance(); Dirty(); - entity.GetComponent().OnAmmoCountChanged -= _magazineAmmoCountChanged; + entity.GetComponent().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().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().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(); count = (magComponent.CountLoaded, magComponent.Capacity); } - return new BallisticMagazineWeaponComponentState(chambered, count); } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticWeapon.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticWeapon.cs new file mode 100644 index 0000000000..6332e89f20 --- /dev/null +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticWeapon.cs @@ -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 +{ + /// + /// Handles firing projectiles from a contained . + /// + public abstract class BallisticWeaponComponent : BaseProjectileWeaponComponent + { + private Chamber[] _chambers; + + /// + /// Number of chambers created during initialization. + /// + private int _chamberCount; + + [ViewVariables] + private BallisticCaliber _caliber ; + /// + /// What type of ammo this gun can fire. + /// + + private string _soundGunEmpty; + /// + /// Sound played when trying to shoot if there is no ammo available. + /// + [ViewVariables(VVAccess.ReadWrite)] + public string SoundGunEmpty { get => _soundGunEmpty; set => _soundGunEmpty = value; } + + private float _spreadStdDevGun; + /// + /// Increases the standard deviation of the ammo being fired. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float SpreadStdDevGun { get => _spreadStdDevGun; set => _spreadStdDevGun = value; } + + private float _evenSpreadAngleGun; + /// + /// Increases the evenspread of the ammo being fired. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float EvenSpreadAngleGun { get => _evenSpreadAngleGun; set => _evenSpreadAngleGun = value; } + + private float _velocityGun; + /// + /// Increases the velocity of the ammo being fired. + /// + [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().FireHandler = TryShoot; + _chambers = new Chamber[_chamberCount]; + for (var i = 0; i < _chambers.Length; i++) + { + var container = ContainerManagerComponent.Ensure($"ballistics_chamber_{i}", Owner); + _chambers[i] = new Chamber(container); + } + } + + /// + /// Fires projectiles based on loaded ammo from entity to a coordinate. + /// + protected void TryShoot(IEntity user, GridCoordinates clickLocation) + { + var ammo = GetChambered(FirstChamber)?.GetComponent(); + 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; + + /// + /// Loads the next ammo casing into the chamber. + /// + 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().Play(_soundGunEmpty); + + protected sealed class Chamber + { + public Chamber(ContainerSlot slot) + { + Slot = slot; + } + + public ContainerSlot Slot { get; } + } + + private const int FirstChamber = 0; + } +} diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticWeaponComponent.cs deleted file mode 100644 index 50bf07b847..0000000000 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BallisticWeaponComponent.cs +++ /dev/null @@ -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($"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().Play(_soundGunEmpty); - } - } - var chambered = GetChambered(0); - if (chambered != null) - { - var bullet = chambered.GetComponent(); - 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; } - } - - } -} diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BaseProjectileWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BaseProjectileWeaponComponent.cs new file mode 100644 index 0000000000..3090ebdcfc --- /dev/null +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/BaseProjectileWeaponComponent.cs @@ -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 +{ + /// + /// Methods to shoot projectiles. + /// + 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"); + } + + /// + /// Fires projectile from an entity at a coordinate. + /// + 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); + } + + /// + /// Fires projectile in the direction of an angle. + /// + protected void FireAtAngle(IEntity source, Angle angle, string projectileType = null, double spreadStdDev = 0, int projectilesFired = 1, double evenSpreadAngle = 0, float velocity = 0) + { + List 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().IgnoreEntity(source);//make sure it doesn't hit the source entity + var finalvelocity = projectile.GetComponent().Velocity + velocity;//add velocity + projectile.GetComponent().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().Play(_soundGunshot); + + /// + /// Gets the angle from an entity to a coordinate. + /// + protected Angle GetAngleFromClickLocation(IEntity source, GridCoordinates clickLocation) => new Angle(clickLocation.Position - source.Transform.GridPosition.Position); + + /// + /// Returns a list of numbers that form a set of equal intervals between the start and end value. Used to calculate shotgun spread angles. + /// + protected List Linspace(double start, double end, int intervals) + { + var linspace = new List { }; + for (var i = 0; i <= intervals - 1; i++) + { + linspace.Add(Angle.FromDegrees(start + (end - start) * i / (intervals - 1))); + } + return linspace; + } + } +} diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/ProjectileWeapon.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/ProjectileWeapon.cs deleted file mode 100644 index e79b680082..0000000000 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Projectile/ProjectileWeapon.cs +++ /dev/null @@ -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(); - 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().IgnoreEntity(user); - var velocity = projectile.GetComponent().Velocity; - - //Give it the velocity this weapon gives to things it fires from itself - projectile.GetComponent().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().Play(_soundGunshot); - } - - /// - /// Try to get a projectile for firing. If null, nothing will be fired. - /// - 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, - } -} diff --git a/Resources/Prototypes/Entities/Weapons/Ammunition/12g/ammunition.yml b/Resources/Prototypes/Entities/Weapons/Ammunition/12g/ammunition.yml new file mode 100644 index 0000000000..fcc8c63695 --- /dev/null +++ b/Resources/Prototypes/Entities/Weapons/Ammunition/12g/ammunition.yml @@ -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 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Weapons/Ammunition/12g/boxes.yml b/Resources/Prototypes/Entities/Weapons/Ammunition/12g/boxes.yml new file mode 100644 index 0000000000..090417ae78 --- /dev/null +++ b/Resources/Prototypes/Entities/Weapons/Ammunition/12g/boxes.yml @@ -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 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Weapons/Ammunition/12g/projectiles.yml b/Resources/Prototypes/Entities/Weapons/Ammunition/12g/projectiles.yml new file mode 100644 index 0000000000..3d2172cbfc --- /dev/null +++ b/Resources/Prototypes/Entities/Weapons/Ammunition/12g/projectiles.yml @@ -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 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml new file mode 100644 index 0000000000..00552cf73c --- /dev/null +++ b/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml @@ -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