Version 0.83 discussion thread

The BBAI 0.82 thread should probably stay dead, but here is a question about the stock BBAI 0.82. I can see in the code that AI spies in an enemy city are likely to cause revolt if there is a strong stack nearby. That is great. I have some players in Dune Wars who are really looking forward to seeing that happen. (In the Dune novel, one of the most important scenes is where a spy does exactly this.)

What we have observed is that AI spies in enemy cities tend to squander their EP on low value missions like steal treasury and they are never around when it would be useful to cause revolt.

I cannot quite locate the code which runs the low value missions in enemy cities. Is there some provision to skip low value missions and wait? Or does the code greedily take the first mission for which there are enough EP? If the spies could be made a little more patient, I think the DW players would be rewarded.

The AI doesn't save up for bigger spy missions ... spy use was a very random process, though it's gotten a lot better in the last couple versions. The choice of which mission to run is weighted fairly intelligently, the problem is when to decide whether to run a mission at all ... if a spy decides to run a mission and only low value missions are available, it'll do it currently.

It'd be easy to add a threshold which could cause the AI to skip low value missions in certain circumstances, but what are the right circumstances? That's not so clear.

Version 0.83.
Press "End Turn" and the game will crash.

I'll check it out, thanks for the post.

Is there any way to get the AI to assign more value to there UU, most of the time the UU are a fair bit better than the standard unit, Cho's are an example of this as the 2 FS + collateral make them very effective compared to a standard Xbow, it would be nice to see the AI place more value on there UU, with perhaps a bonus added to the AI choice logic, as i feel that many a time the AI does not factor in the advantages many UU offer and were UU do not replace core units they see less use than they should.

Definitely easy to do, though we might need to add a small cache list to store unique units ... right now the AI doesn't know which of its units are unique, and unit value calculations can run many many times per turn.

In principle though a unit isn't better because it's unique. It's better because of the boosted properties it gets. Many of those boosts translate into better ratings for certain UNITAIs, so the AI would build them with higher probability ... but the AI doesn't change the basic ratios of UNITAI types it builds, so the differences may not be very noticeable. The different UNITAI type valuations also take different subsets of unit properties into account, so not all property boosts translate into the AI valuations.
 
if a spy decides to run a mission and only low value missions are available, it'll do it currently ... It'd be easy to add a threshold which could cause the AI to skip low value missions in certain circumstances, but what are the right circumstances? That's not so clear.

I'd like to try something, but I need a little guidance. Which function chooses the mission? How can this function tell, whether the spy's owner is at war with the city's owner? Is there an existing function to tell, whether there is an attack stack en route to the city? Then I would assign a 100% chance to wait for the stack if there is one en route, and 90% chance to wait if at war but no stack en route.
 
It'd be easy to add a threshold which could cause the AI to skip low value missions in certain circumstances, but what are the right circumstances? That's not so clear.

I'd suggest that unless the AI is at war with the civ, they should save up espionage points to steal techs from them if there are any available.
 
Jdog, I just found another weak spot in AI building valuation. Apparently, the AI doesn't consider the Great Wall to be more useful if Raging Barbs are on, or less useful if No Barbs are on. Here's the original code and my tweaks (Note: I included an option for Barbarian world too, uncomment that section for RevDCM.).

Original Code:

Code:
if (kBuilding.isAreaBorderObstacle())
				{
					iValue += (iNumCitiesInArea);
				}

New Better AI code:

Code:
if (kBuilding.isAreaBorderObstacle())
				{/************************************************************************************************/
					/* Afforess	                  Start		 01/05/10                                               */
					/*                                                                                              */
					/*                                                                                              */
					/************************************************************************************************/
					//The great wall is much more valuable with more barbarian activity.
					iTempValue = 0;
					iTempValue += (iNumCitiesInArea);
					if (GC.getGameINLINE().isOption(GAMEOPTION_RAGING_BARBARIANS))
						iTempValue *=2;
					/*if (GC.getGameINLINE().isOption(GAMEOPTION_BARBARIAN_WORLD))
						iTempValue *=2;*/
					if (GC.getGameINLINE().isOption(GAMEOPTION_NO_BARBARIANS))
						iTempValue = 0;
					iValue += iTempValue;
					/************************************************************************************************/
					/* Afforess	                     END                                                            */
					/************************************************************************************************/
				}
 
A basic setup for AI espionage mission strategy (which might not fit nicely in the already present code).

First a basic valuation is created for each of the missions dependent on the usefulness of the mission. Then this value is normalised by dividing the value of the mission by the cost of the mission. It's logical that stealing biology is more valuable than destroying a tile improvement but it's also a lot more costly. Creating the basic valuation is not easy, especially since the basic valuations have to create comparable values after normalisation. It's comparable to creating valuations for the civics, that's also hard. The basic valuation formula should have a large multiplier dependent on war. Pillaging resources, stealing military technologies, inciting a revolt in a threatened city are all missions which can be very valuable in war times. Likewise, sabotaging a world wonder construction can be very valuable when the AI is also building this world wonder and won't win the race without sabotaging. Finally the normalised value is multiplied by a random number between 1 and 2 to make the AI unpredictable, a necessity for a competitive AI.

The AI then performs the missions with the highest normalised value until it runs out of espionage points. If the AI doesn't have enough espionage points to perform the most valuable mission, then it won't perform any mission. The AI may perform the second most valuable mission before the most valuable mission if after performing this mission, it still has enough points to perform the most valuable mission. Similar logic applies to the third, fourth, etc. most valuable mission.

Maybe missions with a higher cost than 20 times the AI espionage point generation per turn should be discarded to avoid a situation where the AI is saving for a goal that it can't reach in a reasonable amount of time. The value of 20 should be modified by game speed.
 
I'd suggest that unless the AI is at war with the civ, they should save up espionage points to steal techs from them if there are any available.

If the AI is not at war, stealing techs or whatever seems fine. If the AI is at war, I agree they should "save up". I think that translates to, "Each turn, a fortified spy should have a large percent chance of doing nothing". Assuming the espionage mission weights are set correctly, this will ensure that when an action does come, a lot of points are available and a painful action is chosen. Today points are wasted on less painful actions rather than saving up.
 
I'd suggest that unless the AI is at war with the civ, they should save up espionage points to steal techs from them if there are any available.
Well, there is a high number of situations where this is a bad idea ( preventing another civ of linking a resource, culture wins stopper or , more important , tech theft ). But if you don't have any idea of making the AI to do centralized high level calcs on how, where and when to use the spies ( that is, the situation we still have ), yes, probably it is a good idea to hold the spies at bay besides war times, even if to avoid diplo demerits by spies caught.
 
I'd like to try something, but I need a little guidance. Which function chooses the mission? How can this function tell, whether the spy's owner is at war with the city's owner? Is there an existing function to tell, whether there is an attack stack en route to the city? Then I would assign a 100% chance to wait for the stack if there is one en route, and 90% chance to wait if at war but no stack en route.

CvPlayerAI::AI_bestPlotEspionage picks the mission. The function CvUnitAI::AI_espionageSpy is what gets the process started, it's called in CvUnitAI::AI_spyMove.

There's some code in CvUnitAI::AI_spyMove to determine if there's an attack stack in position (AI_getOurPlotStrength) and also to check if one is on the way (AI_plotTargetMissionAIs).

Points are not wasted if a stack is on the way, but only the city where the spy is waiting is checked. Spies pick cities where stacks are going and likely target cities if no stack is ready, but if the stack starts after the spy is in position they may go to a different city.
 
There was also a bug with the determination of the value of stealing treasury, which I just fixed. This should help. The AI was programmed to only steal treasury if it would get more in gold than the points cost of the mission, but how much it thought it was getting was considerably more than what it would actually get.

The amount of gold the mission yields depends on the pop of the city / total pop of the civ, that scaling was missing.
 
Version 0.83.
Press "End Turn" and the game will crash.

I have found and fixed the problem, you will be able to continue your game when the final version for 0.83 is posted.

Just FYI, the crash happens because the game no longer has a tech to suggest for you to research. The logging system I added tries to look up the name of NO_TECH at index -1 in an array to log it, but of course index -1 is invalid.
 
So when having only 1 tech to choose from the game crashes because of BBAI logging? Good to know, I once researched everything around Scientific Method until I had to no other option and the game would always crash the turn I finished the last tech before SM, right after the message about finishing the tech. Reloading last turn and switching to SM before the other tech finishes, to avoid getting only a single choice, would fix that.


[CvPlayer.cpp] I'm guessing
5987: if (!bValid && pPlot->isWater())
5988: {
is the better version?
BULL has
if (pPlot->isWater())
{
bValid = false;

instead. Which will lead to the same return value eventually, though if bValid is already true, the python callback is skipped in your version (or so I guess, I'm not going to pretend I understand this)
Both parts claim to be from the Unofficial Patch

Spoiler :
/************************************************************************************************/
/* UNOFFICIAL_PATCH 10/04/09 EmperorFool & jdog5000 */
/* */
/* Bugfix */
/************************************************************************************************/
// EF: canFoundCitiesOnWater callback handling was incorrect and ignored isWater() if it returned true
if (!bValid && pPlot->isWater())
{
if(GC.getUSE_CAN_FOUND_CITIES_ON_WATER_CALLBACK())
{
CyArgsList argsList2;
argsList2.add(iX);
argsList2.add(iY);
lResult=0;
gDLL->getPythonIFace()->callFunction(PYGameModule, "canFoundCitiesOnWater", argsList2.makeFunctionArgs(), &lResult);

if (lResult == 1)
{
bValid = true;
}
}
}
/************************************************************************************************/
/* UNOFFICIAL_PATCH END */
/************************************************************************************************/



[CvUnit.cpp]One more issue: A single-line unofficial patch claims to make collateral damage work again for units with barrage promotions but without base collateral abilities.
EmperorFool seems to have added an additional statement into that function, and I'm not sure if that is needed or what it does exactly.
Spoiler :
void CvUnit::collateralCombat(const CvPlot* pPlot, CvUnit* pSkipUnit)
{
CLLNode<IDInfo>* pUnitNode;
CvUnit* pLoopUnit;
CvUnit* pBestUnit;
CvWString szBuffer;
int iTheirStrength;
int iStrengthFactor;
int iCollateralDamage;
int iUnitDamage;
int iDamageCount;
int iPossibleTargets;
int iCount;
int iValue;
int iBestValue;
std::map<CvUnit*, int> mapUnitDamage;
std::map<CvUnit*, int>::iterator it;

int iCollateralStrength = (getDomainType() == DOMAIN_AIR ? airBaseCombatStr() : baseCombatStr()) * collateralDamage() / 100;
// BUG - Unofficial Patch - start
// Barrage promotions made working again on Tanks and other units with no base collateral ability
if (iCollateralStrength == 0 && getExtraCollateralDamage() == 0)
// BUG - Unofficial Patch - end
{
return;
}

iPossibleTargets = std::min((pPlot->getNumVisibleEnemyDefenders(this) - 1), collateralDamageMaxUnits());

pUnitNode = pPlot->headUnitNode();

while (pUnitNode != NULL)
{
pLoopUnit = ::getUnit(pUnitNode->m_data);
pUnitNode = pPlot->nextUnitNode(pUnitNode);

if (pLoopUnit != pSkipUnit)
{
if (isEnemy(pLoopUnit->getTeam(), pPlot))
{
if (!(pLoopUnit->isInvisible(getTeam(), false)))
{
if (pLoopUnit->canDefend())
{
iValue = (1 + GC.getGameINLINE().getSorenRandNum(10000, "Collateral Damage"));

iValue *= pLoopUnit->currHitPoints();

mapUnitDamage[pLoopUnit] = iValue;
}
}
}
}
}

CvCity* pCity = NULL;
if (getDomainType() == DOMAIN_AIR)
{
pCity = pPlot->getPlotCity();
}

iDamageCount = 0;
iCount = 0;

while (iCount < iPossibleTargets)
{
iBestValue = 0;
pBestUnit = NULL;

for (it = mapUnitDamage.begin(); it != mapUnitDamage.end(); it++)
{
if (it->second > iBestValue)
{
iBestValue = it->second;
pBestUnit = it->first;
}
}

if (pBestUnit != NULL)
{
mapUnitDamage.erase(pBestUnit);

if (NO_UNITCOMBAT == getUnitCombatType() || !pBestUnit->getUnitInfo().getUnitCombatCollateralImmune(getUnitCombatType()))
{
iTheirStrength = pBestUnit->baseCombatStr();

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

iStrengthFactor = ((iCollateralStrength + iTheirStrength + 1) / 2);

iCollateralDamage = (GC.getDefineINT("COLLATERAL_COMBAT_DAMAGE") * (iCollateralStrength + iStrengthFactor)) / (iTheirStrength + iStrengthFactor);

iCollateralDamage *= 100 + getExtraCollateralDamage();

iCollateralDamage *= std::max(0, 100 - pBestUnit->getCollateralDamageProtection());
iCollateralDamage /= 100;

if (pCity != NULL)
{
iCollateralDamage *= 100 + pCity->getAirModifier();
iCollateralDamage /= 100;
}

iCollateralDamage /= 100;

iCollateralDamage = std::max(0, iCollateralDamage);

int iMaxDamage = std::min(collateralDamageLimit(), (collateralDamageLimit() * (iCollateralStrength + iStrengthFactor)) / (iTheirStrength + iStrengthFactor));
iUnitDamage = std::max(pBestUnit->getDamage(), std::min(pBestUnit->getDamage() + iCollateralDamage, iMaxDamage));

if (pBestUnit->getDamage() != iUnitDamage)
{
// BUG - Combat Events - start
int iDamageDone = iUnitDamage - pBestUnit->getDamage();
pBestUnit->setDamage(iUnitDamage, getOwnerINLINE());
CvEventReporter::getInstance().combatLogCollateral(this, pBestUnit, iDamageDone);
// BUG - Combat Events - end
iDamageCount++;
}
}

iCount++;
}
else
{
break;
}
}

if (iDamageCount > 0)
{
szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_SUFFER_COL_DMG", iDamageCount);
gDLL->getInterfaceIFace()->addMessage(pSkipUnit->getOwnerINLINE(), (pSkipUnit->getDomainType() != DOMAIN_AIR), GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COLLATERAL", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pSkipUnit->getX_INLINE(), pSkipUnit->getY_INLINE(), true, true);

szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_INFLICT_COL_DMG", getNameKey(), iDamageCount);
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COLLATERAL", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pSkipUnit->getX_INLINE(), pSkipUnit->getY_INLINE());
}
}
 
For the CvPlayer function, I would would go with my code :p I think mine saves a few calls to Python. These differences occur when either someone reports something and EF and I solve it differently, or when EF solves something first and then I decide to do it a little differently.

For the CvUnit change, looks like a reasonable idea but we should probably check with EF.
 
I took my time to understand that part: CvUnit::collateralCombat() with the Unofficial Patch simply makes non-siege units with barrage promotion do about half the damage a siege unit would do, EF's additional code just removes that penalty completely, making barrage tanks extremely powerful. But even with collateral damage halved, barrage1 would be a very powerful promotion for tanks, I think it's a good thing it's disabled.

And I just looked at the other code, isFreshWater() and isCoastalLand() always return false for water, and even EF said if isFound() returns true the callback can be savely skipped.

So I'm using your code everywhere now, except for latitude/longitude.
 
I have a question:

why remove UNITAI_CITY_DEFENSE from Praetorians? Their 8 of strenght is better than 7,5 of Axemen.

I suppose that a praetorian in a city can defend well against a axeman or against a swordman.

PS: Thanks for BBAI. Now I play Civ only with BBAI activated.
 
I believe it was removed from Swordsmen, because they're a poor choice for city defenders, and so it was inherited by the swordsman UU. I suspect that on a per-hammer basis they're still an inferior defender to more traditional options, and the AI shouldn't be building them for a purely defensive role.
 
If you assume that value = strength ^ 1.5, then praetorians have a defensive value of 0.5 per hammer, whereas archers in cities have a defensive value of 0.14 per hammer. Archers are arguably better defensive value against axes, but praetorians are better against everything else.

As I understand things, this means that Praetorians should have UNITAI_CITY_DEFENSE. Also, Roman Archers should have UNITAI_CITY_COUNTER as well as UNITAI_CITY_DEFENSE. Additionally, I notice that Jaguars still have UNITAI_CITY_DEFENSE, which they should probably lose, as they are no better at defending cities than regular swords.
 
There is no "inheritance" here, it was removed, and I agree that this is wrong. Preatorians are superior units, they are good no matter what you do with them.

I believe Jaguars still have it because you don't need copper or iron for them.
And what was city_counter doing again?
 
I sometimes get crashes to desktop when I press the next turn button. Turn number appears to be random.
I've attached a save file to reproduce the error.
I use the official 0.83 release (not the beta) and the crash does _not_ happen if I use 0.82 with the same game file.
(I merged the mod with the original installation btw)
Anyone know how to fix this ?

Thx in advance,
techno
 

Attachments

  • Willem van Oranje AD-1265.zip
    811.2 KB · Views: 117
Top Bottom