CvUnitAI::AI_rangeAttack

vincentz

Programmer
Joined
Feb 4, 2009
Messages
3,614
Location
Denmark
I cant figure out when the AI decides to use CvUnitAI::AI_rangeAttack.
It is only listed in one other place in CvUnitAI.cpp and that is in CvUnitAI::AI_anyAttack
PHP:
	if (AI_rangeAttack(iRange))
	{
		return true;
	}
Since the AI isnt very consistent in using it, I want to figure out how it decides to use it???
Any help on the matter is much appreciated.

cheers
Vincentz :D
 
The way these methods work is that inside AI_rangeAttack(), the AI determines whether a ranged attack is possible and a good idea and immediately performs it. In that case, true is returned as a way to communicate that an attack has been performed.

So you would need to look inside that method to see when the AI will make a ranged attack.
 
PHP:
bool CvUnitAI::AI_rangeAttack(int iRange)
{
	PROFILE_FUNC();

	FAssert(canMove());

	if (!canRangeStrike())
	{
		return false;
	}

	int iSearchRange = AI_searchRange(iRange);

	int iBestValue = 0;
	CvPlot* pBestPlot = NULL;

	for (int iDX = -(iSearchRange); iDX <= iSearchRange; iDX++)
	{
		for (int iDY = -(iSearchRange); iDY <= iSearchRange; iDY++)
		{
			CvPlot* pLoopPlot = plotXY(getX_INLINE(), getY_INLINE(), iDX, iDY);

			if (pLoopPlot != NULL)
			{
				if (pLoopPlot->isVisibleEnemyUnit(this) || (pLoopPlot->isCity() && AI_potentialEnemy(pLoopPlot->getTeam())))
				{
					if (!atPlot(pLoopPlot) && canRangeStrikeAt(plot(), pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE()))
					{
						int iValue = getGroup()->AI_attackOdds(pLoopPlot, true);

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

	if (pBestPlot != NULL)
	{
		FAssert(!atPlot(pBestPlot));
		getGroup()->pushMission(MISSION_RANGE_ATTACK, pBestPlot->getX_INLINE(), pBestPlot->getY_INLINE(), 0);
		return true;
	}

	return false;
}
Well, according to my very limited C++ skills, it starts by looking at all the plots within range, then if an enemy is visible or plot is a city with an enemy on it. Then it checks if the plot is not the same plot it stands on (?) and it can Rangestrike at the plot. Then it sets the value based on the attackOdds (which is strange, coz Rangestrike would be 100% right?) and checks if plotvalue is better than previous looped plot. If it is then Bestplot is the new target. Lastly it checks to see if it have a target at all, and if it have, it pushes the RangeAttack for all possible RangeAttack units in the group.

But, if it indeed calculates and find a spot, then why wouldnt it always do so whenever there is a potential target? Is it depending on the UnitAI (unitAI attack), the case weights, a randomizer? Sometimes it will go suicide-attack, sometimes it will range-attack, and sometimes it just skips turn, even though it would be able to Range-Attack in all three circumstances. And since there are no RangeAttack AI, or any weights for it neither, Im kinda lost. Does it only do RangeAttacks in case of AI_anyAttack and/or is it overridden with AI_collateralMove/AI_attackCityMove.

F.ex. here :
if I put 2 catapults outside the city, it will RangeAttack with both.
Spoiler :

if I put 3, it will RangeAttack with 2 and attack normal with 1
Spoiler :

and putting 4, it Rangeattack with 1 and go attack normal with 3
Spoiler :
 
But, if it indeed calculates and find a spot, then why wouldnt it always do so whenever there is a potential target? Is it depending on the UnitAI (unitAI attack), the case weights, a randomizer? Sometimes it will go suicide-attack, sometimes it will range-attack, and sometimes it just skips turn, even though it would be able to Range-Attack in all three circumstances. And since there are no RangeAttack AI, or any weights for it neither, Im kinda lost. Does it only do RangeAttacks in case of AI_anyAttack and/or is it overridden with AI_collateralMove/AI_attackCityMove.
I think your interpretation of the AI_rangeAttack() code is correct: the AI cannot decide against performing a range attack there, if there is a valid target it will always do it.

What exactly happens during the anyAttack() method? The order is important here: if regular attacks are considered first, the AI might decide that previous ranged attacks have given the remaining units a better chance at attacking directly, making that the preferred action. The number of available units on the tile might influence that. Still not sure what would motivate units to skip their turn entirely though. Could be a deliberate action on the part of the collateral damage AI to skip a turn because nothing is to be gained from collateral attack. The unit AI might not be written with the ability to range attack for collateral damage units in mind.
 
I think you want to look at where AI_anyAttack() is being called. There are probably conditions before or around it that prevent it from being called when you are expecting it to.
 
lol, sometimes I just need to get my head out of my ass.
Instead of working towards getting the AI to want to Rangestrike, I just needed to block it from direct attacks... (its quite opposite with my kids, as it is no good trying to block them, but instead have to encourage them the other way).

So... whenever it called a MOVE_DIRECT_ATTACK mission, I blocked it with
if ((pBestPlot != NULL) && (!canRangeStrike()))
which seems to work as intended. Maybe I add a (getDomainType() != DOMAIN_WATER) somewhere in case I want rangestrikes from ships later on. (they should be able to do both direct and rangestrikes imho)

Well, on to my next project. Thanks guys :D

Spoiler :


edit :
Giving it a second thought it would prolly by wiser to check before function starts as it will save some computing (not a lot, as it is only for the sieges).
so..
PHP:
	if ((canRangeStrike()) && (getDomainType() != DOMAIN_SEA))
	{
		return false;
	}
in front of the 5 direct attacks : AI_cityAttack, AI_anyAttack, AI_stackAttackCity, AI_poach and AI_solveBlockageProblem

edit :
Nope, didnt work. had to place it in the back before if (pBestPlot != NULL)

Also changed the value for rangestrikes. It was attackodds, but that would be really silly as it should be strongest defender, so...
PHP:
//int iValue = getGroup()->AI_attackOdds(pLoopPlot, true);
int iValue = GET_PLAYER(getOwnerINLINE()).AI_getEnemyPlotStrength(plot(), 2, false, false);
 
Step one would be to ensure that you can attack between domains. Sure it sounds obvious, but it's a silly thing to overlook and it tells if the bug is inside or outside the AI code.

Also you noticed that it works inside a domain, but not between domains. This mean the most likely culprit is a return false in some canAttack function and this return is based on a domain check, most likely if (unitA->getDomain() != unitB->getDomain()) {return false;}.

That is how I would look for this issue. Alternatively use the debugger and walk through the AI unit decision code to see where it suddenly returns from the attack code, but that requires a bit more skill than the average modder has.
 
Well, for starters it is working water->land attacks and vice versa :
Spoiler :

so it have to be in the AI.

I looked for several hours yesterday, but couldnt find anything, which is why I posted here ;)

Only thing I could come up with was this one in anyAttack:
if (!atPlot(pLoopPlot) && ((bFollow) ? canMoveInto(pLoopPlot, true) : (generatePath(pLoopPlot, 0, true, &iPathTurns) && (iPathTurns <= iRange))))
Which if I understand it correctly it wants, before an anyAttack, to make sure it ca actually directAttack the plot by walking onto it. However I tried to add a "or it can rangestrike" to the later part so it would become true, but that did nothing.

Im uploading the sourcecode for VIP if anyone wants to take a swing at this. It should work with the VIP 1.0 beta
 
Trying to analyze the problem, finding that if Rangestrikers cannot "path" a way to a tile with enemy on it, it wont attack, but if it can it will, even though the unit attacked is not "pathable", so it have to be the above problem somehow.
F.ex. I put a Missile Cruiser (MC) in the lake and an enemy AI MC in the sea. It wouldnt rangestrike it.
I then put another MC of mine, but this time in the ocean, and voila, the enemy MC rangestriked, BUT not the one in the ocean. Instead it decided to take on the one in the lake... Next turn it went head2head with the one in the ocean.
Spoiler :


Also land-rangestriking AI units wouldnt rangestrike when there was water between them, but when I WB a landbridge it started to rangestrike.
Spoiler :



So I guess its a matter of being able to path a line to the target, and if that is overridden (if unit is rangestrike) then it will work as intended. (Im really looking forward to (1) Put a full broadside into a city and (2) Being able to protect a city's seaside with the cannons on the wall. (and ofcourse, having the AI to do the same) ;)
 
[...]Only thing I could come up with was this one in anyAttack:
if (!atPlot(pLoopPlot) && ((bFollow) ? canMoveInto(pLoopPlot, true) : (generatePath(pLoopPlot, 0, true, &iPathTurns) && (iPathTurns <= iRange))))
That happens only after the call to AI_rangeAttack as far as I can tell; shouldn't be a problem.
Trying to analyze the problem, finding that if Rangestrikers cannot "path" a way to a tile with enemy on it, it wont attack, but if it can it will, even though the unit attacked is not "pathable", so it have to be the above problem somehow.
It could be CvPlayerAI::AI_getPlotDanger. That function only counts units on the same landmass/ ocean and checks CvPlot::canMoveOrAttackInto as well. Some calls to AI_anyAttack are conditional on CvPlayerAI::AI_getPlotDanger, namely those in AI_attackMove, AI_reserveMove and AI_counterMove. You could try copying the call to AI_rangeAttack into those functions, right before the "if (bDanger)" checks. I've done this in the attached file (the lines marked with "nodanger"), but haven't tested it. A long shot :)lol:), but something to try if you're out of ideas.
 

Attachments

  • CvUnitAI.zip
    58.6 KB · Views: 158
generatePath() calls the function of the same name in selectionGroup, which in turn calls gDLL->getFAStarIFace()->GeneratePath(). This one relies on pathValid() from CvGameCoreUtils.cpp. This one reveals that it will not consider plots the selection group can't move on. It assumes units to walk up to the enemy and then attack, hence the ignore water feature.

I think it can be done with something like this:
Code:
if (!atPlot(pLoopPlot) && ((bFollow) ? canMoveInto(pLoopPlot, true) : (generatePath(pLoopPlot, 0, true, &iPathTurns) && (iPathTurns <= iRange)) [B]|| (can RangeAttack(pLoopPlot))[/B] ))
You then write bool CvUnit::isWithinRangeAttack(CvPlot*) to return true if the plot is within the distance allowed by range attack.

One thing I wonder about is that this line is in CvUnitAI::AI_cityAttack(). Don't you try to attack outside cities?
 
I had thought right away that there could be a problem with using AI_getEnemyPlotStrength for selecting the target plot because that function checks for same-area and isValidDomainForAction. However, on closer examination, it seemed like it should work. I've run it in the debugger now, and saw plotStrength always return 0. After changing the call to AI_getEnemyPlotStrength(pLoopPlotplot(), ...) I've managed to get an AI Rocket Artillery to attack my Missile Cruiser. I think the bDanger thing is needed in addition.

I'm not sure if this solves all the problems, so it may still be wiser to go to the root of the issue as Nightinggale suggests.
 
I had thought right away that there could be a problem with using AI_getEnemyPlotStrength for selecting the target plot because that function checks for same-area and isValidDomainForAction. However, on closer examination, it seemed like it should work. I've run it in the debugger now, and saw plotStrength always return 0. After changing the call to AI_getEnemyPlotStrength(pLoopPlotplot(), ...) I've managed to get an AI Rocket Artillery to attack my Missile Cruiser. I think the bDanger thing is needed in addition.

I'm not sure if this solves all the problems, so it may still be wiser to go to the root of the issue as Nightinggale suggests.

What the .... I was about to write that it wouldnt matter on this, because it was something I replaced with a CheckOdds, and I was sure it was just a "dead" weight. I even tried to add 1000 to the value and nothing, but....
suddenly EVERYTHING! worked. Land over sea to other land, ship to land, land to ship. every.god.damn.attack worked!

Thank you!!! :goodjob:
I will try to isolate it to a default gamecoredll as it is really (imho) a lot better than anything else "on the market" ;)

on the next turn I checked for land to sea, which also works flawlessly
Spoiler :


edit : lol, I didnt ADDED 1000, I MULTIPLIED 1000 which, wait let me get my calculator.... is.... 0 :blush:
edit :now the last thing is to put in a check if ship2ship combat odds are above a certain level so it should go in directcombat, but I think I can manage that. Again, thank you very much. This have been a dream since I saw the suicide sieges and ships that couldnt do anything but bombard cities more than 10 years ago. This is a great day :D
 
After changing the call to AI_getEnemyPlotStrength(pLoopPlotplot(), ...)
I would be scared that a change like that affects melee units and that they no longer have to be able to walk to the enemy. It's not enough to fix the new problem. You also have to prevent the already working code from breaking ;)

The more I think about this range thing, the more I like it. Please mark all the changes in the code as I would like to inspect all of it later and perhaps copy it.

There is one thing the AI should learn when using range units and that is strategic unit placement. For instance
Code:
oo  oooA
CoX Booo
 o  o    
 oBoo
C is the city it wants to defend. A is the source of enemy and o is land. X is the strategic spot for a range unit, which the AI should locate and B is the places it can attack without any risk of counterattack (there might be more Bs, but for the sake of the argument). I think I will take a look at the AI code once everything is done to see if advanced setups like this will be possible to teach it. Currently I think the AI will place the unit inside the city, which wouldn't be that great in this case.

Also the AI should figure out moving the unit C to X is a good idea if it plans on attacking B. No need to try to find a melee path to do so. Just a path to a plot within attack range.
 
1) I would be scared that a change like that affects melee units and that they no longer have to be able to walk to the enemy. It's not enough to fix the new problem. You also have to prevent the already working code from breaking ;)
It only checks in the end of CvUnitAI::AI_rangeAttack where it was already checked if unit has rangestrike capabilities. Since I wanted sieges to be rangestrike only when used by AI, I dont think it will be a problem. With that said, I do have a different movementsystem in my mod, so if a artillery was walking with tanks, it would still be able to shoot and move.
The more I think about this range thing, the more I like it. Please mark all the changes in the code as I would like to inspect all of it later and perhaps copy it.
Isolating and commenting Vincentz Rangestrike as we speak. Its only 3 or 4 files with few changes iirc. I like it simple which is why I used the already ingame Rangestrike :)

There is one thing the AI should learn when using range units and that is strategic unit placement. For instance
Code:
oo  oooA
CoX Booo
 o  o    
 oBoo
C is the city it wants to defend. A is the source of enemy and o is land. X is the strategic spot for a range unit, which the AI should locate and B is the places it can attack without any risk of counterattack (there might be more Bs, but for the sake of the argument). I think I will take a look at the AI code once everything is done to see if advanced setups like this will be possible to teach it. Currently I think the AI will place the unit inside the city, which wouldn't be that great in this case.

Also the AI should figure out moving the unit C to X is a good idea if it plans on attacking B. No need to try to find a melee path to do so. Just a path to a plot within attack range.

Yeah, im not expecting it to be great, but if it continued to do with sieges as it does now, it will still be an improvement to vanilla suicide sieges ;) I think giving it different AIUnit roles in XML can somewhat direct it in the right direction.
Will experiment further as I now have it working for AI, lets see if AI is "working it" ;)
 
Well, here is a Vincentz Rangestrike1.0

Range 1: Cannon and Destroyer
Range 2: Artillery and Battleship
Range 3: Mobile Artillery and Missile Cruiser

All set to be able to do 100% airdamage but that can of course be changed.
Including the 3 SDK changed files commented as //Vincentz Rangestrike (though I couldnt have done it without you guys ;))

Spoiler :


edit : oh, forgot, I also set the Machinegunner to range 1. Could also be interesting for subs, helicopters, you name it :D+

Edit :
Just a little info on how to :
firstly I added a // && (getDomainType() != DOMAIN_SEA)) This can be unchecked or changed if some condition should be normal attack instead of ranged (in this case its of course if unit is oceangoing)
To make a unit rangestriked it needs 3 changes in the CIV4UnitInfos.xml.
<iAirRange> is the distance it can shoot
<iAirCombat> is the strength of the attack (I randomized it so it goes 0-100% of the amount)
<iAirCombatLimit> is the max % of damage it can do to a unit
besides that, which are tags already in the XML, there is another value in GlobalDefines to adjust the strength for all Rangestrikers: <DefineName>RANGE_COMBAT_DAMAGE</DefineName>

EDIT : in cvUnit.cpp there is a slight mistake when making the vanilla SDK files
it should be
PHP:
	if (!canMove() && getMoves() > 0)
	{
		return false;
	}
	// Vincentz Rangestrike start
		if (isCargo())
	{
		return false;
	}
	// Vincentz Rangestrike end

	return true;
}

not gonna upload the right one right now, as I have sick wife and baby needs some loving ;)
 
I'm interested! :)

I remember from an older thread - in which you already showed your interest too ;) - that the UNITAI of siege units should then rather be UNITAI_COUNTER.

Playing with just the XML previously, I thought that it was a bit overpowered as the attacker was never damaged by the attack. Maybe the next step could be to provide some random factor about defects in the artillery or even cannons blowing in your face!

It was also mentioned in my WWI thread that ships should not be able to attack when they are in ports (cities) as they can reach there inland tiles that would otherwise not be accessible if they were staying on the coast (1 tile covering already a big territory).

Lastly, did you do a nice button for range attack? Or perhaps the bombard button should actually be the range one and there should be a new one for the bombard one.

Thanks anyway for bringing this up and sharing it! :goodjob:
 
I'm interested! :)

I remember from an older thread - in which you already showed your interest too ;) - that the UNITAI of siege units should then rather be UNITAI_COUNTER.
Without being too sure, I think with this modcomp it will rangestrike with almost any UNITAI, though I did add a lot of different UNITAIs to the sieges, hoping it will use sieges for more roles like Citydefense, Counter, Collateral etc, and therefore build more sieges that it currently does. In my mod the default UNITAI is attack. Testing atm whether it is a good AI for it.

Playing with just the XML previously, I thought that it was a bit overpowered as the attacker was never damaged by the attack. Maybe the next step could be to provide some random factor about defects in the artillery or even cannons blowing in your face!
It can be overpowered, which is why I made it Random Damage. It could be changed to Random between -50 to 100%, given that below zero would be a miss. Or use something before pushing mission like Random(1-100) < airstrength + 50, if true it hits. That way the "quality" of the sieges is in the calculation aswell.
My initial thought was to add "wear and tear/limited ammo" to the sieges, so for every shot it would loose something like 5% of health. shouldnt be difficult to mod in.
Another idea I had was to make damage nonlinear, so the less health enemy unit have, the less damage it does, sort of "bowling effect" where when you have 10 pins its easy to hit with the ball, but when only 1 left its more difficult. Though it would prolly be the opposite with ships as it is a single entity, and should therefore be easier to hit when damaged.

It was also mentioned in my WWI thread that ships should not be able to attack when they are in ports (cities) as they can reach there inland tiles that would otherwise not be accessible if they were staying on the coast (1 tile covering already a big territory).
Yup, should be easy to make a iscity check as well. Though tbh i dont think I will, as the AI likes to put ships in cities, and it would handicap it.

Lastly, did you do a nice button for range attack? Or perhaps the bombard button should actually be the range one and there should be a new one for the bombard one.
in one of the screenies above there is a target with a finger pointing on the center. I recall I made another one which actually showed a ranged hit and another with a white figure carrying a target on its back. let me check

Thanks anyway for bringing this up and sharing it! :goodjob:
Im just happy to contribute (and that there are still so many active modders a decade after launch ;))

edit: couldnt find the ranged hit, but got these:
 

Attachments

  • targets.png
    targets.png
    14.4 KB · Views: 328
Top Bottom