City State Diplomacy Mod (Updated)

I agree with thr ability to buy diplo units for faith and agree with G that it should be on the Patronage finisher (thats where diplo with cs belongs).

Two pleas though. Think about how you implement it to make compatible with other mods that change policies: A) be carefull with just updating specific abilities as These may have been changed in other mods (maybe you should delete the policy and rebuild it from scratch) and B) make it optional in the options file.

\Skodkim
 
I think the piety tree would be better. A added incentive to get that policy tree. I think the patronage policy tree is powerful enough already. Too bad about not increasing unit cost at higher levels. I still think buying dilpo units for cash is still the same as giving a cash gift. Big deal if I have to move the unit, I know there is a happy medium somewhere. This mod is so well done, I have faith:lol: you will fix the problem

I've tweaked the gold hurrying system to be an option in the options.sql file - you can now increase/decrease/eliminate the cost of buying units via gold. v14 almost ready for testing.

Skodkim and Whoward, how comes the .dll testing?

edit: v14 not ready for primetime.
 
I agree with thr ability to buy diplo units for faith and agree with G that it should be on the Patronage finisher (thats where diplo with cs belongs).

Two pleas though. Think about how you implement it to make compatible with other mods that change policies: A) be carefull with just updating specific abilities as These may have been changed in other mods (maybe you should delete the policy and rebuild it from scratch) and B) make it optional in the options file.

\Skodkim

Looking at the xml, I don't see an easy way to enable diplomatic units to be purchased with faith, and, if I can get the .dll to treat diplomatic units as 'not' great people, it won't be an appropriate policy anyways.

Edit: Work on v14 is nearing completion. I've finally nailed the .dll AI work to get the diplomatic units to behave correctly without being treated by the game as great people. This should prevent city-states from gifting them to players and/or diplomatic units increasing the GPP needed for other great people. One more hurdle gone, with but few remaining. Once Whoward and Skodkim give me the 'ok' regarding .dll synchronization, I'll deploy v14 fully. The beta exists below.

I've also edited unit costs to be reduced by around 10-15%, which should accelerate early/mid-game diplomacy a bit. Cheers!
G
 
Hi G

Whoward ended up doing the tests that could be done. We had a discussion on how I could help - I had a proposal concerning using a specific scenario to see how the AI used its diplo units - but it all ended with me not doing any tests. It would be rather hard doing simple tests to see if it worked anyway as it wouldn't be kind of a yes/no test, if you know what I mean. That would require playing full games instead. If I understood correct Whoward was pretty sure it worked anyway.

I guess we're just waiting for a release of the dll...

Looking forward to that and the dll - thanks for all the work you put into this :)

\Skodkim
 
Edit: Work on v14 is nearing completion. I've finally nailed the .dll AI work to get the diplomatic units to behave correctly without being treated by the game as great people.
G

Could that open up the possibility of not having the units be great people at all? If you remember, I would have liked quite a while back already to keep the Swedish trait as it is, but it was not feasible due to the units being the way they are. :)
 
Could that open up the possibility of not having the units be great people at all? If you remember, I would have liked quite a while back already to keep the Swedish trait as it is, but it was not feasible due to the units being the way they are. :)

That's the idea. I'll leave the Sweden Trait as-is (I think it is stronger than the vanilla Sweden trait), but players have the option to take it back to the vanilla trait in the options.sql file. Progress!
 
Any chance of an ETA? :mischief:

V35 is up. You'll need to enable CSD support via the GLOBAL_CSD option (either by editing the CustomModOptions.xml file directly, or adding the XML/SQL to the CSD mod itself - if the latter suggest this is in a file of its own so it can fail gracefully if the DLL is not present)
 
V35 is up. You'll need to enable CSD support via the GLOBAL_CSD option (either by editing the CustomModOptions.xml file directly, or adding the XML/SQL to the CSD mod itself - if the latter suggest this is in a file of its own so it can fail gracefully if the DLL is not present)

Excellent. I'll incorporate the dll into my mod and add the CSD option via SQL (so it can be disabled if need be).

So v14 seems to be working well enough (no crashes, AI is very competitive, World Congress resolutions working nicely). Here's my current 'to-do' list:

1.) Find a way to get the AI to have stronger opinions about the CSD resolutions in the world congress. Currently, the AI doesn't seem to mind if you decolonize one of their allied city-states. There aren't flavors for the resolutions (like other attributes), which may explain the somewhat random nature of world congress selections by the AI (as well as their likely desire to always vote for themselves for everything). As far as I can tell, decisions are weighted in the dll, so more spelunking is needed.

2.) Figure out how to get the AI to spend its gold on useful buildings. This is the biggie. The AI's stupidity regarding gold has to end. I'm going to borrow the CEP LUA method, as attempts to modify the limited 'hurry production' section of the dll have been unsuccessful thus far.

As always, if anyone has any ideas, or wants to help tackle these two issues, let me know.
G
 
That's the idea. I'll leave the Sweden Trait as-is (I think it is stronger than the vanilla Sweden trait), but players have the option to take it back to the vanilla trait in the options.sql file. Progress!

Outstanding! :D I'm looking forward to that implementation and thank you endlessly for working on this (little) detail. :hatsoff: :)
 
It is already an option (has been for some time). Browse the Options.sql folder. :)

The current option gives the normal influence + the 90 from the base game (Sweden's trait) though when using diplomatic units, doesn't it? I think I might have misunderstood something. I would like to have the Swedish trait as it is, allowing great people to be giftable, while at the same time not change the way your diplomatic units work and how much influence those give.
 
The current option gives the normal influence + the 90 from the base game (Sweden's trait) though when using diplomatic units, doesn't it? I think I might have misunderstood something. I would like to have the Swedish trait as it is, allowing great people to be giftable, while at the same time not change the way your diplomatic units work and how much influence those give.

The option to revert to Sweden's trait has been there for awhile. The dll change to make diplo units not great people is new as of v14.
 
Well, I'm at a loss. I've spent the past hour or so trying to compile a simplified purchase scheme using the CEP purchase LUAs. I feel like everything is in its place, and the firetuner isn't throwing back any errors, however...nothing is happening. The AI still purchases tiles with reckless abandon (something I'm finally figuring out how to control), but no buildings or units.

Here's the lua that I've cobbled together. I could really use a fresh set of eyes on this. Thanks in advance:

Spoiler :
Code:
-- Purchases.lua
-- Author: Thalassicus (edited by Gazebo)
-- DateCreated: 12/30/2013 2:11:50 PM
--------------------------------------------------------------

include("FLuaVector")

CSD = {}
for row in GameInfo.CSD() do
	CSD[row.Type] = row.Value
end

--
-- Spend AI gold more intelligently
--

--[[

The AI faces two questions:	
	1)  Will I spend gold?
		Yes, if I have more goldStored than the goldHigh, and my profit-per-turn is positive.
	2)  How much gold will I spend?
		The budget is (goldStored - goldMin), stopping when under goldLow
	
An AI with +20g per turn, 400 goldStored and a 500 goldHigh threshold decides:
	1)  I will not spend gold.
	
An AI with -10g per turn, 800 goldStored and a 500 goldHigh threshold decides:
	1)  I will not spend gold.
	
An AI with +20g per turn, 800 goldStored and a 500 goldHigh threshold decides:
	1)  I will spend gold.
	2)  My budget is 700 gold (800-100).
	3)  I will continue purchasing things until I spend 700 gold,
		without going below the minimum of 100 gold,
		and without going below 0g per turn profit.

--]]

local warUnitFlavorsEarly = {
	{FlavorType="FLAVOR_MOBILE",			Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_SHOCK_1.ID}}		,
	{FlavorType="FLAVOR_MELEE",				Mult=3, Promos={GameInfo.UnitPromotions.PROMOTION_DRILL_1.ID}}		,
	{FlavorType="FLAVOR_SIEGE",				Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_SIEGE.ID}}			,
	{FlavorType="FLAVOR_RANGED",			Mult=2, Promos={GameInfo.UnitPromotions.PROMOTION_BARRAGE_1.ID}}		,
	{FlavorType="FLAVOR_NAVAL_BOMBARDMENT",	Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_BOMBARDMENT_1.ID}}	,
	{FlavorType="FLAVOR_ANTI_MOBILE",		Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_SHOCK_1.ID}}
}

local warUnitFlavorsLate = {
	{FlavorType="FLAVOR_MOBILE",			Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_SHOCK_1.ID,		GameInfo.UnitPromotions.PROMOTION_SHOCK_2.ID}}			,
	{FlavorType="FLAVOR_AIR",				Mult=1, Promos={}},
	{FlavorType="FLAVOR_MELEE",				Mult=3, Promos={GameInfo.UnitPromotions.PROMOTION_DRILL_1.ID,		GameInfo.UnitPromotions.PROMOTION_DRILL_2.ID}}			,
	{FlavorType="FLAVOR_SIEGE",				Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_ACCURACY_1.ID,		GameInfo.UnitPromotions.PROMOTION_SIEGE.ID}}			,
	{FlavorType="FLAVOR_RANGED",			Mult=2, Promos={GameInfo.UnitPromotions.PROMOTION_DRILL_1.ID,		GameInfo.UnitPromotions.PROMOTION_DRILL_2.ID}}			,
	{FlavorType="FLAVOR_NAVAL_BOMBARDMENT",	Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_BOMBARDMENT_1.ID,	GameInfo.UnitPromotions.PROMOTION_BOMBARDMENT_2.ID}}	,
	{FlavorType="FLAVOR_ANTI_MOBILE",		Mult=1, Promos={GameInfo.UnitPromotions.PROMOTION_SHOCK_1.ID}},
	{FlavorType="FLAVOR_ANTIAIR",			Mult=1, Promos={}}			
}

function Game.GetRandomWeighted(list, size)
	size = size or 100
	local chanceIDs = Game.GetWeightedTable(list, size)

	if chanceIDs == -1 then
		return -1
	end
	local randomID = 1 + Map.Rand(size, "Game.GetRandomWeighted")
	if not chanceIDs[randomID] then
		log:Warn("Game.GetrandomIDWeighted: invalid random index selected = %s", randomID)
		chanceIDs[randomID] = -1
	end
	return chanceIDs[randomID]
end

PlayerClass = getmetatable(Players[0]).__index
---------------------------------------------------------------------
-- player:IsBudgetGone(goldMin)
--
function PlayerClass.IsBudgetGone(player, goldMin, extraCost)
	return player:GetYieldStored(YieldTypes.YIELD_GOLD) < math.max(goldMin, (goldMin - 20 * ( player:GetYieldRate(YieldTypes.YIELD_GOLD)-(extraCost or 0) )))
end

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

MapModData.Cep_EndTurnTimes = {
	Turn		= 0,
	Players		= 0,
	Units		= 0,
	Cities		= 0,
	Policies	= 0,
	Plots		= 0,
	Total		= 0
}


function OnTurnEnd()
	--log:Info("OnTurnEnd")
	local startClockTime = os.clock()
	local stepClockTime = os.clock()
	LuaEvents.ActivePlayerTurnEnd_Turn()
	MapModData.Cep_EndTurnTimes.Turn = MapModData.Cep_EndTurnTimes.Turn + (os.clock() - stepClockTime)
	log:Debug("OnTurnEnd   %10s %10.3f seconds", "Turn", os.clock() - stepClockTime)
	stepClockTime = os.clock()
	for playerID, player in pairs(Players) do
		if player:IsAliveCiv() then
			LuaEvents.ActivePlayerTurnEnd_Player(player)
		end
	end
	log:Debug("OnTurnEnd   %10s %10.3f seconds", "Players", os.clock() - stepClockTime)
	MapModData.Cep_EndTurnTimes.Players = MapModData.Cep_EndTurnTimes.Players + (os.clock() - stepClockTime)
	stepClockTime = os.clock()
	for playerID, player in pairs(Players) do
		if player:IsAliveCiv() then
			for pUnit in player:Units() do
				if pUnit then
					LuaEvents.ActivePlayerTurnEnd_Unit(pUnit)
				end
			end
		end
	end
	log:Debug("OnTurnEnd   %10s %10.3f seconds", "Units", os.clock() - stepClockTime)
	MapModData.Cep_EndTurnTimes.Units = MapModData.Cep_EndTurnTimes.Units + (os.clock() - stepClockTime)
	stepClockTime = os.clock()
	for playerID, player in pairs(Players) do
		if player:IsAliveCiv() then
			for city in player:Cities() do
				if city then
					LuaEvents.ActivePlayerTurnEnd_City(city, player)
				end
			end
		end
	end
	log:Debug("OnTurnEnd   %10s %10.3f seconds", "Cities", os.clock() - stepClockTime)
	MapModData.Cep_EndTurnTimes.Cities = MapModData.Cep_EndTurnTimes.Cities + (os.clock() - stepClockTime)
	stepClockTime = os.clock()
	for playerID, player in pairs(Players) do
		if player:IsAliveCiv() then
			if not player:IsMinorCiv() then
				for policyInfo in GameInfo.Policies() do
					local policyID = policyInfo.ID
					if MapModData.Cep_HasPolicy[playerID][policyID] ~= player:HasPolicy(policyID) then
						MapModData.Cep_HasPolicy[playerID][policyID] = player:HasPolicy(policyID)
						LuaEvents.NewPolicy(player, policyID)
					end
				end
			end
		end
	end
	log:Debug("OnTurnEnd   %10s %10.3f seconds", "Policies", os.clock() - stepClockTime)
	MapModData.Cep_EndTurnTimes.Policies = MapModData.Cep_EndTurnTimes.Policies + (os.clock() - stepClockTime)
	stepClockTime = os.clock()
	for plotID = 0, Map.GetNumPlots() - 1, 1 do
		local plot = Map.GetPlotByIndex(plotID)
		LuaEvents.ActivePlayerTurnEnd_Plot(plot)
	end
	log:Debug("OnTurnEnd   %10s %10.3f seconds", "Plots", os.clock() - stepClockTime)
	MapModData.Cep_EndTurnTimes.Plots = MapModData.Cep_EndTurnTimes.Plots + (os.clock() - stepClockTime)
	log:Info("OnTurnEnd    %10s %10.3f seconds", "Total", os.clock() - startClockTime)
	MapModData.Cep_EndTurnTimes.Total = MapModData.Cep_EndTurnTimes.Total + (os.clock() - startClockTime)
	startAITurnTime = os.clock()
end

function SpendAIGold(player)
	if (player:IsHuman()
			or not player:GetCapitalCity() 
			or player:IsBudgetGone(0)
			--or (Game.GetAdjustedTurn() < 10)
			) then
		return
	end
	
	if player:IsMinorCiv() then
		return UpgradeSomeUnit(player, player:GetYieldStored(YieldTypes.YIELD_GOLD))
	end

	local capital			= player:GetCapitalCity()	
	local playerID			= player:GetID()
	local eraID				= Game.GetAverageHumanEra()
	local isWarHuman		= player:IsAtWarWithHuman()
	local isWarAny			= player:IsAtWarWithAny()
	local isEarlyEra		= (player:GetCurrentEra() < GameInfo.Eras.ERA_RENAISSANCE.ID)
	local activePlayer		= Players[Game.GetActivePlayer()]
	local costRA			= GameInfo.Eras[eraID].ResearchAgreementCost * Game.GetSpeedInfo().GoldPercent / 100
	local goldStored		= player:GetYieldStored(YieldTypes.YIELD_GOLD)
	local goldHigh			= Game.Round(costRA * Cep.AI_PURCHASE_BUDGET_HIGH)
	local goldLow			= Game.Round(costRA * Cep.AI_PURCHASE_BUDGET_LOW)
	local goldMin			= Game.Round(costRA * Cep.AI_PURCHASE_BUDGET_MINIMUM)
	local cities			= {}
	local citiesReverse		= {}
	local ports				= {}
	local portsReverse		= {}
	local numMilitaryLand	= 0
	local numMilitaryTotal	= 0
	local numHealers		= 0
	local numWorkers		= 0
	local totalUnitFlavor	= {}
	local medicID			= GameInfo.UnitPromotions.PROMOTION_MEDIC.ID
	
	--
	log:Debug("%-15s %20s %3s available (%s threshold, %s minimum)",
		"AIPurchase",
		player:GetName(),
		goldStored,
		goldHigh,
		goldMin
	)
	--]]
	
	if goldStored < goldHigh then
		return
	end
	
	--
	-- Create lists
	--
	
	for city in player:Cities() do
		if not city:IsResistance() and not city:IsRazing() and not city:IsCapital() then
			table.insert(cities, {id=City_GetID(city), pop=city:GetPopulation()})
			if city:IsCoastal() then
				table.insert(ports, {id=City_GetID(city), pop=city:GetPopulation()})				
			end
		end
	end
	table.sort(cities, function(a, b) return a.pop < b.pop end)
	table.sort(ports, function(a, b) return a.pop < b.pop end)
	citiesReverse = Game.Reverse(cities)
	portsReverse = Game.Reverse(ports)
	
	-- capital gets first priority
	table.insert(cities, 			1, {id=City_GetID(capital), pop=capital:GetPopulation()})
	table.insert(citiesReverse, 	1, {id=City_GetID(capital), pop=capital:GetPopulation()})
	if capital:IsCoastal() then
		table.insert(ports, 		1, {id=City_GetID(capital), pop=capital:GetPopulation()})
		table.insert(portsReverse,	1, {id=City_GetID(capital), pop=capital:GetPopulation()})
	end
	
	for flavorInfo in GameInfo.Flavors() do
		totalUnitFlavor[flavorInfo.Type] = 0
	end
	
	for unit in player:Units() do
		if Unit_IsWorker(unit) then
			numWorkers = numWorkers + 1
		elseif Unit_IsCombatDomain(unit, "DOMAIN_LAND") then
			if unit:IsHasPromotion(medicID) then
				numHealers = numHealers + 1
			else
				numMilitaryLand = numMilitaryLand + 1
			end
		end
		if unit:IsCombatUnit() then
			numMilitaryTotal = numMilitaryTotal + 1
		end
		
		for info in GameInfo.Unit_Flavors{UnitType = GameInfo.Units[unit:GetUnitType()].Type} do
			totalUnitFlavor[info.FlavorType] = totalUnitFlavor[info.FlavorType] + info.Flavor / 8
		end
	end
	for k, v in pairs(totalUnitFlavor) do
		totalUnitFlavor[k] = Game.Round(totalUnitFlavor[k])
	end

	--
	-- Critical priorities
	--
	
	-- Negative income
	if player:GetYieldRate(YieldTypes.YIELD_GOLD) < 0 then
		local attempt = 0
		while PurchaseBuildingOfFlavor(player, cities, 0, "FLAVOR_GOLD") and attempt <= Cep.AI_PURCHASE_FLAVOR_MAX_ATTEMPTS do
			attempt = attempt + 1
		end
		if player:IsBudgetGone(0) then return end
	end
	
	-- Severe negative happiness
	if player:GetYieldRate(YieldTypes.YIELD_HAPPINESS_CITY) <= -10 then
		local attempt = 0
		while PurchaseBuildingOfFlavor(player, cities, 0, "FLAVOR_HAPPINESS") and attempt <= Cep.AI_PURCHASE_FLAVOR_MAX_ATTEMPTS do
			attempt = attempt + 1
		end
		if player:IsBudgetGone(0) then return end
	end
	
	-- Found religion
	if player:CanFoundFaith() then
		local leaderInfo = GameInfo.Leaders[player:GetLeaderType()]
		local relFlavor = Game.GetValue("Flavor", {LeaderType=leaderInfo.Type, FlavorType="FLAVOR_RELIGION"}, GameInfo.Leader_Flavors)
		-- Go for faith if religious, or pantheons are available.
		if relFlavor >= 7 or (player:CanCreatePantheon(false) and not player:HasCreatedPantheon()) then
			if relFlavor >= Map.Rand(10, "Found religion") then
				PurchaseBuildingOfFlavor(player, cities, 0, "FLAVOR_RELIGION")
			end
		end
		if player:IsBudgetGone(0) then return end
	end

	-- First workers
	if isEarlyEra then
		PurchaseUnitsOfFlavor(player, cities, 0, "FLAVOR_TILE_IMPROVEMENT", (player:IsMilitaristicLeader() and 1 or 2) - numWorkers)
		if player:IsBudgetGone(0) then return end
	end
	
	-- Settle cities
	if #cities < 3 and isEarlyEra then
		PurchaseUnitsOfFlavor(player, cities, 0, "FLAVOR_EXPANSION", 3 - (#cities + totalUnitFlavor.FLAVOR_EXPANSION))
		if player:IsBudgetGone(0) then
			-- save gold for settlers
			return
		end
	end
	
	--
	-- Moderate priorities
	--

	if player:IsBudgetGone(goldLow) then return end
	
	-- City Defense
	local numBuy = 0
	if isWarAny or player:IsMilitaristicLeader() then
		numBuy = math.min(isWarHuman and 5 or 1, #cities - numMilitaryLand)
		PurchaseUnitsOfFlavor(player, cities, goldMin, "FLAVOR_CITY_DEFENSE", numBuy)
		if player:IsBudgetGone(goldLow) then return end
		
		PurchaseUnitsOfFlavor(player, ports, goldMin,
			"FLAVOR_NAVAL",
			1 + #ports - totalUnitFlavor.FLAVOR_NAVAL,
			{GameInfo.UnitPromotions.PROMOTION_TARGETING_1.ID}
		)
		if player:IsBudgetGone(goldLow) then return end
	end
	
	-- Negative happiness
	if player:GetYieldRate(YieldTypes.YIELD_HAPPINESS_CITY) < 0 then
		local attempt = 0
		while PurchaseBuildingOfFlavor(player, cities, goldMin, "FLAVOR_HAPPINESS") and attempt <= Cep.AI_PURCHASE_FLAVOR_MAX_ATTEMPTS do
			attempt = attempt + 1
		end
		if player:IsBudgetGone(goldLow) then return end
	end
	
	-- Upgrades
	local upgradesPerformed = 0
	if player:IsMilitaristicLeader() or isWarHuman then
		while UpgradeSomeUnit(player, goldMin) and upgradesPerformed < 10 do
			upgradesPerformed = upgradesPerformed + 1
			if player:IsBudgetGone(goldLow) then return end
		end
	end

	-- Workers
	PurchaseUnitsOfFlavor(player, cities, goldMin, "FLAVOR_TILE_IMPROVEMENT",	1 + #cities - numWorkers)
	if player:IsBudgetGone(goldLow) then return end
	
	-- Research
	PurchaseBuildingOfFlavor(player, cities, goldMin, "FLAVOR_SCIENCE")
	if player:IsBudgetGone(goldLow) then return end
	
	-- Monuments
	if #cities <= 2 and isEarlyEra then
		PurchaseBuildingOfFlavor(player, cities, goldMin, "FLAVOR_CULTURE")
		if player:IsBudgetGone(goldLow) then return end
	end
	
	-- Healers
	PurchaseUnitsOfFlavor(player, cities, goldMin,
		"FLAVOR_HEALING",
		1 + numMilitaryLand/5 - numHealers,
		{GameInfo.UnitPromotions.PROMOTION_SCOUTING_1.ID, medicID}
	)
	if player:IsBudgetGone(goldLow) then return end
	
	-- War
	if isWarAny then
		local numBuy = 1
		local maxMilitary = 1 + #cities
		if player:IsMilitaristicLeader() or isWarHuman then
			numBuy = 2 * numBuy
			maxMilitary = Game.Round(2 * maxMilitary)
		end
		
		if isEarlyEra then
			for _, info in ipairs(warUnitFlavorsEarly) do
				if numMilitaryTotal >= maxMilitary then break end
				numMilitaryTotal = numMilitaryTotal + PurchaseUnitsOfFlavor(player, cities, goldMin, info.FlavorType, info.Mult * numBuy - (totalUnitFlavor[flavorType] or 0), info.Promos)
				if player:IsBudgetGone(goldLow) then return end
			end
		else
			for _, info in ipairs(warUnitFlavorsLate) do
				if numMilitaryTotal >= maxMilitary then break end
				numMilitaryTotal = numMilitaryTotal + PurchaseUnitsOfFlavor(player, cities, goldMin, info.FlavorType, info.Mult * numBuy - (totalUnitFlavor[flavorType] or 0), info.Promos)
				if player:IsBudgetGone(goldLow) then return end
			end
		end
	end
	
	-- Scouts
	if isEarlyEra then
		PurchaseUnitsOfFlavor(player, cities, goldMin, "FLAVOR_RECON", 3 - totalUnitFlavor.FLAVOR_RECON, {GameInfo.UnitPromotions.PROMOTION_SCOUTING_1.ID, GameInfo.UnitPromotions.PROMOTION_SCOUTING_2.ID})
		if player:IsBudgetGone(goldLow) then return end
	end
	
	--log:Debug("goldStored=%s goldHigh=%s goldMin=%s", player:GetYieldStored(YieldTypes.YIELD_GOLD), goldHigh, goldMin)
	if player:IsBudgetGone(goldLow) then return end
	
	
	-- 
	-- Low priorities
	-- 
	
	local leaderInfo	= GameInfo.Leaders[player:GetLeaderType()]
	local unitMod		= 0
	local doUnits		= (GetCurrentUnitSupply(player) < GetMaxUnitSupply(player))
	local attempt		= 1
	local flavorWeights	= {}
	local flavorMod		= {
		FLAVOR_DIPLOMACY			= GetCitystateMod(player),
		FLAVOR_NAVAL				= player:HasTech("TECH_SAILING") and 1 or 0,
		FLAVOR_AIR					= player:HasTech("TECH_FLIGHT") and 1 or 0,
		FLAVOR_EXPANSION			= isWarAny and 0 or 5 / #cities,
		FLAVOR_TILE_IMPROVEMENT		= 0, --numWorkers - #cities and 1 or 0,
		FLAVOR_GROWTH				= 1.1 ^ (10 - citiesReverse[1].pop),
		FLAVOR_HAPPINESS			= 1.1 ^ (10 - player:GetYieldRate(YieldTypes.YIELD_HAPPINESS_CITY)),
		FLAVOR_RELIGION				= player:CanFoundFaith() and 2 or 0.5
	}

	if isWarHuman then
		unitMod = 2
	elseif Game.GetAdjustedTurn() > 25 then
		unitMod = Game.GetValue("Flavor", {LeaderType=leaderInfo.Type, FlavorType="FLAVOR_MILITARY_TRAINING"}, GameInfo.Leader_Flavors) / 4
	end
	
	for row in GameInfo.Leader_Flavors{LeaderType = leaderInfo.Type} do
		local flavorType = row.FlavorType
		local flavorValue = row.Flavor
		local priority = GameInfo.Flavors[flavorType].PurchasePriority * (flavorMod[flavorType] or 1)
		if DoFlavorFunction[flavorType] then
			if DoFlavorFunction[flavorType] == PurchaseOneUnitOfFlavor then
				priority = priority * unitMod
			end
			if flavorValue > 0 and priority > 0 then
				if doUnits or DoFlavorFunction[flavorType] ~= PurchaseOneUnitOfFlavor then
					--log:Info("%-15s %20s %3s/%-4s %-20s priority=%-3s", "AIPurchase", player:GetName(), " ", string.gsub(flavorType, "FLAVOR_", ""), flavorValue)
					flavorWeights[flavorType] = flavorValue * priority
				end
			end
		end
	end
	
	local attempt = 0
	while attempt <= 10 do		
		attempt = attempt + 1

		local flavorType = Game.GetRandomWeighted(flavorWeights)
		if flavorType == -1 then
			log:Warn("%15s %20s %3s %-4s no valid flavors for purchase", "AIPurchase", player:GetName(), " ", " ")
			break
		end
		
		local success = false
		if flavorType == "FLAVOR_EXPANSION" then
			success = DoFlavorFunction[flavorType](player, cities, goldMin, flavorType)
		elseif flavorType == "FLAVOR_GROWTH" or flavorType == "FLAVOR_PRODUCTION" then
			success = DoFlavorFunction[flavorType](player, citiesReverse, goldMin, flavorType)
		else
			if not flavorType or not DoFlavorFunction[flavorType] then
				log:Error("DoFlavorFunction[%s] == nil", flavorType)
			else
				success = DoFlavorFunction[flavorType](player, cities, goldMin, flavorType)
			end
		end
		
		if flavorType == "FLAVOR_DIPLOMACY" or not success then
			flavorWeights[flavorType] = 0
		else
			flavorWeights[flavorType] = 0.5 * flavorWeights[flavorType]
		end
		
		if player:IsBudgetGone(goldLow) then return end
	end

	-- No affordable purchase
	log:Info("%-15s %20s %3s of %-4s (+%s/turn) saved", "", player:GetName(), player:GetYieldStored(YieldTypes.YIELD_GOLD) - goldMin, player:GetYieldStored(YieldTypes.YIELD_GOLD), player:GetYieldRate(YieldTypes.YIELD_GOLD))
end

LuaEvents.ActivePlayerTurnEnd_Player.Add(SpendAIGold)

function UpgradeSomeUnit(player, goldMin)
	local playerID = player:GetID()
	for unit in player:Units() do
		local newID = unit:GetUpgradeUnitType()
		if Unit_CanUpgrade(unit, newID, goldMin) then
			log:Info("%-15s %20s %3s/%-4s PAID for upgrading            %s (#%s)", "AIPurchase", player:GetName(), unit:UpgradePrice(newID), player:GetYieldStored(YieldTypes.YIELD_GOLD), unit:GetName(), unit:GetID())
			Unit_Replace(unit, GameInfo.Units[newID].Class)
			return true
		end
	end
	return false
end

function GetCitystateMod(player)
	local playerID		= player:GetID()
	local csWeight		= 0
	local csTotal		= 0
	local breakpoint	= 15 --GameDefines.FRIENDSHIP_THRESHOLD_FRIENDS
	for minorCivID, minorCiv in pairs(Players) do
		if minorCiv:IsAliveCiv() and minorCiv:IsMinorCiv() and player:IsAtPeace(minorCiv) then
			if not minorCiv:IsAllies(playerID) then
				local influence = minorCiv:GetMinorCivFriendshipWithMajor(playerID)
				if influence < breakpoint then
					csWeight = csWeight + 1
				else
					csWeight = csWeight + breakpoint / influence
				end
			end
			csTotal = csTotal + 1
		end
	end
	if csTotal == 0 then
		-- no known citystates
		return 0
	end
	return (csWeight / csTotal)
end


function PurchaseUnitsOfFlavor(player, cities, goldMin, flavorType, quantity, promotions)
	local unitsBought = 0
	while quantity > 0 do
		quantity = quantity - 1
		local unit = PurchaseOneUnitOfFlavor(player, cities, goldMin, flavorType)
		if unit then
			unitsBought = unitsBought + 1
			for _, promoID in ipairs(promotions or {}) do
				unit:SetHasPromotion(promoID, true)
				unit:ChangeLevel(1)
			end
			if player:IsBudgetGone(goldMin) then break end
		else
			break
		end
	end
	return unitsBought
end

function PurchaseOneUnitOfFlavor(player, cities, goldMin, flavorType)
	for _,cityInfo in ipairs(cities) do
		local city = Map_GetCity(cityInfo.id)
		local units = City_GetUnitsOfFlavor(city, flavorType, goldMin)
		if #units > 0 then
			local itemID = Game.GetRandomWeighted(units)
			if itemID ~= -1 then
				local cost = City_GetPurchaseCost(city, YieldTypes.YIELD_GOLD, GameInfo.Units, itemID)
				local unit = player:InitUnitType(itemID, city:Plot(), City_GetUnitExperience(city, itemID))				
				log:Info("%-15s %20s %3s/%-4s PAID for                      %-25s %s", "AIPurchase", player:GetName(), cost, player:GetYieldStored(YieldTypes.YIELD_GOLD), flavorType, GameInfo.Units[itemID].Type)
				player:ChangeYieldStored(YieldTypes.YIELD_GOLD, -1 * cost)
				return unit
			end
		end
	end
	log:Info("%-15s %20s %3s %-4s no affordable unit of         %s", "", player:GetName(), " ", " ", flavorType)
	return false
end

function PurchaseBuildingOfFlavor(player, cities, goldMin, flavorType)
	if not flavorType then
		log:Error("PurchaseBuildingOfFlavor: flavorType=nil!")
		return
	end
	for _,cityInfo in ipairs(cities) do
		local city = Map_GetCity(cityInfo.id)
		local buildings = City_GetBuildingsOfFlavor(city, flavorType, goldMin)
		if #buildings > 0 then
			local itemID = Game.GetRandomWeighted(buildings)
			if itemID ~= -1 then
				local cost = City_GetPurchaseCost(city, YieldTypes.YIELD_GOLD, GameInfo.Buildings, itemID)
				city:SetNumRealBuilding(itemID, 1)	
				log:Info("%-15s %20s %3s/%-4s PAID for                      %-25s %s", "AIPurchase", player:GetName(), cost, player:GetYieldStored(YieldTypes.YIELD_GOLD), flavorType, GameInfo.Buildings[itemID].Type)
				player:ChangeYieldStored(YieldTypes.YIELD_GOLD, -1 * cost)
				return true
			end
		end
	end
	log:Info("%-15s %20s %3s %-4s no affordable building of     %s", "", player:GetName(), " ", " ", flavorType)
	return false
end

function PurchaseInfluence(player, cities, goldMin, flavorType)
	local playerID		= player:GetID()
	local playerTeam	= Teams[player:GetTeam()]
	local leaderInfo	= GameInfo.Leaders[player:GetLeaderType()]
	local capitalPlot	= player:GetCapitalCity():Plot()
	local chosenMinor	= nil
	local chosenWeight	= -1
	local cost			= math.min(Game.Round(player:GetYieldStored(YieldTypes.YIELD_GOLD) - goldMin, -1), 500)

	for minorCivID, minorCiv in pairs(Players) do
		if CSD.GIFT_OPTION == 1 then
			if minorCiv:IsAliveCiv() and minorCiv:IsMinorCiv() and player:IsAtPeace(minorCiv) then

				local minorTeamID		= minorCiv:GetTeam()
				local minorCapitalPlot	= minorCiv:GetCapitalCity():Plot()			
				local influence			= minorCiv:GetMinorCivFriendshipWithMajor(playerID)
				local influenceDiff		= influence - player:GetRivalInfluence(minorCiv)
				local distance			= Map.PlotDistance(capitalPlot:GetX(), capitalPlot:GetY(), minorCapitalPlot:GetX(), minorCapitalPlot:GetY())
				local military			= Game.GetValue("Flavor", {LeaderType=leaderInfo.Type, FlavorType="FLAVOR_MILITARY_TRAINING"}, GameInfo.Leader_Flavors) - 4
				local weight			= 1
			
				-- influence
				if influence < 50 then
					weight = 10 - 0.002 * influence ^ 2
				else
					weight = 280 / influence
				end
			
				-- rival influence difference
				if influenceDiff <= -10 then
					weight = weight * 10
				else
					weight = weight * 100 / (influenceDiff + 20)
				end
			
				-- distance
				weight = weight / math.max(0.01, distance)
			
				-- trait
				if minorCiv:GetMinorCivTrait() == MinorCivTraitTypes.MINOR_CIV_TRAIT_MILITARISTIC then
					weight = weight * math.max(0.01, 1.1 ^ military)
				else
					weight = weight / math.max(0.01, 1.1 ^ military)
				end
			end
				-- personality
			if minorCiv:GetPersonality() == MinorCivPersonalityTypes.MINOR_CIV_PERSONALITY_HOSTILE then
					weight = weight * 0.66
				elseif minorCiv:GetPersonality() == MinorCivPersonalityTypes.MINOR_CIV_PERSONALITY_NEUTRAL then
					weight = weight * 1.5
			end
				
			if weight > chosenWeight then
					chosenMinor = minorCiv
					chosenWeight = weight
				elseif weight == chosenWeight and 1 == Map.Rand(2, "InitUnitFromList") then
					chosenMinor = minorCiv
					chosenWeight = weight
			end
		end
	end
	
	if chosenMinor then
		local influence = chosenMinor:GetFriendshipFromGoldGift(playerID, cost)
		chosenMinor:ChangeMinorCivFriendshipWithMajor(playerID, influence)
		log:Info("%-15s %20s %3s/%-4s PAID for %2s influence with   %-25s", "AIPurchase", player:GetName(), cost, player:GetYieldStored(YieldTypes.YIELD_GOLD), influence, chosenMinor:GetName())
		player:ChangeYieldStored(YieldTypes.YIELD_GOLD, -1 * cost)
		return true
	end
	
	return false
end

function PurchaseAllInfluence(player, goldMin)
	goldMin = goldMin or 100
	local attempts = 0
	local playerID = player:GetID()
	while player:GetYieldStored(YieldTypes.YIELD_GOLD) > goldMin and attempts < 10 do
		local chosenMinor	= nil
		local chosenWeight	= -1
		local cost			= math.min(500, player:GetYieldStored(YieldTypes.YIELD_GOLD))
		for minorCivID, minorCiv in pairs(Players) do
			if CSD.GIFT_OPTION == 1 then
				if minorCiv:IsAliveCiv() and minorCiv:IsMinorCiv() and player:IsAtPeace(minorCiv) then
					local influence		= minorCiv:GetMinorCivFriendshipWithMajor(playerID)
					local influenceDiff	= influence - player:GetRivalInfluence(minorCiv)
					local weight		= 1

					weight = weight * 10 / (influence + 200)
					weight = weight * math.max(50, 100 - math.abs(influenceDiff + 20))
				
					if weight > chosenWeight then
						chosenMinor = minorCiv
						chosenWeight = weight
					elseif weight == chosenWeight and 1 == Map.Rand(2, "InitUnitFromList") then

						chosenMinor = minorCiv
						chosenWeight = weight
					end
				end
			end
		end
		if chosenMinor then
			local influence = chosenMinor:GetFriendshipFromGoldGift(playerID, cost)
			chosenMinor:ChangeMinorCivFriendshipWithMajor(playerID, influence)
			log:Info("%-15s %20s %3s/%-4s PAID for %2s influence with   %-25s(Diplo victory unlocked!)", "AIPurchase", player:GetName(), cost, player:GetYieldStored(YieldTypes.YIELD_GOLD), influence, chosenMinor:GetName())
			player:ChangeYieldStored(YieldTypes.YIELD_GOLD, -1 * cost)
		else
			return
		end
		attempts = attempts + 1
	end
end

DoFlavorFunction = {
	FLAVOR_DIPLOMACY			= PurchaseInfluence,
	FLAVOR_OFFENSE				= PurchaseOneUnitOfFlavor,
	FLAVOR_DEFENSE				= PurchaseOneUnitOfFlavor,
	FLAVOR_SOLDIER				= PurchaseOneUnitOfFlavor,
	FLAVOR_MOBILE				= PurchaseOneUnitOfFlavor,
	FLAVOR_ANTI_MOBILE			= PurchaseOneUnitOfFlavor,
	FLAVOR_RECON				= PurchaseOneUnitOfFlavor,
	FLAVOR_HEALING				= PurchaseOneUnitOfFlavor,
	FLAVOR_PILLAGE				= PurchaseOneUnitOfFlavor,
	FLAVOR_MELEE				= PurchaseOneUnitOfFlavor,
	FLAVOR_RANGED				= PurchaseOneUnitOfFlavor,
	FLAVOR_SIEGE				= PurchaseOneUnitOfFlavor,
	FLAVOR_NAVAL				= PurchaseOneUnitOfFlavor,
	FLAVOR_NAVAL_BOMBARDMENT	= PurchaseOneUnitOfFlavor,
	FLAVOR_NAVAL_RECON			= PurchaseOneUnitOfFlavor,
	FLAVOR_NAVAL_TILE_IMPROVEMENT	= PurchaseOneUnitOfFlavor,
	FLAVOR_AIR					= PurchaseOneUnitOfFlavor,
	FLAVOR_ANTIAIR				= PurchaseOneUnitOfFlavor,
	FLAVOR_NUKE					= PurchaseOneUnitOfFlavor,
	FLAVOR_TILE_IMPROVEMENT		= PurchaseOneUnitOfFlavor,
	FLAVOR_EXPANSION			= PurchaseBuildingOfFlavor,
	FLAVOR_NAVAL_GROWTH			= PurchaseBuildingOfFlavor,
	FLAVOR_WATER_CONNECTION		= PurchaseBuildingOfFlavor,
	FLAVOR_GREAT_PEOPLE			= PurchaseBuildingOfFlavor,
	FLAVOR_CITY_DEFENSE			= PurchaseBuildingOfFlavor,
	FLAVOR_MILITARY_TRAINING	= PurchaseBuildingOfFlavor,
	FLAVOR_GROWTH				= PurchaseBuildingOfFlavor,
	FLAVOR_PRODUCTION			= PurchaseBuildingOfFlavor,
	FLAVOR_GOLD					= PurchaseBuildingOfFlavor,
	FLAVOR_SCIENCE				= PurchaseBuildingOfFlavor,
	FLAVOR_CULTURE				= PurchaseBuildingOfFlavor,
	FLAVOR_HAPPINESS			= PurchaseBuildingOfFlavor,
	FLAVOR_GREAT_PEOPLE			= PurchaseBuildingOfFlavor,
	FLAVOR_INFRASTRUCTURE		= PurchaseBuildingOfFlavor,
	FLAVOR_WONDER				= PurchaseBuildingOfFlavor,
	FLAVOR_SPACESHIP			= PurchaseBuildingOfFlavor,
	FLAVOR_ESPIONAGE			= PurchaseBuildingOfFlavor,
	FLAVOR_RELIGION				= PurchaseBuildingOfFlavor
}

if CSD.GIFT_OPTION == 2 then
	DoFlavorFunction.FLAVOR_DIPLOMACY = PurchaseOneUnitOfFlavor
end

print ("The AI is ready to spend gold.")
 
The option to revert to Sweden's trait has been there for awhile. The dll change to make diplo units not great people is new as of v14.

Alright, thanks for clearing it up. :) I wish I could help you with your LUA problem, but unfortunately I can't. :sad:
 
Top Bottom