[BTS] Development thread

I believe the current SVN is still missing a few xml assets, like
BBAI_Game_Options_GlobalDefines.xml
BBAI_AI_Variables_GlobalDefines.xml
TechDiffusion_GlobalDefines.xml
LeadFromBehind_GlobalDefines.xml

or at least a GlobalDefinesAlt.xml with all the new variables.

I admit, most variables are already there in some form, BBAI_BOMBARD_ATTACK_STACK_FRACTION is completely new to me though and I have no idea what to do with it.
 
"Minimum attack odds of best attacker for AI to skip bombarding a city and just attack"

there are descriptions in GlobalDefinesAlt.xml (download package).
 
Obviously you didn't read all of it. BBAI_BOMBARD_ATTACK_STACK_FRACTION
I am not talking any of the variables that were there before, I am talking about a new variable that is NOT in any package or svn, and while it is quite clear what this variable is supposed to do, I just need a default value I can start with. Because to me it is not quite clear what value would serve the AI best.

It was introduced in revision 526 and is featured in only a single line in CvPlayerAI.cpp.
 
The svn rep has been updated, the new files are now there.

The new AI setting is as follows:

Code:
	<Define>
		<!-- Approximate fraction by number of units of attack stacks which AI aims to consist of bombard units.  BBAI default: 20 -->
		<DefineName>BBAI_BOMBARD_ATTACK_STACK_FRACTION</DefineName>
		<iDefineIntVal>20</iDefineIntVal>
	</Define>

Basically, until now the AI has attempted to have a sum of 100 bombard strength from land units available to it ... this maybe works okay for the catapult era on standard sized maps, but clearly this setting needed to scale with unit potency and army size. That's where the new setting comes in. The AI will more highly value bombard units for its UNITAI_ATTACK_CITY build decisions when less than 20% of (ATTACK_CITY + ATTACK + COUNTER/2) units are siege units.

This code has not been tested ... not to discourage you from checking it out, just a heads up. If you have any ideas or comments or issues, please post.
 
The basic idea behind the 100 bombard strength seemed to be: the AI only has 1 big SoD, and that's where all the bombard units will be, and this SoD should, if possible, bomb city defences to the ground in 1 turn. 100% is max defense for cities (at least without Chitchen Itza), once the army has 100 bombard strength, no more is needed.
For one, not always will enemy cities have that much defence, before Engineering 60% is likely the maximum you encounter. And after people build castles, bombard strength of 100 is no longer enough to bomb down the whole defence in one turn, because castles not only add defence, they also reduce damage from bombardment. To reduce a city's defence to 0% in one turn, 250 bombard strength can be necessary with castle and Chitchen Itza.
So I think scaling should be done using either 100 (as before) or whatever it takes to take down the defence of the best-fortified opponent city on the map at that time + 20, whichever is higher.
All of that is assuming that the AI only targets one heavily fortified city at once though.

But I think what you wanted to address was a different issue, the one that the AI sometimes doesn't take a lot of collateral damage dealing units with it (note that bombard and collateral damage are not the same in terms of game mechanics even if all land units that have the one ability also have the other). I don't know what unit_ais the AI puts into a SoD but if it's only attack, attack city and coounter, then something needs to be done for unit_ai attack_city to make the collateral damage ability for siege units worth more, and scale with army size. Currently it's just
Code:
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getCollateralDamage()) [B]/ 400[/B]);

UNITAI_COLLATERAL looks a bit strange to me too:
Code:
	case UNITAI_COLLATERAL:
		iValue += iCombatValue;
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getCollateralDamage()) [B]/ 50[/B]);
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getMoves()) / 4);
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getWithdrawalProbability()) / 25);
		iValue [B][COLOR="Red"]-[/COLOR][/B]= ((iCombatValue * GC.getUnitInfo(eUnit).getCityAttackModifier()) / 100);
		break;
For one, the collateral unit_ai gets an 8 times higher bonus for its collateral damage alility. Also, its value gets reduced when it has cityattack: trebuchet has 100% city attack modifier and 4 strength, which means it gets -4 there. If collateral is a unit_ai that never attacks cities then ok, it shouldn't get a bonus from cityattack abilities, but a reduction, and even more so such a massive one, makes no sense. If collateral is a unitai for city attack too then that minus should be a plus, if not (and I think that's the case) then this line should be removed.

CollateralDamageLimit and CombatLimit might not be all that important, CollateralDamageMaxUnits I think should be considered though. and should be what scales to army size most (when big stacks clash, this value plays a big role, though not so much if the enemy only has very few units.)
 
bombard value scaling: assuming the AI can target multiple cities, I started to like the idea as such. However I'm not sure about the actual weighting:

With the iActualBombardFraction at 20 and iActualBombardFraction at 0, this code of yours
Code:
				if (iActualBombardFraction < iAIDesiredBombardFraction)
				{
					iBombardValue *= iAIDesiredBombardFraction + [B]20 *[/B] (iAIDesiredBombardFraction - iActualBombardFraction);
					iBombardValue /= iAIDesiredBombardFraction;
				}
				else
				{
					iBombardValue *= iAIDesiredBombardFraction;
					iBombardValue /= range(iActualBombardFraction,1,99);
				}
would give iBombardValue 21 times its normal value.
(iBombardValue *= 20 + 20 * (20 - 0); iBombardValue /= 20; )
So I think you meant
Code:
 iBombardValue *= iAIDesiredBombardFraction + [B]5 *[/B] (iAIDesiredBombardFraction - iActualBombardFraction);
 
You are certainly correct that there are two separate considerations for siege units, and we shouldn't confuse the two: bombard and collateral. The goals should be having enough bombard strength to drop defenses quickly, plus plenty of collateral damage to soften up the defenders.

Just FYI, UNTIAI_COLLATERAL is purely for anti-stack defense. They're suicide units to wear down incoming enemy stacks.


Bombard:

Life is complicated before gunpowder, as 100 bombard strength in a single stack is not enough. In a city with walls you need 200 to take defenses to 0 in a single turn before gun powder, it goes to 400 in cities with castles.

The AI can also run multiple stacks at once, although this is not common with normal settings before the industrial era.

Perhaps a good compromise would be: if the unit does not ignore city defenses (ie, not gunpodwer), aim for 200 total bombard points. In addition, have bombard value drop off somewhat slowly so that AI might build two stacks worth.

There's a bug in the BTS code where the value placed on the bombard power of a trebuchet drops from +256 to +64 when the total bombard power a player has goes from 99 to 100. That's a big part of the problem. (As a comparison, the value placed on the collateral ability of a trebuchet is always +10 ... siege units are built now mostly based on bombard value)


Collateral:

A boost to the value of collateral for ATTACK_CITY is definitely in order. This is a significant reason to build siege units for attack city stacks, and the AI valuation needs to reflect that.

----------------

I have attached a spreadsheet (both xls and open-office formats) for calculating ATTACK_CITY values for units using the BTS formula. We need a new formula which works well in all eras that boosts the production of siege units.

The spreadsheet has the Trebuchet/Maceman/Knight era to start. Global variables are at the top:

FastMoverMod: 4 or 1 depending on whether strongest unit AI can build is a multi-move unit.

BestLandCombat: Combat value of best land combat unit in the game.

Total bombard: Sum of bombard strength of all the player's land units (regardless of whether they're attack city or collateral).

The next set of variables should be obvious, they're settings for the different unit types being considered.

After that the calculations begin, they mirror the code in AI_unitValue (in the function, search for the second mention of UNITAI_ATTACK_CITY ... the first use handles whether a unit can do the ai type, the second computes values).
 

Attachments

Bombard:

Life is complicated before gunpowder, as 100 bombard strength in a single stack is not enough. In a city with walls you need 200 to take defenses to 0 in a single turn before gun powder, it goes to 400 in cities with castles.

The AI can also run multiple stacks at once, although this is not common with normal settings before the industrial era.

Perhaps a good compromise would be: if the unit does not ignore city defenses (ie, not gunpodwer), aim for 200 total bombard points. In addition, have bombard value drop off somewhat slowly so that AI might build two stacks worth.

There's a bug in the BTS code where the value placed on the bombard power of a trebuchet drops from +256 to +64 when the total bombard power a player has goes from 99 to 100. That's a big part of the problem. (As a comparison, the value placed on the collateral ability of a trebuchet is always +10 ... siege units are built now mostly based on bombard value)
If the army is big enough, your code will make the AI aim for even more, but with lower number of total units I feel the AI won't try hard enough to even reach 100 bombard points, let alone 200. Stealing parts from the old code could work.

Collateral:

A boost to the value of collateral for ATTACK_CITY is definitely in order. This is a significant reason to build siege units for attack city stacks, and the AI valuation needs to reflect that.
currently it's
((iCombatValue * GC.getUnitInfo(eUnit).getCollateralDamage()) / 50)
for collateral unit ai
and
((iCombatValue * GC.getUnitInfo(eUnit).getCollateralDamage()) / 400)
for attack_city. I assume the truth lies, as so often, somewhere in the middle.

edit3: I'm pretty happy with what I have now, should scale down quicky enough.

Spoiler :
Code:
	case UNITAI_ATTACK_CITY:
[COLOR="Green"]/************************************************************************************************/
/* BETTER_BTS_AI_MOD                      02/24/10                                jdog5000      */
/*                                                                                              */
/* War strategy AI                                                                              */
/************************************************************************************************/
		// Effect army composition to have more collateral/bombard units[/COLOR]
		iFastMoverMultiplier = AI_isDoStrategy(AI_STRATEGY_FASTMOVERS) ? 4 : 1;
		
		iTempValue = ((iCombatValue * iCombatValue) / 75) + (iCombatValue / 2);
		iValue += iTempValue;
		if (GC.getUnitInfo(eUnit).isNoDefensiveBonus())
		{
			iValue -= iTempValue / 2;
		}
		if (GC.getUnitInfo(eUnit).getDropRange() > 0)
		{
			iValue -= iTempValue / 2;
		}
		if (GC.getUnitInfo(eUnit).isFirstStrikeImmune())
		{
			iValue += (iTempValue * 8) / 100;
		}		
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getCityAttackModifier()) / 75);
[COLOR="Green"]/* Collateral Damage valuation moved to bombard part
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getCollateralDamage()) / 400);
*/[/COLOR]
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getMoves() * iFastMoverMultiplier) / 4);
		iValue += ((iCombatValue * GC.getUnitInfo(eUnit).getWithdrawalProbability()) / 100);
[COLOR="Green"]/*
		if (!AI_isDoStrategy(AI_STRATEGY_AIR_BLITZ))
		{
*/[/COLOR]
			if (GC.getUnitInfo(eUnit).getBombardRate() > 0 || GC.getUnitInfo(eUnit).getCollateralDamageMaxUnits() > 0)
			{
[COLOR="Green"]				/* original code
				int iBombardValue = GC.getUnitInfo(eUnit).getBombardRate() * 4;
				*/[/COLOR]
				int iBombardValue = GC.getUnitInfo(eUnit).getBombardRate() * (GC.getUnitInfo(eUnit).isIgnoreBuildingDefense() ? 3 : 2);
				[COLOR="Green"]// Army composition needs to scale with army size, bombard unit potency[/COLOR]


				
				[COLOR="Green"]//modified AI_calculateTotalBombard(DOMAIN_LAND) code[/COLOR]
				int iI;
				int iTotalBombard = 0;
				int iSiegeUnits = 0;
				int iSiegeImmune = 0;
				int iTotalSiegeMaxUnits = 0;
				
				for (iI = 0; iI < GC.getNumUnitClassInfos(); iI++)
				{
					UnitTypes eLoopUnit = ((UnitTypes)(GC.getCivilizationInfo(getCivilizationType()).getCivilizationUnits(iI)));
					if (eLoopUnit != NO_UNIT)
					{
						if (GC.getUnitInfo(eLoopUnit).getDomainType() == DOMAIN_LAND)
						{
							int iBombardRate = GC.getUnitInfo(eLoopUnit).getBombardRate();
							
							if (iBombardRate > 0)
							{
								iTotalBombard += (iBombardRate * getUnitClassCount((UnitClassTypes)iI) * (GC.getUnitInfo(eUnit).isIgnoreBuildingDefense() ? 3 : 2)) / 2;
							}
							
							int iBombRate = GC.getUnitInfo(eLoopUnit).getBombRate();
							if (iBombRate > 0)
							{
								iTotalBombard += iBombRate * getUnitClassCount((UnitClassTypes)iI);
							}
							
							
							if (GC.getUnitInfo(eLoopUnit).getCollateralDamageMaxUnits() != 0 && GC.getUnitInfo(eLoopUnit).getCollateralDamage() != 0)
							{
								iTotalSiegeMaxUnits += GC.getUnitInfo(eLoopUnit).getCollateralDamageMaxUnits() * getUnitClassCount((UnitClassTypes)iI);
								iSiegeUnits += getUnitClassCount((UnitClassTypes)iI);
							}
							else if (GC.getUnitInfo(eLoopUnit).getUnitCombatCollateralImmune(GC.getUnitInfo(eUnit).getUnitCombatType()))
							{
								iSiegeImmune+= getUnitClassCount((UnitClassTypes)iI);
							}
						}
					}
				}

				int iNumOffensiveUnits = AI_totalUnitAIs(UNITAI_ATTACK_CITY) + AI_totalUnitAIs(UNITAI_ATTACK) + AI_totalUnitAIs(UNITAI_COUNTER)/2;
				int iNumDefensiveUnits = AI_totalUnitAIs(UNITAI_CITY_DEFENSE) + AI_totalUnitAIs(UNITAI_RESERVE) + AI_totalUnitAIs(UNITAI_CITY_COUNTER)/2 + AI_totalUnitAIs(UNITAI_COLLATERAL)/2;
				iSiegeUnits += (iSiegeImmune*iNumOffensiveUnits)/(iNumOffensiveUnits+iNumDefensiveUnits);

				int iMAX_HIT_POINTS = GC.getDefineINT("MAX_HIT_POINTS");

				int iCollateralDamageMaxUnitsWeight = (100 * (iNumOffensiveUnits - iSiegeUnits)) / std::max(1,iTotalSiegeMaxUnits);
				iCollateralDamageMaxUnitsWeight = std::min(100, iCollateralDamageMaxUnitsWeight);
				[COLOR="Green"]//to decrease value further for units with low damage limits:[/COLOR]
				int iCollateralDamageLimitWeight = 100*iMAX_HIT_POINTS - std::max(0, ((iMAX_HIT_POINTS - GC.getUnitInfo(eUnit).getCollateralDamageLimit()) * (100 -  iCollateralDamageMaxUnitsWeight)));
				iCollateralDamageLimitWeight /= iMAX_HIT_POINTS;
				int iCollateralValue = iCombatValue * GC.getUnitInfo(eUnit).getCollateralDamage() * GC.getDefineINT("COLLATERAL_COMBAT_DAMAGE");
				iCollateralValue /= 100;
				iCollateralValue *= std::max(100, (GC.getUnitInfo(eUnit).getCollateralDamageMaxUnits() * iCollateralDamageMaxUnitsWeight * iCollateralDamageMaxUnitsWeight) / 100);
				iCollateralValue /= 100;
				iCollateralValue *= iCollateralDamageLimitWeight;
				iCollateralValue /= 100;
				iCollateralValue /= iMAX_HIT_POINTS;
				iValue += iCollateralValue;
				
				[COLOR="Green"]//int iTotalBombardValue = 4 * iTotalBombard;[/COLOR]
				int iNumBombardUnits = 2 * iTotalBombard / iBombardValue;
				int iAIDesiredBombardFraction = std::max( 5, /*default: 10*/ GC.getDefineINT("BBAI_BOMBARD_ATTACK_STACK_FRACTION"));
				int iActualBombardFraction = (100*iNumBombardUnits)/std::max(1, iNumOffensiveUnits);

				int iTempBombardValue = 0;
				if (iTotalBombard < 200) [COLOR="Green"]//still less than 200 bombard points[/COLOR]
				{
					iTempBombardValue = iBombardValue * (500 - 2*iTotalBombard);
					iTempBombardValue /= 100;
					[COLOR="Green"]//iTempBombardValue is at most (5 * iBombardValue)[/COLOR]
				}
				if (iActualBombardFraction < iAIDesiredBombardFraction)
				{
					iBombardValue *= iAIDesiredBombardFraction + 3 * (iAIDesiredBombardFraction - iActualBombardFraction);
					iBombardValue /= iAIDesiredBombardFraction;
					[COLOR="Green"]//new iBombardValue is at most (4 * old iBombardValue)[/COLOR]
				}
				else
				{
					iBombardValue *= iAIDesiredBombardFraction;
					iBombardValue /= range(iActualBombardFraction,1,99);
				}

				if (iTempBombardValue > iBombardValue)
				{
					iBombardValue = iTempBombardValue;
				}
				
				if (!AI_isDoStrategy(AI_STRATEGY_AIR_BLITZ))
				{
					iValue += iBombardValue;
				}
			}
[COLOR="Green"]/*
		}
*/
/************************************************************************************************/
/* BETTER_BTS_AI_MOD                       END                                                  */
/************************************************************************************************/[/COLOR]
		break;
 
I was under the impression that while ignorebuildingdefence helps you when actually attacking, it does nothing for you when bombarding, you still need 400 bombard points to take a city with castle down to 0%. I also can't understand your new bombard valuation, the old one looked good enough to me for large armies even if I would set bombard fration to 10 and special case smaller armies to make the AI aim for 200 bombard even if the bombard fraction is higher than 10%.

Comparing 3 cases:
1.) iTotalBombardRate = 0
2.) iTotalBombardRate = (iGoalTotalBombardRate - 1) and
3.) iTotalBombardRate = (iGoalTotalBombardRate) ..
- I can only imagine your "iBombardValue *= (iGoalTotalBombardRate - iTotalBombardRate);" was actually meant to be "iBombardValue *= (2*iGoalTotalBombardRate - iTotalBombardRate);", but still as soon as the else part is hit, value is suddenly 1/4 of what it was with 1 less iTotalBombardRate. Wasn't that what you wanted to fix?

Health/Happy building stuff looks ok.

r531: Floodplains are no longer removed, code is back to original bts?

r532: Ships moving diagonally (but only if there is no other route available), was that really a bug? I am not so sure this is a good idea, and it looks like a huge gameplay change. If a coastal city is surrounded by land except for one diagonal water tile, will that city build ships that can't leave? nvm, you are checking if both from and to is water. Still, I thought that was standard behaviour .. or did you introduce that?

Looking forward to fast units that all stick with the main attack stack :)
 
So ships moving diagonally through land was introduced in r483, and is in fact a bug. And since I only started playing Civ4 recently and mostly with BBAI, I never realized that this behaviour was unintented. >_<
 
Thanks for spotting the missing 2, nice to have multiple eyes looking at the code!

I think the transition right at iGoalTotalBombardRate is working properly, in the else clause there's a std::min so that it starts at 1/1 and the worst it can get to is 1/4.

I'll check out your version of the code later, the bombard/collateral together sounds like a good idea.

Floodplains are back to being removed, my new plan is to replace floodplains when a city is destroyed instead of leaving them under the city (which changed how much food the city got in its center tile).

The ships moving diagonally is now back to its BTS behavior ... I had moved that block down a while ago because the Firaxis comment made it sound like it wasn't important, but turns out it was. In the current BBAI release you can move a ship across the isthmus between two diagonally connected land tiles, which shouldn't be the case of course.
 
yeah, min it is, not max. I confuse those a lot (when reading at least, not so much when coding myself I hope).

Still not sure if the collateral atm doesn't give a little bit too much value. Scaling down should work as I expect, it might still be starting too high though. The first catapults get a (*6/500) instead of the (/400) bts value, which is more than twice your (/200). Maybe a "iValue += (iCollateralValue*2)/3;" is needed to tone it down.

How can you replace floodplains on city racing, by simply checking if the tile is riverside dessert or do you plan do add that former floodplains info to the savegame?
edit2: that looks more elegant than I thought, using appearanceprob 10k. I'd say this should go for all features though, not just for riverside.

- ok, you must be right about the buildingdefence.

edit: I still have a problem with that differentiation though. Imagine this situation: 152 total bombard fom cats and trebs; evalution for a new trebuchet: 16 bombard points, and it gets a bonus because it doesn't ignore buildingdefense and total is still < 200. Next turn steel, can build cannons: only 12 bombard points, and that value is getting reduced even further because this unit ignores buildingdefense and we have more than 100 totalbombard. This just doesn't look right to me.
My solution: always aim for 200 points, and simply count bombard from units that ignore buildingdefense as 1.5 times of the actual bombard rate. So, cannons get treated as if they had 18 bombard even though they only have 12. and they still get the bonus because we are below 200 total. After 3 cannons however we are above 200 (152+ 3*(12*1.5)), so no more bonus from there.
updated my code from 5 posts above, halfing base value from bombard and giving cannons+ that 50% bonus.
 
Nevermind my collateral thing for now, looks like it's crashing all the time., and I don't know why. edit: fixed - no clue how though

I believe the only question left is: why is the workboat no military unit anymore, doesn't that just make it the cheapest (no military upkeep) sentry unit on the ocean after astronomy?
 
I believe the only question left is: why is the workboat no military unit anymore, doesn't that just make it the cheapest (no military upkeep) sentry unit on the ocean after astronomy?

It's an interesting question.

You can probably also use them like bait for AI ships to attack, bringing them in range of your fighters or off the coast or something else like that.

I'd agree they should still require the same upkeep and support costs as military units, to avoid potential exploits.
 
You can use workers as bait as well. My understanding was to make them follow the same rules as workers, for things like whether heroic epic helps in building them and such. Is that not what this does?
 
Yes and no, it's 2 changes after all. Setting military production to 0 makes workboats get no bonus from heroic epic.. and since it doesn't even get the bonus that would make sense, the one from drydock, removing heroic epic bonus is justified.
However, removing military upkeep, combined with how incredibly cheap the unit is, will lead to workboat sentry chains on oceans I fear, at least when humans play on higher difficulty levels, so removing the upkeep is not the best idea. imnsho.
 
(...) it doesn't even get the bonus that would make sense, the one from drydock
I said that, but I am pretty sure I was wrong. Military production bonus from drydock (kBuilding.getDomainProductionModifier()) goes to everything DOMAIN_SEA, not limited by unitcombattype as I assumed earlier.
And since getting bonus from drydock makes sense, I'll be leaving in the military production too, not just the upkeep. Getting bonus from Heric Epic might not make as much sense but that's negligible enough for me.
 
Back
Top Bottom