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 LockWeaponData : InstantWeaponData { [Inspectable, JsonProperty("speed"), Key(21), RuntimeInspectable] public PerformanceStat LockSpeed = new PerformanceStat(); [Inspectable, JsonProperty("sensorImpact"), Key(22)] public PerformanceStat SensorImpact = new PerformanceStat(); [Inspectable, JsonProperty("threshold"), Key(23), RuntimeInspectable] public PerformanceStat LockAngle = new PerformanceStat(); [Inspectable, JsonProperty("directionImpact"), Key(24)] public PerformanceStat DirectionImpact = new PerformanceStat(); [Inspectable, JsonProperty("decay"), Key(25)] public PerformanceStat Decay = new PerformanceStat(); public override Behavior CreateInstance(EquippedItem item) { return new LockWeapon(this, item); } public override Behavior CreateInstance(ConsumableItemEffect item) { return new LockWeapon(this, item); } } public class LockWeapon : InstantWeapon { private LockWeaponData _data; private float _lock; private bool _locking; private Entity _target; public event Action OnLocked; public event Action OnBeginLocking; public event Action OnLockLost; public float LockSpeed { get; private set; } public float SensorImpact { get; private set; } public float LockAngle { get; private set; } public float DirectionImpact { get; private set; } public float Decay { get; private set; } public override float Progress => saturate(_cooldown > 0 ? _cooldown : _lock); public override bool CanFire => base.CanFire && _lock > .99f && Entity.TargetRange > MinRange && Entity.TargetRange < Range; public float Lock { get => saturate(_lock); } // Cut 3 (docs/fire-control-cut.md): the same threshold CanFire above already gates firing on, exposed so // FireControl.HitProbability can zero an unlocked LockWeapon's probability for AI/HUD decisions made // before a shot is ever attempted. public bool IsLocked => _lock > .99f; // Q12 A: a launcher cannot mine. public override bool CanMine => false; public LockWeapon(LockWeaponData data, EquippedItem item) : base(data, item) { _data = data; } public LockWeapon(LockWeaponData data, ConsumableItemEffect item) : base(data, item) { _data = data; } public override bool Execute(float dt) { // Mining Cut 3 (Q12 A): lock builds on an entity only. A chunk target reads as no target here, so a // launcher never locks a rock. var target = Entity.Target.Value.Entity; if (_target != target) { _lock = 0; _target = target; } if (target != null && Entity.IsHostileTo(target)) { LockSpeed = Evaluate(_data.LockSpeed); SensorImpact = Evaluate(_data.SensorImpact); LockAngle = Evaluate(_data.LockAngle); DirectionImpact = Evaluate(_data.DirectionImpact); Decay = Evaluate(_data.Decay); var degrees = acos(dot(normalize(target.Position - Entity.Position), normalize(Entity.Aim))) * 57.2958f; if (degrees < LockAngle) { var lerp = 1 - unlerp(0, 90, degrees); _lock = saturate(_lock + pow(lerp, DirectionImpact) * dt * LockSpeed * pow(Entity.EntityInfoGathered[target], SensorImpact)); target.ReceivePaint(Entity.Position, Item?.Data, Lock); } else _lock = saturate(_lock - dt * Decay); } return base.Execute(dt); } } // What a painted ship senses of one beam on it (ruling sensor-stat-set item 4; strength per ruling // lock-warning-strength). Strength is the emitter's own lock progress at the paint, 0..1, never summed. public readonly struct LockWarning { public readonly float2 Bearing; public readonly EquippableItemData EmitterClass; public readonly float Strength; public LockWarning(float2 bearing, EquippableItemData emitterClass, float strength) { Bearing = bearing; EmitterClass = emitterClass; Strength = strength; } }