2021-01-25 05:53:36 +00:00
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
2026-08-10 14:57:50 +01:00
|
|
|
using MessagePack;
|
|
|
|
|
using Newtonsoft.Json;
|
2026-09-14 17:39:12 +02:00
|
|
|
using CultMath;
|
|
|
|
|
using static CultMath.math;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
|
|
|
|
|
public class ChargedWeaponData : InstantWeaponData
|
|
|
|
|
{
|
|
|
|
|
[Inspectable, JsonProperty("chargeTime"), Key(21), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat ChargeTime = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeEnergy"), Key(22), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat ChargeEnergy = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeHeat"), Key(23), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat ChargeHeat = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("canFireEarly"), Key(24)]
|
|
|
|
|
public bool CanFireEarly;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("failureCharge"), Key(25)]
|
|
|
|
|
public float FailureCharge;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("failureDamage"), Key(26)]
|
|
|
|
|
public float FailureDamage = 1;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeDamage"), Key(27)]
|
|
|
|
|
public float ChargeFiringDamageMultiplier = 1;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeSpread"), Key(28)]
|
|
|
|
|
public float ChargeFiringSpreadMultiplier = 1;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeBurstCount"), Key(29)]
|
|
|
|
|
public float ChargeFiringBurstCountMultiplier = 1;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeVisibility"), Key(30)]
|
|
|
|
|
public float ChargeFiringVisibilityMultiplier = 1;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeVelocity"), Key(31)]
|
|
|
|
|
public float ChargeFiringVelocityMultiplier = 1;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("chargeHeatMul"), Key(32)]
|
|
|
|
|
public float ChargeFiringHeatMultiplier = 1;
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(EquippedItem item)
|
|
|
|
|
{
|
|
|
|
|
return new ChargedWeapon(this, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(ConsumableItemEffect item)
|
|
|
|
|
{
|
|
|
|
|
return new ChargedWeapon(this, item);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
public class ChargedWeapon : InstantWeapon
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
private ChargedWeaponData _data;
|
2021-01-25 05:53:36 +00:00
|
|
|
private bool _charging;
|
|
|
|
|
private bool _charged;
|
|
|
|
|
private float _charge;
|
|
|
|
|
private float _progress;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
public float ChargeTime { get; protected set; }
|
|
|
|
|
public float ChargeEnergy { get; protected set; }
|
|
|
|
|
public float ChargeHeat { get; protected set; }
|
Cut 4: input capacitors close the last direct capacitor spend
Deletes Entity.CanSpendCapacitorCharge/TrySpendCapacitorCharge (and the
now-dead _capacitors bookkeeping list that only fed them) -- Cut 3's
named, temporary exception for the four instant draws. InstantWeapon
(both fire sites), Sensor.Ping, and Shield.CanTakeHit/TakeHit now spend
from their own InputCapacitor, filled continuously by PowerBus like
every other IPowerConsumer instead of touching the entity's shared bus
capacitors directly.
InputCapacitor's Capacity/Rate derive from the owning item's Energy and
Cooldown, with an authored-override hook (ChargedWeapon repurposes its
previously-unused ChargeEnergy stat as that override, per the map's own
instruction, rather than adding a new field). A weapon/sensor fire
attempt only ever spends exactly Capacity, so it only ever succeeds at
full charge -- no fractional shot. Shield is the map's named exception:
its reserve is continuous, not an activation buffer, so it allows a
partial draw; its size (no Energy/Cooldown pair exists on Shield) is a
flagged judgment call, not a spec'd formula.
Adds InputCapacitorTests.cs (8 tests) and
mutation_tests_stats_power_cut4.py (7 mutations, all behave as
declared); removes the one PowerBusTests.cs test and Cut 3 mutation
case that exercised the now-deleted Entity methods.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 22:52:15 +02:00
|
|
|
|
|
|
|
|
// Cut 4 (docs/stats-and-power-cut.md, Cut 4): "its charge cycle already is a buffer, and ChargeEnergy
|
|
|
|
|
// should become the fill rate rather than a separate concept." ChargeEnergy was authored but never spent
|
|
|
|
|
// by anything (Trigger() always paid the base Weapon.Energy per shot); it becomes this weapon's input
|
|
|
|
|
// capacitor rate instead of the InstantWeapon default (Capacity / Cooldown), so a charged weapon's own
|
|
|
|
|
// authored charge-up speed IS its sustained rate of fire, not a second, disconnected number.
|
|
|
|
|
protected override float RateOverride => Evaluate(_data.ChargeEnergy);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
public override float DamagePerSecond => Damage * _data.ChargeFiringDamageMultiplier / (Cooldown + ChargeTime);
|
2021-02-26 19:01:47 +00:00
|
|
|
public override float RangeDamagePerSecond(float range)
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
return Damage *
|
|
|
|
|
_data.ChargeFiringDamageMultiplier *
|
|
|
|
|
_data.DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) /
|
2021-02-26 19:01:47 +00:00
|
|
|
(Cooldown + ChargeTime);
|
|
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
|
2021-01-30 01:02:11 +00:00
|
|
|
public event Action OnStartCharging;
|
|
|
|
|
public event Action OnStopCharging;
|
2021-01-25 05:53:36 +00:00
|
|
|
public event Action OnCharged;
|
|
|
|
|
public event Action OnFailed;
|
|
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
public override void ResetEvents()
|
|
|
|
|
{
|
2021-03-21 01:55:25 +00:00
|
|
|
base.ResetEvents();
|
2021-01-31 03:52:32 +00:00
|
|
|
OnStartCharging = null;
|
|
|
|
|
OnStopCharging = null;
|
|
|
|
|
OnCharged = null;
|
|
|
|
|
OnFailed = null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-25 05:53:36 +00:00
|
|
|
public override float Progress => saturate(_charging ? _charge : _cooldown);
|
|
|
|
|
|
|
|
|
|
public float Charge
|
|
|
|
|
{
|
|
|
|
|
get => _charge;
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
public ChargedWeapon(ChargedWeaponData data, EquippedItem item) : base(data, item)
|
2026-06-15 20:12:34 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
2026-06-15 20:12:34 +01:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
public ChargedWeapon(ChargedWeaponData data, ConsumableItemEffect item) : base(data, item)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
2021-04-18 05:13:20 +01:00
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
protected override void UpdateStats()
|
|
|
|
|
{
|
|
|
|
|
base.UpdateStats();
|
2026-08-10 14:57:50 +01:00
|
|
|
ChargeTime = Evaluate(_data.ChargeTime);
|
|
|
|
|
ChargeEnergy = Evaluate(_data.ChargeEnergy);
|
|
|
|
|
ChargeHeat = Evaluate(_data.ChargeHeat);
|
|
|
|
|
Damage *= lerp(1, _data.ChargeFiringDamageMultiplier, saturate(_charge));
|
|
|
|
|
Heat *= lerp(1, _data.ChargeFiringHeatMultiplier, saturate(_charge));
|
|
|
|
|
Spread *= lerp(1, _data.ChargeFiringSpreadMultiplier, saturate(_charge));
|
|
|
|
|
BurstCount *= lerp(1, _data.ChargeFiringBurstCountMultiplier, saturate(_charge));
|
|
|
|
|
Visibility *= lerp(1, _data.ChargeFiringVisibilityMultiplier, saturate(_charge));
|
|
|
|
|
Velocity *= lerp(1, _data.ChargeFiringVelocityMultiplier, saturate(_charge));
|
2021-01-31 03:52:32 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-14 03:29:14 +01:00
|
|
|
public override bool Execute(float dt)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
|
|
|
|
if (_charging)
|
|
|
|
|
{
|
2021-04-14 03:29:14 +01:00
|
|
|
_charge += dt / ChargeTime;
|
2021-04-30 06:39:57 +01:00
|
|
|
Item.SetAudioParameter(SpecialAudioParameter.ChargeLevel, saturate(_charge));
|
2021-01-31 03:52:32 +00:00
|
|
|
if (!_charged)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2021-04-18 05:13:20 +01:00
|
|
|
AddHeat(ChargeHeat * (dt / ChargeTime));
|
2021-01-31 03:52:32 +00:00
|
|
|
if(_charge > 1)
|
|
|
|
|
{
|
|
|
|
|
_charged = true;
|
|
|
|
|
OnCharged?.Invoke();
|
|
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
if (_data.FailureCharge > 1 && _charge > _data.FailureCharge)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
|
|
|
|
_charging = false;
|
|
|
|
|
_cooldown = 1;
|
2021-01-30 01:02:11 +00:00
|
|
|
_coolingDown = true;
|
|
|
|
|
_charge = 0;
|
2021-01-25 05:53:36 +00:00
|
|
|
OnFailed?.Invoke();
|
2021-04-30 06:39:57 +01:00
|
|
|
Item.FireAudioEvent(ChargedWeaponAudioEvent.Fail);
|
2026-08-10 14:57:50 +01:00
|
|
|
CauseDamage(_data.FailureDamage);
|
2021-01-25 05:53:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-14 03:29:14 +01:00
|
|
|
return base.Execute(dt);
|
2021-01-25 05:53:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Activate()
|
|
|
|
|
{
|
2026-09-29 22:15:51 +02:00
|
|
|
// 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.
|
2026-09-29 21:16:52 +02:00
|
|
|
// A refused weapon never starts charging: it would pay charge heat and audio for a shot Trigger drops.
|
2026-09-29 22:15:51 +02:00
|
|
|
if(!_charging && CanFire && !FireControl.Refuses(this, Entity))
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2021-01-30 01:02:11 +00:00
|
|
|
OnStartCharging?.Invoke();
|
2021-04-30 06:39:57 +01:00
|
|
|
Item.FireAudioEvent(ChargedWeaponAudioEvent.Start);
|
2021-01-25 05:53:36 +00:00
|
|
|
_charging = true;
|
|
|
|
|
_charged = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Deactivate()
|
|
|
|
|
{
|
2021-01-30 01:02:11 +00:00
|
|
|
if (_charging)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
if (_data.CanFireEarly || _charge > 1)
|
2021-01-30 01:02:11 +00:00
|
|
|
{
|
|
|
|
|
Trigger();
|
|
|
|
|
_charge = 0;
|
|
|
|
|
}
|
|
|
|
|
OnStopCharging?.Invoke();
|
2021-04-30 06:39:57 +01:00
|
|
|
Item.FireAudioEvent(ChargedWeaponAudioEvent.Stop);
|
2021-01-30 01:02:11 +00:00
|
|
|
_charging = false;
|
2021-01-25 05:53:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-16 09:01:05 +01:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|