Fancy guns. (#152)
This commit is contained in:
committed by
GitHub
parent
f0aec83be4
commit
0882435293
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using SS14.Shared.Maths;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
public sealed class CameraRecoilComponent : SharedCameraRecoilComponent
|
||||
{
|
||||
public override void Kick(Vector2 recoil)
|
||||
{
|
||||
var msg = new RecoilKickMessage(recoil);
|
||||
SendNetworkMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using SS14.Shared.GameObjects;
|
||||
using SS14.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public class BallisticBulletComponent : Component
|
||||
{
|
||||
public override string Name => "BallisticBullet";
|
||||
|
||||
private BallisticCaliber _caliber;
|
||||
private string _projectileType;
|
||||
private bool _spent;
|
||||
|
||||
public string ProjectileType => _projectileType;
|
||||
public BallisticCaliber Caliber => _caliber;
|
||||
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 _spent, "spent", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using SS14.Server.GameObjects;
|
||||
using SS14.Server.GameObjects.Components.Container;
|
||||
using SS14.Shared.GameObjects;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public class BallisticMagazineComponent : Component
|
||||
{
|
||||
public override string Name => "BallisticMagazine";
|
||||
|
||||
// Stack of loaded bullets.
|
||||
private readonly Stack<IEntity> _loadedBullets = new Stack<IEntity>();
|
||||
private string _fillType;
|
||||
|
||||
private Container _bulletContainer;
|
||||
private AppearanceComponent _appearance;
|
||||
|
||||
private BallisticMagazineType _magazineType;
|
||||
private BallisticCaliber _caliber;
|
||||
private int _capacity;
|
||||
|
||||
public BallisticMagazineType MagazineType => _magazineType;
|
||||
public BallisticCaliber Caliber => _caliber;
|
||||
public int Capacity => _capacity;
|
||||
|
||||
public int CountLoaded => _loadedBullets.Count;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _magazineType, "magazine", BallisticMagazineType.Unspecified);
|
||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||
serializer.DataField(ref _fillType, "fill", null);
|
||||
serializer.DataField(ref _capacity, "capacity", 20);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
||||
|
||||
_bulletContainer =
|
||||
ContainerManagerComponent.Ensure<Container>("magazine_bullet_container", Owner, out var existed);
|
||||
|
||||
if (!existed && _fillType != null)
|
||||
{
|
||||
// Load up bullets from fill.
|
||||
for (var i = 0; i < Capacity; i++)
|
||||
{
|
||||
var bullet = Owner.EntityManager.SpawnEntity(_fillType);
|
||||
AddBullet(bullet);
|
||||
}
|
||||
}
|
||||
|
||||
_appearance.SetData(BallisticMagazineVisuals.AmmoCapacity, Capacity);
|
||||
}
|
||||
|
||||
public void AddBullet(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));
|
||||
}
|
||||
|
||||
_bulletContainer.Insert(bullet);
|
||||
_loadedBullets.Push(bullet);
|
||||
_updateAppearance();
|
||||
}
|
||||
|
||||
public IEntity TakeBullet()
|
||||
{
|
||||
if (_loadedBullets.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bullet = _loadedBullets.Pop();
|
||||
_updateAppearance();
|
||||
return bullet;
|
||||
}
|
||||
|
||||
private void _updateAppearance()
|
||||
{
|
||||
_appearance.SetData(BallisticMagazineVisuals.AmmoLeft, CountLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BallisticMagazineType
|
||||
{
|
||||
Unspecified = 0,
|
||||
A12mm,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using Content.Shared.Interfaces;
|
||||
using SS14.Server.GameObjects;
|
||||
using SS14.Server.GameObjects.Components.Container;
|
||||
using SS14.Server.GameObjects.EntitySystems;
|
||||
using SS14.Shared.Audio;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Maths;
|
||||
using SS14.Shared.Serialization;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public class BallisticMagazineWeaponComponent : BallisticWeaponComponent, IUse, IAttackby
|
||||
{
|
||||
public override string Name => "BallisticMagazineWeapon";
|
||||
|
||||
private string _defaultMagazine;
|
||||
|
||||
private ContainerSlot _magazineSlot;
|
||||
private BallisticMagazineType _magazineType;
|
||||
|
||||
public BallisticMagazineType MagazineType => _magazineType;
|
||||
private IEntity Magazine => _magazineSlot.ContainedEntity;
|
||||
|
||||
private Random _bulletDropRandom;
|
||||
private string _magInSound;
|
||||
private string _magOutSound;
|
||||
private string _autoEjectSound;
|
||||
private bool _autoEjectMagazine;
|
||||
private AppearanceComponent _appearance;
|
||||
|
||||
private static readonly Direction[] _randomBulletDirs = {
|
||||
Direction.North,
|
||||
Direction.East,
|
||||
Direction.South,
|
||||
Direction.West
|
||||
};
|
||||
|
||||
protected override int ChamberCount => 1;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _magazineType, "magazine", BallisticMagazineType.Unspecified);
|
||||
serializer.DataField(ref _defaultMagazine, "default_magazine", null);
|
||||
serializer.DataField(ref _autoEjectMagazine, "auto_eject_magazine", false);
|
||||
serializer.DataField(ref _autoEjectSound, "sound_auto_eject", null);
|
||||
serializer.DataField(ref _magInSound, "sound_magazine_in", null);
|
||||
serializer.DataField(ref _magOutSound, "sound_magazine_out", null);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
||||
|
||||
_magazineSlot =
|
||||
ContainerManagerComponent.Ensure<ContainerSlot>("ballistic_gun_magazine", Owner,
|
||||
out var alreadyExisted);
|
||||
|
||||
_bulletDropRandom = new Random(Owner.Uid.GetHashCode() ^ DateTime.Now.GetHashCode());
|
||||
|
||||
if (!alreadyExisted && _defaultMagazine != null)
|
||||
{
|
||||
var magazine = Owner.EntityManager.SpawnEntity(_defaultMagazine);
|
||||
InsertMagazine(magazine, false);
|
||||
}
|
||||
_updateAppearance();
|
||||
}
|
||||
|
||||
public bool InsertMagazine(IEntity magazine, bool playSound=true)
|
||||
{
|
||||
if (!magazine.TryGetComponent(out BallisticMagazineComponent component))
|
||||
{
|
||||
throw new ArgumentException("Not a magazine", nameof(magazine));
|
||||
}
|
||||
|
||||
if (component.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)
|
||||
{
|
||||
var audioSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>();
|
||||
audioSystem.Play(_magInSound, Owner);
|
||||
}
|
||||
|
||||
if (GetChambered(0) == null)
|
||||
{
|
||||
// No bullet in chamber, load one from magazine.
|
||||
var bullet = component.TakeBullet();
|
||||
if (bullet != null)
|
||||
{
|
||||
LoadIntoChamber(0, bullet);
|
||||
}
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EjectMagazine(bool playSound=true)
|
||||
{
|
||||
var entity = Magazine;
|
||||
if (entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_magazineSlot.Remove(entity))
|
||||
{
|
||||
entity.Transform.GridPosition = Owner.Transform.GridPosition;
|
||||
if (_magOutSound != null)
|
||||
{
|
||||
var audioSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>();
|
||||
audioSystem.Play(_magOutSound, Owner);
|
||||
}
|
||||
_updateAppearance();
|
||||
return true;
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void CycleChamberedBullet(int chamber)
|
||||
{
|
||||
DebugTools.Assert(chamber == 0);
|
||||
|
||||
// Eject chambered bullet.
|
||||
var entity = RemoveFromChamber(chamber);
|
||||
entity.Transform.GridPosition = Owner.Transform.GridPosition;
|
||||
entity.Transform.LocalRotation = _bulletDropRandom.Pick(_randomBulletDirs).ToAngle();
|
||||
var audioSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>();
|
||||
var effect = $"/Audio/items/weapons/casingfall{_bulletDropRandom.Next(1, 4)}.ogg";
|
||||
audioSystem.Play(effect, entity, AudioParams.Default.WithVolume(-3));
|
||||
|
||||
if (Magazine != null)
|
||||
{
|
||||
var magComponent = Magazine.GetComponent<BallisticMagazineComponent>();
|
||||
var bullet = magComponent.TakeBullet();
|
||||
if (bullet != null)
|
||||
{
|
||||
LoadIntoChamber(0, bullet);
|
||||
}
|
||||
|
||||
if (magComponent.CountLoaded == 0 && _autoEjectMagazine)
|
||||
{
|
||||
EjectMagazine();
|
||||
if (_autoEjectSound != null)
|
||||
{
|
||||
audioSystem.Play(_autoEjectSound, Owner, AudioParams.Default.WithVolume(-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateAppearance();
|
||||
}
|
||||
|
||||
public bool UseEntity(IEntity user)
|
||||
{
|
||||
var ret = EjectMagazine();
|
||||
if (ret)
|
||||
{
|
||||
Owner.PopupMessage(user, "Magazine ejected");
|
||||
}
|
||||
else
|
||||
{
|
||||
Owner.PopupMessage(user, "No magazine");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Attackby(IEntity user, IEntity attackwith)
|
||||
{
|
||||
if (!attackwith.TryGetComponent(out BallisticMagazineComponent component))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Magazine != null)
|
||||
{
|
||||
Owner.PopupMessage(user, "Already got a magazine.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (component.MagazineType != MagazineType || component.Caliber != Caliber)
|
||||
{
|
||||
Owner.PopupMessage(user, "Magazine doesn't fit.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return InsertMagazine(attackwith);
|
||||
}
|
||||
|
||||
private void _updateAppearance()
|
||||
{
|
||||
if (Magazine != null)
|
||||
{
|
||||
var comp = Magazine.GetComponent<BallisticMagazineComponent>();
|
||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoLeft, comp.CountLoaded);
|
||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoCapacity, comp.Capacity);
|
||||
_appearance.SetData(BallisticMagazineWeaponVisuals.MagazineLoaded, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoLeft, 0);
|
||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoLeft, 0);
|
||||
_appearance.SetData(BallisticMagazineWeaponVisuals.MagazineLoaded, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Verb]
|
||||
public sealed class EjectMagazineVerb : Verb<BallisticMagazineWeaponComponent>
|
||||
{
|
||||
protected override string GetText(IEntity user, BallisticMagazineWeaponComponent component)
|
||||
{
|
||||
return component.Magazine == null ? "Eject magazine (magazine missing)" : "Eject magazine";
|
||||
}
|
||||
|
||||
protected override bool IsDisabled(IEntity user, BallisticMagazineWeaponComponent component)
|
||||
{
|
||||
return component.Magazine == null;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, BallisticMagazineWeaponComponent component)
|
||||
{
|
||||
component.EjectMagazine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects;
|
||||
using SS14.Server.Chat;
|
||||
using SS14.Server.GameObjects.Components.Container;
|
||||
using SS14.Server.GameObjects.EntitySystems;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.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)
|
||||
{
|
||||
var audioSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>();
|
||||
audioSystem.Play(_soundGunEmpty, Owner);
|
||||
}
|
||||
}
|
||||
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);
|
||||
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; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Projectiles;
|
||||
using SS14.Server.GameObjects;
|
||||
using SS14.Server.GameObjects.EntitySystems;
|
||||
@@ -15,12 +16,8 @@ using SS14.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public class ProjectileWeaponComponent : Component
|
||||
public abstract class ProjectileWeaponComponent : Component
|
||||
{
|
||||
public override string Name => "ProjectileWeapon";
|
||||
|
||||
private string _ProjectilePrototype = "ProjectileBullet";
|
||||
|
||||
private float _velocity = 20f;
|
||||
private float _spreadStdDev = 3;
|
||||
private bool _spread = true;
|
||||
@@ -61,16 +58,27 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
//Spawn the projectilePrototype
|
||||
var projectile = IoCManager.Resolve<IServerEntityManager>().ForceSpawnEntityAt(_ProjectilePrototype, userPosition);
|
||||
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);
|
||||
@@ -84,5 +92,16 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
// Sound!
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().Play("/Audio/gunshot_c20.ogg", user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get a projectile for firing. If null, nothing will be fired.
|
||||
/// </summary>
|
||||
protected abstract IEntity GetFiredProjectile();
|
||||
}
|
||||
|
||||
public enum BallisticCaliber
|
||||
{
|
||||
Unspecified = 0,
|
||||
A12mm,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user