Adds BallisticAmmoSelfRefillerComponent (#38537)
* Adds BallisticAmmoSelfRefillerComponent And uses it to replace battery-based refilling of the Syndicate L6 and Viper modules. - Add `BallisticAmmoSelfRefillerComponent` - Handle `EmpPulseEvent` to pause refilling behavior for EMP's duration - Change `Content.Server.Weapons.Ranged.Systems.Update` override in `GunSystem.AutoFire.cs` to `UpdateAutoFire` - Add `Content.Server.Weapons.Ranged.Systems.Update` to `GunSystem.cs` so that it can call `UpdateAutoFire` and `UpdateBallistic` - Add public methods to GunSystem for use by refilling implementation - PauseSelfRefill - IsFullBallistic (same as #299) - CanInsertBallistic (same as #299) - TryBallisticInsert (same as #299) * _timing -> Timing * unspawned count stuff * imagine building the code before pushing * - apply to c20r ROW - make predicted/shared * revert server system import only changes * oop * o great and wise Slarti * Scar comments * field deltas + correct serializer * review --------- Co-authored-by: ScarKy0 <scarky0@onet.eu>
This commit is contained in:
parent
bb4f91d408
commit
d50706dd5e
|
|
@ -0,0 +1,61 @@
|
|||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Weapons.Ranged.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component, analogous to <see cref="BatterySelfRechargerComponent"/>, will attempt insert ballistic ammunition
|
||||
/// into its owner's <see cref="BallisticAmmoProviderComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause,
|
||||
Access(typeof(SharedGunSystem))]
|
||||
public sealed partial class BallisticAmmoSelfRefillerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the refilling behavior is active, false otherwise.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool AutoRefill = true;
|
||||
|
||||
/// <summary>
|
||||
/// How often a new piece of ammunition is inserted into the owner's <see cref="BallisticAmmoProviderComponent"/>.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan AutoRefillRate = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// If true, causes the refilling behavior to be delayed by at least <see cref="AutoRefillPauseDuration"/> after
|
||||
/// the owner is fired.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool FiringPausesAutoRefill = false;
|
||||
|
||||
/// <summary>
|
||||
/// How long to pause for if <see cref="FiringPausesAutoRefill"/> is true.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan AutoRefillPauseDuration = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// What entity to spawn and attempt to insert into the owner. If null, uses
|
||||
/// <see cref="BallisticAmmoProviderComponent.Proto"/>. If that's also null, this component does nothing but log
|
||||
/// errors.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntProtoId? AmmoProto;
|
||||
|
||||
/// <summary>
|
||||
/// If true, EMPs will pause this component's behavior.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool AffectedByEmp = false;
|
||||
|
||||
/// <summary>
|
||||
/// When the next auto refill should occur. This is just implementation state.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan NextAutoRefill = TimeSpan.Zero;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Emp;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
|
|
@ -16,7 +17,7 @@ public abstract partial class SharedGunSystem
|
|||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
|
||||
|
||||
[MustCallBase]
|
||||
protected virtual void InitializeBallistic()
|
||||
{
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, ComponentInit>(OnBallisticInit);
|
||||
|
|
@ -30,6 +31,14 @@ public abstract partial class SharedGunSystem
|
|||
SubscribeLocalEvent<BallisticAmmoProviderComponent, AfterInteractEvent>(OnBallisticAfterInteract);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, AmmoFillDoAfterEvent>(OnBallisticAmmoFillDoAfter);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, UseInHandEvent>(OnBallisticUse);
|
||||
|
||||
SubscribeLocalEvent<BallisticAmmoSelfRefillerComponent, MapInitEvent>(OnBallisticRefillerMapInit);
|
||||
SubscribeLocalEvent<BallisticAmmoSelfRefillerComponent, EmpPulseEvent>(OnRefillerEmpPulsed);
|
||||
}
|
||||
|
||||
private void OnBallisticRefillerMapInit(Entity<BallisticAmmoSelfRefillerComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
entity.Comp.NextAutoRefill = Timing.CurTime + entity.Comp.AutoRefillRate;
|
||||
}
|
||||
|
||||
private void OnBallisticUse(EntityUid uid, BallisticAmmoProviderComponent component, UseInHandEvent args)
|
||||
|
|
@ -46,20 +55,8 @@ public abstract partial class SharedGunSystem
|
|||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Used))
|
||||
return;
|
||||
|
||||
if (GetBallisticShots(component) >= component.Capacity)
|
||||
return;
|
||||
|
||||
component.Entities.Add(args.Used);
|
||||
Containers.Insert(args.Used, component.Container);
|
||||
// Not predicted so
|
||||
Audio.PlayPredicted(component.SoundInsert, uid, args.User);
|
||||
args.Handled = true;
|
||||
UpdateBallisticAppearance(uid, component);
|
||||
UpdateAmmoCount(args.Target);
|
||||
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
|
||||
if (TryBallisticInsert((uid, component), args.Used, args.User))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBallisticAfterInteract(EntityUid uid, BallisticAmmoProviderComponent component, AfterInteractEvent args)
|
||||
|
|
@ -242,23 +239,29 @@ public abstract partial class SharedGunSystem
|
|||
{
|
||||
for (var i = 0; i < args.Shots; i++)
|
||||
{
|
||||
EntityUid entity;
|
||||
|
||||
EntityUid? ammoEntity = null;
|
||||
if (component.Entities.Count > 0)
|
||||
{
|
||||
entity = component.Entities[^1];
|
||||
|
||||
args.Ammo.Add((entity, EnsureShootable(entity)));
|
||||
var existingEnt = component.Entities[^1];
|
||||
component.Entities.RemoveAt(component.Entities.Count - 1);
|
||||
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
|
||||
Containers.Remove(entity, component.Container);
|
||||
Containers.Remove(existingEnt, component.Container);
|
||||
ammoEntity = existingEnt;
|
||||
}
|
||||
else if (component.UnspawnedCount > 0)
|
||||
{
|
||||
component.UnspawnedCount--;
|
||||
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount));
|
||||
entity = Spawn(component.Proto, args.Coordinates);
|
||||
args.Ammo.Add((entity, EnsureShootable(entity)));
|
||||
ammoEntity = Spawn(component.Proto, args.Coordinates);
|
||||
}
|
||||
|
||||
if (ammoEntity is { } ent)
|
||||
{
|
||||
args.Ammo.Add((ent, EnsureShootable(ent)));
|
||||
if (TryComp<BallisticAmmoSelfRefillerComponent>(uid, out var refiller))
|
||||
{
|
||||
PauseSelfRefill((uid, refiller));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +274,73 @@ public abstract partial class SharedGunSystem
|
|||
args.Capacity = component.Capacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes <paramref name="entity"/> to pause its refilling for either at least <paramref name="overridePauseDuration"/>
|
||||
/// (if not null) or the entity's <see cref="BallisticAmmoSelfRefillerComponent.AutoRefillPauseDuration"/>. If the
|
||||
/// entity's next refill would occur after the pause duration, this function has no effect.
|
||||
/// </summary>
|
||||
public void PauseSelfRefill(
|
||||
Entity<BallisticAmmoSelfRefillerComponent> entity,
|
||||
TimeSpan? overridePauseDuration = null
|
||||
)
|
||||
{
|
||||
if (overridePauseDuration == null && !entity.Comp.FiringPausesAutoRefill)
|
||||
return;
|
||||
|
||||
var nextRefillByPause = Timing.CurTime + (overridePauseDuration ?? entity.Comp.AutoRefillPauseDuration);
|
||||
if (nextRefillByPause > entity.Comp.NextAutoRefill)
|
||||
{
|
||||
entity.Comp.NextAutoRefill = nextRefillByPause;
|
||||
DirtyField(entity.AsNullable(), nameof(BallisticAmmoSelfRefillerComponent.NextAutoRefill));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given <paramref name="entity"/>'s ballistic ammunition is full, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsFull(Entity<BallisticAmmoProviderComponent> entity)
|
||||
{
|
||||
return GetBallisticShots(entity.Comp) >= entity.Comp.Capacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not <paramref name="inserted"/> can be inserted into <paramref name="entity"/>, based on
|
||||
/// available space and whitelists.
|
||||
/// </summary>
|
||||
public bool CanInsertBallistic(Entity<BallisticAmmoProviderComponent> entity, EntityUid inserted)
|
||||
{
|
||||
return !_whitelistSystem.IsWhitelistFailOrNull(entity.Comp.Whitelist, inserted) &&
|
||||
!IsFull(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to insert <paramref name="inserted"/> into <paramref name="entity"/> as ammunition. Returns true on
|
||||
/// success, false otherwise.
|
||||
/// </summary>
|
||||
public bool TryBallisticInsert(
|
||||
Entity<BallisticAmmoProviderComponent> entity,
|
||||
EntityUid inserted,
|
||||
EntityUid? user,
|
||||
bool suppressInsertionSound = false
|
||||
)
|
||||
{
|
||||
if (!CanInsertBallistic(entity, inserted))
|
||||
return false;
|
||||
|
||||
entity.Comp.Entities.Add(inserted);
|
||||
Containers.Insert(inserted, entity.Comp.Container);
|
||||
if (!suppressInsertionSound)
|
||||
{
|
||||
Audio.PlayPredicted(entity.Comp.SoundInsert, entity, user);
|
||||
}
|
||||
|
||||
UpdateBallisticAppearance(entity, entity.Comp);
|
||||
UpdateAmmoCount(entity);
|
||||
DirtyField(entity.AsNullable(), nameof(BallisticAmmoProviderComponent.Entities));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateBallisticAppearance(EntityUid uid, BallisticAmmoProviderComponent component)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted || !TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
|
|
@ -290,6 +360,70 @@ public abstract partial class SharedGunSystem
|
|||
UpdateAmmoCount(entity.Owner);
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
private void OnRefillerEmpPulsed(Entity<BallisticAmmoSelfRefillerComponent> entity, ref EmpPulseEvent args)
|
||||
{
|
||||
if (!entity.Comp.AffectedByEmp)
|
||||
return;
|
||||
|
||||
PauseSelfRefill(entity, args.Duration);
|
||||
}
|
||||
|
||||
private void UpdateBallistic(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<BallisticAmmoSelfRefillerComponent, BallisticAmmoProviderComponent>();
|
||||
while (query.MoveNext(out var uid, out var refiller, out var ammo))
|
||||
{
|
||||
BallisticSelfRefillerUpdate((uid, ammo, refiller));
|
||||
}
|
||||
}
|
||||
|
||||
private void BallisticSelfRefillerUpdate(
|
||||
Entity<BallisticAmmoProviderComponent, BallisticAmmoSelfRefillerComponent> entity
|
||||
)
|
||||
{
|
||||
var ammo = entity.Comp1;
|
||||
var refiller = entity.Comp2;
|
||||
if (Timing.CurTime < refiller.NextAutoRefill)
|
||||
return;
|
||||
|
||||
refiller.NextAutoRefill += refiller.AutoRefillRate;
|
||||
DirtyField(entity, refiller, nameof(BallisticAmmoSelfRefillerComponent.NextAutoRefill));
|
||||
|
||||
if (!refiller.AutoRefill || IsFull(entity))
|
||||
return;
|
||||
|
||||
if (refiller.AmmoProto is not { } refillerAmmoProto)
|
||||
{
|
||||
// No ammo proto on the refiller, so just increment the unspawned count on the provider
|
||||
// if it has an ammo proto.
|
||||
if (ammo.Proto is null)
|
||||
{
|
||||
Log.Error(
|
||||
$"Neither of {entity}'s {nameof(BallisticAmmoSelfRefillerComponent)}'s or {nameof(BallisticAmmoProviderComponent)}'s ammunition protos is specified. This is a configuration error as it means {nameof(BallisticAmmoSelfRefillerComponent)} cannot do anything.");
|
||||
return;
|
||||
}
|
||||
|
||||
SetBallisticUnspawned(entity, ammo.UnspawnedCount + 1);
|
||||
}
|
||||
else if (ammo.Proto == refillerAmmoProto)
|
||||
{
|
||||
// The ammo proto on the refiller and the provider match. Add an unspawned ammo.
|
||||
SetBallisticUnspawned(entity, ammo.UnspawnedCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can't use unspawned ammo, so spawn an entity and try to insert it.
|
||||
var ammoEntity = PredictedSpawnAttachedTo(refiller.AmmoProto, Transform(entity).Coordinates);
|
||||
var insertSucceeded = TryBallisticInsert(entity, ammoEntity, null, suppressInsertionSound: true);
|
||||
if (!insertSucceeded)
|
||||
{
|
||||
PredictedQueueDel(ammoEntity);
|
||||
Log.Error(
|
||||
$"Failed to insert ammo {ammoEntity} into non-full {entity}. This is a configuration error. Is the {nameof(BallisticAmmoSelfRefillerComponent)}'s {nameof(BallisticAmmoSelfRefillerComponent.AmmoProto)} incorrect for the {nameof(BallisticAmmoProviderComponent)}'s {nameof(BallisticAmmoProviderComponent.Whitelist)}?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -682,6 +682,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||
public override void Update(float frameTime)
|
||||
{
|
||||
UpdateBattery(frameTime);
|
||||
UpdateBallistic(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,12 +116,14 @@
|
|||
- type: ContainerContainer
|
||||
containers:
|
||||
ballistic-ammo: !type:Container
|
||||
- type: BatteryAmmoProvider
|
||||
- type: BallisticAmmoProvider
|
||||
whitelist:
|
||||
tags:
|
||||
- CartridgeLightRifle
|
||||
capacity: 100
|
||||
proto: CartridgeLightRifle
|
||||
fireCost: 100
|
||||
- type: Battery
|
||||
maxCharge: 10000
|
||||
startingCharge: 10000
|
||||
- type: BatterySelfRecharger
|
||||
autoRechargeRate: 25
|
||||
cycleable: false # No synthesizing ammo for your syndicate masters.
|
||||
- type: BallisticAmmoSelfRefiller
|
||||
autoRefillRate: 4s
|
||||
affectedByEmp: true
|
||||
- type: AmmoCounter
|
||||
|
|
|
|||
|
|
@ -130,14 +130,16 @@
|
|||
- type: ContainerContainer
|
||||
containers:
|
||||
ballistic-ammo: !type:Container
|
||||
- type: BatteryAmmoProvider
|
||||
proto: BulletPistol
|
||||
fireCost: 100
|
||||
- type: Battery
|
||||
maxCharge: 1000
|
||||
startingCharge: 1000
|
||||
- type: BatterySelfRecharger
|
||||
autoRechargeRate: 25
|
||||
- type: BallisticAmmoProvider
|
||||
whitelist:
|
||||
tags:
|
||||
- CartridgePistol
|
||||
capacity: 10
|
||||
proto: CartridgePistol
|
||||
cycleable: false # No synthesizing ammo for your syndicate masters.
|
||||
- type: BallisticAmmoSelfRefiller
|
||||
autoRefillRate: 4s
|
||||
affectedByEmp: true
|
||||
- type: AmmoCounter
|
||||
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -152,14 +152,16 @@
|
|||
- type: ContainerContainer
|
||||
containers:
|
||||
ballistic-ammo: !type:Container
|
||||
- type: BatteryAmmoProvider
|
||||
- type: BallisticAmmoProvider
|
||||
whitelist:
|
||||
tags:
|
||||
- CartridgePistol
|
||||
capacity: 30
|
||||
proto: CartridgePistol
|
||||
fireCost: 100
|
||||
- type: Battery
|
||||
maxCharge: 3000
|
||||
startingCharge: 3000
|
||||
- type: BatterySelfRecharger
|
||||
autoRechargeRate: 25
|
||||
cycleable: false # No synthesizing ammo for your syndicate masters.
|
||||
- type: BallisticAmmoSelfRefiller
|
||||
autoRefillRate: 4s
|
||||
affectedByEmp: true
|
||||
- type: AmmoCounter
|
||||
|
||||
- type: entity
|
||||
|
|
|
|||
Loading…
Reference in New Issue