[SDK] new function - code review request

keldath

LivE LonG AnD PrOsPeR
Joined
Dec 20, 2005
Messages
7,366
Location
israel
hello good sdk people of Civ4.

its probably a long shot,
but i am writing some AI code for Ranged Strike (Dale combat mod and VIP by Vncent modification).

anyway,
i wrote a function that checks if a plot is worthy of ranged attack.
i would love for thoughts and feedbacks.

Spoiler :

Code:
int CvUnitAI::isPlotWorthRanged(CvPlot const& kPlot)const
{
    int seigeCount = 0;
    int dmgdUnits = 0; //how many damaged units are there
    int totalLimitedDmgUnits = 0; // sum of the dmg taken by all units on the plot
    int attackerCombatLimit = this->combatLimit();   
    CvUnit* nextUnit = NULL;
    int unitCount = kPlot.getNumUnits();
    //loop over all units on the pottential plot to rangestrike
    for (int i = 0; i < unitCount; i++)
    {
        nextUnit = kPlot.getUnitByIndex(i);
        if (nextUnit != NULL)
        {
            if (nextUnit->canRangeStrikeK())
            {
                seigeCount++;//count how many siege units on target plot.
            }
            else if 
            (nextUnit->getDamage() >= this->combatLimit())
            {
                dmgdUnits++;
                totalLimitedDmgUnits+= nextUnit->getDamage();
            }   
        }
    }
   
    int netUnits = std::max(unitCount - seigeCount,0);
    if (unitCount > 0)
    {
        if (netUnits == dmgdUnits &&  attackerCombatLimit > 0 &&
            (totalLimitedDmgUnits / netUnits)  >= attackerCombatLimit + 10)
            /* if the avg of all damange form dmged units larget than the attacker combatlimit + 10%
               and all the units that are not seige are damaged, its a worthy strike
            */
            return 1;
        else 
        {
            //check our stack unit content
            int seigeCountUs = 0;
            CvUnit* nextUnitUs = NULL;
            int unitCountUs = getPlot().getNumUnits();
            //loop over all units on the pottential plot that we attacked from
            for (int i = 0; i < unitCountUs; i++)
            {
                nextUnitUs = getPlot().getUnitByIndex(i);
                if (nextUnitUs != NULL)
                {
                    if (nextUnitUs->canRangeStrikeK())
                    {
                        seigeCountUs++;//count how many siege units on target plot.
                    }
                }
            }
            int netUnitsUs = std::max(unitCountUs - seigeCountUs,0);
            if (netUnitsUs > 0)
            {
                //if we have 1/3 of stronger total attack value from the enemy plot stack
                //than its a worthy ranged strike. ht /2 and /3 are just a way to make sure 
                //ai that attacks have enough power to attack after this ranged strike .
                int ourNoneRangedSTR = (NetTotalStreangth(&getPlot()) / netUnitsUs) / 2;
                int enemyNoneRangeSTR = (NetTotalStreangth(&kPlot) / netUnits) / 3;
                if (ourNoneRangedSTR >= enemyNoneRangeSTR)
                    return 2;//will double the value of this plot on the AI_rangedStrikeValueK()
                // if we have 2 more siege ranged units than the enemy, 
                // our added siege ranged units may even the odds after the range strike
                //should add some check for how bad is the difference of power is.
                if (seigeCountUs - seigeCount > 3)
                    return 1; 
            }
        }
    }
   
    return 0;
}


appreciate any help.
 
General code review points:
  • This function should return a bool, not an int
  • Using "siege" (besides the misspelling) in variable names here can be misleading, because the code actually checks for range strike units instead of siege units, which suggests the unit combat type
About the actual logic, can you explain a little more what the range combat rules are and what your thinking is about the considerations the AI should make?

I am especially confused by NetTotalStrength (besides the misspelling), is that a sum of unit strengths on a plot? Of which player? Why are we dividing that by the number of units of a specific type?
 
oh HI Leoreth,
thank you for responding!
ok, so since i posted this, i had many changes and trials.
also changed this function quite a bit.

so - range combat:
land untis with a tag of rangeStrike (acts exactly like airRange) will gain that range , like air units.
the ranged units , on my mod are siege, but they can be any (thus the explicit name i used). cannot move into a plot with Enemy unit.
meaning it cannot us attack as regular civ4 units.
all ranged units willl have combatlimit.
only 2 missions:
Ranged attack (altered from vanila)
bombard - vanilla.

in another version i wrote,
i merged the ranged mission with the bombard, so ,
one action would do both.
it worked pretty nice for human, according to my rules.
ai code for it was added but i didnt test much as i did on the current version,

Ai should:
look for a valuable plot it find a enemy on. filtering weaker valued plots with lower chances of success or efficiency.
and strike.
ai should attack as long as it is not threatened or combatlimit was reached.
if it has no valid target, it will carry on with other actions, preferably move out, stay with a stack, retreat to city, attack a city, load to transoport.


i made a hybrid of dale , VIP and Realism Invictus codes and i basically changed many ,any parts and added in.
here are two functions im working on :
Spoiler :

Code:
//this is based off the function- removed some stuff from it.int CvSelectionGroupAI::AI_sumStrength
int CvUnitAI::NetTotalStreangth(const CvPlot* pAttackedPlot,bool bRanged) const
{
    // <K-Mod>
    bool const bCountCollateral = true;//(pAttackedPlot && pAttackedPlot != plot()); // </K-Mod>
    int const iBaseCollateral = (bCountCollateral ?
            estimateCollateralWeight(pAttackedPlot, getTeam()) : 0);
    bool bCheckCanAttack = true;
    int    iSum = 0;
    for (CLLNode<IDInfo> const* pUnitNode = pAttackedPlot->headUnitNode(); pUnitNode != NULL; pUnitNode = pAttackedPlot->nextUnitNode(pUnitNode))
    {
        CvUnitAI const& kLoopUnit = *::AI_getUnit(pUnitNode->m_data);
        if (kLoopUnit.isDead() ||
            // advc.opt: (If we want to count air units, then this'll have to be removed.)
            !kLoopUnit.canFight()) 
        {
            continue;
        }
        if (kLoopUnit.rangedStrike() > 0 && !bRanged)//count only none ranged unita 
            continue;
        if (kLoopUnit.rangedStrike() == 0 && bRanged)//count only ranged units 
            continue;
        if (kLoopUnit.getDomainType() != DOMAIN_LAND && kLoopUnit.getDomainType() != DOMAIN_SEA)
            continue; // advc: Moved up
        int const iUnitStr = kLoopUnit.AI_currEffectiveStr(pAttackedPlot, &kLoopUnit,
                bCountCollateral, iBaseCollateral, bCheckCanAttack);
        // </advc.159>
        iSum += iUnitStr;
        // K-Mod end
    }
    return iSum;
}
int CvUnitAI::isPlotWorthRanged(CvPlot const& kPlot, bool bCheckWorthAttack) const
{   
    int powerValue = 0;
    //defender
    int seigeCount = 0; //how many ranegd uunits
    int dmgdUnits = 0; //how many damaged units are there
    int maxDmgdUnits = 0; //how many units are down to their combatlimit value
    int fullhpUnits = 0; //how many untis with full hp
    int totalLimitedDmgUnits = 0; // sum of the dmg taken by all units on the plot
    //attacker
    int attackerCombatLimit = combatLimit();   
//defender info on the plot
    CvUnit* nextUnit = NULL;
    int unitCount = kPlot.getNumUnits();
    //loop over all units on the pottential plot to rangestrike
    // i think i can merge the NetTotalStreangth func to these loops
    for (int i = 0; i < unitCount; i++)
    {
        nextUnit = kPlot.getUnitByIndex(i);
        if (nextUnit != NULL)
        {
            if (nextUnit->canRangeStrikeK())
            {
                seigeCount++;//count how many siege units on target plot.
            }
            else 
            {
                if (nextUnit->currHitPoints() == nextUnit->maxHitPoints())
                {
                    fullhpUnits++;
                }
                //reduncdent....
                else if (nextUnit->getDamage() > 0)
                {
                    dmgdUnits++;
                }
                //how many units that exeeds the combat limit
                if (attackerCombatLimit != 0 && attackerCombatLimit != 100 
                    && nextUnit->getDamage() < attackerCombatLimit)
                {
                    maxDmgdUnits++;
                    totalLimitedDmgUnits+= nextUnit->getDamage();
                }   
            }
        }
    }
//attacker info on the attacking plot
    CvUnit* nextUnitus = NULL;
    int unitCountus = getPlot().getNumUnits(); //how many units 
    int seigeCountus = 0; // how many ranged unit attacker have
    for (int i = 0; i < unitCountus; i++)
    {
        int nextUnitus = getPlot().getUnitByIndex(i);
        if (nextUnitus != NULL)
        {
            if (nextUnitus->canRangeStrikeK())
            {
                seigeCountus++;
            }
        }
    }
    int netUnitsus = unitCountus - seigeCountus; 
    int netUnits = unitCount - seigeCount; 
    int allNetUnits = netUnitsus+netUnits
    int allSiegeUnits = seigeCountus+seigeCount
    //calculate Streangth og the plot for none ranged units
    int ourNoneRangedSTR = NetTotalStreangth(&getPlot(),true); 
    int enemyNoneRangeSTR = NetTotalStreangth(&kPlot,true);
    int allNoneRangedSTR = ourNoneRangedSTR + enemyNoneRangeSTR;
    //calculate streangth to ranged units only
    int ourRangedSTR = NetTotalStreangth(&getPlot(),false);
    int enemyRangeSTR = NetTotalStreangth(&kPlot,false);
    int allRangedSTR = ourRangedSTR + enemyRangeSTR;
    //each if statement have a precentage to fill a 100%  numbers are in the comments.
    //negative value - to find the worst and most dangerus plot near the attacker?
    if (allNetUnits > 0)
    {
        //number of total none ranged units ratio us to them    lets say, we need to have above 40& of their number    0.3
        if ((::round(netUnitsus /allNetUnits)) * 100 >= 40 )
            powerValue +=20;
        else 
            powerValue -=20;       
    }   
    if     (allSiegeUnits > 0)
    {
        //number of total ranged units ratio us to them    lets say, we need to have above 40& of their number    0.1
        if ((::round(seigeCountus /allSiegeUnits)) * 100 >= 20 )
            powerValue +=10;
        else 
            powerValue -=10;   
    }
    if (allNoneRangedSTR > 0)
    {
        //our average none range unit STR us to them, lets say we need to have at least 90 rate to the enemy 0.4   
        if  (::round(ourNoneRangedSTR/allNoneRangedSTR) *100 >= 90)
            powerValue +=30;
        else 
            powerValue -=30;
    }
    if (allRangedSTR > 0)
    {
        //our average range unit STR us to them, lets say we need to have at least 50 rate to the enemy    - lets say our ranged unit str should be full...    0.2
        if  (::round(ourRangedSTR/allRangedSTR)*100 >= 50)
            powerValue +=20;
        else 
            powerValue -=20;
    }
   
    //anther calculation which i want to factor in how many damaged units there are and if all of the units are damaged, but not on the max combat limit minus combat limit 
    //number : -10 and -20. due to heal rate, units keeps healig and raising their streangth above comvat limit - this get the ai stuck on attacking every turn
    //so i was hoping to tell the ai that if unit is damaged enought, let it go...
    //i think this is only relvant attacker looks for more validation for plot attacking.
    if (bCheckWorthAttack)
    {   
        //if there are some damaged units with average dmg under our combat limit less 10% of it
        if (netUnits-dmgdUnits != 0 && (totalLimitedDmgUnits / netUnits)  < attackerCombatLimit - 10)
        {   
            powerValue = std::max(powerValue * 110,100);
        }
        //if all damaged units with average dmg under our combat limit less 20% of it    
        else if (netUnits-dmgdUnits == 0 && (totalLimitedDmgUnits / netUnits)  < attackerCombatLimit-20)
        {
            powerValue = std::max(powerValue * 120,100);
        }
    }
    return powerValue;
}


the isPlotWorthRanged will check a plot for the amount of none ranged units, ranged units, average strength per unit, how many damaged units there are and how many are at combatLimit.
this will do two things, tell me if a plot is worth attacking (my loop func is based of bool CvUnitAI::AI_airStrike(int iThreshold))
the function is sort of a double check for the things i want to define after the loop found a good plot to attack.
the negative values i need to read as a danger plot that is near the attacker, so it will bring null from the loop even if there is a good one to attack according to the AI_airStrike similar func.
the positive will help me determine if i want to attack it ,lets say if the result is 80% good enough.

im also using kmod advc for basis and i also adedd in some odds functions that exists there.

i have a git with on going development.

thank you Leoreth,
anything you think i would be heappy to hear, im far froma programmer of c++, just some stuff i picked up.

sorry for the spelling errors.
 
Last edited:
the function is sort of a double check for the things i want to define after the loop found a good plot to attack. the negative values i need to read as a danger plot that is near the attacker, so it will bring null from the loop even if there is a good one to attack according to the AI_airStrike similar func.
Maybe better to make these checks already in that function resembling AI_airStrike. If the attacker is in danger, should it always retreat? In that case, the danger check should arguably happen before looking for any targets. If the danger is supposed to be weighed against the benefits of attacking, then some sort of utility value needs to be computed – but not necessarily in two stages. A single function
int CvUnitAI::AI_rangeStrikeValue(CvPlot const& kTargetPlot) const
might be enough, returning negative values (or maybe small positive values) for attacks that aren't worthwhile.

Why is the number of enemy siege units (seigeCount) in the target plot relevant?
Why is the total damage that maximally damaged units have taken (totalLimitedDmgUnits) relevant; specifically the ratio of that damage total and the number of enemy non-siege units (netUnits)?

Explicitly discouraging the AI from attacking stacks that are already near the damage limit seems a bit convoluted anyway. Ideally, the damage limit should be taken into account when evaluating how much damage the range strike will inflict in total.

Looks like the K-Mod AI code for air strikes does take the damage limit into account (though not for collateral damage):
Code:
iStrikeValue = std::max(0,
      std::min(pDefender->getDamage() + iDamage, airCombatLimit())
      - pDefender->getDamage());
iStrikeValue += iDamage * collateralDamage() *
      std::min(iDefenders - 1, collateralDamageMaxUnits()) / 200;

Implementation issues:
Your unit counting loops need to skip neutral units, invisible units and probably also civilians. Specifically, in the target plot, you should probably only count units that would be affected by a range strike. A function for checking that doesn't seem to exist; should check the same things as CvPlot::getNumVisibleEnemyDefenders.
im also using kmod advc for basis and i also adedd in some odds functions that exists there.
This doesn't work
::round(netUnitsus /allNetUnits)
because they're two integer variables, so the result is already rounded (down). This
::round(netUnitsus /(double)allNetUnits)
would work, but, to avoid floating point math, it's better to use the K-Mod macro
ROUND_DIVIDE(netUnitsus, allNetUnits)
In the bCheckWorthAttack branch, the divisions don't seem to be protected against netUnits==0. In comparisons, one can sometimes avoid rounding (and issues with division by 0) entirely by turning the divisor into a multiplier on the other side of the (in-)equation.

Side note about getUnitByIndex: I think this (Vanilla) function was only intended for Python export. I see that some old code of mine also uses it, but I'm going to change that to
for (CLLNode<IDInfo> const* pNode = kPlot.headUnitNode(); ...
and I'll restrict getUnitByIndex to calls from CyPlot. Mainly to be consistent; though looping via headUnitNode/ nextUnitNode should also be a little bit faster, at least in AdvCiv.
 
hi,
thanks for the reply,

cks already in that function resembling AI_airStrike.
my thought is this - if the plot finder for a target found a good target to attack,
i want that in the same loop to check other surrounding plots to be looked at for potential threats from larger stacks and such.(maybe compare the total strength of all the plots in the loop all own units vs all enemy units?
evaluating if the force present on that area of battle has a chance.

Why is the number of enemy siege units (seigeCount) in the target plot relevant?
if there are ranged/siege units it means that they can range attack also, so, add another positive value if atatcker has more of these.
also might be helpful for later range > 1.

Why is the total damage that maximally damaged units have taken (totalLimitedDmgUnits) relevant
2 options,
either some untis are damaged or all units are damanged.
if some, than check how many units form that are at max combat limit dmg
if all, do the same.
only attack if the percentage ive set is enough.
consider none ranged units, as range units - cannot kill, thus, not a death risk.

the damage limit should be taken into account when evaluating how much damage the range strike will inflict in total.
how?


CvPlot::getNumVisibleEnemyDefenders.
your right , i saw it after, yet to change it.
thanks.

rounding values.
ok thanks for that, ill do the proper changes.
i lack some thought about the values i did,
calculate a worth precentage how?
100 * value / 100,
or 0 + 20 + 40 ...n according to hat i did with max of 100.
having tough time to define the evaluation it self.


getUnitByIndex
yup, saw your latest commits :)
cool
 
I guess we both got distracted from this topic. I'm at least going to post a short reply to the first half of your post that I had already written last month:
the damage limit should be taken into account when evaluating how much damage the range strike will inflict in total.
how?
By computing the total damage that a ranged strike will inflict? Though, without collateral damage, this computation isn't all that interesting; it's something like
min(iRangedAttackDamage, pTargetUnit->currHitPoints() - combatLimit())
pTargetUnit should be the unit that will get hit by the ranged attack.
my thought is this - if the plot finder for a target found a good target to attack,
i want that in the same loop to check other surrounding plots to be looked at for potential threats from larger stacks and such.(maybe compare the total strength of all the plots in the loop all own units vs all enemy units?
evaluating if the force present on that area of battle has a chance.
Perhaps it's sufficient to do that analysis once upfront for an area centered at the ranged unit. AI_localAttackStrength and AI_localDefenceStrength in CvPlayerAI have a range parameter.
 
thanks man,

i didnt edit the code, but i plan to very soon.

min(iRangedAttackDamage, pTargetUnit->currHitPoints() - combatLimit())
ye good one.

CvPlayerAI have a range parameter.
i added some changes to the AI_localAttackStrength and AI_localDefenceStrength functions ,
i use these for now.
the function in here is not yet being used, i disabled it until ill optimize it more.
 
Hi,

i completed a rewrite of my ranged strike ai functions
and functionality.

first,
ranged attack game option, renders vanilla bombardment inactive.
if the plot is a city,
the city will take bombard damage following damage to the units, where the remaining city defense acts as a damage reducer for the defender units.


flow of functions:

CvUnitAI::AI_rangeAttackK
AI_rangeStrikeTargetPlotK
evalPlottoStrike
evalUnitPlottoStrike
AI_rangeStrikeTargetPlotK


CvUnitAI::AI_rangeStrikeTargetPlotK
loop over the range of attack, start evaluating plots.

Spoiler :

Code:
// DOTO-MODrangedattack-keldath - START - Ranged Strike AI realism invictus
//realism invictus splitted this AI_rangeStrikeTargetPlot
//attacker is to check if we can bombard a close city
CvPlot* CvUnitAI::AI_rangeStrikeTargetPlotK(int iSearchRange, CvUnitAI* pAttacker) const
{
    CvPlot* pBestPlot = NULL;
//    CvPlot* pWorstPlot = NULL;
    int iBestValue = 0;
    bool isDangerPlot = false;
    for (SquareIter it(*this,iSearchRange , false);it.hasNext(); ++it)
    {
        CvPlot& kLoopPlot = *it;
        if (kLoopPlot.isVisibleEnemyUnit(this) /*|| // K-Mod: disabled
            (kLoopPlot.isCity() && AI_potentialEnemy(kLoopPlot.getTeam()))*/)
        {
            {    //advc adjustment - i changed to wieghted odds as f1rpo wrote above - had to change the fn also.keldath
                int iValue = 0;
                iValue = evalPlottoStrike(kLoopPlot);
                //if hte ivalue of the plot is -1 -> it means the plot is defined as a danger plot
                //which means, that even if we have a good target - its better not to attack
                //by sending out nukk the rangestrike mission will be false and other functions will kick in - hopefull - run away to hide!
                if (iValue == -1)
                    return NULL;
                if (!evalUnitPlottoStrike(kLoopPlot))
                {
                    //based on units vs units the plot deemed either dangeouros 
                    //or worthless ,skip this plot from being attacked.
                    continue;
                }

                iValue += AI_rangedStrikeValueK(kLoopPlot); //uses the method of air stike. 

                if (iValue > iBestValue)
                {
                    iBestValue = iValue;
                    pBestPlot = &kLoopPlot;
                }   
            }
        }
    }
    return pBestPlot;
}

CvUnitAI::AI_rangedStrikeValueK :
evaluate the value of a plot in range. UNUSED for now - replaced by
CvUnitAI::evalPlottoStrike:
Spoiler :

Code:
int CvUnitAI::AI_rangedStrikeValueK(CvPlot const& kPlot) const
{   
/*  based ::AI_airStrike  */
    int iStrikeValue = 0;
    int iAdjacentAttackers = 0; // (only count adjacent units if we can air-strike)
    int iAssaultEnRoute = !kPlot.isCity() ? 0 : GET_PLAYER(getOwner()).
            AI_plotTargetMissionAIs(kPlot, MISSIONAI_ASSAULT, getGroup(), 1);

    // TODO: consider changing the evaluation system so that instead of simply counting units, it counts attack / defence power.

    iAdjacentAttackers += GET_PLAYER(getOwner()).AI_adjacentPotentialAttackers(kPlot);
    //if (kPlot.isWater() || iPotentialAttackers > 0 || kPlot.isAdjacentTeam(getTeam()))
    CvUnit* pDefender = rangedStrikeTargetK(&kPlot);
    if (pDefender != NULL)
    {
        //CvUnit* pDefender = kPlot.getBestDefender(NO_PLAYER, getOwner(), this, true);
        int iDamage = rangeCombatDamageK(pDefender);
        int iDefenders = kPlot.getNumVisibleEnemyDefenders(this);
        iStrikeValue = std::max(0, std::min(pDefender->getDamage() + iDamage,
                                combatLimit()) - pDefender->getDamage());
/* the best defender should get a unit with exceedeing combat limit i think.
        //if the best defender is with all the limit, its 0 worth.
        if ((iStrikeValue + 100 / 100) >= combatLimit())
            iStrikeValue = 0;
*/       
        iStrikeValue += iDamage * collateralDamage() * std::min(iDefenders - 1, collateralDamageMaxUnits()) / 200;
        iStrikeValue *= (3 + iAdjacentAttackers + iAssaultEnRoute / 2);
        iStrikeValue /= (iAdjacentAttackers + iAssaultEnRoute > 0 ? 4 : 6) +
                std::min(iAdjacentAttackers + iAssaultEnRoute / 2, iDefenders)/2;
        if (kPlot.isCity(true, pDefender->getTeam()))
        {
            // units heal more easily in a city / fort
            iStrikeValue *= 3;
            iStrikeValue /= 4;
        }
        if (kPlot.isWater() && (iAdjacentAttackers > 0 || kPlot.getTeam() == getTeam()))
            iStrikeValue *= 3;
        else if (kPlot.isAdjacentTeam(getTeam())) // prefer defensive strikes
            iStrikeValue *= 2;
    }
    // bombard (destroy improvement / city defences)
    //add the city value to the strike value - prefer cities.
    if (kPlot.isCity())
    {
        CvCity const* pCity = kPlot.getPlotCity();
        if(pCity->getDefenseModifier(true) > 0) // advc.004c
        {
            iStrikeValue += std::max(0, std::min(pCity->getDefenseDamage() +
                    bombardRate(), GC.getMAX_CITY_DEFENSE_DAMAGE()) -
                    pCity->getDefenseDamage());
            iStrikeValue *= iAdjacentAttackers + 2*iAssaultEnRoute +
                    (getArea().getAreaAIType(getTeam()) == AREAAI_OFFENSIVE ? 5 : 1);
            iStrikeValue /= 2;
        }
    }
    //if no city and theres a bonus on the plot, value it (rangedattack also bombards improvements.
    else
    {
        BonusTypes eBonus = kPlot.getNonObsoleteBonusType(getTeam(), true);
        if (eBonus != NO_BONUS && kPlot.isOwned() &&
            canAirBombAt(plot(), kPlot.getX(), kPlot.getY()))
        {
            iStrikeValue += GET_PLAYER(kPlot.getOwner()).AI_bonusVal(eBonus, -1);
            iStrikeValue += GET_PLAYER(kPlot.getOwner()).AI_bonusVal(eBonus, 0);
        }
    }
    return iStrikeValue;
}

CvUnitAI::NetTotalStreangth:
add up total strength of a unit stack on a plot.
Spoiler :

Code:
//int CvSelectionGroupAI::AI_sumStrength
int CvUnitAI::NetTotalStreangth(const CvPlot* pAttackedPlot,bool bRanged) const
{
    // <K-Mod>
    bool const bCountCollateral = true;//(pAttackedPlot && pAttackedPlot != plot()); // </K-Mod>
    int const iBaseCollateral = (bCountCollateral ?
            estimateCollateralWeight(pAttackedPlot, getTeam()) : 0);
    bool bCheckCanAttack = true;
    int    iSum = 0;
    for (CLLNode<IDInfo> const* pUnitNode = pAttackedPlot->headUnitNode(); pUnitNode != NULL; pUnitNode = pAttackedPlot->nextUnitNode(pUnitNode))
    {
        CvUnitAI const& kLoopUnit = *::AI_getUnit(pUnitNode->m_data);
        if (kLoopUnit.isDead() ||
            // advc.opt: (If we want to count air units, then this'll have to be removed.)
            !kLoopUnit.canFight()) 
        {
            continue;
        }
        if (kLoopUnit.rangedStrike() > 0 && !bRanged)//exclude the ranged units
            continue;
        if (kLoopUnit.getDomainType() != DOMAIN_LAND && kLoopUnit.getDomainType() != DOMAIN_SEA)
            continue; // advc: Moved up
        int const iUnitStr = kLoopUnit.AI_currEffectiveStr(pAttackedPlot, &kLoopUnit,
                bCountCollateral, iBaseCollateral, bCheckCanAttack);
        // </advc.159>
        iSum += iUnitStr;
        // K-Mod end
    }
    return iSum;
}


CvUnitAI::evalPlottoStrike:
evaluate the plot based on power comparisons along with
providing a ranged strike breaker if in the range strike theres a threat.
Spoiler :

Code:
int CvUnitAI::evalPlottoStrike(CvPlot const& kPlot) const
{
    int worthScore = 0;
    CvCity* targetpCity = kPlot.getPlotCity();//add check for kLoopPlot.isEnemyCity(*this))
    CvCity* attackerpCity = getPlot().getPlotCity();
    CvPlayerAI const& kOwner = GET_PLAYER(getOwner());
    int iOurDefence = kOwner.AI_localDefenceStrength(plot(), getTeam(), getDomainType(), 1, false, false);
    int iEnemyStrength = kOwner.AI_localAttackStrength(&kPlot, NO_TEAM, DOMAIN_LAND, 0, true, false, false, false);//range 0 i hope will catch ,that plot only
    //if we are 1/4 weeker - we have a close plot that endanger us, so stop the range atack.
    //add some parameters and maybe a random evaluation for the AI or some other factors
    if ((4 * iOurDefence < 3 * iEnemyStrength))//make to vars, random, maybe include aggressive ai here...
    {
        return -1;
    }
    //dont stop bombing a city 
    if (targetpCity != NULL)
    {
        worthScore += 100;//attacking a plot with a city is better
    }
    //from a city , bomb all threats
    if (attackerpCity != NULL)
    {
        worthScore += 110; //withing a city all plots are ok to
    }
    //basic score calc take range units to the odds
    worthScore += AI_getGroup()->AI_getWeightedOdds(&kPlot, true, true);/*used to be->AI_getGroup()->AI_attackOdds(&kLoopPlot, true);*/
    //check attack odds of the entire stack
    int iAttackofNoneRangedOdds = AI_getGroup()->AI_getWeightedOdds(&kPlot, true, false);
    //check stack vs stack chances + note that i added to CvSelectionGroupAI::AI_sumStrength check for ranged 
    int iStackComparison = AI_getGroup()->AI_compareStacks(&kPlot, true);
    //reduced to 70 was 80 and 100 was 150 - i though, ranged should have better lower risk to strike
    //joint to one check, attcker stack must be stronger in addition to have more none ranged strength.
    //i had it separated before/
    if (iAttackofNoneRangedOdds > 70/*GC.getSKIP_RANGE_ATTACK_MIN_BEST_ATTACK_ODDS()*/
        &&
        iStackComparison < 100/*GC.getSKIP_RANGE_ATTACK_MIN_STACK_RATIO()*/)
    {
        worthScore += (iAttackofNoneRangedOdds + iStackComparison); //if our chances for our best defender is ok..70 0 add the value to the plot
    }
    else
    {
        return -1;
        //if our stack and none ranged both lower, this plot is a danger  - which means - stack should not use range attack -> better to run away...
    }
    /*
        added another touch to calculate plot worthwhile.
        check surronding plots for danger, 
        the tile range is the attacker range +1 - maybe in the future
        the code will allow usage of range strike above 1....
        anyway - generate a random negetive deducer of the already calculated worth score...
        maybe if theres danger - we should avert the attck all together?
    */
    worthScore += kOwner.AI_isAnyPlotDanger(getPlot(), rangedStrike()+1) ? (-1 * GC.getGame().getSorenRandNum(worthScore, "Plot Danger deduction")) : 0;
    return worthScore;
}


CvUnitAI::evalUnitPlottoStrike:
another estimation to evalute if the plot has units worth striking.
evaluation is combat limit focused.
Spoiler :

Code:
bool CvUnitAI::evalUnitPlottoStrike(CvPlot const& kPlot) const
{
    /*this function will determine whether this plot contains units 
    //that are worthy to strike.
    //factoring, unit composition, damage taken by the enemy alreay,
    the amount of ranged units ratio enemy to attacker.*/
    int rangedCntdef = 0;
    int dmgdUnitsDef = 0;
    int maxDmgdUnitsDef = 0;
    int fullhpUnitsDef = 0;
    int totalLimitedDmgUnitsDef = 0;
    int rangedCntAtr = 0;
    int unitsCntAtr = 0;
    int attackerCombatLimit = combatLimit();
    int unitCntDef = kPlot.getNumVisibleEnemyDefenders(this);
    //loop over all units on the pottential plot to rangestrike
    // i think i can merge the NetTotalStreangth func to these loops
////////////////////////////
//defender
///////////////////////////
    for (CLLNode<IDInfo> const* pNode = kPlot.headUnitNode(); pNode != NULL;pNode = kPlot.nextUnitNode(pNode))
    {
        CvUnit const& nextUnit = *::getUnit(pNode->m_data);
        if (&nextUnit != NULL)
        {
            if (nextUnit.canRangeStrikeK())
            {   
                rangedCntdef++;
                //ignoring damaged ranged units.
            }
            else 
            {
                if (nextUnit.getDamage() == 0)
                {
                    fullhpUnitsDef++;
                }
                else
                {
                    dmgdUnitsDef++;
                }
                //how many units that exeeds the combat limit
                if (attackerCombatLimit != 0 && attackerCombatLimit != 100 
                    && nextUnit.getDamage() < attackerCombatLimit)
                {
                    maxDmgdUnitsDef++;
                    totalLimitedDmgUnitsDef += nextUnit.getDamage();
                }   
            }
        }
    }
////////////////////////////
//attacker
///////////////////////////
//    CvUnit* nextUnitus = NULL;
    unitsCntAtr = getPlot().getNumVisibleEnemyDefenders(this);
    //loop over all units on the pottential plot to rangestrike
    // i think i can merge the NetTotalStreangth func to these loops
    for (CLLNode<IDInfo> const* pNode = getPlot().headUnitNode(); pNode != NULL;
        pNode = getPlot().nextUnitNode(pNode))
    {
        CvUnit const& nextUnitus = *::getUnit(pNode->m_data);
        if (&nextUnitus != NULL)
        {
            if (nextUnitus.canRangeStrikeK())
            {
                rangedCntAtr++;//count how many siege units on target plot.
            }
        }
    }
   
////////////////////////////
//evaluation start
///////////////////////////
    int netUnitsAtr = unitsCntAtr - rangedCntAtr; //how many none ranged units the attacker have
    int netUnitsDef = unitCntDef - rangedCntdef;//how many none ranged units the defender have
    int netFullHpUnitsDef = netUnitsDef - dmgdUnitsDef; //how many unit with full hp that are not ranged the defender have
    //average damage taken already in the enemy target plot.using rounddivide to avoind floats.
    int averageDmgofStack = ROUND_DIVIDE(totalLimitedDmgUnitsDef, (netUnitsDef == 0 ? totalLimitedDmgUnitsDef : netUnitsDef));
    CvPlayerAI const& kOwner = GET_PLAYER(getOwner());
    //first handle none range units.
    if (netUnitsDef > 0)
    {   
        if (attackerCombatLimit != 0 && attackerCombatLimit != 100)
        {       
            /*if the units on the target plot, already have an average damage close(+10)
            //to the attacker combatlimit,but there are still units with full hp
            its worth the ranged strike some more - hurt the remaining full hp ones.*/
            if (netFullHpUnitsDef > 0 &&
                (averageDmgofStack < attackerCombatLimit + 10))//maybe make a var
            {
                return true;
            }
            //if all the units are damaged, but the combat limit is not enough (+20)
            //inverstigate deeper if its worth a strike.
            else if (netFullHpUnitsDef == 0 &&
                    (averageDmgofStack  < attackerCombatLimit+20))//maybe make a var
            {
                //i came up with this
                //calculate total average strength sum of attack/defender ratio WITHOUT ranged units
                //if our stack is stonger, it means the plot is ok to attack.
                //maybe i should add that to the int evalPlottoStrike or delete this all together....
                int ourNoneRangedSTR = NetTotalStreangth(&getPlot(),true);
                int enemyNoneRangeSTR = NetTotalStreangth(&kPlot,true);
                bool checkAllSTR = true;
                if(netUnitsAtr != NULL && netUnitsDef != NULL)
                {
                    if ((ourNoneRangedSTR / (netUnitsAtr == 0 ? ourNoneRangedSTR : netUnitsAtr))
                        >=
                        (enemyNoneRangeSTR / (netUnitsDef == 0 ? enemyNoneRangeSTR : netUnitsDef))
                        )
                        return true;
                }
            }
            else 
            {
                /*  if all units are damaged and combat estimated limit is reached
                    dont waste a strike on this plot....*/
                return false;
            }
        }   
        else 
        {
            //add area check? maybe fix that crappi weighted odds....//old comment from me...
            /*
                if the attacker have no combat limit,  calculate the plot worthiness 
                while counteing all none ranged units and compare the  strength of the stacks.
                same method is used above:
                calculate total average strength sum of attack/defender ratio WITHOUT ranged units
                if our stack is stonger, it means the plot is ok to attack.
                maybe i should add that to the int evalPlottoStrike or delete this all together....
            */
            int ourNoneRangedSTR = NetTotalStreangth(&getPlot(),false);
            int enemyNoneRangeSTR = NetTotalStreangth(&kPlot,false);
            if (netUnitsAtr != 0 && netUnitsDef != 0)
            {
                if ((ourNoneRangedSTR/ (netUnitsAtr == 0 ? ourNoneRangedSTR : netUnitsAtr)) 
                    >= 
                    (enemyNoneRangeSTR / (netUnitsDef == 0 ? enemyNoneRangeSTR : netUnitsDef))
                   )
                {
                    return true;
                }
            }
        }
    }
    //if there are no none ranged units in the defender stack
    //just check how many ranged we have for now, if we have more ranged, continue blasting
    //i should change this to factor in combat limit, total stength or comparestack,
    //some way to tell if ranged untis should attack ranged units.
    if ((rangedCntAtr - rangedCntdef >= 2) && rangedCntdef > 0  )//consider making this as a va
    {
        return true;
    }
    //if the plot target is a city and no units  - dont deem this 
    //plot un worthy - let the ai bombared cities with no units but with buuilding defense.
    //this will probably wont happen in reality.
    if (rangedCntdef == 0 && netUnitsDef == 0 && kPlot.getPlotCity() != NULL)
        return true;
    return false;
}


CvUnitAI::AI_rangeAttackK main ai ranged strike function
Spoiler :

Code:
//realism invictus splitted this AI_rangeStrikeTargetPlot
bool CvUnitAI::AI_rangeAttackK(MovementFlags eFlags, bool bAppend, bool bManual, MissionAITypes eMissionAI, CvPlot* pMissionAIPlot, CvUnit* pMissionAIUnit)
{
    FAssert(canMove());
    if (!canRangeStrikeK())
        return false;
    CvPlot* pBestPlot = AI_rangeStrikeTargetPlotK(rangedStrike(), this);//this means the attacker
    if (pBestPlot != NULL)
    {
        FAssert(!atPlot(pBestPlot));
        getGroup()->pushMission(MISSION_RANGE_ATTACK, pBestPlot->getX(), pBestPlot->getY(), eFlags, bAppend, bManual, eMissionAI, pMissionAIPlot, pMissionAIUnit);
        return true;
    }
    return false;
}


22 functions that shorten repetetive code in the uniai file.
Spoiler :

Code:
bool CvUnitAI::AI_rangeAttackOrSkipK(MovementFlags eFlags, bool bAppend, bool bManual, MissionAITypes eMissionAI, CvPlot* pMissionAIPlot, CvUnit* pMissionAIUnit)
{
    if (AI_rangeAttackK(eFlags, bAppend, bManual, eMissionAI, pMissionAIPlot, pMissionAIUnit))
    {
        return true;
    }
    else
    {
        getGroup()->pushMission(MISSION_SKIP, -1, -1, eFlags, bAppend, bManual, eMissionAI, pMissionAIPlot, pMissionAIUnit);
        return true;
    }
}
bool CvUnitAI::AI_rangeAttackOrFortifyK(MovementFlags eFlags, bool bAppend, bool bManual, MissionAITypes eMissionAI, CvPlot* pMissionAIPlot, CvUnit* pMissionAIUnit)
{
    if (AI_rangeAttackK(eFlags, bAppend, bManual, eMissionAI, pMissionAIPlot, pMissionAIUnit))
    {
        return true;
    }
    else
    {
        getGroup()->pushMission(isFortifyable() ? MISSION_FORTIFY : MISSION_SKIP, -1, -1, eFlags, bAppend, bManual, eMissionAI, pMissionAIPlot, pMissionAIUnit);
        return true;
    }
 
i changes some stuff in the above,

right now what im struggling with mostly,
is to make the ai retreat, or run away, when theres a close by threat - bigger stack for example.
what ai function could help me make the ai to do some move to saftey code and not attack, or stand still?
 
right now what im struggling with mostly,
is to make the ai retreat, or run away, when theres a close by threat - bigger stack for example.
what ai function could help me make the ai to do some move to saftey code and not attack, or stand still?
I suppose your ranged units still have AI types ATTACK, ATTACK_CITY or COLLATERAL(?).
CvUnitAI::AI_collateralMove, for example, normally calls the (BtS) range strike code via AI_anyAttack. If your range attack code concludes that the unit is in too much danger and therefore picks no target, then AI_collateralMove will call AI_retreatToCity and AI_safety eventually. Though some other routines are tried before that, e.g. AI_heal, that may or may not have adequate checks for nearby enemies. One could argue that a unit (stack) should check for danger once upfront – but I guess how much danger is tolarable depends on what the unit is able to do, i.e. what targets are available in the case of range strikes. (Civilian units do some simplistic danger checks upfront; e.g. AI_isAnyPlotDanger in AI_settleMove.)
As for avoiding moves into dangerous tiles, I've written a reply in the AdvCiv thread.
 
hey,
tnx for answering here.

yes, indeed uses both ai types that you mentioned.
i had some nice tweaks done up till now.
thing is with danger assessment, is that for ranged units it might be tricky.
since these are attacking without retaliation damage , so the question is, should a range strike hit any enemy near it?
so ,
i used the regular localstrength compare to do it, along with my added funcs.
but, when in a city, i saw that the ranged wont strike, so i did some more manipulations to make it strike regardless of the risk.
but i know i must consider the ai evacuate and such. so the range check comes right before the skip mission.
so if its a skip, it means all other ai checks were made, so ill do one more for range.

i noticed these - AI_isAnyPlotDanger in AI_settleMove
i considered using some parts of the AI_settleMove.
for now i got nice results. ill continue to share my progress here.

thank you.
 
thing is with danger assessment, is that for ranged units it might be tricky. since these are attacking without retaliation damage , so the question is, should a range strike hit any enemy near it?
Doesn't seem all that different from the decision whether to bombard the defenses of a city (AI_bombardCity). Rather than checking for danger, AI_bombardCity checks whether the besieging stack is strong enough to (eventually) take the city. Otherwise, even if there is no danger, there is nothing to be gained by bombarding the city. I guess, ideally, one should check both – can the ranged unit inflict any meaningful damage on an enemy stack -and- is the ranged unit's stack threatened by any enemy stack.
so the range check comes right before the skip mission. so if its a skip, it means all other ai checks were made, so ill do one more for range.
Right, so I assume that you call AI_rangeAttack in two places – once before AI_safety (and before the various low-priority routines), and once after AI_safety; the second call with a parameter like bForce=true.
 
Top Bottom