How to calculate combat odds (moved from the Bugs subforum)

True except that there CAN be two 'winners' in the round - both units can land a hit (iirc).

Bad news, but not very bad ones. It means much more pn() to include - all probabilities that attacker loses x HP and at the same time defender loses y HP

And obviously, the strength depletion from a hit will have an impact on odds the next round.

Yes, of course - this is why pn's depend not only on i (the number of the round) but also current HP of the attacker and defender (AHP, DHP).

S.
 
Ok... well at least it sounds like you CAN compensate so we're making progress there. Keep letting me know what you need from me. At some point I'll have to figure out how to work the formula into the code and the more verbose the formula can be the better - I get easily lost with single letter termed variables so if the word(s) they represent can be used in full it will make it much easier for me to follow.
 
The last few years of my working life I specialised in translating from Mathematical specifications to Programming specifications.:D
 
It seems the most important thing is to determine all pn() - the probabilities of all possible results of a given, single round. I do not know how it is done in the code but if I would do it in the following way:

- to determine all pn (n=1 to ntot) and make sure p1+p2+...+pntot=1 (one may use expressions like wn/(w1+...+wntot) where wn are relative weights of an event n)

- generate a random number R from 0 to 1

- check the number:

0<=R<p1: event 1
p1<=R<p1+p2: event 2
p1+p2<=R<p1+p2+p3: event 3
...
p1+p2+p3+...+pntot-1<=R<=1: event ntot

I would like to ask you to search the code to look for anything like this (maybe this is a more tree-like one routine with generating a few random numbers).

S.
 
It sounds promising, because it may mean much less cases to include. My nightmare would be a range of possible values of damage (for a given round, based on current odds) with a strongly nonlinear function to randomly choose the actual value (e.g. preferring smaller, greater or central values). If the damage is non-random (even if it is calculated separately for each round), ntot is much smaller (something around 6 or 8).

S.
 
Yeah, it's just a calculation without any random element - not even based on the random hit check factor - once struck, damage is a fixed calculation.

BTW, sorry I didn't get to the odds bit last night - I got lost in the coding project I'm working on and kinda forgot to address it. I'll try to remember tonight ;)
 
Ok, this is getCombatOdds which is used as a basis for display... Again, there's still some confusion as to what draws on this result...
Code:
// FUNCTION: getCombatOdds
// Calculates combat odds, given two units
// Returns value from 0-1000
// Written by DeepO
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
	//////
	//TB Combat Mod begin

	int iDefenderSupportStrength = 0;
	int iAttackerSupportStrength = 0;
	if (GC.getGameINLINE().isOption(GAMEOPTION_STRENGTH_IN_NUMBERS))
	{
		iDefenderSupportStrength = pDefender->getDefenderSupportValue(pAttacker);
		iAttackerSupportStrength = pAttacker->getAttackerSupportValue();
	}
	
	//Added ST
	iAttackerStrength = pAttacker->currCombatStr(NULL, NULL) + iAttackerSupportStrength;
	iAttackerFirepower = pAttacker->currFirepower(NULL, NULL) + iAttackerSupportStrength;

	iDefenderStrength = pDefender->currCombatStr(pDefender->plot(), pAttacker) + iDefenderSupportStrength;
	iDefenderFirepower = pDefender->currFirepower(pDefender->plot(), pAttacker) + iDefenderSupportStrength;
	//TB Combat Mod end
	FAssert((iAttackerStrength + iDefenderStrength) > 0);
	FAssert((iAttackerFirepower + iDefenderFirepower) > 0);
/************************************************************************************************/
/* BETTER_BTS_AI_MOD                      02/21/10                                jdog5000      */
/*                                                                                              */
/* Efficiency, Lead From Behind                                                                 */
/************************************************************************************************/
	// From Lead From Behind by UncutDragon
/* original code
	iDefenderOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
*/	// modified
	/*iDefenderOdds = ((GC.getCOMBAT_DIE_SIDES() * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));*/
	// /UncutDragon
	//TB Combat Mod begin
	int iDefenderDodge = pDefender->dodgeVSOpponentProbTotal(pAttacker);
	int iDefenderPrecision = pDefender->precisionVSOpponentProbTotal(pAttacker);
	int iAttackerDodge = pAttacker->dodgeVSOpponentProbTotal(pDefender);
	int iAttackerPrecision = pAttacker->precisionVSOpponentProbTotal(pDefender);
	int iAttackerHitModifier = iAttackerPrecision - iDefenderDodge;
	int iDefenderHitModifier = iDefenderPrecision - iAttackerDodge;

	int iDefenderInitialOdds = ((GC.getCOMBAT_DIE_SIDES() * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
	int iDefenderHitOdds = std::max(5, iDefenderInitialOdds + ((iDefenderHitModifier * iDefenderInitialOdds)/100));

	int iAttackerInitialOdds = GC.getCOMBAT_DIE_SIDES() - iDefenderInitialOdds;
	int iAttackerHitOdds = std::max(5, iAttackerInitialOdds + ((iAttackerHitModifier * iAttackerInitialOdds)/100));

	iDefenderOdds = ((iDefenderHitOdds - iAttackerHitOdds)+ GC.getCOMBAT_DIE_SIDES())/2;
	iAttackerOdds = ((iAttackerHitOdds - iDefenderHitOdds)+ GC.getCOMBAT_DIE_SIDES())/2;
	//TB Combat Mods end
	if (iDefenderOdds == 0)
	{
		return 1000;
	}

	// UncutDragon
/* original code
	iAttackerOdds = GC.getDefineINT("COMBAT_DIE_SIDES") - iDefenderOdds;	
*/	// modified
	//iAttackerOdds = GC.getCOMBAT_DIE_SIDES() - iDefenderOdds;  	
	// /UncutDragon

	if (iAttackerOdds == 0)
	{
		return 0;
	}

	iStrengthFactor = ((iAttackerFirepower + iDefenderFirepower + 1) / 2);

	// calculate damage done in one round
	//////
	//TB Combat Mods (Armor Compare)
	int iAttackArmorTotal = pAttacker->armorVSOpponentProbTotal(pDefender);
	int iDefendPunctureTotal = pDefender->punctureVSOpponentProbTotal(pAttacker);
	int iAttackPunctureTotal = pAttacker->punctureVSOpponentProbTotal(pDefender);
	int iDefendArmorTotal = pDefender->armorVSOpponentProbTotal(pAttacker);

	int iUnmodifiedDefenderArmor = (iDefendArmorTotal - iAttackPunctureTotal);
	int iUnmodifiedAttackerArmor = (iAttackArmorTotal - iDefendPunctureTotal);
	int iModifiedDefenderArmorZero = (iUnmodifiedDefenderArmor < 0 ? 0 : iUnmodifiedDefenderArmor);
	int iModifiedAttackerArmorZero = (iUnmodifiedAttackerArmor < 0 ? 0 : iUnmodifiedAttackerArmor);
	int iModifiedDefenderArmor = (iModifiedDefenderArmorZero > 95 ? 95 : iModifiedDefenderArmorZero);
	int iModifiedAttackerArmor = (iModifiedAttackerArmorZero > 95 ? 95 : iModifiedAttackerArmorZero);

	int iDefenderArmor = (100 - iModifiedDefenderArmor);
	int iAttackerArmor = (100 - iModifiedAttackerArmor);

	int iDefendDamageModifierTotal = pDefender->damageModifierTotal();
	int iAttackDamageModifierTotal = pAttacker->damageModifierTotal();

	int iDamageToAttackerBase = ((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / std::max(1,(iAttackerFirepower + iStrengthFactor)));
	int iDamageToDefenderBase = ((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / std::max(1,(iDefenderFirepower + iStrengthFactor)));
	int iDamageToAttackerModified = iDamageToAttackerBase + ((iDamageToAttackerBase * iDefendDamageModifierTotal)/100);
	int iDamageToDefenderModified = iDamageToDefenderBase + ((iDamageToDefenderBase * iAttackDamageModifierTotal)/100);
	int iDamageToAttackerArmor = (iDamageToAttackerModified * iAttackerArmor)/100;
	int iDamageToDefenderArmor = (iDamageToDefenderModified * iDefenderArmor)/100;
	iDamageToAttacker  = std::max(1, iDamageToAttackerArmor);
	iDamageToDefender  = std::max(1, iDamageToDefenderArmor);
	//TB Combat Mods (Armor Compare) end
	// UncutDragon
/* original code
	iDamageToAttacker = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));
	iDamageToDefender = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));
*/	// modified
	//TB Combat Mods (Armor) begin
	//iDamageToAttacker = std::max(1,((((GC.getCOMBAT_DAMAGE() * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)) * iAttackerArmor)/100));
	//iDamageToDefender = std::max(1,((((GC.getCOMBAT_DAMAGE() * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)) * iDefenderArmor)/100));
	//TB Combat Mods (Armor) end
	// /UncutDragon

	// calculate needed rounds.
	// Needed rounds = round_up(health/damage)
	//////

	iDefenderHitLimit = pDefender->maxHitPoints() - pAttacker->combatLimit(pDefender);

	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
					//////

					// UncutDragon
/* original code
					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));
*/					// modified
					fOddsEvent = ((float)getBinomialCoefficient(iFirstStrikes, iI3)) * pow((((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES()), iI3) * pow((1.0f - (((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES())), (iFirstStrikes - iI3));
					// /UncutDragon

					// 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
							//////

							// UncutDragon
/* original code
							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));
*/							// modified
							fOddsAfterEvent += ((float)getBinomialCoefficient((iMaxRounds - iI3), iI4)) * pow((((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES()), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES())), ((iMaxRounds - iI3) - iI4));
							// /UncutDragon
						}
					}

					// 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
						//////

						// UncutDragon
/* original code
						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));
*/						// modified
						fOddsEvent = ((float)getBinomialCoefficient(iFirstStrikes, iI3)) * pow((((float)iDefenderOdds) / GC.getCOMBAT_DIE_SIDES()), iI3) * pow((1.0f - (((float)iDefenderOdds) / GC.getCOMBAT_DIE_SIDES())), (iFirstStrikes - iI3));
						// /UncutDragon

						// 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
							//////

							// UncutDragon
/* original code
							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));
*/							// modified
							fOddsAfterEvent += ((float)getBinomialCoefficient((iMaxRounds - iI3), iI4)) * pow((((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES()), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getCOMBAT_DIE_SIDES())), ((iMaxRounds - iI3) - iI4));
							// /UncutDragon
						}

						// Multiply these together, round them properly, and add 
						// the result to the total iOdds
						//////

						iOdds += ((int)(1000.0 * (fOddsEvent*fOddsAfterEvent + 0.0005)));
					}
				}				
			}
		}
	}
/************************************************************************************************/
/* BETTER_BTS_AI_MOD                       END                                                  */
/************************************************************************************************/

	// 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;
}
As noted in the notes at the beginning this one produces an integer from 0-100.
 
And getCombatOddsSpecific (this one produces a decimalized 'float' value)
Code:
//Calculates the probability of a particular combat outcome
//Returns a float value (between 0 and 1)
//Written by PieceOfMind
//n_A = hits taken by attacker, n_D = hits taken by defender.
float getCombatOddsSpecific(CvUnit* pAttacker, CvUnit* pDefender, int n_A, int n_D)
{
    int iAttackerStrength;
    int iAttackerFirepower;
    int iDefenderStrength;
    int iDefenderFirepower;
    int iDefenderOdds;
    int iAttackerOdds;
    int iStrengthFactor;
    int iDamageToAttacker;
    int iDamageToDefender;
    int iNeededRoundsAttacker;
    //int iNeededRoundsDefender;

    int AttFSnet;
    int AttFSC;
    int DefFSC;

    int iDefenderHitLimit;

	//TB Combat Mods (Armor Compare)
	int iAttackArmorTotal = pAttacker->armorVSOpponentProbTotal(pDefender);
	int iDefendPunctureTotal = pDefender->punctureVSOpponentProbTotal(pAttacker);
	int iAttackPunctureTotal = pAttacker->punctureVSOpponentProbTotal(pDefender);
	int iDefendArmorTotal = pDefender->armorVSOpponentProbTotal(pAttacker);

	int iUnmodifiedDefenderArmor = (iDefendArmorTotal - iAttackPunctureTotal);
	int iUnmodifiedAttackerArmor = (iAttackArmorTotal - iDefendPunctureTotal);
	int iModifiedDefenderArmorZero = (iUnmodifiedDefenderArmor < 0 ? 0 : iUnmodifiedDefenderArmor);
	int iModifiedAttackerArmorZero = (iUnmodifiedAttackerArmor < 0 ? 0 : iUnmodifiedAttackerArmor);
	int iModifiedDefenderArmor = (iModifiedDefenderArmorZero > 95 ? 95 : iModifiedDefenderArmorZero);
	int iModifiedAttackerArmor = (iModifiedAttackerArmorZero > 95 ? 95 : iModifiedAttackerArmorZero);

	int iDefenderArmor = (100 - iModifiedDefenderArmor);
	int iAttackerArmor = (100 - iModifiedAttackerArmor);

	int iDefenderSupportStrength = 0;
	int iAttackerSupportStrength = 0;
	if (GC.getGameINLINE().isOption(GAMEOPTION_STRENGTH_IN_NUMBERS))
	{
		iDefenderSupportStrength = pDefender->getDefenderSupportValue(pAttacker);
		iAttackerSupportStrength = pAttacker->getAttackerSupportValue();
	}
	//TB Combat Mods End

    iAttackerStrength = pAttacker->currCombatStr(NULL, NULL) + iAttackerSupportStrength;
    iAttackerFirepower = pAttacker->currFirepower(NULL, NULL) + iAttackerSupportStrength;
    iDefenderStrength = pDefender->currCombatStr(pDefender->plot(), pAttacker) + iDefenderSupportStrength;
    iDefenderFirepower = pDefender->currFirepower(pDefender->plot(), pAttacker) + iDefenderSupportStrength;

    iStrengthFactor = ((iAttackerFirepower + iDefenderFirepower + 1) / 2);

	int iDefendDamageModifierTotal = pDefender->damageModifierTotal();
	int iAttackDamageModifierTotal = pAttacker->damageModifierTotal();

	int iDamageToAttackerBase = ((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / std::max(1,(iAttackerFirepower + iStrengthFactor)));
	int iDamageToDefenderBase = ((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / std::max(1,(iDefenderFirepower + iStrengthFactor)));
	int iDamageToAttackerModified = iDamageToAttackerBase + ((iDamageToAttackerBase * iDefendDamageModifierTotal)/100);
	int iDamageToDefenderModified = iDamageToDefenderBase + ((iDamageToDefenderBase * iAttackDamageModifierTotal)/100);
	int iDamageToAttackerArmor = (iDamageToAttackerModified * iAttackerArmor)/100;
	int iDamageToDefenderArmor = (iDamageToDefenderModified * iDefenderArmor)/100;
	iDamageToAttacker  = std::max(1, iDamageToAttackerArmor);
	iDamageToDefender  = std::max(1, iDamageToDefenderArmor);
	//TB Combat Mods Begin (Armor/Puncture)
//  iDamageToAttacker = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));
//  iDamageToDefender = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));
	//iDamageToAttacker  = std::max(1, ((((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)) * iAttackerArmor)/100));
	//iDamageToDefender  = std::max(1, ((((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)) * iDefenderArmor)/100));
	//TB Combat Mods end (Armor/Puncture)
	//TB Combat Mods begin (Dodge/Precision)
	int iDefenderDodge = pDefender->dodgeVSOpponentProbTotal(pAttacker);
	int iDefenderPrecision = pDefender->precisionVSOpponentProbTotal(pAttacker);
	int iAttackerDodge = pAttacker->dodgeVSOpponentProbTotal(pDefender);
	int iAttackerPrecision = pAttacker->precisionVSOpponentProbTotal(pDefender);
	int iAttackerHitModifier = iAttackerPrecision - iDefenderDodge;
	int iDefenderHitModifier = iDefenderPrecision - iAttackerDodge;

	int iDefenderInitialOdds = ((GC.getCOMBAT_DIE_SIDES() * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
	int iDefenderHitOdds = std::max(5, iDefenderInitialOdds + ((iDefenderHitModifier * iDefenderInitialOdds)/100));

	int iAttackerInitialOdds = GC.getCOMBAT_DIE_SIDES() - iDefenderInitialOdds;
	int iAttackerHitOdds = std::max(5, iAttackerInitialOdds + ((iAttackerHitModifier * iAttackerInitialOdds)/100));

	iDefenderOdds = ((iDefenderHitOdds - iAttackerHitOdds)+ GC.getCOMBAT_DIE_SIDES())/2;
	iAttackerOdds = ((iAttackerHitOdds - iDefenderHitOdds)+ GC.getCOMBAT_DIE_SIDES())/2;
	//original:
    //iDefenderOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
    //iAttackerOdds = GC.getDefineINT("COMBAT_DIE_SIDES") - iDefenderOdds;
	//TB Combat Mods end (Dodge/Precision)

    if (GC.getDefineINT("ACO_IgnoreBarbFreeWins")==0)
    {
        if (pDefender->isBarbarian())
        {
            //defender is barbarian
            if (!GET_PLAYER(pAttacker->getOwnerINLINE()).isBarbarian() && GET_PLAYER(pAttacker->getOwnerINLINE()).getWinsVsBarbs() < GC.getHandicapInfo(GET_PLAYER(pAttacker->getOwnerINLINE()).getHandicapType()).getFreeWinsVsBarbs())
            {
                //attacker is not barb and attacker player has free wins left
                //I have assumed in the following code only one of the units (attacker and defender) can be a barbarian

                iDefenderOdds = std::min((10 * GC.getDefineINT("COMBAT_DIE_SIDES")) / 100, iDefenderOdds);
                iAttackerOdds = std::max((90 * GC.getDefineINT("COMBAT_DIE_SIDES")) / 100, iAttackerOdds);
            }
        }
        else if (pAttacker->isBarbarian())
        {
            //attacker is barbarian
            if (!GET_PLAYER(pDefender->getOwnerINLINE()).isBarbarian() && GET_PLAYER(pDefender->getOwnerINLINE()).getWinsVsBarbs() < GC.getHandicapInfo(GET_PLAYER(pDefender->getOwnerINLINE()).getHandicapType()).getFreeWinsVsBarbs())
            {
                //defender is not barbarian and defender has free wins left and attacker is barbarian
                iAttackerOdds = std::min((10 * GC.getDefineINT("COMBAT_DIE_SIDES")) / 100, iAttackerOdds);
                iDefenderOdds = std::max((90 * GC.getDefineINT("COMBAT_DIE_SIDES")) / 100, iDefenderOdds);
            }
        }
    }

    iDefenderHitLimit = pDefender->maxHitPoints() - pAttacker->combatLimit(pDefender);

    //iNeededRoundsAttacker = (std::max(0, pDefender->currHitPoints() - iDefenderHitLimit) + iDamageToDefender - (((pAttacker->combatLimit())==GC.getMAX_HIT_POINTS())?1:0) ) / iDamageToDefender;
    iNeededRoundsAttacker = (pDefender->currHitPoints() - pDefender->maxHitPoints() + pAttacker->combatLimit(pDefender) - (((pAttacker->combatLimit(pDefender))==pDefender->maxHitPoints())?1:0))/iDamageToDefender + 1;
	//TB Combat Mods begin
	int iNeededRoundsDefender = (pAttacker->currHitPoints() + iDamageToAttacker - 1 ) / iDamageToAttacker;
	//TB Combat Mods end

    int N_D = (std::max(0, pDefender->currHitPoints() - iDefenderHitLimit) + iDamageToDefender - (((pAttacker->combatLimit(pDefender))==pDefender->maxHitPoints())?1:0) ) / iDamageToDefender;

    //int N_A = (pAttacker->currHitPoints() + iDamageToAttacker - 1 ) / iDamageToAttacker;  //same as next line
    int N_A = (pAttacker->currHitPoints() - 1)/iDamageToAttacker + 1;


    //int iRetreatOdds = std::max((pAttacker->withdrawalProbability()),100);
	//  TB Combat Mods:
	//  Determine Attack Withdraw odds
	int iAttackerWithdraw = pAttacker->withdrawVSOpponentProbTotal(pDefender, pDefender->plot());
	int iDefenderPursuit = pDefender->pursuitVSOpponentProbTotal(pAttacker);
	int iAttackerEarly = pAttacker->earlyWithdrawTotal();
	int AdjustedAttWithdrawalstep1 = iAttackerWithdraw - iDefenderPursuit;
	int AdjustedAttWithdrawalstep2 = ((AdjustedAttWithdrawalstep1 > 100) ? 100 : AdjustedAttWithdrawalstep1);
	int AdjustedAttWithdrawal = ((AdjustedAttWithdrawalstep2 < 0) ? 0 : AdjustedAttWithdrawalstep2);
	
	int expectedrndcnt = std::min(iNeededRoundsDefender, iNeededRoundsAttacker);
	int expectedrnds = ((expectedrndcnt * iAttackerEarly)/100);

	int y = AdjustedAttWithdrawal;
	int z = AdjustedAttWithdrawal;
	int Time;
	for (Time = 0; Time < expectedrnds; ++Time)
	{
		z += ((AdjustedAttWithdrawal * y)/100);
		y = ((AdjustedAttWithdrawal * (100 - z))/100);	//	Prob next round is prob per round times prob you haven't already
	}
	
	int EvaluatedAttWithdrawOdds = z;

	//  Determine Attack Knockback odds
	int iAttackerKnockback = pAttacker->knockbackVSOpponentProbTotal(pDefender);
	int iDefenderUnyielding = pDefender->unyieldingTotal();
	int iAttackerKnockbackTries = pAttacker->knockbackRetriesTotal() + 1;

	int AdjustedKnockbackstep1 = iAttackerKnockback - iDefenderUnyielding;
	int AdjustedKnockbackstep2 = ((AdjustedKnockbackstep1 > 100) ? 100 : AdjustedKnockbackstep1);
	int AdjustedKnockback = ((AdjustedKnockbackstep2 < 0) ? 0 : AdjustedKnockbackstep2);

	y = AdjustedKnockback;
	z = AdjustedKnockback;
	
	for (Time = 0; Time < iAttackerKnockbackTries; ++Time)
	{
		z += ((AdjustedKnockback * y)/100);
		y = ((AdjustedKnockback * (100 - z))/100);	//	Prob next round is prob per round times prob you haven't already
	}
		
	int EvaluatedKnockbackOdds = z;

	//  Determine Defensive Withdrawal odds
	int iDefenderWithdraw = pDefender->withdrawVSOpponentProbTotal(pAttacker, pDefender->plot());
	int iAttackerPursuit = pAttacker->pursuitVSOpponentProbTotal(pDefender);
	int iDefenderEarly = pDefender->earlyWithdrawTotal();
	int AdjustedDefWithdrawalstep1 = iDefenderWithdraw - iAttackerPursuit;
	int AdjustedDefWithdrawalstep2 = ((AdjustedDefWithdrawalstep1 > 100) ? 100 : AdjustedDefWithdrawalstep1);
	int AdjustedDefWithdrawal = ((AdjustedDefWithdrawalstep2 < 0) ? 0 : AdjustedDefWithdrawalstep2);
	
	expectedrnds = ((expectedrndcnt * iDefenderEarly)/100);

	y = AdjustedDefWithdrawal;
	z = AdjustedDefWithdrawal;
	
	for (Time = 0; Time < expectedrnds; ++Time)
	{
		z += ((AdjustedDefWithdrawal * y)/100);
		y = ((AdjustedDefWithdrawal * (100 - z))/100);	//	Prob next round is prob per round times prob you haven't already
	}
		
	int EvaluatedDefWithdrawalOdds = z;

	// Fortify, Repel Odds
	int iDefenderFortifyTotal = pDefender->fortifyModifier();
	int iDefenderRepel = pDefender->repelVSOpponentProbTotal(pAttacker);
	int iDefenderFortRepel = pDefender->fortifyRepelModifier();
	int iRepelAttempts = (pDefender->repelRetriesTotal() + 1);
	int iAttackerOverrun = pAttacker->overrunTotal();
	int iAttackerUnyielding = pAttacker->unyieldingTotal();
	int iFortRepellessOverrun = iDefenderFortRepel - iAttackerOverrun;
	int iFortRepelZero = (iFortRepellessOverrun < 0 ? 0 : iFortRepellessOverrun);
	int iFortRepelTotal = (iFortRepelZero > 100 ? 100 : iFortRepelZero);
	int iDefenderRepelwithFortRepel = iDefenderRepel + iFortRepelTotal;
	int iRepelwithUnyielding = iDefenderRepelwithFortRepel - iAttackerUnyielding;
	int iRepelZero = (iRepelwithUnyielding < 0 ? 0 : iRepelwithUnyielding);
	int iRepelTotal = (iRepelZero > 100 ? 100 : iRepelZero);
	int iFortifylessOverrun = iDefenderFortifyTotal - iAttackerOverrun;
	int iFortifyTotal = (iFortifylessOverrun < 0 ? 0 : iFortifylessOverrun);

	y = iRepelTotal;
	z = iRepelTotal;
	
	for (Time = 0; Time < iRepelAttempts; ++Time)
	{
		z += ((iRepelTotal * y)/100);
		y = ((iRepelTotal * (100 - z))/100);	//	Prob next round is prob per round times prob you haven't already
	}
		
	int EvaluatedRepelOdds = z;

	float RetreatOdds = ((float)(std::min((EvaluatedAttWithdrawOdds),100)))/100.0f ;
	float DefRetreatOdds = ((float)(std::min((EvaluatedDefWithdrawalOdds),100)))/100.0f;
	float RepelOdds = ((float)(std::min((EvaluatedRepelOdds),100)))/100.0f ;
	float KnockbackOdds = ((float)(std::min((EvaluatedKnockbackOdds),100)))/100.0f ;
	//TB Combat Mods End (above original:float RetreatOdds = ((float)(std::min(pAttacker->withdrawalProbability(),100)))/100.0f ;

    AttFSnet = ( (pDefender->immuneToFirstStrikes()) ? 0 : pAttacker->firstStrikes() ) - ((pAttacker->immuneToFirstStrikes()) ? 0 : pDefender->firstStrikes());
    AttFSC = (pDefender->immuneToFirstStrikes()) ? 0 : (pAttacker->chanceFirstStrikes());
    DefFSC = (pAttacker->immuneToFirstStrikes()) ? 0 : (pDefender->chanceFirstStrikes());


    float P_A = (float)iAttackerOdds / GC.getDefineINT("COMBAT_DIE_SIDES");
    float P_D = (float)iDefenderOdds / GC.getDefineINT("COMBAT_DIE_SIDES");
    float answer = 0.0f;
    if (n_A < N_A && n_D == iNeededRoundsAttacker)   // (1) Defender dies or is taken to combat limit
    {
        float sum1 = 0.0f;
        for (int i = (-AttFSnet-AttFSC<1?1:-AttFSnet-AttFSC); i <= DefFSC - AttFSnet; i++)
        {
            for (int j = 0; j <= i; j++)
            {

                if (n_A >= j)
                {
                    sum1 += (float)getBinomialCoefficient(i,j) * pow(P_A,(float)(i-j)) * getBinomialCoefficient(iNeededRoundsAttacker-1+n_A-j,iNeededRoundsAttacker-1);

                } //if
            }//for j
        }//for i
        sum1 *= pow(P_D,(float)n_A)*pow(P_A,(float)iNeededRoundsAttacker);
        answer += sum1;


        float sum2 = 0.0f;


        for (int i = (0<AttFSnet-DefFSC?AttFSnet-DefFSC:0); i <= AttFSnet + AttFSC; i++)
        {

            for (int j = 0; j <= i; j++)
            {
                if (N_D > j)
                {
                    sum2 = sum2 + getBinomialCoefficient(n_A+iNeededRoundsAttacker-j-1,n_A) * (float)getBinomialCoefficient(i,j) * pow(P_A,(float)iNeededRoundsAttacker) * pow(P_D,(float)(n_A+i-j));

                }
                else if (n_A == 0)
                {
                    sum2 = sum2 + (float)getBinomialCoefficient(i,j) * pow(P_A,(float)j) * pow(P_D,(float)(i-j));
                }
                else
                {
                    sum2 = sum2 + 0.0f;
                }
            }//for j

        }//for i
        answer += sum2;

    }
    else if (n_D < N_D && n_A == N_A)  // (2) Attacker dies!
    {

        float sum1 = 0.0f;
        for (int i = (-AttFSnet-AttFSC<1?1:-AttFSnet-AttFSC); i <= DefFSC - AttFSnet; i++)
        {

            for (int j = 0; j <= i; j++)
            {
                if (N_A>j)
                {
                    sum1 += getBinomialCoefficient(n_D+N_A-j-1,n_D) * (float)getBinomialCoefficient(i,j) * pow(P_D,(float)(N_A)) * pow(P_A,(float)(n_D+i-j));
                }
                else
                {
                    if (n_D == 0)
                    {
                        sum1 += (float)getBinomialCoefficient(i,j) * pow(P_D,(float)(j)) * pow(P_A,(float)(i-j));
                    }//if (inside if) else sum += 0
                }//if
            }//for j

        }//for i
        answer += sum1;
        float sum2 = 0.0f;
        for (int i = (0<AttFSnet-DefFSC?AttFSnet-DefFSC:0); i <= AttFSnet + AttFSC; i++)
        {
            for (int j = 0; j <= i; j++)
            {
                if (n_D >= j)
                {
                    sum2 += (float)getBinomialCoefficient(i,j) * pow(P_D,(float)(i-j)) * getBinomialCoefficient(N_A-1+n_D-j,N_A-1);
                } //if
            }//for j
        }//for i
        sum2 *= pow(P_A,(float)(n_D))*pow(P_D,(float)(N_A));
        answer += sum2;
		//TB Combat Mods (Repel & Knockback)
//orig: answer = answer * (1.0f - RetreatOdds);
		answer = answer * (1.0f - RetreatOdds) * (1.0f - RepelOdds) * (1.0f - KnockbackOdds) * (1.0f - DefRetreatOdds);
		//TB Combat Mods End

    }
	//TB Combat Mods begin - original: else if (n_A == (N_A-1) && n_D < N_D)  // (3) Attacker retreats!
    else if (n_A == (N_A-1) && n_D < N_D)  // (3) Attacker retreats, is repelled or knocks opponent back!
	//TB Combat Mods end
    {
        float sum1 = 0.0f;
        for (int i = (AttFSnet+AttFSC>-1?1:-AttFSnet-AttFSC); i <= DefFSC - AttFSnet; i++)
        {

            for (int j = 0; j <= i; j++)
            {
                if (N_A>j)
                {
                    sum1 += getBinomialCoefficient(n_D+N_A-j-1,n_D) * (float)getBinomialCoefficient(i,j) * pow(P_D,(float)(N_A)) * pow(P_A,(float)(n_D+i-j));
                }
                else
                {
                    if (n_D == 0)
                    {
                        sum1 += (float)getBinomialCoefficient(i,j) * pow(P_D,(float)(j)) * pow(P_A,(float)(i-j));
                    }//if (inside if) else sum += 0
                }//if
            }//for j

        }//for i
        answer += sum1;

        float sum2 = 0.0f;
        for (int i = (0<AttFSnet-DefFSC?AttFSnet-DefFSC:0); i <= AttFSnet + AttFSC; i++)
        {
            for (int j = 0; j <= i; j++)
            {
                if (n_D >= j)
                {
                    sum2 += (float)getBinomialCoefficient(i,j) * pow(P_D,(float)(i-j)) * getBinomialCoefficient(N_A-1+n_D-j,N_A-1);
                } //if
            }//for j
        }//for i
        sum2 *= pow(P_A,(float)(n_D))*pow(P_D,(float)(N_A));
        answer += sum2;
		//TB Combat Mods (Repel & Knockback)
		//orig: answer = answer * RetreatOdds;
		answer = answer * RetreatOdds * RepelOdds * KnockbackOdds * DefRetreatOdds;
		//TB Combat Mods End
        
    }
    else
    {
        //Unexpected value.  Process should not reach here.
    }

    answer = answer / ((float)(AttFSC+DefFSC+1)); // dividing by (t+w+1) as is necessary
    return answer;
}// getCombatOddsSpecific

See if you can make heads or tails of it all - there's the distinct possibility of some bugs or inaccuracies here.

Ask about ANYTHING you can't tell exactly what it does or refers to!
 
It may take some time to analyze that (I leave on Saturday and probably I will be back next Saturday) but I think I can understand most of these codes. At the first glance the second routine (getCombatOddsSpecific) uses some model based on the Pascal triangle. I am not sure if I understand what does it return: n_A = hits taken by attacker, n_D = hits taken by defender but what is "answer"? And why it is not called by getCombatOdds?

By the way: I forgot that the units may start the combat injured (so initial HP may be smaller than max HP).

S.
 
What comes above 'float getCombatOddsSpecific(CvUnit* pAttacker, CvUnit* pDefender, int n_A, int n_D)' is just comments with the apparent intention to be helpful.

I'd have to find where this function is called to see what n_A and n_D are passed as and how those get initially determined from those reference points. So I'll have to look in the code deeper for that. Pretty sure I've always been a little vague on that but it's been a bit since I studied this code in this much depth. I'm a lot more experienced now in reading code than I was when I was initially making some surgical adjustments here so I can now try to expand my understanding of some of this.

So by the notes, this function returns a value from 0-1 with 2 decimal points. So it's percentage basically.

Why it's not called by getCombatOdds? I dunno... there may have been some severe flaws in structure and approach here long before the combat mod. I may want to go back and look at the vanilla bts version and see if and what was changed since then. But it's Alberts2 and I's suspicion that having these two differing calculations may be causing some problems when they don't completely agree.
 
But it's Alberts2 and I's suspicion that having these two differing calculations may be causing some problems when they don't completely agree.

The AI code in CvUnitAI::AI_attackOddsAtPlotInternal has to be very close to the actual combat outcome. Otherwise the AI can only suck in combat and danger evaluations.

At the moment that code is different but one reason for that is that it also sets the PredictedHitPoints.
 
As far as I understand, for a round i both A and D may lose fixed number of HP (let's say, Aloss and Dloss). So I would suggest to calculate the probabilities that:


pAmiss(i,AHP,DHP) - A wins but misses
pAhit(i,AHP,DHP) - A wins and takes Dloss HP from D
pDretreat(i,AHP,DHP) - A wins and D retreats
pDmiss(i,AHP,DHP) - D wins but misses
pDhit(i,AHP,DHP) - D wins and takes Aloss HP from A
pAretreat(i,AHP,DHP) - D wins and A retreats

and (if it is possible)

pABhit(i,AHP,DHP) - A loses Aloss HP and D loses Dloss HP
pABretreat(i,AHP,DHP) - both units retreat

All these probabilities should add up to 1.0.

General scheme would be like I suggested in the first post. Average HP at the end of the fight would be the sum of all k*S0(k,0) + k*S0(k,-1) (attacker) and the sum of all l*S0(0,l) + l*S0(-1,l) (defender).

I still do not understand some things (e.g. what is knockback and how it affects the combat).


S.
 
Ok... I'm sorry I haven't had much time to focus here... I'd really like to get into more depth with this... explain everything I can and ask (a lot) of you. Work has been exhausting this week so I'll try to get back around to this on the weekend.
 
Instead of `bumping' just some other remark. Some part of the code is based on binomial coefficients. It assumes that the probabilities of win/loss each round is fixed and it must somehow estimate the number of the rounds. I think this is fast but not very accurate, especially if we want to estimate final HP of the winner. My suggestion is inspired by so-called `path integrals' in quantum mechanics (all possible paths of a particle are taken into account). The array of all possible HP of attacker and defender is our `parameter space' and we trace all possible states of our `system' (with appriopriate probabilities). This is more complicated, but much more flexible and we may change probabilities each round (they may also depend on current HP, not only the initial ones).

S.
 
Instead of `bumping' just some other remark. Some part of the code is based on binomial coefficients. It assumes that the probabilities of win/loss each round is fixed and it must somehow estimate the number of the rounds. I think this is fast but not very accurate, especially if we want to estimate final HP of the winner. My suggestion is inspired by so-called `path integrals' in quantum mechanics (all possible paths of a particle are taken into account). The array of all possible HP of attacker and defender is our `parameter space' and we trace all possible states of our `system' (with appriopriate probabilities). This is more complicated, but much more flexible and we may change probabilities each round (they may also depend on current HP, not only the initial ones).

S.

Well... I get the theory differences and it sure sounds good. The practice of the differences I cannot imagine.
 
Well... I get the theory differences and it sure sounds good. The practice of the differences I cannot imagine.

I think the best solution would be some sort of a mirror of the code used to calculate the results of the combat. Each step in the original code should be reflected in the routine to calculate the odds and remaining HP (one may calculate both at once), plus some limits to avoid an (almost) infinite loop.

S.
 
Top Bottom