Question about Lua

Anachist

Chieftain
Joined
Oct 29, 2010
Messages
37
Hello!

I wish to learn how to mod Lua in Civ V. For sake of testing, I'm trying to create an event, which gives a city a extra population very turn. (I know it's dumb idea, but this is just for testing.)

Is it possible to do and how I should do this?

Something like this?
Code:
function AddPopulation([Do I need something here?])
     [How do I add population to city?]
end

Events.ActivePlayerTurnStart.Add(AddPopulation)

Thanks in advance!

EDIT: Completely forget about SDK forum. :blush: If any moderator happens to see this, please move!
 
Yes, you could do that.
function AddPopulation([Do I need something here?])
For this particular event no, you don't need anything as the event doesn't pass any variables to the function. Some events do though, and you can check this for each event by using Windows Grep to search through the .lua files for instances where Event.[event_name].Add is used and looking at what the function gets.
[How do I add population to city?]
Well first you need to decide if it'll be added to only one city, or all the cities, or a different city each turn. Once you have the city you want you just call

city:SetPopulation(city:GetPopulation() + 1);

Since the documentation is rather lacking right now it all boils down to a lot of searching through the .lua files to see how things are done. That and good old trial and error. Have a look through files like CityList.lua or CityBannerManager.lua to get some ideas on how you can work with cities in the lua code.
 
So, if I want to add the effect to all the cities every turn, does the following work:

Code:
function AddPopulation()
     city:SetPopulation(city:GetPopulation() + 1);
end

Events.ActivePlayerTurnStart.Add(AddPopulation)

Or does it now something else?
 
So, if I want to add the effect to all the cities every turn, does the following work:

Code:
function AddPopulation()
     city:SetPopulation(city:GetPopulation() + 1);
end

Events.ActivePlayerTurnStart.Add(AddPopulation)

Or does it now something else?
No, you'd need that SetPopulation line to be inside a loop iterating through all of the cities. If you used that, city would be undefined, thus nil, and there'd be some sort of error.
 
That's what I thought too, but is the rest right?

How do I define a city, then or how could I create a loop, which goes through all the cities?

(Please bare with me, I'm only a beginner when it comes to Lua or Civ source code modding.)
 
Here's how I'd handle it. First of all, you don't need the Get/Set pairing, you can just use ChangePopulation. (Wherever possible, use the Change functions.)

Spoiler :
function AddPopulation()
for index,pPlayer in pairs (Players) do
if pPlayer ~= nil and pPlayer:IsAlive() and (not pPlayer:IsBarbarian()) then
for pCity in pPlayer:Cities() do
pCity:ChangePopulation(1);
end
end
end
end

Events.ActivePlayerTurnStart.Add(AddPopulation)


That IF check in the middle may or may not be necessary, I don't know what happens if you try to loop over pPlayer:Cities when there are no cities. But it's always safer to check.
 
Thank you very much! :D

Does that code work now?

I have some questions about the code, though.

What does 'pPlayer' mean? What it does in the code?

And how about 'pCity'?

'for index,pPlayer in pairs (Players)' what does this line do? Apart from starting a loop.

I'd be really grateful if someone could answer to those questions.
 
Does that code work now?

Well, it probably would, it's almost a direct copy of some of my own code, which works, but I can't guarantee that the cut-'n-paste went flawlessly.

What does 'pPlayer' mean? What it does in the code?
And how about 'pCity'?
'for index,pPlayer in pairs (Players)' what does this line do? Apart from starting a loop.

It goes like this.

"Players" is a structure. It contains a ton of variables and functions (listed here) that are indexed by each player, meaning it's basically a two-dimensional array (although in one dimension you have a variety of variable types and such, so it's not a perfect analogy). Now, there are basically two ways to access these sorts of structures:

1> Loop over all of them.
Since the Players structure is a multidimensional array, you need to thin it down a bit and use one player's data at a time. For that, you need to use either "pairs" or "ipairs". This takes the Players structure, takes a single "slice" of it (all the entries with player number X) and dumps that into a temporary variable, which I've called "pPlayer". You could call it "Bob" and it'd still work right, the name is purely arbitrary. I tend to use consistent prefixes: p means any player, m means main player, o means other player. So my variables tend to be named pPlayer, pTeam, oPlayer, and so on.

2> If you know the index of the specific entry to use (let's say "pNum"), you can access it directly, with calls like
Players[pNum]:GetTeam();
This is really handy if, for instance, you only want to do things for the Active Player (i.e., the guy sitting at the keyboard) instead of every player.
Functionally, I could say
pPlayer = Players[pNum];
and we'd be right back at #1.

Okay, so now you've established the loop over players, and dumped all the data about a given player into a smaller temporary structure, pPlayer. So now we have to loop over all of the cities that player controls, which is where the pCity part comes in. You see, within the Players structure is a second structure, Cities, with its own contents detailed here. You can't access the cities directly, you HAVE to do it within the context of the Player. Cities, then, contains even more variables and functions. So the easiest thing to do is, again, assign the structure for a given city to a temporary variable, pCity, which we can then access normally. Again the name is arbitrary. And again, you could get around this by looping something like

numCities = pPlayer:GetNumCities();
for iCity = 0, numCities-1 do
pCity = pPlayer:Cities[iCity];
pCity:ChangePopulation(1);
end

but that's not as elegant. One of the nice things about Lua is that you don't need to use loop indices unless you really want to, and you rarely do.

Now, the more complex you want to get, the deeper the loops and conditionals end up stacking. It can get pretty complex, and you want to be careful about having so many loops that it starts to cut into your processor time. (This is really only an issue with loops over the various tiles on a map, usually.)

To demonstrate how complex this can get in practice, I'll attach here a block of code from my own mod. This is a fairly simple Wonder effect, based on the Internet wonder from earlier Civ games, that gives you a technology if X other civs have it.
(The CustomNotification part is based on someone else's work, and this requires a small XML table as well (stored in the temporary variable "row"), but the basic logic should be apparent. "aID" is the active player's ID, set earlier in the Lua file that this was taken from.)

Spoiler :

if( row.StealTechNumCivs > 0) then
-- This is an Internet-style wonder, which gives you technologies if other civs already have them.
for index,mPlayer in pairs( Players ) do
if( mPlayer ~= nil and mPlayer:IsAlive() ) then
if( mPlayer:IsMinorCiv() or mPlayer:IsBarbarian() ) then
else
for mCity in mPlayer:Cities() do
if( mCity:IsHasBuilding(bID) ) then
-- Okay, we have the wonder. Now, go through the techs, and see how many other civs have each.
local mTnum = mPlayer:GetTeam();
local mTeam = Teams[mTnum];
for tech in GameInfo.Technologies() do
if mTeam:IsHasTech(tech.ID) then
-- I have it, do nothing
else
if( tech.Disable ) then
-- We don't want to be able to steal locked techs
else
local nOthers = 0;
for index,oPlayer in pairs( Players ) do
if( oPlayer ~= nil and oPlayer:IsAlive() ) then
local oTnum = oPlayer:GetTeam();
if Teams[oTnum]:IsHasTech(tech.ID) then
nOthers = nOthers + 1;
end
end
end

if ( nOthers >= row.StealTechNumCivs ) then
-- We've met the criteria, go nuts!
mTeam:SetHasTech(tech.ID, true);
if( mPlayer:GetID() == aID ) then
local CityPos = { mCity:GetX(), mCity:GetY() };
toolTip = "You have stolen the [COLOR_POSITIVE_TEXT]"..Locale.ConvertTextKey(tech.Description).."[ENDCOLOR] technology from your rivals through the wonder of [COLOR_POSITIVE_TEXT]"..bName.."[ENDCOLOR].";
LuaEvents.CustomNotification( 1004 --StealTech.
, "[COLOR_POSITIVE_TEXT]Free Technology[ENDCOLOR]", toolTip
, {["location"] = CityPos,["techType"] = tech.ID} );
end
end
end -- If it's not a disabled tech
end -- If I have the tech
end -- loop over Technologies
end -- If this city has this wonder
end -- loop over cities
end -- if I'm a Barbarian or city-state
end -- If I'm a valid player
end -- loop over players
end
 
Also, in order to get any lua file to work properly in-game, you have to make sure that the "import into VFS" property on your lua file is set to true, and that there is a Content declaration for that file (I just call it an InGameUIAddin, with name and description identical to the file name, for lack of more refined knowledge about the intent of that part of the manifest).

This one tripped me up for far longer than it should have. :(
 
Thank you very very much!

I now understand Civ source code modding better and better! :D:D
 
Also, in order to get any lua file to work properly in-game, you have to make sure that the "import into VFS" property on your lua file is set to true,

Not true. The only ones that need VFS=true are Lua files that replace an existing file, like if you've modified AssignStartingPlots.lua to add a new resource. The VFS flag just ensures that your file overrides any other file of the same name being loaded in. If you make a brand-new Lua file, like, say, Spatz_Terraform.lua, then no, you don't have to set it to true.
The important thing is that the Lua file needs to be loaded. Either through the Content tab, or by modifying InGame.xml (although I'd highly recommend doing the Content tab, too many older mods tweaked InGame and the result are a lot of incompatibilities.)
 
Top Bottom