[TOTPP] Lua Scenario Template

Is it possible to make a delay when an event occur? In Macro there was a possibility that I could make a delay of certain turns after an event like city capture until units should spawn. I would like to use this in LUA, if possible.
 
Is it possible to make a delay when an event occur? In Macro there was a possibility that I could make a delay of certain turns after an event like city capture until units should spawn. I would like to use this in LUA, if possible.

I wrote a 'delay' module a while ago. Here's the documentation at the top:


-- A module for specifying an action which might have to wait until
-- a different player's turn, (or, a turn at some point in the future)
--
-- Functions that can be delayed will be provided to this module in
-- a table indexed by strings
-- These functions will take a table as their argument.
-- The keys to the argument table must be strings or numbers,
-- and the values must be strings, numbers, or tables
-- (and each table must only have strings, numbers, and tables)
--
-- usage:
-- delay = require("delayedAction")
--
-- To enable a function to be delayed
-- delay.makeFunctionDelayable(functionNameString,function)
--
-- To delay a function use one of these two functions
-- delay.doInFuture(functionNameString,argumentTable,turn,tribeID=-1)
-- performs an action specified by the functionNameString
-- and the argumentTable, on the turn specified
-- happens after production of the tribe associated with tribeID,
-- or onTurn, if submitted tribeID is -1
--
-- delay.doNextOpportunity(functionNameString,argumentTable={},tribeOrTribeID=-1)
-- performs a delayed action the next time a tribe is active, or immediately
-- if the tribe is the currently active tribe. If no tribe is specified,
-- or the tribeID specified is -1,
-- the action will be performed with the onTurn actions for the next turn
-- if no argumentTable is specified, an empty table is generated

untested example (that I wrote based on looking at the documentation):
Code:
local delay = require("delay")
local function twoTurnsAfterCityCapture(argTable)
    civ.ui.text(civ.getCity(argTable["cityID"]).name.." was captured two turns ago.")
end
delay.makeFunctionDelayable("MessageTwoTurnsAfterCityCapture",twoTurnsAfterCityCapture)

function cityTaken.onCityTaken(city,defender)
    -- display a message to the tribe that lost the city 2 turns after it was captured, during their after production phase
    delay.doInFuture("MessageTwoTunsAfterCapture",{["cityID"] = city.id},civ.getTurn()+2,defender)
end
 
Hey, guys. :)
I have in mind an event that gives the player a 50% chance of capturing guns when destroying enemy artillery.

Any suggestions on what would I need to add to the code below to make this work?

Code:
if loser.type == object.uCannon then
local newCannons = civ.createUnit(object.uCannon,winner.owner,winner.location)
end
if winner.owner == civ.getPlayerTribe() then
civ.ui.text("Enemy cannons captured!")
end

Thanks in advance!
 
Last edited:
Hey, guys. :)
I have in mind an event that gives the player a 50% chance of capturing guns when destroying enemy artillery.

Any suggestions on what would I need to add to the code below to make this work?

You will want to use math.random()
Code:
if loser.type == object.uCannon and math.random() < 0.5 then
    local newCannons = civ.createUnit(object.uCannon,winner.owner,winner.location)
    if winner.owner == civ.getPlayerTribe() then
        civ.ui.text("Enemy cannons captured!")
    end
end

By default, the game chooses the nearest city (I think the same way it would choose a city if you used the cheat menu to create a unit). If you want to make sure the unit always has a home city, but won't overload the city, this function should be helpful.
Code:
-- gen.homeToNearestCity(unit)-->void
-- finds the nearest city (of the same tribe) that can support another
-- unit, and sets the unit's home city to that city
-- if there is no suitable city, the unit's home city isn't changed

If you plan to have several unit types be capturable, and with different chances, you can do this
Code:
-- outside the unit killed event function
local canCapture = {}
canCapture[object.uCannon.id]=0.5
canCapture[object.uHeavyArtillery.id]=0.3 -- 30% chance to capture

-- inside the unit killed event function
if canCapture[loser.type.id] and math.random() < canCapture[loser.type.id] then
    local newCannon = civ.createUnit(loser.type,winner.owner.winner.location)
   -- other code
end
 
Last edited by a moderator:
I wrote a 'delay' module a while ago. Here's the documentation at the top:


-- A module for specifying an action which might have to wait until
-- a different player's turn, (or, a turn at some point in the future)
--
-- Functions that can be delayed will be provided to this module in
-- a table indexed by strings
-- These functions will take a table as their argument.
-- The keys to the argument table must be strings or numbers,
-- and the values must be strings, numbers, or tables
-- (and each table must only have strings, numbers, and tables)
--
-- usage:
-- delay = require("delayedAction")
--
-- To enable a function to be delayed
-- delay.makeFunctionDelayable(functionNameString,function)
--
-- To delay a function use one of these two functions
-- delay.doInFuture(functionNameString,argumentTable,turn,tribeID=-1)
-- performs an action specified by the functionNameString
-- and the argumentTable, on the turn specified
-- happens after production of the tribe associated with tribeID,
-- or onTurn, if submitted tribeID is -1
--
-- delay.doNextOpportunity(functionNameString,argumentTable={},tribeOrTribeID=-1)
-- performs a delayed action the next time a tribe is active, or immediately
-- if the tribe is the currently active tribe. If no tribe is specified,
-- or the tribeID specified is -1,
-- the action will be performed with the onTurn actions for the next turn
-- if no argumentTable is specified, an empty table is generated

untested example (that I wrote based on looking at the documentation):
Code:
local delay = require("delay")
local function twoTurnsAfterCityCapture(argTable)
    civ.ui.text(civ.getCity(argTable["cityID"]).name.." was captured two turns ago.")
end
delay.makeFunctionDelayable("MessageTwoTurnsAfterCityCapture",twoTurnsAfterCityCapture)

function cityTaken.onCityTaken(city,defender)
    -- display a message to the tribe that lost the city 2 turns after it was captured, during their after production phase
    delay.doInFuture("MessageTwoTunsAfterCapture",{["cityID"] = city.id},civ.getTurn()+2,defender)
end

Many thanks for the explanations @Prof. Garfield
I will try to get it work
 
Small update to the template. Moved scripts (like the makeObject.lua script) to a 'scripts' directory instead of the LuaCore directory. The makeObject.lua script now automatically creates object entries for the locations of all cities. City object entries are also available, but they default to not being created (using if false then), since errors could happen when trying to reference a city that has been destroyed.
 
May I ask you for support again, @Prof. Garfield?
I would like to add a delay after an event with dialog options. The delay should occur if choosen choice 2. I tried to use the codes you provided but for some reason I can't run them.
A link to the module "delayedAction" is given, I'm using "delay.doInFuture" for saying LUA to fire choice 2 six turns later if choosen.
The code looks like this one:
Code:
    if  turn ==5 then
        gen.justOnce("Conquest of Mexico", function()
        local ConquestMexico = civ.ui.loadImage("Images/99_ConquestMexico.bmp")
        local dialog = civ.ui.createDialog()
            dialog.title = "Conquest of Mexico"
            dialog.width = 700
            dialog:addImage(ConquestMexico)
            local multiLineText = "Following an earlier expedition..."
            text.addMultiLineTextToDialog(multiLineText,dialog)
                dialog:addOption("Yes, we will command the expedition.", 1)
                dialog:addOption("No, we are not interested. Lets Cortes do the job.", 2)
                local choice = dialog:show()
                if choice == 1 then
                civlua.createUnit(object.uCarrack, object.tHabsburgians, {{9,125,0},{9,123,0}}, {count=1, randomize=true, veteran=false})
                    flag.setTrue("ConquestofMexico")
                elseif choice == 2 then
                delay.doInFuture("Conquest of Mexico 2", civ.getTurn()+6)   
                    civ.ui.text("TEST")
                    object.cZacatecas.owner = object.tHabsburgians
                    object.cZacatecas.name = "San Luis Potosi"
                    for unit in object.cZacatecas.location.units do
                        if unit.owner ~= object.tHabsburgians then
                        civ.deleteUnit(unit)
                    end
                    end

But unfortunately it doesn't work. What do I wrong?
 
Here is the relevant code that you have:

Code:
                elseif choice == 2 then
                delay.doInFuture("Conquest of Mexico 2", civ.getTurn()+6)   
                    civ.ui.text("TEST")
                    object.cZacatecas.owner = object.tHabsburgians
                    object.cZacatecas.name = "San Luis Potosi"
                    for unit in object.cZacatecas.location.units do
                        if unit.owner ~= object.tHabsburgians then
                        civ.deleteUnit(unit)
                    end
                    end

Put this code in your file, but outside of the onTurn or afterProduction function.

Code:
local delay = require("delay")
local function conqOfMexico2(argTable)
    civ.ui.text("TEST")
   object.cZacatecas.owner = object.tHabsburgians
   object.cZacatecas.name = "San Luis Potosi"
   for unit in object.cZacatecas.location.units do
          if unit.owner ~= object.tHabsburgians then
              civ.deleteUnit(unit)
        end
   end
end
delay.makeFunctionDelayable("Conquest of Mexico 2",conqOfMexico2)
Then, in the afterProduction or onTurn function, at the same place you were before
Code:
              elseif choice == 2 then
                delay.doInFuture("Conquest of Mexico 2",{},civ.getTurn()+6,object.tHabsburgians)
              end
I presume that you want the code to run during the after production of the Habsburgians turn. If you want it to happen during someone else's afterProduction phase, use that tribe, or the tribe's id. If you want it to run during the onTurn event, use -1 as the fourth argument.

Let me know if you need more help.
 
Many thanks @Prof. Garfield :)

Now I see what I did wrong. I have to put everything which should occur after the delay in a local function table outside the AfterProduction Function and within the original event setting a link to the local table.
So all other delay function will be work the same way?

I made everything like you wrote but unfortunately LUA shows me the following error message:
upload_2021-5-4_22-13-2.png
 
Now I see what I did wrong. I have to put everything which should occur after the delay in a local function table outside the AfterProduction Function and within the original event setting a link to the local table.
So all other delay function will be work the same way?

Exactly.

I made everything like you wrote but unfortunately LUA shows me the following error message:

I made a mistake. I thought I could pass a tribe object to the doInFuture function.

Add a .id to the tribe object:

delay.doInFuture("Conquest of Mexico 2",{},civ.getTurn()+6,object.tHabsburgians.id)

FYI, the error provides this information:
Code:
doInFuture: the turn and tribeID arguments must be numbers.

"Arguments" in the context of lua functions are the data provided to the function
delay.doInFuture(argument1,argument2,argument3,argument4)

So, in this case, the error is saying that civ.getTurn()+6 must be a number (which it already is) and that object.tHabsburgians must also be a number (which it isn't, it is a tribe object). To convert a tribe object to the equivalent number, we use the .id key.
 
Perfect, now it works :thumbsup:

Another thing I would like to realize. Spain can colonize Cuba if a certain technology is researched. After that, all Cuban cities will change ownership to the Spanish Empire. Cuba was for it's sugar and tobacco important during that time and I would like to illustrate this via a bonus, which should be paid every turn to the player as long as Cuba is under his control. The bonus should start two turns after the technology is reseached.
Everything works fine and the money will be also paid, but only one time. How can I change it, so that every turn the bonus will be paid?

Attached the code I'm using, which works fine but only give one time the bonus.
Code:
--COLONIZATION OF CUBA
local delayedAction = require("delayedAction")
local function ColonizationofCuba2(argTable)
    civ.ui.text("TEST")
    local SpanishCuba = 2500
    local tribe = object.tHabsburgians
    
    if flag.value("ColonizationofCuba") == true and tribe == object.tHabsburgians then
    tribe.money = tribe.money + SpanishCuba
    end
end   
end   
delay.makeFunctionDelayable("Colonization of Cuba 2",ColonizationofCuba2)
 
How can I change it, so that every turn the bonus will be paid?

You will have to check for the bonus in the afterProduction function. Something like

Code:
local delayedAction = require("delayedAction")
local param = require("parameters")
local spanishCubaBonus = param.cubaBonus -- This requires putting cubaBonus in the parameter table
-- local spanishCubaBonus = 2500 -- if you don't want to use parameter table, use this line instead
local function ColonizationofCuba2(argTable)
   civ.ui.text("TEST")
   flag.setTrue("ColonizationOfCubaBonusActive") -- define flag in object file, false by default
   --object.tHabsburgians.money = object.tHabsburgians.money = +2500 -- If the bonus isn't paid the turn the text appears, uncomment this
end

local function spainHoldsCuba()
    -- Check if spain still holds cuba, perhaps if certain cities are still held by habsburgs, or something
    -- return true if it does, return false if it does not

end

Code:
function afterProduction.afterProduction(turn,tribe)  -- I don't remember the exact wording of this line, could be doAfterProduction, or eventTriggers.afterProduction if you're in that file
  -- other afterProduction code
  if tribe == object.tHabsburgians and flag.value("ColonizationOfCubaBonusActive") and spainHoldsCuba() then
        tribe.money = tribe.money+spanishCubaBonus
   end
    -- other afterProduction code
end
 
I tried to use the codes but unfortunately it doesn't work. I'm pretty sure it's because I have to define the cities in the 'local function SpainHoldsCuba' function.
If I don't write anything into the function or using flags, the event works but without getting the bonus.

I tried to define the Cuban cities with the following code but this doesn't work. LUA shows an error message.
Code:
Havana[object.cHavana.id] = true
Camaguey[object.cCamaguey.id] = true
SantiagoCuba[object.cSantiagoCuba.id] = true

How can I define the Cuban cities?
 
I tried to use the codes but unfortunately it doesn't work. I'm pretty sure it's because I have to define the cities in the 'local function SpainHoldsCuba' function.
If I don't write anything into the function or using flags, the event works but without getting the bonus.

There is no 'return' line in the function I provided, which is equivalent to returning nil, and nil counts as false

Try this code:
Code:
cubaCities = {object.cHavana, object.cCamaguey, object.cSantiagoCuba}

-- Check if all cuban cities are owned by habsburgians
local function spainHoldsCuba()
    for __,cubaCity in pairs(cubaCities) do
        if cubaCity.owner ~= object.tHabsburgians then
            return false -- not all cities owned by habsburgians
       end
    end
    return true  -- if we get here, all cities are owned by habsburgians
end

If you have other events that check if individual cities are cuban, and you want to reuse the same table,

Code:
cubanCity={}
cubanCity[object.cHavana.id] = true
cubanCity[object.cCamaguey.id] = true
cubanCity[object.cSantiagoCuba.id] = true

-- Check if all cuban cities are owned by habsburgians
local function spainHoldsCuba()
    for cityIndex,__ in pairs(cubanCity) do
        cubaCity=civ.getCity(cityIndex)
        if cubaCity.owner ~= object.tHabsburgians then
            return false -- not all cities owned by habsburgians
       end
    end
    return true
end
 
Many thanks again for giving light into these codes, so that apprentices like me understand how to use them @Prof. Garfield :)

Everything works now like it should.
These codes are very usefull as I would like to use them for other cities too.

Just one thing I'm thinking about to use. Spain plundered tons of silver from the New World which led several times into a inflation because there was too much silver on the market. I would like to simulate the possibility of an inflation if Spain conquers certain cities in Central and South America. Is it difficult to add a code which takes an amount of money from the player if he holds cities with silvermines in America?
 
Just one thing I'm thinking about to use. Spain plundered tons of silver from the New World which led several times into a inflation because there was too much silver on the market. I would like to simulate the possibility of an inflation if Spain conquers certain cities in Central and South America. Is it difficult to add a code which takes an amount of money from the player if he holds cities with silvermines in America?

To subtract money, you do
Code:
tribe.money = math.max(0,tribe.money-amount)
the math.max makes sure that the tribe always has at least 0 money

If you want to reduce the treasury by 10%, say, you could do this
Code:
tribe.money = math.floor(.9*tribe.money)
math.floor rounds down to the nearest integer, so that you don't try to give a fractional amount of money (not sure what happens if you try).

Code:
local silverMineCities = {} -- fill with relevant cities
local function hasSilverMine(tribe)
    for __,city in pairs(silverMineCities) do
        if city.owner == tribe then
           return true
       end
    end
    return false
end

Code:
-- assuming afterProduction, and tribe variable available
-- assuming param.inflationLoss exists, and is fraction of money to be taken away
if hasSilverMine(tribe) then
        tribe.money = math.ceil((1-param.inflationLoss)*tribe.money)
end
Note: I used math.ceil to round up instead of down. No particular reason to choose one over the other.
 
Many thanks for the codes, @Prof. Garfield :)

Currently I'm writing the codes for the silver cities events. Everything works good but the bonus is paid only one time. Could it be because I'm using the 'gen.justOnce' line in the code?
Is it possible to show the dialog options only onne time but the bonus should be paid every turn?
Code:
if tribe == object.tHabsburgians and SpainholdsSilverCities() then
    gen.justOnce("Silver Cities", function()
        local SilverCities = civ.ui.loadImage("Images/99_SilverCities.bmp")
        local dialog = civ.ui.createDialog()
            dialog.title = "Richdoms of the New World"
            dialog.width = 700
            dialog:addImage(SilverCities)
            local multiLineText = "The Spanish treasure fleet transporting gold and silver from the New World..."
            text.addMultiLineTextToDialog(multiLineText,dialog)
                dialog:addOption("Let's go the safe way, less money, no risk of inflation.", 1)
                dialog:addOption("Let's plunder the mines. I don't scare an inflation", 2)
                local choice = dialog:show()
                if choice == 1 then
                    tribe.money = tribe.money+SilverCitiesBonus1
                elseif choice == 2 then
                    tribe.money = tribe.money+SilverCitiesBonus2
                end   
    end)
end
 
Not being a picky word bully here, but in your event title, I would swap out "Richdoms" for "Abundance", as it is a more fitting English useage. :)

"Richedom" is an old word, but it is never used in common speech.
 
Many thanks for the tip with the word "Abundance", Curt. I appreciate this :thumbsup:
As a non native english speaking guy I'm always a little bit afraid that I don't use the correct words or in this case an old one.

Maybe I should ask one of you guys for looking at my texts when I'm ready with the scenario.
 
Back
Top Bottom