[Lua] Basic Modding Guide for Civ5

bane_

Howardianism High-Priest
Joined
Nov 27, 2013
Messages
1,559
DISCLAIMER​
This is isn't a Guide for Lua programming, but a very lean begginer's guide. It will only be useful if the following statements are both true:
1) You don't understand the basics of Lua already.
2) You don't want to use this tutorial as an entry level for Lua Programming, but for Civ5 Lua Basic Modding.
I'll try to be more layman friendly than precise, because the hardest part in learning to program in Lua for Civ5 is the first step; it is usually a brand new world and no guides to help you discover and conquer new lands.
With that said, if you want to add something, please say so. Thank you. :)


==================================================

Let's start with a question that I got here, which was:
How would i get it to check whether a mountain is in your territory[?]

The answer, the short answer, is this:
Code:
for i = 0, pCity:GetNumCityPlots() - 1, 1 do
	local pPlot = pCity:GetCityIndexPlot(i)
	if pPlot:GetPlotType() == PlotTypes.PLOT_MOUNTAIN then
		--rest of the code here
	end
end
(Well, that didn't really help, didn't it?)

Note, however, that this is just a part of the code, the variable pCity must be defined earlier in that code. If you are iterating through all cities from a player each turn, you can do it like this:

Code:
[COLOR="Red"]Line #01[/COLOR]	function CheckCityMountains(iPlayer)
[COLOR="red"]Line #02[/COLOR]		local pPlayer = Players[iPlayer]
[COLOR="red"]Line #03[/COLOR]		for pCity in pPlayer:Cities() do
[COLOR="red"]Line #04[/COLOR]			for i = 0, pCity:GetNumCityPlots() - 1, 1 do
[COLOR="red"]Line #05[/COLOR]				local pPlot = pCity:GetCityIndexPlot(i)
[COLOR="red"]Line #06[/COLOR]				if pPlot:GetPlotType() == PlotTypes.PLOT_MOUNTAIN then
[COLOR="red"]Line #07[/COLOR]					--rest of the code here
[COLOR="red"]Line #08[/COLOR]				end
[COLOR="red"]Line #09[/COLOR]			end
[COLOR="red"]Line #10[/COLOR]		end
[COLOR="red"]Line #11[/COLOR]	end
[COLOR="red"]Line #12[/COLOR]
[COLOR="red"]Line #13[/COLOR]	GameEvents.PlayerDoTurn.Add(CheckCityMountains)

See, now everything was defined earlier than it was used (rightly so). And look down there! There is a new element we didn't knew before. That is a 'hook', something to tell the game when to run your code. They are found in that first site I told you about, but there are a few more here (I don't know if there is an exaustive list anywhere though, sorry).

Note that the Red-colored line numbers aren't anything you'll ever put into your actual code. They're shown so that it will be easier to understand which individual line is being discussed. (Thank you LeeS) Ok, so, looking at the code line by line independently we get:

Line #01 function CheckCityMountains(iPlayer)
Functions are the heart of this tutorial and modding, almost everything you'll do with Lua is through these named functions.
This is the first line of every function you will design. It follows this instruction:
function Identifier(ARGUMENTS)
- function must always be there.
- The Identifier is the name that will, well, identify your function. It is your choice. Although, they cannot start with a digit, be one of the reserved Lua keywords¹. It is also good to avoid identifiers starting with underscores that also follow with uppercase letters, like _VERSION.
- ARGUMENTS are passed to you through the hook you chose OR when you call the function yourself (not needed right now). In this case, we can see here that the GameEvents merely passes to us the Player ID.

Line #02 local pPlayer = Players[iPlayer]
This is how you define a variable.
('local' OR nothing) Identifier = function
- local means it will only be used by anything that goes below, until it's own 'end' statement. In our case here, pPlayer will be a valid variable until before the very last 'end', which means it'll be usable anywhere in this function.
- Again: The Identifier is the name that will, well, identify your variable. It is your choice. Although, they cannot start with a digit, be one of the reserved Lua keywords¹. It is also good to avoid identifiers starting with underscores that also follow with uppercase letters, like _VERSION.
- I don't know if 'function' is the proper use here, I don't study Lua nor programming, but for the sake of this post, it is now. :) Here you'll tell the game: "Hey game, go get this information for me". In our case, we look at a table called "Players" that has information on which players is which based on their ID, and we have the Player whose turn just began because the hook gave it to us (iPlayer)! So it'll compare to the list it already haves and return the correct pointer to the Player we want.

Line #03 for pCity in pPlayer:Cities() do
'for' is a statement²; you use it when you need a loop, in our case here, you want to iterate through all cities from a player.
This is the model (for Civ5 modding, keep in mind that! The number of identifiers for this kind of 'for' loop is defined by the iterator, so it varies):
for Identifier, [Identifier, Identifier, ...] in function do
- The Identifier is the name that will identify the variable you will call later. You do a loop to use/identify the values of that table, so it is a good thing to name it consistently. Remember of the reserved Lua keywords¹ and earlier exposition about Identifier. This is a variable.
- function will be the method you'll be using to make this iteration. We have pairs() and ipairs() as native iterators to Lua; the Civ5 API has many more: Player:Cities() and Player:Units() are two good examples.

We are defining pCity here through the iterator defined by Fireaxis that goes through all your cities so you can use it later.

Line #04 for i = 0, pCity:GetNumCityPlots() - 1, 1 do
Now, imagined reader, you say: But wait! You already told us about 'for' loops!
Yes, I did, but the prior was to get through an array of items. In this case, we want to iterate through numbers. Here it is:
for Identifier = startValue, endValue, progression do
- The Identifier is the name that will identify the variable you will call later. Remember of the reserved Lua keywords¹ and earlier exposition about Identifier. This is a variable.
- startValue Loops increase incrementaly until they reach a value, they must start from somewhere, this is the somewhere.
- endValue As said above, this loop stops when it reaches a value, the endValue represents that. It is important to note that
- progression is the amount by which the startValue will be increased until it reaches the endValue; this increase happens each time you reach the end of that part of the chunk, in this case, riiiight before the third 'end' statement.

What we see here is a loop through ALL plots that city has. For each plot in pCity (defined in the first loop), do something, it is very straigthforward.
This time we are also creating a variable, in this line's case, i. We use 'i' to indicate an 'integer', some people use it before a variable to indicate that variable will be an integer - this convention is called Hungarian Notation - that identifies a plot.

Line #05 local pPlot = pCity:GetCityIndexPlot(i)
We already talked about defining variables, but let's see how we got this one:
pCity was defined earlier, it will change in each step of the first iteration by which city is the target now.
:GetCityIndexPlot this is a function, but a special one, it is called 'method'² - every method is preceded by a ":". You'll use plenty of methods when programming, they are described in the Lua API References and the other link in the sources that redirects to a post here in CivFanatics.
(i) is the plot you want to get, defined earlier in the second 'for' loop.

Line #06 if pPlot:GetPlotType() == PlotTypes.PLOT_MOUNTAIN then
'if' statements are the most common you will find here. It is a condition to keep the code going, if the 'if' doesn't hold - if it is not true - everything between the if statement and it's 'end' statement do not happen. So, if you, say, define a variable within 'if' and call it from outside, you will get a 'nil' value.
Here we have the following:
"if the plot I defined earlier has a type equal to the one I'm comparing to, then go on with the code"
Remember when we defined pPlayer, imagined reader? I told you that we used a table with all player's IDs to compare, this time we are using another table, PlotTypes (the most common table to use is 'GameInfoTypes' that contains ALL almost all TYPES in the game - PlotTypes, CivilizationTypes, UnitTypes, FeatureTypes, etc.) and when you provide a key, it returns the (numerical) value, so, in our case we are asking:
"Is pPlot's type = 0?"
Why zero? Oh, you combative imagination... Here, take a look.

Ok, with that said, we finished our work. What? We didn't? Oh, right, we didn't!
What about the phrase after the if statement? What about the ends?

In Lua, when you input '--', anything after the doubly-hyfen is a comment - will have no effect in the code. This is a 'short commentary', when you want to comment only a certain part of, or multiple lines of a code, you use --[[]]; like this:

Code:
if pPlot:GetPlotType() [COLOR="DeepSkyBlue"]--[[Fireaxis and Mountains, such a great friendship!]][/COLOR] == PlotTypes.PLOT_MOUNTAIN then
[...]
and
[...]
[COLOR="DeepSkyBlue"]--[[this is a comment
this is part of the same comment!
No effect in the code]][/COLOR]

Now, the 'end' statement serves to end a chunk (this is the actual name for it) of a code. Bringing it to our case we have 4 ends, being:
- The last one, present at Line #11, or the 'higher' one is closing the whole function, as soon as you reach it, the function is over.
- The third one (Line #10) - as already exposed - closes the first loop (Line #03), it is only reached as soon as the 'for' loop reaches a 'nil' value, which means it'll keep looping until ALL your cities were accounted for, then it ends his participation.
- The second one at Line #09 closes the second loop (Line #04), the numerical 'for', it is reached as soon as all plots from the city (defined in the first loop) are accounted for.
- The very first one right at Line #08, the lowest of the ends, is closing the if statement (Line #06). Since it isn't a loop, if statements are read in order until the 'end' statement and really end there.


========================================
I'll expand on this later, but if you need some more assistance
please post below and I'll tackle on it. The more information I
gather from new Lua-programmers (and experienced as well!)
the better this guide will be for everybody and more people
will join us in modding Civ5! :D
========================================​


Sources
Lua Manual 5.1 (the version used in Civ5)
Lua API
More Lua GameEvents (found by bc1)


¹ and / break / do / else / elseif / end / false / for / function / if / in / local / nil / not / or / repeat / return / then / true / until / while -- Remember, though, that Lua is case-sensitive, which means that while 'else' is a reserved keyword 'eLse', 'elsE', 'Else' and 'elSe' aren't; yet, it is good to avoid using these for the sake of simplicity.
² What that means is outside of the scope of this tutorial.
 
(slightly)Advanced Concepts
More About Functions
if, else and elseif
The above keywords are ways to strict your code, making it only able to procceed if the conditions are met. 'else' and 'elseif' are dependents of the 'if' statement, you cannot start a condition with 'else' or 'elseif', for reasons quite obvious for anyone able to read English (ie you all reading this, and my thousand inner voices).
'if' is the most simple of the conditions, it'll make a simple comparation between two values:
if iGold == 30 then --code end
Even when you see a code written as
if bGold then
there is a comparation occurring: "if bGold == true then". The opposite is true, though, you can say that the 'if' statement is always searching for a 'true' result, so, 'if iGold == 30 then' is the same as saying in plain English: "if the following statement is true then, do something". Sure, this is just semantics, but being able to abstract stuff is always better in the road of learning.
The subconditions 'else' and 'elseif' will get in opposition to the 'if' statement, but each in it's own particular way; 'else' will be a contradiction of 'if', while 'elseif' will state a new condition related to the first:
Code:
if iGold == 30 then
	--code
else
	--iGold isn't 30. It can be 31, 29, 300, 3, 30000 or any other number that is not 30.
end

and

Code:
if iGold == 30 then
	--code
elseif iGold > 30 then
	--code if iGold is higher than 30
elseif iGold < 5 then
	--code if iGold is higher than 25
else
	--if none of the above is true, this will run instead
end

This last code uses ALL THREE in a way you can easily differentiate between them; if starts the whole thing, followed by 2 other specific alternative ('elseif') and a generic one ('else').
There is no secret here, but remember their uses and you'll be free like a summer bird! Not the best analogy, as I was told they are terrible coders (one of them worked in Custer's Revenge and Superman 64).


Loops
As I already talked about both kinds of 'for' loops, I'll take briefly about other two that aren't as widely used as those.

while loop
The while loop is used to repeat a function as long as some condition is met. It can create infinite loops if you forget to create some kind of fail-safe. This is the model:
Code:
while [COLOR="Plum"]condition[/COLOR] do --code end
- condition is the only variable we have here, this can be any kind of comparison (like we did with the 'if' above) you want. It'll will keep repeating whatever --code stands for until condition is met no more.
A quick example:

Code:
i = 10
while [COLOR="Plum"]i > 0[/COLOR] do
	pPlayer:ChangeGold(1) --pPlayer was already defined beforehand.
	print(i)
end

Oh! An infinite loop! Since i (10) will always be higher than 0, the code will run forever, way after your resected body becomes dust and the machines will win. Thank you for that, dude.
To avoid dooming the human race, all you need to do is to redefine i within the code with the progression (look at the 'for' examples in the post above) you want the code to occur, so:


Code:
i = 10
while [COLOR="Plum"]i > 0[/COLOR] do
	pPlayer:ChangeGold(1)
	print(i)
	i = i - 1
end

That's it? Yep. That's all you need. But beware, you want to redefine the variable AFTER everything in the code that uses it (like the print() function above it in this last code), otherwise you'll receive the modified i, not the original that prompted the loop.
What this loop does, to explain abstractly, is, it initially checks to see if the condition is met; if it is, procceed from the first subsequent line, changing the player's gold by 1; print i; reduce i by the progression we chose. Now, as it is a loop, it will return and see if the condition is still satisfied riiiight before the 'end' statement, and in being the same, it will all run again (that's the definition of a loop), until i is not higher than 0 anymore.


repeat loop
This is a kind of variation of 'while', think of it as the younger brother that hates people and does never leaves home without a fully-charged Nintendo DS (remember this kid?). In all honesty, I have no idea which came first, but I don't remember seeing many of these in the wild (differently than what happens with 'while' and 'for', this last being the Zubat of occurrences).
It is quite similar:

repeat --code until condition
- Again, condition is the only variable we have here and can be comparison you want. It'll will keep repeating whatever --code stands for until condition is met.

Picking the 'while' loop from above, we can transform it in a 'repeat' like this:

Code:
i = 2
repeat
	pPlayer:ChangeGold(1)
	print(i)
	i = i - 1
until [COLOR="Plum"]i = 0[/COLOR]

Here the code will run until the condition is met, as said above, which is the exact opposite of 'while'!
The major difference of use I can think of, is that this loop will ALWAYS be executed at least once, as we know the code is read from top-left to bottom-right, as it is the usual for writing/reading (damn you cyrillics!).


Local and Global variables




Tables


Other Lua Examples


*In Construction*
 
Tips
Consistency & Simplicity
This is a topic for human-eyes only, you machines that want do somehow enslave the world without opposable thumbs and using Lua, please move along.
Ok, now that we are among carbon-based entities only, I'll explain: Consistency is about form, the code doesn't give a smelly rat's buttocks to how you call your function or your variable, as long as they are defined, the code will go down smoothly. So, why is this important, you may be asking, reason for my schizophrenic meds? The reason is simple:

People will read your code, and you are a person too! Let's see, when you have a large chunk of code with lots of variables and 'if', 'else' and 'elseif', it becomes tiresome to go back and check what exactly are you comparing to that hypothetical number, not only that, but you yourself will eventually get confused as to what is what and why is it there. This is kind of like your missing socks that appear in the kitchen's drawer.

Even with a small function you can confuse the crap out of the reader.
Let's see an example. You are reading through someone's code and you get this:

Code:
function func1(a, b, c)
	if a:At(b:GetX(), b:GetY()) then
		v = c:Plot()
	end
end

Well. That was uninformative... You can get that all three of those are objects and that's it.
This is a very radical and says more about descriptablity then consistency, but let's keep going and the connection will get more clear.
There is no real problem with the above code. You can check where func1() is called and you'll get what those arguments refer to, and, again, the machine will not care how do you call your variables. Another note, it is common to use 'i' (as in integer) for a numerical 'for' loop (remember that one from the first post? It used a variable called only by the letter 'i'), as long as it's short-lived and/or won't be much used, since it'll cause no confusion at all and it keeps your code cleaner.

A few tips for consistency then:

  1. Name your variables with a level of explicitness related to it's genericness.
    pCity is enough to denote the city of a single player, but if there is comparison between two cities, pCity1 and pCity2 may be ok (again, code is blind to your ridiculously named variables), but pOurCity and pTheirCity is much more understandable. bIsStronger doesn't tell me much about what this boolean is talking about, but bIsStrongerUnit is good enough for a check if this is the stronger unit in a certain scope, while bIsStrongerUnitAmongAllOthersWhoseLevelIsEqualOrLower is very descriptive, but there is little need for such a big variable, specially since it has a low genericness, as there aren't many things you can get confused with if you choose, for example, bIsStrongerUnit.
  2. Standardize your Capitalization.
    There are plenty of ways to write words. Some became so common they got names, like the camelCase. If you want to write your variables as iLoveVariables; I_Love_Variables; ilOvevaRiablEs (2nd upper, than each 5th letter is upper-cased) or even i__l_o_v_e__v_a_r_i_a_b_l_e_s it doesn't matter (although I'll never help you revise your code if you use these last two :lol:), as long as you KEEP USING IT in your whole code.
  3. Write in English (UK/US)
    "Well, that's not fair. I'm Spanish/South African/Chilean/Japanese/Montenegrin (don't tell the user crake in this case)/Indonesian!"
    The code itself is in English even though it was created right here in Brazil (hi mom) at the Pontific Catholic University of Rio de Janeiro. The reason is quite simple: English is a worldly language. Deals are broke in English, most people speak English as a second language, almost every school teaches English in the main curriculum, etc. "What if I don't know ANY English?" well, than you're another reason for me taking my meds, imaginary voice, because you wouldn't be able to read this paragraph; all English you need to read a simple sentence is more than what you'll need to code Lua.
    As a side note, I advise to use US English, even though you may like tea or The Regent kind of hat, or.. well, be British. This is one good reason.
  4. Hungarian Notation
    This one is a matter of contention among some programming languages. I like it, very much.
    Remember our codes? Why did I called a variable 'pCity' instead of the simpler 'City'? Well, because a City have an ID (integer), a text name (string), a pointer (object), etc, which you can differentiate by using, respectively, iCity, sCity and pCity. Most people around the forums write with this convention in mind, so it is of good use to do the same and keep things standard. Hungarian Notation, then, is the convention of telling the code-reader which TYPE is the variable; for the purpouse of this tutorial, simply knowing those in use by Fireaxis is enough:
    • 'b' - Boolean (true, false or nil). Ex.: if bIsFortified then -- code end
    • 'i' - Integer (number). Ex.: local iUnitID = pUnit:GetID()
    • 'p' - Pointer (refers to an object). To better understand when you use 'p', any time you want to call a method (like :GetPlotType() or the iterator :Cities()) you must point[i/] in the information's direction. So, pPlot:GetPlotType() you are telling the code that you want pPlot's Plot Type. You can't use the Plot's ID or any other information, it must be it's object.
      [*]'s' - String (text). Ex.: local sCity = pCity:GetName()




Following this tip will make your code more readable to you, your friends and to anyone you're asking for help, and to boot, you won't be helping the Computer Revolution anyway!



Quick Useful Tests
At the start of your journey of modding you'll get in many situations where everything seems right but the code just. doesn't. want. to. work! :mad:
As a first note, even if you don't mod and just use those, it is a good idea to enable logging. Logging is the game's way to provide you with feedback, not only errors, but you can write print() functions to get information on your code that will be written in these logs. The log where stuff related to Lua is written is named, unsurprisingly, Lua.log; so, no way to get the logs mixed up.

"Code loaded" print
This is quite simple but still effective. Plenty of times people have forgotten to add the Lua file to InGameUIAddin (see here a good tutorial by whoward69 on this matter) and go back to their code trying to see where it could go wrong.
Well, the easier way to find out if the wrong/missing file attribute was the issue, a simple 'print("My Mod was loaded")' in your code is enough.
When you go see your Lua.log, this is what you'll see:
[FileName]: My Mod was loaded

Quite simple huh? :)

Variable loaded print statements
The above solution works for basically anything. If you want to see if your function is being called after you already know your mod was loaded properly, you can also ask for a print in the same way as the above, just inserting the print in the first line* of the code, like this:
Code:
function Eat(bHamburger, bPizza, bGrass)
	print("Eat function loaded!")
	if bHamburger then
		print("Call Godfather McDonald, tell him we got Hamburglar.")
	end
	if bPizza then
		print("Kawabanga!")
	end
	if bGrass then
		print("Dammit...")
	end
end
* This way there will be no other possible reason for the code not being called.

This way, you'll get the print as soon as the function is called. If neither of the three cases are true, the log will only show "Eat function loaded"; otherwise, for each that is true, you'll get their print in the order shown. For exclusive prints, see 'else' in the (slightly) Advanced Concepts topic.
But what if you want to see the value of a variable? Take this as an example:
Code:
function RichiesCoffers(iPlayer)
	local pPlayer = Players[iPlayer]
	if pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_RICHIE_RICH"] then
		local iGold = pPlayer:GetGold()
		print(iGold)
	end
end
GameEvents.PlayerDoTurn.Add(RichiesCoffers)
With this function, every turn, you'll get an entry in the log saying this:
[FileName]: *amount of gold*
Note that there is no double quote (") around iGold; if you place the quotes, you'll get this instead:
[FileName]: iGold
Lua understands anything within double quotes to be a string, so, "iGold" becomes merely a text, while iGold is a variable and the code will return return the amount of gold stored in it.

Missing ends, misquotes and other simple mistakes.
Sometimes all you need to make it all go south is a missing end statement. Maybe your indentation is wrong or you typed emd or something.
This is Lua's official testing grounds. All you need to do is input your code (there is a upper limit, but I forgot what it was :blush:) here and run it, the page will refresh very soon and will return an observation, either 'Your program ran successfully.' or 'Your program failed to compile.', when it is the second, you'll get a box explaining where the first error occurred so you can fix it.
Remember that your log will return these errors too, but you'll need to start the game to get them. It is better to prevent the issue than to fix it, right?



The All Powerful All-Seeing "Fire Tuner"
Fire Tuner is Fireaxis' tool to fine-tune (hope you got the joke) a modded game; you can get it at Steam -> Library -> Tools -> "Sid Meier's Civilization V SDK", for $3.99 free. It was probably used by developers too [citation needed]. This is the Fire Tuner.
What it does is, basically, give you a live feed of what you'll later get in your log. Well, is it all? No, not really, it also lets you control many stuff in the ongoing game as well as inputing codes! Thanks to Fire Tuner, Ulixes found out how to turn some graphics on/off while ingame!
For the purpouses of this tutorial, though, it kinda is. This is a very useful tool, but it's true power is unlocked when you couple it with InGame Editor. With those too you'll be able to get all the information you need from your Lua files, when exactly does an error occur (since you can forge situations with IGE), find out why it does; which kind of leftovers a situation creates; discover new information through prints, etc...
Another interesting function of the Fire Tuner is the ability to see how the AI performs with your mod in action! That's right, you can set the game to AutoPlay:
(click for full-size)

Note that the map is fully explored and visible. The game will assign you a Civilization that is not ingame while you're an Observer, but you can't interact with any player whatsoever, not even when being the receiving end of a notification (it'll always say "Unknown Civ/Player" instead of "Carthage" or "Dido").
You can stop the AutoPlay at any moment, in which case you'll return to be the player (or the Observer) selected in the panel right beside the AutoPlay buttons. You can even choose another player then the one you begun with!
A tip I can give you when testing your Civilization is NOT choose to play it in the Civ Selection screen but assign it to an AI player (as they get extra benefits so you can see how they truly play) and mark "Complete Kills" in the advanced setup.
WARNING: Do not bring IGE's popup while you're an observer, as you won't be able to close it anymore.

This is the scope of Fire Tuner exploring we are going to do with this tutorial. There is much more it can do, though, and you're welcome to explore! To expand, this one is a very good tutorial on Fire Tuner.



Searching for Code Examples
The best way to learn Lua is, without doubt, modding. It is a bit rendundant to say that, as most things follow this pattern, as repetition is a good way to remember something. The close second is by reading other modder's work; in regards to Civ5 modding, this is specially good, since the modding community isn't that large and is concentrated here at the CFC, so you can easily get the coder's attention via PM (please, follow the usual rules for sending PMs and avoid issuing one just to ask simple stuff - if you get arrested do not invoke my name, by the way).
Many times you'll get codes with comments, which will ease your way into the working cogs of the code.
The search function on these forums is a good choice if you use generic words and avoid methods (as those usually get bigger than what the search engine allows), but I prefer to use Google, as it allows the use of wildcards. If you want to search in this specific site, just add "site:forums.civfanatics.com" (without quotes) to the end of your search.
 
Oooh! We've definitely been needing a beginner's Lua guide. I debated making one myself off and on, so glad to see someone finally rose to the challenge.

One issue... would checking the plot type really work for mountains? I've always used "pPlot:IsMountain()" to confirm whether or not a plot was Mountains. (I believe a similar function is needed for Hills, too, because they're so weird.)
 
One issue... would checking the plot type really work for mountains? I've always used "pPlot:IsMountain()" to confirm whether or not a plot was Mountains.

No and Yes.

No, as there is no PLOT_MOUNTAIN in the database (there's no Plots table in the standard core database), so GameInfoTypes["PLOT_MOUNTAIN"] will always return nil

Yes, if you use PlotTypes.PLOT_MOUNTAIN

Internally, pPlot:IsMountain() is functionally identical to (pPlot:GetPlotType() == PlotTypes.PLOT_MOUNTAIN) and pPlot:IsHills() is functionally identical to (pPlot:GetPlotType() == PlotTypes.PLOT_HILLS)
 
The only real suggestion I have so far is you might want to do something like:
Spoiler :
Now let's look at a short lua function that allows the lua to check for blah blah blah

Note that the Red-colored line numbers aren't anything you'll ever put into your actual code. They're shown so that it will be easier to understand which individual line is being discussed.
Code:
[COLOR="Red"]Line #01[/COLOR]	function CheckCityMountains(iPlayer)
[COLOR="Red"]Line #02[/COLOR]		local pPlayer = Players[iPlayer]
[COLOR="Red"]Line #03[/COLOR]		for pCity in pPlayer:Cities() do
[COLOR="Red"]Line #04[/COLOR]			for i = 0, pCity:GetNumCityPlots() - 1, 1 do
[COLOR="Red"]Line #05[/COLOR]				local pPlot = pCity:GetCityIndexPlot(i)
[COLOR="Red"]Line #06[/COLOR]				if pPlot:GetPlotType() == GameInfoTypes["PLOT_MOUNTAIN"] then
[COLOR="Red"]Line #07[/COLOR]					--rest of the code here
[COLOR="Red"]Line #08[/COLOR]				end
[COLOR="Red"]Line #09[/COLOR]			end
[COLOR="Red"]Line #10[/COLOR]		end
[COLOR="Red"]Line #11[/COLOR]	end

[COLOR="Red"]Line #12[/COLOR]	GameEvents.PlayerDoTurn.Add(CheckCityMountains)
And then refer to those line numbers as you demonstrate what each line in the lua-code is for, or is doing. So you would have:
Ok, so, looking at the code line by line independently we get:
Line #01
function CheckCityMountains(iPlayer)

Functions....blah blah blah

Line #02
local pPlayer = Players[iPlayer]

This is how you define a variable.....blah blah blah
 
Internally, pPlot:IsMountain() is functionally identical to (pPlot:GetPlotType() == PlotTypes.PLOT_MOUNTAIN)

I remember having some kind of problem with IsMountain(), is there any known issue with this function?

Thank you guys! I'll make the fixes to the OP.

EDIT:
Does anyone knows which Types the GameInfoTypes doesn't cover?
 
This seems very well done, I'll read through it when I'm not incredibly tired.

That said, I need to code this UA:
Upon acquiring a City-State, gain a unique World Wonder in that city depending on the type conquered. +3 Culture from World Wonders.

I have the wonders done in the XML, as well as the code (stolen from JFD) on assigning the wonder and the conquest detection, though I have no idea how to link it to the city-state type. Don't suppose I could ask for the logic there, please? :)
 
Ah! Absolutely.
The City-states have 'TraitTypes' which you can access through their own individual tables. Give me a few minutes to search for the correct column and I'll get back to you. :)

Ok, found it (\Assets\DLC\Expansion2\Gameplay\XML\Civilizations\CIV5MinorCivilizations.xml).
The column is the <MinorCivTrait> in the <MinorCivilizations> table.

An example of how to access this is (UNTESTED):
Code:
--with pMinorCiv (same as a pPlayer but indicating the minor civ) being defined elsewhere:
local sTrait = GameInfo.MinorCivilizations[pMinorCiv:GetMinorCivType()].MinorCivTrait 
if sTrait == MinorCivTraitTypes["MINOR_CIV_TRAIT_CULTURED"] then
	--do code
end

One way to define pMinorCiv (in your specific case) is by the iOldOwner returned by the OnCityCapture hook.

EDIT:
Ok, found an easier way: Player:GetMinorCivTrait() :blush:
 
Thank you very much! That should work fine; again, thank you. :D I'm now faced with the problem of assigning every city state with a building to mark it as a city state (given the unit attribute of taking over a CS removes its CS status). I don't suppose you could run me through the logic of assigning the building, then when it's taken over by the civ, it checks for the building and assigns a wonder based on the type? (At least, that's the only way I can think of doing it.) Thanks!
 
I'm not sure I'm following; it does become your city (and thus is not from another player anymore), but it's original owner is still a Minor Civ and you can get it with the method I posted above.
That will identify if the City was a City-State or not!

EDIT: You're welcome. :D
(I edited to avoid a post just for that haha)
 
Just a hit-and-run post:

It's just a minor point, but new Lua coders might want to note that these two things:
Code:
MinorCivTraitTypes["MINOR_CIV_TRAIT_CULTURED"]
MinorCivTraitTypes.MINOR_CIV_TRAIT_CULTURED
...are exactly equivalent in Lua. The second "dot" format is a Lua shortcut that can be used when a table key happens to be a string without spaces or other control characters. In this case, the key is "MINOR_CIV_TRAIT_CULTURED" which meets that requirement. The bracket format is more general and works for any key. But keep in mind that the dot makes Lua assume it is a string key, so t.2 is really equivalent to t["2"], which is not at all the same as t[2]. I just bring this up because there is some jumping between the "dot" and "bracket" formats in a post above, which is fine to do but might be confusing for new coders.

A more substantive comment is that the following two lines may or may not be the same:
Code:
MinorCivTraitTypes.MINOR_CIV_TRAIT_CULTURED
GameInfoTypes.MINOR_CIV_TRAIT_CULTURED
They will be equivalent if you have not modified the MinorCivTraits DB table. However, if you have added to or deleted from the MinorCivTraits table, the first will be wrong for any cases where the database row is new or its ID has been changed. This is true for all of these tables like ImprovementTypes, FeatureTypes, and so on. However, GameInfoTypes is generated from the modified DB tables, so it correctly reflects the modded table. So you would want to use GameInfoTypes if, for example, you had added new minor civ traits in your mod (or you wanted it to be compatible with another mod that did this).
 
This post reminds me of how difficult it is to explain lua to beginners like me with absolutely no knowledge of programming. This is a good starting point, but I would also recommend looking at the Firaxis scenarios. Most of the scenario game changes are executed through paired lua/xml files called "TurnsRemaining".
 
I'll just take this chance to advertise my strict.lua mod component (follow link in my sig). The example I give to demonstrate it's function is this:
Code:
local bWrapX = Map.IsWrapX()
function AdjacentPlotIterator(plot)
	[COLOR="Silver"]< snip >[/COLOR]
	if bXWrap then
		[COLOR="Silver"]< snip >[/COLOR]
	end
end
Lua's normal behavior here is to not give you an error. The misspelled bXWrap is evaluated as nil, which behaves exactly like false in that line. So there's never an error. But the function doesn't do what it's supposed to do in the edge case (literally an edge case here). It's far far worse to have a code mistake like this that doesn't give a Runtime Error than one that does, since you don't know that there is a mistake that needs to be fixed.

Note that this would never be a problem in C++ or any language that has strict declaration rules, but scripting languages like Lua let you skip this. That's nice sometimes when your scripts are very short (which is the whole point of scripting languages), but leads to a pile-up in errors in long code. What strict.lua does is give you most of the benefit of strict declaration rules.

In the above example, strict.lua prints an error: 'Attempt to access undeclared global "bXWrap" at line ___ in function ___'. It's easy to use: just add file to mod (with vfs=true) and include it with the statement include("strict.lua"). After that, any access or assignments to undeclared globals (which are usually due to misspelling) are caught as an error. With all default settings, a global is "declared" by assigning anything to it outside of a function. You can play around with settings to make it work differently or get additional functionality -- for example, the ability to make specific tables "strict" so that access or assignment to undeclared keys causes an error.

When I first implemented this in Éa I cleaned up about 50 different coding mistakes. Of course, Éa is well over 20000 lines of Lua so maybe that's not surprising. But I think it could be useful for anyone that codes more than 100 lines of Lua.
 
This post reminds me of how difficult it is to explain lua to beginners like me with absolutely no knowledge of programming. This is a good starting point, but I would also recommend looking at the Firaxis scenarios. Most of the scenario game changes are executed through paired lua/xml files called "TurnsRemaining".

Hey!
If you're having any difficulties, why don't you say it so I can help you and then we can make this guide better for another new modders?
 
Does anyone knows which Types the GameInfoTypes doesn't cover?

Short answer: Anything that doesn't have a database table.

Longer answer: GameInfoTypes is created from all the tables that have both an ID and a Type column - GameInfoTypes.TABLE_TYPE is just an optimised short-cut for GameInfo.TABLEs["TABLE_TYPE"].ID As there is no Plots table in the standard game files, hence there are no PLOT_XYZ entries in GamInfoTypes.
 
Top Bottom