Lua Scripting Possibilities

I'm swinging and missing all over the place! Here's another one I can't get to work (I don't get any errors but the terrain never changes) - I am using the civ.scen.onTurn(function (turn)

Code:
if tribeAliases.Allies:hasTech(civ.getTech(76)) then
    tile = civ.getTile(405, 75, 0)
    tile.terrainType = 2
  end

after then, add civ.ui.text("Allies have tech 76"). This will at least tell if the event is firing or if the problem is in actually changing the terrain.

About the several cluttered 'ends', would it be also correct to write end, end end, end, end in one line?

Yes, I believe so. But from a code readability perspective, you probably shouldn't (unless you want to fit a function definition on one line or something).
 
Prof. Garfield, one more time thank you very much for the answer. :)

So I´m still far away from the ability to write correct lua code, the answers in this thread are helping me to understand more parts of the existing lua work for ToTPP. :thumbsup:
 
@Prof. Garfield - both of your solutions work as intended, thank you! I don't know why the terrain was not firing for me last night and now it is. I do recall while working on Caesar that lua seemed a bit "finicky" and sometimes two totally unrelated events would cancel each other out for whatever reason. So perhaps solving the one issue that wasn't working also solved the other? In any event, thank you for your help!
 
"if" checks in Lua require a double equals sign; a single equals sign is used to set or change a value. In programmer-speak, = is an assignment operator and == is a relational or comparison operator. Try:

if killedTerrain == 1 then

EDIT: That might fix the syntax, although I'm not sure it will clear up all your issues. It looks to me like you're re-using killedTerrain as both an integer (0 or 1) and as the value returned by pairs() -- and as a key within cannotLandCityItalyRussia. That probably isn't quite what you need to be doing.

Yeah, the event is not firing.
So I've thought about trying to resolve this issue in a few different ways. The approach I posted earlier attempted to mirror Grishnach's munitions event:

Code:
civ.scen.onTurn(function (turn) -- this is line 1141

local currentUnit = civ.getActiveUnit()
local cannotLandCityItalyRussia = {
["Me109G6"] = { cannotLand=civ.getUnitType(12), killedTerrain={0, 15, -128, -114} },
}
for unit in civ.iterateUnits()do
        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand then  -- This is line 1195
            killedTerrain=0
            for _, killedTerrain in pairs (cannotLandCityItalyRussia.killedTerrain) do
                if currentUnit.location.terrainType==killedTerrain then
                killedTerrain==1
                end
            end
         
            if killedTerrain==1 then
                civ.deleteUnit(unit)
                end
            end
     
        end

But that brings the following error:

D:\Test of Time\Scenario\OTR3\events.lua:1195: attempt to index a nil value (global 'unit')
stack traceback:
D:\Test of Time\Scenario\OTR3\events.lua:1195: in function <D:\Test of Time\Scenario\OTR3\events.lua:1141>

I'm not totally sure why I'm getting "attempt to index a nil value" when I specifically write that I want this to apply to the Me109G6?

Regardless, I also thought maybe it would be easier (in terms of not having to construct a huge table when all I'm doing is comparing most units to terrain 0 and a few units to terrain 15) to mirror Grishnach's munition deletion event and build a table like these:

Code:
-- Defines aircraft that should not be able to land in city squares or different theatre squares (Italian Theatre and Russian Front)
--First one is aircraft that can't land anywhere except an airbase city
--Second is for Italian Theatre aircraft and Russian Front aircraft
--local cannotLandCityItalyRussia =
    --{ 5, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 73, 76, 120, 125 }

--local cannotLandCity =
--    { 83, 84, 96, 99 }


The code for the unit deletion appears to work:

Code:
local unitTypesToBeDeletedEachTurn =
    { 8, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 116, 123, 124 }

for unit in civ.iterateUnits() do
   
        for _, typeToBeDeleted in pairs(unitTypesToBeDeletedEachTurn) do
            if unit.type.id==typeToBeDeleted then
                civ.deleteUnit(unit)
            end
        end
       
    end

Maybe I'll try messing around with that and see if I can somehow add the terrain check to it with an "and"?

Edit - this also gets the same nil error...

Code:
for unit in civ.iterateUnits()do

        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand and tile.terrainType==0
            then
                civ.deleteUnit(unit)
                end
            end
        
        end
 
unit.type.id is an integer, while cannotLand is a table, so unit.type.id == cannotLand will give an error. Not sure why it is a nil error.

You seem to have the variable killedTerrain trying to do about three different things. Use different variables. Name one of them incorrectlyLanded, initialize it as false, and set it to true if appropriate.
 
unit.type.id is an integer, while cannotLand is a table, so unit.type.id == cannotLand will give an error. Not sure why it is a nil error.

See, this is confusing to me because all of this works in the typeToBeDeleted above despite that also being a table? All I did was change typeToBeDeleted to a different name (and add the "and" terrain part). Admittedly I haven't tried getting rid of the terrain part and seeing if it will work, but I don't see why it shouldn't in principle?
 
I've attached some (untested) code that should probably work for you.

if unit.type.id==cannotLand and tile.terrainType==0

should be unit.location.terrainType

you didn't define tile to be anything.
 

Attachments

See, this is confusing to me because all of this works in the typeToBeDeleted above despite that also being a table? All I did was change typeToBeDeleted to a different name (and add the "and" terrain part). Admittedly I haven't tried getting rid of the terrain part and seeing if it will work, but I don't see why it shouldn't in principle?
In the various pieces of code you posted earlier (not reviewing what Prof. Garfield just attached), typeToBeDeleted is not a table -- unitTypesToBeDeletedEachTurn is a table, but
for _, typeToBeDeleted in pairs(unitTypesToBeDeletedEachTurn) do
is breaking that table apart into its "key" and "value" portions (that's what "pairs" does), and typeToBeDeleted therefore holds only the value portion, which (for each table entry) is an integer, as Prof. Garfield said.

By contrast, cannotLand is a table. This is because cannotLandCityItalyRussia is a table of tables instead of a table of integers. So
for _, cannotLand in pairs (cannotLandCityItalyRussia) do
is breaking the first table apart into its "key" and "value portions, and cannotLand therefore holds only the value portion, but that is also a table, with its own keys and values.

local cannotLandCityItalyRussia = { ["Me109G6"] = { cannotLand=civ.getUnitType(12), killedTerrain={0, 15, -128, -114} }, }
You made it extra confusing by using cannotLand as a variable in your for loop, holding output of the pairs command, when that is also a key within the nested table as you defined it above. Within your for loop, it would be "cannotLand.cannotLand" that is equal to civ.getUnitType(12).

In order for it to make a lot more sense, just pick a different variable to hold the content returned by pairs:
for _, landingData in pairs (cannotLandCityItalyRussia) do

EDITED: Then take careful note of the two object types you're comparing:
unit.type.id is an integer. But you stored civ.getUnitType(12) within cannotLand, which is a unit type object (the whole unit type, not just its ID). So you need either
if unit.type.id == landingData.cannotLand.id (to compare two integers)
or
if unit.type == landingData.cannotLand (to compare two unit type objects)

and then, as Prof. Garfield said, you probably need to reference unit.location.terrainType rather than tile.terrainType.

Are we having fun yet? :crazyeye: Nested tables are a really powerful feature of Lua, though definitely an advanced one! You can even add more layers if you need to -- tables of tables of tables! That can become one of the most complex things to deal with in Lua, in my opinion.
 
Last edited:
I'm not really clear if I need both the part I wrote and the part you wrote however I have tried including my portion and excluding it and neither works. I did have to add "do" after .forbiddenTerrain or else an error was thrown up seeking it. Should this "do" be somewhere else?

Code:
local cannotLandCityItalyRussia = {
[unitAliases.Me109G6.id] = { forbiddenTerrain={0, 15, -128, -114} }, --Index unit types by their id number
}

    --[[for unit in civ.iterateUnits()do

        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand and unit.location.terrainType==0
            then
                civ.deleteUnit(unit)
                end
            end
        
        end]]

function doThisBetweenTurns(turn)
    for unit in civ.iterateUnits() do
        if cannotLandCityItalyRussia[unit.type.id]  then -- If the unittype id is not in the table, the call will return
                                                                                    -- nil, which is interpreted by lua as false
            local unitMustBeKilled = false
            for __, terrain in pairs(cannotLandCityItalyRussia[unit.type.id].forbiddenTerrain) do
                if unit.location.terrainType == terrain then
                    unitMustBeKilled = true  -- Could simply kill unit here, I guess.
                end
            end --end check each type of terrain
        end -- end if statement for if unit is in the cannotLandCityItalyRussia table
        if unitMustBeKilled then
            civ.deleteUnit(unit)
        end
    end -- end the for loop over all units in the game
end -- end function
 
I did have to add "do" after .forbiddenTerrain or else an error was thrown up seeking it. Should this "do" be somewhere else?

You put it in the correct spot.

I think my code can be dropped in, but I forgot the line

civ.scen.onTurn(doThisBetweenTurns) to actually run the code.
 
Ok - I'm really not trying to be dense here, but I think part of this is that you might think I have all of this code "active" at any point while really I'm doing the --[[ ]] to get rid of whichever "half" I'm not trying at the moment. So anyway, assume that only the below is in the file:
Grishnach's delete munitions code, which works:

Code:
local unitTypesToBeDeletedEachTurn =
    { 8, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 116, 123, 124 }
for unit in civ.iterateUnits() do
  
        for _, typeToBeDeleted in pairs(unitTypesToBeDeletedEachTurn) do
            if unit.type.id==typeToBeDeleted then
                civ.deleteUnit(unit)
            end
        end
      
    end

My attempt to copy it:

Code:
local cannotLandCityItalyRussia =
    { 5, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 73, 76, 120, 125 }
for unit in civ.iterateUnits()do
        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand -- let's ignore the terrain part for now and tile.terrainType==0
            then
                civ.deleteUnit(unit)
                end
            end
      
        end

When I look at these side by side (and ignoring the terrain part), this is the exact same code, but "typeToBeDeleted" is renamed "cannotLand" and "unitTypesToBeDeletedEachTurn" is renamed "cannotLandCityItalyRussia"

Throwing out the other "option" that I tried (which is --[[ ]] out of the code for the moment), it looks like the exact same thing? Yet one works and one doesn't?

Is typeToBeDeleted an actual "hidden" command somewhere that Grishnach was referencing? I assumed it was just a shortened version of "unitTypesToBeDeletedEachTurn" and didn't have special meaning beyond that. The assumption being made because this is the only place I see "typeToBeDeleted" in the script.

if I am making any sense....
 
I agree those look the same to me, just with different variables. If you add a space so that civ.iterateUnits()do becomes civ.iterateUnits() do does that make a difference?
If not, then something else is up.
 
@JPetroski Just want to make sure you saw my post above. You've made cannotLandCityItalyRussia a lot simpler now, so that it is just a table of integers, but hopefully what I wrote in that previous message gives you some helpful information about why certain things work and others do not. :)
 
I did, Knighttime - I think a big part of the confusion though was that I was trying twould different approaches (one at a time mind you) but had similar names in each of them.

Your post definitely helped me understand why the longer version I tried had no hope to work. I will try your suggestion of adding the space when I get back to the computer.

Thanks again to both of you!
 
I've been trying to troubleshoot this for a bit this morning and am running out of ideas.

1. I've tried adding a local forbiddenTerrain = unit.location.terrainType because I wasn't sure if maybe forbiddenTerrain had to be defined somewhere. No dice with that.
2. I've tried changing "terrain" in if unit.location.terrainType == terrain then to a few other things like tile.terrainType and this does not work.

I'm not really sure what else to do?

Code:
local cannotLandCityItalyRussia = {
[unitAliases.Me109G6.id] = { forbiddenTerrain={0, 15, -128, -114} }, --Index unit types by their id number
}

    for unit in civ.iterateUnits() do

        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand and unit.location.terrainType==0
            then
                civ.deleteUnit(unit)
                end
            end
        
        end


function doThisBetweenTurns(turn)
civ.scen.onTurn(doThisBetweenTurns)
    for unit in civ.iterateUnits() do
        if cannotLandCityItalyRussia[unit.type.id]  then -- If the unittype id is not in the table, the call will return
                                                                                    -- nil, which is interpreted by lua as false
            local unitMustBeKilled = false
            for __, terrain in pairs(cannotLandCityItalyRussia[unit.type.id].forbiddenTerrain) do
                if unit.location.terrainType == terrain then
                    unitMustBeKilled = true  -- Could simply kill unit here, I guess.
                end
            end --end check each type of terrain
        end -- end if statement for if unit is in the cannotLandCityItalyRussia table
        if unitMustBeKilled then
            civ.deleteUnit(unit)
        end
    end -- end the for loop over all units in the game
end -- end function


Here is the entire onTurn grouping in case I have it in the wrong place?

Spoiler full length code :
Code:
civ.scen.onTurn(function (turn)
    --g delete all air craft which are on the wrong carrier :)


if state.delay and state.delay > 0 then  -- this is line 1139
    state.delay = state.delay - 1
  end


for unit in civ.iterateUnits() do
    
        for _, typeToBeDeleted in pairs(unitTypesToBeDeletedEachTurn) do
            if unit.type.id==typeToBeDeleted then
                civ.deleteUnit(unit)
            end
        end
        
    end



if tribeAliases.Allies:hasTech(civ.getTech(76)) then
    tile = civ.getTile(405, 75, 0)
    tile.terrainType = 2
    civ.ui.text("Allies have tech 76")
  end



        
--[[local currentUnit = civ.getActiveUnit()
local cannotLandCityItalyRussia = {

["Me109G6"] = { cannotLand=civ.getUnitType(12), killedTerrain={0, 15, -128, -114} },

}


        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand then
            killedTerrain=0
            for _, killedTerrain in pairs (cannotLandCityItalyRussia.killedTerrain) do
                if currentUnit.location.terrainType==killedTerrain then
                killedTerrain=1
                end
            end
            
            if killedTerrain==1 then
                civ.deleteUnit(unit)
                end
            end
        
        end ]]


local cannotLandCityItalyRussia = {
[unitAliases.Me109G6.id] = { forbiddenTerrain={0, 15, -128, -114} }, --Index unit types by their id number
}

    for unit in civ.iterateUnits() do

        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
            if unit.type.id==cannotLand and unit.location.terrainType==0
            then
                civ.deleteUnit(unit)
                end
            end
        
        end


function doThisBetweenTurns(turn)
civ.scen.onTurn(doThisBetweenTurns)
    for unit in civ.iterateUnits() do
        if cannotLandCityItalyRussia[unit.type.id]  then -- If the unittype id is not in the table, the call will return
                                                                                    -- nil, which is interpreted by lua as false
            local unitMustBeKilled = false
            for __, terrain in pairs(cannotLandCityItalyRussia[unit.type.id].forbiddenTerrain) do
                if unit.location.terrainType == terrain then
                    unitMustBeKilled = true  -- Could simply kill unit here, I guess.
                end
            end --end check each type of terrain
        end -- end if statement for if unit is in the cannotLandCityItalyRussia table
        if unitMustBeKilled then
            civ.deleteUnit(unit)
        end
    end -- end the for loop over all units in the game
end -- end function
    
        
        

    if turn == 1 then
        civ.ui.text(func.splitlines(textAliases.firstTurn1))
        
    end

--civ.scen.onTurn(function (turn)
  if turn % 4 == 0 then
        civ.ui.text(func.splitlines(textAliases.freighterText))
        civlua.createUnit(unitAliases.Freighter, tribeAliases.Allies, {{3,119,0},{8,122,0},{4,86,0},{5,13,0}}, {count=2, randomize=true, veteran=false})
        civlua.createUnit(unitAliases.Freighter, tribeAliases.Allies, {{200,6,0},{193,17,0},{185,9,0},{3,5,0}}, {count=2, randomize=true, veteran=false})
        civlua.createUnit(unitAliases.Freighter, tribeAliases.Allies, {{8,8,0},{4,46,0},{9,67,0}}, {count=2, randomize=true, veteran=false})
    end
end)  --g end of onTurn function
 
Hi John,

Yes, it does seem to me like you may have something in the wrong place. Within function doThisBetweenTurns(turn), on its first line, you have civ.scen.onTurn(doThisBetweenTurns). You don't want the function to be referencing itself that way, nor do you want the civ.scen.onTurn() trigger to be found within a function instead of directly at the events.lua main level. I think you should remove that line entirely.

Then you also probably don't want function doThisBetweenTurns(turn) to be defined within civ.scen.onTurn(function (turn) as shown in your "Spoiler full length code". I would move the function code (about 16 lines) outside of and before that trigger. Within civ.scen.onTurn(function (turn), then, you can call it with a line that says doThisBetweenTurns(turn) (note that you don't use the word "function" when you call it, only when you define it).

Side note: It's perfectly acceptable to put code directly within the onTurn trigger, but it's also acceptable to have the onTurn trigger call a separate outside function. There's no right or wrong approach. For simplicity, though, my recommendation is to start by putting code directly within the onTurn trigger as a default, and only move it out to a separate function when you find that you need to run it multiple times. The key benefit of functions is that they let you define code once, and then reference that code as many times as you wish, without repeating the logic. On the other hand, if you define a lot of functions but then use each one in only a single place, that just ends up being a lot of misdirection while you try to chase around in the code to find out which lines are actually executing. :)

Do these changes make a difference? We can dig into the function code itself more, if it's still not working. If you're not sure whether a block of code is even executing, put a couple "print" statements in it, so you can see on the console what's going on. I'll sometimes scatter them in a bunch of places with boring text like print("Got here #1") and print("Got here #2") so I can trace exactly which parts of the code are running.
 
Last edited:
Hi,

I tried taking your suggestions but cannot get this to work:

Spoiler Does not work :
Code:
--Attempt at moving the deletion per turn outside of the other function

local currentUnit = civ.getActiveUnit()

local cannotLandCityItalyRussia = {
[unitAliases.Me109G6.id] = { forbiddenTerrain={0, 15, -128, -114} }, --Index unit types by their id number
}


function doThisBetweenTurns(turn) 
    for unit in civ.iterateUnits() do

        for _, cannotLand in pairs (cannotLandCityItalyRussia) do
        
            if unit.type.id==cannotLand and unit.location.terrainType==0
            then
            print("Got here #1")
                civ.deleteUnit(unit)
                end
            end
        
        end



    for unit in civ.iterateUnits() do
        if cannotLandCityItalyRussia[unit.type.id]  then -- If the unittype id is not in the table, the call will return
        
                                                                                    -- nil, which is interpreted by lua as false
            local unitMustBeKilled = false
            
            for __, terrain in pairs(cannotLandCityItalyRussia[unit.type.id].forbiddenTerrain) do
                            if unit.location.terrainType == terrain then
                            print("Got here #2")
                    unitMustBeKilled = true  -- Could simply kill unit here, I guess.
                end
            end --end check each type of terrain
        end -- end if statement for if unit is in the cannotLandCityItalyRussia table
        if unitMustBeKilled then
            civ.deleteUnit(unit)
            print("Got here #3")
        end
    end -- end the for loop over all units in the game
end -- end function



------------------------------------------------------------------------------------------------------------------------------------------------
-- The `onTurn` function runs its argument every turn, with the turn number passed as `turn`.
-- THIS IS NOT USED FOR TEXT DUE TO THE MULTIPLAYER NATURE OF THE SCENARIO!!!
civ.scen.onTurn(function (turn)
    --g delete all air craft which are on the wrong tile


if state.delay and state.delay > 0 then  -- this is line 1139
    state.delay = state.delay - 1
  end

 

doThisBetweenTurns(turn)




for unit in civ.iterateUnits() do
    
        for _, typeToBeDeleted in pairs(unitTypesToBeDeletedEachTurn) do
            if unit.type.id==typeToBeDeleted then
                civ.deleteUnit(unit)
            end
        end
        
    end



if tribeAliases.Allies:hasTech(civ.getTech(76)) then
    tile = civ.getTile(405, 75, 0)
    tile.terrainType = 2
    civ.ui.text("Allies have tech 76")
  end

On the bright side, I'm going to stay positive here because I was able to get the event where terrain is changed under any new city to fire, so there's that!
 
OK, the top-level structure seems reasonable now. Let's see... try this version of your function: (Warning -- Not Tested!)
Code:
function doThisBetweenTurns(turn)
   for unit in civ.iterateUnits() do
       for unitTypeId, data in pairs(cannotLandCityItalyRussia) do
           if unit.type.id == unitTypeId then
               print("Got here #1")
               for _, cannotLand in pairs(data.forbiddenTerrain) do
                   print("Got here #2")
                   if unit.location.terrainType == cannotLand then
                       print("Got here #3")
                       civ.deleteUnit(unit)
                   end
               end
           end
       end
    end -- end the for loop over all units in the game
end -- end function
Any success? Do you get any "Got here" messages on the console?

EDIT: in the code you posted before, there isn't an "end" for the civ.scen.onTurn function... was that just below the code you provided? You'll need an end statement for that somewhere after everything you did post.
 
Last edited:
It worked! Awesome, thank you :)

I'm down to one last event necessary! Things are looking pretty good!

I appreciate both of your considerable efforts here!
 
How would all of you handle this last event? I'm pretty sure the way I'm thinking to do it is way more complicated than it probably needs to be.

Basically, at the start of every turn, I want to check for how many [19] units each civ has.

For every unit that civ [1] (Allies) has, I want civ[1] to get $100
For every unit that civ [2] (Germans) does NOT have, I want civ[1] (again, the ALLIES) to get $100

Both civs start with a full compliment of these units (the most they can ever have, because this unit can only be built in coastal cities that are already on the map at the start, I know how many there will be). The Germans have 19, the Allies have 11. That's 30 states that I'd have to toggle back and forth when a unit is destroyed (shut it off) and an improvement is created (turn it back on).

That seems like a lot of work, time, and code. Is there a way to just use an equation each turn?

for unit in civ.iterateUnits() do
if unit.type.id == 19 then
--some sort of count or math equation?

I would really like to avoid 30 states if possible, but this is a little beyond what I'm seeing in the examples that I have and in the resources currently available. Am I on the right track that there is a better way?

Thanks,
 
Back
Top Bottom