first strike chance v first strike

davelisowski

Spartan
Joined
Mar 10, 2006
Messages
354
Drill I - +1 first strike chance

Drill II - +1 first strike

So what is the chance part anyways? It seems ridiculous to require 2 promotions to get a first strike. That would be like the Combat I promotion giving a chance for increased strength.
 
davelisowski said:
Drill I - +1 first strike chance

Drill II - +1 first strike

So what is the chance part anyways? It seems ridiculous to require 2 promotions to get a first strike. That would be like the Combat I promotion giving a chance for increased strength.

N first strike chances means that you gain between 0 and N additional first strikes in each combat. ie, with one first strike chance, you may or may not get a first strike (it's a diceroll that takes place before combat starts).

As far as I know, it isn't documented anywhere except in the SDK.
 
I always assumed that chance meant 50%. While I agree that Drill I is pretty weak, the promotions actually stack, which makes the Drill line pretty strong towards the end.

So if you have Drill I and II, you get 1-2 first strikes. With Drill I, II and III it is 1-4 and finally (if you ever get there) 3-6 with Drill I, II, III and IV.
 
Agree with Teg. Drill branch is awesome (but yes, Drill I sort of blows). However, what really bugs me is that the combat log won't tell me when a "round" is a first strike. I want to be able to look and see if any of my first strike chances landed.

Likewise, when my mace dies to a longbow with out getting hit, I see the log show "you mace was hit by Monte's longbow X/100" over and over again, but no mention of first strike. I want to know just how lucky the &#!@er was. Was this me losing every roll in regular combat, or did he win three strikes and then won the next two rolls of fair combat.
 
If you watch the battle itself, I believe the Longbows fire arrows during their First Strikes and use close-range physical attacks for normal combat (when they're fighting non-ranged units, of course).
 
Rex Tyrannus said:
Agree with Teg. Drill branch is awesome (but yes, Drill I sort of blows). However, what really bugs me is that the combat log won't tell me when a "round" is a first strike. I want to be able to look and see if any of my first strike chances landed.

I would so love this myself. I would like to actually see when and how often (or not) the first strikes are played out. I think pre 1.52, it showed the first strike rounds but you could only notice it if you lost the first strike round (thus showing that no health was reduced). I think it needs a [FS] in there or something to show that it is a first strike round. But, considering that they changed the combat logs so that it doesn't show any way of observing first strike rounds, it seems as if they don't want people to see it in effect.

Mewtarthio said:
If you watch the battle itself, I believe the Longbows fire arrows during their First Strikes and use close-range physical attacks for normal combat (when they're fighting non-ranged units, of course).

Good point. I do wonder though whether it only shows successful first strikes though. I would just like to know decisively whether a round has a first strike in it or not. At least then I can see if it is really worth it or not.

davelisowski said:
Drill I - +1 first strike chance

Drill II - +1 first strike

So what is the chance part anyways? It seems ridiculous to require 2 promotions to get a first strike. That would be like the Combat I promotion giving a chance for increased strength.

A first strike round is a normal round except the first striker cannot be harmed during that round. A first strike chance gives the possibility of having a first strike round or not. 4-7 essentially means you will have 4 to 7 first strike rounds (that may or may not result in hits) in a given battle. One battle it may be 4 first strike rounds, in another battle it may be 6 first strike rounds. I am not sure how it comes up with how many first strike rounds there will be. My assumption would be a random number between (in this example) 4 and 7 (including both 4 and 7).

All in all though, I cannot see why the first strike chance even exists. I don't see why they just don't can the first strike chance idea and make each Drill promotion have +1 first strike, except Drill IV which will give you +2 first strikes (for a total of 5 first strikes). I do hope in the expansion though, that the combat log does state the first strike rounds. Maybe the idea of the first strike chance is to round it off a little so that it isn't so noticable. I get the feeling Firaxis wants the first strike idea to be invisible. In general, I just conclude first strikes to essentially mean: "The more first strikes (and chances) you have, the greater the probability you have of winning the first round."

Watiggi
 
So there's nothing that says how the chance is calculated? I know that the success % gets messed up with first strikes, and the strength does not include those. But is it 50%, 70%, 75%, random, dice roll? I don't have any idea how to find this out.
 
Wouldn't it make sence for it to always be 50%?

So on drill 1, you get a 50% chance of having the first strike, at drill 2 you get the other 50%, so you have 100% chance of 1 first strike.

Drill 3 gives you 2 first strike chances, so that'd be 2 50/50 dice rolls, and then drill 4 makes it so you get 2 100% dice rolls (plus the 100% chance you got from drill 1+2).

Or am I totally confused?
 
It works like this:

Drill 1: 0-1 first strikes
Drill 2: 1-2 first strikes
Drill 3: 1-4 first strikes
Drill 4: 3-6 first strikes (!)
 
jimbob27 said:
Wouldn't it make sence for it to always be 50%?

So on drill 1, you get a 50% chance of having the first strike, at drill 2 you get the other 50%, so you have 100% chance of 1 first strike.

Drill 3 gives you 2 first strike chances, so that'd be 2 50/50 dice rolls, and then drill 4 makes it so you get 2 100% dice rolls (plus the 100% chance you got from drill 1+2).

Or am I totally confused?

I don't actually know (someone would have to go into the SDK to find out. I for the life of me don't know where to look for it though).

But, I would assume that it would just simply throw a random number between the minium number of first strikes and the maximum number of first strikes. That makes sense to me:

Say 4-7 first strikes for the unit. Before the battle, roll a random number between 4 and 7 (inclusive). If it's 4, then there will be 4 first strikes, if it rolls a 5, then there will be 5 first strike rounds. It just makes sense for me to do it that way.

One thing I do know, looking in the SDK, is that when combat starts, the number of first strikes appears to have already been calculated, which suggests to me that it doesn't figure it out on the fly. I would be surprised if it doesn't just get a random number between x-y and then that will be the number of first strikes for that battle.

Watiggi
 
Well that would mean a 'first Strike chance' is effectively, on Average 1/2 first Strike

0-1 on average would be 1/2
1-2 on average would be 1 1/2 (1+1/2)
1-4 on average would be 2 1/2 (1+3/2)
3-6 on average would be 4 1/2 (3 +3/2)
 
@Watiggi

I spent a lunch hour pouring over the SDK and found the following code in the CVGameCoreUtils.cpp file. It's the function for combat odds calculation and has a lot to do with first strikes. I'm trying to decipher it, but I figured I'd put it in here for you to look at too. (if you want). I also want to find the actual battle code that deals with the strikes, but I haven't yet.

Spoiler :

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 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") * iAttackerStrength) / (iAttackerStrength + iDefenderStrength));	
	if (iAttackerOdds == 0)
	{
		return 0;
	}

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

	// calculate damage done in one round
	//////

	iDamageToAttacker = max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));
	iDamageToDefender = max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));

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

	iNeededRoundsAttacker = (pDefender->currHitPoints() + 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;
}
 
Krikkitone said:
Well that would mean a 'first Strike chance' is effectively, on Average 1/2 first Strike

0-1 on average would be 1/2
1-2 on average would be 1 1/2 (1+1/2)
1-4 on average would be 2 1/2 (1+3/2)
3-6 on average would be 4 1/2 (3 +3/2)

Not exactly. If it were a fifty-fifty chance, then the odds of hitting 3/3 first strike chances would be (1/2)^3 = 1 in 8, while the "drawing a random number from min to max" gives odd of 1 in 3.
 
Rex Tyrannus said:
@Watiggi

I spent a lunch hour pouring over the SDK and found the following code in the CVGameCoreUtils.cpp file. It's the function for combat odds calculation and has a lot to do with first strikes. I'm trying to decipher it, but I figured I'd put it in here for you to look at too. (if you want). I also want to find the actual battle code that deals with the strikes, but I haven't yet.

Wow! Thanks for that. I have been looking for that for quite a while. I think the code you're looking for is in CvUnit.cpp and the method is called UpdateCombat. Here is the function (I have bolded the actual while loop that resolves the combat rounds):

Spoiler :

Code:
void CvUnit::updateCombat(bool bQuick)
{
	CvUnit* pDefender;
	CvPlot* pPlot;
	CvWString szBuffer;
	int iAttackerStrength;
	int iAttackerFirepower;
	int iDefenderStrength;
	int iDefenderFirepower;
	int iStrengthFactor;
	bool bFinish;
	bool bAdvance;
	bool bVisible;
	bool bFocused;
	bool bFirst;
	int iExperience;
	int iDefenderOdds;
	int iDamage;

	bFinish = false;
	bVisible = false;

	if (getCombatTimer() > 0)
	{
		changeCombatTimer(-1);

		if (getCombatTimer() > 0)
		{
			return;
		}
		else
		{
			bFinish = true;
		}
	}

	pPlot = getAttackPlot();

	if (pPlot == NULL)
	{
		return;
	}

	if (getDomainType() == DOMAIN_AIR)
	{
		if (!bFinish)
		{
			if (!bQuick)
			{
				if (isHuman())
				{
					bVisible = !(GET_PLAYER(getOwnerINLINE()).isOption(PLAYEROPTION_QUICK_ATTACK));
				}
			}

			if (bVisible)
			{
				setCombatTimer(GC.getMissionInfo(MISSION_AIRSTRIKE).getTime());

				GC.getGameINLINE().incrementTurnTimer(getCombatTimer());
			}

			airStrike(pPlot);

			if (bVisible)
			{
				return;
			}
		}

		setAttackPlot(NULL);

		getGroup()->clearMissionQueue();

		return;
	}

	if (bFinish)
	{
		pDefender = getCombatUnit();
	}
	else
	{
		pDefender = pPlot->getBestDefender(NO_PLAYER, getOwnerINLINE(), this, true);
	}

	if (pDefender == NULL)
	{
		setAttackPlot(NULL);
		setCombatUnit(NULL);

		getGroup()->groupMove(pPlot, true, ((canAdvance(pPlot, 0)) ? this : NULL));

		getGroup()->clearMissionQueue();

		return;
	}

	if (!bQuick)
	{
		if (isHuman())
		{
			bVisible = !(GET_PLAYER(getOwnerINLINE()).isOption(PLAYEROPTION_QUICK_ATTACK));
		}
		else if (pDefender->isHuman())
		{
			bVisible = !(GET_PLAYER(pDefender->getOwnerINLINE()).isOption(PLAYEROPTION_QUICK_DEFENSE));
		}
	}

	FAssertMsg((pPlot == pDefender->plot()), "There is not expected to be a defender or the defender's plot is expected to be pPlot (the attack plot)");

	if (!bFinish)
	{
		if (!isFighting())
		{
			if (plot()->isFighting() || pPlot->isFighting())
			{
				return;
			}

			setMadeAttack(true);

			setCombatUnit(pDefender, true);
			pDefender->setCombatUnit(this, false);

			pDefender->getGroup()->clearMissionQueue();

			bFocused = (bVisible && isCombatFocus() && gDLL->getInterfaceIFace()->isCombatFocus());

			if (bFocused)
			{
				DirectionTypes directionType = directionXY(plot(), pPlot);
				//								N			NE				E				SE					S				SW					W				NW
				NiPoint2 directions[8] = {NiPoint2(0, 1), NiPoint2(1, 1), NiPoint2(1, 0), NiPoint2(1, -1), NiPoint2(0, -1), NiPoint2(-1, -1), NiPoint2(-1, 0), NiPoint2(-1, 1)};
				NiPoint3 attackDirection = NiPoint3(directions[directionType].x, directions[directionType].y, 0);
				float plotSize = GC.getDefineFLOAT("PLOT_SIZE");
				NiPoint3 lookAtPoint(plot()->getPoint().x + plotSize / 2 * attackDirection.x, plot()->getPoint().y + plotSize / 2 * attackDirection.y, (plot()->getPoint().z + pPlot->getPoint().z) / 2);
				attackDirection.Unitize();
				gDLL->getInterfaceIFace()->lookAt(lookAtPoint, (((getOwnerINLINE() != GC.getGameINLINE().getActivePlayer()) || gDLL->getGraphicOption(GRAPHICOPTION_NO_COMBAT_ZOOM)) ? CAMERALOOKAT_BATTLE : CAMERALOOKAT_BATTLE_ZOOM_IN), attackDirection);
			}

			gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), ((!bFocused) ? gDLL->getText("TXT_KEY_MISC_YOU_UNITS_UNDER_ATTACK", GET_PLAYER(getOwnerINLINE()).getNameKey()).GetCString() : NULL), ((!bFocused) ? "AS2D_COMBAT" : NULL), MESSAGE_TYPE_DISPLAY_ONLY, GC.getUnitInfo(getUnitType()).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), !bFocused);
		}

		FAssertMsg(pDefender != NULL, "Defender is not assigned a valid value");

		FAssertMsg(plot()->isFighting(), "Current unit instance plot is not fighting as expected");
		FAssertMsg(pPlot->isFighting(), "pPlot is not fighting as expected");

		if (!(pDefender->canDefend()))
		{
			if (bVisible)
			{
				if (GET_PLAYER(getOwnerINLINE()).isBarbarian() && !(pDefender->isNoCapture()))
				{
					CvBattleDefinition kBattle;
					kBattle.pkUnits[BDU_ATTACKER] = this;
					kBattle.pkUnits[BDU_DEFENDER] = pDefender;

					kBattle.iDamage[BDU_ATTACKER][BDT_BEGIN] = getDamage();
					kBattle.iDamage[BDU_ATTACKER][BDT_RANGED] = getDamage();
					kBattle.iDamage[BDU_ATTACKER][BDT_END] = getDamage();

					kBattle.iDamage[BDU_DEFENDER][BDT_BEGIN] = pDefender->getDamage();
					kBattle.iDamage[BDU_DEFENDER][BDT_RANGED] = pDefender->getDamage();
					kBattle.iDamage[BDU_DEFENDER][BDT_END] = 100;

					kBattle.iFirstStrikes[BDU_ATTACKER] = 0;
					kBattle.iFirstStrikes[BDU_DEFENDER] = 0;

					int iTurns = planBattle( kBattle );
					kBattle.fMissionTime = (float)(iTurns * gDLL->getSecsPerTurn());

					gDLL->getEntityIFace()->AddMission(kBattle);

					setCombatTimer(iTurns);
				}
				else
				{
					CvMissionDefinition kMission;
					kMission.fMissionTime = getCombatTimer() * gDLL->getSecsPerTurn();
					kMission.eMissionType = MISSION_SURRENDER;
					kMission.pkUnits[BDU_ATTACKER] = this;
					kMission.pkUnits[BDU_DEFENDER] = pDefender;
					kMission.pkPlot = pPlot;
					gDLL->getEntityIFace()->AddMission(kMission);

					// Surrender mission
					setCombatTimer(GC.getMissionInfo(MISSION_SURRENDER).getTime());
				}

				GC.getGameINLINE().incrementTurnTimer(getCombatTimer());
			}
			else
			{
				bFinish = true;
			}

			// Kill them!
			pDefender->setDamage(GC.getMAX_HIT_POINTS());
		}
		else
		{
			CvBattleDefinition kBattle;
			kBattle.pkUnits[BDU_ATTACKER] = this;
			kBattle.pkUnits[BDU_DEFENDER] = pDefender;
			kBattle.iDamage[BDU_ATTACKER][BDT_BEGIN] = getDamage();
			kBattle.iDamage[BDU_DEFENDER][BDT_BEGIN] = pDefender->getDamage();

			//Added ST
			CombatDetails cdAttackerDetails;
			CombatDetails cdDefenderDetails;
			iAttackerStrength = currCombatStr(NULL, NULL, &cdAttackerDetails);
			iAttackerFirepower = currFirepower(NULL, NULL);
			iDefenderStrength = pDefender->currCombatStr(pPlot, this, &cdDefenderDetails);
			iDefenderFirepower = pDefender->currFirepower(pPlot, this);

			FAssert((iAttackerStrength + iDefenderStrength) > 0);
			FAssert((iAttackerFirepower + iDefenderFirepower) > 0);

			iDefenderOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));

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

			bFirst = true;

			if (isHuman() || pDefender->isHuman())
			{
				//Added ST
				CyArgsList pyArgsCD;
				pyArgsCD.add(gDLL->getPythonIFace()->makePythonObject(&cdAttackerDetails));
				pyArgsCD.add(gDLL->getPythonIFace()->makePythonObject(&cdDefenderDetails));
				pyArgsCD.add(getCombatOdds(this, pDefender));
				gDLL->getEventReporterIFace()->genericEvent("combatLogCalc", pyArgsCD.makeFunctionArgs());
			}

			if (pDefender->isBarbarian())
			{
				if (GET_PLAYER(getOwnerINLINE()).getWinsVsBarbs() < GC.getHandicapInfo(GET_PLAYER(getOwnerINLINE()).getHandicapType()).getFreeWinsVsBarbs())
				{
					iDefenderOdds = min(10, iDefenderOdds);
				}
			}
			if (isBarbarian())
			{
				if (GET_PLAYER(pDefender->getOwnerINLINE()).getWinsVsBarbs() < GC.getHandicapInfo(GET_PLAYER(pDefender->getOwnerINLINE()).getHandicapType()).getFreeWinsVsBarbs())
				{
					iDefenderOdds = max(90, iDefenderOdds);
				}
			}

			[b]while (true)
			{
				if (bFirst)
				{
					collateralCombat(pPlot, pDefender);
				}

				if (GC.getGameINLINE().getSorenRandNum(GC.getDefineINT("COMBAT_DIE_SIDES"), "Combat") <= iDefenderOdds)
				{
					if (getCombatFirstStrikes() == 0)
					{
						iDamage = max(1, ((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));

						if (((getDamage() + iDamage) >= maxHitPoints()) && (GC.getGameINLINE().getSorenRandNum(100, "Withdrawal") < withdrawalProbability()))
						{
							changeExperience(GC.getDefineINT("EXPERIENCE_FROM_WITHDRAWL"), pDefender->maxXPValue());
							break;
						}

						changeDamage(iDamage, pDefender->getOwnerINLINE());

						if (pDefender->getCombatFirstStrikes() > 0)
						{
							kBattle.iFirstStrikes[BDU_DEFENDER]++;
							kBattle.iDamage[BDU_ATTACKER][BDT_RANGED] += iDamage;
						}

						//Added ST
						cdAttackerDetails.iCurrHitPoints=currHitPoints();

						if (isHuman() || pDefender->isHuman())
						{
							CyArgsList pyArgs;
							pyArgs.add(gDLL->getPythonIFace()->makePythonObject(&cdAttackerDetails));
							pyArgs.add(gDLL->getPythonIFace()->makePythonObject(&cdDefenderDetails));
							pyArgs.add(1);
							pyArgs.add(iDamage);
							gDLL->getEventReporterIFace()->genericEvent("combatLogHit", pyArgs.makeFunctionArgs());
						}
					}
				}
				else
				{
					if (pDefender->getCombatFirstStrikes() == 0)
					{
						iDamage = max(1, ((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));

						pDefender->changeDamage(iDamage);

						if (getCombatFirstStrikes() > 0)
						{
							kBattle.iFirstStrikes[BDU_ATTACKER]++;
							kBattle.iDamage[BDU_DEFENDER][BDT_RANGED] += iDamage;
						}

						//Added ST
						cdDefenderDetails.iCurrHitPoints=pDefender->currHitPoints();

						if (isHuman() || pDefender->isHuman())
						{
							CyArgsList pyArgs;
							pyArgs.add(gDLL->getPythonIFace()->makePythonObject(&cdAttackerDetails));
							pyArgs.add(gDLL->getPythonIFace()->makePythonObject(&cdDefenderDetails));
							pyArgs.add(0);
							pyArgs.add(iDamage);
							gDLL->getEventReporterIFace()->genericEvent("combatLogHit", pyArgs.makeFunctionArgs());
						}
					}
				}

				if (getCombatFirstStrikes() > 0)
				{
					changeCombatFirstStrikes(-1);
				}

				if (pDefender->getCombatFirstStrikes() > 0)
				{
					pDefender->changeCombatFirstStrikes(-1);
				}

				bFirst = false;

				if (isDead() || pDefender->isDead())
				{
					if (isDead())
					{
						iExperience = defenseXPValue();
						iExperience = ((iExperience * iAttackerStrength) / iDefenderStrength);
						iExperience = range(iExperience, GC.getDefineINT("MIN_EXPERIENCE_PER_COMBAT"), GC.getDefineINT("MAX_EXPERIENCE_PER_COMBAT"));
						pDefender->changeExperience(iExperience, maxXPValue());
					}
					else
					{
						iExperience = pDefender->attackXPValue();
						iExperience = ((iExperience * iDefenderStrength) / iAttackerStrength);
						iExperience = range(iExperience, GC.getDefineINT("MIN_EXPERIENCE_PER_COMBAT"), GC.getDefineINT("MAX_EXPERIENCE_PER_COMBAT"));
						changeExperience(iExperience, pDefender->maxXPValue());
					}

					break;
				}
			}[/b]

			if (bVisible)
			{
				kBattle.iDamage[BDU_ATTACKER][BDT_END] = getDamage();
				kBattle.iDamage[BDU_DEFENDER][BDT_END] = pDefender->getDamage();
				kBattle.bAdvanceSquare = canAdvance(pPlot, 1);

				if (isRanged() && pDefender->isRanged())
				{
					kBattle.iDamage[BDU_ATTACKER][BDT_RANGED] = kBattle.iDamage[BDU_ATTACKER][BDT_END];
					kBattle.iDamage[BDU_DEFENDER][BDT_RANGED] = kBattle.iDamage[BDU_DEFENDER][BDT_END];
				}
				else
				{
					kBattle.iDamage[BDU_ATTACKER][BDT_RANGED] += kBattle.iDamage[BDU_ATTACKER][BDT_BEGIN];
					kBattle.iDamage[BDU_DEFENDER][BDT_RANGED] += kBattle.iDamage[BDU_DEFENDER][BDT_BEGIN];
				}

				int iTurns = planBattle( kBattle);
				kBattle.fMissionTime = iTurns * gDLL->getSecsPerTurn();
				setCombatTimer(iTurns);

				GC.getGameINLINE().incrementTurnTimer(getCombatTimer());

				if (pPlot->isActiveVisible(false))
				{
					ExecuteMove(0.0f);
					gDLL->getEntityIFace()->AddMission(kBattle);
				}
			}
			else
			{
				bFinish = true;
			}
		}
	}

	if (bFinish)
	{
		if (bVisible)
		{
			if (isCombatFocus() && gDLL->getInterfaceIFace()->isCombatFocus())
			{
				if (getOwnerINLINE() == GC.getGameINLINE().getActivePlayer())
				{
					gDLL->getInterfaceIFace()->releaseLockedCamera();
				}
			}
		}

		setAttackPlot(NULL);

		setCombatUnit(NULL);
		pDefender->setCombatUnit(NULL);

		if (isDead())
		{
			if (isBarbarian())
			{
				GET_PLAYER(pDefender->getOwnerINLINE()).changeWinsVsBarbs(1);
			}

			if (pPlot->findHighestCultureTeam() != getTeam())
			{
				GET_TEAM(getTeam()).changeWarWeariness(pDefender->getTeam(), GC.getDefineINT("WW_UNIT_KILLED_ATTACKING"));
			}
			if (pPlot->findHighestCultureTeam() != pDefender->getTeam())
			{
				GET_TEAM(pDefender->getTeam()).changeWarWeariness(getTeam(), GC.getDefineINT("WW_KILLED_UNIT_DEFENDING"));
			}
			GET_TEAM(pDefender->getTeam()).AI_changeWarSuccess(getTeam(), GC.getDefineINT("WAR_SUCCESS_DEFENDING"));

			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_DIED_ATTACKING", getNameKey(), pDefender->getNameKey());
			gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitDefeatScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_KILLED_ENEMY_UNIT", pDefender->getNameKey(), getNameKey(), GET_PLAYER(getOwnerINLINE()).getCivilizationAdjectiveKey());
			gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitVictoryScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

			// report event to Python, along with some other key state
			gDLL->getEventReporterIFace()->combatResult(pDefender, this);
		}
		else if (pDefender->isDead())
		{
			if (pDefender->isBarbarian())
			{
				GET_PLAYER(getOwnerINLINE()).changeWinsVsBarbs(1);
			}

			if (pPlot->findHighestCultureTeam() != pDefender->getTeam())
			{
				GET_TEAM(pDefender->getTeam()).changeWarWeariness(getTeam(), GC.getDefineINT("WW_UNIT_KILLED_DEFENDING"));
			}
			if (pPlot->findHighestCultureTeam() != getTeam())
			{
				GET_TEAM(getTeam()).changeWarWeariness(pDefender->getTeam(), GC.getDefineINT("WW_KILLED_UNIT_ATTACKING"));
			}
			GET_TEAM(getTeam()).AI_changeWarSuccess(pDefender->getTeam(), GC.getDefineINT("WAR_SUCCESS_ATTACKING"));

			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_DESTROYED_ENEMY", getNameKey(), pDefender->getNameKey());
			gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitVictoryScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED", pDefender->getNameKey(), getNameKey(), GET_PLAYER(getOwnerINLINE()).getCivilizationAdjectiveKey());
			gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer,GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitDefeatScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

			// report event to Python, along with some other key state
			gDLL->getEventReporterIFace()->combatResult(this, pDefender);

			bAdvance = canAdvance(pPlot, ((pDefender->canDefend()) ? 1 : 0));

			if (bAdvance)
			{
				if (!isNoCapture())
				{
					pDefender->setCapturingPlayer(getOwnerINLINE());
				}
			}

			pDefender->kill(false, getOwnerINLINE());
			pDefender = NULL;

			if (!bAdvance)
			{
				changeMoves(max(GC.getMOVE_DENOMINATOR(), pPlot->movementCost(this, plot())));

				if (!canMove() || !isBlitz())
				{
					if (IsSelected())
					{
						if (gDLL->getInterfaceIFace()->getLengthSelectionList() > 1)
						{
							gDLL->getInterfaceIFace()->removeFromSelectionList(this);
						}
					}
				}
			}

			if (pPlot->getNumVisibleEnemyDefenders(getOwnerINLINE()) == 0)
			{
				getGroup()->groupMove(pPlot, true, ((bAdvance) ? this : NULL));
			}

			// This is is put before the plot advancement, the unit will always try to walk back
			// to the square that they came from, before advancing.
			getGroup()->clearMissionQueue();
		}
		else
		{
			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_WITHDRAW", getNameKey(), pDefender->getNameKey());
			gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, "AS2D_OUR_WITHDRAWL", MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
			szBuffer = gDLL->getText("TXT_KEY_MISC_ENEMY_UNIT_WITHDRAW", getNameKey(), pDefender->getNameKey());
			gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, "AS2D_THEIR_WITHDRAWL", MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

			changeMoves(max(GC.getMOVE_DENOMINATOR(), pPlot->movementCost(this, plot())));

			getGroup()->clearMissionQueue();
		}
	}
}
 
Watiggi said:
Say 4-7 first strikes for the unit. Before the battle, roll a random number between 4 and 7 (inclusive). If it's 4, then there will be 4 first strikes, if it rolls a 5, then there will be 5 first strike rounds. It just makes sense for me to do it that way.

Exactly right: 4-7 means you are equally likely to have 4,5,6, or 7 first strikes.

The actual number of first strikes are calculated before the combat begins. The function with the die roll is CvUnit::setCombatUnit; which is called for the attacker and defender prior to the combat loop in CvUnit::updateCombat
 
Mewtarthio said:
Not exactly. If it were a fifty-fifty chance, then the odds of hitting 3/3 first strike chances would be (1/2)^3 = 1 in 8, while the "drawing a random number from min to max" gives odd of 1 in 3.

Well I'm not saying what the odds are, I'm saying how much you would get on average.
 
VoiceOfUnreason said:
Exactly right: 4-7 means you are equally likely to have 4,5,6, or 7 first strikes.

The actual number of first strikes are calculated before the combat begins. The function with the die roll is CvUnit::setCombatUnit; which is called for the attacker and defender prior to the combat loop in CvUnit::updateCombat
Thanks for that. In the setCombatUnit method there is this line:
Spoiler :
Code:
		FAssertMsg(getCombatUnit() == NULL, "Combat Unit is not expected to be assigned");
		FAssertMsg(!(plot()->isFighting()), "(plot()->isFighting()) did not return false as expected");
		m_bCombatFocus = (bAttacking && !(gDLL->getInterfaceIFace()->isFocusedWidget()) && ((getOwnerINLINE() == GC.getGameINLINE().getActivePlayer()) || ((pCombatUnit->getOwnerINLINE() == GC.getGameINLINE().getActivePlayer()) && !(GC.getGameINLINE().isMPOption(MPOPTION_SIMULTANEOUS_TURNS)))));
		m_combatUnit = pCombatUnit->getIDInfo();
		[B]setCombatFirstStrikes((pCombatUnit->immuneToFirstStrikes()) ? 0 : (firstStrikes() + GC.getGameINLINE().getSorenRandNum(chanceFirstStrikes(), "First Strike")));[/B]
		setCombatDamage(0);
Which essentially means: # of first strikes = first strikes + random number between 0 and the number of first strike chances. If the other unit is immune to first strikes, then it will be 0. An example, 4-7 first strikes; pick random number between 0 and 3, then add 4.

The sorenRandNum seems to just be a random number generator that also logs the results and what it was for.

setCombatFirstStrikes method is nothing out of the ordinary:
Spoiler :
Code:
void CvUnit::setCombatFirstStrikes(int iNewValue)			
{
	m_iCombatFirstStrikes = iNewValue;
	FAssert(getCombatFirstStrikes() >= 0);
}
For those who want to track it down: CvRandom.cpp contains the random number generator code. sorenRandNum is in CvGame.cpp and eventually refers to CvRandom. It appears to be a normal random number generator.

chanceFirstStrikes() is:
Spoiler :
Code:
int CvUnit::chanceFirstStrikes() const
{
	return max(0, (GC.getUnitInfo(getUnitType()).getChanceFirstStrikes() + getExtraChanceFirstStrikes()));
}

firstStrikes() is:
Spoiler :
Code:
int CvUnit::firstStrikes() const
{
	return max(0, (GC.getUnitInfo(getUnitType()).getFirstStrikes() + getExtraFirstStrikes()));
}
So, it does pick a random number between x-y (inclusive), where x is the number of first strikes and y is the number of first strike chances. This is done before each battle.

Again, thanks for that VoiceOfUnreason. It good to have my suspicion/theory confirmed. :)

Watiggi
 
Back
Top Bottom