"Why don't you attack..."

Manco Capac

Friday,13 June,I Collapse
Joined
Mar 1, 2010
Messages
8,051
Getting a tired to search this specific diploevent for days, I'll call the help of people here. :)

What I'm trying to see is how it affects AI stacks to target cities. What kind of trajectory/generatepath function does it use to get to the targeted city.

First, the diploevent is in CvPlayer.cpp indeed.

Code:
void CvPlayer::handleDiploEvent(DiploEventTypes eDiploEvent, PlayerTypes ePlayer, int iData1, int iData2)
{
CivicTypes* paeNewCivics;
	CvCity* pCity;
	int iI;

	FAssertMsg(ePlayer != getID(), "shouldn't call this function on ourselves");

	switch (eDiploEvent)
	{
[...]
case DIPLOEVENT_TARGET_CITY:
		pCity = GET_PLAYER((PlayerTypes)iData1).getCity(iData2);
		if (pCity != NULL)
		{
			pCity->area()->setTargetCity(getID(), pCity);
		}
		break;
               default:
		FAssert(false);
		break;
	}

}

I looked into CvArea.cpp
Code:
void CvArea::setTargetCity(PlayerTypes eIndex, CvCity* pNewValue)
{
	FAssertMsg(eIndex >= 0, "eIndex is expected to be >= 0");
	FAssertMsg(eIndex < MAX_PLAYERS, "eIndex is expected to be < MAX_PLAYERS");

	if (pNewValue != NULL)
	{
		m_aTargetCities[eIndex] = pNewValue->getIDInfo();
	}
	else
	{
		m_aTargetCities[eIndex].reset();
	}
}

Sadly, I hit a dead end for my abilities. I don't see any reference on how units will move to get to the target city. Will it ignore passing cities in their route? Will they attack everything meanwhile because the stack is not really set to lemming type of behaviour?

I can't envision anything with what I got on hands right now. :sad:

=======================================================

Personal edits:

Code:
CvCity* CvArea::getTargetCity(PlayerTypes eIndex) const
{
	FAssertMsg(eIndex >= 0, "eIndex is expected to be >= 0");
	FAssertMsg(eIndex < MAX_PLAYERS, "eIndex is expected to be < MAX_PLAYERS");
	return getCity(m_aTargetCities[eIndex]);
}

Code:
CyCity* CyArea::getTargetCity(int /*PlayerTypes*/ eIndex)
{
	return m_pArea ? new CyCity(m_pArea->getTargetCity((PlayerTypes) eIndex)) : NULL;
}
 
I supposed it must be defined in CvUnitAI.cpp. I just don't see the missing link between both sides (Why don't you attack... and my hunch).

Normally it is used to determine what is the most juicy target city from an AI point of view AND then calculate the best plot to stumble by and from which to invade the target city.

Let's assume the first block is maximized to maximum iValue and become the new iBestValue. Then it runs the second block. ???

BTW, does someone know where the pathfinder (or whatever is defined the MISSION_GO_TO ....I guess)? And what is the difference with the generatePath function that seems to be passive one (compared to MISSION_GO_TO)?

Here's the supposed block of code that might and I say might be part of the global solution:

Spoiler :
Code:
bool CvUnitAI::AI_targetCity(int iFlags)
{
	PROFILE_FUNC();

	CvCity* pTargetCity;
	CvCity* pLoopCity;
	CvCity* pBestCity;
	CvPlot* pAdjacentPlot;
	CvPlot* pBestPlot;
	int iPathTurns;
	int iValue;
	int iBestValue;
	int iLoop;
	int iI;

	iBestValue = 0;
	pBestCity = NULL;

	pTargetCity = area()->getTargetCity(getOwnerINLINE());

	if (pTargetCity != NULL)
	{
		if (AI_potentialEnemy(pTargetCity->getTeam(), pTargetCity->plot()))
		{
			if (!atPlot(pTargetCity->plot()) && generatePath(pTargetCity->plot(), iFlags, true))
			{
				pBestCity = pTargetCity;
			}
		}
	}

	if (pBestCity == NULL)
	{
		for (iI = 0; iI < MAX_CIV_PLAYERS; iI++)
		{
			if (GET_PLAYER((PlayerTypes)iI).isAlive())
			{
				for (pLoopCity = GET_PLAYER((PlayerTypes)iI).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iI).nextCity(&iLoop))
				{
					if (AI_plotValid(pLoopCity->plot()) && AI_potentialEnemy(GET_PLAYER((PlayerTypes)iI).getTeam(), pLoopCity->plot()))
					{
						if (!atPlot(pLoopCity->plot()) && generatePath(pLoopCity->plot(), iFlags, true, &iPathTurns))
						{
							iValue = 0;
							if (AI_getUnitAIType() == UNITAI_ATTACK_CITY) //lemming?
							{
								iValue = GET_PLAYER(getOwnerINLINE()).AI_targetCityValue(pLoopCity, false, false);
							}
							else
							{
								iValue = GET_PLAYER(getOwnerINLINE()).AI_targetCityValue(pLoopCity, true, true);
							}

							iValue *= 1000;
							
							if ((area()->getAreaAIType(getTeam()) == AREAAI_DEFENSIVE))
							{
								if (pLoopCity->calculateCulturePercent(getOwnerINLINE()) < 75)
								{
									iValue /= 2;
								}
							}

							iValue /= (4 + iPathTurns*iPathTurns);

							if (iValue > iBestValue)
							{
								iBestValue = iValue;
								pBestCity = pLoopCity;
							}
						}
					}
				}
			}
		}
	}

	if (pBestCity != NULL)
	{
		iBestValue = 0;
		pBestPlot = NULL;

		if (0 == (iFlags & MOVE_THROUGH_ENEMY))
		{
			for (iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
			{
				pAdjacentPlot = plotDirection(pBestCity->getX_INLINE(), pBestCity->getY_INLINE(), ((DirectionTypes)iI));

				if (pAdjacentPlot != NULL)
				{
					if (AI_plotValid(pAdjacentPlot))
					{
						if (!(pAdjacentPlot->isVisibleEnemyUnit(this)))
						{
							if (generatePath(pAdjacentPlot, iFlags, true, &iPathTurns))
							{
								iValue = std::max(0, (pAdjacentPlot->defenseModifier(getTeam(), false) + 100));

								if (!(pAdjacentPlot->isRiverCrossing(directionXY(pAdjacentPlot, pBestCity->plot()))))
								{
									iValue += (12 * -(GC.getRIVER_ATTACK_MODIFIER()));
								}

								if (!isEnemy(pAdjacentPlot->getTeam(), pAdjacentPlot))
								{
									iValue += 100;                                
								}

								iValue = std::max(1, iValue);

								iValue *= 1000;

								iValue /= (iPathTurns + 1);

								if (iValue > iBestValue)
								{
									iBestValue = iValue;
									pBestPlot = getPathEndTurnPlot();
								}
							}
						}
					}
				}
			}
		}


		else
		{
			pBestPlot =  pBestCity->plot();
		}

		if (pBestPlot != NULL)
		{
			FAssert(!(pBestCity->at(pBestPlot)) || 0 != (iFlags & MOVE_THROUGH_ENEMY)); // no suicide missions...
			if (!atPlot(pBestPlot))
			{
				getGroup()->pushMission(MISSION_MOVE_TO, pBestPlot->getX_INLINE(), pBestPlot->getY_INLINE(), iFlags);
				return true;
			}
		}
	}

	return false;
}
 
Paths are assigned to stacks (CvSelectionGroup::generatePath(..)).
Units within a stack have access the path (like to ask for the next move) - this is handled within CvUnit (not CvUnitAI, since non AI units might have to make use of the pathfinder as well say for GoTo). The code is mostly simple access methods and as such not very informative (CvUnit::getPathLastNode() etc).

If you follow the calls through the classes, you ultimately end up at something like this:
gDLL->getFAStarIFace()->GeneratePath(&GC.getPathFinder(), pFromPlot->......)

gDLL seems to be the virtual class\interface for accessing functions defined outside of the publicly accessible SDK, so is in particular the method

virtual bool GeneratePath(FAStar*,.......)

for which i there seems to be no definition. So i have to assume, that the actual pathfinder algorithm is part of the "game engine", that is not available to modders.

The name suggests that the pathfinder is some variant of A*. You can easily read up on the internet, how it works in general, it will however probably not give a lot inside on the specific decision making of the game. Generally you can assume, that A* will return the "cheapest" viable (like in connected and non-blocked) path with respect to some cost function. I do suspect, that the cost is measured in something like Sum(terrainMovementCost) but in theory there might be other considerations as well (you could for example penalize moves through enemy boarders or something).

Aside from the question of the pathfinder, units might re-evaluate they missions on turn by turn basis. This part of the decision making should indeed be within the CvUnitAI () (for example the guard AI_guardCity method we were talking a while ago might cause the current mission to be abandoned). I am not familiar with the details of however, but i suspect this is where the "delay" is created.

So roughly speaking i imagine it goes like this: a stack of units en route to the objective caused by that diplo event, which is just 8 turns away. Along the way they might decide, for example, that a friendly city endangered by barbarians has a higher priority an leave 2 units there. Maybe twice. Then they find the path blocked (and reevaluated) by some enemy unit, causing it to take a longer path around for an extra 2 turns (and possibly get attacked, and having to delay for healing), while at it maybe decide to take the opportunity and pillage something, etc. And then, 10+ turns later the stack might have lost too much of its strength or the diplomatic situation might have changed, or another military decision made.
 
Paths are assigned to stacks (CvSelectionGroup::generatePath(..)).
Units within a stack have access the path (like to ask for the next move) - this is handled within CvUnit (not CvUnitAI, since non AI units might have to make use of the pathfinder as well say for GoTo).
The code is mostly simple access methods and as such not very informative (CvUnit::getPathLastNode() etc).

If you follow the calls through the classes, you ultimately end up at something like this:
gDLL->getFAStarIFace()->GeneratePath(&GC.getPathFinder(), pFromPlot->......)

gDLL seems to be the virtual class\interface for accessing functions defined outside of the publicly accessible SDK, so is in particular the method

virtual bool GeneratePath(FAStar*,.......)

for which i there seems to be no definition. So i have to assume, that the actual pathfinder algorithm is part of the "game engine", that is not available to modders.

The name suggests that the pathfinder is some variant of A*. You can easily read up on the internet, how it works in general, it will however probably not give a lot inside on the specific decision making of the game.

Ouch. I'm not sure to catch all that info. Only the green part was clearly understandable for me.
Let's resume for my newbie mind:

First, FAstarIFace() [Function A Star InterFace] is hidden somewhere because contrary to many codes we see uncompiled and released after the first appearance of Civ4 Vanilla (I think they never release a viewable code for CivIII, am I right?).
The devs wanted a game that is easily moddable for the majority and that was the success of CIV. Now, that A Star function, which seems like a Fermat principle for distance/cost instead is not publicized because those devs deemed who would mod that give its the deepest level of game mechanics (core mechanics).

Now looking at the function and fiddling with an actual game test, I think there is another factor other than distance and cost: natural feature defenses.
Yes, units tend to follow the lowest cost and also follow paths that offer best defense outputs for units that can get use of feature defense bonus (most mounted unit class cannot with few exceptions like the immortal and Conquistador).

Take a look on the following images. The chariot simply doesn't care about features that give defense while the immortal does. The warrior which represents most units really care to end each turn (if possible) on a possible feature (hills, forests or jungle).



They all want that iron plot but take different paths because defensive plots are another parameter.

So, this is a customized A* function with 3 variables instead of two (which is distance+cost). Defense variable seems to take second priority.

May not feel very important, but let's take again the example of AI's stacks redirected to a city under the orders the human player, given most stacks are led by a head unit and this head unit is determined by a simple check (in CvUnitAI.cpp IIRC and the second check is weird and seems to be just for DOMAIN_AIR). In an attacking stack, there is not going to have any workers or settlers, thus city attackers are prioritized. It can be either metal units early game (sometimes mixing some mounted units) or during classical age, it can be catapults. And not only mixing mounted, single :move: units with other stuff seems to make weird paths for the human player. That is why I suppose for AI stacks, it can be the head unit who decides. Right?

Why is that so important again? Well, albeit taking the same turn cost, taking different paths mean the AI stack may stumble by some unwished city targets and the stack will follow bloodlust rule (personal name for what passes by must be attacked at all cost if our odds are good).

Many people complained that "Why don't you attack..." diploevent is just a broken one that has no consequence. I do think it has consequence, but impeding targets (possibly cities or units) may change their paths.

Miscellaneous comments:

gDLL seems to be the virtual class\interface for accessing functions defined outside of the publicly accessible SDK, so is in particular the method

Can you elaborate on that subject? Or is it simply and really something no one can get access to and may represent the deepest core functions?
Is PROFILE_FUNC(); one of them? I've seen that one so many times?

Lastly, do you have an idea where is the missing link between the target city diploevent and the actual stack actions?

BTW, thanks Refar for the help again. Supposedly, you hinted to get help here but you ended to help me again. :crazyeye::lol:
 
Oh cross post to additional info you had added.

EDIT: Man, is that weird I have more fun in code reading than playing anymore lol?
 
Yeah sorry about the edit-spree :S

Virtual functions is a mechanic to "outsource" some parts of the work to other element - an interface connection of kind. It's not necessarily made inaccessible this way (for example it's the the python part of the game is connected to the sdk in a similar manner).

But, yes the AStar function seems hidden from us. I don't think the purpose was to obscure it, probably there was some performance or technical reason to compile it in the exe or some other library, rather than the public SDK (for many games the pathfinder is easily eating up more than 50% of the overall processing time and a lot of memory, so optimising it is a big issue).
But the result is the same - we don't know what exactly it does.

Here's the full head of the pathfinder interface. No idea what the iInfo is for.
virtual bool GeneratePath(FAStar*, int iXstart, int iYstart, int iXdest, int iYdest, bool bCardinalOnly = false, int iInfo = 0, bool bReuse = false)

As for the missing link - there must be some structure for storing the current mission of a stack. CvSelectionGroup::PushMission seems to be one of the methods handling the assignment of missions to units. It works on some struct type MissionData, however i am unable to find the actual definition of this structure (probably my fault, as it should be somewhere).

The Profiler thingy is just a Macro defined from FProfiler.h, and as far as i can tell inactive within the release version. I don't know it's exact purpose - probably to check on the performance of some functions, and identify possible sources of slowdown. Almost certainly nothing to do with the actual game logic.
 
That was the problem. It's a dead end for someone who doesn't know the intricacies of the code.
If you use VisualStudio (e.g. the express edition that is used quite commonly to edit/compile the Civ SDK) you can right click on the variable and let it search all places that reference this variable.

Virtual functions is a mechanic to "outsource" some parts of the work to other element - an interface connection of kind. It's not necessarily made inaccessible this way (for example it's the the python part of the game is connected to the sdk in a similar manner).
Eh, no, that is not quite what virtual methods are (although the main use here is somewhat similar to what you explain). Virtual methods are class methods that are not statically linked during compilation (usually the compiler determines which method or function is called) but instead a virtual method table is used at runtime to call the right method depending on the object. The main use is that you can have a base class that describes a kind of interface and multiple classes that inherit from it and at run time the method of the actual sub class you have at hand will be called.

In this case the actual sub class implementation is in the exe and you only get an abstract base class declaration in the SDK headers.
 
If you use VisualStudio (e.g. the express edition that is used quite commonly to edit/compile the Civ SDK) you can right click on the variable and let it search all places that reference this variable.

.

Thanks. As you can see, I'm a beginner level. :eek:
Your patience is precious to me as it can saves me hours of searching.

Later, I'll see the behaviours of a AI stack after using "Why don't you attack..." diploevent with debugger. This might give away some experimental info.
Probably, the best use of that diploevent is to make recalling the AI what was the target each turn to ensure it plunges into the target without hesitation.


The Profiler thingy is just a Macro defined from FProfiler.h, and as far as i can tell inactive within the release version. I don't know it's exact purpose - probably to check on the performance of some functions, and identify possible sources of slowdown. Almost certainly nothing to do with the actual game logic.

Ok, just like those FAssert lines, it is for debuggers or simply tools for easing the devs' work.
 
Technically your definition is correct and well, very technical.
The point is still, that the method is in this case is implemented somewhere else. As far as i am aware, Tachywaxon only tries to understand the inner workings of the AI, not to reprogram it - for now.

Using Visual Studio is a good point tho. You can use it to read code without actually creating a working compiling environment for the SDK, in that case any version of VS, VS Express or Codeblocks will do. But then again, any advanced Text editor capable of doing searches through multiple files is ok as well.
 
Yes, as Refar said, I'm working to understand the AI and its inner mechanics. I'm not really a modder.

Ok, I was finally at home to fiddle with AI stacks and I was disappointed how random it seems and sometimes stacks are hard to control (prolly due to new in-between missions).

Look at this picture; the red path is the one the AI took and it is cleary different from mine although the cost and stepdistance are the same.

I suppose when we set a target, the AI chooses the best adjacent tile to the targetCity from which to attack.



Repeating the diploeven IS a good thing to do because the AI seems to be struck by ADHD.
 
Seeing this i come to remember, as odd as it seems, even for player units the path you see in the mouseover is not necessarily the pat your unit will actually take - i noticed that a few times when played and it always bugged me a bit. Probably another sign for the "unsteady behavior" of the AI, perhaps reevaluating decisions on step-by-step or turn-by-turn basis.
 
Hmmmm....I remember TMIT ranting about paths taken that are not the path predicted by GO TO.
This is often happening to me with mounted units.

I think PieceOfMind discussed about it. I think.
 
Yes, as Refar said, I'm working to understand the AI and its inner mechanics. I'm not really a modder.

Ok, I was finally at home to fiddle with AI stacks and I was disappointed how random it seems and sometimes stacks are hard to control (prolly due to new in-between missions).

Look at this picture; the red path is the one the AI took and it is cleary different from mine although the cost and stepdistance are the same.
The path the stack took was actually the better one as it kept it one more turn inside your own borders with all the advantages that has.
 
The path the stack took was actually the better one as it kept it one more turn inside your own borders with all the advantages that has.

Still updating info that might have correlations to the customized FAStar:

I saw various macros that might be part of the cuztomized heuristics of A*.

In CvGameCoreUtils.cpp

It loads a header called: #include "FAStarNode.h"

Code:
#define PATH_MOVEMENT_WEIGHT									(1000)
#define PATH_RIVER_WEIGHT											(100)
#define PATH_CITY_WEIGHT											(100)
#define PATH_DEFENSE_WEIGHT										(10)
#define PATH_TERRITORY_WEIGHT									(3)
#define PATH_STEP_WEIGHT											(2)
#define PATH_STRAIGHT_WEIGHT									(1)

#define PATH_DAMAGE_WEIGHT										(500)

I also saw this post:

iFlags is used to get some control on the path generated. You can avoid enemy territory and similar stuff. These are the values, combine them with + :
Code:
#define MOVE_IGNORE_DANGER			(0x00000001)
#define MOVE_SAFE_TERRITORY			(0x00000002)
#define MOVE_NO_ENEMY_TERRITORY		(0x00000004)
#define MOVE_DECLARE_WAR				(0x00000008)
#define MOVE_DIRECT_ATTACK			(0x00000010)
#define MOVE_THROUGH_ENEMY			(0x00000020)
#define MOVE_MAX_MOVES				(0x00000040)
// These two flags signal to weight the cost of moving through or adjacent to enemy territory higher
// Used to reduce exposure to attack for approaching enemy cities
#define MOVE_AVOID_ENEMY_WEIGHT_2		(0x00000080)
#define MOVE_AVOID_ENEMY_WEIGHT_3		(0x00000100)
//	Koshling - This flag is used to check the turn's immediate movement for ending up next to
//	an enemy unit that we don't have a high chance of surviving an attack from
#define	MOVE_AVOID_ENEMY_UNITS		(0x00000200)
//	Don't go into plots which appear dangerous, even in our own territory unless grouped with a defender
#define	MOVE_WITH_CAUTION			(0x00000400)
#define MOVE_OUR_TERRITORY			(0x00000800)
The flag names are probably not exposed to Python so you will have to use the numbers themselves.

So, the only link we have with FAStar is in these adresses.

And basically, "flags" are what create the specificities of A* heuristics?
 
Why all of you refuse to answer? Sometimes, it is simply a nod and I don't get an answer. I notice here that if you don't have some mod under construction, you got no help.

Come on AIAndy, some comments plox.
 
Back
Top Bottom