Units/Targetable.cs
using System;
namespace PlanetMeat;
[Group( "Planet Meat - Units" ), Title( "Targetable" ), Icon( "center_focus_strong" )]
public sealed class Targetable : Component, Component.IDamageable, ITargeterStatus
{
[Property] public string Ident { get; set; } = "";
[Property] public ITargeterStatus.Teams Team { get; set; } = ITargeterStatus.Teams.Enemy;
public bool IsPlayerControlled => Team == ITargeterStatus.Teams.Player;
[Property] public Vector3 BullseyeCenter { get; set; }
[Property] public float BullseyeRadius { get; set; } = 16.0f;
public Vector3 BullseyeWorld => WorldTransform.PointToWorld( BullseyeCenter );
public Vector3 NextBullseye => BullseyeWorld + Game.Random.VectorInSphere( BullseyeRadius );
[Property] public int BaseMaxHp { get; set; } = 100;
[Property] public string MaxHpImprovement { get; set; }
public ValueAggregator<int> AggregateMaxHp => field ??= ValueAggregator<int>.FromImprovement( MaxHpImprovement, BaseMaxHp );
public int MaxHp => AggregateMaxHp.Total;
public float BaseResistPerc { get; set; } = 0.0f;
[Property] public string ResistPercImprovement { get; set; }
public ValueAggregator<float> AggregateResistPerc => field ??= ValueAggregator<float>.FromImprovement( ResistPercImprovement, BaseResistPerc );
public float ResistPercentage => AggregateResistPerc.Total;
public int BaseResistFlat { get; set; } = 0;
[Property] public string ResistFlatImprovement { get; set; }
public ValueAggregator<int> AggregateResistFlat => field ??= ValueAggregator<int>.FromImprovement( ResistFlatImprovement, BaseResistFlat );
public int ResistFlat => AggregateResistFlat.Total;
public float BaseRegen { get; set; } = 0.0f;
[Property] public string RegenImprovement { get; set; }
public ValueAggregator<float> AggregateRegen => field ??= ValueAggregator<float>.FromImprovement( RegenImprovement, BaseRegen );
public float Regen => AggregateRegen.Total;
private float RegenProgress { get; set; } = 0.0f;
[Property] public string ThornsReflectImprovement { get; set; } = "";
public ValueAggregator<float> AggregateThornsReflect => field ??= ValueAggregator<float>.FromImprovement( ThornsReflectImprovement );
[Property] public string PhoenixChargesImprovement { get; set; } = "";
public ValueAggregator<int> AggregatePhoenixCharges => field ??= ValueAggregator<int>.FromImprovement( PhoenixChargesImprovement );
[Property] public string PhoenixRegenImprovement { get; set; } = "";
public ValueAggregator<float> AggregatePhoenixRegen => field ??= ValueAggregator<float>.FromImprovement( PhoenixRegenImprovement );
[Property] public string PhoenixDmgImprovement { get; set; } = "";
public ValueAggregator<int> AggregatePhoenixDmg => field ??= ValueAggregator<int>.FromImprovement( PhoenixDmgImprovement );
[Property] public string PhoenixRangeImprovement { get; set; } = "";
public ValueAggregator<int> AggregatePhoenixRange => field ??= ValueAggregator<int>.FromImprovement( PhoenixRangeImprovement );
[Property] public GameObject BlameThorns { get; set; }
public bool HasThorns { get; private set; }
[Property] public GameObject BlamePhoenix { get; set; }
public bool HasPhoenix { get; private set; }
public int PhoenixChargesRemaining => AggregatePhoenixCharges.Total - _timesPhoenixed;
private int _timesPhoenixed = 0;
[Property] public string SoundOnHit { get; set; } = "";
public int CurrentHp
{
get
{
if ( field > MaxHp || (field < 0 && !Killed) )
field = MaxHp;
return field;
}
set
{
if ( value > MaxHp )
value = MaxHp;
field = value;
Killed = value <= 0;
}
} = -1;
public bool Killed
{
get;
private set
{
if ( field != value )
{
field = value;
if ( value )
{
foreach ( var listener in GameObject.Components.GetAll<ITargetableListener>() )
listener.OnKilled();
}
}
}
} = false;
protected override void OnStart()
{
base.OnStart();
var hasThornsTech = Research.Current.HasTech( "thorns" );
HasPhoenix = hasThornsTech && (BlamePhoenix?.IsValid ?? false);
HasThorns = hasThornsTech && (BlameThorns?.IsValid ?? false);
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( CurrentHp < MaxHp && Agenda.Current.Phase == Agenda.Phases.Wave )
{
RegenProgress += Regen * MaxHp * Time.Delta;
var hpRegen = (int)RegenProgress;
if ( hpRegen > 0 )
{
RegenProgress -= hpRegen;
CurrentHp += hpRegen;
}
}
}
private static void IncrementScopedStats( string statIdent, string targIdent = "", string blameIdent = "", double amount = 1.0 )
{
var stats = Career.Current;
stats.IncrementStat( statIdent + "_all", amount );
var hasTarg = targIdent != "";
if ( hasTarg )
stats.IncrementStat( statIdent + "_" + targIdent, amount );
if ( blameIdent != "" )
{
stats.IncrementStat( statIdent + "_by_" + blameIdent, amount );
if ( hasTarg )
stats.IncrementStat( statIdent + "_" + targIdent + "_by_" + blameIdent, amount );
}
}
private void IncrementScopedEnemyStats( string statIdent, string blameIdent, double amount = 1.0 ) => IncrementScopedStats( statIdent, Ident, blameIdent, amount );
public void OnDamage( in DamageInfo damage )
{
if ( damage.IsTargetValid( this ) )
{
var rawDmg = damage.Damage;
if ( rawDmg > 0.0f )
{
var finalDmg = Math.Max( 1, (int)Math.Ceiling( (1.0f - ResistPercentage) * rawDmg - ResistFlat ) );
if ( finalDmg >= CurrentHp && !damage.IsRetaliation() && _timesPhoenixed < AggregatePhoenixCharges.Total && HasPhoenix )
{
_timesPhoenixed++;
foreach ( var effect in Components.GetAll<EffectOnKilled>( FindMode.EnabledInSelfAndDescendants ) )
effect.PerformEffects();
CurrentHp = (int)Math.Ceiling( MaxHp * AggregatePhoenixRegen.Total );
var phoenixDmg = new DamageInfo( AggregatePhoenixDmg.Total, GameObject, BlamePhoenix );
phoenixDmg.SetRetaliation( true );
phoenixDmg.Explode( this, AggregatePhoenixRange.Total );
}
else
{
var overkill = finalDmg > CurrentHp ? finalDmg - CurrentHp : 0;
CurrentHp -= finalDmg;
if ( SoundOnHit != "" )
Sound.Play( SoundOnHit );
if ( HasThorns && !damage.IsRetaliation() && damage.FindAttackingTargetable() is Targetable attacker && !this.IsFriendlyTo( attacker ) )
{
var thornsDmg = new DamageInfo( AggregateThornsReflect.Total * finalDmg, GameObject, BlameThorns );
thornsDmg.SetRetaliation( true );
attacker.OnDamage( in thornsDmg );
}
var blameIdent = damage.FindBlameIdent();
if ( blameIdent == "" )
Log.Warning( $"Missing blame ident for damage from {damage.Attacker} using {damage.Weapon}" );
if ( Team == ITargeterStatus.Teams.Enemy )
{
IncrementScopedEnemyStats( "damage", blameIdent, finalDmg );
IncrementScopedEnemyStats( "overkill", blameIdent, overkill );
foreach ( var l in Lifesteal.All )
l.StealDamage( this, finalDmg - overkill );
if ( Killed )
{
foreach ( var l in Lifesteal.All )
l.KillHeal( this );
IncrementScopedEnemyStats( "kills", blameIdent );
if ( blameIdent != "" && Catalogue.Current.Categories.ContainsKey( blameIdent ) )
Career.Current.IncrementStat( "wep_kills" );
}
}
else
{
IncrementScopedStats( "hurt", "", blameIdent, finalDmg );
if ( Killed && blameIdent != "" )
Career.Current.IncrementStat( "defeated_by_" + blameIdent );
}
}
}
}
}
public void Instakill( string blameIdent = "" )
{
if ( !Killed )
{
CurrentHp = 0;
if ( blameIdent != "" )
{
if ( Team == ITargeterStatus.Teams.Enemy )
IncrementScopedEnemyStats( "kills", blameIdent );
else
Career.Current.IncrementStat( "defeated_by_" + blameIdent );
}
}
}
public void Instakill( DamageInfo dmg ) => Instakill( dmg.FindBlameIdent() );
protected override void DrawGizmos()
{
base.DrawGizmos();
Gizmo.Draw.Color = Color.Yellow;
Gizmo.Draw.LineSphere( BullseyeCenter, BullseyeRadius );
}
}