Gate a charged weapon's Activate on CanFire, and start a burst's cooldown with the tick it flew in
A released charge has a burst pending and no cooldown until its first round, so a second Activate in that window started a second charge; Activate now asks CanFire. The round that starts a burst's cooldown does so after the tick's decrement, which made every cooldown one tick long; it now charges that tick to the cooldown. Pins both, and that a burst triggered after a fired one is not ready before its first round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
819b282346
commit
742cb23f5b
3 changed files with 80 additions and 2 deletions
|
|
@ -160,8 +160,10 @@ public class ChargedWeapon : InstantWeapon
|
|||
|
||||
public override void Activate()
|
||||
{
|
||||
// CanFire also holds a weapon whose released charge has a burst pending: its cooldown starts at its first round,
|
||||
// so a charge started before then would overlap it.
|
||||
// A refused weapon never starts charging: it would pay charge heat and audio for a shot Trigger drops.
|
||||
if(!_charging && !_coolingDown && !FireControl.Refuses(this, Entity))
|
||||
if(!_charging && CanFire && !FireControl.Refuses(this, Entity))
|
||||
{
|
||||
OnStartCharging?.Invoke();
|
||||
Item.FireAudioEvent(ChargedWeaponAudioEvent.Start);
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo
|
|||
if (!_burstStarted)
|
||||
{
|
||||
_burstStarted = true;
|
||||
_cooldown = 1;
|
||||
_cooldown = 1 - dt / Cooldown;
|
||||
// The round starts the cooldown after this tick's decrement above: charge the tick to it, so the cadence stays Cooldown
|
||||
_coolingDown = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -711,4 +711,79 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
|
||||
Assert.False(weapon.CanFire);
|
||||
}
|
||||
|
||||
// A charge released with a burst pending has not started its cooldown, but it is not ready either: pressed
|
||||
// again before that burst's first round it would start a second charge over it. Kills: ChargedWeapon.Activate
|
||||
// that reads the cooldown alone instead of CanFire.
|
||||
[Fact]
|
||||
public void AChargedWeaponPressedAgainBeforeItsReleasedRoundFliesStartsNoSecondCharge()
|
||||
{
|
||||
var r = Build(charged: true, range: 100f);
|
||||
r.Shooter.Target.Value = null;
|
||||
Aim(r, float2(0, 1));
|
||||
var weapon = (ChargedWeapon) r.Gun.Weapon;
|
||||
var starts = 0;
|
||||
weapon.OnStartCharging += () => starts++;
|
||||
r.Zone.Update(.01f); // resolves the stats
|
||||
weapon.Activate();
|
||||
for (var i = 0; i < 15; i++) r.Zone.Update(.1f);
|
||||
Assert.Equal(1, starts);
|
||||
|
||||
weapon.Deactivate();
|
||||
weapon.Activate();
|
||||
r.Zone.Update(.1f);
|
||||
|
||||
Assert.Equal(1, starts);
|
||||
Assert.False((bool) typeof(ChargedWeapon).GetField("_charging", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(weapon));
|
||||
}
|
||||
|
||||
// Holding the trigger fires once per Cooldown: the round that starts the cooldown pays for the tick it flew in.
|
||||
// Cooldown 1 at .25 s ticks is a shot every 5 ticks, 12 in 15 s. And a burst whose every round is refused
|
||||
// leaves no cooldown: the same held trigger is ready every tick. Kills: a first round that starts the cooldown
|
||||
// at 1 after the tick's decrement (a shot every 6 ticks, 10 in 15 s); a cooldown left by a refused burst.
|
||||
[Theory]
|
||||
[InlineData(100f, 12)]
|
||||
[InlineData(29.5f, 0)]
|
||||
public void AHeldTriggerFiresOncePerCooldownAndARefusedBurstLeavesNone(float range, int shots)
|
||||
{
|
||||
var r = Build(fuse: WeaponFuse.Proximity, blast: 30f, range: range);
|
||||
r.Shooter.Target.Value = null;
|
||||
Aim(r, float2(0, 1));
|
||||
var weapon = (InstantWeapon) r.Gun.Weapon;
|
||||
var fired = 0;
|
||||
weapon.OnFire += _ => fired++;
|
||||
r.Zone.Update(.25f); // resolves the stats
|
||||
var readyTicks = 0;
|
||||
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
weapon.Activate();
|
||||
r.Zone.Update(.25f);
|
||||
if (weapon.CanFire) readyTicks++;
|
||||
}
|
||||
|
||||
Assert.Equal(shots, fired);
|
||||
if (shots == 0) Assert.Equal(60, readyTicks);
|
||||
}
|
||||
|
||||
// Kills: Trigger that leaves _burstStarted true from the previous burst, so the next burst reads as started
|
||||
// (and ready) before its first round has flown.
|
||||
[Fact]
|
||||
public void ABurstTriggeredAfterAFiredOneIsNotReadyBeforeItsFirstRound()
|
||||
{
|
||||
var r = Build(range: 100f);
|
||||
r.Shooter.Target.Value = null;
|
||||
Aim(r, float2(0, 1));
|
||||
var weapon = (InstantWeapon) r.Gun.Weapon;
|
||||
r.Zone.Update(.25f);
|
||||
weapon.Activate();
|
||||
r.Zone.Update(.25f);
|
||||
Assert.False(weapon.CanFire, "control: the first burst fired and is cooling");
|
||||
for (var i = 0; i < 10 && !weapon.CanFire; i++) r.Zone.Update(.25f);
|
||||
Assert.True(weapon.CanFire, "fixture: the cooldown expired");
|
||||
|
||||
weapon.Activate();
|
||||
|
||||
Assert.False(weapon.CanFire);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue