Question: Setting Objectives

ww2commander

Emperor
Joined
Aug 23, 2003
Messages
1,243
Location
Australia
Is there a way to flag certain cities as objectives so that the AI targets them over other cities.

Basically I need some way of telling the AI to capture , for example, Leningrad, Moscow and Stalingrad based on various events in my Red Storm Rising scenario. For those that played Civ 2, there was a way to flag cities as objectives which gave the AI a goal it had to achieve.

If there is no actual way to flag objectives then can I do this through some others means such as a python variable or maybe even placing 'special' objective resources directly under the cities and make the AI value them a lot.

Hopefully someone has tried to do something similar.

Any suggestions or ideas would be appreciated as I would really like to add this into my scenario to give it the appropriate flavor :) .
 
If you want the AI to analyze for a set of conditions, then that can be done...Iustus and Blake are your guys for experts on the AI code.

Otherwise you should be able to create a mission in the SDK with fixed objectives, instead of variable that you could get from AI code.

Sorry but I am not a Civ Coder....and no mrgenie is too busy on ViSa :D
 
So to make the AI even consider capturing 1 specific city would more than likley require SDK coding :(

This is beginning to get too hard.
 
So to make the AI even consider capturing 1 specific city would more than likley require SDK coding :(

This is beginning to get too hard.

Well unless somebody else has any ideas....SDK it is.

And modding is very hard if you want to do real good stuff, not just XML tweaking or graphics stuff....although most of the SDK coding goes un-noticed. :(
 
After contacting Rhye about whether he is familiar with something like this, he suggested I investigate 'int CvPlayerAI::AI_targetCityValue(CvCity* pCity, bool bRandomize)'.

As I dont have any understanding of C++ or how the actual SDK functions get called via python, can someone who understands C++ please explain what this function does exactly and how it if it could be used as a way of setting city objectives to th AI. I understand that explaining code can be quite daunting so any effort would be hugely appreciated to help us newbs learn.

Alternatively, if you know of a med component that already uses this area in a manner required then please point me in the right direction.

Thanks :)
 
After contacting Rhye about whether he is familiar with something like this, he suggested I investigate 'int CvPlayerAI::AI_targetCityValue(CvCity* pCity, bool bRandomize)'.

As I dont have any understanding of C++ or how the actual SDK functions get called via python, can someone who understands C++ please explain what this function does exactly and how it if it could be used as a way of setting city objectives to th AI. I understand that explaining code can be quite daunting so any effort would be hugely appreciated to help us newbs learn.

Alternatively, if you know of a med component that already uses this area in a manner required then please point me in the right direction.

Thanks :)

Well, if you don't plan on doing much in terms of C++, having the function explained to you isn't going to help much, since actually changing the function is what would be important.

Your best bet for python-based ai attacking a certain set of cities is in the CvGameUtil.py file, the AI_UnitUpdate function.

The function gives to you a unit that is under AI control (whether it be an AI unit or a human unit that is currently automated). The function is called whenever that unit needs something to do. You would have to come up with a crafty way of controlling the unit. You wouldn't want to just say "move to that city", because while you'd get the entire continent of units blitzing the cities you want, they wouldn't exactly be doing it in a very strategic way. You'd probably also not want every unit to start moving towards the city, for obvious reasons.

Perhaps check to see if there are a number of other units in the same plot as the unit that is reasonably close to the city you want attacked, to put them in a group and command them all to move towards that city, leaving of course some behind.

Of course, this is all pretty low level. You'd be controlling individual movements, which means that even with a substantial amount of tweaking, you'd be making the AI pretty predictable. There's no easy way to say "make this city a higher priority" without modifying the SDK (and even there it's not foolproof).
 
Thanks for providing some insight on this Gerikes :D

Unless someone is willing to give it a try and make a mod component of sorts then I guess some lame method of using 'valuable' resources will have to do. I have yet to try this idea out either.

Just a quick question, hypothetically, if the above C++ code was to be modified, would it even achieve workable results?
 
Thanks for providing some insight on this Gerikes :D

Unless someone is willing to give it a try and make a mod component of sorts then I guess some lame method of using 'valuable' resources will have to do. I have yet to try this idea out either.

Just a quick question, hypothetically, if the above C++ code was to be modified, would it even achieve workable results?

Here's the actual code...

Code:
int CvPlayerAI::AI_targetCityValue(CvCity* pCity, bool bRandomize)
{
	PROFILE_FUNC();

	CvCity* pNearestCity;
	CvPlot* pLoopPlot;
	int iValue;
	int iI;

	FAssertMsg(pCity != NULL, "City is not assigned a valid value");

	iValue = 1;

	iValue += ((pCity->getPopulation() * (50 + pCity->calculateCulturePercent(getID()))) / 100);

	if (pCity->getDefenseDamage() > 0)
	{
		iValue += ((pCity->getDefenseDamage() / 30) + 1);
	}

	if (pCity->isCoastal())
	{
		iValue++;
	}

	if (pCity->hasActiveWorldWonder())
	{
		iValue += 2;
	}

	if (pCity->isHolyCity())
	{
		iValue += 2;
	}

	if (pCity->isEverOwned(getID()))
	{
		iValue += 3;
	}

	iValue += AI_adjacentPotentialAttackers(pCity->plot());

	for (iI = 0; iI < NUM_CITY_PLOTS; iI++)
	{
		pLoopPlot = plotCity(pCity->getX_INLINE(), pCity->getY_INLINE(), iI);

		if (pLoopPlot != NULL)
		{
			[B]if (pLoopPlot->getBonusType(getTeam()) != NO_BONUS)
			{
				iValue++;
			}[/B]

			if (pLoopPlot->getOwnerINLINE() == getID())
			{
				iValue++;
			}

			if (pLoopPlot->isAdjacentPlayer(getID(), true))
			{
				iValue++;
			}
		}
	}

	pNearestCity = GC.getMapINLINE().findCity(pCity->getX_INLINE(), pCity->getY_INLINE(), getID());

	if (pNearestCity != NULL)
	{
		iValue += max(1, ((GC.getMapINLINE().maxStepDistance() * 2) - GC.getMapINLINE().calculatePathDistance(pNearestCity->plot(), pCity->plot())));
	}

	if (bRandomize)
	{
		iValue += GC.getGameINLINE().getSorenRandNum(((pCity->getPopulation() / 2) + 1), "AI Target City Value");
	}

	return iValue;
}

I've highlighted the valuable resources part. Basically, for every resource, you're only adding one more to the city's "target value" (how "attractive" it is to the AI).

Basically, during a player's turn, right before their unit's do their auto-move, there's a 1 in 8 chance of the AI updating it's "target cities", that is, the cities it's currently targetting. There will be one target city for every land "area" (an area is a block of plots that are all land or all water. Thus, a continent is an "area", as well as a lake or ocean are each "areas"). For each land area, the ai player will draw another random number to get a 1 in 3 chance of then updating that areas current target city. So, basically every 24 turns or so, a land area will have it's target updated.

If these checks pass, the new target city is discovered by calling the above function.

What's interesting is that this function, when finished, will use the CvArea::setTargetCity function. This is probably what you would be looking for. Unfortunately, it's not exposed to python. If you could at least do that, you'd have a lot easier time.

Basically, you can then at the beginning of the enemy's turn loop through all the land areas and if the city you want to be targetted isn't the target, to change it. Because the target is changed right before the unit's turn (before your python function would be called) you probably would end up having one in every 24 turns or so having the target city not correctly set. This could be adjusted by checking at the beginning of AI_unitUpdate function rather than the player's turn function.
 
Ok, if I understand your explination on the code above and how it works(please excuse my lack of knowledge as I am not good at programming), iValue gets incremented everytime a desirable condition is met. One of these conditions is if the city location has a bonus type in its fat cross or is on top of it (not sure which!!!).

Overall, it loops through each plot that has a city and then determines the most desirable of the cities based on iValue and randomly chooses the closest one?

Am I atleast close to correct???

Thus it would be better keeping the code random rather than forcing the AI to choose specific cities (too predictable for human players) and making the resources more desirable to influence the iValue. This would give the cities are better chance of standing out but keep the random factor in the game untouched. Thus......should I modify something to make the code increase the AIs importance of lets say 'bananas'?

I apologise if I am not making any sense. I try my best to understand python and C++ :(
 
Ok, if I understand your explination on the code above and how it works(please excuse my lack of knowledge as I am not good at programming), iValue gets incremented everytime a desirable condition is met. One of these conditions is if the city location has a bonus type in its fat cross or is on top of it (not sure which!!!).

Overall, it loops through each plot that has a city and then determines the most desirable of the cities based on iValue and randomly chooses the closest one?

The randomness is just there so that the same city might not be picked every time. Each city will, in the end, have a certain value, the higher of which means it will become the city is more likely to be targeted. All the factors you mentioned are small pieces of the total equation. So, yes, in that sense, you are correct.

Thus it would be better keeping the code random rather than forcing the AI to choose specific cities (too predictable for human players) and making the resources more desirable to influence the iValue. This would give the cities are better chance of standing out but keep the random factor in the game untouched. Thus......should I modify something to make the code increase the AIs importance of lets say 'bananas'?

Unfortunately, it doesn't work like that. I highlighted the part that shows the bonuses. When it sees a bonus, it increments iValue. There is no way to make one bonus more important than another, there's nothing in the XML that will make one bonus more important than another. If you want to make it add five, you would need five bonuses.

The advice I offered earlier is just one way to make the AI more likely to target a certain city.

Changing that function is the only way to change how the AI views what city to attack. The other way to do it, that requires less SDK work but maybe more python work, would be to allow yourself to specifically set the target by exposing the CvArea::setTargetCity function (which there are tutorials that show you how to expose a single function, thus why I think it might be easier), and then use that in python to specifically select the city to target.
 
OK, I found the tutoiral you mentioned and read thru it. I understand what you mean but it is all too overwhelming considering I dont know what I am doing.

Thanks for replying back. One final question, I know I am probably testing your patience :(, but once I expose the function and use via python, how would I call it in python and what kind of parameters should I pass....an example bit of code if possible might assist.

Thanks Gerikes....its an honour to get help from one of the expert SDK modders on CFC :)
 
OK, I found the tutoiral you mentioned and read thru it. I understand what you mean but it is all too overwhelming considering I dont know what I am doing.

Thanks for replying back. One final question, I know I am probably testing your patience :(, but once I expose the function and use via python, how would I call it in python and what kind of parameters should I pass....an example bit of code if possible might assist.

You'd be passing the same arguments that the function has. In this case, you would be doing something like...

pArea.setTarget(iPlayer, pCity)

The main battle would be getting the C++ up and running.
 
Ok, I think I stumbled across a solution to this problem and it is indeed done through the resources. Whilst daydreaming through the bonus XML file, I noticed that the warlords version includes the tag <iAIObjective>.

I then did some investigation to figure out what the hell this tag does. I final tracked it down to the Rise of Rome scenario that comes with warlord and they actually use it in the exact manner that I want.....I will admit I have never checked out any of the mods that came with warlords :blush:

The scenario has 4 victory resources which are located outside each major location and the AITradeModifier is also set high for these resources along with a '1' for the iAIObjective.

I then did a search using grep32 and found several references to functions that use it....but unfortunately since I dont know much about C++, I cant figure out exactly how to read the code that uses this tag.

Heres the code snippets if someone is kind enough to explain how they work to me please :)

Code:
// Returns true if a mission was pushed...
bool CvUnitAI::AI_guardBonus(int iMinValue)
{
 PROFILE_FUNC();
 CvPlot* pLoopPlot;
 CvPlot* pBestPlot;
 CvPlot* pBestGuardPlot;
 ImprovementTypes eImprovement;
 BonusTypes eNonObsoleteBonus;
 int iPathTurns;
 int iValue;
 int iBestValue;
 int iI;
 iBestValue = 0;
 pBestPlot = NULL;
 pBestGuardPlot = NULL;
 for (iI = 0; iI < GC.getMapINLINE().numPlotsINLINE(); iI++)
 {
  pLoopPlot = GC.getMapINLINE().plotByIndexINLINE(iI);
  if (AI_plotValid(pLoopPlot))
  {
   if (pLoopPlot->getOwnerINLINE() == getOwnerINLINE())
   {
    eNonObsoleteBonus = pLoopPlot->getNonObsoleteBonusType(getTeam());
    if (eNonObsoleteBonus != NO_BONUS)
    {
     eImprovement = pLoopPlot->getImprovementType();
     if ((eImprovement != NO_IMPROVEMENT) && GC.getImprovementInfo(eImprovement).isImprovementBonusTrade(eNonObsoleteBonus))
     {
      iValue = GET_PLAYER(getOwnerINLINE()).AI_bonusVal(eNonObsoleteBonus);
      iValue += max(0, 200 * GC.getBonusInfo(eNonObsoleteBonus).getAIObjective());
      if (pLoopPlot->getPlotGroupConnectedBonus(getOwnerINLINE(), eNonObsoleteBonus) == 1)
      {
       iValue *= 2;
      }
      if (iValue > iMinValue)
      {
       if (!(pLoopPlot->isVisibleEnemyUnit(getOwnerINLINE())))
       {
        if (GET_PLAYER(getOwnerINLINE()).AI_plotTargetMissionAIs(pLoopPlot, MISSIONAI_GUARD_BONUS, getGroup()) == 0)
        {
         if (generatePath(pLoopPlot, 0, true, &iPathTurns))
         {
          iValue *= 1000;
          iValue /= (iPathTurns + 1);
          if (iValue > iBestValue)
          {
           iBestValue = iValue;
           pBestPlot = getPathEndTurnPlot();
           pBestGuardPlot = pLoopPlot;
          }
         }
        }
       }
      }
     }
    }
   }
  }
 }
 if ((pBestPlot != NULL) && (pBestGuardPlot != NULL))
 {
  if (atPlot(pBestGuardPlot))
  {
   getGroup()->pushMission(MISSION_SKIP, -1, -1, 0, false, false, MISSIONAI_GUARD_BONUS, pBestGuardPlot);
   return true;
  }
  else
  {
   FAssert(!atPlot(pBestPlot));
   getGroup()->pushMission(MISSION_MOVE_TO, pBestPlot->getX_INLINE(), pBestPlot->getY_INLINE(), 0, false, false, MISSIONAI_GUARD_BONUS, pBestGuardPlot);
   return true;
  }
 }
 return false;
}


Code:
// Returns true if a mission was pushed...
bool CvUnitAI::AI_improveBonus(int iMinValue)
{
 PROFILE_FUNC();
 CvPlot* pLoopPlot;
 CvPlot* pBestPlot;
 ImprovementTypes eImprovement;
 BuildTypes eBuild;
 BuildTypes eBestBuild;
 BuildTypes eBestTempBuild;
 BonusTypes eNonObsoleteBonus;
 int iPathTurns;
 int iValue;
 int iBestValue;
 int iBestTempBuildValue;
 int iI, iJ;
 iBestValue = 0;
 eBestBuild = NO_BUILD;
 pBestPlot = NULL;
 for (iI = 0; iI < GC.getMapINLINE().numPlotsINLINE(); iI++)
 {
  pLoopPlot = GC.getMapINLINE().plotByIndexINLINE(iI);
  if (AI_plotValid(pLoopPlot))
  {
   if (pLoopPlot->getOwnerINLINE() == getOwnerINLINE()) // XXX team???
   {
    eNonObsoleteBonus = pLoopPlot->getNonObsoleteBonusType(getTeam());
    if (eNonObsoleteBonus != NO_BONUS)
    {
     eImprovement = pLoopPlot->getImprovementType();
     if ((eImprovement == NO_IMPROVEMENT) || !(GET_PLAYER(getOwnerINLINE()).isOption(PLAYEROPTION_SAFE_AUTOMATION)))
     {
      if ((eImprovement == NO_IMPROVEMENT) || !(GC.getImprovementInfo(eImprovement).isImprovementBonusTrade(eNonObsoleteBonus)))
      {
       iBestTempBuildValue = MAX_INT;
       eBestTempBuild = NO_BUILD;
       for (iJ = 0; iJ < GC.getNumBuildInfos(); iJ++)
       {
        eBuild = ((BuildTypes)iJ);
        if (GC.getBuildInfo(eBuild).getImprovement() != NO_IMPROVEMENT)
        {
         if (GC.getImprovementInfo((ImprovementTypes) GC.getBuildInfo(eBuild).getImprovement()).isImprovementBonusTrade(eNonObsoleteBonus))
         {
          if (canBuild(pLoopPlot, eBuild))
          {
           iValue = 10000;
           iValue /= (GC.getBuildInfo(eBuild).getTime() + 1);
           // XXX feature production???
           if (iValue < iBestTempBuildValue)
           {
            iBestTempBuildValue = iValue;
            eBestTempBuild = eBuild;
           }
          }
         }
        }
       }
       if (eBestTempBuild != NO_BUILD)
       {
        if (!(pLoopPlot->isVisibleEnemyUnit(getOwnerINLINE())))
        {
         if (GET_PLAYER(getOwnerINLINE()).AI_plotTargetMissionAIs(pLoopPlot, MISSIONAI_BUILD, getGroup()) == 0)
         {
          if (generatePath(pLoopPlot, 0, true, &iPathTurns)) // XXX should this actually be at the top of the loop? (with saved paths and all...)
          {
           iValue = GET_PLAYER(getOwnerINLINE()).AI_bonusVal(eNonObsoleteBonus);
           iValue += max(0, 100 * GC.getBonusInfo(eNonObsoleteBonus).getAIObjective());
 
           if (GET_PLAYER(getOwnerINLINE()).getNumTradeableBonuses(eNonObsoleteBonus) == 0)
           {
            iValue *= 2;
           }
           if (iValue > iMinValue)
           {
            iValue *= 1000;
            iValue /= (iPathTurns + 1);
            if (pLoopPlot->isCityRadius())
            {
             iValue *= 2;
            }
            if (iValue > iBestValue)
            {
             iBestValue = iValue;
             eBestBuild = eBestTempBuild;
             pBestPlot = pLoopPlot;
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  }
 }
 if (pBestPlot != NULL)
 {
  FAssertMsg(eBestBuild != NO_BUILD, "BestBuild is not assigned a valid value");
  getGroup()->pushMission(MISSION_MOVE_TO, pBestPlot->getX_INLINE(), pBestPlot->getY_INLINE(), 0, false, false, MISSIONAI_BUILD, pBestPlot);
  getGroup()->pushMission(MISSION_BUILD, eBestBuild, -1, 0, (getGroup()->getLengthMissionQueue() > 0), false, MISSIONAI_BUILD, pBestPlot);
  return true;
 }
 return false;
}
 
I did several quick tests with the iAIObjective tag above and found that the AI does indeed favour gaurded cities over unguarded if they have an objective resource.

I placed the resource in 3 cities on my map. I defended all cities near the resources and left the remainder of the cities on my map ungaurded (around 20-30 unguarded cities.

I then gave the AI around 60 panzers and just let the game run quite a few turns. It seems that the AI captured all cities in the path of the resources (mostly guarded) and ignore all those in opposite directions that were unguarded. In the end it managed to concentrate its units on 2 out of 3 cities with the ibjective resource.

I think I may have finally found a solution to the blitzkrieg issue I may have for my scenario :)
 
Must .. expose ... CvArea::setTargetCity function .... to.. python!
 
Note sure if your hinting to me that iAIObjective is not the way to do this or whether your interested in the setTargetCity function... :lol:

setTargetCity could probably do much more but I will take the 'quick and nasty' option as I dont know C++. If you succeed in making a mod that uses these unexposed function then I would love to give it a try as well :D
 
Ok, I think I stumbled across a solution to this problem and it is indeed done through the resources. Whilst daydreaming through the bonus XML file, I noticed that the warlords version includes the tag <iAIObjective>.

I then did some investigation to figure out what the hell this tag does. I final tracked it down to the Rise of Rome scenario that comes with warlord and they actually use it in the exact manner that I want.....I will admit I have never checked out any of the mods that came with warlords :blush:

The scenario has 4 victory resources which are located outside each major location and the AITradeModifier is also set high for these resources along with a '1' for the iAIObjective.

I then did a search using grep32 and found several references to functions that use it....but unfortunately since I dont know much about C++, I cant figure out exactly how to read the code that uses this tag.

Heres the code snippets if someone is kind enough to explain how they work to me please :)

Code:
// Returns true if a mission was pushed...
bool CvUnitAI::AI_guardBonus(int iMinValue)

When a unit decides what it wants to do, it goes through all it's options and gives them a value. This function determines the value of having this unit go to a nearby bonus and defend on it, thus "guarding the bonus". The problem is that the function checks to make sure that the bonus is on their own land first, so technically, it will never use any of those tags for bonuses set in enemy lands.
 
So how do the objective bonuses work in the Rise of Rome scenario then. I know that the player gets points for each one it holds so the AI must be clued at this and try to capture them....otherwise what is the purpose of this tag and the use in the above scenario???
 
So how do the objective bonuses work in the Rise of Rome scenario then. I know that the player gets points for each one it holds so the AI must be clued at this and try to capture them....otherwise what is the purpose of this tag and the use in the above scenario???

Perhaps the tag is used elsewhere then the code you provided? I'm really not sure, I don't have warlords, I'm just going by what was given in this post.
 
Back
Top Bottom