It is pretty much impossible to distill the decision making code to the simple rule of thumb you would like. It isn't calculated from the % chances anyway, but from raw combat ratings.
Also the chance of evading is also modified by the chance of being caught if they evade.
Also, the unit also tests against other enemy units that might charge them later this turn if they don't evade, so the decision to evade may not be based on the unit that is currently charging them. (A gamey trick in FOG1 was to charge the enemy with a weak unit to trick them into standing, and then charge them with a strong one).
Also, troops who have a better shooting rating than the enemy are more likely to evade, because in the long run it may be better to keep shooting rather than get stuck in melee. (Even if they have a reasonable chance of winning this particular melee eventually - the longer it will take, the more chance of other enemy units intervening).
random element around the cut off point for evading or not.
Code: Select all
if (evade == 1)
{
evadeAP = GetBaseAttrib(me,"AP");
pursuitAP = GetAttrib(enemy,"AP");
// See whether unit decides to stand rather than evade
if ((IsUnitSquadType(me, "Light_Foot") != 1) || (IsLightTroops(enemy) == 1) || (IsRoughOrDifficult(GetUnitX(me),GetUnitY(me)) == 1) || (IsTileEdgeDefendibleObstacle(GetUnitX(me), GetUnitY(me), adjacent_X, adjacent_Y) > -1))
// v1.5.0 addition. Light foot always evade non-lights unless in rough or difficult or defending obstacle.
// Note that currently they don't automatically evade if alternative chargers could charge them not across an obstacle.
{
if ((IsFlankRearAttack(enemy, me) == 0) || ((IsLightTroops(me) == 0) && (IsLightTroops(enemy) == 1))) // Always evade if charged in flank/rear, unless non-lights being charged by lights
// V 1.2.2 change. Note that this does not take into account the guaranteed net +50 POA for a flank charge by lights on non-lights, but as the non-lights are likely to win the melee even
// if they disrupt on impact, I don't think this matters.
{
// Move enemy to fighting position, calculate combat margin, then move it back to starting position.
// Note: in the unlikely event that there is a unit there already it will get swapped out then swapped back again.
current_X = GetUnitX(enemy);
current_Y = GetUnitY(enemy);
UnitDeployIfPositionDifferent(enemy, adjacent_X, adjacent_Y, 1);
Log("Combat margin calc in pos: Chargee x,y, charger x,y", GetUnitX(me), GetUnitY(me), GetUnitX(enemy), GetUnitY(enemy));
combatMargin = CalculateModifiedCloseCombatRating(me, enemy, enemy, 0, 0) - CalculateModifiedCloseCombatRating(enemy, me, enemy, 0, 0);
Log("Main charger, combat margin", enemy, combatMargin);
Log("Pursuit AP", pursuitAP);
// Don't move the unit back to its start position until after testing other potential chargers, because its combat position may block them from charging
// Also check if any other nasty enemy could charge them this turn or next (from current position) - if so, and it is nastier than the charger, substitute the combat margin pertaining to it.
// This is intended to stop players pinning enemy light troops by charging them with something weak enough that they won't evade, and then immediately charging them with something nasty
// when they are in close combat and can therefore no longer evade.
// Note, however, does not currently take into account other pursuers that might contact it.
enemySide = GetEnemySide(me);
total = GetUnitCount(enemySide);
for (i = 0; i < total; i++)
{
id = GetUnitID(enemySide, i);
if ((id != -1) && (id != enemy))
{
if (GetDistanceBetween(id, me) <= GetBaseAttrib(id, "AP") / 4)
{
// Store current values of relevant attributes and set the attribs to start turn condition
actualAP = GetAttrib(id, "AP");
SetAttrib(id, "AP", GetBaseAttrib(id, "AP"));
largeTurn = GetAttrib(id, "MadeLargeTurn");
SetAttrib(id, "MadeLargeTurn", 0);
freeTurn = GetAttrib(id, "MadeFreeTurn");
SetAttrib(id, "MadeFreeTurn", 0);
shots = GetAttrib(id, "Shots");
SetAttrib(id, "Shots", GetBaseAttrib(id, "Shots"));
// Determine whether unit could charge this turn or next turn from current position, and if so whether it is nastier than the original charger.
if (CallUnitFunctionDirect(id, "CHECK_UNIT_ASSAULT", id, me) >= 0)
{
// Move enemy unit to fighting position, calculate combat margin, then move it back to starting position.
// Note: in the unlikely event that there is a unit there already it will get swapped out then swapped back again.
unit_X = GetUnitX(me);
unit_Y = GetUnitY(me);
potentialCharger_X = GetUnitX(id);
potentialCharger_Y = GetUnitY(id);
if (AreTilesAdjacent(potentialCharger_X, potentialCharger_Y, unit_X, unit_Y) == 1)
{
adjacent_X = potentialCharger_X;
adjacent_Y = potentialCharger_Y;
}
else
{
route = GetRouteCost(id, unit_X, unit_Y, 0, 1);
length = GetCheckRouteLength();
adjacent_X = GetCheckRouteX(length-2);
adjacent_Y = GetCheckRouteY(length-2);
}
UnitDeployIfPositionDifferent(id, adjacent_X, adjacent_Y, 1);
Log("Combat margin calc in pos: Chargee x,y, charger x,y", GetUnitX(me), GetUnitY(me), GetUnitX(id), GetUnitY(id));
newCombatMargin = CalculateModifiedCloseCombatRating(me, id, id, 0, 0) - CalculateModifiedCloseCombatRating(id, me, id, 0, 0);
combatMargin = Min(newCombatMargin, combatMargin);
Log("Unit, new combat margin, worst combat margin", id, newCombatMargin, combatMargin);
UnitDeployIfPositionDifferent(id, potentialCharger_X, potentialCharger_Y, 1);
// v1.5.8 addition - always evade if effective flank/rear charge from new unit is possible
if ((IsFlankRearAttack(id, me) == 1) && ((IsLightTroops(me) == 1) || (IsLightTroops(id) == 0)))
{
definitely_evade = 1;
}
// End v1.5.8 addition
}
// Restore previous Attrib values
SetAttrib(id, "AP", actualAP);
SetAttrib(id, "MadeLargeTurn", largeTurn);
SetAttrib(id, "MadeFreeTurn", freeTurn);
SetAttrib(id, "Shots", shots);
}
}
}
// Move original charger back to its starting position
UnitDeployIfPositionDifferent(enemy, current_X, current_Y, 1);
// v1.5.8 addition
if (definitely_evade == 0)
{
// End v1.5.8 addition
desiredMargin = 10 - Rand(0,10); // May need further tweaking
Log("\nShall we evade?: target, charger", me, enemy);
// Reduce desired margin if light foot being charged by light foot, or light horse by light horse
if ((IsLightTroops(me) == 1) && (IsLightTroops(enemy) == 1))
{
if ((IsMounted(me) == 1) && (IsMounted(enemy) == 1))
{
desiredMargin -= 12; // May need tweaking
}
if ((IsFoot(me) == 1) && (IsFoot(enemy) == 1))
{
desiredMargin -= 12; // May need tweaking
}
}
// Reduced desired margin if cavalry, camelry or light chariots
if ((IsUnitSquadType(me, "Cavalry") == 1) || (IsUnitSquadType(me, "Camelry") == 1) || (IsUnitSquadType(me, "Light_Chariots") == 1))
{
// desiredMargin -= 12; // may need tweaking.
Log("Before cavalry anti-evade adjustment: charger, target unit, desiredMargin, combatMargin", me, enemy, desiredMargin, combatMargin);
// v1.3.5 change
// Don't make anti-evade adjustment if unit has significantly higher shooting rating than charger.
// (Takes into account current ammunition state, although not how many turns of full ammo left).
myShootingRating = CalculateModifiedShootingRating(me, enemy, -1);
enemyShootingRating = CalculateModifiedShootingRating(enemy, me, -1);
comparator = (myShootingRating * 67) / 100;
Log("me (target unit), target shooting rating, charger shooting rating, comparator", me, myShootingRating, enemyShootingRating, comparator);
if ((myShootingRating == 0) || (enemyShootingRating > comparator))
{
desiredMargin -= 12; // may need tweaking.
Log("Cavalry Anti-evade boost applied");
}
else
{
Log("Cavalry Anti-evade boost NOT applied");
}
// End v1.3.5 change
}
Log("charger, target unit, desiredMargin, combatMargin", me, enemy, desiredMargin, combatMargin);
if (combatMargin >= desiredMargin) // Don't evade because we could win combat.
{
evade = 0;
}
else
{
// Consider not evading anyway if likely to be caught - however, the higher the risk of losing straight combat, the more likely to evade despite risk of being caught.
// Note that to catch the evaders, the pursuers need to reach a square adjacent to the evaders AND have sufficient AP left to assault the evaders' square.
safetyMargin = evadeAP - pursuitAP;
desiredMargin = 0;
if (combatMargin > 0) // May need further tweaking
{
desiredMargin = 8; // Will get away unless evader goes down on VMD AND charger goes up on VMD (6% chance of being caught)
}
else
{
if (combatMargin > -20) // May need further tweaking
{
desiredMargin = 4; // Will get away unless evader goes down on VMD OR charger goes up on VMD (and other doesn't go other way) (37.5% chance of being caught)
}
}
Log("Unit, enemy, AP: safetyMargin, desiredMargin", me, enemy, safetyMargin, desiredMargin);
// DebugLogX("Unit, enemy, AP: safetyMargin, desiredMargin", me, enemy, safetyMargin, desiredMargin);
if (safetyMargin < desiredMargin)
{
evade = 0;
}
}
} // v1.5.8 addition
}
}
}
From a design point of view, we are happy that this leaves the question of whether a unit will evade or not sometimes somewhat unpredictable. In real life, such things would not be easily predictable for the charging unit or either side's C-in-C.