Going through all cities

Iceco

Warlord
Joined
Oct 27, 2010
Messages
188
I'm trying to make a mod that gives XP to garrisoned units (with various conditions to it). This is the Lua mod I've attempted.

However, I'm currently stuck trying to make the script go through all players (major and minor civs), so I can then go through their cities and check if there's a garrison in it.

The problem is finding or compiling the list of active (major and minor) civilizations and getting their ID (the ID in the Game, not in GameInfo.Civilizations) and/or that of their cities.
I think I got it working for just the major civs
Code:
Code:
> for civilization in GameInfo.Civilizations() do
>      for city in Players[civilization.ID]:Cities() do
>           print (
>                Players[civilization.ID]:GetID(), 
>                city:GetID()
>           )
>      end
> end

Return:
 WorldView: 0	8192
 WorldView: 1	8192
 WorldView: 2	8192
 WorldView: 3	8192
This returned the same value for every city, but did give the different names when I did city:GetName(), so I'll probably be able to work with this code (and replace the 'print' statement with whatever will be necessary).

For the city states I got this far: (I made it print out a bunch of things to make it more readable and to better find out what could be wrong.)
Code:
Code:
> for minor in GameInfo.MinorCivilizations() do 
>      for city in Players[minor.ID]:Cities() do 
>           print(
>                "CIV:", 
>                Players[minor.ID]:GetID(),  
>                GameInfo.MinorCivilizations[Players[minor.ID]:GetCivilizationType()].Type, 
>                "CITY:", 
>                city:GetID(), 
>                city:GetName()
>           ) 
>      end 
> end

Return:
 WorldView: CIV:	0	MINOR_CIV_VENICE	CITY:	8192	Thebes
 WorldView: CIV:	1	MINOR_CIV_BRUSSELS	CITY:	8192	Sukhothai
 WorldView: CIV:	2	MINOR_CIV_LHASA       	CITY: 	8192	Karakorum
 WorldView: CIV:	3	MINOR_CIV_BELGRADE	CITY:	8192	Rome
 WorldView: CIV:	22	MINOR_CIV_SEOUL       	CITY: 	8192	Hanoi
 WorldView: CIV:	23	MINOR_CIV_SEOUL       	CITY: 	8192	Helsinki
 WorldView: CIV:	24	MINOR_CIV_SEOUL       	CITY: 	8192	Lhasa
 WorldView: CIV:	25	MINOR_CIV_SEOUL       	CITY: 	8192	Geneva
 WorldView: CIV:	26	MINOR_CIV_SEOUL       	CITY: 	8192	Almaty
 WorldView: CIV:	27	MINOR_CIV_SEOUL       	CITY: 	8192	Monaco
There are a number of things wrong with this: firstly, obviously the presence of major civ city names at the end (the correct ones though), secondly the number of entries (there are 8 minor civs in-game) and then pretty much all Type entries. Surprisingly, the 6 listed names of city-state cities are correct, but there are 2 missing: Florence and Vienna.

So what do I need to change? Or maybe it's possible to do it without splitting things up between major and minor civs? Any help would be appreciated.
 
Players gives you civs that aren't in the game, IIRC. On all such iterations, you need to check firstly that the value isn't nil, and in some cases (like players, I think) check they are in the game; I think in that case it's checking they are alive.
 
I knew Players[] listed all civs, but your comment did make me realise I just had to find a way to cut it off after having found all civs, so thank you for the help.

What I did (for anyone interested):
Code:
Code:
> found=0; 
> increment=0; 
> while found<Game:CountCivPlayersAlive() do 
>      if Players[increment]:IsAlive() == true then 
>           found=found+1; 
>           for city in Players[increment]:Cities() do 
>                print(
>                     found, 
>                     GameInfo.Civilizations[Players[increment]:GetCivilizationType()].Type, 
>                     "is alive", 
>                     city:GetName()
>                )
>           end 
>      else 
>           print(
>                found, 
>                "dead civ"
>           ) 
>      end 
> increment = increment+1; 
> end

Return:
 WorldView: 1	CIVILIZATION_EGYPT	is alive	Thebes
 WorldView: 2	CIVILIZATION_AZTEC	is alive	Tenochtitlan
 WorldView: 3	CIVILIZATION_RUSSIA	is alive	Moscow
 WorldView: 4	CIVILIZATION_JAPAN	is alive	Kyoto
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 4	dead civ
 WorldView: 5	CIVILIZATION_MINOR	is alive	Copenhagen
 WorldView: 6	CIVILIZATION_MINOR	is alive	Warsaw
 WorldView: 7	CIVILIZATION_MINOR	is alive	Budapest
 WorldView: 8	CIVILIZATION_MINOR	is alive	Hanoi
 WorldView: 9	CIVILIZATION_MINOR	is alive	Dublin
 WorldView: 10	CIVILIZATION_MINOR	is alive	Geneva
 WorldView: 11	CIVILIZATION_MINOR	is alive	Seoul
 WorldView: 12	CIVILIZATION_MINOR	is alive	Monaco
Thank you, again.
I hope I'll be able to do the rest of the code without help, but I might have to come back to ask more questions.
 
Yeah, looping over Players and making sure they are non-nil and alive is the standard way to do this. I would write it something like this:

Code:
for iPlayer, pPlayer in ipairs(Players) do
  if (pPlayer ~= nil and pPlayer:IsAlive()) then
    print(string.format("Player %d (%s)", iPlayer, pPlayer:GetName()))
    for pCity in pPlayer:Cities() do
      if (pCity ~=nil) then
        print(string.format("  City ID %d (%s)", pCity:GetID(), pCity:GetName()))
        pUnit = pCity:GetGarrisonedUnit()
         if (pUnit ~= nil) then
            print(string.format("    Garrison Unit ID %d (%s)", pUnit:GetID(), pUnit:GetName()))
          else
           print("    No Garrison")
         end
      end
    end
  end
end
 
Hmm, my code did work, but yours is more economic and the ipairs thing is interesting. (And it's damn fancy.)
Is there a way to know what ipairs exist for Civ and what they contain or is this a general Lua thing you just have to conjure up and see what works?

Thank you for the help.
 
Ok, I got the script completed.
It further checks if a building of the barracks class - so barracks or UB equivalent - is in the city and if the unit is a melee one.

Two things though:
  1. Since it uses Event.ActivePlayerTurnEnd, it will be called several time per turn if we ever get mods in multiplayer games. I think I know how to solve it, but am not sure and have no way to test it (since multiplayer mods don't work). Would anyone know?
    Code:
    function TheMod()
         if Game:GetTurn() ~= iLastTurnChecked do
              <rest of script>
         end
         iLastTurnChecked = Game:GetTurn()
    end
    The first time the function is called, iLastTurnChecked would be a nil value, I don't know if that's a problem. Or, indeed, if that condition works at all.
  2. To get the game to restrict the XP gain to melee units, I had to do this:
    Code:
    if 
    	pUnit:GetUnitCombatType() == GameInfo.UnitCombatInfos["UNITCOMBAT_RECON"].ID or
    	pUnit:GetUnitCombatType() == GameInfo.UnitCombatInfos["UNITCOMBAT_MOUNTED"].ID or
    	pUnit:GetUnitCombatType() == GameInfo.UnitCombatInfos["UNITCOMBAT_MELEE"].ID or
    	pUnit:GetUnitCombatType() == GameInfo.UnitCombatInfos["UNITCOMBAT_GUN"].ID or
    	pUnit:GetUnitCombatType() == GameInfo.UnitCombatInfos["UNITCOMBAT_ARMOR"].ID or
    	pUnit:GetUnitCombatType() == GameInfo.UnitCombatInfos["UNITCOMBAT_HELICOPTER"].ID
    then
    	pUnit:ChangeExperience(1)
    end
    Sure, it works, but I get the feeling it could have been a lot shorter.
    I tried
    Code:
    if pUnit:GetUnitCombatType() == (
         GameInfo.UnitCombatInfos["UNITCOMBAT_RECON"].ID or 
         GameInfo.UnitCombatInfos["UNITCOMBAT_MOUNTED"].ID or 
         GameInfo.UnitCombatInfos["UNITCOMBAT_MELEE"].ID or...
    )
    but then only the first (the recon units) got the XP.
Soon I'll make a thread for the mod and I'll make sure to credit you all. I don't know if I should post the link here for you guys, as I guess I don't have to keep bumping this thread unnecessarily. Should I?
 
Since it uses Event.ActivePlayerTurnEnd, it will be called several time per turn if we ever get mods in multiplayer games. I think I know how to solve it, but am not sure and have no way to test it (since multiplayer mods don't work). Would anyone know?
Fit it to gameplay events, rather than UI events, as you can in this case. Check out the GameEvents docs on the wiki, I think you want PlayerDoTurn, which will then run it once per player predictably. Then make it only iterate through the cities of that player, which I believe there is an iterator for on the Player object.
 
That could work and take out the step of iterating over all players, but I think it would be called at the start of that player's turn, which could mean a unit doesn't get a promotion until the turn after that.
I'll try it out to see how it works.

And yes, you're referring to "Players[]:Cities()". It's already in the code.

I released the first version of the mod, since the problem with the multiple checks doesn't happen in the single player games (and we can't do multiplayer). The thread for it is here. You two are credited.
 
Back
Top Bottom