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

[Lua] Basic Modding Guide for Civ5

Discussion in 'Civ5 - Modding Tutorials & Reference' started by bane_, Sep 11, 2014.

  1. bane_

    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:
    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:

    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.

    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.

    '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.

    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.

    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.

    '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:
    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:
    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.
     
  2. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    (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:
    Even when you see a code written as
    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:

    - 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*
     
  3. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    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:
    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:
    Note that there is no double quote (") around iGold; if you place the quotes, you'll get this instead:
    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.
     
  4. bouncymischa

    bouncymischa Synthetic Genie

    Joined:
    Nov 28, 2012
    Messages:
    1,537
    Location:
    In a bottle
    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.)
     
  5. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,292
    Location:
    Near Portsmouth, UK
    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)
     
  6. LeeS

    LeeS Imperator

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

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    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?
     
  8. Viregel

    Viregel , The Rt. Hon.

    Joined:
    Jun 10, 2013
    Messages:
    1,945
    Gender:
    Male
    Location:
    Kingdom of the Britons
    This seems very well done, I'll read through it when I'm not incredibly tired.

    That said, I need to code this UA:
    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? :)
     
  9. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    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:
     
  10. Viregel

    Viregel , The Rt. Hon.

    Joined:
    Jun 10, 2013
    Messages:
    1,945
    Gender:
    Male
    Location:
    Kingdom of the Britons
    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!
     
  11. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    You can get the original owner object with Players[City:GetOriginalOwner()] and check if it is a minor civ with Player:IsMinorCiv. :D
    No need for buildings.

    [[The City:GetOriginalOwner() gets the ID which is then compared to all IDs from the Players table]]
     
  12. Viregel

    Viregel , The Rt. Hon.

    Joined:
    Jun 10, 2013
    Messages:
    1,945
    Gender:
    Male
    Location:
    Kingdom of the Britons
    The problem is that once it's annexed using the Venetian Merchant ability, it looses its city state status, so that's my concern. Thanks anyway, I'll put that code in and see if it works. :D
     
  13. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    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)
     
  14. Viregel

    Viregel , The Rt. Hon.

    Joined:
    Jun 10, 2013
    Messages:
    1,945
    Gender:
    Male
    Location:
    Kingdom of the Britons
    Ah, I think I get it now. Thank you very much! :D
     
  15. Pazyryk

    Pazyryk Chieftain

    Joined:
    Jun 13, 2008
    Messages:
    3,584
    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).
     
  16. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    Thank you! I will add that.

    I am using your posts to create the Tables topic since... well, since TABLES ARE MY SWORN ENEMY! :lol:
    Credit will be given, of course.
     
  17. FramedArchitect

    FramedArchitect Reluctant Modder

    Joined:
    Mar 25, 2012
    Messages:
    797
    Location:
    Missouri
    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".
     
  18. Pazyryk

    Pazyryk Chieftain

    Joined:
    Jun 13, 2008
    Messages:
    3,584
    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.
     
  19. bane_

    bane_ Howardianism High-Priest

    Joined:
    Nov 27, 2013
    Messages:
    1,559
    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?
     
  20. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,292
    Location:
    Near Portsmouth, UK
    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.
     

Share This Page