Units/AutoWeapon.cs
using System;
namespace PlanetMeat;
[Group( "Planet Meat - Units" ), Title( "AutoWeapon" ), Icon( "search" )]
public sealed class AutoWeapon : Component, IUpgradeableAttack, IAttackBlame, IEnemyAttackReceiver
{
public const float FAST_RECHECK = 0.2f;
[Property] public float AimRotationSpeed = 5.0f;
[Property] public int BaseDamage { get; set; } = 5;
[Property] public string DamageImprovement { get; set; } = "";
public ValueAggregator<int> AggregateDamage => field ??= ValueAggregator<int>.FromImprovement( DamageImprovement, BaseDamage );
[Property] public float BaseRecharge { get; set; } = 1.0f;
[Property] public string RechargeImprovement { get; set; } = "";
public ValueAggregator<float> AggregateRecharge => field ??= ValueAggregator<float>.FromImprovement( RechargeImprovement, BaseRecharge );
[Property] public bool IsMultishotter { get; set; } = false;
[Property] public string MultishotRateImprovement { get; set; } = "";
public ValueAggregator<float> AggregateMultishotRate => field ??= ValueAggregator<float>.FromImprovement( MultishotRateImprovement );
private float MultishotProgress { get; set; } = 0.0f;
private int _bakedMultishotShots = 0;
private bool TrackMultishots { get; set; }
[Property] public string MultishotAmountImprovement { get; set; } = "";
public ValueAggregator<int> AggregateMultishotAmount => field ??= ValueAggregator<int>.FromImprovement( MultishotAmountImprovement );
[Property] public string MultishotDamageImprovement { get; set; } = "";
public ValueAggregator<float> AggregateMultishotDamage => field ??= ValueAggregator<float>.FromImprovement( RechargeImprovement );
public float ShotDelay => 1.0f / this.GetAttackRecharge();
private float untilShot = 0.0f;
[Property] public string Ident { get; set; } = "";
[Property] public Animator Animator { get; set; }
[Property] private GameObject ModelGameObject { get; set; }
[Property] private GameObject MuzzleGameObject { get; set; }
[Property] private GameObject MuzzleEffect { get; set; }
[Property] private GameObject TracerEffect { get; set; }
public int ShotsFired { get; set; } = 0;
public Targeter Targeter => field ??= this.FindNearestUpwards<Targeter>();
private Targetable LatestTarget { get; set; } = null;
private Vector3 LatestValidAimPosition { get; set; }
protected override void OnStart()
{
base.OnStart();
if ( Targeter is not null )
untilShot = FAST_RECHECK;
else
Log.Warning( $"{this} is an AutoWeapon with no Targeter" );
TrackMultishots = IsMultishotter && Research.Current.HasTech( "multishot" );
if ( TrackMultishots )
ApplyMultishotAmount();
}
public void ApplyMultishotAmount()
{
Targeter?.NumTargets = _bakedMultishotShots = 1 + AggregateMultishotAmount.Total;
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( Agenda.Current.Phase != Agenda.Phases.Wave || Targeter.Status.Killed )
return;
if ( untilShot > -0.01f )
{
untilShot -= Time.Delta;
while ( untilShot < 0.01f )
{
var targets = Targeter.NextTargets;
if ( targets.Count > 0 )
{
var numShots = 1;
if ( TrackMultishots )
{
MultishotProgress += AggregateMultishotRate.Total;
if ( MultishotProgress > 0.99f )
numShots = _bakedMultishotShots;
}
Shoot( targets, numShots );
LatestTarget = targets[0];
untilShot += ShotDelay;
}
else
{
LatestTarget = null;
untilShot = MathF.Min( FAST_RECHECK, ShotDelay );
}
}
}
if ( ModelGameObject is GameObject model && LatestTarget is Targetable t )
{
if ( t.IsValid )
LatestValidAimPosition = t.BullseyeWorld;
model.WorldRotation = Rotation.Lerp(
model.WorldRotation,
Rotation.LookAt( LatestValidAimPosition - model.WorldPosition ),
AimRotationSpeed * Time.Delta
);
}
}
private void ShootEffects( GameObject muzzle )
{
if ( muzzle is not null )
MuzzleEffect?.Clone( new CloneConfig { Parent = muzzle, Transform = global::Transform.Zero, StartEnabled = true } );
Animator?.Attack();
}
private DamageInfo CreateDamage( float dmg )
{
var d = new DamageInfo( dmg, Targeter.Status.GameObject, GameObject );
d.SetFriendlyFire( Targeter.CanTargetFriends );
return d;
}
private DamageInfo CreateDamage() => CreateDamage( this.GetAttackDamage() );
private void ShootTarget( Targetable targ, in DamageInfo damage, GameObject muzzle )
{
var tracer = TracerEffect?.Clone( new CloneConfig { Transform = (muzzle?.WorldTransform ?? WorldTransform).WithScale( 1.0f ), StartEnabled = true } );
tracer?.GetComponentInChildren<ITargetedEffect>()?.SetTarget( targ.NextBullseye );
targ.OnDamage( in damage );
ShotsFired++;
}
private void Shoot( Targetable targ )
{
var muzzle = MuzzleGameObject;
ShootEffects( muzzle );
ShootTarget( targ, CreateDamage(), muzzle );
}
private void Shoot( List<Targetable> targs, int numShots )
{
if ( targs.Count < numShots )
numShots = targs.Count;
if ( numShots < 1 )
return;
if ( numShots == 1 )
{
Shoot( targs[0] );
}
else
{
MultishotProgress -= 1.0f;
var muzzle = MuzzleGameObject;
ShootEffects( muzzle );
var dmg = CreateDamage( this.GetAttackDamage() * AggregateMultishotDamage.Total / numShots );
for ( int i = 0; i < numShots; i++ )
ShootTarget( targs[i], in dmg, muzzle );
}
}
}