int getCombatOdds(CvUnit* pAttacker, CvUnit* pDefender)
{
float fOddsEvent;
float fOddsAfterEvent;
int iAttackerStrength;
int iAttackerFirepower;
int iDefenderStrength;
int iDefenderFirepower;
int iDefenderOdds;
int iAttackerOdds;
int iStrengthFactor;
int iDamageToAttacker;
int iDamageToDefender;
int iNeededRoundsAttacker;
int iNeededRoundsDefender;
int iMaxRounds;
int iAttackerLowFS;
int iAttackerHighFS;
int iDefenderLowFS;
int iDefenderHighFS;
int iFirstStrikes;
int iDefenderHitLimit;
int iI;
int iJ;
int iI3;
int iI4;
int iOdds = 0;
// setup battle, calculate strengths and odds
//////
//Added ST
iAttackerStrength = pAttacker->currCombatStr(NULL, NULL);
iAttackerFirepower = pAttacker->currFirepower(NULL, NULL);
iDefenderStrength = pDefender->currCombatStr(pDefender->plot(), pAttacker);
iDefenderFirepower = pDefender->currFirepower(pDefender->plot(), pAttacker);
FAssert((iAttackerStrength + iDefenderStrength) > 0);
FAssert((iAttackerFirepower + iDefenderFirepower) > 0);
iDefenderOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
if (iDefenderOdds == 0)
{
return 1000;
}
iAttackerOdds = GC.getDefineINT("COMBAT_DIE_SIDES") - iDefenderOdds;
if (iAttackerOdds == 0)
{
return 0;
}
iStrengthFactor = ((iAttackerFirepower + iDefenderFirepower + 1) / 2);
// calculate damage done in one round
//////
iDamageToAttacker = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));
iDamageToDefender = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));
// calculate needed rounds.
// Needed rounds = round_up(health/damage)
//////
iDefenderHitLimit = pDefender->maxHitPoints() - pAttacker->combatLimit();
iNeededRoundsAttacker = (std::max(0, pDefender->currHitPoints() - iDefenderHitLimit) + iDamageToDefender - 1 ) / iDamageToDefender;
iNeededRoundsDefender = (pAttacker->currHitPoints() + iDamageToAttacker - 1 ) / iDamageToAttacker;
iMaxRounds = iNeededRoundsAttacker + iNeededRoundsDefender - 1;
// calculate possible first strikes distribution.
// We can't use the getCombatFirstStrikes() function (only one result,
// no distribution), so we need to mimic it.
//////
iAttackerLowFS = (pDefender->immuneToFirstStrikes()) ? 0 : pAttacker->firstStrikes();
iAttackerHighFS = (pDefender->immuneToFirstStrikes()) ? 0 : (pAttacker->firstStrikes() + pAttacker->chanceFirstStrikes());
iDefenderLowFS = (pAttacker->immuneToFirstStrikes()) ? 0 : pDefender->firstStrikes();
iDefenderHighFS = (pAttacker->immuneToFirstStrikes()) ? 0 : (pDefender->firstStrikes() + pDefender->chanceFirstStrikes());
// For every possible first strike event, calculate the odds of combat.
// Then, add these to the total, weighted to the chance of that first
// strike event occurring
//////
for (iI = iAttackerLowFS; iI < iAttackerHighFS + 1; iI++)
{
for (iJ = iDefenderLowFS; iJ < iDefenderHighFS + 1; iJ++)
{
// for every possible combination of fs results, calculate the chance
if (iI >= iJ)
{
// Attacker gets more or equal first strikes than defender
iFirstStrikes = iI - iJ;
// For every possible first strike getting hit, calculate both
// the chance of that event happening, as well as the rest of
// the chance assuming the event has happened. Multiply these
// together to get the total chance (Bayes rule).
// iI3 counts the number of successful first strikes
//////
for (iI3 = 0; iI3 < (iFirstStrikes + 1); iI3++)
{
// event: iI3 first strikes hit the defender
// calculate chance of iI3 first strikes hitting: fOddsEvent
// f(k;n,p)=C(n,k)*(p^k)*((1-p)^(n-k))
// this needs to be in floating point math
//////
fOddsEvent = ((float)getBinomialCoefficient(iFirstStrikes, iI3)) * pow((((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES")), iI3) * pow((1.0f - (((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES"))), (iFirstStrikes - iI3));
// calculate chance assuming iI3 first strike hits: fOddsAfterEvent
//////
if (iI3 >= iNeededRoundsAttacker)
{
fOddsAfterEvent = 1;
}
else
{
fOddsAfterEvent = 0;
// odds for _at_least_ (iNeededRoundsAttacker - iI3) (the remaining hits
// the attacker needs to make) out of (iMaxRounds - iI3) (the left over
// rounds) is the sum of each _exact_ draw
//////
for (iI4 = (iNeededRoundsAttacker - iI3); iI4 < (iMaxRounds - iI3 + 1); iI4++)
{
// odds of exactly iI4 out of (iMaxRounds - iI3) draws.
// f(k;n,p)=C(n,k)*(p^k)*((1-p)^(n-k))
// this needs to be in floating point math
//////
fOddsAfterEvent += ((float)getBinomialCoefficient((iMaxRounds - iI3), iI4)) * pow((((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES")), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES"))), ((iMaxRounds - iI3) - iI4));
}
}
// Multiply these together, round them properly, and add
// the result to the total iOdds
//////
iOdds += ((int)(1000.0 * (fOddsEvent*fOddsAfterEvent + 0.0005)));
}
}
else // (iI < iJ)
{
// Attacker gets less first strikes than defender
iFirstStrikes = iJ - iI;
// For every possible first strike getting hit, calculate both
// the chance of that event happening, as well as the rest of
// the chance assuming the event has happened. Multiply these
// together to get the total chance (Bayes rule).
// iI3 counts the number of successful first strikes
//////
for (iI3 = 0; iI3 < (iFirstStrikes + 1); iI3++)
{
// event: iI3 first strikes hit the defender
// First of all, check if the attacker is still alive.
// Otherwise, no further calculations need to occur
/////
if (iI3 < iNeededRoundsDefender)
{
// calculate chance of iI3 first strikes hitting: fOddsEvent
// f(k;n,p)=C(n,k)*(p^k)*((1-p)^(n-k))
// this needs to be in floating point math
//////
fOddsEvent = ((float)getBinomialCoefficient(iFirstStrikes, iI3)) * pow((((float)iDefenderOdds) / GC.getDefineINT("COMBAT_DIE_SIDES")), iI3) * pow((1.0f - (((float)iDefenderOdds) / GC.getDefineINT("COMBAT_DIE_SIDES"))), (iFirstStrikes - iI3));
// calculate chance assuming iI3 first strike hits: fOddsAfterEvent
//////
fOddsAfterEvent = 0;
// odds for _at_least_ iNeededRoundsAttacker (the remaining hits
// the attacker needs to make) out of (iMaxRounds - iI3) (the left over
// rounds) is the sum of each _exact_ draw
//////
for (iI4 = iNeededRoundsAttacker; iI4 < (iMaxRounds - iI3 + 1); iI4++)
{
// odds of exactly iI4 out of (iMaxRounds - iI3) draws.
// f(k;n,p)=C(n,k)*(p^k)*((1-p)^(n-k))
// this needs to be in floating point math
//////
fOddsAfterEvent += ((float)getBinomialCoefficient((iMaxRounds - iI3), iI4)) * pow((((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES")), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES"))), ((iMaxRounds - iI3) - iI4));
}
// Multiply these together, round them properly, and add
// the result to the total iOdds
//////
iOdds += ((int)(1000.0 * (fOddsEvent*fOddsAfterEvent + 0.0005)));
}
}
}
}
}
// Weigh the total to the number of possible combinations of first strikes events
// note: the integer math breaks down when #FS > 656 (with a die size of 1000)
//////
iOdds /= (((pDefender->immuneToFirstStrikes()) ? 0 : pAttacker->chanceFirstStrikes()) + 1) * (((pAttacker->immuneToFirstStrikes()) ? 0 : pDefender->chanceFirstStrikes()) + 1);
// finished!
//////
return iOdds;
}