Gunify pneumatic cannon (#13296)

This commit is contained in:
Kara
2023-01-16 10:56:09 -06:00
committed by GitHub
parent e29233d6b4
commit 7253592126
19 changed files with 351 additions and 561 deletions

View 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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