PushMoveToMission Function Help

TC01

Deity
Joined
Jun 28, 2009
Messages
2,216
Location
Irregularly Online
I'm making use of the CySelectionGroup.pushMoveToMission function(iX, iY). This function is triggered if the coordinates are within 2 plots of the unit that is in the selection group's movement range (I also prevent more then one unit from being in that group).

I have two questions.

If I trigger this function, does the movement instantly happen before going to the next line of code? I want to do some stuff to the unit after it moves. Can I just do it like this?

Code:
pAttackerGroup.PushMoveToMission(pTarget.getX(), pTarget.getY())
pAttacker.changeMoves(60)
pAttacker.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), true)
pAttacker.setScriptData(-1)

If I make movement using a pushMoveToMission function, will the unit attack a hostile unit on the destination plot? If not, how could I make it attack?

Also, I only want the unit to attack if it has greater then 50% odds of beating the unit on the plot. Is there a function that lets you calculate combat odds?
 
I have done a little with this, but I do not fully understand it either.

The idea of "pushing" a moveto mission is that a unit may have a queue of missions. I have always called clearMissionQueue() beforehand; you should probably add that call. Are you calling this code from within the AI_unitUpdate function of CvGameUtils? If you are calling it at the end of the game turn, I am not sure it will have any effect.

As far as I know if the destination plot is occupied by an enemy, it will attack, but I am not sure what happens if the destination plot is occupied by a neutral. As a player, you would get a popup, "Do you mean to declare war with the Romans?"

I don't know how to find the combat odds. In general you probably cannot force your unit to attack some specific unit from the defender stack. So you really want to find the odds against the stack, not one specific unit. Any solution will probably involve CyPlot::getBestDefender() to start, but then you need to know the adjusted strength of that unit for your attacker. If you had that, you could probably figure what is the max strength you should attack to ensure 50% odds.

EDIT: Oh, and call finishMoves() afterwards, and return true to AI_unitUpdate so it knows you did something. You may find it helpful to look in Fury Road (see sig) in CvGameUtils.py for a working example; search for pushMoveToMission.
 
I had a similar Problem in my FFH mod. I wanted to make sure the AI uses some spells after it moves its units. As far as I know there is no python function that forces the game to immideatly run a mission. Easiest way I think would be to add a Python Callback to void CvGame::updateMoves() just before player.setAutoMoves(false);. But then you would still need to figure out which unit to add the cloak thing to.

finishmoves makes the game think the unit already moved, I don't know how this helps here.

When you push a MISSION_MOVE_TO you can set a flag to Direct attack, ignore danger, move through enemy etc. Can't remember what the default value is. There are lots of examples in the SDK.
 
Are you calling this code from within the AI_unitUpdate function of CvGameUtils? If you are calling it at the end of the game turn, I am not sure it will have any effect.

Actually, I was trying to call it from onUnitMove. It was pretty stupid. I didn't even know that function existed- but it's the one that I probably should be using.

As far as I know if the destination plot is occupied by an enemy, it will attack, but I am not sure what happens if the destination plot is occupied by a neutral.

Well, that's okay, since one prereq is that the destination plot must be occupied by an enemy (I'm trying to force a unit to attack).

I don't know how to find the combat odds. In general you probably cannot force your unit to attack some specific unit from the defender stack. So you really want to find the odds against the stack, not one specific unit. Any solution will probably involve CyPlot::getBestDefender() to start, but then you need to know the adjusted strength of that unit for your attacker. If you had that, you could probably figure what is the max strength you should attack to ensure 50% odds.

I'll try this, then. Do I use the "currCombatStrength" function? "maxCombatStrength"? There are a bunch of unit functions to get combat strength

EDIT: Oh, and call finishMoves() afterwards, and return true to AI_unitUpdate so it knows you did something. You may find it helpful to look in Fury Road (see sig) in CvGameUtils.py for a working example; search for pushMoveToMission.

I'll also look at that, thanks.

But then you would still need to figure out which unit to add the cloak thing to.

I think I can do this via script data, by applying a string to the unit before I push the move to, and then somewhere (onBeginPlayerTurn-> use the "call function at the end of player's turn" stuff?) remove the script data, apply the promotion, etc.

finishmoves makes the game think the unit already moved, I don't know how this helps here.

It actually might help, since I want the unit to "cloak" after attacking (which takes one movement cost away). If the game thinks the unit already moved, it should still have 1 movement point (the range of possible plots to push to for this is the movement points of the unit - 2 movement) that I can remove later.
 
Do I use the "currCombatStrength" function? "maxCombatStrength"? There are a bunch of unit functions to get combat strength

This is the part I don't know how to do. The problem is that your strength against one unit may be quite different than your strength against another unit. Each unitcombat has bonuses and penalties, promotions have bonuses and penalties, the terrain is related, etc.

finishmoves makes the game think the unit already moved, I don't know how this helps here.

Well, once you get done moving the unit, you have to tell the game that. Otherwise it will keep calling your routine with the same unit, or keep trying to move it on its own.
 
Well, this is the CvUnit::currCombatStr function:

Code:
int CvUnit::currCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
{
	return ((maxCombatStr(pPlot, pAttacker, pCombatDetails) * currHitPoints()) / maxHitPoints());
}

Then there's the CvUnit::currFirepower function:

Code:
int CvUnit::currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker) const
{
	return ((maxCombatStr(pPlot, pAttacker) + currCombatStr(pPlot, pAttacker) + 1) / 2);
}

Both seem to call CvUnit::maxCombatStr:

Spoiler :
Code:
int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
{
	int iModifier;
	int iCombat;

	FAssertMsg((pPlot == NULL) || (pPlot->getTerrainType() != NO_TERRAIN), "(pPlot == NULL) || (pPlot->getTerrainType() is not expected to be equal with NO_TERRAIN)");

	if (pCombatDetails != NULL)
	{
		pCombatDetails->iExtraCombatPercent = 0;
		pCombatDetails->iAnimalCombatModifierTA = 0;
		pCombatDetails->iAIAnimalCombatModifierTA = 0;
		pCombatDetails->iAnimalCombatModifierAA = 0;
		pCombatDetails->iAIAnimalCombatModifierAA = 0;
		pCombatDetails->iBarbarianCombatModifierTB = 0;
		pCombatDetails->iAIBarbarianCombatModifierTB = 0;
		pCombatDetails->iBarbarianCombatModifierAB = 0;
		pCombatDetails->iAIBarbarianCombatModifierAB = 0;
		pCombatDetails->iPlotDefenseModifier = 0;
		pCombatDetails->iFortifyModifier = 0;
		pCombatDetails->iCityDefenseModifier = 0;
		pCombatDetails->iHillsAttackModifier = 0;
		pCombatDetails->iHillsDefenseModifier = 0;
		pCombatDetails->iFeatureDefenseModifier = 0;
		pCombatDetails->iTerrainDefenseModifier = 0;
		pCombatDetails->iCityAttackModifier = 0;
		pCombatDetails->iDomainDefenseModifier = 0;
		pCombatDetails->iCityBarbarianDefenseModifier = 0;
		pCombatDetails->iClassDefenseModifier = 0;
		pCombatDetails->iClassAttackModifier = 0;
		pCombatDetails->iCombatModifierA = 0;
		pCombatDetails->iCombatModifierT = 0;
		pCombatDetails->iDomainModifierA = 0;
		pCombatDetails->iDomainModifierT = 0;
		pCombatDetails->iAnimalCombatModifierA = 0;
		pCombatDetails->iAnimalCombatModifierT = 0;
		pCombatDetails->iRiverAttackModifier = 0;
		pCombatDetails->iAmphibAttackModifier = 0;
		pCombatDetails->iKamikazeModifier = 0;
		pCombatDetails->iModifierTotal = 0;
		pCombatDetails->iBaseCombatStr = 0;
		pCombatDetails->iCombat = 0;
		pCombatDetails->iMaxCombatStr = 0;
		pCombatDetails->iCurrHitPoints = 0;
		pCombatDetails->iMaxHitPoints = 0;
		pCombatDetails->iCurrCombatStr = 0;
		pCombatDetails->eOwner = getOwnerINLINE();
		pCombatDetails->sUnitName = getName().GetCString();
	}

	if (baseCombatStr() == 0)
	{
		return 0;
	}

	iModifier = 0;

	iModifier += getExtraCombatPercent();
	if (pCombatDetails != NULL)
	{
		pCombatDetails->iExtraCombatPercent = getExtraCombatPercent();
	}

	if (pAttacker != NULL)
	{
		if (isAnimal())
		{
			if (pAttacker->isHuman())
			{
				iModifier += GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAnimalCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAnimalCombatModifierTA = GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAnimalCombatModifier();
				}
			}
			else
			{
				iModifier += GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIAnimalCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAIAnimalCombatModifierTA = GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIAnimalCombatModifier();
				}
			}
		}

		if (pAttacker->isAnimal())
		{
			if (isHuman())
			{
				iModifier -= GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAnimalCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAnimalCombatModifierAA = -(GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAnimalCombatModifier());
				}
			}
			else
			{
				iModifier -= GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIAnimalCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAIAnimalCombatModifierAA = -(GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIAnimalCombatModifier());
				}
			}
		}

		if (isBarbarian())
		{
			if (pAttacker->isHuman())
			{
				iModifier += GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getBarbarianCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iBarbarianCombatModifierTB = GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getBarbarianCombatModifier();
				}
			}
			else
			{
				iModifier += GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIBarbarianCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAIBarbarianCombatModifierTB = GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIBarbarianCombatModifier();
				}
			}
		}

		if (pAttacker->isBarbarian())
		{
			if (isHuman())
			{
				iModifier -= GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getBarbarianCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iBarbarianCombatModifierAB = -(GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getBarbarianCombatModifier());
				}
			}
			else
			{
				iModifier -= GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIBarbarianCombatModifier();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAIBarbarianCombatModifierTB = -(GC.getHandicapInfo(GC.getGameINLINE().getHandicapType()).getAIBarbarianCombatModifier());
				}
			}
		}
	}

	if (pPlot != NULL)
	{
		if (!noDefensiveBonus())
		{
			iModifier += pPlot->defenseModifier(getTeam(), (pAttacker != NULL) ? pAttacker->ignoreBuildingDefense() : true);
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iPlotDefenseModifier = pPlot->defenseModifier(getTeam(), (pAttacker != NULL) ? pAttacker->ignoreBuildingDefense() : true);
			}
		}

		iModifier += fortifyModifier();
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iFortifyModifier = fortifyModifier();
		}

		if (pPlot->isCity(true, getTeam()))
		{
			iModifier += cityDefenseModifier();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iCityDefenseModifier = cityDefenseModifier();
			}
		}

		if (pPlot->isHills())
		{
			iModifier += hillsDefenseModifier();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iHillsDefenseModifier = hillsDefenseModifier();
			}
		}

		if (pPlot->getFeatureType() != NO_FEATURE)
		{
			iModifier += featureDefenseModifier(pPlot->getFeatureType());
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iFeatureDefenseModifier = featureDefenseModifier(pPlot->getFeatureType());
			}
		}
		else
		{
			iModifier += terrainDefenseModifier(pPlot->getTerrainType());
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iTerrainDefenseModifier = terrainDefenseModifier(pPlot->getTerrainType());
			}
		}
	}

	if (pAttacker != NULL)
	{
		FAssertMsg(pAttacker != this, "pAttacker is not expected to be equal with this");

		if (plot()->isCity(true, getTeam()))
		{
			iModifier -= pAttacker->cityAttackModifier();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iCityAttackModifier = -(pAttacker->cityAttackModifier()); 
			}

			if (pAttacker->isBarbarian())
			{
				iModifier += GC.getDefineINT("CITY_BARBARIAN_DEFENSE_MODIFIER");
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iCityBarbarianDefenseModifier = GC.getDefineINT("CITY_BARBARIAN_DEFENSE_MODIFIER");
				}
			}
		}

		if (plot()->isHills())
		{
			iModifier -= pAttacker->hillsAttackModifier();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iHillsAttackModifier = -(pAttacker->hillsAttackModifier()); 
			}
		}

		iModifier += unitClassDefenseModifier(pAttacker->getUnitClassType());
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iClassDefenseModifier = unitClassDefenseModifier(pAttacker->getUnitClassType());
		}
		iModifier -= pAttacker->unitClassAttackModifier(getUnitClassType());
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iClassAttackModifier = -(pAttacker->unitClassAttackModifier(getUnitClassType()));
		}

		if (pAttacker->getUnitCombatType() != NO_UNITCOMBAT)
		{
			iModifier += unitCombatModifier(pAttacker->getUnitCombatType());
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iCombatModifierA = unitCombatModifier(pAttacker->getUnitCombatType());
			}
		}
		if (getUnitCombatType() != NO_UNITCOMBAT)
		{
			iModifier -= pAttacker->unitCombatModifier(getUnitCombatType());
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iCombatModifierT = -(pAttacker->unitCombatModifier(getUnitCombatType()));
			}
		}

		iModifier += domainModifier(pAttacker->getDomainType());
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iDomainModifierA = domainModifier(pAttacker->getDomainType());
		}
		iModifier -= pAttacker->domainModifier(getDomainType());
		if (pCombatDetails != NULL)
		{
			pCombatDetails->iDomainModifierT = -(pAttacker->domainModifier(getDomainType()));
		}

		if (pAttacker->isAnimal())
		{
			iModifier += animalCombatModifier();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iAnimalCombatModifierA = animalCombatModifier();
			}
		}
		if (isAnimal())
		{
			iModifier -= pAttacker->animalCombatModifier();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iAnimalCombatModifierT = -(animalCombatModifier());
			}
		}

		if (!(pAttacker->isRiver()))
		{
			if (pAttacker->plot()->isRiverCrossing(directionXY(pAttacker->plot(), plot())))
			{
				iModifier -= GC.getRIVER_ATTACK_MODIFIER();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iRiverAttackModifier = -(GC.getRIVER_ATTACK_MODIFIER());
				}
			}
		}

		if (!(pAttacker->isAmphib()))
		{
			if (!(plot()->isWater()) && pAttacker->plot()->isWater())
			{
				iModifier -= GC.getAMPHIB_ATTACK_MODIFIER();
				if (pCombatDetails != NULL)
				{
					pCombatDetails->iAmphibAttackModifier = -(GC.getAMPHIB_ATTACK_MODIFIER());
				}
			}
		}

		if (pAttacker->getKamikazePercent() != 0)
		{
			iModifier += pAttacker->getKamikazePercent();
			if (pCombatDetails != NULL)
			{
				pCombatDetails->iKamikazeModifier = pAttacker->getKamikazePercent();
			}
		}
	}

	if (pCombatDetails != NULL)
	{
		pCombatDetails->iModifierTotal = iModifier;
		pCombatDetails->iBaseCombatStr = baseCombatStr();
	}

	if (iModifier > 0)
	{
		iCombat = (baseCombatStr() * (iModifier + 100));
	}
	else
	{
		iCombat = ((baseCombatStr() * 10000) / (100 - iModifier));
	}

	if (pCombatDetails != NULL)
	{
		pCombatDetails->iCombat = iCombat;
		pCombatDetails->iMaxCombatStr = max(1, iCombat);
		pCombatDetails->iCurrHitPoints = currHitPoints();
		pCombatDetails->iMaxHitPoints = maxHitPoints();
		pCombatDetails->iCurrCombatStr = ((pCombatDetails->iMaxCombatStr * pCombatDetails->iCurrHitPoints) / pCombatDetails->iMaxHitPoints);
	}

	return max(1, iCombat);
}

I guess currCombatStr is the one I want, but I don't get the point of the Firepower one... or exactly how to use currCombatStr.
 
I did not go into too much detail on that code. But it seems that you call currCombatStr on the defender unit, and pass in the attacker unit, and it returns the effective strength of the defender putting all the modifiers on the defender. So if the attacker has +25% against the defender due to unitcombat, the strength which is returned is -25%.

I guess if you have this, then you can just compare to the attacker's base combat strength. If the attacker's base strength is greater or equal to the defender's adjusted strength returned by currCombatStr, then you have a 50% chance of success.
 
currCombatStrength is your effective strength, accounting for all wounds. maxFirePower is how much damage you should do per round to the opponent (units have 100 Hp, and in an even match do... I think it was 10?.. damage to each other each round, until one of them dies. If you are much stronger than the other guy at the start of the match, you do more than 10(?) damage per round, and he does less than 10(?) per round (thus he needs more round wins than you do, AND is unlikely to win during each round to boot).
 
If I make movement using a pushMoveToMission function, will the unit attack a hostile unit on the destination plot? If not, how could I make it attack?

Also, I only want the unit to attack if it has greater then 50% odds of beating the unit on the plot. Is there a function that lets you calculate combat odds?

Starting with the FuryRoad gas tanker code, I've enhanced this some and use it to create some things the AI would not naturally accomplish. Hannibal's invasion of Italia and the Vandal's migration through Gaul, Hispania, and finally Africa to mention the sort of thing that can be accomplished in this manner.

Code:
    def AI_unitUpdate(self,argsList):
          pUnit = argsList[0]
          iHannibal = gc.getInfoTypeForString("UNIT_CARTHAGINIAN_HERO1")
          iType = pUnit.getUnitType()
          name = pUnit.getNameNoDesc()
          # CvUtil.pyPrint('checking %s ' %(name) )
          if (iType == iHannibal or name == "Exercitus Hannibal" ):
		  ### get the current objective:
		  pCarthage = gc.getPlayer(1)
		  self.scriptData = pickle.loads(pCarthage.getScriptData())
		  index = self.scriptData['index']
		  list = self.scriptData['objectiveList']
		  try:
		        currentObjective = list[index]
		  except:
			return False
                  xy = currentObjective.split(",") ; x = int(xy[0]) ; y = int(xy[1])
                  pGroup = pUnit.getGroup()
                  CvUtil.pyPrint('group size = %s   name = %s  To %s please. ' %(pGroup.getNumUnits(),name,currentObjective) )
                  pGroup.clearMissionQueue()
                  pGroup.pushMoveToMission(x,y)
                  pUnit.finishMoves()
                  # CyInterface().addImmediateMessage("paging Mr Hannibal, Mr Hannibal to Capua please. %s"%(pGroup.getNumUnits()),"")
    	          return True
	  elif (name == "Thervingi raiders" or name == "Thervingi Raiders") :
		  pGoth = gc.getPlayer(39)
		  self.scriptData = pickle.loads(pGoth.getScriptData())
		  index = self.scriptData['index']
		  list = self.scriptData['objectiveList']
		  try:
		        currentObjective = list[index]
		  except:
			return False
                  xy = currentObjective.split(",") ; x = int(xy[0]) ; y = int(xy[1])
		  if ( pUnit.getX() == x and pUnit.getY() == y ):
			pUnit.setName("Thervingi   raiders")
			return True
		  else:
			unitX = pUnit.getX()
			unitY = pUnit.getY()
                        pGroup = pUnit.getGroup()
			try:
                            pFirstPlot = pGroup.getPathFirstPlot()
			except:
			    CvUtil.pyPrint('gothic seaborne invaders getPathFirstPlot failed: current X = %s  currentY = %s' %(unitX,unitY) )
			    # pUnit.setName("Thervingi   raiders")
			    pPlot = pUnit.plot()
			    iNumPlotUnits = pPlot.getNumUnits()
                            for i in range(iNumPlotUnits):
                                 pLoopUnit = pPlot.getUnit(i)
		                 if ( not pLoopUnit.isDead() ): #is the unit alive and valid?
				   if ( pLoopUnit.getOwner() == 39 ):
				       if ( name == "Thervingi raiders" ):
			                   pLoopUnit.setName("Thervingi Raiders")
			                   CvUtil.pyPrint('gothic seaborne invaders considering bailing out')
				       else:
			                   pLoopUnit.setName("")
			                   CvUtil.pyPrint('gothic seaborne invaders actually bailing out')
                            pGroup.clearMissionQueue()
                            return False

			nextX = pFirstPlot.getX()
			nextY = pFirstPlot.getY()
			CvUtil.pyPrint('gothic seaborne invaders: nextX = %s   nextY = %s   current X = %s  currentY = %s' %(nextX,nextY,unitX,unitY) )
			if ( nextX == unitX and nextY == unitY ): 
                            CvUtil.pyPrint('No path to desination?? %s' %(currentObjective) )
			    pUnit.setName("Thervingi")
                            pGroup.clearMissionQueue()
    	                    return False

			else:
                            CvUtil.pyPrint('group size = %s   name = %s  To %s please. ' %(pGroup.getNumUnits(),name,currentObjective) )
                            pGroup.clearMissionQueue()
                            pGroup.pushMoveToMission(x,y)
                            pUnit.finishMoves()
    	                    return True

Basically when an event happen there is some random number generated to select which "strategy" is to be used. The strategy is usually a stack of coordinate pairs.

Code:
    def vandalStrategy (self):
		 a = '13,21'
		 b = '18,19'
		 c = '23,18'
		 d = '27,17'

		 ca = '5,32'
		 Kimbri = '4,28'
		 between = '8,29'
		 malaga = '6,25'
                 iRand = self.getRand(2)
		 if iRand == 0:
			  olist2 = [Kimbri,malaga] 
		 elif iRand == 1:
			  olist2 = [between,malaga] 
		 else:
			  olist2 = [ca,malaga]

		 ca = '28,47'
		 Kimbri = '27,43'
		 between = '28,45'
		 obj2 = self.varyCoordPair(28,43)
		 obj3 = self.varyCoordPair(17,40)
		 obj4 = self.varyCoordPair(14,35)
		 obj5 = self.varyCoordPair(5,32)
                 iRand = self.getRand(2)
		 if iRand == 0:
			 olist = [Kimbri,obj2,obj3,obj4,obj5] 
		 elif iRand == 1:
			 olist = [between,obj2,obj3,obj4,obj5]
		 else:
			 olist = [ca,obj2,obj3,obj4,obj5]

		 vandalScriptData = {  'index': 0,
                                       'index2':0,
                                       'index3':0,
				       'objectiveList':  olist,
				       'objectiveList2':  olist2,
			               'objectiveList3': [a,b,c,d] }

	         pVandal = gc.getPlayer(self.iVandalID)
		 pVandal.setScriptData(pickle.dumps(vandalScriptData))
                 CvUtil.pyPrint('debug: vandal strategy is %s' %(vandalScriptData) )

A problem shared by all civ4 versions is that if the path is blocked, the AI breaks down. It usually just doesn't do anything. One way around is to specify each adjacent plot along the path to the objective. Painful to generate, but it solves the stalling problem. Note that the AI will pretty readily (within 1-2 turns) attack the objective plot. I haven't paid attention to the odds when it does so.

Hannibal's army is mostly 1 move units, but also has some 2 move units that tend to get adjacent first. They will wait for the main army to show up if they need the help to take the plot.

The units will usually recover some or all of the damage before setting off for the next objective. The AI will avoid attacking other than the objective plot. So if the player knows the path he can throw a wall of crappy units around the objective and foil the attackers. So typically the groups contain some "free" units as well. They don't have a name set so are free to do whatever the AI does.

When Hannibal's army captures a city a garrison is created. The units in the army (ie those that have the magic name) will all continue on.

The Thervingi raiders have a bail out clause in their routine. It is easy to block their path, in which case they won't move. So if the unit hasn't moved, it gets renamed. The 2nd name also lands in the routine, and if still stuck gets renamed yet again, which won't trigger the routine. I'm assuming that the group only hits once per turn, but that might not be true, so something more complex is probably needed.
 
Well, once you get done moving the unit, you have to tell the game that. Otherwise it will keep calling your routine with the same unit, or keep trying to move it on its own.

If you use finishmoves after pushing a MISSION_MOVE_TO the game will execute your mission next turn instead of this one. That's all finishmoves is going to do. If you use the AI_update python callback only return true when you actually push a Mission. There are lots of examples in the SDK where a Move to mission is pushed and finishmoves is never used in such cases.

To check if a path is blocked you could use generatePath, not sure if you can access it in python though.
 
Well, before xienwolf, primordial stew, and Sephi posted again, this was the function.

Code:
bOverride = false
		
pPlayer = gc.getPlayer(pUnit.getOwner())
if (not pPlayer.isHuman()):
	if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK')):
		if pUnit.getMoves() > 0:
			iMovement = pUnit.getMoves() - 2
			iiMovement = pUnit.getMoves() - 1
			for iTargetX in range(pUnit.getX()-iMovement, pUnit.getX+iiMovement, 1):
				for iTargetY in range(pUnit.getY()-iMovement, pUnit.getY()+iiMovement, 1):
					pTargetPlot = CyMap().plot(iMovement, iiMovement)
					for iTarget in range(pTargetPlot.getNumUnits()):
						pTargetUnit = pTargetPlot.getUnit(iTarget)
						if gc.getTeam(gc.getPlayer(pTargetUnit.getOwner()).getTeam()).isAtWar(gc.getTeam(gc.getPlayer(pUnit.getOwner()).getTeam())):
							pBestDefender = pTargetPlot.getBestDefender(pTargetPlot.getOwner(), pUnit.getOwner, pUnit, true, true, true)
							if pBestDefender.currCombatStr(pTargetPlot, pUnit) <= pUnit.baseCombatStr():
								pUnitGroup = pUnit.getGroup()
								pUnitGroup.clearMissionQueue()
								pUnit.setScriptData("Cloak Attack")
								pUnit.changeMoves(60)
								pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), false)
								pUnitGroup.PushMoveToMission(pTargetPlot.getX(), pTargetPlot.getY())
								pUnit.changeMoves(60)
								pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), true)
								pUnit.finishMoves()
								pUnit.setScriptData(-1)
								bOverride = true
								break
					break
				break
		
		
return bOverride

So:

1. I can't use finishMoves()?

2. Will I have to trigger the code after the pushing of the mission somewhere else, like in onBeginPlayerTurn (but use the method allowing you to trigger code on the end of someone's turn)?

3. CySelectionGroup.generatePath(pFromPlot, pToPlot, iFlags, bReuse, piPathTurns) is exposed to python. I have no idea what iFlags and bReuse mean. piPathTurns I guess is the number of turns this path should take.

So would I need to add an if statement along the lines of "if pUnitGroup.generatePath(pPlot, pTargetPlot, ____, ____, 1):" before actually pushing the mission? (again, I don't know what the third and fourth arguments mean.

4. The way I wrote the break statements doesn't work, from what I can see. (So ignore them). It would be better if I checked if bOverride was true or false, right?
 
I guess finishMoves() does not do what I thought, so you don't need it.

I am not sure what you are trying to accomplish with bOverride and your "break" statements. Assuming you are calling this as a function within AI_unitUpdate, then once you push the mission, you should just "return true", and "return false" at the bottom of the loop to indicate that nothing was found.

I am also not sure what you are doing with the scriptdata. You set it to a string and never check it. But you should not set it to -1, that will probably give a python exception.

Perhaps it is a cut and paste error, but you are calling setHasPromotion and changeMoves twice.

I guess you can use generatePath(), but keep in mind that your mission may last several turns. Just because there is a path when you trigger the mission, does not mean that the path will still be there. P.Stew gave an example of shielding a target with a ring of cheap units.
 
I am not sure what you are trying to accomplish with bOverride and your "break" statements. Assuming you are calling this as a function within AI_unitUpdate, then once you push the mission, you should just "return true", and "return false" at the bottom of the loop to indicate that nothing was found.

Ah. That would probably be a lot easier, thanks.

I am also not sure what you are doing with the scriptdata. You set it to a string and never check it. But you should not set it to -1, that will probably give a python exception.

Perhaps it is a cut and paste error, but you are calling setHasPromotion and changeMoves twice.

The setHasPromotion and changeMoves() first removes the promotion (with a movement cost) and then adds the promotion (with a movement cost). To see exactly what this is for, see here. I'm making the AI unit, if it is "cloaked", look for units to attack, "decloak" (which has a movement cost), attack, and then "cloak" again (which also has a movement cost).

I don't know if onUnitMove actually touches units that have a pushed mission, but I assume it does. I have code there to make the AI "cloak" if on space terrain (this is for FF), so I can add a check for script data.

I believe setting script data to -1 removes all script data. Am I wrong?
 
The setHasPromotion and changeMoves() first removes the promotion (with a movement cost) and then adds the promotion (with a movement cost).

One of us is confused about what setHasPromotion does. As far as I know, this just sets a flag on the unit. The cloak *action button* you point in your link operates totally differently. Using setHasPromotion, you can put on and take off the promotion for free, with no movement cost. The action button probably does a bunch of other stuff, such as charging one movement point, before it calls setHasPromotion. Think of your routine as going in and making a change at a much lower level.

You may find you need to call the action button routine to do all its other stuff, rather than just setHasPromotion. You would need to investigate where that button routine is in the python for that mod.

I believe setting script data to -1 removes all script data. Am I wrong?

setScriptData takes a string. Use "" instead of -1. I am guessing you haven't run the code yet, since this would throw a python alert.
 
One of us is confused about what setHasPromotion does. As far as I know, this just sets a flag on the unit. The cloak *action button* you point in your link operates totally differently. Using setHasPromotion, you can put on and take off the promotion for free, with no movement cost. The action button probably does a bunch of other stuff, such as charging one movement point, before it calls setHasPromotion. Think of your routine as going in and making a change at a much lower level.

You may find you need to call the action button routine to do all its other stuff, rather than just setHasPromotion. You would need to investigate where that button routine is in the python for that mod.

I am:

Code:
[B]pUnit.changeMoves(60)[/B]
pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), false)
pUnitGroup.PushMoveToMission(pTargetPlot.getX(), pTargetPlot.getY())
[B]pUnit.changeMoves(60)[/B]
pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), true)

changeMoves(60) takes 1 full movement point off the unit. That's all the cloak and decloak action buttons do. Well... I've added a 2D Sound in the as-yet-unreleased-version-v1.2, but otherwise that's all it does. From handleInput:

Code:
		if (inputClass.getNotifyCode() == 11 and inputClass.getData1() == 660 and inputClass.getData2() == 660):
			self.iPushedButtonUnit = g_pSelectedUnit
			self.iPushedButtonUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), true)
			self.iPushedButtonUnit.changeMoves(60)
			CyAudioGame().Play2DSound('AS2D_CLOAK_ON')

		if (inputClass.getNotifyCode() == 11 and inputClass.getData1() == 661 and inputClass.getData2() == 661):
			self.iPushedButtonUnit = g_pSelectedUnit
			self.iPushedButtonUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), false)
			self.iPushedButtonUnit.changeMoves(60)
			CyAudioGame().Play2DSound('AS2D_CLOAK_OFF')

The promotion applies the invisibility.

setScriptData takes a string. Use "" instead of -1. I am guessing you haven't run the code yet, since this would throw a python alert.

Ah, never mind. I messed up, sorry. No, I haven't run this yet, I was going to until I saw xienwolf's, primordial stew's, and Sephi's posts.
 
This code does not do what you are thinking it does:
Code:
pUnit.changeMoves(60)
pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), false)
pUnitGroup.PushMoveToMission(pTargetPlot.getX(), pTargetPlot.getY())
pUnit.changeMoves(60)
pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK'), true)

This all happens instantaneously. The mission may take a few turns to execute, but all that pushmoveto does is give it the order to go. So, you are taking 120 off the movement, adding and then immediately removing the promotion, all at the same time.

I think you are somehow expecting that this code evaluates over many turns, but it doesn't.
 
Okay, I thought that command actually made as much of the movement that could happen on one turn happen before going to the next line of code. So... if I specify that the plot should only be targeted if a possible path to it can be done in one turn (the piPathTurns part of generatePath()), can I trigger all the stuff below the order to go on the end of the AI's turn, as shown here?
 
I'm not quite sure what this action would look like, if the human player did it. Would I move adjacent to the target unit, click the uncloak action button, click the target to attack, and then click the cloak action button, all in one turn? Or would that occur across multiple turns? Each time AI_unitUpdate is called, it is performing actions for that turn. You can push a mission which will take multiple turns to complete, but anything the function does takes effect immediately. So adding and removing the promotion like your current code does has zero net effect.

If you want the action to take effect across multiple turns, you should probably use scriptdata to store some "state", and then take different actions depending on the state.
 
Well, one condition is that the target plot must have a route to the attacker (the unit this code is running for)'s plot that takes one turn to go across.

So I was assuming that the entire thing would take one turn, like this:

Would I move adjacent to the target unit, click the uncloak action button, click the target to attack, and then click the cloak action button, all in one turn?
 
Looking at CvSelectionGroup::pushMission() when the mission is appended to the queue (which is cleared first by default in this case) it starts the mission.
 
Top Bottom