// Assets/_Project/Scripts/Gameplay/EnemyAbilities/SplitOnDeathAbilityDefinition.cs
using UnityEngine;
using TD.Core;
namespace TD.Gameplay.EnemyAbilities
{
///
/// On death, spawns smaller copies of the dying enemy's own type at
/// the corpse's position, continuing on toward the goal instead of restarting from the wave's
/// spawner.
///
///
/// No separate minion asset. The copies reuse whichever
/// and prefab the dying enemy was spawned from; every stat is expressed here as a percentage
/// of what the parent actually spawned with, so one card covers any enemy it lands on.
///
/// Percentages are of the PARENT, not the asset. If other cards on the same wave
/// have already modified the enemy, the minions scale off the modified values — a split of a
/// buffed enemy produces buffed minions, which is what players will expect from watching it.
///
/// Minions inherit no abilities at all — not even this one. That is what makes the
/// recursion terminate: a minion of a splitting enemy is a plain enemy, so a wave carrying
/// Split produces exactly one extra generation regardless of how many other cards it has
/// collected. Enforced at the spawn site, not here.
///
[CreateAssetMenu(fileName = "SplitOnDeathAbility", menuName = "TD/Enemy Abilities/Split On Death")]
public class SplitOnDeathAbilityDefinition : EnemyAbilityDefinition
{
public override EnemyAbilityKind Kind => EnemyAbilityKind.SplitOnDeath;
[Header("Split")]
[Tooltip("How many copies spawn when the parent dies.")]
[Min(1)]
public int SplitCount = 2;
[Header("Minion stats (percent of the parent's spawned values)")]
[Tooltip("Minion max HP as a fraction of the parent's max HP.")]
[Range(0.01f, 2f)]
public float HpPercent = 0.5f;
[Tooltip("Minion move speed as a fraction of the parent's speed. Above 1 makes the split " +
"faster than what died — the classic 'smaller and quicker' read.")]
[Range(0.01f, 3f)]
public float SpeedPercent = 1.25f;
[Tooltip("Minion transform scale as a fraction of the parent's scale. Requires the enemy " +
"prefab's NetworkTransform to sync scale, or minions render full-size on remote peers.")]
[Range(0.1f, 2f)]
public float ScalePercent = 0.6f;
[Tooltip("Minion lives cost as a fraction of the parent's, rounded down but never below 1 " +
"— a leak always has to hurt, or splits become a way to farm free ground.")]
[Range(0f, 2f)]
public float LivesCostPercent = 1f;
[Tooltip("Minion flight height as a fraction of the parent's. Only meaningful when the " +
"parent is flying; ground splits ignore it.")]
[Range(0.1f, 2f)]
public float FlightHeightPercent = 1f;
[Header("Placement")]
[Tooltip("Random scatter radius (world units) applied to each spawn so minions don't " +
"stack exactly on top of each other.")]
[Min(0f)]
public float ScatterRadius = 0.5f;
public override void ServerOnDeath(EnemyAbility instance, EnemyHealth health)
{
var def = health.Definition;
if (def == null) return;
var movement = instance.GetComponent();
var atTile = GridCoordinates.WorldToGrid(instance.transform.position);
var ownerSlot = movement != null ? movement.OriginZone : PlayerSlot.None;
// Derive the minion's stats from what the parent actually spawned as.
var parent = instance.SpawnContext;
var minion = new EnemySpawnContext
{
MaxHp = Mathf.Max(1f, parent.MaxHp * HpPercent),
MoveSpeed = Mathf.Max(0.01f, parent.MoveSpeed * SpeedPercent),
IsFlying = parent.IsFlying,
FlightHeight = parent.FlightHeight * FlightHeightPercent,
LivesCost = Mathf.Max(1, Mathf.FloorToInt(parent.LivesCost * LivesCostPercent)),
VisualScale = parent.VisualScale * ScalePercent,
};
WaveManager.Instance?.ServerSpawnSplitEnemies(
def, SplitCount, atTile, ownerSlot, ScatterRadius, minion);
}
}
}