Vassals

Firstly, awesome work on this. Looks very exciting.

Can I suggest a subsystem in which the vassal receives a bonus to military production based on culture output? The theory is that the more culturally power a vassal is, the more difficult it should be to be for their overlord to control them. Culture could kind of act as patriotic resistance. Also provides a nice bonus for vassals to help become independent through military. Alternatively perhaps if they do revolt, their culture score might modify the number of "free" units that pop up for the vassal?

Unsure if you've coveres this already but if the vassal revolts, it should automatically sign peace treaties in any other wars they may be in (usually due to master being at war). Would also be good to prevent other civs declaring war on them which they may do if the vassal is losing/lost military units.

Why should a vassal revolting cause other civs to make peace? Seems rather arbitrary in my opinion. The option to make peace should exist, however, because they are now an independent state.

In regards to suggestions for treatment modifiers:

- Providing them with science/techs/research points. Perhaps tax rate can include research points?
- Subsidies for Military upkeep (master to pay a % of military upkeep? Although tax rate might a better way of handling this)
- Master going to war should count somewhat like a demand and hence incur a penalty
- Some Policies or ideolgies may modify treatment
- Trade routes from Master (perhaps count the sum of incoming gold to target vassal)

I would think that if I add any more "tax" rates (for example, a science meter) I would keep them all distinct. Otherwise it's super confusing, and less controllable, for a human player to understand the mechanic (which is really the goal of this update). And a trade route to a vassal makes them like it a lot more (as currently implemented in the dev build). :) Really trying to avoid tying policies, techs, resolutions or other things to the vassal system or diplomacy in general, as it doesn't seem like good behavior.

Some suggestions for overall formulation of revolt chance modifiers:

- Difference in military power (unless this is already be formulated in protection score)
- Culture delta or ratio between master and vassal (if vassal has strong culture this should increase their restive state)
- Happiness of the master (lower happiness should embolden the vassals yearn for independence as unhappiness weakens the master through usual penalties)
- Master being at war with powerful civs. Although this could modify chance of revolt organically with the loss of military power during the war (military power ratio of vassal vs master)

I like all of these ideas, except the master being at war (unless they're losing).

Some Thoughts!!


Maybye you can have something like pledge to protect for your Vassal!
so you get positive points from Vassal or Negative from Civs that hate your Vassal Civ!
other Civs should know that u protect your Vassal!

Get positive if u Gifts units like to City states?

If you have Troops in your Vassal Territory you get positive points,
like please remove your troops from our borders but the opposite for Vassal!
instead !Thank u for protecting us!

When u Demand something from your vassal he should give u that!!
maybye not a City but if u want 5 Iron he should give it!
(but that get negative relationship)

Vassals are not City States. :) The whole point of a vassal agreement is the agreement to protect them rather than annihilate them. Although I kinda like the troops in vassal territory causing a small diplomatic boost. :) Also, as already implemented, demanding from vassals is easier, but not guaranteed.
 
Some Thoughts!!

If you have Troops in your Vassal Territory you get positive points,
like please remove your troops from our borders but the opposite for Vassal!
instead !Thank u for protecting us!

When u Demand something from your vassal he should give u that!!
maybye not a City but if u want 5 Iron he should give it!
(but that get negative relationship)

I like that idea - but I'd make it more like an oppression thing and more like a last resort if the relationship is getting bad and the overlord needs to contain them from revolting. Perhaps if you put military units within 1 tile of their cities it reduces their chance to revolt but lowers relationship and/or increases their military unit production or something like that.
 
I had an idea - if you have multiple vassals, and one successfully fulfills the requirements to request independence, it would be neat if your remaining vassals were to get a negative diplo boost with you, because they've become enticed with the idea of their own independence, and begin a more aggressive push to be able to declare their own.

Or, pairing with G's idea, maybe when one vassal becomes ready to ask for their independence, if you decline (and declare war on them as a result), one or more of your other vassals who are unsatisfied with you may team up with them, even though they've only completed 75% or so of their own requirements needed for independence. Just spit-balling ideas :lol:

Thoughts on this Putmalk? After more thought, the second idea may be a bit much, and the first idea would sort of take up that role in a more sane way.
 
Why should a vassal revolting cause other civs to make peace? Seems rather arbitrary in my opinion. The option to make peace should exist, however, because they are now an independent state.

I mean that the Vassal may already be at war with another civ at the time of revolt due to being forced to parttake in overlords wars (am I mistaken in assuming that the vassal always follows the master in to wars?)

If the vassal revolts against the overlord while it is involved in another war, it will have a very hard time surviving fighting a war on two fronts. If the vassal breaks away from the overlord, I would imagine it would make sense to no longer be involved in the previous wars of the overlord with other civs.
 
Thoughts on this Putmalk? After more thought, the second idea may be a bit much, and the first idea would sort of take up that role in a more sane way.

Like the idea of vassals getting more enticed with independence the more other civs successfully declare independence.

No to breaking off vassalage without explicitly meeting the requirements. Otherwise the mechanic means nothing. :)

If the vassal revolts against the overlord while it is involved in another war, it will have a very hard time surviving fighting a war on two fronts. If the vassal breaks away from the overlord, I would imagine it would make sense to no longer be involved in the previous wars of the overlord with other civs.

This is an AI issue, not a game mechanic one. Forcing peace is not correct behavior, having the AI want to end an existing war is correct behavior - assuming the other party agrees. :mischief:
 
This is an AI issue, not a game mechanic one. Forcing peace is not correct behavior, having the AI want to end an existing war is correct behavior - assuming the other party agrees. :mischief:


Fair enough ;)

What do you think about the idea of the overlord stationing units near vassal cities to reduce chance of revolt? Formula could be total military strength within 1 or 2 tiles of vassal cities to offset chance of revolt. It could be another more direct way for overlords to maintain grip on vassals, albeit wasteful. It should have negative consequences though - either with the vassal itself or with other civs (ie oppressive behaviour).

Might be hard to code the AI overlords to do this to their vassals...
 
Putmalk, re diplo code, I've added in the loop you need, and I did the first two of your static integers to give you an idea of what I mean:


Spoiler :
Code:
// Determine how much we are going to tax this player, if we can
void CvDiplomacyAI::DoDetermineTaxRateForVassalOnePlayer(PlayerTypes ePlayer)
{

	// Must be able to set taxes for player
	if(!GET_TEAM(GetPlayer()->getTeam()).CanSetVassalTax(ePlayer))
		return;

	TeamTypes eMyTeam = GetPlayer()->getTeam();
	CvTeam& kMyTeam = GET_TEAM(eMyTeam);

	// Do not allow an AI teammate to do this for a human
	if(!GetPlayer()->isHuman() && kMyTeam.isHuman())
		return;
		
	[B]FStaticVector< int, 128, true, c_eCiv5GameplayDLL > viApproachWeightsPersonality;
	for(iApproachLoop = 0; iApproachLoop < NUM_MAJOR_CIV_APPROACHES; iApproachLoop++)
	{
		viApproachWeightsPersonality.push_back(GetPersonalityMajorCivApproachBias((MajorCivApproachTypes) iApproachLoop));
	}[/B]

	// Current tax rate
	int iTaxRate = GET_TEAM(GetPlayer()->getTeam()).GetVassalTax(ePlayer);

	// Make sure we can actually do that...
	bool bWantToLower = iTaxRate > GC.getVASSALAGE_VASSAL_TAX_PERCENT_MINIMUM();
	bool bWantToRaise = iTaxRate < GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM();

	// Because this function will involve lots of iteration over team members, let's store all alive team members in temporary vectors to improve the speed of this function
	std::vector<CvPlayerAI*> m_MasterTeam;
	std::vector<CvPlayerAI*> m_VassalTeam;

	for(int iI=0; iI < MAX_MAJOR_CIVS; iI++)
	{
		PlayerTypes eLoopPlayer = (PlayerTypes) iI;
		if(GET_PLAYER(eLoopPlayer).isAlive())
		{
			// Master team
			if(GET_PLAYER(eLoopPlayer).getTeam() == GetPlayer()->getTeam())
			{
				m_MasterTeam.push_back(&GET_PLAYER(eLoopPlayer));
			}
			// Vassal team
			else if(GET_PLAYER(eLoopPlayer).getTeam() == GET_PLAYER(ePlayer).getTeam())
			{
				m_VassalTeam.push_back(&GET_PLAYER(eLoopPlayer));
			}
		}
	}
	
	MajorCivOpinionTypes eTeamOpinion = MAJOR_CIV_OPINION_NEUTRAL;
	int iMyCurrentGPT = 0, iMyCurrentGross = 0, iAverageMeanness = 0, iAverageLoyalty = 0, iAverageOpinionScore = 0;
	for(std::vector<CvPlayerAI*>::iterator it = m_MasterTeam.begin(); it != m_MasterTeam.end(); it++)
	{
		iMyCurrentGPT += (*it)->GetTreasury()->CalculateBaseNetGoldTimes100();
		iMyCurrentGross += (*it)->GetTreasury()->CalculateBaseNetGoldTimes100();

		iAverageMeanness += (*it)->GetDiplomacyAI()->GetMeanness();
		iAverageLoyalty += (*it)->GetDiplomacyAI()->GetLoyalty();
		iAverageOpinionScore += (int) (*it)->GetDiplomacyAI()->GetMajorCivOpinion(ePlayer);
	}
	iAverageMeanness /= m_MasterTeam.size();
	iAverageLoyalty /= m_MasterTeam.size();
	iAverageOpinionScore /= m_MasterTeam.size();

	eTeamOpinion = (MajorCivOpinionTypes) iAverageOpinionScore;

	int iVassalCurrentGPT = 0, iVassalCurrentGross = 0;
	for(std::vector<CvPlayerAI*>::iterator it = m_VassalTeam.begin(); it != m_VassalTeam.end(); it++)
	{
		iVassalCurrentGPT += (*it)->GetTreasury()->CalculateBaseNetGoldTimes100();
		iVassalCurrentGross += (*it)->GetTreasury()->CalculateGrossGoldTimes100();
	}

	// Hate him? Don't consider lowering!
	if(eTeamOpinion == MAJOR_CIV_OPINION_UNFORGIVABLE)
		bWantToLower = false;

	
	// Like him? Don't consider raising!
	if(eTeamOpinion == MAJOR_CIV_OPINION_ALLY)
		bWantToRaise = false;

	// We have some choice in the direction taxes can go - pick a direction so we can start deciding
	if(bWantToLower && bWantToRaise)
	{
		// We're in dire straights
		if(iMyCurrentGPT <= 0)
		{
			bWantToLower = false;	// don't even consider lowering
			
			// Check to see if taxing the vassal the maximum would get us out of dire straights
			if(bWantToRaise)
			{
				// Wouldn't help us out at all
				if((iVassalCurrentGross * GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM() / 100 < 0))
				{
					bWantToRaise = false;
				}
				// Tax vassal the maximum to get us out of trouble - his feelings be damned
				else
				{
					GET_TEAM(GetPlayer()->getTeam()).DoApplyVassalTax(ePlayer, GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM());
				}
			}
		}
		// Doing fine - have some choice
		else
		{	

			int iScoreForLower = 0;
			int iScoreForRaise = 0;

			// Starting values based on opinion
			switch(eTeamOpinion)
			{
			case MAJOR_CIV_OPINION_ENEMY:
				iScoreForLower -= [B]viApproachWeightsPersonality[MAJOR_CIV_APPROACH_HOSTILE];
				iScoreForRaise += viApproachWeightsPersonality[MAJOR_CIV_APPROACH_HOSTILE];
			case MAJOR_CIV_OPINION_COMPETITOR:
				iScoreForLower -= viApproachWeightsPersonality[MAJOR_CIV_APPROACH_DECEPTIVE];
				iScoreForRaise += viApproachWeightsPersonality[MAJOR_CIV_APPROACH_DECEPTIVE];[/B]
				break;
			case MAJOR_CIV_OPINION_NEUTRAL:
				iScoreForLower = 0;
				iScoreForRaise = 0;
				break;
			case MAJOR_CIV_OPINION_FAVORABLE:
				iScoreForLower = 10;
				iScoreForRaise = -10;
				break;
			case MAJOR_CIV_OPINION_FRIEND:
				iScoreForLower = 25;
				iScoreForRaise = -25;
				break;
			default:
				CvAssertMsg(false, "Should not have gotten here.");
			}

			// Still deciding what to be done?
			if(bWantToLower && bWantToRaise)
			{
				// Is our vassal doing better than us monetarily?
				if(iVassalCurrentGPT >= iMyCurrentGPT)
				{
					iScoreForLower *= 75;
					iScoreForLower /= 100;

					iScoreForRaise *= 125;
					iScoreForRaise /= 100;
				}
				// He is doing worse than 85% of our GPT
				else if(iVassalCurrentGPT * 85 <= iMyCurrentGPT * 100)
				{
					// Have to like the vassal
					if(eTeamOpinion > MAJOR_CIV_OPINION_NEUTRAL)
					{
						// Determine a percentage to lower
						int iThreshold = 33;

						// He is doing REALLY bad
						if(iVassalCurrentGPT * 150 < iMyCurrentGPT * 100)
							iThreshold = 75;

						int iRand = GC.getGame().getJonRandNum(100, "CvDiplomacyAI: Do we want to give this vassal a nice boost in GPT cus his GPT is less and we like him?");
						if(iRand < iThreshold)
						{
							iScoreForLower *= 150;
							iScoreForLower /= 100;
						}
					}
				}

				// Raise score for lowering based on loyalty
				iScoreForLower *= 100 + (iAverageLoyalty - 5) * 10;
				iScoreForLower /= 100;

				// Raise score for lowering based on meanness
				iScoreForRaise *= 100 + (iAverageMeanness - 5) * 10;
				iScoreForRaise /= 100;

				bWantToLower = (iScoreForLower > iScoreForRaise);
				bWantToRaise = (iScoreForLower < iScoreForRaise);
			}
		}
	}

	CvWeightedVector<int, 20, true> aPossibleValues;	// in case changed, 100 / 5  is a safe bet for number of possible elements
	
	// New tax value defaults to current tax rate
	int iNewTaxValue = iTaxRate;

	// Decided we're going to lower - figure out by how much
	if(bWantToLower)
	{
		int iCurrentIndex = 0;

		// Possible values are determined by increments of 5 starting from below the current tax line
		for(int i = (iTaxRate - 5); i >= GC.getVASSALAGE_VASSAL_TAX_PERCENT_MINIMUM(); i -= 5)
		{
			int iValue = i;
			int iWeight = (iAverageOpinionScore - 3) * iCurrentIndex + 100;
			
			// Determine if we will make at least one GPT profit off of this value, if not, then decentivize
			if(iVassalCurrentGross * iValue < 10000)
				iWeight /= 4;

			aPossibleValues.push_back(iValue, iWeight);
			iCurrentIndex++;
		}

		RandomNumberDelegate fcn;
		fcn = MakeDelegate(&GC.getGame(), &CvGame::getJonRandNum);
		iNewTaxValue = aPossibleValues.ChooseByWeight(&fcn, "Choose the tax value to assign");
	}
	// Decided we're going to raise - figure out by how much
	else
	{
		int iCurrentIndex = 0;
		
		// Possible values are determined by increments of 5 starting from above the current tax line
		for(int i = (iTaxRate + 5); i <= GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM(); i += 5)
		{
			int iValue = i;
			int iWeight = (3 - iAverageOpinionScore) * iCurrentIndex + 100;
			
			// Determine if we will make at least one GPT profit off of this value, if not, then decentivize
			if(iVassalCurrentGross * iValue < 10000)
				iWeight /= 4;
			
			aPossibleValues.push_back(iValue, iWeight);
			iCurrentIndex++;
		}

		RandomNumberDelegate fcn;
		fcn = MakeDelegate(&GC.getGame(), &CvGame::getJonRandNum);
		iNewTaxValue = aPossibleValues.ChooseByWeight(&fcn, "Choose the tax value to assign");
	}

	// Set the tax.
	kMyTeam.DoApplyVassalTax(ePlayer, iNewTaxValue);
}

The idea here is that a civ's personality-based integer (somewhere between 1 and 10, but sometimes higher than that) is used to 'define' their behavior towards a vassal based on opinion. Should make them a little less predictable, but in an easily-controlled way.

G
 
Fair enough ;)

What do you think about the idea of the overlord stationing units near vassal cities to reduce chance of revolt? Formula could be total military strength within 1 or 2 tiles of vassal cities to offset chance of revolt. It could be another more direct way for overlords to maintain grip on vassals, albeit wasteful. It should have negative consequences though - either with the vassal itself or with other civs (ie oppressive behaviour).

Might be hard to code the AI overlords to do this to their vassals...

I think too complicated. :)
 
Putmalk, re diplo code, I've added in the loop you need, and I did the first two of your static integers to give you an idea of what I mean:


Spoiler :
Code:
// Determine how much we are going to tax this player, if we can
void CvDiplomacyAI::DoDetermineTaxRateForVassalOnePlayer(PlayerTypes ePlayer)
{

	// Must be able to set taxes for player
	if(!GET_TEAM(GetPlayer()->getTeam()).CanSetVassalTax(ePlayer))
		return;

	TeamTypes eMyTeam = GetPlayer()->getTeam();
	CvTeam& kMyTeam = GET_TEAM(eMyTeam);

	// Do not allow an AI teammate to do this for a human
	if(!GetPlayer()->isHuman() && kMyTeam.isHuman())
		return;
		
	[B]FStaticVector< int, 128, true, c_eCiv5GameplayDLL > viApproachWeightsPersonality;
	for(iApproachLoop = 0; iApproachLoop < NUM_MAJOR_CIV_APPROACHES; iApproachLoop++)
	{
		viApproachWeightsPersonality.push_back(GetPersonalityMajorCivApproachBias((MajorCivApproachTypes) iApproachLoop));
	}[/B]

	// Current tax rate
	int iTaxRate = GET_TEAM(GetPlayer()->getTeam()).GetVassalTax(ePlayer);

	// Make sure we can actually do that...
	bool bWantToLower = iTaxRate > GC.getVASSALAGE_VASSAL_TAX_PERCENT_MINIMUM();
	bool bWantToRaise = iTaxRate < GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM();

	// Because this function will involve lots of iteration over team members, let's store all alive team members in temporary vectors to improve the speed of this function
	std::vector<CvPlayerAI*> m_MasterTeam;
	std::vector<CvPlayerAI*> m_VassalTeam;

	for(int iI=0; iI < MAX_MAJOR_CIVS; iI++)
	{
		PlayerTypes eLoopPlayer = (PlayerTypes) iI;
		if(GET_PLAYER(eLoopPlayer).isAlive())
		{
			// Master team
			if(GET_PLAYER(eLoopPlayer).getTeam() == GetPlayer()->getTeam())
			{
				m_MasterTeam.push_back(&GET_PLAYER(eLoopPlayer));
			}
			// Vassal team
			else if(GET_PLAYER(eLoopPlayer).getTeam() == GET_PLAYER(ePlayer).getTeam())
			{
				m_VassalTeam.push_back(&GET_PLAYER(eLoopPlayer));
			}
		}
	}
	
	MajorCivOpinionTypes eTeamOpinion = MAJOR_CIV_OPINION_NEUTRAL;
	int iMyCurrentGPT = 0, iMyCurrentGross = 0, iAverageMeanness = 0, iAverageLoyalty = 0, iAverageOpinionScore = 0;
	for(std::vector<CvPlayerAI*>::iterator it = m_MasterTeam.begin(); it != m_MasterTeam.end(); it++)
	{
		iMyCurrentGPT += (*it)->GetTreasury()->CalculateBaseNetGoldTimes100();
		iMyCurrentGross += (*it)->GetTreasury()->CalculateBaseNetGoldTimes100();

		iAverageMeanness += (*it)->GetDiplomacyAI()->GetMeanness();
		iAverageLoyalty += (*it)->GetDiplomacyAI()->GetLoyalty();
		iAverageOpinionScore += (int) (*it)->GetDiplomacyAI()->GetMajorCivOpinion(ePlayer);
	}
	iAverageMeanness /= m_MasterTeam.size();
	iAverageLoyalty /= m_MasterTeam.size();
	iAverageOpinionScore /= m_MasterTeam.size();

	eTeamOpinion = (MajorCivOpinionTypes) iAverageOpinionScore;

	int iVassalCurrentGPT = 0, iVassalCurrentGross = 0;
	for(std::vector<CvPlayerAI*>::iterator it = m_VassalTeam.begin(); it != m_VassalTeam.end(); it++)
	{
		iVassalCurrentGPT += (*it)->GetTreasury()->CalculateBaseNetGoldTimes100();
		iVassalCurrentGross += (*it)->GetTreasury()->CalculateGrossGoldTimes100();
	}

	// Hate him? Don't consider lowering!
	if(eTeamOpinion == MAJOR_CIV_OPINION_UNFORGIVABLE)
		bWantToLower = false;

	
	// Like him? Don't consider raising!
	if(eTeamOpinion == MAJOR_CIV_OPINION_ALLY)
		bWantToRaise = false;

	// We have some choice in the direction taxes can go - pick a direction so we can start deciding
	if(bWantToLower && bWantToRaise)
	{
		// We're in dire straights
		if(iMyCurrentGPT <= 0)
		{
			bWantToLower = false;	// don't even consider lowering
			
			// Check to see if taxing the vassal the maximum would get us out of dire straights
			if(bWantToRaise)
			{
				// Wouldn't help us out at all
				if((iVassalCurrentGross * GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM() / 100 < 0))
				{
					bWantToRaise = false;
				}
				// Tax vassal the maximum to get us out of trouble - his feelings be damned
				else
				{
					GET_TEAM(GetPlayer()->getTeam()).DoApplyVassalTax(ePlayer, GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM());
				}
			}
		}
		// Doing fine - have some choice
		else
		{	

			int iScoreForLower = 0;
			int iScoreForRaise = 0;

			// Starting values based on opinion
			switch(eTeamOpinion)
			{
			case MAJOR_CIV_OPINION_ENEMY:
				iScoreForLower -= [B]viApproachWeightsPersonality[MAJOR_CIV_APPROACH_HOSTILE];
				iScoreForRaise += viApproachWeightsPersonality[MAJOR_CIV_APPROACH_HOSTILE];
			case MAJOR_CIV_OPINION_COMPETITOR:
				iScoreForLower -= viApproachWeightsPersonality[MAJOR_CIV_APPROACH_DECEPTIVE];
				iScoreForRaise += viApproachWeightsPersonality[MAJOR_CIV_APPROACH_DECEPTIVE];[/B]
				break;
			case MAJOR_CIV_OPINION_NEUTRAL:
				iScoreForLower = 0;
				iScoreForRaise = 0;
				break;
			case MAJOR_CIV_OPINION_FAVORABLE:
				iScoreForLower = 10;
				iScoreForRaise = -10;
				break;
			case MAJOR_CIV_OPINION_FRIEND:
				iScoreForLower = 25;
				iScoreForRaise = -25;
				break;
			default:
				CvAssertMsg(false, "Should not have gotten here.");
			}

			// Still deciding what to be done?
			if(bWantToLower && bWantToRaise)
			{
				// Is our vassal doing better than us monetarily?
				if(iVassalCurrentGPT >= iMyCurrentGPT)
				{
					iScoreForLower *= 75;
					iScoreForLower /= 100;

					iScoreForRaise *= 125;
					iScoreForRaise /= 100;
				}
				// He is doing worse than 85% of our GPT
				else if(iVassalCurrentGPT * 85 <= iMyCurrentGPT * 100)
				{
					// Have to like the vassal
					if(eTeamOpinion > MAJOR_CIV_OPINION_NEUTRAL)
					{
						// Determine a percentage to lower
						int iThreshold = 33;

						// He is doing REALLY bad
						if(iVassalCurrentGPT * 150 < iMyCurrentGPT * 100)
							iThreshold = 75;

						int iRand = GC.getGame().getJonRandNum(100, "CvDiplomacyAI: Do we want to give this vassal a nice boost in GPT cus his GPT is less and we like him?");
						if(iRand < iThreshold)
						{
							iScoreForLower *= 150;
							iScoreForLower /= 100;
						}
					}
				}

				// Raise score for lowering based on loyalty
				iScoreForLower *= 100 + (iAverageLoyalty - 5) * 10;
				iScoreForLower /= 100;

				// Raise score for lowering based on meanness
				iScoreForRaise *= 100 + (iAverageMeanness - 5) * 10;
				iScoreForRaise /= 100;

				bWantToLower = (iScoreForLower > iScoreForRaise);
				bWantToRaise = (iScoreForLower < iScoreForRaise);
			}
		}
	}

	CvWeightedVector<int, 20, true> aPossibleValues;	// in case changed, 100 / 5  is a safe bet for number of possible elements
	
	// New tax value defaults to current tax rate
	int iNewTaxValue = iTaxRate;

	// Decided we're going to lower - figure out by how much
	if(bWantToLower)
	{
		int iCurrentIndex = 0;

		// Possible values are determined by increments of 5 starting from below the current tax line
		for(int i = (iTaxRate - 5); i >= GC.getVASSALAGE_VASSAL_TAX_PERCENT_MINIMUM(); i -= 5)
		{
			int iValue = i;
			int iWeight = (iAverageOpinionScore - 3) * iCurrentIndex + 100;
			
			// Determine if we will make at least one GPT profit off of this value, if not, then decentivize
			if(iVassalCurrentGross * iValue < 10000)
				iWeight /= 4;

			aPossibleValues.push_back(iValue, iWeight);
			iCurrentIndex++;
		}

		RandomNumberDelegate fcn;
		fcn = MakeDelegate(&GC.getGame(), &CvGame::getJonRandNum);
		iNewTaxValue = aPossibleValues.ChooseByWeight(&fcn, "Choose the tax value to assign");
	}
	// Decided we're going to raise - figure out by how much
	else
	{
		int iCurrentIndex = 0;
		
		// Possible values are determined by increments of 5 starting from above the current tax line
		for(int i = (iTaxRate + 5); i <= GC.getVASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM(); i += 5)
		{
			int iValue = i;
			int iWeight = (3 - iAverageOpinionScore) * iCurrentIndex + 100;
			
			// Determine if we will make at least one GPT profit off of this value, if not, then decentivize
			if(iVassalCurrentGross * iValue < 10000)
				iWeight /= 4;
			
			aPossibleValues.push_back(iValue, iWeight);
			iCurrentIndex++;
		}

		RandomNumberDelegate fcn;
		fcn = MakeDelegate(&GC.getGame(), &CvGame::getJonRandNum);
		iNewTaxValue = aPossibleValues.ChooseByWeight(&fcn, "Choose the tax value to assign");
	}

	// Set the tax.
	kMyTeam.DoApplyVassalTax(ePlayer, iNewTaxValue);
}

The idea here is that a civ's personality-based integer (somewhere between 1 and 10, but sometimes higher than that) is used to 'define' their behavior towards a vassal based on opinion. Should make them a little less predictable, but in an easily-controlled way.

G

Thanks for that, maybe I'll add it in during beta testing.

Should be feature complete soon. =) I think my last step is the master overview in the vassal overview, and then a metric ton of bugtesting and UAT, from the community :). I am targeting feature-complete by sunday.
 
So should I not start a game 'til then? :p

I am looking forward to the new things that you've put up though, it really appears to add more depth to vassalage.

Haha, maybe not. :crazyeye:

I'm trying to make this update save game compatible. That is an issue I'll need to work with G to get it integrated correctly. Feature complete is untested and probably won't be deployed to CP and CBP til a bit after that.

Go ahead and keep playing, and when I'm ready to release it to the Beta, everyone will know. :)
 
Haha, maybe not. :crazyeye:

I'm trying to make this update save game compatible. That is an issue I'll need to work with G to get it integrated correctly. Feature complete is untested and probably won't be deployed to CP and CBP til a bit after that.

Go ahead and keep playing, and when I'm ready to release it to the Beta, everyone will know. :)

I wouldn't worry about save game compatibility. Most users will start new with the mod anyways.

G
 
How is the trade value for capitulation calculated? I'm fighting a civ with a small city that's surrounded by enemies. It's literally him against the world and his max trade value is around 2800. I briefly saw the actual trade value for capitulation and it was about 20 million.

I find trade values to work weird in general. I was fighting a civ who had two cities left and his trade value was such that capitulation was an option. After letting an allied city-state take one of his cities his trade value went down to 400 (capitulation impossible) when he was in a seemingly worse predicament.

Basically whether it's the capitulation value or max trade values I have found forcing capitulation too hard as it is.
 
How is the trade value for capitulation calculated? I'm fighting a civ with a small city that's surrounded by enemies. It's literally him against the world and his max trade value is around 2800. I briefly saw the actual trade value for capitulation and it was about 20 million.

I find trade values to work weird in general. I was fighting a civ who had two cities left and his trade value was such that capitulation was an option. After letting an allied city-state take one of his cities his trade value went down to 400 (capitulation impossible) when he was in a seemingly worse predicament.

Basically whether it's the capitulation value or max trade values I have found forcing capitulation too hard as it is.

20 million? Sounds like (2^32 / 2 - 1) / 100 or INT_MAX / 100 (was it 21,474,836?). Why is that value being displayed? :crazyeye: That's basically the "not gonna happen" value.

As for what's causing it? Could be numerous things - things I'd like to check out and see if they can't be fixed! Thats TODO for Beta but I haven't worked on it cus I'm still developing features! But if it hit INT MAX, you can forget about the AI doing it.

Edit:needed to fix errors
 
Doing some housecleaning right now, want to check out my logic for checking if vassalage is acceptable (voluntarily and capitulation) and touching up on the UI so things are clearer for players.
 
Figured I'd post now so I can get some feedback.

Here are the new methods for determining whether vassalage is acceptable. Could contain bugs:

Code:
///	Do we want to capitulate to ePlayer? (bWar: is this a wartime assessment?)
bool CvDiplomacyAI::IsVassalageAcceptable(PlayerTypes ePlayer, bool bWar)
{
	CvAssertMsg(ePlayer >= 0, "DIPLOMACY_AI: Invalid Player Index.  Please send Jon this with your last 5 autosaves and what changelist # you're playing.");
	CvAssertMsg(ePlayer < MAX_MAJOR_CIVS, "DIPLOMACY_AI: Invalid Player Index.  Please send Jon this with your last 5 autosaves and what changelist # you're playing.");

	// We can't become ePlayer's vassal
	if(!GET_TEAM(GET_PLAYER(ePlayer).getTeam()).canBecomeVassal(GetPlayer()->getTeam()))
		return false;

	// Human teams can capitulate, but the AI can't do it for him and he must accept on the trade screen
	if(GET_TEAM(GetPlayer()->getTeam()).isHuman() || GetPlayer()->IsAITeammateOfHuman())
		return false;

	// Vassalage never acceptable if I have vassals already
	if(GET_TEAM(GetPlayer()->getTeam()).GetNumVassals() > 0)
		return false;

	// Split this function into two evaluations, capitulation (war) and voluntary (voluntary)
	if(bWar)
	{
		return IsCapitulationAcceptable(ePlayer);
	}
	else
	{
		return IsVoluntaryVassalageAcceptable(ePlayer);
	}
}

// Do we want to capitulate due to war to ePlayer?
bool CvDiplomacyAI::IsCapitulationAcceptable(PlayerTypes ePlayer)
{
	TeamTypes eOurTeam = GetPlayer()->getTeam();
	CvTeam& kOurTeam = GET_TEAM(eOurTeam);

	TeamTypes eTheirTeam = GET_PLAYER(ePlayer).getTeam();
	CvTeam& kTheirTeam = GET_TEAM(eTheirTeam);

	if(!kOurTeam.isAtWar(eTheirTeam))
		return false;

	// How's the war going?
	WarStateTypes eWarState = GetWarState(ePlayer);
	if(eWarState >= WAR_STATE_STALEMATE ||
		eWarState == NO_WAR_STATE_TYPE)
		return false;
	
	int iWantVassalageScore = 0;
	if(eWarState == WAR_STATE_NEARLY_DEFEATED)
		iWantVassalageScore = 100;
	else
		iWantVassalageScore = 70;

	// Mod based on distance
	switch(GetPlayer()->GetProximityToPlayer(ePlayer))
	{
		case PLAYER_PROXIMITY_DISTANT:
			iWantVassalageScore *= 33;
			iWantVassalageScore /= 100;
			break;
		case PLAYER_PROXIMITY_FAR:
			iWantVassalageScore *= 66;
			iWantVassalageScore /= 100;
			break;
		case PLAYER_PROXIMITY_CLOSE:
			iWantVassalageScore *= 100;
			iWantVassalageScore /= 100;
			break;
		case PLAYER_PROXIMITY_NEIGHBORS:
			iWantVassalageScore *= 120;
			iWantVassalageScore /= 100;
			break;
	}

	// Factor in this guy's military strength
	switch(GetPlayerMilitaryStrengthComparedToUs(ePlayer))
	{
		case STRENGTH_IMMENSE:
			iWantVassalageScore *= 200;
			iWantVassalageScore /= 100;
			break;
		case STRENGTH_POWERFUL:
			iWantVassalageScore *= 150;
			iWantVassalageScore /= 100;
			break;
		case STRENGTH_STRONG:
			iWantVassalageScore *= 125;
			iWantVassalageScore /= 100;
			break;
		default:
			return false;
			break;
	}

	// We're going for conquest...
	if(IsGoingForWorldConquest())
	{
		iWantVassalageScore *= 50;
		iWantVassalageScore /= 100;
	}

	// Lost our capital?
	if(GetPlayer()->IsHasLostCapital())
	{
		iWantVassalageScore *= 150;
		iWantVassalageScore /= 100;

		// to this player?
		CvPlot* pOriginalCapitalPlot = GC.getMap().plot(GET_PLAYER(ePlayer).->GetOriginalCapitalX(), GET_PLAYER(ePlayer).GetOriginalCapitalY());
		if(pOriginalCapitalPlot != NULL)
		{
			if(pOriginalCapitalPlot->isCity())
			{
				if(GET_PLAYER(pOriginalCapitalPlot->getOwner()).getTeam() == GET_PLAYER(ePlayer).getTeam())
				{
					iWantVassalageScore *= 150;
					iWantVassalageScore /= 100;
				}
			}
		}
	}

	// They have more civs than us!
	if(kOurTeam.getAliveCount() < kTheirTeam.getAliveCount())
	{
		iWantVassalageScore *= 200;
		iWantVassalageScore /= 100;
	}
	else if(kOurTeam.getAliveCount() > kTheirTeam.getAliveCount())
	{
		iWantVassalageScore *= 50;
		iWantVassalageScore /= 100;
	}

	int iCulturalDominanceOverUs = GET_PLAYER(ePlayer).GetCulture()->GetInfluenceLevel(m_pPlayer->GetID()) - GetPlayer()->GetCulture()->GetInfluenceLevel(ePlayer);

	iWantVassalageScore *= (100 + 10 * iCulturalDominanceOverUs);
	iWantVassalageScore /= 100;

	int iOurTechs = kOurTeam.GetTeamTechs()->GetNumTechsKnown();
	int iTheirTechs = kTheirTeam.GetTeamTechs()->GetNumTechsKnown();
	int iTechPercent = 0;
	if(iTheirTechs == 0)
		iTechPercent = INT_MAX;
	else
		iTechPercent = iOurTechs * 100 / iTheirTechs;

	if(iTechPercent < 50) {
		iWantVassalageScore *= 250;
		iWantVassalageScore /= 100;
	}
	else if(iTechPercent < 75) {
		iWantVassalageScore *= 150;
		iWantVassalageScore /= 100;
	}
	else if(iTechPercent < 85) {
		iWantVassalageScore *= 120;
		iWantVassalageScore /= 100;
	}
	else if(iTechPercent < 95) {
		iWantVassalageScore *= 105;
		iWantVassalageScore /= 100;
	}
	else {
		iWantVassalageScore *= 100;
		iWantVassalageScore /= 100;
	}


	int iThreshold = /*100*/ GC.getVASSALAGE_CAPITULATE_BASE_THRESHOLD();
	return (iWantVassalageScore > iThreshold);
}

// Do we want to voluntarily become ePlayer's vassal?
bool CvDiplomacyAI::IsVoluntaryVassalageAcceptable(PlayerTypes ePlayer)
{
	TeamTypes eOurTeam = GetPlayer()->getTeam();
	CvTeam& kOurTeam = GET_TEAM(eOurTeam);

	TeamTypes eTheirTeam = GET_PLAYER(ePlayer).getTeam();
	CvTeam& kTheirTeam = GET_TEAM(eTheirTeam);

	// No cities
	if(kTheirTeam.getNumCities() == 0)
		return false;

	// We have more members - no
	if(kOurTeam.getAliveCount() > kTheirTeam.getAliveCount())
		return false;

	std::vector<PlayerTypes> aOurTeam;
	std::vector<PlayerTypes> aTheirTeam;
	for(int iI = 0; iI < MAX_MAJOR_CIVS; iI++)
	{
		PlayerTypes eLoopPlayer = (PlayerTypes) iI;
		if(GET_PLAYER(eLoopPlayer).isAlive())
		{
			if(GET_PLAYER(eLoopPlayer).getTeam() == eOurTeam)
				aOurTeam.push_back(eLoopPlayer);
			if(GET_PLAYER(eLoopPlayer).getTeam() == eTheirTeam)
				aTheirTeam.push_back(eLoopPlayer);
		}
	}

	int iAverageOpinionScore = 0;
	int iOurCapitals = 0;
	int iTheirCapitals = 0;
	for(std::vector<PlayerTypes>::iterator it = aOurTeam.begin(); it != aOurTeam.end(); it++)
	{
		iOurCapitals += GET_PLAYER(*it).GetNumCapitalCities();
		iAverageOpinionScore += (int) GET_PLAYER(*it).GetDiplomacyAI()->GetMajorCivOpinion(ePlayer);
	}
	for(std::vector<PlayerTypes>::iterator it = aTheirTeam.begin(); it != aTheirTeam.end(); it++)
	{
		iTheirCapitals += GET_PLAYER(*it).GetNumCapitalCities();
	}
	iAverageOpinionScore /= aOurTeam.size();
	MajorCivOpinionTypes eOpinion = (MajorCivOpinionTypes) iAverageOpinionScore;

	if(eOpinion <= MAJOR_CIV_OPINION_COMPETITOR)
		return false;

	// We possess more capitals than them
	if(iOurCapitals > iTheirCapitals)
		return false;

	// We're going for world conquest?
	if(IsGoingForWorldConquest())
	{
		if(GetPlayerMilitaryStrengthComparedToUs(ePlayer) <= STRENGTH_STRONG)
			return false;
	}

	MajorCivApproachTypes eTrueApproach = GetMajorCivApproach(ePlayer, /*bHideTrueFeelings*/ false);
	
	// if we're planning on war or hostile with this player, don't want to be his voluntary vassal
	if(eTrueApproach == MAJOR_CIV_APPROACH_WAR ||
		eTrueApproach == MAJOR_CIV_APPROACH_HOSTILE)
		return false;

	//Denouncement in either direction?
	if(IsDenouncedPlayer(ePlayer) || GET_PLAYER(ePlayer).GetDiplomacyAI()->IsDenouncedPlayer(GetPlayer()->GetID()))
			return false;

	// Don't accept vassalage from players too far away
	if(GetPlayer()->GetProximityToPlayer(ePlayer) < PLAYER_PROXIMITY_CLOSE)
		return false;

	// Player is not strong
	if(GetPlayerMilitaryStrengthComparedToUs(ePlayer) <= STRENGTH_AVERAGE)
		return false;

	// Are we dominating them in some way?
	InfluenceLevelTypes eInfluence = GetPlayer()->GetCulture()->GetInfluenceLevel(ePlayer);
	if(eInfluence >= INFLUENCE_LEVEL_INFLUENTIAL)
		return false;

	// No voluntary capitulation if there's less than 50% of the players ever alive
	if(GC.getGame().countMajorCivsAlive() < (GC.getGame().countMajorCivsEverAlive() / 2 ))
		return false;

	// Has this player a military promise against us?
	if(IsPlayerBrokenMilitaryPromise(ePlayer))
		return false;

	// Do we think this player is a warmonger?
	if(GetWarmongerThreat(ePlayer) >= THREAT_SEVERE)
		return false;

	// Differing ideologies?
	if(GetPlayer()->GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE &&
		GET_PLAYER(ePlayer).GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE &&
		GetPlayer()->GetPlayerPolicies()->GetLateGamePolicyTree() != GET_PLAYER(ePlayer).GetPlayerPolicies()->GetLateGamePolicyTree())
		return false;

	// If we got down here, then vassalage is possible - let's evaluate
	int iWantVassalageScore = 0;
	
	int iPercentPopulation = kOurTeam.getTotalPopulation() * 100 / kTheirTeam.getTotalPopulation();
	if(iPercentPopulation >= 100)
		return false;
	
	if(iPercentPopulation < 50)
		iWantVassalageScore += 75;
	else if(iPercentPopulation < 75)
		iWantVassalageScore += 40;
	else if(iPercentPopulation < 85)
		iWantVassalageScore += 25;
	else
		iWantVassalageScore += 10;

	int iOurTechs = kOurTeam.GetTeamTechs()->GetNumTechsKnown();
	int iTheirTechs = kTheirTeam.GetTeamTechs()->GetNumTechsKnown();
	int iTechPercent = 0;
	if(iTheirTechs == 0)
		iTechPercent = INT_MAX;
	else
		iTechPercent = iOurTechs * 100 / iTheirTechs;

	// We have a lot more techs than them!
	if(iTechPercent > 130)
		return false;
	else if(iTechPercent < 50) {
		iWantVassalageScore *= 250;
		iWantVassalageScore /= 100;
	}
	else if(iTechPercent < 75) {
		iWantVassalageScore *= 150;
		iWantVassalageScore /= 100;
	}
	else if(iTechPercent < 85) {
		iWantVassalageScore *= 120;
		iWantVassalageScore /= 100;
	}
	else if(iTechPercent < 95) {
		iWantVassalageScore *= 105;
		iWantVassalageScore /= 100;
	}
	else {
		iWantVassalageScore *= 100;
		iWantVassalageScore /= 100;
	}

	// Mod based on opinion
	switch(eOpinion)
	{
		case MAJOR_CIV_OPINION_NEUTRAL:
			iWantVassalageScore *= 100;
			iWantVassalageScore /= 100;
			break;
		case MAJOR_CIV_OPINION_FAVORABLE:
			iWantVassalageScore *= 110;
			iWantVassalageScore /= 100;
			break;
		case MAJOR_CIV_OPINION_FRIEND:
			iWantVassalageScore *= 120;
			iWantVassalageScore /= 100;
			break;
		case MAJOR_CIV_OPINION_ALLY:
			iWantVassalageScore *= 125;
			iWantVassalageScore /= 100;
			break;
	}

	int iCulturalDominanceOverUs = GET_PLAYER(ePlayer).GetCulture()->GetInfluenceLevel(m_pPlayer->GetID()) - GetPlayer()->GetCulture()->GetInfluenceLevel(ePlayer);

	iWantVassalageScore *= (100 + 10 * iCulturalDominanceOverUs);
	iWantVassalageScore /= 100;

	// Factor in this guy's military strength
	switch(GetPlayerMilitaryStrengthComparedToUs(ePlayer))
	{
		case STRENGTH_IMMENSE:
			iWantVassalageScore *= 200;
			iWantVassalageScore /= 100;
			break;
		case STRENGTH_POWERFUL:
			iWantVassalageScore *= 150;
			iWantVassalageScore /= 100;
			break;
		case STRENGTH_STRONG:
			iWantVassalageScore *= 125;
			iWantVassalageScore /= 100;
			break;
		default:
			return false;
	}

	switch(GetPlayer()->GetProximityToPlayer(ePlayer))
	{
		case PLAYER_PROXIMITY_NEIGHBORS:
			iWantVassalageScore *= 133;
			iWantVassalageScore /= 100;
			break;
		case PLAYER_PROXIMITY_CLOSE:
			iWantVassalageScore *= 110;
			iWantVassalageScore /= 100;
			break;
		default:
			return false;
	}

	// Reduce score based on at war count
	iWantVassalageScore -= 15 * kTheirTeam.getAtWarCount(true);

	int iThreshold = /*100*/ GC.getVASSALAGE_CAPITULATE_BASE_THRESHOLD();

	return (iWantVassalageScore > iThreshold);
}
 
I am not sure if the mod is feature complete, but I am currently doing a stable check to see if everything is not gonna break, then I want to work on pushing it to a Beta build for others to play, and then I'll add some other stuff (like help screens, civilopedia, etc.)
 
I am not sure if the mod is feature complete, but I am currently doing a stable check to see if everything is not gonna break, then I want to work on pushing it to a Beta build for others to play, and then I'll add some other stuff (like help screens, civilopedia, etc.)


Awesome!
 
I think I'm nearing the end.

Here is a compilation of the current patch notes I have

Spoiler :

General
  • Masters now receive a +33% tourism bonus against their vassals
  • Vassals can no longer be considered for Choose Host or World Leader &#8211; but they still become host of the World Congress upon founding it.
  • Death of a Vassal's unit inside his territory now counts for failed protect score
  • Death of a Vassal's unit inside enemy territory no longer counts for failed protect score
  • Pillaged vassal territory now applies a small failed protect score
  • Notifications related to Vassalage have received an upgrade to be more descriptive.
  • Upon capitulation (forced vassalage as part of a peace treaty), the new vassal will reset negative diplomatic modifiers similar to civ resurrection. Voluntary vassals are not affected by this.
  • You can declare war on your vassals, but other teams cannot declare war on your vassals. This provides two options of breaking vassalage: declaration of war, and peaceful liberation (see Vassal Liberation section).
  • Extended range of failed protect score near vassal cities to 3 hexes away.
  • Added a new Vassalage Overview &#8211; see the Vassalage Overview section
  • Technology Trading has been moved to Scientific Theory

AI
  • AI Vassals can denounce their masters &#8211; the weight is determined based on how well you've treated your vassal.
  • AI Masters will never denounce their vassals
  • Retooled AI evaluation on the following:
    • Wants to capitulate
    • Wants to become your voluntary vassal
    • Wants to end vassalage with master
    • Wants to accept human's bid to end vassalage
  • Now evaluate a few flavors (expansion, offense, military training defense, culture, wonder) when determining whether a vassal wants to become a voluntary vassal or not.
    • For example: Ramesses is 22, Enrico is 10, Shaka is -34, Attila is -14
    • Note to modders: if you delete those flavors, then the flavor mod difference for that flavor is 0
  • Common sense evaluation: If an AI is going for diplomatic victory, and the United Nations has started, then they will never accept voluntary vassalage and will attempt to leave vassalage immediately.

New Feature: Vassal Overview
  • In an effect to provide a unified and easy to manage screen for all things related to Vassalage, I have created the Vassal Overview
  • If you have vassals, you can select each one individually to receive more details about them
  • The Vassal Overview provides a concise description of the type of vassalage, how long the vassalage has lasted (and the turn it started) as well as helpful tooltips to provide more meaning to things that can be unclear to the player.
  • On the left side of the Vassal Details box, you can see basic stats (similar to the top panel for your own civ) for your vassal. You can also see what tech they are researching, and how long
  • On the right side of the Vassal Details box, you can set your vassal taxes and view details on how they view your vassalage and why they feel that way. You can also liberate your vassal (when applicable, see Vassal Liberation section)

New Feature: Vassal Taxes
  • You can set vassal taxes on intervals of 5% (from 0%-25%).
  • Vassal taxes are applied per vassal, but distributed evenly amongst teammates.
  • Vassal taxes are calculated at the beginning of the vassal's turn and are a percentage of their gross (positive gold) income.
  • The heavier you tax a vassal, the more they will hate you.
  • AI teammates will never set taxes for human players.
  • AI opponents will set taxes based on a number of factors, including how much they like you, and how both of your financial situations are.

New Feature: Vassal Liberation
  • Masters can now liberate their vassals after a certain period of turns (50 turns on Standard).
  • Liberation of vassals acts as a peaceful removal of vassalage, and grants a huge positive modifier for liberating a vassal
  • You may liberate vassals when eligible through the new Vassalage Overview
  • You may request independence from masters through the new Vassalage Overview
  • AI masters will liberate you if they like you, and they do not deem you a threat to them
  • This is not the same as resurrecting a player, so you will not receive a reset in negative diplomatic modifiers.

Bugfixes
  • Fixed a bug where voluntary vassalage was not being correctly set
  • Fixed a bug where you could call the LUA function DoBecomeVassal() with invalid civs (for example, could set a civ to be a vassal of itself)
  • Reversed the function for canBecomeVassal() to be actually sane. This mostly affects modders:
    • Team1.canBecomeVassal(Team2) now checks if Team1 can become Team2's vassal, was Team2 can become Team1's vassal
  • Added new function CanMakeVassal() which is simply the reverse of CanBecomeVassal()
  • Fixed issue where DealAI did not send offers to revoke human vassals

UI
  • Complete pass on tooltips on the Diplomacy Trade screen related to "Vassal State", "Capitulation", and "Liberation" in an effort to provide more clarity to the player

 
Putmalk is awesome :cool:
I can't wait to see all these vassal ideas in action!
 
Top Bottom