Announcement

Collapse
No announcement yet.

CIV 4 combat *facts* - Looking at the Code

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • CIV 4 combat *facts* - Looking at the Code

    I´ve read the forums, and I´ve seen that there is much speculation based on incomplete information on how the combat system works, leading to much flawed considerations. So, what I propose in this post is that we look at the combat system at the code level, so you guys get solid facts to decide on what units and promotions you use.

    Right now, lets see where you can find CIV core dll, that will give you all the rules, written in C++ language. They are at the installation folder:
    Code:
    []Civ folder
        []Beyond the Sword
          []Assets
            []CvGameCoreDLL
    They have a lot of Cpp files. The files starting with Cy are python interfaces, we are not interested in them. So lets look at the Cv* files:
    There is a file call CvUnit. This is the file where all unit actions and info, including combat calculations, are made. There is a method in there:
    Code:
    void CvUnit::resolveCombat(CvUnit* pDefender, CvPlot* pPlot, CvBattleDefinition& kBattle)
    {
    	CombatDetails cdAttackerDetails;
    	CombatDetails cdDefenderDetails;
    
    	int iAttackerStrength = currCombatStr(NULL, NULL, &cdAttackerDetails);
    	int iAttackerFirepower = currFirepower(NULL, NULL);
    	int iDefenderStrength;
    All right, let's stop here: what in the heck is this currCombatStr?

    It's in this code:
    Code:
    int CvUnit::currCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
    {
    	return ((maxCombatStr(pPlot, pAttacker, pCombatDetails) * currHitPoints()) / maxHitPoints());
    }
    That is, the current combat strength is your max combat strength adjusted to the hit points that you have.

    All right, I'm speculating here. After all, what in the heck is maxCombatStr?

    It's here:
    Code:
    int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
    {
    ... 
    	iExtraModifier = getExtraCombatPercent();
    What in the heck is this? It's a property, saved and load with the unit. The starting value of this surely is zero. But thats come clear after you see the promotion system: they are used for giving the promotions bonus...
    ... continuing on the maxCombatStr calculation... remember that pPlot is null right now and pAttacker is also null ... also, we are calling this from the method “resolveCombat” that clearly is a situation where this is the current attacker. This makes all the code of maxCombatStr be basically skipped, so it all goes to
    Code:
    	else
    	{
    		iCombat = ((baseCombatStr() * 10000) / (100 - iModifier));
    	}
    Where baseCombatStr is the strength of the attacker (this).
    the result is, at last:
    return std::max(1, iCombat);
    That is: maxCombatStr for the attacker is his strength multiplied by 100. Unless that pesky modifier ,extraCombatPercent, is different than zero.

    We know now what that iAttackerStrength has at the resolveCombat code. Now let's see iAttackerFirepower; that goes on this method:
    int iAttackerFirepower = currFirepower(NULL, NULL);
    this is the method:
    Code:
    int CvUnit::currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker) const
    {
    	return ((maxCombatStr(pPlot, pAttacker) + currCombatStr(pPlot, pAttacker) + 1) / 2);
    }
    So, the firepower of a almost dead attacker is half the firepower of a 100% healthy unit. Interesting...
    Back to the resolveCombat code:
    ... just after the iAttackerStrength and iAttackerFirepower calculations ...
    Code:
    	getDefenderCombatValues(*pDefender, pPlot, iAttackerStrength, 
                    iAttackerFirepower, iDefenderOdds,
                    iDefenderStrength, iAttackerDamage,   
                    iDefenderDamage, &cdDefenderDetails);
    Let's see what the getDefenderCombatValues does:
    Code:
    void CvUnit::getDefenderCombatValues(CvUnit& kDefender, const CvPlot* pPlot, 
          int iOurStrength, int iOurFirepower, 
          int& iTheirOdds, int& iTheirStrength, 
          int& iOurDamage, int& iTheirDamage, 
          CombatDetails* pTheirDetails) const
    {
    	iTheirStrength = kDefender.currCombatStr(pPlot, this, pTheirDetails);
    	int iTheirFirepower = kDefender.currFirepower(pPlot, this);
    Ah... for their strength (the defender strength), they inform the plot and the attacker... This makes the whole maxCombatStr calculation different:
    Code:
    int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
    {
    ...
    if (pAttacker != NULL)
       {
          if (isAnimal())
          {
             if (pAttacker->isHuman())
             {
                iExtraModifier = GC.getHandicapInfo(
                   GC.getGameINLINE().getHandicapType())
                   .getAnimalCombatModifier();
                iModifier += iExtraModifier;
                if (pCombatDetails != NULL)
                {
                   pCombatDetails->iAnimalCombatModifierTA = 
                      iExtraModifier;
                }
             }
             else
             {
                iExtraModifier = 
                   GC.getHandicapInfo(
                   GC.getGameINLINE().getHandicapType())
                   .getAIAnimalCombatModifier();
                iModifier += iExtraModifier;
                if (pCombatDetails != NULL)
                {
                   pCombatDetails->iAIAnimalCombatModifierTA = 
                       iExtraModifier;
                }
             }
          }
    if the defender is an animal, the attacker get a bonus, depending on the attacker being human or AI. Here I'm speculating: I think that these are the values of the iAnimalBonus and the iAIAnimalBonus that you get at the GameInfo/CIV4HandicapInfo.xml. So, for prince level, they would be: -30 for humans attacking animals and -70 for AI attacking animals.
    So... as these modifier get as divisor at the end of the game, the AI get a greater bonus when fighting animals... cute. That's why those darn AI scouts keep surviving bears...
    As you can guess, and I will let you see the code for this, the modifiers get reversed if that's an Animal attacking a unit.
    I won't put the code in here. But the same calculation is made if the attacking/defender is a barbarian; in the XML the values would be iBarbarianBonus and iAIBarbarianBonus. They are at prince level, -5 for humans and -40 for AI. So, the AI get a big advantage when fighting barbs.
    Then they get the plot defensive value (terrain values: hills, forests, walls, city culture, etc) and add to the iModifier. Unless, of course, the unit doen't receive defensive bonuses.
    Well, making a long story short, all bonuses/onuses of the attacker and the defender get summed up with the proper signals (a bonus of the attacker is of course an onus to the defender) at the defender side, and the following calculation is made:
    Code:
    	if (iModifier > 0)
    	{
    		iCombat = (baseCombatStr() * (iModifier + 100));
    	}
    	else
    	{
    		iCombat = ((baseCombatStr() * 10000) / (100 - iModifier));
    	}
    This leads to some interesting conclusions:
    I've seen people making calcs saying for instance that a dog soldier is better than an axeman because the bonus of the dog soldier is 100%, so 4 * 2 = 8, against the axeman, where the bonus is 5 * 1,5 = 7,5.
    You now know that that calculation is not exactly right (even more, as we will see later...)
    A dog soldier attacking an axeman in clear terrain:
    attacker bonus: 100%; defender bonus: 50%; the modifiers are stacked up at the defender, resulting in -50 to the defender, making the combat 4 against 5 / 1,5, that is, 4 against 3,333; better than 8 against 7,5 that people usually make. Yes, that axeman is probably toast in clear terrain...
    Continuing on the analysis of getDefenderCombatValues:
    This function has calc'ed iTheirStrength and iTheirFirepower. Then they calc iTheirOdds:
    Code:
    	iTheirOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iTheirStrength) 
    / (iOurStrength + iTheirStrength));
    
    	if (kDefender.isBarbarian())
    	{
    		if (GET_PLAYER(getOwnerINLINE())
    .getWinsVsBarbs() < 
    GC.getHandicapInfo(GET_PLAYER(getOwnerINLINE()).getHandicapType()).getFreeWinsVsBarbs())
    		{
    			iTheirOdds = std::min((10 * GC.getDefineINT("COMBAT_DIE_SIDES")) / 100, 
    iTheirOdds);
    		}
    	}
    	if (isBarbarian())
    	{
    		if 
    (GET_PLAYER(kDefender.getOwnerINLINE()).getWinsVsBarbs() < 
    GC.getHandicapInfo(GET_PLAYER(kDefender.getOwnerINLINE()).getHandicapType()).getFreeWinsVsBarbs())
    		{
    			iTheirOdds =  std::max((90 * GC.getDefineINT("COMBAT_DIE_SIDES")) / 100, 
    iTheirOdds);
    		}
    	}
    If you get it right, yes, this indicates the odds of the defender winning a round.
    But look at it: when dueling barbs for the first "n" times, the odds get capped at 90% if I'm the attacker and 10% if I'm the defender.
    The Constant “COMBAT_DIE_SIDES” is 1000, as showed on the xml GlobalDefines.xml at the root of the Assets/XML folder.
    Wins vs barbs is 1 at prince level. This means that they put a capped 10% chance of barbarians winning against me if I never had won a struggle against barbs. Neat...
    Now, continuing the getDefenderCombatValues code, we get to the firepower thing:
    Code:
    	int iStrengthFactor = ((iOurFirepower + iTheirFirepower + 1) / 2);
    
    	iOurDamage = std::max(1, ((GC.getDefineINT("COMBAT_DAMAGE") * (iTheirFirepower + 
    iStrengthFactor)) / (iOurFirepower + iStrengthFactor)));
    	iTheirDamage = std::max(1, ((GC.getDefineINT("COMBAT_DAMAGE") * (iOurFirepower + 
    iStrengthFactor)) / (iTheirFirepower + iStrengthFactor)));
    COMBAT_DAMAGE = (as you probably have guessed) 20. Look at the globalDefines.xml.

    This is the damage I get with each round won. And this at least is 1.

    So, let's calc the dog soldier x axeman thing again:
    The attacker get strength 400; the defender get strength 333. The attacker has firepower 400 and the defender 333. The strength factor gets to be (400+333+1)/2 = 367.
    So ourDamage gets to be 20*(333+367)/(400+367). So we get to get 17 damage. Our dog soldier gets only 17 damage each turn. So we can lose 5 rounds and still survive.
    TheirDamage get to be 20*(400+367)/(333+367). So they get 21 damage. If they lose 5 turns, they DIE.
    And the defender chances on winning a round are (333)/(333+400) = 45,4%. Plus a free lose that we have, they are really toast.
    Back to the resolveCombat function, look at this:
    Code:
    	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());
    	}
    Hey, that getCombatOdds seems to be the odds shown at the civ screen!
    All you guys that think that the odds are bad calculated, well, go for it...
    Moving on... they calc the collateralDamage:
    Code:
    	collateralCombat(pPlot, pDefender);
    First, the collateral strength is calculated, as follows:
    	iCollateralStrength = ((((getDomainType() == DOMAIN_AIR) ? airBaseCombatStr() : baseCombatStr()) * 
    collateralDamage()) / 100);
    At the civ4UnitInfos.xml we have this values for the catapult
    100
    50
    6
    So, the collateral strength for a catapult would be 5.
    They throw some dice in here, deciding what units will be eligible for damage:
    Code:
    if (pLoopUnit != pSkipUnit)
    		{
    			if (isEnemy(pLoopUnit->getTeam(), pPlot))
    			{
    				if (!(pLoopUnit->isInvisible(getTeam(), false)))
    				{
    					if (pLoopUnit->canDefend())
    					{
    						iValue = (1 + GC.getGameINLINE().getSorenRandNum(10000, "Collateral 
    Damage"));
    
    						iValue *= pLoopUnit->currHitPoints();
    
    						mapUnitDamage[pLoopUnit] = iValue;
    					}
    				}
    			}
    		}
    See, this iValue is used at the unit selection phase: the biggest values will be the damage units. So, the units with more hitpoints get more chance to be the damaged ones. But, other units can be damaged as well. The defender is the pSkipUnit: it is not eligible for collateral damage.
    Look that they don't look if the defender is immune to collateral damage. That is, catapults are eligible to receive collateral damage (zero, in this case): they get in the count!
    The damage is calculated as follows:
    Code:
    if (NO_UNITCOMBAT == getUnitCombatType() || !pBestUnit->getUnitInfo().getUnitCombatCollateralImmune(getUnitCombatType()))
    			{
    				iTheirStrength = pBestUnit->baseCombatStr();
    
    				iStrengthFactor = ((iCollateralStrength + iTheirStrength + 1) / 2);
    
    				iCollateralDamage = (GC.getDefineINT("COLLATERAL_COMBAT_DAMAGE") * 
    (iCollateralStrength + iStrengthFactor)) / (iTheirStrength + iStrengthFactor);
    
    				iCollateralDamage -= (iCollateralDamage * 
    pBestUnit->getCollateralDamageProtection()) / 100;
    
    				iCollateralDamage = std::max(0, iCollateralDamage);
    
    				int iMaxDamage = std::min(collateralDamageLimit(), (collateralDamageLimit() * 
    (iCollateralStrength + iStrengthFactor)) / (iTheirStrength + iStrengthFactor));
    				iUnitDamage = std::max(pBestUnit->getDamage(), std::min(pBestUnit->getDamage() + iCollateralDamage, iMaxDamage));
    
    				if (pBestUnit->getDamage() != iUnitDamage)
    				{
    					pBestUnit->setDamage(iUnitDamage, getOwnerINLINE());
    					iDamageCount++;
    				}
    			}
    
    			iCount++;
    Their strength is based on baseCombatStr, so the defense bonuses simply doesn't count.
    The collateral combat damage constant is 10, as written in the “GlobalDefines.xml”.
    Let's see this: A catapult attacks a stack of six longbowman:
    The collateral damage to each of them is:
    iStrengthFactor = ((500 + 600 + 1)/2) = 550
    iCollateralDamage = 10 * (500+550)/(600+550) = 9
    But some of the archers get drill2 (-20% collateral damage). So, iCollateralDamage = 9 – 9*20/100 = 9-1 = 8. These get only 8 damage.
    ImaxDamage = 50*(500+550)/(600+550), so ImaxDamage = 45.
    No, we can't put those longbowman below 55 hit points...
    Some interesting conclusions:
    that's not really a good idea to counter that rifleman army with a lot of catapults;
    catapults are good defenses against collateral damage...
    damaged catapults are as good as new catapults when doing collateral damage.
    Continuing the resolveCombat...
    The code is a little too big, you can follow just by reading. I will only coment it here:
    The first strikes are calculated before this function call, at the setCombatUnit function; basically, they see if the opponent is immune to first strikes. If not, they sum the firstStrike() stat of the unit and a random value saying how much extra first strikes the unit will get, between 0 and the extra first strikes unit value number.
    So, if the unit has 1 extra first strike chance. There will be a 50% chance that it will have 1 first strike.
    If the unit has 2 extra first strikes chance there will be equal chances that it will have 0, 1 or 2 first strikes. So, it will have 1 extra first strike, on the average.
    At each round the available first strikes for the attacker and defender are reduced by 1. If the loser of the round is out of first strikes, then it will get damage, the amount calculated at the getDefenderCombatValues.
    If the loser is the defender, the amount of damage is capped at the iCombatLimit attribute of the attacker (at the CIV4UnitInfos.xml).
    If the attacker has flanking strike and it survives combat, the flankingStrikeCombat function is activated:
    The idea is that if the defender is out of city squares, all catapults get attacked by one round by the defender, with the strength the attacker had before the beginning of combat.
    So, that's it. The combat system. The end...

  • #2
    That's a very long, but nicely complete post. I hope some of the maths fans get stuck in.
    www.neo-geo.com

    Comment


    • #3
      You should post this in Apolyton University.

      Comment


      • #4
        Nobody would notice it there.

        Comment


        • #5
          Originally posted by Sir Ralph
          Nobody would notice it there.
          Or even understand...
          RIAA sucks
          The Optimistas
          I'm a political cartoonist

          Comment


          • #6
            Like we all get it here, without some serious study.
            No matter where you go, there you are. - Buckaroo Banzai
            "I played it [Civilization] for three months and then realised I hadn't done any work. In the end, I had to delete all the saved files and smash the CD." Iain Banks, author

            Comment


            • #7
              Regarding collateral damage from siege, does this mean that if I am attacked by a catapult and I am defending with a stack of say 9 catapults and 1 warrior, there is a chance that there will be no collateral damage done at all because all the collateral damage has been absorbed by the defending catapults (which are immune to collateral damage)?

              Comment


              • #8
                Yes. Of course, that would mean that you will be at less than 50% defense against that attacking catapult, or worse, trebuchet, that I assume would get a city attack bonus.
                And also that you are in a real bad situation if you are defending a city with catapults only. Specially if they have those pesky horse archer attacking: you will suffer flank damage on each catapult... And if I remember well, flank attack doesn't have a damage limit, like a catapult attack. It may kill them.

                Comment


                • #9
                  Ops... my bad. I forgot that flank damage only occurs outside cities...


                  Oh, and also that you aren't mentioning the location...
                  Last edited by albucc; January 16, 2008, 19:40.

                  Comment


                  • #10
                    So one reason why I see AI catapults stationed in cities under siege could be because the AI knows the code for collateral damage? If this is true, it's nice to know that the AI actually has some tricks up it's sleeve.

                    Comment


                    • #11
                      More because the AI programmer knows the code
                      <Reverend> IRC is just multiplayer notepad.
                      I like your SNOOPY POSTER! - While you Wait quote.

                      Comment

                      Working...
                      X