Lua Scripting Possibilities

Hi to all Civers,

@JPetroski: I am following your initiative to apply Lua in Civ scenarios with great interest. Hope, you don’t mind if I address my Lua issues here as well? Just as you said:
“First of all, this is a generic lua thread where anyone who knows something about it can help out… Not just TNO, who can either ignore it or join as he sees fit. [#3]”
I think it’s a good idea to have individual “scenario creation threads” like your “Over the Reich” on the one hand, and besides that specifically Lua related Q&As threads – therefore I take the liberty to hijack this one. And: I try to be meaningful with my questions, which may help others in practice, too. ;)

Basically, I’d like to write the code of Prof. Garfield’s suggestion:
“Depending on what kinds of triggers are possible […] after producing unit "soldiers" and unit "horses" you get them in the same position, trigger something ("key pressed" would be perfect for that), the two individual units are destroyed and a "combined" dragoon unit is created at the same position. [#698]”

After hundreds of scripting approaches I am kind of stucked. Here’s where I got
Code:
local factoryLocations = {x, y, z}
for unit in civ.iterateUnits() do if unit.type.id == 62 then
    print (unit.location.x, unit.location.y, unit.location.z)
  end
end

local currentUnit = civ.getActiveUnit()
local currentTileCoordinates = { {currentUnit.location.x, currentUnit.location.y, currentUnit.location.z} }

local correctPosition=0
    while factoryLocations.x==currentUnit.location.x and factoryLocations.y==currentUnit.location.y and factoryLocations.z==currentUnit.location.z do
--Another try: while factoryLocations[x, y, z] == currentUnit.location[x, y, z] do
    correctPosition=1
end

if correctPosition==1 then
    civ.ui.text("It works!")
end

The Lua console returns for unit A (id=62) in my example correctly three positions: {77, 209, 0}, {87, 207, 0}, {95, 209, 0}, whereas the only unit B in game is on tile {87, 207, 0}. The point is: That should be a match – but the ‘correctPosition’ query is not firing – still revealing “0”. I also tried to combine it with 'civ.scen.onTurn( function(turn)' with the same result. Please be aware that I do not want to give upfront statically tiles as code’s input. It would be really cool to dynamically read out the positions of the two moving units (A, B) and then – if they are on the same tile – deliberately hit the key (start action).

Any help would be greatly appreciated!
 
Last edited:
Hi to all Civers,

@JPetroski: I am following your initiative to apply Lua in Civ scenarios with great interest. Hope, you don’t mind if I address my Lua issues here as well? Just as you said: I think it’s a good idea to have individual “scenario creation threads” like your “Over the Reich” on the one hand, and besides that specifically Lua related Q&As threads – therefore I take the liberty to hijack this one. And: I try to be meaningful with my questions, which may help others in practice, too. ;)

By all means! That's the point of this thread. General brainstorming and help with lua. Fire away!

By the way, great to see you in here! Are you working on this for a particular project or just having fun trying something new?
 
@SorFox

I think you've misunderstood how programming works.

Code:
local correctPosition=0
    while factoryLocations.x==currentUnit.location.x and factoryLocations.y==currentUnit.location.y and factoryLocations.z==currentUnit.location.z do
--Another try: while factoryLocations[x, y, z] == currentUnit.location[x, y, z] do
    correctPosition=1
end

It looks like what you want here is for the game to constantly check if the current unit is at a factory location. If it is, set correctPosition to 1, otherwise keep it at 0.

That is not what this code will do.

If this code is ever executed when the current unit is on a factory location, it will keep running forever. (Actually, it won't since you've set up factoryLocations incorrectly on an earlier line, but let's deal with the logic for now.)

Case 1: Current unit not at a factory location.

Computer tests the condition for the "while loop" and finds that it is false.
The computer then skips to the "end" line for the "while loop" and continues following instructions after that.

Case 2: Current unit is at a factory location.

Computer tests the condition for the while loop and finds that it is true.
Computer proceeds to next instruction, which is to set the value of correctPosition to 1, which it does.
Computer proceeds to next "instruction," which is the end of the loop.
Computer returns to top of while loop.
Computer tests the condition for the while loop and finds that it is true.
Computer proceeds to next instruction, which is to set the value of correctPosition to 1, which it does.
Computer proceeds to next "instruction," which is the end of the loop.
Computer returns to top of while loop.

I think you see that the computer will stay in the while loop forever, since nothing done inside the loop renders the condition false.

Here's the way to think about using Lua:

Begin by choosing a "trigger," which is to say a time when the game will execute some code you have written.

Choose from The scenario library

https://forums.civfanatics.com/threads/totpp-lua-function-reference.557527/#civ.scen

for example,

onKeyPress
civ.scen.onKeyPress(function (keyCode) -> void)

Somewhere in our events script, we write

civ.scen.onKeyPress(doThisOnKeyPress)

which tells the computer to follow the instructions given by doThisOnKeyPress whenever a key is pressed.

Now, we need to specify what those instructions are.

We begin by writing:

Code:
function doThisOnKeyPress(codeForKeyPressed)

end

The computer will then proceed line by line to follow the instructions we give until it reaches end.

I'll explain more later, but I have to go for now.
 
Let us consider a simple example. We want settlers to be able to learn from engineers. Specifically, we want one settler to become an engineer if
1. We have an active unit on a square, or we have the square selected in view mode
2. In the selected square, there is at least one settler and one engineer.
3. We press backspace.

Also,
4. We don't care which settler becomes an engineer
5. We want the new engineer to have the same home city, veteran status, damage, and expended movement as the settler it is replacing.

The following code will achieve that:

Code:
function doThisOnKeyPress(keyCode)
if keyCode == 214 then -- Condition is that BackSpace is pressed
    if civ.getActiveUnit() then --Check that there is an active unit
        local activeTile = civ.getActiveUnit().location -- Gets the acive unit and selects the tile
    else  --Code for case where there is no active unit
        activeTile = civ.getCurrentTile()
    end
    local settlerType = civ.getUnitType(0)
    local engineerType = civ.getUnitType(1)
    local numberOfSettlers = 0
    local numberOfEngineers = 0
    local firstSettler = nil
    for currentUnit in activeTile.units do
        if currentUnit.type == settlerType then
            numberOfSettlers = numberOfSettlers+1
            if numberOfSettlers == 1 then
                firstSettler = currentUnit
            end
        end
        if currentUnit.type == engineerType then
            numberOfEngineers = numberOfEngineers+1
        end
    end
    if numberOfSettlers >=1 and numberOfEngineers>=1 then
        civ.ui.text("Settlers learn from engineers")
        newEngineer = civ.createUnit(engineerType,firstSettler.owner, activeTile)
        newEngineer.homeCity = firstSettler.homeCity
        newEngineer.damage = firstSettler.damage
        newEngineer.moveSpent = firstSettler.moveSpent
        newEngineer.veteran = firstSettler.veteran
        civ.deleteUnit(firstSettler)
    end
end -- End keyCode 214 (Backspace) instructions
if keyCode == 81 then -- Condition that q is pressed
       civ.ui.text("Don't press q!")
end --End keyCode 81 (q) instructions
end --End doThisOnKeyPress


civ.scen.onKeyPress(doThisOnKeyPress)

The last line tells Test of Time to run the instructions given by the function doThisOnKeyPress.

Code:
function doThisOnKeyPress(keyCode)

Whenever we want the computer to follow the instructions doThisOnKeyPress, we must specify an integer. E.g. doThisOnKeyPress(17). The computer will follow the instructions in doThisOnKeyPress, replacing keyCode with the integer 17.

civ.scen.onKeyPress takes a function (set of instructions) as the argument, therefore we do not specify an argument for doThisOnKeyPress in the line

Code:
civ.scen.onKeyPress(doThisOnKeyPress)

Next,
Code:
if keyCode == 214 then -- Condition is that BackSpace is pressed
ensures that the set of instructions up to the corresponding "end" are only executed if keyCode is 214.

Code:
 if civ.getActiveUnit() then --Check that there is an active unit
        local activeTile = civ.getActiveUnit().location -- Gets the acive unit and selects the tile
    else --Code for case where there is no active unit
        activeTile = civ.getCurrentTile()
    end

The computer checks if there is an active unit. If so, it runs the code between "then" and "else". If there is no active unit, the computer runs the code between "else" and "end".

We use the TOTPP library functions civ.getActiveUnit and civ.getCurrentTile to determine which tile we are on. The computer saves that information as the variable activeTile, to be used later.

Code:
    local settlerType = civ.getUnitType(0)
    local engineerType = civ.getUnitType(1)
    local numberOfSettlers = 0
    local numberOfEngineers = 0
    local firstSettler = nil

Some more information is stored in memory to be accessed later. Defining the kind of unit that is a settler and engineer here allows us to make a change in one spot if we decide that settlers or engineers should occupy a different unit slot.

Code:
    for currentUnit in activeTile.units do
        if currentUnit.type == settlerType then
            numberOfSettlers = numberOfSettlers+1
            if numberOfSettlers == 1 then
                firstSettler = currentUnit
            end
        end
        if currentUnit.type == engineerType then
            numberOfEngineers = numberOfEngineers+1
        end
    end
the tile object has a built in function accessed as tile.units to produce an "iterator" of the units on that tile.
For each unit on the tile, the computer performs the following instructions, one after another. currentUnit changes to the next unit in the list one the "end" corresponding to the "do" is reached.

If the current unit is a settler, we change the value held in numberOfSettlers, and increase it by 1. Similarly for numberOfEngineers.

If the unit type is a settler, and the numberOfSettlers ==1, then we select the current unit to be the "firstSettler", which is to say the settler that will be replaced by an engineer. It is important that this if statement is already inside an if statement ensuring that the current unit is a settler. Otherwise, the firstSettler would be changed to the current unit in the list until a second settler was found.

Code:
    if numberOfSettlers >=1 and numberOfEngineers>=1 then
        civ.ui.text("Settlers learn from engineers")
        newEngineer = civ.createUnit(engineerType,firstSettler.owner, activeTile)
        newEngineer.homeCity = firstSettler.homeCity
        newEngineer.damage = firstSettler.damage
        newEngineer.moveSpent = firstSettler.moveSpent
        newEngineer.veteran = firstSettler.veteran
        civ.deleteUnit(firstSettler)
    end

This is code to be run only if there is at least one settler and one engineer. Otherwise, none of this stuff should be done.

A message is displayed.

A new unit is created with civ.createUnit, and we store the reference to that specific unit as newEngineer.

The computer takes the characteristics we want to preserve from the unit information of the settler we just replaced, and, after getting all the relevant information, deletes the settler.

Code:
end -- End keyCode 214 (Backspace) instructions
if keyCode == 81 then -- Condition that q is pressed
       civ.ui.text("Don't press q!")
end --End keyCode 81 (q) instructions
end --End doThisOnKeyPress
The if statement for keyCode 214 is closed with "end", and the code to be executed when q is pressed follows.

Finally, the "end" of the instructions of doThisOnKeyPress is reached.
 
3. When an aircraft moves onto a certain terrain type, it is deleted. Because I'm dealing with aircraft, this is OK for a check at the start of a turn (the terrain type will exclusively be under certain cities so as soon as the aircraft moves there its turn is over anyway) but I'd rather it happen instantly so I can have a text box warning the player it happened and they don't land their whole air force there.

Perhaps use the onActivation event.

When you activate an air unit, place the unit of a spare allied civ in each city where landing is forbidden. Your air unit will be unable to enter the city because of the "we are allied" message.

When a ground unit is activated, remove those units so that traffic can proceed as normal.

Perhaps keep a global table of those units so they are easy to remove (instead of looking through each city to find it).
 
There's only going to be two civs so that won't work - Grishnach came up with some code to delete units each turn for Gallic Wars (so munitions wouldn't stack up) and I think I could tweak this slightly to have it delete any aircraft unit that happen to be on tiles 0 or 15.
 
By all means! That's the point of this thread. General brainstorming and help with lua. Fire away! By the way, great to see you in here! Are you working on this for a particular project or just having fun trying something new?

Thanks mate! :) And yes, I have a special project in mind, but since this is ages away from being published, I am just having fun exploring these wonderful new options for event writing.

@SorFox I think you've misunderstood how programming works.
No question about that… ;)

Wow, I would like to say a big “Thanks” @Prof. Garfield – your answer is more than I expected!

With no studied IT background it is kind of hard to catch up. However, I like the challenge to learn – at least the basics – of Lua programming, considering the immense potential we all dreamed of for years while playing/building scenarios. I appreciate your detailed explanations as well as John’s courage to ask questions the way he does. And especially did in the first posts, since meanwhile I think, John, you’re already close to the inner circle of Lua experts.

Just two basic questions:
Code:
    local numberOfSettlers = 0
    local numberOfEngineers = 0
I am assuming that these statements are a kind of equivalent to the “flag”, used in the former ToT Macro event language, which turns states as ‘on’ (set) or ‘off’ (clear)?

4. We don't care which settler becomes an engineer
Code:
local firstSettler = nil
That is why you specified it “only” with ‘nil’ and no attributes (like tribe, damage etc.), right?

As soon as some new ideas need to be implemented and I even more familiarized with the syntax I will get back. For now, your code snippets and especially the comments are of great help!!
 
Just two basic questions:
local numberOfSettlers = 0
local numberOfEngineers = 0

I am assuming that these statements are a kind of equivalent to the “flag”, used in the former ToT Macro event language, which turns states as ‘on’ (set) or ‘off’ (clear)?

numberOfSettlers is the name of a place in memory where I'm storing an integer, so that I can use it later. I'm actually counting settlers (and engineers) rather than simply checking if there is at least one.

numberOfSettlers = numberOfSettlers + 1 means
"Take the value in numberOfSettlers and add 1 to it" and then "replace what is currently stored in numberOfSettlers with the result of that calculation"

A variable can be used as a "flag" by setting it to 0 or 1, but I would recommend using "booleans," which is to say the special values true and false.

For what I ended up doing, I didn't need a count of all settlers and engineers, so I could have written the code to stop looking after it found one settler and one engineer.


4. We don't care which settler becomes an engineer
Code:
local firstSettler = nil
That is why you specified it “only” with ‘nil’ and no attributes (like tribe, damage etc.), right?

nil is a special "object" in lua that basically says there is no data.

I'm labeling a piece of memory "firstSettler" so that I can reference it later, but I'm not putting anything in it.

Code:
if numberOfSettlers == 1 then
               firstSettler = currentUnit
end

is where I actually make firstSettler reference a specific unit on the map.
 
3. When an aircraft moves onto a certain terrain type, it is deleted. Because I'm dealing with aircraft, this is OK for a check at the start of a turn (the terrain type will exclusively be under certain cities so as soon as the aircraft moves there its turn is over anyway) but I'd rather it happen instantly so I can have a text box warning the player it happened and they don't land their whole air force there.

Perhaps use the onActivation event.

When you activate an air unit, place the unit of a spare allied civ in each city where landing is forbidden. Your air unit will be unable to enter the city because of the "we are allied" message.

When a ground unit is activated, remove those units so that traffic can proceed as normal.

Perhaps keep a global table of those units so they are easy to remove (instead of looking through each city to find it).

There's only going to be two civs so that won't work - Grishnach came up with some code to delete units each turn for Gallic Wars (so munitions wouldn't stack up) and I think I could tweak this slightly to have it delete any aircraft unit that happen to be on tiles 0 or 15.

Hi @JPetroski , just wanted to note that I did offer a partial solution to this earlier, recommending the use of civ.scen.onActivateUnit() just as @Prof. Garfield did:
3. Since you only care where an aircraft ends its turn, this would be pretty straightforward to check at the beginning of the following turn. "I'd rather it happen instantly so I can have a text box warning the player it happened and they don't land their whole air force there." -- To do that, though, you'd need to use a different trigger, besides civ.scen.onTurn(). But there isn't a trigger that fires when a unit moves, or stops moving. I think you could take the same code, and copy it to civ.scen.onActivateUnit() as well. This means it would also fire at the beginning of the next unit's turn, and then if the game found any aircraft to delete, the player would be notified immediately, before they did the same thing with the next unit. My only concern would be performance; the Lua code needs to process quickly enough that you don't notice any lag, since it would be firing so many times per turn. It would probably be faster to loop over cities with civ.iterateCities() rather than units with civ.iterateUnits(), since you said all instances of this terrain type will be beneath a city and there are probably fewer cities than units in the game. Looping over all map tiles with civlua.iterateTiles() would probably be slower yet, I'd avoid that.

This would achieve your goal the way you originally stated it: "I'd rather it happen instantly so I can have a text box warning the player it happened and they don't land their whole air force there." If you also want to prevent it from happening even the first time... that's a bit more difficult. I think Prof. Garfield was trying to give you ideas for solving it at that level. Is that necessary?

What about adding some type of visual indicator to the map in your scenario setup, using the "labels" feature supported in TOTPP, to show which cities are "safe for aircraft" vs. "not safe for aircraft"? Or does this change during the game?
 
Thank you both for your help - thinking about it further, I don't know that the player really needs warning. The visuals between cities and airfields are pretty obvious in my mind and it will be in the readme. If you aren't the kind of person that would normally read it, you really need to become the kind of person who does now that these lua scenarios are starting to take hold!
 
@Prof. Garfield , hats off to you :hatsoff:for taking the time to cover general programming topics and explain how Lua works as a language. You're living up to your handle with your professor-like teaching! :goodjob: Besides the great help you're providing to the people posting here, hopefully there are other people out there who want to learn how to write Lua events, that will also benefit -- both now and when they review this thread in the future.
 
@Prof. Garfield , hats off to you :hatsoff:for taking the time to cover general programming topics and explain how Lua works as a language. You're living up to your handle with your professor-like teaching! :goodjob: Besides the great help you're providing to the people posting here, hopefully there are other people out there who want to learn how to write Lua events, that will also benefit -- both now and when they review this thread in the future.

Indeed. There have been so many brilliant people helping the rest if us along :)

I was going through the Scenario League wiki's tips and tricks section. It could really use a section on lua. These responses in here if organized a bit, could be a great article. One of the biggest challenges for me in reading the lua manual is it really doesn't translate well to Civ2, yet this thread does, so it is immensely helpful!
 
@Prof. Garfield: Only after careful reading your helpful comments I eventually applied the code itself, getting this fault reporting “…f Time\Scenarios\[…]\Events.lua:39: attempt to index a nil value (global 'activeTile') stack traceback: ...f Time\Scenarios\[…]\Events.lua:39: in function 'doThisOnKeyPress'” Line 39 in my script order refers to “for currentUnit in activeTile.units do”.

Any ideas/recommendations? Maybe I am doing something wrong in the start settings?
 
@SorFox

I think this fixes it.

Code:
function doThisOnKeyPress(keyCode)
if keyCode == 214 then -- Condition is that BackSpace is pressed
    local activeTile = nil
    if civ.getActiveUnit() then --Check that there is an active unit
        activeTile = civ.getActiveUnit().location -- Gets the acive unit and selects the tile
    else --Code for case where there is no active unit
        activeTile = civ.getCurrentTile()
    end
    local settlerType = civ.getUnitType(0)
    local engineerType = civ.getUnitType(1)
    local numberOfSettlers = 0
    local numberOfEngineers = 0
    local firstSettler = nil
    for currentUnit in activeTile.units do
        if currentUnit.type == settlerType then
            numberOfSettlers = numberOfSettlers+1
            if numberOfSettlers == 1 then
                firstSettler = currentUnit
            end
        end
        if currentUnit.type == engineerType then
            numberOfEngineers = numberOfEngineers+1
        end
    end
    if numberOfSettlers >=1 and numberOfEngineers>=1 then
        civ.ui.text("Settlers learn from engineers")
        newEngineer = civ.createUnit(engineerType,firstSettler.owner, activeTile)
        newEngineer.homeCity = firstSettler.homeCity
        newEngineer.damage = firstSettler.damage
        newEngineer.moveSpent = firstSettler.moveSpent
        newEngineer.veteran = firstSettler.veteran
        civ.deleteUnit(firstSettler)
    end
end -- End keyCode 214 (Backspace) instructions
if keyCode == 81 then -- Condition that q is pressed
       civ.ui.text("Don't press q!")
end --End keyCode 81 (q) instructions
end --End doThisOnKeyPress

EDIT:
Looks like I can't put colour in "code"

if keyCode == 214 then -- Condition is that BackSpace is pressed
local activeTile = nil
if civ.getActiveUnit() then --Check that there is an active unit
activeTile = civ.getActiveUnit().location -- Gets the acive unit and selects the tile
else --Code for case where there is no active unit
activeTile = civ.getCurrentTile()
end

I think I made an error using "local." Using "local" means the variable you are declaring can only be used by a subset of the program. I declared a "local" active tile, so i think that variable was only accessible inside the "if statement" that I declared it in. In the "else" statement, I didn't use "local", so I instead declared a global variable, which was why it was available later on.

My new place for activeTile means that it will be available anywhere inside the "if keyCode == 214 then" staement.

I think what happened before was that I first pressed "backspace" in "view mode", so the else statement ran. That created a global variable for activeTile, which lasted after the function was complete. When I then used backspace while a unit was flashing, the line

for currentUnit in activeTile.units do

used the activeTile I had created the last time I ran the function, not the current time I ran it.
 
@Prof. Garfield: It works!
I think what happened before was that I first pressed "backspace" in "view mode", so the else statement ran. [...] I had created the last time I ran the function, not the current time I ran it.
Sounds logically. But while trying to fix it myself before and now reading what the real cause was, the more it becomes obvious to me, how valuable it is to have people like you around. Thanks a lot! :goodjob:
 
@Prof. Garfield - how would I take your earlier code regarding the improvement restrictions and have it apply to several buildings? Cities with buildings 13, 17, or 33 should never be able to build numerous other buildings. The problem seems to be that the first "civ.getImprovement" MUST be a unique integer, while the second one can be a repeat. Trying to reverse this causes the game to only recognize the last entry.

This does not work as the first integer is constant but the final one is different:
Code:
local buildRestrictionsImprovements={
 ["IndustryI"] = {improvement=civ.getImprovement(15), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(13)) then return false else return nil end end},
    ["IndustryI"] = {improvement=civ.getImprovement(15), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(17)) then return false else return nil end end},
    ["IndustryI"] = {improvement=civ.getImprovement(15), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(33)) then return false else return nil end end},
}

This does work as the final integer is constant but the first one is always different:
Code:
local buildRestrictionsImprovements={

    ["TrainedPilots"] = {improvement=civ.getImprovement(32), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(8)) then return false else return nil end end},
    ["LuftwaffeAiport"] = {improvement=civ.getImprovement(13), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(8)) then return false else return nil end end},
    ["RAFAiport"] = {improvement=civ.getImprovement(17), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(8)) then return false else return nil end end},
    ["USAAFAiport"] = {improvement=civ.getImprovement(33), conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(8)) then return false else return nil end end},

I'm guessing this needs to be reworked so that we're using { } instead of ( ) after the second civ.getImprovement so that we can have a few improvements? {{13},{17},{33}} but I'm not sure how to add that to the onBuild trigger. Any idea what I need to do to resolve this? Thank you!

Here is the code for the trigger:
Code:
civ.scen.onCanBuild(function (defaultBuildFunction, city, item)
    local separateCondition=nil
    if civ.isUnitType(item) then
        for _,restrictedUnit in pairs(buildRestrictionsUnits) do
            if item.id == restrictedUnit.unit.id then
                separateCondition = restrictedUnit.conditionMet(city,state)
            end
        end
    end
    if civ.isImprovement(item) then
        for _,restrictedImprovement in pairs(buildRestrictionsImprovements) do
            if item.id == restrictedImprovement.improvement.id then
                separateCondition = restrictedImprovement.conditionMet(city,state)
            end
        end
    end
    if civ.isWonder(item) then
        for _,restrictedWonder in pairs(buildRestrictionsWonders) do
            if item.id == restrictedWonder.wonder.id then
                separateCondition = restrictedWonder.conditionMet(city,state)
            end
        end
    end
    if separateCondition == nil then
        return defaultBuildFunction(city,item)
    else
        return separateCondition
    end
end)
 
I think we'd do well to build a library function to handle common build restrictions.

For the moment, try this

Code:
local buildRestrictionsImprovements={
 ["IndustryI"] = {improvement=civ.getImprovement(15),
conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(13))  or civ.hasImprovement(city, civ.getImprovement(17)) or civ.hasimprovement(city, civ.getImprovement(33)) then return false else return nil end end},
}
 
Well that did solve one problem (the airfields no longer can build the industry) but it creates another - when going into any actual "city" city that should be able to build Industry I I now get:

Code:
Over the Reich
Events.lua parsed successfully at 08/05/18 12:56:29

D:\Test of Time\Scenario\OTR3\events.lua:366: attempt to call a nil value (field 'hasimprovement')
stack traceback:
    D:\Test of Time\Scenario\OTR3\events.lua:366: in field 'conditionMet'
    D:\Test of Time\Scenario\OTR3\events.lua:545: in function <D:\Test of Time\Scenario\OTR3\events.lua:533>
D:\Test of Time\Scenario\OTR3\events.lua:366: attempt to call a nil value (field 'hasimprovement')
stack traceback:
    D:\Test of Time\Scenario\OTR3\events.lua:366: in field 'conditionMet'
    D:\Test of Time\Scenario\OTR3\events.lua:545: in function <D:\Test of Time\Scenario\OTR3\events.lua:533>
D:\Test of Time\Scenario\OTR3\events.lua:366: attempt to call a nil value (field 'hasimprovement')
stack traceback:
    D:\Test of Time\Scenario\OTR3\events.lua:366: in field 'conditionMet'
    D:\Test of Time\Scenario\OTR3\events.lua:545: in function <D:\Test of Time\Scenario\OTR3\events.lua:533>
D:\Test of Time\Scenario\OTR3\events.lua:366: attempt to call a nil value (field 'hasimprovement')
stack traceback:
    D:\Test of Time\Scenario\OTR3\events.lua:366: in field 'conditionMet'
    D:\Test of Time\Scenario\OTR3\events.lua:545: in function <D:\Test of Time\Scenario\OTR3\events.lua:533>

Where 366 is the line where I just added the new stuff you input, Garfield and 533 is the first line of:

Code:
civ.scen.onCanBuild(function (defaultBuildFunction, city, item)
    local separateCondition=nil
    if civ.isUnitType(item) then
        for _,restrictedUnit in pairs(buildRestrictionsUnits) do
            if item.id == restrictedUnit.unit.id then
                separateCondition = restrictedUnit.conditionMet(city,state)
            end
        end
    end
    if civ.isImprovement(item) then
        for _,restrictedImprovement in pairs(buildRestrictionsImprovements) do
            if item.id == restrictedImprovement.improvement.id then
                separateCondition = restrictedImprovement.conditionMet(city,state)
            end
        end
    end
    if civ.isWonder(item) then
        for _,restrictedWonder in pairs(buildRestrictionsWonders) do
            if item.id == restrictedWonder.wonder.id then
                separateCondition = restrictedWonder.conditionMet(city,state)
            end
        end
    end
    if separateCondition == nil then
        return defaultBuildFunction(city,item)
    else
        return separateCondition
    end
end)

Lua can be very tricky but at least it tells you when there's a problem unlike macro.txt that may simply never fire for various reasons.
 
local buildRestrictionsImprovements={
["IndustryI"] = {improvement=civ.getImprovement(15),
conditionMet=function (city, state) if civ.hasImprovement(city, civ.getImprovement(13)) or civ.hasImprovement(city, civ.getImprovement(17)) or civ.hasimprovement(city, civ.getImprovement(33)) then return false else return nil end end},
}
Typo here. should be civ.hasImprovement

The way or works is that once it gets a true evaluation, it doesn't continue processing the next statements.
 
Why is it that things that should be simple in Lua are very challenging for me? :lol:
This does not appear to work:

Code:
local cityAliases={}
cityAliases.Berlin = civ.getCity(0)

local textAliases = {}
  
textAliases.TestText = [[Let's see if this works...]]

local tribeAliases={}
tribeAliases.Allies = civ.getTribe(1)
tribeAliases.Germans = civ.getTribe(2)

civ.scen.onCityTaken(function (city, defender)
    if city.cityAliases == cityAliases.Berlin and defender.tribeAliases == tribeAliases.Germans then
    civ.ui.text(func.splitlines(textAliases.TestText))
  end
end)

While on topic - is there any way to do "any city?" I ask because what I'm trying to do here is create an event where the Allies get a technology if they capture any German city, and a 10 turn timer also starts whereby if it gets recaptured, they lose. I don't want there to be a 10 turn count down for every city - just the first one.
 
Top Bottom