-- 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.")