Aetheria/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs
Meta Vens 76aeeabfca EW 2: claims ride the act into the book; schematic and AI classify from them
Signature, ActiveEmission and Reveal carry Claimed (the class the ship's EW claims for the item, the true item's data by default); Entity gains the claim slots and one write port; Contact gains Profile, derived from the ship's claimed hull once Identified; Entity.RevealedWeapons is the one classifying read for weapons. Combat and the enemy schematic read it; their reads of the target's live gear are cut. Weapons rate by their claimed design (ew-claim-weapon-measure-ruled, rated-design): WeaponData.RatedRangeDamagePerSecond runs the same per-kind formula as the live weapon. Aim, damage and physics keep the truth.

Not yet compiled or run at this commit; the Unity-only SchematicDisplay is unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:17:14 +02:00

197 lines
7.2 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
using static CultMath.math;
[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);
}
// The one formula of a charged weapon's damage per second at a range (see InstantWeaponData.RangeDamagePerSecond).
internal static float RangeDamagePerSecond(BezierCurve damageCurve, float damage, float chargeFiringDamageMultiplier,
float minRange, float range, float cooldown, float chargeTime, float atRange) =>
damage * chargeFiringDamageMultiplier * damageCurve.Evaluate(saturate(unlerp(minRange, range, atRange))) / (cooldown + chargeTime);
public override float RatedRangeDamagePerSecond(float range) =>
RangeDamagePerSecond(DamageCurve, Damage.Max / (int) Count.Max, ChargeFiringDamageMultiplier, MinRange.Max, Range.Max,
Cooldown.Max, ChargeTime.Max, range);
}
public class ChargedWeapon : InstantWeapon
{
private ChargedWeaponData _data;
private bool _charging;
private bool _charged;
private float _charge;
private float _progress;
public float ChargeTime { get; protected set; }
public float ChargeEnergy { get; protected set; }
public float ChargeHeat { get; protected set; }
// 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);
public override float DamagePerSecond => Damage * _data.ChargeFiringDamageMultiplier / (Cooldown + ChargeTime);
public override float RangeDamagePerSecond(float range)
{
return ChargedWeaponData.RangeDamagePerSecond(_data.DamageCurve, Damage, _data.ChargeFiringDamageMultiplier,
MinRange, Range, Cooldown, ChargeTime, range);
}
public event Action OnStartCharging;
public event Action OnStopCharging;
public event Action OnCharged;
public event Action OnFailed;
public override void ResetEvents()
{
base.ResetEvents();
OnStartCharging = null;
OnStopCharging = null;
OnCharged = null;
OnFailed = null;
}
public override float Progress => saturate(_charging ? _charge : _cooldown);
public float Charge
{
get => _charge;
}
public ChargedWeapon(ChargedWeaponData data, EquippedItem item) : base(data, item)
{
_data = data;
}
public ChargedWeapon(ChargedWeaponData data, ConsumableItemEffect item) : base(data, item)
{
_data = data;
}
protected override void UpdateStats()
{
base.UpdateStats();
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));
}
public override bool Execute(float dt)
{
if (_charging)
{
_charge += dt / ChargeTime;
Item.SetAudioParameter(SpecialAudioParameter.ChargeLevel, saturate(_charge));
if (!_charged)
{
AddHeat(ChargeHeat * (dt / ChargeTime));
if(_charge > 1)
{
_charged = true;
OnCharged?.Invoke();
}
}
if (_data.FailureCharge > 1 && _charge > _data.FailureCharge)
{
_charging = false;
_cooldown = 1;
_coolingDown = true;
_charge = 0;
OnFailed?.Invoke();
Item.FireAudioEvent(ChargedWeaponAudioEvent.Fail);
CauseDamage(_data.FailureDamage);
}
}
return base.Execute(dt);
}
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 && CanFire && !FireControl.Refuses(this, Entity))
{
OnStartCharging?.Invoke();
Item.FireAudioEvent(ChargedWeaponAudioEvent.Start);
_charging = true;
_charged = false;
}
}
public override void Deactivate()
{
if (_charging)
{
if (_data.CanFireEarly || _charge > 1)
{
Trigger();
_charge = 0;
}
OnStopCharging?.Invoke();
Item.FireAudioEvent(ChargedWeaponAudioEvent.Stop);
_charging = false;
}
}
}