Automatically Created Buildings based on Population

Enginseer

Salientia of the Community Patch
Supporter
Joined
Nov 7, 2012
Messages
3,674
Location
Somewhere in California
I'm trying to make a mod that lets civilization cities create buildings based on their population and remove these buildings if the population is under that threshold. So far it doesn't seem to be working any suggestion?
Code:
function OnThePlayerNextTurn (playerID)
local tplayer = Players[playerID]
local tcity = player:GetCityByID(cityID)

if tplayer:IsMinorCiv() 
	then return end

if not tplayer:IsAlive() 
	then return end

for tcity in tplayer:Cities() do
	if (tcity:GetPopulation() >= 5) then
		tCity:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	elseif (tcity:GetPopulation() < 5) then
		tCity:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
		end
	end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)

What it's saying basically is that if a city is greater or equal to 5 population, give a free monument. Otherwise, if it's less than 5 population, make sure that monument will never exist. I'm not trying to do a CityCanConstruct since I want them as a free building.

I, of course hasn't tested it out due to my lack of time, so if anyone could test it if this might actually work; it would be greatly appreciated
 
I've heard that SerialEvents only pop up when only the player can see it rather than affecting the entire map instead.
 
Check the logs, you'll be getting lots of errors about "player" being nil

Code:
local [B][COLOR="Red"]tplayer[/COLOR][/B] = Players[playerID]
local tcity = [B][COLOR="red"]player[/COLOR][/B]:GetCityByID(cityID)

but you don't need the line generating the errors at all, as a) cityID is not a passed parameter, and b) even if it was you overwrite tcity in the for loop.

WRT SerialEvents, most only fire in the tile is visible to the active human player, however a few always fire, and I believe the pop change one is one of the latter type. But the only way to know for sure is to test it.
 
Just tried it, it only worked for cities that I could only see. All well back to the drawing board.

Code:
function OnThePlayerNextTurn (iPlayer)
local tPlayer = GetPlayer()

if tplayer:IsMinorCiv() 
	then return end

if not tplayer:IsAlive() 
	then return end

for v in tplayer:Cities() do
	if (v:GetPopulation() >= 5) then
		v:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	elseif (v:GetPopulation() < 5) then
		v:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
		end
	end	
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)
Basing my code on TutorialChecks.lua, anyone know what v could be substituted for? I can't ctrl-f it.
 
"v" is by general convention used to refer to a "value" in a "key, value" pair stored within an lua table. Firaxis quite commonly pulls info from a nested table where, for example, an individual "key, value" pair has an ID number of a civ or a city or a trade-route as the "key", and the "value" is in fact another table of data.

Specifically what you are doing here:
Code:
for [COLOR="Blue"]v[/COLOR] in tplayer:Cities() do
You are telling lua to run through all the player's cities, performing actions on each individual city as the loop progresses from city to city, and while this loop is running each individual city will be referred to as v so far as the instructions on what to do for each city is concerned.

I can state anything I want for the ? in this sort of construction:
Code:
for [COLOR="Blue"]?[/COLOR] in tplayer:Cities() do
so any of these would work:
Code:
function OnThePlayerNextTurn (iPlayer)
   local tplayer = Players[iPlayer]

   if tplayer:IsMinorCiv() or tplayer:IsBarbarian()
	then return end

   if not tplayer:IsAlive() 
	then return end

   for pCity in tplayer:Cities() do
	if (pCity:GetPopulation() >= 5) then
		pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	elseif (pCity:GetPopulation() < 5) then
		pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
	end
   end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)
or
Code:
function OnThePlayerNextTurn (iPlayer)
   local tplayer = Players[iPlayer]

   if tplayer:IsMinorCiv() or tplayer:IsBarbarian()
	then return end

   if not tplayer:IsAlive() 
	then return end

   for city in tplayer:Cities() do
	if (city:GetPopulation() >= 5) then
		city:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	elseif (city:GetPopulation() < 5) then
		city:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
	end
   end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)
or
Code:
function OnThePlayerNextTurn (iPlayer)
   local tplayer = Players[iPlayer]

   if tplayer:IsMinorCiv() or tplayer:IsBarbarian()
	then return end

   if not tplayer:IsAlive() 
	then return end

   for Cheeseburger in tplayer:Cities() do
	if (Cheeseburger:GetPopulation() >= 5) then
		Cheeseburger:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	elseif (Cheeseburger:GetPopulation() < 5) then
		Cheeseburger:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
	end
   end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)
or
Code:
function OnThePlayerNextTurn (iPlayer)
   local HamSandwiches = Players[iPlayer]

   if HamSandwiches:IsMinorCiv() or HamSandwiches:IsBarbarian()
	then return end

   if not HamSandwiches:IsAlive() 
	then return end

   for Lettuce in HamSandwiches:Cities() do
	if (Lettuce:GetPopulation() >= 5) then
		Lettuce:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	elseif (Lettuce:GetPopulation() < 5) then
		Lettuce:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
	end	
   end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)
Both the way you are referring to a player and the way you are referring to a player's cities are variables that can be defined in pretty much any way you want -- you just have to remember within a single function to always refer to them in the same way once you define them.


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

BTW, GetPlayer() is a function that is defined within that tutorial and will not be available to you in the context of your lua file. You would need to add it to your code in order for your code to work
Code:
function GetPlayer ()
	local iPlayerID = Game.GetActivePlayer();
	if (iPlayerID < 0) then
		dprint("Error - player index not correct");
		return nil;
	end

	if (not Players[iPlayerID]:IsHuman()) then
		return nil;
	end;

	return Players[iPlayerID];
end
but there is yet another problem for you in that dprint is yet another function that only exists within the context of file TutorialChecks.lua, so you would also need
Code:
function dprint(msg)
	if (ShowDebugMessages) then
		print(msg);
	end
end
which leads to yet another issue in that variable ShowDebugMessages is also not defined within your code, so then you also need
Code:
local ShowDebugMessages = Game.IsTutorialLogging();
and then you would need to alter it to read "true" or "false" depending on whether you want your lua script to print debug error messages you place within your code.

But you really don't need any of this because GameEvents.PlayerDoTurn directly gives you the player ID# of each player that is being processed while turn processing is being performed. All you need to do is sort out whether the current player is the player you are interested in.

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

Using the existing Firaxis code is a good method in many cases to see how different things are/can-be done in lua, but you have to look also for all these interlocking functions and variables to see how they work before you know if you can directly copy a piece of code, or are better served using a bit of Firaxis code as a template to understand how to structure your commands to get a similar effect.

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

I would try as this, which also eliminates the uneeded "elseif.....then" portion of the code. The population of the city is either going to be ">= 5" or "else" it is not, so there is no need for an additional "elseif" check:
Code:
function OnThePlayerNextTurn (iPlayer)
   local tplayer = Players[iPlayer]

   if tplayer:IsMinorCiv() or tplayer:IsBarbarian()
	then return end

   if not tplayer:IsAlive() 
	then return end

   for pCity in tplayer:Cities() do
	if (pCity:GetPopulation() >= 5) then
		pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 1)
	else
		pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_MONUMENT"], 0)
	end
   end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)
 
Code:
function OnThePlayerNextTurn (iPlayer)
   local pplayer = Players[iPlayer]
   local venicey = GameInfoTypes.CIVILIZATION_VENICE

   if pplayer:IsMinorCiv() or pplayer:IsBarbarian() then
		return
	end
	
   if not pplayer:IsAlive() then 
		return
	end

	[B]if not (pplayer:GetCivilizationType() == venicey) then 
		print("You're not Venice, right?")[/B]
		for pCity in pplayer:Cities() do
			print("checking the population's cities.")
			if (pCity:IsPuppet() and pCity:GetPopulation() >=25) then 
				print("This city is puppeted and 25+!")
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_4"], 1)
			end
			if (pCity:IsOccupied() and pCity:GetPopulation() >=25) then 
				print("This city is occupied and 25+!")
				pCity:SetNumRealbuilding(GameInfoTypes["BUILDING_POPULATION_4"], 1)
			end
			if (pCity:GetPopulation() >=50) then
				print("This city has 50+")
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_3"], 1)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_2"], 1)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_1"], 1)
			end
			if (pCity:GetPopulation() >=35 and pCity:GetPopulation() <50) then
				print("This city has 35+!")
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_3"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_2"], 1)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_1"], 1)
			end
			if (pCity:GetPopulation() >=20 and pCity:GetPopulation() <35) then
				print("This city has 20+!")
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_3"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_2"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_1"], 1)
			end
			if (pCity:GetPopulation() <20) then
				print("CITY NOT ENOUGH POPULATION, SELF-DESTRUCT")
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_5"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_4"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_3"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_2"], 0)
				pCity:SetNumRealBuilding(GameInfoTypes["BUILDING_POPULATION_1"], 0)
			end
		end
	end
end
GameEvents.PlayerDoTurn.Add(OnThePlayerNextTurn)

So, I've readjusted the code and it seems to work until I added the bolded part.

My bad, I never saved the solution.
 
I've heard that SerialEvents only pop up when only the player can see it rather than affecting the entire map instead.

Try using GameEvents.SetPopulation.Add ? I've been using it in a few mods and it seems to fire reliably for all population changes...
 
Try using GameEvents.SetPopulation.Add ? I've been using it in a few mods and it seems to fire reliably for all population changes...

Yeah my bad I forgot to build the solution, so it never saved.
 
Back
Top Bottom