AI-Programming Through Lua

bane_

Howardianism High-Priest
Joined
Nov 27, 2013
Messages
1,559
I'm having a major headache from trying to make the AI build a damn Improvement in foreign lands.
It is a major pain in the ass to get out of the game each 10 minutes to re-build the mod, wait for the game to load again, then build the world, start it all, alt+tab to start firetuner, wait until error, rinse & repeat. :mad:

I decided so, to ask the more experienced modders for advice. Anything really. The latest wall I found to bang my head against was that the mission MoveTo is cancelled if there is an unit in the plot, even if I can move into that plot (no Civillian Traffic Jam) and even if it is invisible. So, I order the unit to go there, and it never completes the mission, because the plot is more than 2-3 turns away and the tile owner will move someone there eventually.
I'm trying a new workaround, but it consists on repeating the order for one single unit, which kind of sucks, making it only possible for the AI to build on at a time. :\


I'll put the relevant code here in the off chance someone with more patience than I have right now will read it and comment on it:

-AIHelper.lua
Spoiler :
Code:
include("DomotaiUtils.lua")

iUnitIDBuilder = 0
pTargettedPlot = 0
bBuildingGarden = true
local ArtisanBuildingCheck = 0
local bCraneAvWorkers = false
local usedCitiesCrane = {}
--local CraneAvWorkers = {}
--local CraneAvWorkersUsed = {}

--[[function IsWorkerUsed(iCraneWorker)
	if CraneAvWorkersUsed then
		for _, v in pairs(CraneAvWorkersUsed) do
			if iCraneWorker == v then
				return true
			end
		end
	end
	return false
end]]

function CraneCheckCityValidity(pCity)
	for _, v in pairs(usedCitiesCrane) do
		if v == pCity:GetID() then
			print("CraneCheckCityValidity: Found city in the table.")
			return false
		end
	end
	print("CraneCheckCityValidity: City absent from the table.")
	return true
end

[B]function FindWorkerCrane(iPlayer, iOtPlayer)
	local pPlayer = Players[iPlayer]
	for pUnit in pPlayer:Units() do
		if (pUnit:GetUnitType() == GameInfoTypes["UNIT_KAKITA_ARTISAN"] or pUnit:GetUnitType() == GameInfoTypes["UNIT_WORKER"]) and pUnit:GetPlot():GetOwner() == iPlayer and not pUnit:IsHasPromotion(GameInfoTypes["PROMOTION_SELECTED_WORKER"]) then
			if pUnit:GetUnitType() == GameInfoTypes["UNIT_KAKITA_ARTISAN"] then
				if pPlayer:IsPlayerHasOpenBorders(iOtPlayer) then
					pUnit:SetHasPromotion(GameInfoTypes["PROMOTION_SELECTED_WORKER"], true)
					local iUnit = pUnit:GetID()
					return iUnit
				else
					return nil
				end
			else
				pUnit:SetHasPromotion(GameInfoTypes["PROMOTION_SELECTED_WORKER"], true)
				local iUnit = pUnit:GetID()
				return iUnit
			end
		end
	end
	return nil
end

function CraneAIHelpUI(iPlayer)
	local pPlayer = Players[iPlayer]
	if pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CRANE_CLAN"] and not pPlayer:IsHuman() then
		if not pPlayer:HasTech(GameInfoTypes["TECH_PHILOSOPHY"]) then
			print("Doesn't know Philosophy yet.")
			return
		end
		local iNumFriends = GetNumberCraneFriends(iPlayer)
		if iNumFriends == 0 then
			print("iNumFriends = 0")
			return
		end
		local iNumTreshold = math.ceil(iNumFriends + ((Game.GetNumCivCities() / CraneCountCivPlayersAlive()) * 1.75))
		if iNumFantasticGardensAI ~= nil then
			if iNumFantasticGardensAI >= iNumTreshold then
				print("Number of Fantastic Gardens is already higher than the treshold.")
				return
			end
		else
			print("iNumFantasticGardensAI == nil")
		end
		if bBuildingGarden then
			LookForSuitableGardens(iUnitIDBuilder, pTargettedPlot, true)
		else
			for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
				print("Checking now if anyone is friend with the Crane.")
				if Players[i]:IsDoF(iPlayer) then
					for pCity in Players[i]:Cities() do
						local iCraneWorker = FindWorkerCrane(iPlayer, i)
						if iCraneWorker == nil then
							return
						end
						local pUnit = pPlayer:GetUnitByID(iCraneWorker)
						local pPlot = Map.GetPlot(pCity:GetX(), pCity:GetY())
						print("Looking for suitable place for a garden.")
						LookForSuitableGardens(iUnit, pPlot, true)
					end
				end
			end
		end
	end
end[/B]

[...]

GameEvents.GetScenarioDiploModifier1.Add(function(iPlayer1, iPlayer2) --From ViceVirtuoso's Fate Zero.
	local pPlayer1 = Players[iPlayer1]
	local pPlayer2 = Players[iPlayer2]
	if pPlayer1:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CRANE_CLAN"] and not pPlayer1:IsHuman() then
		return -30
	elseif pPlayer2:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CRANE_CLAN"] and not pPlayer2:IsHuman() then
		return -30
	else
		return 0
	end
end)

GameEvents.PlayerBuilt.Add(
function(iPlayer, iUnit, iX, iY, iBuild) 
	local pPlayer = Players[iPlayer]
	if iBuild == GameInfoTypes["BUILD_FANTASTIC_GARDENS"] then
		gT.iNumFantasticGardensAI = gT.iNumFantasticGardensAI + 1
		print("Garden built.")
	end
	if not pPlayer:IsHuman() then
		local pUnit = pPlayer:GetUnitByID(iUnit)
		if pUnit:IsHasPromotion(GameInfoTypes["PROMOTION_SELECTED_WORKER"]) then
			pUnit:SetHasPromotion(GameInfoTypes["PROMOTION_SELECTED_WORKER"], false)
		end
	end
end)
GameEvents.PlayerDoTurn.Add(CraneAIHelpArtisanBuilding)
GameEvents.PlayerDoTurn.Add(CraneAIHelpUI)

-DomotaiUtils.lua
Spoiler :
Code:
iCraneClan = 0

function GetCranePlayer()
	for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		Players[i]:SetNumFreePolicies(1)
		Players[i]:SetNumFreePolicies(0)
		if Players[i]:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CRANE_CLAN"] then
			iCraneClan = i
			print("Crane Clan ID: " ..iCraneClan)
		end
	end
end

function GetRandom(iMin, iMax)
	return Map.Rand((iMax + 1) - iMin, "") + iMin
end

function tablerandom(tbl)
	local keys = {}
	for k in pairs(tbl) do
		table.insert(keys, k)
	end
	local randIndex = math.random(#keys)
	local randKey = keys[randIndex]
	return tbl[randKey]
end

function LookForSuitableGardens(iUnit, pPlot, bAppend)
	local iPlotOwner = pPlot:GetOwner()
	local pUnit = Players[iCraneClan]:GetUnitByID(iUnit)
	for idX = -2, 2, 1 do
		for idY = -2, 2, 1 do
			local pTargetPlot = Map.PlotXYWithRangeCheck(pPlot:GetX(), pPlot:GetY(), idX, idY, 2)
			if pTargetPlot and pTargetPlot:GetOwner() == iPlotOwner and pTargetPlot:CanHaveImprovement(GameInfoTypes["IMPROVEMENT_FANTASTIC_GARDENS"], NO_TEAM) then
				local buildType = GameInfoTypes["BUILD_FANTASTIC_GARDENS"]
				if bAppend then
					pUnit:PushMission(MissionTypes.MISSION_MOVE_TO, pTargetPlot:GetX(), pTargetPlot:GetY(), 0, 1, 1, MissionTypes.MISSION_MOVE_TO, pUnit:GetPlot(), pUnit)
					pUnit:PushMission(MissionTypes.MISSION_BUILD, buildType, -1, 0, 1, 1, MissionTypes.MISSION_BUILD, pPlot, pUnit)
					print("Found a good place")
				else
					pUnit:PushMission(MissionTypes.MISSION_BUILD, buildType, -1, 0, 0, 1, MissionTypes.MISSION_BUILD, pPlot, pUnit)
					print("Found a good place")
				end
				return
			end
		end
	end
end

function GetNumberCraneFriends(iCraneClan)
	local iNumFriendsAI = 0
	for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		local pOtPlayer = Players[i]
		if pOtPlayer:IsDoF(iCraneClan) then
			iNumFriendsAI = iNumFriendsAI + 1
			print("GetNumberCraneFriends: Found friend. iNumFriendsAI is now: " ..iNumFriendsAI)
		end
	end
	if iNumFriendsAI >= 1 then
		print("GetNumberCraneFriends: " ..iNumFriendsAI)
		return iNumFriendsAI * 3
	else
		print("GetNumberCraneFriends: No friends.")
		return 0
	end
end

function MakeMedianCulturePerTurn()
	local RawCultureOtherPlayers = 0
	for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		if Players[i]:GetCivilizationType() ~= GameInfoTypes["CIVILIZATION_CRANE_CLAN"] then
			RawCultureOtherPlayers = RawCultureOtherPlayers + (Players[i]:GetTotalJONSCulturePerTurn())
		else
			pCraneClan = Players[i]
		end
	end
	print("Making Median Culture/turn.")
	local iMedianCultureOtherPlayers = math.floor((RawCultureOtherPlayers / CraneCountCivPlayersAlive()) + ((RawCultureOtherPlayers / CraneCountCivPlayersAlive()) * 0.45))
	local iCraneCulture = pCraneClan:GetTotalJONSCulturePerTurn()
	print("iMedianCultureOtherPlayers (with the +45%): " ..iMedianCultureOtherPlayers.. ". iCraneCulture: " ..iCraneCulture)
	if iMedianCultureOtherPlayers <= iCraneCulture then
		print("Crane's Culture/turn is at least 145% of the median from other players.")
		return true
	else
		print("Crane's Culture/turn is NOT enough.")
		return false
	end
end

function CraneCountCivPlayersAlive()
	local CraneCountCivPlayersAlive = 0
	for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		if Players[i]:IsAlive() then
			CraneCountCivPlayersAlive = CraneCountCivPlayersAlive + 1
		end
	end
	print("CraneCountCivPlayersAlive: " ..CraneCountCivPlayersAlive)
	return CraneCountCivPlayersAlive
end
 
alt+tab to start firetuner, wait until error,
Don't do either of these.

alt+tab:
Change Civ5 to a reduced windows mode and leave Fire Tuner running all the time. You can mod while you're running an autoplay or starting/testing last change.

"wait for error":
Many of the issues you post could have been easily tested ahead of time in Fire Tuner. For example in your other thread whoward69 and others are deducing what may have gone wrong with your tablerandom(tbl) function. While that may be an effective way to get an answer, a much faster way is simply to try tablerandom in Fire Tuner:

>tablerandom({"a", "b", "c"})
c
>tablerandom({"a", "b", "c"})
a
>tablerandom({"a", "b", "c"})
a
>tablerandom({})
Runtime Error: ...

You make a function to do something specific. Whenever you make one, do a little test in Fire Tuner to see if it does what you think it does and how it handles various situations. (Do this before the error, unless you are very confident in your programming. Certainly do it if there are problems.)
 
Even if I do that, when I confirm it works, I'll have to leave the game to put that new code into the file, rebuilding and starting it all over again.

Is there some way to create entire functions with GameEvents.*.Add (etc) in FireTuner? I know you can make some codes and all with clicking the right button on an empty space, but will they accept the GameEvents entry?
If so, I can make the mod without any Lua files and just put the codes with Firetuner. That may prevent a few bruises on my forehead...


Any idea how on a better workaround than mine for the MISSION_MOVE_TO stopping all the time?
 
No, I didn't mean you could enter your whole mod by Fire Tuner. What I mean is that you can test individual parts of it (functions) to make sure they work as intended (whether it is your function or a Firaxis object method), or to see if one gives a particular desired effect with a particular input.

You can add/replace global functions in Fire Tuner by entering them as a single line (use ;'s where needed). I do it for very small functions, but it's not practical for larger ones (better to exit/mod/restart). Just remember that this new function doesn't have access to any local variables you have declared in any mod files. It does have access to all globals though.

You can add a GameEvents/Events listener while running Civ5:

GameEvents.BuildFinished.Add(function(...) print("BuildFinished listener: ", ...) end)
 
Top Bottom