[TOTPP] Lua Scenario Template

There's an important change to the template that will impact anyone who downloaded a version in November 2020 or later. I'm pretty sure this applies to @civ2units , and it may apply to others.

In the version I shipped, I wanted to make it optional to have certain files. Since the 'require' function causes an error if the file is missing, I used pcall(require,"moduleName") instead, since pcall suppresses errors, and instead just returns a boolean.

I recently realised that this is a problem, however, because if the file is there, but loading it causes an error, instead of telling you about the error, Lua will just act as if the file wasn't found at all. This will make debugging much more frustrating and difficult.

If you're not sure if your scenario is affected, open events.lua and do a search for 'pcall'. You will find numerous lines similar to
Code:
    local fileFound,prefix = pcall(require,fileName)
    local fileFound, prefix = pcall(require,"recentFeature"..tostring(fileNumber))
    pcall(require,"customMusicIntegration")

If you're not sure, send me a copy of your events.lua, and I'll be able to tell.

If you haven't done too much work on your scenario, download the latest version of the template, and copy over what you have done to the new version. If you've already done a lot of work, or aren't comfortable moving the code, send me a copy of your scenario and I'll make the changes line by line.

I searched for "pcall" but didn't find anything in the events.lua file. I've attached the file I'm currently using in my Reformation scenario so I would like to ask you if you could check the file, please?
 

Attachments

@Prof. Garfield, is it possible to create an event where I have two choice options and if I choose one option that there will be a 50% chance that a certain additional event triggers?

For example, if you play with France a event will fire where you can choose if you would like to pay money for getting two cities. If you pay the money, there should be a 50% chance getting them, otherwise another civ will take over control of the two cities.

If something like this should be possible, would you like to add the code for it into the following code? If it doesn't mean too much work for you, of course.

This is the code I'm using currently:
Code:
--Duchy of Brittany joins Kingdom of France (Tuen 32 => Jul 1532)
if  turn ==32 and object.pFrench.isHuman == true then
    gen.justOnce("Duchy of Brittany", function()
    local DuchyofBrittany = civ.ui.loadImage("Images/07_DuchyofBrittany.bmp")
    local dialog = civ.ui.createDialog()
        dialog.title = "World Events: Union of Brittany and France"
        dialog.width = 700
        dialog:addImage(DuchyofBrittany)
    local multiLineText = "Since 939 was the Duchy ..."
        text.addMultiLineTextToDialog(multiLineText,dialog)
    if object.pFrench.money >= 1500 then       
        dialog:addOption("Let's bribe the members! (Pay 1000 Thaler, 50% chance of unification)", 1)       
    end
        dialog:addOption("We shouldn't do this. (Brittany gets independent", 2)
    local choice = dialog:show()   
    if choice == 1 then       
        local tribe = object.pFrench
            tribe.money = tribe.money - 1500
        local dialog = civ.ui.createDialog()
            dialog.title = "World Events: Union of Brittany"
            dialog.width = 600
        local multiLineText = "The members of the Estate of Brittany voted for the unification of both countries."
            text.addMultiLineTextToDialog(multiLineText,dialog)   
            dialog:show()
    object.cBrest.owner = object.pFrench
    object.cNantes.owner = object.pFrench
    for unit in civ.iterateUnits() do
        if unit.owner ~= object.pFrench and unit.location.x == object.cBrest.location.x and unit.location.y == object.cBrest.location.y and unit.location.z == object.cBrest.location.z then
        civ.deleteUnit(unit)
        end
        if unit.owner ~= object.pFrench and unit.location.x == object.cNantes.location.x and unit.location.y == object.cNantes.location.y and unit.location.z == object.cNantes.location.z then
        civ.deleteUnit(unit)
        end   
    end
    civlua.createUnit(object.uFrenchPikemen, object.pFrench, {{138,44,0}}, {count=1, randomize=false, veteran=false})
    civlua.createUnit(object.uFrenchPikemen, object.pFrench, {{141,47,0}}, {count=1, randomize=false, veteran=false})           
    elseif choice == 2 then
        local dialog = civ.ui.createDialog()
            dialog.title = "World Events: Union of Brittany"
            dialog.width = 600
        local multiLineText = "Most of the members of the Estate of Brittany ..."
            dialog:show()
    object.cBrest.owner = object.pIndependentNations
    object.cNantes.owner = object.pIndependentNations
    for unit in civ.iterateUnits() do
        if unit.owner ~= object.pIndependentNations and unit.location.x == object.cBrest.location.x and unit.location.y == object.cBrest.location.y and unit.location.z == object.cBrest.location.z then
        civ.deleteUnit(unit)
        end
        if unit.owner ~= object.pIndependentNations and unit.location.x == object.cNantes.location.x and unit.location.y == object.cNantes.location.y and unit.location.z == object.cNantes.location.z then
        civ.deleteUnit(unit)
        end   
    end
    civlua.createUnit(object.uPikemen, object.pIndependentNations, {{138,44,0}}, {count=1, randomize=false, veteran=false})
    civlua.createUnit(object.uPikemen, object.pIndependentNations, {{141,47,0}}, {count=1, randomize=false, veteran=false})   
    end
    end)
end
 
I searched for "pcall" but didn't find anything in the events.lua file. I've attached the file I'm currently using in my Reformation scenario so I would like to ask you if you could check the file, please?

This is good. I thought you might have downloaded a new version of the template recently based on the fact that you ran into trouble by not having upgraded to TOTPPv17. The template requiring TOTPP v17 is the one that will cause issues (or, rather, silently ignore them).
 
@Prof. Garfield, is it possible to create an event where I have two choice options and if I choose one option that there will be a 50% chance that a certain additional event triggers?

For example, if you play with France a event will fire where you can choose if you would like to pay money for getting two cities. If you pay the money, there should be a 50% chance getting them, otherwise another civ will take over control of the two cities.

If something like this should be possible, would you like to add the code for it into the following code? If it doesn't mean too much work for you, of course.

This is the code I'm using currently:

You will want to use math.random to make this happen. E.g.
Code:
if math.random() < 0.25 then
    -- event with 25% chance of happening
else
   -- event with 75% chance of happening
end

If I understand your code, this should work for you:

Code:
-- in parameters.lua
param.brittanyJoinsFranceChance = 0.5

Code:
param = require("parameters")
--Duchy of Brittany joins Kingdom of France (Tuen 32 => Jul 1532)
if  turn ==32 and object.pFrench.isHuman == true then
   gen.justOnce("Duchy of Brittany", function()
    local DuchyofBrittany = civ.ui.loadImage("Images/07_DuchyofBrittany.bmp")
    local dialog = civ.ui.createDialog()
        dialog.title = "World Events: Union of Brittany and France"
        dialog.width = 700
        dialog:addImage(DuchyofBrittany)
    local multiLineText = "Since 939 was the Duchy ..."
        text.addMultiLineTextToDialog(multiLineText,dialog)
    if object.pFrench.money >= 1500 then       
        dialog:addOption("Let's bribe the members! (Pay 1000 Thaler, 50% chance of unification)", 1)       
    end
        dialog:addOption("We shouldn't do this. (Brittany gets independent", 2)
    local choice = dialog:show()   
    if choice == 1 then       
        local tribe = object.pFrench
            tribe.money = tribe.money - 1500
        if math.random() < param.brittanyJoinsFranceChance then
            local dialog = civ.ui.createDialog()
            dialog.title = "World Events: Union of Brittany"
            dialog.width = 600
            local multiLineText = "The members of the Estate of Brittany voted for the unification of both countries."
            text.addMultiLineTextToDialog(multiLineText,dialog)   
            dialog:show()
            object.cBrest.owner = object.pFrench
           object.cNantes.owner = object.pFrench
           for unit in civ.iterateUnits() do
                if unit.owner ~= object.pFrench and unit.location.x == object.cBrest.location.x and unit.location.y == object.cBrest.location.y and unit.location.z == object.cBrest.location.z then
                    civ.deleteUnit(unit)
                end
                if unit.owner ~= object.pFrench and unit.location.x == object.cNantes.location.x and unit.location.y == object.cNantes.location.y and unit.location.z == object.cNantes.location.z then
                   civ.deleteUnit(unit)
               end   
           end
        civlua.createUnit(object.uFrenchPikemen, object.pFrench, {{138,44,0}}, {count=1, randomize=false, veteran=false})
        civlua.createUnit(object.uFrenchPikemen, object.pFrench, {{141,47,0}}, {count=1, randomize=false, veteran=false})
        else
            civ.ui.text("Despite our efforts, Brittany did not join France."
        end           
    elseif choice == 2 then
        local dialog = civ.ui.createDialog()
            dialog.title = "World Events: Union of Brittany"
            dialog.width = 600
        local multiLineText = "Most of the members of the Estate of Brittany ..."
            dialog:show()
    object.cBrest.owner = object.pIndependentNations
    object.cNantes.owner = object.pIndependentNations
    for unit in civ.iterateUnits() do
        if unit.owner ~= object.pIndependentNations and unit.location.x == object.cBrest.location.x and unit.location.y == object.cBrest.location.y and unit.location.z == object.cBrest.location.z then
        civ.deleteUnit(unit)
        end
        if unit.owner ~= object.pIndependentNations and unit.location.x == object.cNantes.location.x and unit.location.y == object.cNantes.location.y and unit.location.z == object.cNantes.location.z then
        civ.deleteUnit(unit)
        end   
    end
    civlua.createUnit(object.uPikemen, object.pIndependentNations, {{138,44,0}}, {count=1, randomize=false, veteran=false})
    civlua.createUnit(object.uPikemen, object.pIndependentNations, {{141,47,0}}, {count=1, randomize=false, veteran=false})   
    end
    end)
end
 
Many thanks for the new code @Prof. Garfield
The cities are under control of the Christian Nations at the start of the game.
When the event fires, the player has a choice option and if he agrees to pay money, the two cities should either change ownership to the players civ or to the Independent Nations with a 50 percent chance.
 
Hello @Prof. Garfield, I've another question about the Lua Template.

In the former version I used the function for changing ruler names during the game. I copied the codes into the latest template version and unfortunatelly the game doesn't recognize the codes.
The Lua console doesn't show me any error message. These codes are located above the function 'events.onActivateUnit':
Code:
--Kingdom of France
local FrenchRulers = {
[1] = {name = "Francis I", female = false},
[62] = {name = "Henry II", female = false},

The other part of the code is located after 'events.afterProduction':
Code:
--Kingdom of France
if FrenchRulers[civ.getTurn()] then
    object.pFrench.leader.name = FrenchRulers[civ.getTurn()].name
    object.pFrench.leader.female = FrenchRulers[civ.getTurn()].female
end

I thought the code would work as well in the new version as I didn't change anything on it. Could you let me know, what I did wrong please?
 
In the former version I used the function for changing ruler names during the game. I copied the codes into the latest template version and unfortunatelly the game doesn't recognize the codes.
The Lua console doesn't show me any error message. These codes are located above the function 'events.onActivateUnit':

Does this table have a } to close it? If not, then it should be causing an error, so please send me a copy of your code so I can diagnose it.
Code:
--Kingdom of France
local FrenchRulers = {
[1] = {name = "Francis I", female = false},
[62] = {name = "Henry II", female = false},

The other part of the code is located after 'events.afterProduction':
Code:
--Kingdom of France
if FrenchRulers[civ.getTurn()] then
object.pFrench.leader.name = FrenchRulers[civ.getTurn()].name
object.pFrench.leader.female = FrenchRulers[civ.getTurn()].female
end
I thought the code would work as well in the new version as I didn't change anything on it. Could you let me know, what I did wrong please?

How are you testing this code? The name will only be changed during the afterProduction phase of turns 1 and 62. Just changing the year in the game won't make the change. You can either change the game to turn 61 and then end the turn, or you can run the following command in the console on turn 62:
Code:
console.afterProduction()
You can use this command in the console to test onTurn events:
Code:
console.onTurn()
 
Sorry, it was my mistake with testing the code. When I change the year one turn before the name changing everything works now. Just like you wrote.

Another thing I noticed when ending the turns, the Lua console shows nearly unlimited the message 'false' which makes the turns very slow. Could it be a script I accidentially activated in the background?
upload_2022-1-9_19-6-57.png
 
Another thing I noticed when ending the turns, the Lua console shows nearly unlimited the message 'false' which makes the turns very slow. Could it be a script I accidentially activated in the background?

That was my mistake. I accidentally left in a testing/debugging print function in canBuild.lua. Here's the updated file (which goes in LuaCore).
 

Attachments

I'm currently using the following code (borrowed from JPetroski's Cold War scenario) for a unit capture event.
Code:
if loser.type == object.uSpanishPikemen and aggressor.owner == object.pSpanishHabsburgEmpire and aggressor.location.terrainType ~= 10  and math.random() < .5 then
    civ.ui.text("Mesoamerican Warriors captured some firearms from the defeated Spanish troops.")
    civlua.createUnit(object.uMesoamericanArquebusiers, object.pMesoamericans, {{aggressor.location.x,aggressor.location.y,aggressor.location.z}}, {randomize=false, veteran=false})
    end

Everytime when the AI controlled tribe Mesoamericans defeats a Spanish Pikemen they should get an own Arquebusier unit. The code works and the text message shows up but the unit doesn't appear.
What do I have to change that the code works and the new unit appears on the location on the defeated unit?
 
I'm currently using the following code (borrowed from JPetroski's Cold War scenario) for a unit capture event.
Code:
if loser.type == object.uSpanishPikemen and aggressor.owner == object.pSpanishHabsburgEmpire and aggressor.location.terrainType ~= 10  and math.random() < .5 then
    civ.ui.text("Mesoamerican Warriors captured some firearms from the defeated Spanish troops.")
    civlua.createUnit(object.uMesoamericanArquebusiers, object.pMesoamericans, {{aggressor.location.x,aggressor.location.y,aggressor.location.z}}, {randomize=false, veteran=false})
    end

Everytime when the AI controlled tribe Mesoamericans defeats a Spanish Pikemen they should get an own Arquebusier unit. The code works and the text message shows up but the unit doesn't appear.
What do I have to change that the code works and the new unit appears on the location on the defeated unit?
If I remember well and am not mistaking, dead units are sent by civ2 engine on a "nil" tile when arriving in the onUnitKilled function.

Thus, "aggressor.location.x,aggressor.location.y,aggressor.location.z" make the unit created at this nil tile, which is impossible. Then, the unit creation is aborded.
You're not checking either if the tile of the agressor is left empty for that unit creation by the way ?

Why not creating this unit on the defending unit tile ?
 
I've started updating the template to reflect the changes for v0.18.

Changes:

1. Updated loops over technologies to account for the 253 possible technologies (units already have civ.cosmic.numberOfUnitTypes for the end of relevant loops).

2. AfterProduction now uses the onCityProcessingComplete execution point instead of piggybacking on onActivateUnit. Existing functionality with the name AfterProduction still works, but is hidden in discreteEvents and consolidatedEvents in favour of onCityProcessingComplete.

3. BeforeProduction now uses onTribeTurnBegin execution point instead of piggybacking on calculate city yield. BeforeProduction can still be used, but is discouraged.

4. onTribeTurnEnd added to the consolidated and discrete events, along with a file in EventsFiles.

5. onUseNuclearWeapon.lua added to MechanicsFiles (though it can be deleted if unneeded). Events.lua has code to run the unit killed events for units caught in blast radius of a nuke (except the nuke itself).

6. onGetFormattedDate added to MechanicsFiles (though it can be deleted if unneeded).

7. Added this code to check if the TOTPP version is up to date. This may be useful for existing scenarios. It has been an occasional source of error.

Code:
local TOTPPMajor = 0
local TOTPPMinor = 18
if not totpp.version then
    local message = "You are using Test of Time Patch Project version 0.15 or older.  This scenario requires TOTPP v"..TOTPPMajor.."."..TOTPPMinor.." or later.  Get the latest version of the TOTPP here (this message will also appear in the console, where you can copy the link): https://forums.civfanatics.com/threads/the-test-of-time-patch-project.517282/"
    civ.ui.text(message)
    error(message)
end
if (totpp.version.major < TOTPPMajor) or
    (totpp.version.major == TOTPPMajor and totpp.version.minor < TOTPPMinor) then
    local message = "You are using Test of Time Patch Project version "..totpp.version.major.."."..totpp.version.minor..".  This scenario requires TOTPP v"..TOTPPMajor.."."..TOTPPMinor.." or later.  Get the latest version of the TOTPP here (this message will also appear in the console, where you can copy the link): https://forums.civfanatics.com/threads/the-test-of-time-patch-project.517282/"
    civ.ui.text(message)
    error(message)
end
 
More changes:

Updated onCityFounded stuff so that the sample code returns cancellation functions, and updated events.lua code so that the cancellation functions from discreteEvents, consolidatedEvents, and onCityFounded.lua are all executed (if provided) when a city is cancelled.

Changed the integrateCustomMusic.lua file to take advantage of the new TOTPPv0.18 music feature. Of note is that the player can simply choose not to have a music folder in the scenario, and everything will work fine, just without the custom music.
 
Once HoF is done I'm going to upgrade Cold War to bring it in line with the template and to take advantage of all the new units so hopefully I'll be able to give you some feedback. It's scary to change so close to the end of a scenario and I don't really think I need the new additions for HoF but I certainly appreciate your efforts. Having worked on multiple lua projects by now, I think I'm in a good place to attest to how much easier the template makes things for designers. Making a small scenario like Midway felt like a gargantuan task compared to a truly large scenario like HoF and that's a testament to your work, so thank you!

I lost track on the transporter module - I think you were working with Tech to build that out. I'd be interested in incorporating it into HoF in lieu of the one I have now simply so that something is done with the units that are carried by a halftrack when the halftrack is killed. Is that still in the works? No real rush but I'm hoping to let others play this in another 6-8 weeks or so.
 
It's scary to change so close to the end of a scenario and I don't really think I need the new additions for HoF but I certainly appreciate your efforts.

I've made changes to the template in the past couple months that should make it easier to add features to the template that have been developed after the initial download. But it's still easier to just have everything included already, which is why I've focused the last few days on TOTPP18 features.

I lost track on the transporter module - I think you were working with Tech to build that out. I'd be interested in incorporating it into HoF in lieu of the one I have now simply so that something is done with the units that are carried by a halftrack when the halftrack is killed. Is that still in the works? No real rush but I'm hoping to let others play this in another 6-8 weeks or so.

I remember promising a strategic bombing module. I wasn't planning on re-writing @SorFox 's transportation module, though I do remember discussing it a bit. I can certainly work on it if you need it, however.
 
@Prof. Garfield To the extent you could give me a hand with just deleting the units that were carried by a particular carrier when it gets destroyed, I'd appreciate it. I've found that what happens now is if the carrier is destroyed the unit activates on the "hide away" tile and gives the "this unit is out of supply" text. It invites problems with the player then finding this tile and messing around with other units.

Also - I have input all the extra units in my scenario but for whatever reason canBuildSettings does not take effect with them. When you were building this out was there any check somewhere that only looked to units up to 127? I'm poking around a bit and am not sure.
 
Just to assure you it's not me this time, the only one of these that works is the first one as that is one of the original 127 units. All are properly defined in object as well.

--German Minors
unitTypeBuild[object.uHungarianInfantry.id]={location=hungarianCities,allImprovements={object.iFactory}}
unitTypeBuild[object.uHungarianTank.id]={location=hungarianCities,allImprovements={object.iFactory}}
unitTypeBuild[object.uHungarianArtillery.id]={location=hungarianCities,allImprovements={object.iFactory}}
unitTypeBuild[object.uHungarianFighter.id]={location=hungarianCities,allImprovements={object.iFactory}}
unitTypeBuild[object.uHungarianBomber.id]={location=hungarianCities,allImprovements={object.iFactory}}
 
And finally - if you want to test it yourself, here is a rules/units file as well as canBuildSettings and object that you can add to the stuff I've sent you prior.
 

Attachments

Back
Top Bottom