This method assumes that both unit and target start at the same location, and that both will continuously run in a straight line in the same direction. Every time enemy gets within the attack range of unit, unit will attack but it needs to stand still and complete it's attack animation before being able to run or attack again. Attack will hit once it completes it's fore swing even if the back swing and idle time were not able to be completed due to duration ending. Even if enemy is able to get out of unit attack range while unit is doing his attack animation, attack will still hit and animation will still complete.
Is there anything I can do to make it more optimized? I need it to have the best performance it can get.
public static float EstimatedNumberOfAttacks(this Unit unit, Unit target, float duration, float overrideMoveSpeed = 0)
{
var immobileDuration = target.GetImmobileDuration();
var attackAnimation = unit.ForeSwing + unit.BackSwing + unit.AttackIdleTime;
//target is not able to move for whole duration
if (!target.IsMoving() || immobileDuration >= duration)
{
return (float) Math.Floor(duration / attackAnimation);
}
var durationLeft = duration;
float endDuration;
var enemySpeed = target.MovementSpeed;
var unitSpeed = overrideMoveSpeed <= 0 ? unit.MovementSpeed : overrideMoveSpeed;
var speedDifference = unitSpeed - enemySpeed;
var numberOfAttacksWhileImmobile = (float) Math.Ceiling(immobileDuration / attackAnimation);
var immobileTimeElapsed = numberOfAttacksWhileImmobile * attackAnimation;
var distanceTraveledAfterImmobile = (immobileTimeElapsed - immobileDuration) * enemySpeed;
//duration ends in the middle of the attack animation of your last attack before immobility ends
if ((endDuration = durationLeft - immobileTimeElapsed + attackAnimation) < 0)
{
while (endDuration < unit.ForeSwing)
{
numberOfAttacksWhileImmobile--;
endDuration += attackAnimation;
}
return numberOfAttacksWhileImmobile;
}
durationLeft -= immobileTimeElapsed;
var startNumberOfAttacks = 0f;
var startTimeElapsed = 0f;
float startCatchUpTime;
var distanceTraveledDuringAttack = attackAnimation * enemySpeed;
var unitAttackRange = unit.GetAttackRange();
//enemy was not able to get out of attack range within the last attack animation before immobility ended
if (unitAttackRange >= distanceTraveledAfterImmobile)
{
float startDistance;
//enemy was not able to get out of attack range after a single attack
if (distanceTraveledDuringAttack <= unitAttackRange - distanceTraveledAfterImmobile)
{
startNumberOfAttacks = (float) Math.Ceiling((unitAttackRange - distanceTraveledAfterImmobile) / distanceTraveledDuringAttack);
startDistance = startNumberOfAttacks * distanceTraveledDuringAttack;
startTimeElapsed = startDistance / enemySpeed;
}
else
{
startNumberOfAttacks = 1;
startDistance = distanceTraveledDuringAttack + distanceTraveledAfterImmobile;
startTimeElapsed = attackAnimation;
}
//duration ends in the middle of the attack animation of your last attack before enemy is able to get out of your attack range
if ((endDuration = durationLeft - startTimeElapsed + attackAnimation) < 0)
{
while (endDuration < unit.ForeSwing)
{
startNumberOfAttacks--;
endDuration += attackAnimation;
}
return numberOfAttacksWhileImmobile + startNumberOfAttacks;
}
startCatchUpTime = startDistance / speedDifference;
}
else
{
startCatchUpTime = distanceTraveledAfterImmobile / speedDifference;
}
//target is uncatchable
if (speedDifference <= 0)
{
return numberOfAttacksWhileImmobile + startNumberOfAttacks;
}
durationLeft -= startTimeElapsed + startCatchUpTime;
durationLeft -= attackAnimation;
startNumberOfAttacks++;
//see if last attack was able to hit
if (durationLeft < 0)
{
if (durationLeft + attackAnimation > unit.ForeSwing)
{
startNumberOfAttacks--;
}
return numberOfAttacksWhileImmobile + startNumberOfAttacks;
}
//when both are now running its just a repeating pattern of catch up-attack-catch up-attack until duration ends
var catchUpTime = distanceTraveledDuringAttack / speedDifference;
var additionalAttacks = (float) Math.Floor(durationLeft / (catchUpTime + attackAnimation));
var additionalAttackTimeTaken = (catchUpTime + attackAnimation) * additionalAttacks;
durationLeft -= additionalAttackTimeTaken;
durationLeft -= catchUpTime;
//see if last attack was able to hit
if (durationLeft > unit.ForeSwing) additionalAttacks++;
return numberOfAttacksWhileImmobile + startNumberOfAttacks + additionalAttacks;
}
Mathf
instead ofMath
, since it is designed for 32 bit floats. Other than that, your while loops are the only bottlenecks, as @juvian noted. Without them, my guess is that the code runs in <1ms. \$\endgroup\$