Vassals and Attitude

Yakk

Cheftan
Joined
Mar 6, 2006
Messages
1,288
So, I was noticing something strange. . .

I was Friendly with another empire, yet they refused to make deals with me in many many areas. What was going on?

Well, I figured it out. CvTeamAI::AI_getAttitude!

See, I had defeated a number of nations. And the team attitude calculates the average attitude between the two teams -- including any vassals!
Code:
if (eTeamLoop == eTeam || GET_TEAM(eTeamLoop).isVassal(eTeam) || GET_TEAM(eTeam).isVassal(eTeamLoop))

What is worse is that it doesn't average the attitude value -- it averages the attitude level.

What is worse is that this measurement is asymmetric. It iterates over each member of this team, sums their attitude with each member of the other team and any vassals of that other team, and then divides by the number of such pairs.

This quickly makes forming any non-forced diplomatic deal nearly impossible. Especially after you take out and force capitulation on any enemy of an empire.

Similarly, forming a colony makes forming an alliance ridiculously hard: I believe the new colony starts out at neutral with every other empire. And, because the UI only gives player-to-player attitude, it is really confusing when a friendly empire refuses a deal because "we don't like you enough": when it turns out the problem that they don't like the 5 city rump-colony you built centuries ago.

Thoughts on how to fix this issue:
1> Average the attitude Value of each participant.
2> Weigh the totals by Population of each player.
3> Count vassals as significantly less important than members of the team.

...

Another approach might be to try to give a newly formed colony the same base diplomatic status that you have -- then let it diverge from there. This prevents some of the "my ally made a colony, who decided I am it's worst enemy out of sheer randomness", or "I made a deal with a newly formed colony of a friend, which turned out to be (out of nowhere) the worst enemy of another friend".

(File in question: CvTeamAI.cpp, around line 4082).
 
Hmmm ... those are interesting questions. The mechanisms seem sensible for teammates, ie treating a merger of equals equally ... but it is certainly a bit ridiculous to have vassals weigh so strongly on a third-party civs opinion of the master.

Averaging the attitude value and then finding the attitude also makes sense.
 
Here is a first pass attempt (has been compiled):
Code:
AttitudeTypes CvTeamAI::AI_getAttitude(TeamTypes eTeam, bool bForced) const
{
	FAssertMsg(eTeam != getID(), "shouldn't call this function on ourselves");

	int TotalAttitudeValue = 0;
	int TotalWeight = 0;

	// Calculate the average attitude value of each player player pair
	// Where players are team members or vassals
	// These averages are weighted by the populations of the various players
	// And divided by 2 for "other" vassals, and 4 for "this team" vassals
	for (PlayerTypes eTeamPlayer = (PlayerTypes)0; eTeamPlayer < MAX_PLAYERS; eTeamPlayer = (PlayerTypes)(eTeamPlayer+1))
	{
		// At this point, the "team player" is only a potential team player of *this
		int relationship_removed = 0;
		CvPlayerAI& TeamPlayer = GET_PLAYER(eTeamPlayer);
		if (!TeamPlayer.isAlive()) {
			continue;
		}
		bool TeamPlayerOnTeam = TeamPlayer.getTeam() == getID();
		CvTeam& TeamPlayerTeam = GET_TEAM(TeamPlayer.getTeam());

		TeamTypes eTeamPlayerTeam = TeamPlayer.getTeam();

		bool TeamPlayerIsVassalRelationship = this->isVassal(eTeamPlayerTeam) || TeamPlayerTeam.isVassal(getID());

		if (!TeamPlayerOnTeam && !TeamPlayerIsVassalRelationship) {
			continue;
		}
		// At this point we have convfirmed that the eTeamPlayer is actually on the *this team
		// (or in a vassal relationship)
		int TeamPlayerPopulation = TeamPlayer.getTotalPopulation();

		for (PlayerTypes eOtherPlayer = (PlayerTypes)0; eOtherPlayer < MAX_PLAYERS; eOtherPlayer = (PlayerTypes)(eOtherPlayer+1))
		{
			CvPlayerAI& OtherPlayer = GET_PLAYER(eOtherPlayer);
			if (!OtherPlayer.isAlive()) {
				continue;
			}
			TeamTypes eOtherPlayerTeam = OtherPlayer.getTeam();
			CvTeam& OtherPlayerTeam = GET_TEAM(eOtherPlayerTeam);
			bool OtherPlayerRightTeam = eOtherPlayerTeam == eTeam;
			bool OtherPlayerVassalRelationshipOtherTeam = GET_TEAM(eTeam).isVassal(eOtherPlayerTeam) || OtherPlayerTeam.isVassal(eTeam);
			if (!OtherPlayerRightTeam && !OtherPlayerVassalRelationshipOtherTeam) {
				continue;
			}
			// At this point, we have confirmed that the eOtherPlayer is on the eTeam "target" attitude we are
			// working on.  So now we need to do a weighted sum of the attitude pair...

			// 1/4 weight for your vassals, 1/2 weight for their vassals
			int relationship_removed_factor = 0 + (TeamPlayerIsVassalRelationship?2:0) + (OtherPlayerVassalRelationshipOtherTeam?1:0);
			int OtherPlayerPopulation = OtherPlayer.getTotalPopulation();

			int ThisPairAttitudeWeight = (TeamPlayerPopulation * OtherPlayerPopulation) >> relationship_removed_factor;
			int ThisPairAttitudeValue = TeamPlayer.AI_getAttitudeVal( eOtherPlayer, bForced );

			TotalAttitudeValue += ThisPairAttitudeValue*ThisPairAttitudeWeight;
			TotalWeight += ThisPairAttitudeWeight;
		}
	}

	if (TotalWeight > 0) {
		int AverageAttitudeValue = (TotalAttitudeValue+TotalWeight/2) / TotalWeight;
		AttitudeTypes AverageAttitude = CvPlayerAI::AI_getAttitudeTypeForVal( AverageAttitudeValue );
		return AverageAttitude;
	}

	return ATTITUDE_CAUTIOUS;
}

For each (x,y) in (Players, Players) such that (x is in *this team, or in a vassal relationship with *this team, and y is in eTeam or in vassal relationship with eTeam):

Figure out the average (x to y) attitude value. Weight each contribution by the respective populations of x and y. Furthermore, divide the weight by 4 if it is a "this side" vassal, and 2 if it is an "other side" vassal.

Then, map this average attitude value to an attitude level, and return it.

...

Tested. The effect is not overwealming -- but a huge empire with a small defeated vassal is no longer "persona non grata". In particular, having lots of "above friendly" points helps make up for vassals who are gits. There is still a hit from having vassals.

I'm next thinking of the problem of creating colonies. Really, a fresh colony of Empire X should have the relations of Empire X, roughly (maybe with each category averaged with 0?) A newly created British colony shouldn't start out with the attitude of neutral with the enemies and allies of the English, should it?
 
Cool, I'll try out your method and see how it goes.

I agree on colonies, averaging the different effects with 0 to produce a middle ground seems a sensible approach ... they'll start with some Annoyed and Pleased relations based on their parent, but no Furious or Friendly. Overtime these will change as they get themselves settled, but as a start it makes much more sense than an artificially blank slate.
 
Things to propogate:
1> AI_getAtPeaceCounter
2> Create new colony with the same civics and religion as master had.
3> Maybe move some AI_getBonusTradeCounter to the colony? Or clone it?
(Aside: the AI never seems to use, at first glance, AI_getBonusTradeCounter to offer "good" deals to the person who gave things to them... Maybe we should add this. Wait, nevermind: AI_changePeacetimeGrantValue is the counter for "granted" trades. What is AI_getBonusTradeCounter -- oh, trading bonuses!)
4> Grant some basic treaties (such as open borders) that line up with master.
5> Set AI_getOpenBordersCounter to be the same as master (maybe /2).
6> Copy AI_getShareWarCounter (maybe /2).
AI_getHasMetCounter? (team)
Copy some of AI_getMemoryAttitude?(s)

...

Looking at the "trade value of diplomacy", it works out to be:
Total of things given + Total "surplus" trade from that target (cannot be negative), divided by [(# of turns with contact + 1)*5].

Hmm. Granting "grant" points could be exploitable (by a smart AI, that is).

The trick is to move over stuff that makes sense, so that if you are friends with the master, you'll likely be somewhat friendly with the new colony. Just creating an "extra" style category isn't the right thing, as a new colony could then amass the same modifiers, and "double-dip" as it where.
 
In a related subject: surrender code.

Code:
		int iPersonalityModifier = 0;
		int iMembers = 0;
		for (int iI = 0; iI < MAX_PLAYERS; iI++)
		{
			if (GET_PLAYER((PlayerTypes)iI).isAlive())
			{
				if (GET_PLAYER((PlayerTypes)iI).getTeam() == getID())
				{
					iPersonalityModifier += GC.getLeaderHeadInfo(GET_PLAYER((PlayerTypes)iI).getPersonalityType()).getVassalPowerModifier();
					++iMembers;
				}
			}
		}

		int iTotalPower = GC.getGameINLINE().countTotalCivPower();
		int iAveragePower = iTotalPower / std::max(1, GC.getGameINLINE().countCivTeamsAlive());
		int iMasterPower = GET_TEAM(eTeam).getPower(false);
		int iVassalPower = (getPower(true) * (iPowerMultiplier + iPersonalityModifier / std::max(1, iMembers))) / 100;

		if (isAtWar(eTeam))
		{
			int iTheirSuccess = std::max(10, GET_TEAM(eTeam).AI_getWarSuccess(getID()));
			int iOurSuccess = std::max(10, AI_getWarSuccess(eTeam));
			int iOthersBestSuccess = 0;
			for (int iTeam = 0; iTeam < MAX_CIV_TEAMS; ++iTeam)
			{
				if (iTeam != eTeam && iTeam != getID())
				{
					CvTeam& kLoopTeam = GET_TEAM((TeamTypes)iTeam);

					if (kLoopTeam.isAlive() && kLoopTeam.isAtWar(getID()))
					{
						int iSuccess = kLoopTeam.AI_getWarSuccess(getID());
						if (iSuccess > iOthersBestSuccess)
						{
							iOthersBestSuccess = iSuccess;
						}
					}
				}
			}

			// Discourage capitulation to a team that has not done the most damage
			if (iTheirSuccess < iOthersBestSuccess)
			{
				iOurSuccess += iOthersBestSuccess - iTheirSuccess;
			}

			iMasterPower = (2 * iMasterPower * iTheirSuccess) / (iTheirSuccess + iOurSuccess);

			if (AI_getWorstEnemy() == eTeam)
			{
				iMasterPower *= 3;
				iMasterPower /= 4;
			}
		}
From CvTeamAI.cpp, CvTeamAI::AI_surrenderTrade, starting around line 1749.

iMasterPower and iVassalPower (by that they mean "potential vassal") is later used to determine if the Vassal has "low enough" power to surrender.

The most obvious problem above is the "iOthersBestSuccess" -- note that iOurSuccess and iTheirSuccess are capped at 10, but iOthersBestSuccess is not.

So a war in which the master gets 500 victories, the defender gets 10, and a 3rd party gets 20 -- is considered a war in which the master is not the most successful... Huh?

So the most obvious thing is to have each side capped at the same value. But that just spreads that cap of 10 successes:

And that cap of 10 successes seems to be quite low. In any reasonably long war, you'd expect that the WarSuccess count would hit a rather high number.

Not sure how to fix that, just ran into it while code splunking.
 
Back
Top Bottom