1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

A couple more "quick" inquiries

Discussion in 'Civ5 - Creation & Customization' started by AW Arcaeca, Dec 7, 2014.

  1. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,498
    Location:
    Near Portsmouth, UK
    Code:
    GameEvents.CitySoldBuilding.Add(function (iPlayer, iCity, iBuilding) ... end)
    
     
  2. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  3. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,498
    Location:
    Near Portsmouth, UK
    Don't you get a population change event from 1 to 0?
     
  4. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  5. AW Arcaeca

    AW Arcaeca Deus Vult

    Joined:
    Mar 10, 2013
    Messages:
    2,967
    Gender:
    Male
    Location:
    Operation Padlock ground zero
    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.
     
  6. Tarmont

    Tarmont Empress

    Joined:
    Feb 26, 2014
    Messages:
    1,312
    Location:
    Brazil
    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
     
  7. AW Arcaeca

    AW Arcaeca Deus Vult

    Joined:
    Mar 10, 2013
    Messages:
    2,967
    Gender:
    Male
    Location:
    Operation Padlock ground zero
    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.
     
  8. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  9. AW Arcaeca

    AW Arcaeca Deus Vult

    Joined:
    Mar 10, 2013
    Messages:
    2,967
    Gender:
    Male
    Location:
    Operation Padlock ground zero
    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.
     
  10. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  11. AW Arcaeca

    AW Arcaeca Deus Vult

    Joined:
    Mar 10, 2013
    Messages:
    2,967
    Gender:
    Male
    Location:
    Operation Padlock ground zero
    From what I understand, City.GetOwner() -> Player, Player.GetID() -> playerID.
    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
    Not the first time, either. :/
     
  12. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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
    -------

    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.
     
  13. AW Arcaeca

    AW Arcaeca Deus Vult

    Joined:
    Mar 10, 2013
    Messages:
    2,967
    Gender:
    Male
    Location:
    Operation Padlock ground zero
    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.
     
  14. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  15. AW Arcaeca

    AW Arcaeca Deus Vult

    Joined:
    Mar 10, 2013
    Messages:
    2,967
    Gender:
    Male
    Location:
    Operation Padlock ground zero
    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
     
  16. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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
     
  17. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,081
    Location:
    Illinois, USA
    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.
     
  18. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  19. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,081
    Location:
    Illinois, USA
    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"]
     
  20. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     

Share This Page