1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

[RaRE] Reworking the combat system

Discussion in 'Civ4Col - Creation & Customization' started by Commander Bello, Sep 10, 2014.

  1. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,356
    Adding files is easy. Just copy existing files and rename them. After that use "add existing files" in the project and select them. Clear the content and add your own. Alternatively you can use VC++ wizard to add a new class, which will more or less do the same thing.

    Constructor is a member function called the same as the class and it is called whenever you make a new class. What I said was to make two unit pointers as arguments to start combat. Look at say CvUnit::CvUnit.
     
  2. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    In combination with the first question in posting #5, why a certain improvement should increase the defense modifier only in friendly territory, I found almost the same case for isActsAsCity()

    Code:
    bool CvPlot::isCity(bool bCheckImprovement, TeamTypes eForTeam) const
    {
    	if (bCheckImprovement && NO_IMPROVEMENT != getImprovementType())
    	{
    		if (GC.getImprovementInfo(getImprovementType()).[B]isActsAsCity()[/B])
    		{
    			if (NO_TEAM == eForTeam || [B](NO_TEAM == getTeam() && GC.getImprovementInfo(getImprovementType()).isOutsideBorders()) || GET_TEAM(eForTeam).isFriendlyTerritory(getTeam())[/B])
    			{
    				return true;
    			}
    		}
    	}
    	return (getPlotCity() != NULL); 
    }
    
    Once again, as long as nobody can give me any good reason why this would be necessary or at least justified, I am thinking about changing this.
     
  3. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    And the next item I am going to change if noone can give me a good reason for not doing so. :)

    Currently, the attack/defense modifiers for terrain and feature are handled seperately which is o.k. in most cases.
    Yet, in the case of swamp I think an exception from above rule has to be made.

    While all other terrains can be thought to be "stable ground", not hindering a unit's movement or attack/defense capabilities or in other words, being "neutral" to the unit's combat capabilities, swamp is moist and deep, holds impassable parts in itself and therefore, should hinder attacks and positively affect defense.
    As far as I see it, this will add with the respective attack/defense modifiers of say, jungle.
    In other words, if jungle has a defense modifier of +50% and is located on swamp (say, +20% defense), then the total should be +70% for the defender.
    For attacking units the adding of the individual (attacking) modifiers would be the same, then.
     
  4. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    In the original comments of int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const we find the following:
    I have to admit that the "unknown attacker/defender" cases are completely confusing me.
    Can anybody think of any combination in which either the attacker or the defender in a battle would be unknown?

    Point is, I'm thinking it all could be made easier if the combation pUnit (=pAttacker) / pPlot would determine whether we are calculating the values for the attacker or the defender.
     
  5. Nightinggale

    Nightinggale Deity

    Joined:
    Feb 2, 2009
    Messages:
    4,356
    The comments are not really clear, not here or anywhere else in the function in question. To make it worse, it doesn't really say what is vanilla and what is changed or why it is changed. The log says that it was added in the old svn server, hence any log entry or diff file is lost by now.

    I tried searching the function to find places where the two new setups makes a difference. It looks like they do sort of the same thing as the first two cases, except they skip comparing the two units. My guess is that it is a GUI thing for combat odds when you don't know the combat strength of the enemy. The question is: when will that happen? (rangers?)

    Another very valid question in this context: while it is how it works right now, is it how we want it to work? If the attacker can't modify combat str for the defender and vice versa, then it wouldn't really make sense to make a function setup, which has a special setup for just one of them. Worst case is calculating everything and then just use the data for one of them. Also it might make more sense to make a function, which calculates the strength of one unit and then call it for both units rather than having sort of duplicated code.


    Also now that RaR 2.2 is out, I should have git up and running shortly. Any work on this would likely benefit from being added to git while it is in development.
     
  6. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    The answer, as far as I see it, is: it will never happen.
    A battle is between an attacker and a defender. Point. Period. End of discussion.

    We may not be able to see the ranger (just to pick up your example), but it is there and it is attacking us. It is the attacker.
    And if there is no defender ("defender unknown") then there is no battle. If we don't see the ranger, we just march into his field. No battle.

    I think I am going to chance this function in an evolutionary way: first changing some subroutines for cities, swamps and so on, as mentioned previously, then changing it from int to float and finally just have it calculate the combat values for either, the attacker or the defender, depending on which values are transferred to it (including changing that currently most modifiers are only allocated on the defender's side).
     
  7. agnat86

    agnat86 Warlord

    Joined:
    Jun 30, 2011
    Messages:
    218

    That seems reasonable. If jungle or forest grows on a hills tile, you get both bonuses as well. It never made much sense that it worked differently for marsh.
     
  8. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    This posting is more or less a reminder for myself :)

    While thinking about the "fortify" effect for units I came to the conclusion that fortifying units in a city doesn't make too much sense.
    You may pick your shovel and dig a trench in the open field. You won't do this behind a city wall, obviously.

    In a first step, I will exclude the effects of the "fortifying" feature from any unit being located inside a "city" (more changes will come according to the following thoughts).

    We should have a look at the current situation:
    Units/professions can have city attack/defense modifiers.

    Nevertheless, we have different types of cities:
    a) the native camp
    (tipis and wigwams)
    b) the native or European settlement with (mostly) wooden, stable buildings
    (Cherokee longhouses, or western plank houses)
    c) the native or European city with walls
    (Mezoamerican and south american cities and European cities, if a wall etc. has been built there, or even pueblos)

    It is quite obvious that these types of cities should have different defense modifiers.
    "Type a" shouldn't provide any defense modifier
    "Type b" should provide a defense modifier
    "Type c" obviously provides the defense modifier of the respective fortification and therefore, will allow a city defense modifier for the defending unit, too.

    In turn, especially in the case of mounted units we should differ between these types in the case of attack.
    "Type a" should not influence the city attack modifier at all.
    "Type b" should somewhat reduce the city attack modifier.
    "Type c" should drastically reduce the city attack modifier and in case of mounted units, plainly set it to "0" or even a negative value. Horses have been rarely mentioned for being good wall climbers.

    About how to do this, I don't have a clear picture yet, but want to do these changes in the process of reworking the combat strength calculation.

    Comments?
     
  9. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    Another reminder for myself.

    In CvUnit::isBetterDefenderThan(...) we find the following line
    Code:
    	iOurDefense /= (getCargo() + 1);
    which decreases the defense value of cargo carrying units (especially ships) for the sake of determining the best defender.

    The problem is that this may lead to unexpected results.
    Say we're having a "fleet" of 1 Galleon (str: 5, veteran2, 3 cargo slots occupied) and a Caravel (str: 3, no promotion, no cargo).
    In that case the Galleon would be valued with a strength of (5*1.2)/4 = 1.5 => 1 (integer value!)

    Any defense action would be performed by the Caravel then, while the Galleon is clearly the much better defender.

    I think we're needing a little subroutine here checking if the cargo carries might still be the best defender, even if taking the cargo into consideration.

    Edit:
    Once again it becomes clear that using integers in combat calculations may easily lead to unexpected and undesired results...
     
  10. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    Reminder for myself

    There's a function int CvUnitAI::AI_attackOdds(const CvPlot* pPlot, bool bPotentialEnemy) const
    Spoiler :
    Code:
    int CvUnitAI::AI_attackOdds(const CvPlot* pPlot, bool bPotentialEnemy) const 
    {
    	CvUnit* pDefender;
    	int iOurStrength;
    	int iTheirStrength;
    	int iOurFirepower;
    	int iTheirFirepower;
    	int iBaseOdds;
    	int iStrengthFactor;
    	int iDamageToUs;
    	int iDamageToThem;
    	int iNeededRoundsUs;
    	int iNeededRoundsThem;
    
    	pDefender = pPlot->getBestDefender(NO_PLAYER, getOwnerINLINE(), this, !bPotentialEnemy, bPotentialEnemy);
    
    	CvCity* pCity = pPlot->getPlotCity();
    	if (pCity != NULL)
    	{
    		pDefender = pCity->getBestDefender(NULL, pDefender, this);
    	}
    					
    	if (pDefender == NULL)
    	{
    		return 100;
    	}
    
    	iOurStrength = currCombatStr(NULL, NULL);
    	iOurFirepower = currFirepower(NULL, NULL);
    
    	if (iOurStrength == 0)
    	{
    		return 1;
    	}
    
    	iTheirStrength = pDefender->currCombatStr(pPlot, this);
    	iTheirFirepower = pDefender->currFirepower(pPlot, this);
    
    
    	FAssert((iOurStrength + iTheirStrength) > 0);
    	FAssert((iOurFirepower + iTheirFirepower) > 0);
    
    	iBaseOdds = (100 * iOurStrength) / (iOurStrength + iTheirStrength);
    	if (iBaseOdds == 0)
    	{
    		return 1;
    	}
    
    	iStrengthFactor = ((iOurFirepower + iTheirFirepower + 1) / 2);
    
    	iDamageToUs = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iTheirFirepower + iStrengthFactor)) / (iOurFirepower + iStrengthFactor)));
    	iDamageToThem = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iOurFirepower + iStrengthFactor)) / (iTheirFirepower + iStrengthFactor)));
    
    	iNeededRoundsUs = (std::max(0, pDefender->currHitPoints()) + iDamageToThem - 1 ) / iDamageToThem;
    	iNeededRoundsThem = (std::max(0, currHitPoints()) + iDamageToUs - 1 ) / iDamageToUs;
    
    	iNeededRoundsUs = std::max(1, iNeededRoundsUs); // CBM wtf?
    	iNeededRoundsThem = std::max(1, iNeededRoundsThem); // CBM wtf?
    
    	int iRoundsDiff = iNeededRoundsUs - iNeededRoundsThem; 
    	if (iRoundsDiff > 0)
    	{
    		iTheirStrength *= (1 + iRoundsDiff); 
    	}
    	else
    	{
    		iOurStrength *= (1 - iRoundsDiff);
    	}
    
    	int iOdds = (((iOurStrength * 100) / (iOurStrength + iTheirStrength)));
    	iOdds += ((100 - iOdds) * (std::min(100, iOdds * 2) * withdrawalProbability() / 100)) / 100;
    	iOdds += GET_PLAYER(getOwnerINLINE()).AI_getAttackOddsChange();
    
    	return std::max(1, std::min(iOdds, 99));
    }


    Edit:
    To make things even more confusing, there's an additional function for the defender's combat values:

    Spoiler :
    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);
    
    	FAssert((iOurStrength + iTheirStrength) > 0);
    	FAssert((iOurFirepower + iTheirFirepower) > 0);
    
    	iTheirOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iTheirStrength) / (iOurStrength + iTheirStrength));
    	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)));
    }
    


    while function int getCombatOdds(CvUnit* pAttacker, CvUnit* pDefender) from CvGameCoreUtils.cpp should do the same - calculating combat odds. So, where is the difference between both?


    Spoiler :
    Code:
    int getCombatOdds(CvUnit* pAttacker, CvUnit* pDefender) // TODO CBM rework this function
    {
    	// setup battle, calculate strengths and odds
    	//////
    
    	int iAttackerStrength = pAttacker->currCombatStr(NULL, NULL);
    	int iAttackerFirepower = pAttacker->currFirepower(NULL, NULL);
    
    	int iDefenderStrength = pDefender->currCombatStr(pDefender->plot(), pAttacker);
    	int iDefenderFirepower = pDefender->currFirepower(pDefender->plot(), pAttacker);
    
    	FAssert((iAttackerStrength + iDefenderStrength) > 0);
    	FAssert((iAttackerFirepower + iDefenderFirepower) > 0);
    
    	int iDefenderOdds = ((GC.getDefineINT("COMBAT_DIE_SIDES") * iDefenderStrength) / (iAttackerStrength + iDefenderStrength));
    	if (iDefenderOdds == 0)
    	{
    		return 1000;
    	}
    	int iAttackerOdds = GC.getDefineINT("COMBAT_DIE_SIDES") - iDefenderOdds;
    	if (iAttackerOdds == 0)
    	{
    		return 0;
    	}
    
    	int iStrengthFactor = ((iAttackerFirepower + iDefenderFirepower + 1) / 2);
    
    	// calculate damage done in one round
    	//////
    
    	int iDamageToAttacker = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iDefenderFirepower + iStrengthFactor)) / (iAttackerFirepower + iStrengthFactor)));
    	int iDamageToDefender = std::max(1,((GC.getDefineINT("COMBAT_DAMAGE") * (iAttackerFirepower + iStrengthFactor)) / (iDefenderFirepower + iStrengthFactor)));
    
    	// calculate needed rounds.
    	// Needed rounds = round_up(health/damage)
    	//////
    
    	int iNeededRoundsAttacker = (std::max(0, pDefender->currHitPoints()) + iDamageToDefender - 1 ) / iDamageToDefender;
    	int iNeededRoundsDefender = (pAttacker->currHitPoints() + iDamageToAttacker - 1 ) / iDamageToAttacker;
    	int iMaxRounds = iNeededRoundsAttacker + iNeededRoundsDefender - 1;
    
    
    
    	float fOdds = 0;
    	for (int iI4 = iNeededRoundsAttacker; iI4 <= iMaxRounds; 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
    		//////
    
    		fOdds += ((float)getBinomialCoefficient(iMaxRounds, iI4)) * pow((((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES")), iI4) * pow((1.0f - (((float)iAttackerOdds) / GC.getDefineINT("COMBAT_DIE_SIDES"))), (iMaxRounds - iI4));
    	}
    
    	return ((int)(1000.0 * (fOdds + 0.0005f)));
    }


    Answer:
    AI_attackOdds(...) takes withdrawalProbability() into consideration and readjusts the individual strengths based on number of hits needed, but returns purely integer based calculation result,
    where getCombatOdds(...) works internally with floats but neglects withdrawalProbability().

    I like this. :)
    Having two three different calculations for combat odds seems to be big fun. :rolleyes:
     
  11. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    Another question arises:

    Code:
    		if (pPlot->isCity(true, getTeam()))
    		{
    			iCurrentModifier = cityDefenseModifier();
    			iModifier += iCurrentModifier;
    			if (pCombatDetails != NULL)
    			{
    				pCombatDetails->iCityDefenseModifier = iCurrentModifier;
    			}
    		}
    This code segment defines that the defender only get's the city defense bonus if being located in an own team's city which in turn raises the question if we shall still allow to attack an enemy unit in a neutral city. After all, doing so would mean fighting in a third nation's city which wouldn't make them very happy...
    A typical scenario is fighting an enemy's unit inside a native village. That never seemed right to me, especially not taking into consideration that this is not possible for naval units.

    According to my view this should be turned around: landbound battles in neutral cities should be forbidden or cause severe damage to the relationship to that third nation, but engaging ships in neutral "harbors" (which in case of native settlements will be just a bay) should be allowed at least for pirates without doing harm to the relations. If we were engaging the enemy's ships with own warships, this should cause diplomatic problems as it would do in case of landbound fighting.

    I have to admit though that I don't know yet how to teach the AI to take possible negative diplomatic effects into account. On the other hand I assume this wouldn't take place too often, so we could just test it...

    What do you think?
     
  12. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    That is a good question. By the way, I haven' t made any replies to this thread yet but I have been keeping watch on it, somewhat. When I am "mostly" done with the Economy aspects of M:C I plan to visit Combat and suggestions posted here may come in handy.

    Again, you pose a good question. One that has crossed my mine while playing. We could add a new Diplomacy option that addresses this, as it wouldn't necessarily be an Open Borders an agreement Besides, Natives don't have Open Borders treaties anyway, maybe this could be an alternative for them. In M:C I have added a new Diplomatic option of Trade Relations. Before you can have Open Borders, you must first have Trade Relations. This allows none combat units to enter their territory. Open Borders treaties will then only be given if they really like you. Historically, what would be the term here for this type of Treaty, I can't think of it at the moment? If a Player doesn't have this Treaty type then it could cause Negative effects if they do do battle in their territory. Perhaps, when it happens or before it happens (like founding a City on Native land) the offended party appears and gives you his dislike, or perhaps his blessing if he currently hates the other player.

    Why wouldn't you want someone to do battle in your land? Probably because it would cause lots of mayhem and unwanted destruction. So, perhaps when a plot hosts a battle that plot has a reduction in resource potential for a few turns, perhaps to the point where it produces nothing. This could even be an addition to Pillaging in that it causes this reduction as well. A good way to starve out an entrenched opponent.

    Adding the "positive negative" effects in Diplomacy isn't that difficult to code. The devs did a good job of making the system easily expanded through dll and XML changes.
     
  13. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    This is a good idea! :goodjob:
     
  14. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    This question I asked in posting 24.

    Meanwhile I think I've got an idea of Firaxis' intention.
    Thinking about what had to be calculated for which combination when preparing combat, I realized that we likely have quite some different cases to handle.

    First, we have to take into consideration that we will have a unit (in this context, I will use the term "unit" even for unit/profession/promotion combinations). This unit may be either the attacker or the defender.
    Say, our unit would be a Spanish Veteran as Musketman with Ranger1(please keep this in mind for the later explanations).

    Now, when we want to find out our strongest unit in a general case, we calculate base strength (as by unit or unit/profession), which results in 3.3.
    If we want to find out the strongest unit for an attack against a plot with a forest, we have to take the Ranger1 promotion into account, too (20% for attack on forests). The strengths now is 3.9.
    And finally, we have the real combat situation, where a Brave is located on that plot with the forest. Then we have to add the combat strength against natives to the Veteran, too (20% due to the Spanish trait). The strength now is 4.5.

    The same principles are valid in case of a defender, too.

    So, we have six cases to be checked:
    A = Attacker
    P = Plot
    D = Defender

    A-- (general strength of the attacker; no real combat situation)
    AP- (strength of the attacker, considering a certain plot; no real combat situation)
    APD (strength of the attacker in a defined combat situation; real combat situation, all necessary information is given)
    D-- (general strength of the defender; no real combat situation)
    DP- (strength of the defender on his plot; no real combat situation)
    DPA (strength of the defender in a defined combat situation; real combat situation, all necessary information is given)

    So, we will have six different cases for which our new maxCombatStr(...) function has to be prepared.
    For this, we will need three different parameters pAttacker, pDefender (new) and pPlot.
    By combining these three parameters we will be able to identify any of the aforementioned six different cases.

    Now I only have to find a way to bring this into readable and streamlined code...

    Edit:
    One might argue that case 4 (D--) doesn't make too much sense, as a defender will always and inevitably being located on a plot...
     
  15. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    Another reminder.

    Spoiler :
    Code:
    CvUnit* CvPlot::getBestDefender(PlayerTypes eOwner, PlayerTypes eAttackingPlayer, const CvUnit* pAttacker, bool bTestAtWar, bool bTestPotentialEnemy, bool bTestCanMove) const
    {
    	CLLNode<IDInfo>* pUnitNode;
    	CvUnit* pLoopUnit;
    	CvUnit* pBestUnit;
    
    	pBestUnit = NULL;
    
    	pUnitNode = headUnitNode();
    
    	while (pUnitNode != NULL)
    	{
    		pLoopUnit = ::getUnit(pUnitNode->m_data);
    		pUnitNode = nextUnitNode(pUnitNode);
    
    		if (pLoopUnit->isOnMap() && !pLoopUnit->isCargo())
    		{
    			// CBM: this literally means no check for best defender in enemy territory
    			if ((eOwner == NO_PLAYER) || (pLoopUnit->getOwnerINLINE() == eOwner))
    			{
    				// CBM checking for eAttackingPlayer == NO_PLAYER three times in a row
    				// CBM not to mention that eAttackingPlayer == NO_PLAYER doesn't make too much sense
    				if (([B]eAttackingPlayer == NO_PLAYER[/B]) 
    					|| !(pLoopUnit->isInvisible(GET_PLAYER(eAttackingPlayer).getTeam(), false)))
    				{
    					if ([B]!bTestAtWar[/B] 
    						|| [B]eAttackingPlayer == NO_PLAYER[/B] 
    						|| [COLOR="Blue"]pLoopUnit->isEnemy(GET_PLAYER(eAttackingPlayer).getTeam(), this)[/COLOR] 
    						|| (NULL != pAttacker && [COLOR="Red"]pAttacker->isEnemy(GET_PLAYER(pLoopUnit->getOwnerINLINE()).getTeam(), this)[/COLOR]))
    					{
    						if ([B]!bTestPotentialEnemy[/B] 
    							|| ([B]eAttackingPlayer == NO_PLAYER[/B]) 
    							||  [COLOR="Blue"]pLoopUnit->isPotentialEnemy(GET_PLAYER(eAttackingPlayer).getTeam(), this)[/COLOR] 
    							|| (NULL != pAttacker && [COLOR="Red"]pAttacker->isPotentialEnemy(GET_PLAYER(pLoopUnit->getOwnerINLINE()).getTeam(), this)[/COLOR]))
    						{
    							if ([B]!bTestCanMove[/B] || pLoopUnit->canMove())
    							{
    								if (pLoopUnit->isBetterDefenderThan(pBestUnit, pAttacker, true))
    								{
    									pBestUnit = pLoopUnit;
    								}
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    
    	return pBestUnit;
    }
    
    The bools are defaulted as false.
    The blue and red lines are doing the same.
    Why a unit should not be able to defend if it cannot move is completely beyond me.
    And the transfer of plot (this) leads to this:
    Code:
    bool CvUnit::isAlwaysHostile(const CvPlot* pPlot) const
    {
    	if (!m_pUnitInfo->isAlwaysHostile())
    	{
    		return false;
    	}
    
    	if (NULL != pPlot && pPlot->isCity(true, getTeam()))
    	{
    		return false;
    	}
    
    	return true;
    }
    
     
  16. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    I am facing a problem.

    Spoiler :
    Function maxCombatStr(...) stores individual values of the combat units in the elements of struct CombatDetails.
    In CyStructsInterface1.cpp these values seem to be made available for Python:
    Code:
    	
    ...
    python::class_<CombatDetails>("CombatDetails")
    		.def_readwrite("iExtraCombatPercent", [B]&CombatDetails::iExtraCombatPercent)[/B]
    		.def_readwrite("iNativeCombatModifierTB", &CombatDetails::iNativeCombatModifierTB)
    		.def_readwrite("iNativeCombatModifierAB", &CombatDetails::iNativeCombatModifierAB)
    ...
    
    In CvUtil.py Python does make use of them:
    Code:
    ...
    def [B]combatDetailMessageBuilder(cdUnit, ePlayer, iChange)[/B]:
    	if (cdUnit.iExtraCombatPercent != 0):
    		msg=localText.getText("TXT_KEY_COMBAT_MESSAGE_EXTRA_COMBAT_PERCENT",([B]cdUnit.iExtraCombatPercent[/B] * iChange,))
    		CyInterface().addCombatMessage(ePlayer,msg)
    ...
    ...
    def [B]combatMessageBuilder(cdAttacker, cdDefender, iCombatOdds)[/B]:
    	if (cdAttacker.eVisualOwner != PlayerTypes.UNKNOWN_PLAYER):
    		combatMessage = "%s's %s (%.2f)" %(gc.getPlayer(cdAttacker.eVisualOwner).getName(),[B]cdAttacker.sUnitName,cdAttacker.iCurrCombatStr[/B]/100.0,)
    
    And in CvDLLPython.cpp we find this:
    Code:
    [B]DllExport void DLLPublishToPython()[/B]
    {
    ...
    	[B]CyStructsPythonInterface1()[/B];
    ...
    
    What is not clear to me is how this whole complex works.
    I have created some new elements in struct CombatDetails and would like to have them displayed by Python.
    Would it be enough to insert these new elements in CyStructsInterface1.cpp?
    And how does Python distinguish between cdUnit, cdAttacker and cdDefender (see code sniplet of CvUtil.py)?

    Has been solved, I have found the way in which the data are processed.
     
  17. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    Meanwhile I have to disagree with the assumption that this might not be a performance problem.

    maxCombatStr(...) or meanwhile, my replacement of it, is called hundred of times as each single function I have stated above are calling it over and over and over again. And I even have reduced the number of calls already as I got rid of currEffectiveStr(...) and currFirePower(...) totally.

    Even if you (as human player) are just checking combat odds via mouse over it is called again and again (well, this is not really a performance issue as this is only called during the human player's turn, but it is an indication of how the whole sequence works).
    Unfortunately, the setCombatPlotHelp(...) is called from a place I currently cannot identify:
    Code:
    >	CvGameCoreDLL.dll!CvGameTextMgr::setCombatPlotHelp(CvWStringBuffer & szString, CvPlot * pPlot)  Zeile 2225	C++
     	Colonization.exe!0050b022() 	
     	Colonization.exe!004ecf73() 	
     	Colonization.exe!initCvPythonExtensions()  + 0x2357a Bytes	
     	boost_python-vc71-mt-1_32.dll!boost::python::objects::find_instance_impl()  + 0x28 Bytes	
    
    All I noticed so far is that setCombatPlotHelp(...) is called several times where neither pAttacker nor pDefender nor pPlot are changing, meaning that the whole sequence of calculations is repeated multiple times without any changes.
    Somehow we should find a way to identify, store and process whether a certain combination of pAttacker, pDefender, pPlot is checked multiple times in a row.

    This is clearly something we have to keep in mind and might be worth the effort to try to improve it.

    Nevertheless, for the time being I will leave it as it is.
    At the current moment, I have achieved to add the combat modificators on the respective side and combat calculations seem to work as intended (not regarding above mentioned unnecessary repititions, of course).

    I will run some more tests to identify possible errors and then I may release a new version of the combat system, allowing us to maintain the units' combat values in a more meaningful way.
     
  18. Commander Bello

    Commander Bello Say No 2 Net Validations

    Joined:
    Sep 3, 2003
    Messages:
    3,858
    Location:
    near Koblenz, Germany
    Another issue is that two functions (currCombatStrFloat(...) and maxCombatStrFloat(...)) seem to be called for each unit of the player from somewhere within the .exe.

    Spoiler :
    Code:
    >	CvGameCoreDLL.dll!CvUnit::CBM_CombatStr(const CvUnit * pAttacker, const CvUnit * pDefender, const CvPlot * pPlot, CBM_CombatDetails * pCBM_CombatDetails)  Zeile 8861	C++
     	CvGameCoreDLL.dll!CvUnit::maxCombatStrFloat(const CvPlot * pPlot, const CvUnit * pAttacker)  Zeile 8972	C++
     	Colonization.exe!0060d995() 	
    


    In Vanilla, currCombatStrFloat(...) even called maxCombatStrFloat(...), meaning that the latter was executed two times in a row for no added value.
    In addition to this, both functions were just meant to make integer values become float values which meanwhile isn't necessary anymore, as this is done in my new routines already.
    But the whole process was questionable anyway, as the integer values were the result of a row of calculations of integer values, which made the seemingly higher accuracy of the float value just being a higher "accuracy" of an already inaccurate base value. Anyway...

    I don't know how to supress the calls to these functions, nor do I know why and for what reason they are called.

    To make it worse, all these calls are done for possible roles of an unit: attacker and defender.
    In the current case, my cannon was calculated around 30 times against a number of Braves (with only one being in the vicinity) and even a Lynx (wherever that beast may be):
    Spoiler :
     

    Attached Files:

  19. FlashXAron

    FlashXAron Warlord

    Joined:
    Sep 24, 2010
    Messages:
    144
    Hi there,

    and thank you for making mods !

    May I ask if you have been successful in improving combat, as I am playing with my brother that TAC Final mod and I "rage quit" 3 times now, because of combat ...
    AI always have been attacking with
    1 cuirassier and 10 cannons

    okay 2 times , my defenses would have lost even with a logical combat system

    Last time he attacked my capital (Fort 90% defense) with such stack of idiotic combination
    but I had there
    2 cuirassier
    2 dragons
    4 veterans
    4 musketeers
    2 cannons
    4 garrison cannons

    HE KILLED them all and he didn't lost a single unit

    Tried a counterattack with the cuirassier, which had a 95% chance to win, but they have been killed ???


    Anyway seems that you HAVE TRIED to improve that,
    so my question HAVE YOU SUCCEEDED ?
    and does it help against idiotic art armies ? So when I use cav from the back they should kill that stack without problem ... but I think that is too much I want and I will have to wait for HOI V :) , but they also had that STACK OF DOOM problem at the beginning of HOI IV

    If so, PLEASE WHERE could we download it ?
    IS THERE A PROBLEM WITH the random numbers
    or is that combat TAC AI (Col AI) cheating like hell (played even only EXPLORER Level) !
     
  20. Kailric

    Kailric Jack of All Trades

    Joined:
    Mar 25, 2008
    Messages:
    3,095
    Location:
    Marooned, Y'isrumgone
    Do you mean you tried to counterattack but they killed you even with a 95% chance to win? I heard that Cannons where over powered in TAC. Did you not try to destroy them before they could attack? Does TAC add Civ4s ability to do Collateral Damage?
     

Share This Page