Add 'is allied' check to Merchant AI

Yeah, sure, I probably just glanced over it, thinking it was the initial section (from the first drop of code I put in). It's what happens when you mess around in programmer's notepad without reading it fully. >.<

No worries. It seems to be working fairly well. Thanks again for your assistance - I'll check back in if anything comes up.
 
I've ran a few games with these tweaks and the AI is doing a very good job overall. I do, however, have one question regarding the most recent change. In some situations (typically late-game) an AI might build a diplomatic unit yet lack a valid target due to the 'hashighally=true' rule you added for the AI. When this happens, the AI diplomatic units simply idle about. In this case, is it possible to add a 'if all city-states are hashighally=true, send to the nearest city-state the player is not already allied with' (or even just the nearest city-state, for simplicity's sake)?
Thanks again for your help.
Gazebo
 
This code won't run the check once 75% of the game has been completed (i.e. 375 turns on Standard).

Code:
// Putmalk: Don't consider targets that have an ally with another major civ that our influence bonus wouldn't be enough to make some impact
PlayerTypes eMajor;
bool bHasHighAlly = false;
for(int iI = 0; iI < MAX_MAJOR_CIVS; iI++)
{
	eMajor = (PlayerTypes) iI;
	// don't care about us or teammates
	if(GET_PLAYER(eMajor).getTeam() != getTeam())
	{
		// Later in the game lots of civs may build influence and we may end up idling about.
		if(GC.getGame().getGameTurn() < (GC.getGame().getEstimateEndTurn() * 75 / 100 ))
		{
			// Only care if they are allies
			if(kPlayer.GetMinorCivAI()->IsAllies(eMajor))
			{
				// If their friendship after a trade mission is greater than the influence we would get from a gold gift of 250, then don't care
				// i.e. they have 120 influence, we have 40, and a 250 gold gift gives 15
				// 120 > (30 + 40 + 15)
				if(kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(eMajor) >		// Rival influence
					(pGreatMerchant->getTradeInfluence(kPlayer.getCapitalCity()->plot()) +	// 30 influence, Venice: 60 influence
					kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(GetID()) +		// Our current influence
					kPlayer.GetMinorCivAI()->GetFriendshipFromGoldGift(GetID(), GC.getMINOR_GOLD_GIFT_SMALL()))) // What we would get from a 250 gold gift to this player
				{
					// If we got this far no need to evaluate this minor civ anymore
					bHasHighAlly = true;
					break;
				}
			}
		}
	}
}

if(bHasHighAlly)
	continue;

Optional hack to ensure they will always pick a CS, run before the return statement.

Code:
// Putmalk: a bit of a hack here, in the best case scenario we don't want this block of code to run
	// This is essentially repeat code, we really don't want this to run
	// This will run if the game isn't 75% done and there are no valid city-states...
	if(pBestTargetPlot == NULL && (GC.getGame().getGameTurn() < (GC.getGame().getEstimateEndTurn() * 75 / 100 )))
	{
		PlayerTypes eMinor;
		// Loop through all majors
		for(int iI = 0; iI < MAX_PLAYERS; iI++)
		{
			if(!GET_PLAYER((PlayerTypes)iI).isMinorCiv())
			{
				continue;
			}

			eMinor = (PlayerTypes) iI;
			CvPlayerAI& kPlayer = GET_PLAYER(eMinor);

			CvPlot* pCSPlot = kPlayer.getStartingPlot();
			if (!pCSPlot)
			{
				continue;
			}

			if (!pCSPlot->isRevealed(getTeam()))
			{
				continue;
			}

			// Is this a minor we are friendly with?
			bool bMinorCivApproachIsCorrect = (GetDiplomacyAI()->GetMinorCivApproach(kPlayer.GetID()) != MINOR_CIV_APPROACH_CONQUEST);
			bool bNotAtWar = !kTeam.isAtWar(kPlayer.getTeam());
			bool bNotPlanningAWar = GetDiplomacyAI()->GetWarGoal(kPlayer.GetID()) == NO_WAR_GOAL_TYPE;

			if(bMinorCivApproachIsCorrect && bNotAtWar && bNotPlanningAWar)
			{
				// Search all the plots adjacent to this city (since can't enter the minor city plot itself)
				for(int jJ = 0; jJ < NUM_DIRECTION_TYPES; jJ++)
				{
					CvPlot* pAdjacentPlot = plotDirection(pCSPlot->getX(), pCSPlot->getY(), ((DirectionTypes)jJ));
					if(pAdjacentPlot != NULL)
					{
						// Make sure this is still owned by the city state and is revealed to us and isn't a water tile
						//if(pAdjacentPlot->getOwner() == (PlayerTypes)iI && pAdjacentPlot->isRevealed(getTeam()) && !pAdjacentPlot->isWater())
						bool bRightOwner = (pAdjacentPlot->getOwner() == (PlayerTypes)iI);
						bool bIsRevealed = pAdjacentPlot->isRevealed(getTeam());
						if(bRightOwner && bIsRevealed)
						{
							iPathTurns = TurnsToReachTarget(pMerchant, pAdjacentPlot, true /*bReusePaths*/, !bOnlySafePaths/*bIgnoreUnits*/);
							if(iPathTurns < iBestTurnsToReach)
							{
								iBestTurnsToReach = iPathTurns;
								pBestTargetPlot = pAdjacentPlot;
							}
						}
					}
				}
			}
		}
	}

I hope this exercise in teaching the AI how to do things has been fun. It certainly is for me, and soon I'll be teaching them how to fight with some custom mechanics. :P
 
This code won't run the check once 75% of the game has been completed (i.e. 375 turns on Standard).

Code:
// Putmalk: Don't consider targets that have an ally with another major civ that our influence bonus wouldn't be enough to make some impact
PlayerTypes eMajor;
bool bHasHighAlly = false;
for(int iI = 0; iI < MAX_MAJOR_CIVS; iI++)
{
	eMajor = (PlayerTypes) iI;
	// don't care about us or teammates
	if(GET_PLAYER(eMajor).getTeam() != getTeam())
	{
		// Later in the game lots of civs may build influence and we may end up idling about.
		if(GC.getGame().getGameTurn() < (GC.getGame().getEstimateEndTurn() * 75 / 100 ))
		{
			// Only care if they are allies
			if(kPlayer.GetMinorCivAI()->IsAllies(eMajor))
			{
				// If their friendship after a trade mission is greater than the influence we would get from a gold gift of 250, then don't care
				// i.e. they have 120 influence, we have 40, and a 250 gold gift gives 15
				// 120 > (30 + 40 + 15)
				if(kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(eMajor) >		// Rival influence
					(pGreatMerchant->getTradeInfluence(kPlayer.getCapitalCity()->plot()) +	// 30 influence, Venice: 60 influence
					kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(GetID()) +		// Our current influence
					kPlayer.GetMinorCivAI()->GetFriendshipFromGoldGift(GetID(), GC.getMINOR_GOLD_GIFT_SMALL()))) // What we would get from a 250 gold gift to this player
				{
					// If we got this far no need to evaluate this minor civ anymore
					bHasHighAlly = true;
					break;
				}
			}
		}
	}
}

if(bHasHighAlly)
	continue;

Optional hack to ensure they will always pick a CS, run before the return statement.

Code:
// Putmalk: a bit of a hack here, in the best case scenario we don't want this block of code to run
	// This is essentially repeat code, we really don't want this to run
	// This will run if the game isn't 75% done and there are no valid city-states...
	if(pBestTargetPlot == NULL && (GC.getGame().getGameTurn() < (GC.getGame().getEstimateEndTurn() * 75 / 100 )))
	{
		PlayerTypes eMinor;
		// Loop through all majors
		for(int iI = 0; iI < MAX_PLAYERS; iI++)
		{
			if(!GET_PLAYER((PlayerTypes)iI).isMinorCiv())
			{
				continue;
			}

			eMinor = (PlayerTypes) iI;
			CvPlayerAI& kPlayer = GET_PLAYER(eMinor);

			CvPlot* pCSPlot = kPlayer.getStartingPlot();
			if (!pCSPlot)
			{
				continue;
			}

			if (!pCSPlot->isRevealed(getTeam()))
			{
				continue;
			}

			// Is this a minor we are friendly with?
			bool bMinorCivApproachIsCorrect = (GetDiplomacyAI()->GetMinorCivApproach(kPlayer.GetID()) != MINOR_CIV_APPROACH_CONQUEST);
			bool bNotAtWar = !kTeam.isAtWar(kPlayer.getTeam());
			bool bNotPlanningAWar = GetDiplomacyAI()->GetWarGoal(kPlayer.GetID()) == NO_WAR_GOAL_TYPE;

			if(bMinorCivApproachIsCorrect && bNotAtWar && bNotPlanningAWar)
			{
				// Search all the plots adjacent to this city (since can't enter the minor city plot itself)
				for(int jJ = 0; jJ < NUM_DIRECTION_TYPES; jJ++)
				{
					CvPlot* pAdjacentPlot = plotDirection(pCSPlot->getX(), pCSPlot->getY(), ((DirectionTypes)jJ));
					if(pAdjacentPlot != NULL)
					{
						// Make sure this is still owned by the city state and is revealed to us and isn't a water tile
						//if(pAdjacentPlot->getOwner() == (PlayerTypes)iI && pAdjacentPlot->isRevealed(getTeam()) && !pAdjacentPlot->isWater())
						bool bRightOwner = (pAdjacentPlot->getOwner() == (PlayerTypes)iI);
						bool bIsRevealed = pAdjacentPlot->isRevealed(getTeam());
						if(bRightOwner && bIsRevealed)
						{
							iPathTurns = TurnsToReachTarget(pMerchant, pAdjacentPlot, true /*bReusePaths*/, !bOnlySafePaths/*bIgnoreUnits*/);
							if(iPathTurns < iBestTurnsToReach)
							{
								iBestTurnsToReach = iPathTurns;
								pBestTargetPlot = pAdjacentPlot;
							}
						}
					}
				}
			}
		}
	}

I hope this exercise in teaching the AI how to do things has been fun. It certainly is for me, and soon I'll be teaching them how to fight with some custom mechanics. :P

Awesome! This may do the trick. I'll give this a go. I've enjoyed it- my skill in expression-based code is very weak, so this has been a nice primer for me (and a boost to my own desire to learn this language generally).

Plans for the combat AI, eh? Good luck!
 
The game turn bit caused a break during compiling. Should it be
Code:
if(GC.getGame().getGameTurn() < (GC.getGame().getEstimate[B]d[/B]EndTurn() * 75 / 100 ))?
I added a 'd' to estimate.
G

Absolutely not. What does the error say?
 
Also, where, specifically, should I put the 'optional hack?' Would it be possible to include the code where the AI won't send it to a city-state it is already substantially allied with? Here's the overall logic I'm trying to implement.

1.) Build diplomatic unit - great! Where should it go?
2.) Nearest city-state - okay, am I allied with that city-state?
2a.) If not allied, send unit.
2b.)If allied over x amount, send to next-nearest city-state.
2c.)If allied with other player over x amount, send to next-nearest city-state that does not violate 2b.
3.) Are all city-states are allied to other players with high influence?
3a.) If so, send to nearest city-state I am not allied with.
4.) Are all city-states allied with me?
4a.) Send unit to nearest city-state.

Also, have I mentioned how excellent your help has been thus far?
 
I've tried implementing the game-turn snippet, but to no avail. Any thoughts on that? It isn't a crucial modification, so no worries if time/energy are at a premium right now.

The logic you posted in the post above yours requires a rewrite and rethinking of this functions works, something I really don't want to do since I'm busy with other projects.

There is no reason estimatedgameturn() should not work nor do I see how it could possibly compile error, but if you post a screenshot of what the compile error looks like, I can try to debug it.

edit: that optional hack is designed to re-run what the function does but without the edits to my code, the real "non-hacking solution" is one of two things: recursively run the function with a boolean block (requires changing the function prototype, etc.) or as I said, rewriting the logic to keep track of what the closest city-state is even though it's actually not valid.
 
No worries about being busy, I've had a massive influx of actual work as of late and I've been lax in my modding duties.

The logic I mentioned is roughly translated into what has already been done here, minus the last three points, which are merely situational considerations (and rare at that). I simply wanted to outline an overall logic plan for my own thoughts. I'll tinker with the last two snippets you posted and see if I can diagnose the problem. I think, if I can get the 'optional hack' to work, it will roughly approximate points 3a, 4 and 4a in my logic plan (generally the closest city-state to a civ is contested and, if not, it will at least guarantee the use of diplo units and a stable ally for a civ in the late-game).

Thanks again for all the hard work. Good luck on your new endeavors - I look forward to the results.
 
Putmalk,
I got the edit to work, finally. I reloaded the entire DLL and started from scratch- perhaps something was edited by mistake on my end. In any case, here's the full set of changes, implemented (I hope) correctly. If you have a spare moment, take a look and see if it looks correct.

I modified your final 'optional hack' to include the 'if over x influence with a city-state, look somewhere else' logic - does it appear to be in the correct place?
Thanks,
Gazebo

Here's the code. The part I modified is in bold. the rest is yours.
Spoiler :
Code:
CvPlot* CvPlayerAI::FindBestMerchantTargetPlot(CvUnit* pGreatMerchant, bool bOnlySafePaths)
{
	CvAssertMsg(pGreatMerchant, "pGreatMerchant is null");
	if(!pGreatMerchant)
	{
		return NULL;
	}

	int iBestTurnsToReach = MAX_INT;
	CvPlot* pBestTargetPlot = NULL;
	int iPathTurns;
	UnitHandle pMerchant = UnitHandle(pGreatMerchant);
	CvTeam& kTeam = GET_TEAM(getTeam());

	//bool bIsVenice = GetPlayerTraits()->IsNoAnnexing();
	//bool bWantsCash = GreatMerchantWantsCash();

	// Loop through each city state
	for(int iI = 0; iI < MAX_PLAYERS; iI++)
	{
		CvPlayer& kPlayer = GET_PLAYER((PlayerTypes)iI);
		if (!kPlayer.isMinorCiv())
		{
			continue;
		}

		// if I'm Venice, I don't want to send a Merchant of Venice to a buy a city that I have trade routes 
		// with because it's probably more valuable as a trade partner than as an owned entity
		//if (!bWantsCash)
		//{
		//	if (bIsVenice)
		//	{
		//		if (GetTrade()->IsConnectedToPlayer(kPlayer.GetID()))
		//		{
		//			continue;
		//		}
		//	}
		//}

		CvPlot* pCSPlot = kPlayer.getStartingPlot();
		if (!pCSPlot)
		{
			continue;
		}

		if (!pCSPlot->isRevealed(getTeam()))
		{
			continue;
		}

		// Putmalk: Don't consider targets that we're friendly with an our influence is pretty high (i.e. at least 90 influence over the Ally threshold)
		if (kPlayer.GetMinorCivAI()->IsAllies(GetID()))
		{
			// If our friendship is already 90 influence or higher than the allied threshold, don't send our merchant there
			if(kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(GetID()) >= (kPlayer.GetMinorCivAI()->GetAlliesThreshold() + 90))
			{
				continue;
			}
		}

// Putmalk: Don't consider targets that have an ally with another major civ that our influence bonus wouldn't be enough to make some impact
PlayerTypes eMajor;
bool bHasHighAlly = false;
for(int iI = 0; iI < MAX_MAJOR_CIVS; iI++)
{
	eMajor = (PlayerTypes) iI;
	// don't care about us or teammates
	if(GET_PLAYER(eMajor).getTeam() != getTeam())
	{
		// Only care if they are allies
		if(kPlayer.GetMinorCivAI()->IsAllies(eMajor))
		{
			// If their friendship after a trade mission is greater than the influence we would get from a gold gift of 250, then don't care
			// i.e. they have 120 influence, we have 40, and a 250 gold gift gives 15
			// 120 > (30 + 40 + 15)
			if(kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(eMajor) >		// Rival influence
				(pGreatMerchant->getTradeInfluence(kPlayer.getCapitalCity()->plot()) +	// 30 influence, Venice: 60 influence
				kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(GetID()) +		// Our current influence
				kPlayer.GetMinorCivAI()->GetFriendshipFromGoldGift(GetID(), GC.getMINOR_GOLD_GIFT_SMALL()) + // What we would get from a 250 gold gift to this player
				100)) 
			{
				// If we got this far no need to evaluate this minor civ anymore
				bHasHighAlly = true;
				break;
			}
		}
	}
}

if(bHasHighAlly)
	continue;

		// Is this a minor we are friendly with?
		bool bMinorCivApproachIsCorrect = (GetDiplomacyAI()->GetMinorCivApproach(kPlayer.GetID()) != MINOR_CIV_APPROACH_CONQUEST);
		bool bNotAtWar = !kTeam.isAtWar(kPlayer.getTeam());
		bool bNotPlanningAWar = GetDiplomacyAI()->GetWarGoal(kPlayer.GetID()) == NO_WAR_GOAL_TYPE;

		if(bMinorCivApproachIsCorrect && bNotAtWar && bNotPlanningAWar)
		{
			// Search all the plots adjacent to this city (since can't enter the minor city plot itself)
			for(int jJ = 0; jJ < NUM_DIRECTION_TYPES; jJ++)
			{
				CvPlot* pAdjacentPlot = plotDirection(pCSPlot->getX(), pCSPlot->getY(), ((DirectionTypes)jJ));
				if(pAdjacentPlot != NULL)
				{
					// Make sure this is still owned by the city state and is revealed to us and isn't a water tile
					//if(pAdjacentPlot->getOwner() == (PlayerTypes)iI && pAdjacentPlot->isRevealed(getTeam()) && !pAdjacentPlot->isWater())
					bool bRightOwner = (pAdjacentPlot->getOwner() == (PlayerTypes)iI);
					bool bIsRevealed = pAdjacentPlot->isRevealed(getTeam());
					if(bRightOwner && bIsRevealed)
					{
						iPathTurns = TurnsToReachTarget(pMerchant, pAdjacentPlot, true /*bReusePaths*/, !bOnlySafePaths/*bIgnoreUnits*/);
						if(iPathTurns < iBestTurnsToReach)
						{
							iBestTurnsToReach = iPathTurns;
							pBestTargetPlot = pAdjacentPlot;
						}
					}
				}
			}
		}
	}
// Putmalk: a bit of a hack here, in the best case scenario we don't want this block of code to run
	// This is essentially repeat code, we really don't want this to run
	// This will run if the game isn't 85% done and there are no valid city-states...
	if(pBestTargetPlot == NULL && (GC.getGame().getGameTurn() < (GC.getGame().getEstimateEndTurn() * 85 / 100 )))
	{
		PlayerTypes eMinor;
		// Loop through all majors
		for(int iI = 0; iI < MAX_PLAYERS; iI++)
		{
			if(!GET_PLAYER((PlayerTypes)iI).isMinorCiv())
			{
				continue;
			}

			eMinor = (PlayerTypes) iI;
			CvPlayerAI& kPlayer = GET_PLAYER(eMinor);

			CvPlot* pCSPlot = kPlayer.getStartingPlot();
			if (!pCSPlot)
			{
				continue;
			}

			if (!pCSPlot->isRevealed(getTeam()))
			{
				continue;
			}

			[B]// Putmalk: Don't consider targets that we're friendly with an our influence is pretty high (i.e. at least 90 influence over the Ally threshold)
		if (kPlayer.GetMinorCivAI()->IsAllies(GetID()))
		{
			// If our friendship is already 90 influence or higher than the allied threshold, don't send our merchant there
			if(kPlayer.GetMinorCivAI()->GetEffectiveFriendshipWithMajor(GetID()) >= (kPlayer.GetMinorCivAI()->GetAlliesThreshold() + 90))
			{
				continue;
			}
		}[/B]
			// Is this a minor we are friendly with?
			bool bMinorCivApproachIsCorrect = (GetDiplomacyAI()->GetMinorCivApproach(kPlayer.GetID()) != MINOR_CIV_APPROACH_CONQUEST);
			bool bNotAtWar = !kTeam.isAtWar(kPlayer.getTeam());
			bool bNotPlanningAWar = GetDiplomacyAI()->GetWarGoal(kPlayer.GetID()) == NO_WAR_GOAL_TYPE;

			if(bMinorCivApproachIsCorrect && bNotAtWar && bNotPlanningAWar)
			{
				// Search all the plots adjacent to this city (since can't enter the minor city plot itself)
				for(int jJ = 0; jJ < NUM_DIRECTION_TYPES; jJ++)
				{
					CvPlot* pAdjacentPlot = plotDirection(pCSPlot->getX(), pCSPlot->getY(), ((DirectionTypes)jJ));
					if(pAdjacentPlot != NULL)
					{
						// Make sure this is still owned by the city state and is revealed to us and isn't a water tile
						//if(pAdjacentPlot->getOwner() == (PlayerTypes)iI && pAdjacentPlot->isRevealed(getTeam()) && !pAdjacentPlot->isWater())
						bool bRightOwner = (pAdjacentPlot->getOwner() == (PlayerTypes)iI);
						bool bIsRevealed = pAdjacentPlot->isRevealed(getTeam());
						if(bRightOwner && bIsRevealed)
						{
							iPathTurns = TurnsToReachTarget(pMerchant, pAdjacentPlot, true /*bReusePaths*/, !bOnlySafePaths/*bIgnoreUnits*/);
							if(iPathTurns < iBestTurnsToReach)
							{
								iBestTurnsToReach = iPathTurns;
								pBestTargetPlot = pAdjacentPlot;
							}
						}
					}
				}
			}
		}
	}
	return pBestTargetPlot;
}
 
Yeah, that looks about right. Although I'm curious why you chose to keep the 85% game completion in the hack but not the main code. The goal of that is to prevent the AI from not picking city-states that have high influence late in the game, when it's very likely that EVERY city-state will have high influence with another major civ.
 
Yeah, that looks about right. Although I'm curious why you chose to keep the 85% game completion in the hack but not the main code. The goal of that is to prevent the AI from not picking city-states that have high influence late in the game, when it's very likely that EVERY city-state will have high influence with another major civ.

I couldn't get the bit in the main code to work – I'll try it again when I get a chance. I actually modified the last piece and flipped the '<' to make it activate after a certain point (not before, as it was doing). It seemed to make the AI more effective during the late game.
 
I've had quite a few people PM me about using your Diplomacy mod alongside CSD. Since the inclusions in your DLL modifications are much more intensive than the ones for CSD, I was wondering if you would like to add the changes we've outlined above to your DLL for that mod. That way, people can have the best of both worlds. If you have reason not to, I understand. Your mod, your call.
Thanks again for your help.
G
 
Thanks for the heads-up...but merging really isn't my thing. I maintain 2 DLLs and adding another one will just be confusing.
 
Thanks for the heads-up...but merging really isn't my thing. I maintain 2 DLLs and adding another one will just be confusing.

The changes to CSD's AI are complete. All you need is the CvPlayerAI file, which I can send to you if you'd like. It would alleviate the current message spam I'm receiving from fans of our mods. The AI changes will benefit the AI regardless of whether players use CSD or not.

Cheers,
G
 
Back
Top Bottom