• Our friends from AlphaCentauri2.info are in need of technical assistance. If you have experience with the LAMP stack and some hours to spare, please help them out and post here.

[TOTPP] Prof. Garfield's Lua Code Thread

@Prof. Garfield

Hi there!
I am trying to create a straightforward event that generates gold per turn if a civ has a certain tech.

Code:
function triggerEvents.onTurn(turn)
    context[getContext()]["onTurn"](turn)
    universal["onTurn"](turn)
    delay.doOnTurn(turn)
    legacy.onTurnEventsAndMaintenance(turn)

--If the Soviets have the "Communism" tech, the event will generate 1000 gold per turn...
if civ.hasTech(object.tRussians, object.aCommunism)
    object.tRussians.money = object.tRussians.money + 1000
end
end

I am assuming this goes in the "onTurn" section of the trigger events.
Problem is, the event is creating huge amounts of gold.
16,000 instead of 1000...

Am I doing something wildly wrong here?
 
I am assuming this goes in the "onTurn" section of the trigger events.
Problem is, the event is creating huge amounts of gold.
16,000 instead of 1000...

Am I doing something wildly wrong here?
Your code looks correct. Try commenting out the money change, and see if you're still getting most of the gold. Perhaps you have a legacy event or an event in a separate file which is causing the money to change.
 
@Prof. Garfield
You'll scoff at how simple this one is - :)
Using the text events - EG:
Code:
civ.ui.text("blah, blah")

How do I make the text boxes centre alligned, instead of the default left-alligned text?

As you know, In macro this was achieved by using "^"^:
Code:
^
^^ In this war, there are no second prizes...Good luck!
^

How do I use the same functionality in lua?

Thanks in advance. :)
 
To pile onto CurtSibling's question, I was wondering how to get an image to show in a text box, with a title to the text box. I tried playing with code from other scenarios, and tinkering with the text.lua file, to no avail.

Going through the tutorial again, I had a question about capturing units. I managed to get this to work well, and with a civ.ui.text coming up informing the player. However, I was wanting to put the following in place:
- Textbox only shows up for the player, and not for AI (e.g., you won't see it if the AI does it, only when you do it)
- Capture only happens when the unit was the defender (you have to attack the unit for it to defect)

And, if possible, what I really wanted:
- A textbox that comes up, saying the unit has defected, etc., but gives the player two choices: "keep" the unit, or "free" the unit -- or something like that (and reminding the player if they keep, they'll have to pay support)

EDIT: Got the textbox to come up with an image and dialog choices. But still can't figure out to make it so it doesn't show up even when AI attack that unit. To be clear, I want the effect to still happen for the AI, but without the player seeing a popup. And I only want it to happen when someone attacks, not defends. Puzzled! I'm also trying to figure out code to increase population of nearest city?
 
Last edited:
@Prof. Garfield
You'll scoff at how simple this one is - :)
Using the text events - EG:
Code:
civ.ui.text("blah, blah")
How do I make the text boxes centre alligned, instead of the default left-alligned text?

As you know, In macro this was achieved by using "^"^:
Code:
^
^^ In this war, there are no second prizes...Good luck!
^
How do I use the same functionality in lua?

Thanks in advance. :)
I never knew that ^^ would centre the line of text.

If you want to use civ.ui.text, you split each line into a separate argument, like this:
Code:
civ.ui.text("^","^^ Some Text","^")
The text module will separate automatically on newline characters ("\n")
Code:
text.simple("^\n^^ Some Text\n^")
(If you want to try this in the Lua Console, you can use console.text.simple("^\n^^ Some Text\n^").)
To pile onto CurtSibling's question, I was wondering how to get an image to show in a text box, with a title to the text box. I tried playing with code from other scenarios, and tinkering with the text.lua file, to no avail.
The text module has you covered (at the moment you still have to look at the comments in that file for documentation).

Code:
text.simple(message,title,imageObject)
The title is "" if it is omitted, and the imageObject is nil if it is omitted.

Going through the tutorial again, I had a question about capturing units. I managed to get this to work well, and with a civ.ui.text coming up informing the player. However, I was wanting to put the following in place:
- Textbox only shows up for the player, and not for AI (e.g., you won't see it if the AI does it, only when you do it)
- Capture only happens when the unit was the defender (you have to attack the unit for it to defect)
If you want the text box to happen only for the human player, you'd add an if statement checking if the tribe matches the player tribe:
Code:
if winner.owner == civ.getPlayerTribe() then
    text.simple(message,messageTitle,image)
The Lua Template version of onUnitKilled provides you with easy access to the "aggressor" and "victim" units in combat, so you can make that check:
Code:
if loser == victim then
    --capture code
end

And, if possible, what I really wanted:
- A textbox that comes up, saying the unit has defected, etc., but gives the player two choices: "keep" the unit, or "free" the unit -- or something like that (and reminding the player if they keep, they'll have to pay support)
For this you can use text.menu. Here's the documentation from text.lua
Code:
--  Menu Table Specification
--  menuTable[i]=optionName is the i'th option that will appear in the menu
--  and menu will return i if option is chosen
--  optionName will be a string
--  start counting at 1, can skip numbers (incl. 1), but don't have other entries in table

-- text.menu(menuTable,menuText,menuTitle="",canCancel=false,menuPage=1)-->integer,integer
-- text.menu(menuTable,menuText,menuTitle="",canCancel=false,imageInfo=nil,dimensions={width=nil,height=nil},menuPage=1) --> integer,integer
--  (last 4 arguments can be in any order or omitted)
-- returns the key of the menu table of the option chosen, second parameter returns menu page of selection
-- menuText is displayed above the options
-- menuTitle is the title of the menu
-- canCancel if true, offers a 'cancel' option on each page, returns 0 if selected
--           if false, there is no cancel option
-- menuPage is the "page" of the menu that is to be opened
-- imageInfo a way to get an image, either key for the imageTable, an imageObject,
--          or a table of arguments for civ.ui.loadImage
--  Arguments 4,5,6,7 (canCancel,imageInfo,menuPage,dimensions) can be in any order; the code will
--  figure out which is which
-- dimensions is a way to specify the size of the menu text box
--      {width=integerOrNil, height=integerOrNil}
--      can't have 1 as a key (that will be interpreted as an imageInfo)

The menuTable would look like this:
Code:
menuTable = {[1] = "Free Captured Unit",[2]="Enslave Captured Unit"}

Let me know if you need any more help.
 
Excellent, Prof., thank you! I managed to get the dialog box to load, and only for the human player!
I still would like to create an effect for the AI, though. Is there a way to also specify all other AI tribes?
Can the AI "select" from a textbox logically (should I "show" the textbox for them in their imaginary world), or is it best to assign a simpler value/benefit for them?

Hope this makes sense.
 
I still would like to create an effect for the AI, though. Is there a way to also specify all other AI tribes?
I'm not sure what you mean here. You can check tribe.isHuman to determine if a tribe is controlled by a human player.
Can the AI "select" from a textbox logically (should I "show" the textbox for them in their imaginary world), or is it best to assign a simpler value/benefit for them?
Have a look in the text module for the "menuRecord". In my file, documentation starts at line 2251, though it might be slightly different for you. There are some examples starting around line 2659 (-- This is some example menuRecord code). You can assign an 'autoChoice' function which determines whether the menu is shown to the player, or if a choice is made automatically based on some weights.

Alternatively, since you have a really simple choice to make, you could do something like this
Code:
local choice = nil
if winner.owner.isHuman then
    local menuTable = {[1]="Choice 1",[2]="Choice 2"}
    choice = text.menu(menuTable,menuText,menuTitle)
else
    choice = math.random(1,2) -- or however you wish to choose between options 1 and 2
end
if choice == 1 then

else

end
 
Thank you! OK, one step at a time: I got this to work, but only if I specify what the AI gets. So basically, the human gets 2 choices, the AI will always get only one thing. With your tips, I made it so the dialog only appears for the human player.

Is there a simple way to say "or," as in, give the system 2 things and have it choose at random which one it gets? The "choice = math.random(1,2)" did not present an error, but it also did not do anything. So I want, when the AI attacks, for the system to roll a die, and just choose one of two things. Or is this too complicated?
 
Is there a simple way to say "or," as in, give the system 2 things and have it choose at random which one it gets? The "choice = math.random(1,2)" did not present an error, but it also did not do anything. So I want, when the AI attacks, for the system to roll a die, and just choose one of two things. Or is this too complicated?
math.random(a,b) returns a randomly chosen integer between a and b (inclusive). So my code made the AI choose choice 1 and choice 2 each with 50% probability. However, I didn't make it do anything for either choice. Can you post the code you ended up trying, so I can spot the error?
 
Sure! Here you go. Thanks for this. This code works, but only gives AI the Gutian Riders unit, wherein I'd like it to have a chance of also getting a Laborers unit.

Speaking of having the computer randomly choose between 2 things: if in my game, a settler is attacked, and there is no choice, but I want the computer to choose between 2 different types of units that could be generated as a result, how would I do that?

Settler creates Laborers or Settler creates Armed Mob, for example... but these being chosen by the system at random.

Code:
if loser.type == unitAliases.GutianRiders and loser.owner == tribeAliases.Barbarians and loser == victim then
        local choice = nil
        if winner.owner.isHuman then
            local dialog = civ.ui.createDialog()
                dialog.title = "Tribe Subjugated!"
                dialog.width = 500
                dialog:addImage(BarbarianCapture)
            local multiLineText = "Survivors of this battle have been pressed into service. How should we put them to use?"
                text.addMultiLineTextToDialog(multiLineText,dialog)
                    dialog:addOption("Add them to our ranks! We could use more soldiers.", 1)
                    dialog:addOption("Our empire needs more hands. Turn them into workers!", 2)
                local choice = dialog:show()
                if choice == 1 then
                local newGutianRiders = civ.createUnit(unitAliases.GutianRiders, winner.owner, winner.location)
                    newGutianRiders.moveSpent = 255
                    newGutianRiders.damage = newGutianRiders.type.hitpoints / 2
                elseif choice == 2 then
                local newLaborers = civ.createUnit(unitAliases.Laborers, winner.owner, winner.location)
                    newLaborers.moveSpent = 255
                    newLaborers.damage = newLaborers.type.hitpoints / 2
                end
        else
            local newGutianRiders = civ.createUnit(unitAliases.GutianRiders, winner.owner, winner.location)
                    newGutianRiders.moveSpent = 255
                    newGutianRiders.damage = newGutianRiders.type.hitpoints / 2
        end
    end
end
 
Sure! Here you go. Thanks for this. This code works, but only gives AI the Gutian Riders unit, wherein I'd like it to have a chance of also getting a Laborers unit.
Rearrange your code like this:
Code:
if loser.type == unitAliases.GutianRiders and loser.owner == tribeAliases.Barbarians and loser == victim then
    local choice = nil
    if winner.owner.isHuman then
        local dialog = civ.ui.createDialog()
        dialog.title = "Tribe Subjugated!"
        dialog.width = 500
        dialog:addImage(BarbarianCapture)
        local multiLineText = "Survivors of this battle have been pressed into service. How should we put them to use?"
        text.addMultiLineTextToDialog(multiLineText,dialog)
        dialog:addOption("Add them to our ranks! We could use more soldiers.", 1)
        dialog:addOption("Our empire needs more hands. Turn them into workers!", 2)
        local choice = dialog:show()
    else
        choice = math.random(1,2)
    end
    if choice == 1 then
        local newGutianRiders = civ.createUnit(unitAliases.GutianRiders, winner.owner, winner.location)
        newGutianRiders.moveSpent = 255
        newGutianRiders.damage = newGutianRiders.type.hitpoints / 2
    elseif choice == 2 then
        local newLaborers = civ.createUnit(unitAliases.Laborers, winner.owner, winner.location)
        newLaborers.moveSpent = 255
        newLaborers.damage = newLaborers.type.hitpoints / 2
    end
end
If the winner is AI controlled, it chooses between one or the other at random. If you want the choice to be systematic (or have some weighted randomness), you would change the line
Code:
choice = math.random(1,2)

Speaking of having the computer randomly choose between 2 things: if in my game, a settler is attacked, and there is no choice, but I want the computer to choose between 2 different types of units that could be generated as a result, how would I do that?

Settler creates Laborers or Settler creates Armed Mob, for example... but these being chosen by the system at random.

You want something like this:
Code:
if loser.type == settlers --[[and all other conditions]] then
    if math.random() < 0.33 then -- 1/3 chance to produce laborers
        local newLaborers = civ.createUnit(unitAliases.Laborers, winner.owner, winner.location)
        newLaborers.moveSpent = 255
        newLaborers.damage = newLaborers.type.hitpoints / 2
    else
        local newArmedMob = civ.createUnit(unitAliases.ArmedMob,winner.owner, winner.location)
        newArmedMob.moveSpent = 255
        newArmedMob.damage = newArmedMob.type.hitpoints / 2
    end
end
If math.random() is called, it generates a random number (fraction) between 0 and 1. If math.random(a) is called, it generates a random integer between 1 and a (inclusive). If math.random(a,b) is called, it generates a random integer between a and b (inclusive).
 
Thanks Prof.

I tried this code. So, the AI choice works! The AI is choosing between the two. This is incredible!
However, now, as a player, when I select either option, nothing happens. There are no errors -- the unit just doesn't spawn.

Any ideas?
 
Thanks Prof.

I tried this code. So, the AI choice works! The AI is choosing between the two. This is incredible!
However, now, as a player, when I select either option, nothing happens. There are no errors -- the unit just doesn't spawn.

Any ideas?
Oops, this line
Code:
local choice = dialog:show()
should be
Code:
choice = dialog:show()

To understand this mistake, we need to understand variable "scope". Basically, when you define a variable, the scope determines what other parts of the program can access it. If you define a variable without using the 'local' keyword, that variable is good anywhere in your program, and is a "global" variable. Unintentional global variables can cause bugs that are very difficult to notice and track down, so the Lua Template has code to disable most global variables. the variable _global is a table where you can place other global variables, if you really want to use them.

If we use the 'local' keyword, then we have a local variable, which is limited in scope. (Function parameters are also local variables.) The scope can be the entire file, or a function, or a block of code like part of a loop or if statement. A variable will be available in the entire scope, including any smaller divisions of code, unless a local variable with the same name is used in one of those smaller divisions.

Code:
    local choice = nil
    if winner.owner.isHuman then
        local dialog = civ.ui.createDialog()
 --code
        local choice = dialog:show()
    else
        choice = math.random(1,2)
    end
Since I need the choice variable outside of this if statement (in order to use it in the next block of code), I define choice before the if winner.owner.isHuman then line. However, the line local choice = dialog:show() line defined a new local variable, which was only good between if ... then and else. By removing the local keyword, the code knows to look for the choice variable defined in a higher block.

I hope this explanation is useful. If not, please let me know. I'm going to want to write this sort of explanation in a code guide "soon".
 
Last edited:
This is very useful, Prof.! I'm honestly amazed that, as someone with no coding knowhow whatsoever, I have -- with your help and guides -- been able to grasp the very basics of this in a relatively short timeframe. Extremely accessible. Thank you for all of your efforts. My mod now has this, improvement requirements for units, and some others.

The code, by the way, is flawless. Everything works!

Here are my next two ideas, and I think the first one is easy, I just am having trouble wording it in code (or maybe I'm trying to use a function that does not exist); the second one seems like it might be tough. We discussed briefly in PM, but thought others would find it useful, as it would have wide applications.

1. I want to generate a special unit inCapital of civ upon research of a tech. All civs, and it must be incapital, as this is a mod, and not a scenario. In macro language there was a phrase for this, not sure about Lua language.

2. After stepping into a Goodie Hut, I want to:
- Have the normal Goodie Hut effect launch
- Replace that tile with another terrain type
- Randomly generate (in Macro language, you'd give a # to generate things on random turns) [as in, on random turns] Barbarian units on those tiles
--- Have the # of units randomized, favoring fewer units
- If in an adjacent tile to that tile during the game, give a % chance of a unit generating in your control (or AI's control)
- Place text under the tile (like the ToTPP text?)

Already seems way over my head and complicated, but thought I'd ask. It would be really cool if, too, each tile originated from a different line on a table, so that each Goodie Hut spot that is being replaced had a different name and generated a different type of unit, cycling through a list.
 
1. I want to generate a special unit inCapital of civ upon research of a tech. All civs, and it must be incapital, as this is a mod, and not a scenario. In macro language there was a phrase for this, not sure about Lua language.
gen.createUnit has inCapital as an option for creating units. (I think you'd just use an empty table for the locations in that case.)

You could also use civlua.findCapital(tribe) to find a tribe's capital.
 
2. After stepping into a Goodie Hut, I want to:
- Have the normal Goodie Hut effect launch
- Replace that tile with another terrain type
- Randomly generate (in Macro language, you'd give a # to generate things on random turns) [as in, on random turns] Barbarian units on those tiles
--- Have the # of units randomized, favoring fewer units
- If in an adjacent tile to that tile during the game, give a % chance of a unit generating in your control (or AI's control)
- Place text under the tile (like the ToTPP text?)
Here's some code (only briefly tested). At the first opportunity, all the existing goodie huts are recorded, and those tiles kept in the saved game structure, for later reference.
Code:
local postGoodieHutTerrain = civ.getBaseTerrain(0,0)
local goodieHutBarbChance = 0.8
discreteEvents.onTurn(function(turn)
    local state = gen.getState()
    if not state.hutTable then
        state.hutTable = {}
        for tile in civlua.iterateTiles() do
            if tile.hasGoodieHut then
                state.hutTable[gen.getTileID(tile)] = true
            end
        end
    end
    for tileID,_ in pairs(state.hutTable) do
        local tile = gen.getTileFromID(tileID)
        if not tile.hasGoodieHut and tile.baseTerrain == postGoodieHutTerrain  then
            if math.random() < goodieHutBarbChance then
                -- generate barb units
                --civ.createUnit(gen.original.uHowitzer,civ.getTribe(0),tile)
            end
            for _,adjacentTile in pairs(gen.getAdjacentTiles(tile)) do
                local tileOccupierTribe = adjacentTile.defender
                if tileOccupierTribe then
                    -- do something for the tribe that occupies the tile
                end
            end
        end
    end
end)
discreteEvents.onEnterTile(function (unit, previousTile, previousDomainSpec)
    local state = gen.getState()
    if not state.hutTable then
        state.hutTable = {}
        for tile in civlua.iterateTiles() do
            if tile.hasGoodieHut then
                state.hutTable[gen.getTileID(tile)] = true
            end
        end
    end
    if state.hutTable[gen.getTileID(unit.location)] then
        if unit.location.baseTerrain ~= postGoodieHutTerrain then
            unit.location.baseTerrain = postGoodieHutTerrain
        end
    end
end)
discreteEvents.onCanFoundCity(function (unit, advancedTribe)
    local state = gen.getState()
    if not state.hutTable then
        state.hutTable = {}
        for tile in civlua.iterateTiles() do
            if tile.hasGoodieHut then
                state.hutTable[gen.getTileID(tile)] = true
            end
        end
    end
    if state.hutTable[gen.getTileID(unit.location)] then
        return false
    end
    return true
end)
discreteEvents.onScenarioLoaded(function ()
    local state = gen.getState()
    if not state.hutTable then
        state.hutTable = {}
        for tile in civlua.iterateTiles() do
            if tile.hasGoodieHut then
                state.hutTable[gen.getTileID(tile)] = true
            end
        end
    end
end)
You'll have to add the events for generating barbarian units and the bonus for units on adjacent tiles (as well as changing the barbarian probability and the base terrain to change the tile to).
- Place text under the tile (like the ToTPP text?)
Lua doesn't have that feature, as far as I am aware.
--- Have the # of units randomized, favoring fewer units

You could use a threshold table to determine relative quantities.

Something like this:
Code:
local quantityChanceThresholdTable = gen.makeThresholdTable({
    [0.85] = 6, -- 15%
    [0.75] = 3, -- 10%
    [0.5] = 2, -- 25%
    [0.3] = 1, -- 20%
    [0] = 0, -- 30
})
local function getQuantity()
    return quantityChanceThresholdTable[math.random()]
end
 
I'm speechless! This is working really well. And opens up so many possibilities! Thank you Prof.

I do have one question: when I want to generate the Barb unit (I successfully tested this), could I say "or" for the computer to cycle through different options? Or am I limited to loading the same unit every time?

I added, too, a probability counter for options coming up if the unit is adjacent to the former goodie hut location. :)

gen.createUnit has inCapital as an option for creating units. (I think you'd just use an empty table for the locations in that case.)

You could also use civlua.findCapital(tribe) to find a tribe's capital.

This makes sense. I'll try the empty table, too. A really dumb question, but what is the code for research/tech? I tried everything listed on TNO's thread, but to no avail. How do I make a command, "When X is researched, Y happens?"
 
I do have one question: when I want to generate the Barb unit (I successfully tested this), could I say "or" for the computer to cycle through different options? Or am I limited to loading the same unit every time?
There's nothing stopping you from making a threshold table with unitTypes as the values, and selecting the unitType to be created in the same way as the number. That would probably be the most compact way. Otherwise, you could have if .. else statements mixed with probability checks.

This makes sense. I'll try the empty table, too. A really dumb question, but what is the code for research/tech? I tried everything listed on TNO's thread, but to no avail. How do I make a command, "When X is researched, Y happens?"
Choose an execution point to check if the tribe has the technology (probably onCityProcessingComplete), and do something if it has the technology. Use gen.justOnce to make sure the event happens only once. Something like this.
Code:
discreteEvents.onCityProcessingComplete(function (turn, tribe)
    if tribe:hasTech(object.aSomeTech) then
        gen.justOnce("someTechReceivedBy"..tostring(tribe.id),function()
            civ.ui.text("Some Tech researched by "..tribe.name)
        end)
    end
end)

The "macro" system for Test of Time makes this check during the equivalent of onTurn. This confused me a bit when making the Legacy Event Engine. I think MGE has the received technology event happen immediately upon getting a tech, but I never bothered to test that, so it could be a false memory. If the change was made, I imagine it was made to accommodate Test of Time's ability to have 2 trigger conditions, not just one.
 
Thank you Prof. I'll give this a shot in the morning.

I found some quirks with the code above (with the Goodie Huts), tested some code for a few hours, but came up short. Here is what is happening:

- Units (friendly, I think?) are healing in adjacent tiles around the goodie hut locations -- interesting, but unintended
- This one is strange... Barbarian Gutian Riders attack without ever losing HP or taking HP away -- when attacking a city, it seems. Perhaps even just the player city? Other Gutian Riders (player/AI) will lose health and operate normally. Barbarian Gutian Riders are destroyed / operate normally in the field, just not when attacking player cities as far as I can tell.

EDIT: My mistake. This is happening with that code removed, too -- but not in earlier save files, for some reason. In new games. Something with it being a mod instead of a scenario?
 
Last edited:
Back
Top Bottom