Gunify pneumatic cannon (#13296)
This commit is contained in:
53
Content.Shared/PneumaticCannon/PneumaticCannonComponent.cs
Normal file
53
Content.Shared/PneumaticCannon/PneumaticCannonComponent.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Shared.Tools;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.PneumaticCannon;
|
||||
|
||||
/// <summary>
|
||||
/// Handles gas powered guns--cancels shooting if no gas is available, and takes gas from the given container slot.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class PneumaticCannonComponent : Component
|
||||
{
|
||||
public const string TankSlotId = "gas_tank";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public PneumaticCannonPower Power = PneumaticCannonPower.Medium;
|
||||
|
||||
[DataField("toolModifyPower", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
||||
public string ToolModifyPower = "Anchoring";
|
||||
|
||||
/// <summary>
|
||||
/// How long to stun for if they shoot the pneumatic cannon at high power.
|
||||
/// </summary>
|
||||
[DataField("highPowerStunTime")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float HighPowerStunTime = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of moles to consume for each shot at any power.
|
||||
/// </summary>
|
||||
[DataField("gasUsage")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float GasUsage = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Base projectile speed at default power.
|
||||
/// </summary>
|
||||
[DataField("baseProjectileSpeed")]
|
||||
public float BaseProjectileSpeed = 20f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How strong the pneumatic cannon should be.
|
||||
/// Each tier throws items farther and with more speed, but has drawbacks.
|
||||
/// The highest power knocks the player down for a considerable amount of time.
|
||||
/// </summary>
|
||||
public enum PneumaticCannonPower : byte
|
||||
{
|
||||
Low = 0,
|
||||
Medium = 1,
|
||||
High = 2,
|
||||
Len = 3 // used for length calc
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.PneumaticCannon
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum PneumaticCannonVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
Tank
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PneumaticCannonVisuals
|
||||
{
|
||||
Tank
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.PneumaticCannon;
|
||||
|
||||
public abstract class SharedPneumaticCannonSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedContainerSystem Container = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PneumaticCannonComponent, AttemptShootEvent>(OnAttemptShoot);
|
||||
}
|
||||
|
||||
private void OnAttemptShoot(EntityUid uid, PneumaticCannonComponent component, ref AttemptShootEvent args)
|
||||
{
|
||||
// we don't have atmos on shared, so just predict by the existence of a slot item
|
||||
// server will handle auto ejecting/not adding the slot item if it doesnt have enough gas,
|
||||
// so this won't mispredict
|
||||
if (!Container.TryGetContainer(uid, PneumaticCannonComponent.TankSlotId, out var container) ||
|
||||
container is not ContainerSlot slot || slot.ContainedEntity is null)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,12 @@ namespace Content.Shared.Storage.Components
|
||||
|
||||
[DataField("sprite")] public ResourcePath? RSIPath;
|
||||
|
||||
/// <summary>
|
||||
/// If this exists, shown layers will only consider entities in the given containers.
|
||||
/// </summary>
|
||||
[DataField("containerWhitelist")]
|
||||
public HashSet<string>? ContainerWhitelist;
|
||||
|
||||
public readonly List<string> SpriteLayers = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +42,18 @@ namespace Content.Shared.Storage.EntitySystems
|
||||
private void MapperEntityRemoved(EntityUid uid, ItemMapperComponent itemMapper,
|
||||
EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (itemMapper.ContainerWhitelist != null && !itemMapper.ContainerWhitelist.Contains(args.Container.ID))
|
||||
return;
|
||||
|
||||
UpdateAppearance(uid, itemMapper, args);
|
||||
}
|
||||
|
||||
private void MapperEntityInserted(EntityUid uid, ItemMapperComponent itemMapper,
|
||||
EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (itemMapper.ContainerWhitelist != null && !itemMapper.ContainerWhitelist.Contains(args.Container.ID))
|
||||
return;
|
||||
|
||||
UpdateAppearance(uid, itemMapper, args);
|
||||
}
|
||||
|
||||
@@ -76,7 +82,7 @@ namespace Content.Shared.Storage.EntitySystems
|
||||
out IReadOnlyList<string> showLayers)
|
||||
{
|
||||
var containedLayers = _container.GetAllContainers(msg.Container.Owner)
|
||||
.SelectMany(cont => cont.ContainedEntities).ToArray();
|
||||
.Where(c => itemMapper.ContainerWhitelist?.Contains(c.ID) ?? true).SelectMany(cont => cont.ContainedEntities).ToArray();
|
||||
|
||||
var list = new List<string>();
|
||||
foreach (var mapLayerData in itemMapper.MapLayers.Values)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Weapons.Ranged.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Handles pulling entities from the given container to use as ammunition.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ContainerAmmoProviderComponent : AmmoProviderComponent
|
||||
{
|
||||
[DataField("container", required: true)]
|
||||
public string Container = default!;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Weapons.Ranged.Systems;
|
||||
|
||||
public partial class SharedGunSystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public void InitializeContainer()
|
||||
{
|
||||
SubscribeLocalEvent<ContainerAmmoProviderComponent, TakeAmmoEvent>(OnContainerTakeAmmo);
|
||||
SubscribeLocalEvent<ContainerAmmoProviderComponent, GetAmmoCountEvent>(OnContainerAmmoCount);
|
||||
}
|
||||
|
||||
private void OnContainerTakeAmmo(EntityUid uid, ContainerAmmoProviderComponent component, TakeAmmoEvent args)
|
||||
{
|
||||
if (!_container.TryGetContainer(uid, component.Container, out var container))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < args.Shots; i++)
|
||||
{
|
||||
if (!container.ContainedEntities.Any())
|
||||
break;
|
||||
|
||||
var ent = container.ContainedEntities[0];
|
||||
|
||||
if (_netMan.IsServer)
|
||||
container.Remove(ent);
|
||||
|
||||
args.Ammo.Add(EnsureComp<AmmoComponent>(ent));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnContainerAmmoCount(EntityUid uid, ContainerAmmoProviderComponent component, ref GetAmmoCountEvent args)
|
||||
{
|
||||
if (!_container.TryGetContainer(uid, component.Container, out var container))
|
||||
{
|
||||
args.Capacity = 0;
|
||||
args.Count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Capacity = int.MaxValue;
|
||||
args.Count = container.ContainedEntities.Count;
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
InitializeMagazine();
|
||||
InitializeRevolver();
|
||||
InitializeBasicEntity();
|
||||
InitializeContainer();
|
||||
|
||||
// Interactions
|
||||
SubscribeLocalEvent<GunComponent, GetVerbsEvent<AlternativeVerb>>(OnAltVerb);
|
||||
@@ -205,11 +206,13 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
|
||||
private void AttemptShoot(EntityUid user, GunComponent gun)
|
||||
{
|
||||
if (gun.FireRate <= 0f) return;
|
||||
if (gun.FireRate <= 0f)
|
||||
return;
|
||||
|
||||
var toCoordinates = gun.ShootCoordinates;
|
||||
|
||||
if (toCoordinates == null) return;
|
||||
if (toCoordinates == null)
|
||||
return;
|
||||
|
||||
if (TagSystem.HasTag(user, "GunsDisabled"))
|
||||
{
|
||||
@@ -217,11 +220,13 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var curTime = Timing.CurTime;
|
||||
|
||||
// Need to do this to play the clicking sound for empty automatic weapons
|
||||
// but not play anything for burst fire.
|
||||
if (gun.NextFire > curTime) return;
|
||||
if (gun.NextFire > curTime)
|
||||
return;
|
||||
|
||||
// First shot
|
||||
if (gun.ShotCounter == 0 && gun.NextFire < curTime)
|
||||
@@ -269,7 +274,10 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
// where the gun may be SemiAuto or Burst.
|
||||
gun.ShotCounter += shots;
|
||||
|
||||
if (ev.Ammo.Count <= 0)
|
||||
var attemptEv = new AttemptShootEvent(user);
|
||||
RaiseLocalEvent(gun.Owner, ref attemptEv);
|
||||
|
||||
if (ev.Ammo.Count <= 0 || attemptEv.Cancelled)
|
||||
{
|
||||
// Play empty gun sounds if relevant
|
||||
// If they're firing an existing clip then don't play anything.
|
||||
@@ -288,6 +296,8 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
|
||||
// Shoot confirmed - sounds also played here in case it's invalid (e.g. cartridge already spent).
|
||||
Shoot(gun, ev.Ammo, fromCoordinates, toCoordinates.Value, user);
|
||||
var shotEv = new GunShotEvent(user);
|
||||
RaiseLocalEvent(gun.Owner, ref shotEv);
|
||||
// Projectiles cause impulses especially important in non gravity environments
|
||||
if (TryComp<PhysicsComponent>(user, out var userPhysics))
|
||||
{
|
||||
@@ -410,6 +420,24 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on the gun before firing to see if the shot should go through.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handling this in server exclusively will lead to mispredicts.
|
||||
/// </remarks>
|
||||
/// <param name="User">The user that attempted to fire this gun.</param>
|
||||
/// <param name="Cancelled">Set this to true if the shot should be cancelled.</param>
|
||||
[ByRefEvent]
|
||||
public record struct AttemptShootEvent(EntityUid User, bool Cancelled=false);
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on the gun after firing.
|
||||
/// </summary>
|
||||
/// <param name="User">The user that fired this gun.</param>
|
||||
[ByRefEvent]
|
||||
public record struct GunShotEvent(EntityUid User);
|
||||
|
||||
public enum EffectLayers : byte
|
||||
{
|
||||
Unshaded,
|
||||
|
||||
Reference in New Issue
Block a user