Files
tbd-station-14/Content.Server/Weapon/Ranged/GunSystem.cs
2022-05-22 12:31:46 +10:00

254 lines
12 KiB
C#

using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Hands.Components;
using Content.Server.PowerCell;
using Content.Server.Stunnable;
using Content.Server.Weapon.Ranged.Ammunition.Components;
using Content.Server.Weapon.Ranged.Barrels.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Camera;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.PowerCell.Components;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Weapon.Ranged;
public sealed partial class GunSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly AdminLogSystem _logs = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly CameraRecoilSystem _recoil = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly EffectSystem _effects = default!;
[Dependency] private readonly PowerCellSystem _cell = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
/// <summary>
/// How many sounds are allowed to be played on ejecting multiple casings.
/// </summary>
private const int EjectionSoundMax = 3;
public override void Initialize()
{
base.Initialize();
// TODO: So at the time I thought there might've been a need to keep magazines
// and ammo boxes separate.
// There isn't.
// They should be combined.
SubscribeLocalEvent<AmmoComponent, ExaminedEvent>(OnAmmoExamine);
SubscribeLocalEvent<AmmoBoxComponent, ComponentInit>(OnAmmoBoxInit);
SubscribeLocalEvent<AmmoBoxComponent, MapInitEvent>(OnAmmoBoxMapInit);
SubscribeLocalEvent<AmmoBoxComponent, ExaminedEvent>(OnAmmoBoxExamine);
SubscribeLocalEvent<AmmoBoxComponent, InteractUsingEvent>(OnAmmoBoxInteractUsing);
SubscribeLocalEvent<AmmoBoxComponent, UseInHandEvent>(OnAmmoBoxUse);
SubscribeLocalEvent<AmmoBoxComponent, InteractHandEvent>(OnAmmoBoxInteractHand);
SubscribeLocalEvent<AmmoBoxComponent, GetVerbsEvent<AlternativeVerb>>(OnAmmoBoxAltVerbs);
SubscribeLocalEvent<RangedMagazineComponent, ComponentInit>(OnRangedMagInit);
SubscribeLocalEvent<RangedMagazineComponent, MapInitEvent>(OnRangedMagMapInit);
SubscribeLocalEvent<RangedMagazineComponent, UseInHandEvent>(OnRangedMagUse);
SubscribeLocalEvent<RangedMagazineComponent, ExaminedEvent>(OnRangedMagExamine);
SubscribeLocalEvent<RangedMagazineComponent, InteractUsingEvent>(OnRangedMagInteractUsing);
// Whenever I get around to refactoring guns this is all going to change.
// Essentially the idea is
// You have GunComponent and ChamberedGunComponent (which is just guncomp + containerslot for chamber)
// GunComponent has a component for an ammo provider on it (e.g. battery) and asks it for ammo to shoot
// ALTERNATIVELY, it has a "MagazineAmmoProvider" that has its own containerslot that it can ask
// (All of these would be comp references so max you only ever have 2 components on the gun).
SubscribeLocalEvent<BatteryBarrelComponent, ComponentInit>(OnBatteryInit);
SubscribeLocalEvent<BatteryBarrelComponent, MapInitEvent>(OnBatteryMapInit);
SubscribeLocalEvent<BatteryBarrelComponent, PowerCellChangedEvent>(OnCellSlotUpdated);
SubscribeLocalEvent<BoltActionBarrelComponent, ComponentInit>(OnBoltInit);
SubscribeLocalEvent<BoltActionBarrelComponent, MapInitEvent>(OnBoltMapInit);
SubscribeLocalEvent<BoltActionBarrelComponent, GunFireAttemptEvent>(OnBoltFireAttempt);
SubscribeLocalEvent<BoltActionBarrelComponent, UseInHandEvent>(OnBoltUse);
SubscribeLocalEvent<BoltActionBarrelComponent, InteractUsingEvent>(OnBoltInteractUsing);
SubscribeLocalEvent<BoltActionBarrelComponent, ComponentGetState>(OnBoltGetState);
SubscribeLocalEvent<BoltActionBarrelComponent, ExaminedEvent>(OnBoltExamine);
SubscribeLocalEvent<BoltActionBarrelComponent, GetVerbsEvent<InteractionVerb>>(AddToggleBoltVerb);
SubscribeLocalEvent<MagazineBarrelComponent, ComponentInit>(OnMagazineInit);
SubscribeLocalEvent<MagazineBarrelComponent, MapInitEvent>(OnMagazineMapInit);
SubscribeLocalEvent<MagazineBarrelComponent, ExaminedEvent>(OnMagazineExamine);
SubscribeLocalEvent<MagazineBarrelComponent, UseInHandEvent>(OnMagazineUse);
SubscribeLocalEvent<MagazineBarrelComponent, InteractUsingEvent>(OnMagazineInteractUsing);
SubscribeLocalEvent<MagazineBarrelComponent, ComponentGetState>(OnMagazineGetState);
SubscribeLocalEvent<MagazineBarrelComponent, GetVerbsEvent<InteractionVerb>>(AddMagazineInteractionVerbs);
SubscribeLocalEvent<MagazineBarrelComponent, GetVerbsEvent<AlternativeVerb>>(AddEjectMagazineVerb);
SubscribeLocalEvent<PumpBarrelComponent, ComponentGetState>(OnPumpGetState);
SubscribeLocalEvent<PumpBarrelComponent, ComponentInit>(OnPumpInit);
SubscribeLocalEvent<PumpBarrelComponent, MapInitEvent>(OnPumpMapInit);
SubscribeLocalEvent<PumpBarrelComponent, ExaminedEvent>(OnPumpExamine);
SubscribeLocalEvent<PumpBarrelComponent, UseInHandEvent>(OnPumpUse);
SubscribeLocalEvent<PumpBarrelComponent, InteractUsingEvent>(OnPumpInteractUsing);
SubscribeLocalEvent<RevolverBarrelComponent, MapInitEvent>(OnRevolverMapInit);
SubscribeLocalEvent<RevolverBarrelComponent, UseInHandEvent>(OnRevolverUse);
SubscribeLocalEvent<RevolverBarrelComponent, InteractUsingEvent>(OnRevolverInteractUsing);
SubscribeLocalEvent<RevolverBarrelComponent, ComponentGetState>(OnRevolverGetState);
SubscribeLocalEvent<RevolverBarrelComponent, GetVerbsEvent<AlternativeVerb>>(AddSpinVerb);
SubscribeLocalEvent<SpeedLoaderComponent, ComponentInit>(OnSpeedLoaderInit);
SubscribeLocalEvent<SpeedLoaderComponent, MapInitEvent>(OnSpeedLoaderMapInit);
SubscribeLocalEvent<SpeedLoaderComponent, UseInHandEvent>(OnSpeedLoaderUse);
SubscribeLocalEvent<SpeedLoaderComponent, AfterInteractEvent>(OnSpeedLoaderAfterInteract);
SubscribeLocalEvent<SpeedLoaderComponent, InteractUsingEvent>(OnSpeedLoaderInteractUsing);
// SubscribeLocalEvent<ServerRangedWeaponComponent, ExaminedEvent>(OnGunExamine);
SubscribeNetworkEvent<FirePosEvent>(OnFirePos);
SubscribeLocalEvent<ServerRangedWeaponComponent, MeleeAttackAttemptEvent>(OnMeleeAttempt);
}
private void OnFirePos(FirePosEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} user)
return;
if (!msg.Coordinates.IsValid(EntityManager))
return;
if (!TryComp(user, out HandsComponent? handsComponent))
return;
// TODO: Not exactly robust
var gun = handsComponent.ActiveHand?.HeldEntity;
if (gun == null || !TryComp(gun, out ServerRangedWeaponComponent? weapon))
return;
// map pos
TryFire(user, msg.Coordinates, weapon);
}
public EntityUid? PeekAtAmmo(ServerRangedBarrelComponent component)
{
return component switch
{
BatteryBarrelComponent battery => PeekAmmo(battery),
BoltActionBarrelComponent bolt => PeekAmmo(bolt),
MagazineBarrelComponent mag => PeekAmmo(mag),
PumpBarrelComponent pump => PeekAmmo(pump),
RevolverBarrelComponent revolver => PeekAmmo(revolver),
_ => throw new NotImplementedException()
};
}
public EntityUid? TakeOutProjectile(ServerRangedBarrelComponent component, EntityCoordinates spawnAt)
{
return component switch
{
BatteryBarrelComponent battery => TakeProjectile(battery, spawnAt),
BoltActionBarrelComponent bolt => TakeProjectile(bolt, spawnAt),
MagazineBarrelComponent mag => TakeProjectile(mag, spawnAt),
PumpBarrelComponent pump => TakeProjectile(pump, spawnAt),
RevolverBarrelComponent revolver => TakeProjectile(revolver, spawnAt),
_ => throw new NotImplementedException()
};
}
/// <summary>
/// Drops multiple cartridges / shells on the floor
/// Wraps EjectCasing to make it less toxic for bulk ejections
/// </summary>
public void EjectCasings(IEnumerable<EntityUid> entities)
{
var soundPlayCount = 0;
var playSound = true;
foreach (var entity in entities)
{
EjectCasing(entity, playSound);
soundPlayCount++;
if (soundPlayCount > EjectionSoundMax)
{
playSound = false;
}
}
}
/// <summary>
/// Drops a single cartridge / shell
/// </summary>
public void EjectCasing(
EntityUid entity,
bool playSound = true,
AmmoComponent? ammoComponent = null)
{
const float ejectOffset = 0.4f;
if (!Resolve(entity, ref ammoComponent)) return;
var offsetPos = (_random.NextFloat(-ejectOffset, ejectOffset), _random.NextFloat(-ejectOffset, ejectOffset));
var xform = Transform(entity);
var coordinates = xform.Coordinates;
coordinates = coordinates.Offset(offsetPos);
xform.LocalRotation = _random.NextFloat(MathF.Tau);
xform.Coordinates = coordinates;
if (playSound)
SoundSystem.Play(Filter.Pvs(entity), ammoComponent.SoundCollectionEject.GetSound(), coordinates, AudioParams.Default.WithVolume(-1));
}
private Angle GetRecoilAngle(ServerRangedBarrelComponent component, Angle direction)
{
var currentTime = _gameTiming.CurTime;
var timeSinceLastFire = (currentTime - component.LastFire).TotalSeconds;
var newTheta = MathHelper.Clamp(component.CurrentAngle.Theta + component.AngleIncrease - component.AngleDecay * timeSinceLastFire, component.MinAngle.Theta, component.MaxAngle.Theta);
component.CurrentAngle = new Angle(newTheta);
var random = (_random.NextDouble(-1, 1));
var angle = Angle.FromDegrees(direction.Degrees + component.CurrentAngle.Degrees * random);
return angle;
}
/// <summary>
/// Raised on a gun when it fires.
/// </summary>
public sealed class GunShotEvent : EntityEventArgs
{
}
public sealed class GunFireAttemptEvent : CancellableEntityEventArgs
{
public EntityUid? User = null;
public ServerRangedWeaponComponent Weapon;
public GunFireAttemptEvent(EntityUid? user, ServerRangedWeaponComponent weapon)
{
User = user;
Weapon = weapon;
}
}
}