A couple more "quick" inquiries

However, this won't catch when a player sells off buildings

Code:
GameEvents.CitySoldBuilding.Add(function (iPlayer, iCity, iBuilding) ... end)
 
Code:
GameEvents.CitySoldBuilding.Add(function (iPlayer, iCity, iBuilding) ... end)

You're right -- I keep forgetting about the handful of new events that were added in the last patch.

That's that dealt with, although I assume since you didn't address the point about catching a City being razed it means there really isn't any real way to do so.
 
didn't address the point about catching a City being razed it means there really isn't any real way to do so.

Don't you get a population change event from 1 to 0?
 
I see a GameEvents.SetPopulation() but that doesn't seem like it would report population changes.. outside of that, I can't see anything obviously related to population (or at least has 'population' in its method name.)

My current solution is to record the City / plots in a table, and scan that table each turn checking for plot owner. If plot owner becomes -1, a city has been razed.

Edit:
Actually, thinking about it, I suppose this is exactly the one you're referring to.
I had mistakenly assumed that it would be used to set a new population for a City, but that was only because I've been looking at the other Lua methods for so long. The GameEvent would be hooked into whenever the game itself changed a City's population by "setting" a new value. So then one should be able to create a function which fires when iOldPop == 1 and iNewPop == 0, then find the City with the x/y coordinates. (I'm assuming at the moment when it fires to change population from 1 to 0 that the City itself is still standing, otherwise finding the City would fail, and you'd just have to find the plot instead.)

Still feels quite a roundabout way of getting here though, instead of, say, GameEvents.CityRazed or something similar.

With all that said, AW, you can probably make use of a combination of SetPopulation(), and optionally CitySoldBuilding() if you want, but you will still need to scan and store the buildings in each City every turn, I imagine.
 
I see a GameEvents.SetPopulation() but that doesn't seem like it would report population changes.. outside of that, I can't see anything obviously related to population (or at least has 'population' in its method name.)
It's a game event, so I would imagine so. If nothing else, as Pazyryk taught us, events can be directly called instead of just subscribed to, and so I assume it works in reverse: events can be subscribed to instead of directly called...

Maybe. I suppose we'll find out.

And that leads us to the last problem: how do we store all the buildings a city has?
I would think something like this would work:
Code:
tBuildingsInRazedCity = {}

...

-- assuming pCity is already defined:
if pCity:IsRazing() then
	[COLOR="Red"]for pBuilding in pCity:Buildings() do[/COLOR]
		tBuildingsInRazedCity[pCity:GetID()] = { pBuilding }
	end
end

...
...If it weren't for the line in red. I couldn't find any function that returns all the cities in a city the same way that Player.Cities() or Player.Units() works. :/ At least I can imagine it exists.
 
Check the database for every building in the game and use that as a "for".
Use the usual ishasbuilding to check if the city has that building.
Add the building to the list.

All my knowledge from lua comes from seeing what other people do so sorry for not having fancy words to explain what I'm trying to say :p
 
Derp, didn't think of that :crazyeye:
Then it should be as simple as this:
Code:
tBuildingsInRazedCity = {}

...

-- assuming pCity is already defined:
if pCity:IsRazing() then
	for row in GameInfo.Buildings() do
		if (pCity:IsHasBuilding(row.Type)) then
			tBuildingsInRazedCity[pCity:GetID()] = { row.Type }
		end
	end
end

...
Now I have yet to construct the full skeleton code.
 
I don't believe a pCity:Buildings() iterator exists, so what you wrote there won't be able to run.

However, I suppose you can make use of one of my debug functions -- I wrote one up to quickly print out a list of all the buildings currently in a City. You can probably adapt it to instead add the building to a table, but as tarcisiocm alluded to, it will involve iterating over the Buildings table.

Code:
for row in GameInfo.Buildings() do
	if pCity:[B]IsHasBuilding[/B](row.ID) then
		print(pCity:GetName() .. " has " .. tostring(pCity:[B][COLOR="Blue"]GetNumBuilding[/COLOR][/B](row.ID)) .. " x " .. row.Type .. " (" .. Locale.ConvertTextKey(row.Description) .. ")")
	end
end

There are two considerations made here, and I've highlighted them in the code:
As I've mentioned elsewhere GetNumRealBuilding() (the usual counterpart to SetNumRealBuilding) will not pick up "free" buildings in the city, but IsHasBuilding() does! This is why my above code uses GetNumBuilding() instead, because my purpose is to print out a list of every building, regardless of it being free or not.

If you are only looking to find 'regular' buildings, you can use GetNumRealBuilding() instead, and it will simply return 0 for all the free buildings.

Either way, simply replace my print() line with code to add the building into your table.
Keep in mind that your table needs a way to distinguish between buildings in different Cities. Also, this will be iBuilding or buildingID, not pBuilding.

As well, pCity:GetID() will give you duplicates -- every single player's Capital will return an ID of 8192, I think.
 
So, here's a very rough skeleton code I whipped up, with the paramters of tBuildingsInRazedCity extended to playerID, cityX, cityY, {Buildings}:
Code:
BuildingX = GameInfoTypes.BuildingX
tBuildingsInRazedCity = {}
-- params of table: playerID, x, y, {Buildings}

GameEvents.SetPopulation.Add(
function(iX, iY, iPopOld, iPopNew)
	local pCity = Map.GetPlot(iX, iY):GetPlotCity()
	if (iPopNew < iPopOld) then -- if the population decreased; next we'll chec kto see if that's because of city razing
		if (pCity:IsRazing()) and (pCity:IsHasBuilding(BuildingX) then
			for row in GameInfo.Buildings() do
				if (pCity:IsHasBuilding(row.Type)) then
					tBuildingsInRazedCity[pCity:GetOwner():GetID(), iX, iY] = { row.Type }
				end
			end
		end
	end
end)

GameEvents.PlayerCityFounded.Add(
function(iPlayer, iX, iY)
	local pPlot = Map.GetPlot(iX, iY)
	local pCity = pPlot:GetPlotCity()
	for a,b,c,d in pairs(tBuildingsInRazedCity) do
	-- a == iPlayer, b == x, c == y, d == {Buildings}
		if (pPlot == Map.GetPlot(b, c)) and (iPlayer == a) then
			pCity:SetNumRealBuilding(d, 1)
		end
	end
end)
Haven't yet coded for sold buildings.
I'm not even sure if pairs is meant to be able to handle more than 2 variables and I'm pretty sure this whole thing is coded poorly, but I'd like to know if anyone has any input on what exactly I'm probably doing wrong here.
 
Your table construction code looks awkward, if not downright incorrect, haha.

You may want to check out this tutorial which may explain some basics of Lua tables.

Lua tables are generally key/value pairs, but your code attempts to stuff the "key" with 3 different items, which won't really work.

There are a few different ways you can structure your table to hold all that information, but you need to decide on a single item to work as the "key" for that particular entry.

You can choose to make the key the city ID, but as I mentioned, this will result in duplicates, so short of using string manipulation to concatenate and subsequently extract the player ID out, it will probably be easier for you to simply rely on the regular numerical indices.

For this, you can rely on two methods -- using the 'syntactic sugar' #tBuildingsInRazedCity to get the length of the table, or using table.insert(). The latter is slower from a processing standpoint, though.

Before that, though:
Code:
pCity:IsHasBuilding(row.Type)
I don't believe this will work. Per the wiki's documentation along with Firaxis' native examples there, IsHasBuilding() should be expecting a building ID -- you keep trying to feed it the building type string, which may cause it to fail. Maybe it won't, I haven't tried it.

Anyway, what I suggest is that you create a new local temporary table within the function called buildingTable or something, then add the building ID to that table. Once the loop is done, you can run this line after the end of the loop:
Code:
tBuildingsInRazedCity[#tBuildingsInRazedCity + 1] = {pCity:GetOwner():GetID(), iX, iY, buildingTable}

(Unrelated: What the heck is pCity:GetOwner():GetID() supposed to do? Aren't they the same thing?)

After that, your function to iterate through this table would be along the lines of:
Code:
	for a,b in pairs(tBuildingsInRazedCity) do
		local iStoredPlayerID = b[1]
		local iStoredX = b[2]
		local iStoredY = b[3]
		if (pPlot == Map.GetPlot(iStoredX, iStoredY)) and (iPlayer == iStoredPlayerID) then
			local buildingTable = b[4]
			for i = 1, #buildingTable do
				pCity:SetNumRealBuilding(buildingTable[i], 1)
			end
		end
	end

Jeez. Some of your assumptions and oddities with your code are making me confused, haha.
 
(Unrelated: What the heck is pCity:GetOwner():GetID() supposed to do? Aren't they the same thing?)
From what I understand, City.GetOwner() -> Player, Player.GetID() -> playerID.
I don't believe this will work. Per the wiki's documentation along with Firaxis' native examples there, IsHasBuilding() should be expecting a building ID -- you keep trying to feed it the building type string, which may cause it to fail. Maybe it won't, I haven't tried it.
If that's the case, then every other time I've used something along the lines of "if the city has this building then" is wrong. But now you have me doubting it too. I guess it could be changed to "if pCity:GetNumBuilding(row.Type) > 0 then". Or change it to row.ID, but I prefer the former because for all those other codes that fed Type into IsHasBuilding(), I'm not sure how to get the ID of that building... isn't it something like GameInfo.BUILDING_X.ID?
I typically don't like dealing IDs for a reason I still haven't nailed down. I honestly don't know why IDs make me shudder more than any other integer variable. :p
Jeez. Some of your assumptions and oddities with your code are making me confused, haha.
Not the first time, either. :/
 
From what I understand, City.GetOwner() -> Player, Player.GetID() -> playerID.

According to the modiki GetOwner() already returns you the player ID.

-------
EDIT:
Since I have Firetuner up here, this is what you get:
Code:
> UI.GetHeadSelectedCity():GetOwner()
0
> UI.GetHeadSelectedCity():GetOwner():GetID()
Runtime Error: _cmdr = {UI.GetHeadSelectedCity():GetOwner():GetID()}:1: attempt to index a number value
-------

If that's the case, then every other time I've used something along the lines of "if the city has this building then" is wrong. But now you have me doubting it too. I guess it could be changed to "if pCity:GetNumBuilding(row.Type) > 0 then". Or change it to row.ID, but I prefer the former because for all those other codes that fed Type into IsHasBuilding(), I'm not sure how to get the ID of that building... isn't it something like GameInfo.BUILDING_X.ID?
I typically don't like dealing IDs for a reason I still haven't nailed down. I honestly don't know why IDs make me shudder more than any other integer variable. :p

What exactly do you think row.Type gives you?
Let's say you had pCity:IsHasBuilding(row.Type) for the Market.

row.Type here would give you:
pCity:IsHasBuilding("BUILDING_MARKET")

Code:
> UI.GetHeadSelectedCity():SetNumRealBuilding(GameInfoTypes.BUILDING_MARKET,1)
1
> UI.GetHeadSelectedCity():IsHasBuilding(GameInfo.Buildings.BUILDING_MARKET.Type)
false

In other words, row.Type is the equivalent of:
GameInfo.Buildings.BUILDING_MARKET.Type -> This returns the string value "BUILDING_MARKET" and I've just tested this for you -- it will return false, because it expects the ID.

Code:
> GameInfo.Buildings.BUILDING_MARKET.Type
BUILDING_MARKET
> type(GameInfo.Buildings.BUILDING_MARKET.Type)
string

You may be confusing the word "Type" from the GameInfoTypes table that we can use to easily grab building ID's.

To get a building's ID, you normally do one of the following:
GameInfo.Buildings.BUILDING_MARKET.ID
GameInfoTypes.BUILDING_MARKET

Both of those will return the building ID for the Market.

However, when you're iterating through the Buildings table, row.ID is exactly what will return the same ID as the above two methods -- those methods simply give you an easier way of accessing this very same buildings table! (Okay, technically GameInfoTypes is its own table, but whatever.)

Code:
> UI.GetHeadSelectedCity():IsHasBuilding(GameInfo.Buildings.BUILDING_MARKET.ID)
true

To clarify: The ID for buildings, technologies, resources, anything in the game that we use GameInfoTypes for is simply the ID of the row that its entry sits within the database.

You can check this very easily by using any sort of SQLite database viewer (I use SQLite manager extension for Firefox) to open up Civ's DebugDatabase in the cache folder) and you can match up all the buildings ID's with the row numbers in the "Buildings" table.

EDIT:
Since Firetuner is up, I posted examples for the market instead.
 
According to the modiki GetOwner() already returns you the player ID.
Yeah, I just found that on the modwiki too. (While finding the returned value of Plot.GetOwner() and finding that it returns a playerID, checked City.GetOwner() and found that it returns the same)

So nothing actually checks pointers. :/ That could've been causing thousands of errors in all of my codes for all I know.
 
Yeah, the modiki should tell you what certain methods return, or use, so you should be able to tell ahead of time whether it returns an ID or an object.

Also, I edited my above post as you posted yours, so you may want to go over it again to see examples of my other points.
 
Hey, this is going on a completely different track (how often do people derail their own threads on purpose...?), but say I wanted to check that a civilization's leader's trait is valid instead directly checking that the civilization type is valid, 99% just to make things needlessly complicated but 1% because hardcoding == bad.

So instead of doing this, which is normal and more understandable:
Code:
if (pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_X) then
Would I do this?
Code:
if (Leader_Traits{LeaderType={GameInfo.Civilization_Leaders{CivilizationType={GameInfo.Civilizations{ID=pPlayer:GetCivilizationType()}}}}} == GameInfoTypes.TRAIT_X) then
 
Certainly, it's possible to match based on Trait Types instead of Civilization Types, if that's what you want to do.

Vice Virtuoso's Civs' Lua matches based on Traits, in case anyone else decides to use Civs' defined Traits for their own purposes.

However, you're making it very confusing for yourself trying to stuff everything into one line like that.
Separating the individual chunks into separate local variables would make your code vastly easier to read and troubleshoot.

Right at the beginning you have this:
Code:
if (Leader_Traits{...

What is Leader_Traits supposed to be? I'm pretty sure it's supposed to be GameInfo.Leader_Traits.

Taken from a random VV Civ mod I have here (Archer, since it's right at the top):
Code:
leaderType = GameInfo.Leaders[pPlayer:GetLeaderType()].Type
leadertraitType = GameInfo.Leader_Traits("LeaderType ='" .. leaderType .. "'")().TraitType
traitType = GameInfo.Traits[leadertraitType]
if traitType.CanUseUnlimitedBladeWorks == 1 or traitType.CanUseUnlimitedBladeWorks == true then
	tWhoIsArcher[i] = true
	bAnyArchers = true
end
 
Beware the mod that adds additional traits to all/some leaders.

My understanding was also that "GameInfo." does not [edit]always[edit] give you the information as it is after mods have made changes to the database, whereas "GameInfoTypes" does do so.
 
Beware the mod that adds additional traits to all/some leaders.

My understanding was also that "GameInfo." does not [edit]always[edit] give you the information as it is after mods have made changes to the database, whereas "GameInfoTypes" does do so.

Where did you see this?

This would be disconcerting if true, but I don't think this should happen, since all of the database changes are made when you enable the mods at the MODS screen, but the Lua doesn't run until you actually generate a map and load a game.

At least in my testing, GameInfo and GameInfoTypes always returned data that matched up with what I was seeing in the database via SQLite Manager.
 
Where did you see this?

This would be disconcerting if true, but I don't think this should happen, since all of the database changes are made when you enable the mods at the MODS screen, but the Lua doesn't run until you actually generate a map and load a game.

At least in my testing, GameInfo and GameInfoTypes always returned data that matched up with what I was seeing in the database via SQLite Manager.
Treat that whole paragraph as a strike-through. I can't find where I (must have mis-re) remembered seeing a comment about issues relating to GameInfo as opposed to GameInfoTypes. I generally only use GameInfo when I want to look through the contents of a game table as in
Code:
for row in GameInfo.Buildings() do
or I want to pull the row info for a specific ID #:
Code:
local freeUnitDetails = GameInfo.Units{ID=iFreeUnit}()
Otherwise I tend to use GameInfoTypes as:
Code:
local iBuildingSomething = GameInfoTypes.BUILDING_SOMETHING

or

local iBuildingSomething = GameInfoTypes["BUILDING_SOMETHING"]
 
Fair enough.

I generally try to avoid the GameInfo table as much as possible, especially since 90% of the time all I need is the ID, and GameInfoTypes gets that for me very quickly.

The biggest reason I generally avoid iterating over it is that it is much slower than going through GameInfoTypes just to get the ID.
 
Top Bottom