Lua Scripting Possibilities

Looking over the different lua functions reference, I see:

teleportUnit

civ.teleportUnit(unit, tile)
**I am kind of curious if it should really be civ.teleportUnit (unit, {tile})?

civ.teleportUnit(unit,tile) takes a unit object and a tile object. A tile object is different from a table that has three elements which we interpret as coordinates.

If we have
destination = {10,12,1}
then we use
civ.teleportUnit(myUnit,civ.getTile(destination[1],destination[2],destination[3]))

since we may do this a lot, we might define a function to make it quicker
function tableTile (myTile)
return civ.getTile(myTile[1],myTile[2],myTile[3])
end

**Though I'm not totally certain what "returns" completely means...

When a function returns a variable, that variable is then available for other computations. Let's evaluate in steps the following line

1. civ.teleportUnit(myUnit,civ.getTile(destination[1],destination[2],destination[3]))
destination[1] returns 10, destination[2] returns 12, destination[3] returns 1, for
2. civ.teleportUnit(myUnit,civ.getTile(10,12,1))
civ.getTile picks out the tile object in the game with coordinates 10,12,1, which we'll call destinationTile
3. civ.teleportUnit(myUnit,destinationTile)
This whole thing can be evaluated. It doesn't return anything, but does the game board action of moving the unit in question.

But what I'm thinking is that you could write some code that would basically check for a unit to enter one tile, and it would then automatically teleport it to another tile, and you could do this as many times as you wished in a turn.

Teleporting between turns is possible. To teleport in the middle of a turn would require an event trigger, such as a unit kill. The only event we have at the moment that can be triggered at will is the civ.scen.onLoad/civ.scen.onSave, which is a hassle.
 
Here is how I would write the code:
1. civ.scen.onUnitKilled(function (killed, killedBy)
2. local unitType = killed.type.id
3. if unitType == 54 then
4. civ.ui.text(func.splitlines(LegioIText))
5. civlua.createUnit(civ.getUnitType(31),civ.getTribe(0), massacreLocations[31][2], {randomize=false,veteran=true,count=27, homeCity=civ.getCity(0)})
6. end

7. end)

This is much more familiar/similar to the macro.txt... I took a shot at doing this twice (for Legio I and II)... I did add the civ.giveTech to each one - I'm assuming I placed this in the right spot?

Spoiler Latest Attempt :
--RHINE LEGION DESTROYED
local LegioIText = [[Legio I “Germanica” has been destroyed. The fate of the survivors may be worse than that of the dead, as human sacrifice of the most hellish sort is known to be practiced by the local barbarians.]]
local LegioIIText = [[Legio II “Augusta” has been destroyed! Praetorians on duty in the Palatium claim to hear the late Emperor Augustus again crying “Give me back my legion!” Though unconfirmed, many are inclined to believe the ghost story, for Legio II did indeed bear Augustus’ name.]]

--TEXT TO DISPLAY AND TECH TO GIVE IF RHINE LEGION DESTROYED (artifact from earlier attempt/other way of writing it but leaving here because I think I still need it in conjunction with the red text?
local LegionKilled = {
[54]={text=LegioIText, tech=22},
[55]={text=LegioIIText, tech=25},
}

local massacreLocations = {[31]={{2,2,0}}}

--UNIT KILLED (LEGIO I)

civ.scen.onUnitKilled(function (killed, killedBy)
local unitType = killed.type.id
if unitType == 54 then
civ.ui.text(func.splitlines(LegioIText))
civ.giveTech (killed.owner, civ.getTech (LegionKilled[id].tech))
civlua.createUnit(civ.getUnitType(31),civ.getTribe(0), massacreLocations[31][1], {randomize=false, veteran=true, count=3, homeCity=civ.getCity(Batavodurum)})
end
end)

--UNIT KILLED (LEGIO II)

civ.scen.onUnitKilled(function (killed, killedBy)
local unitType = killed.type.id
if unitType == 55 then
civ.ui.text(func.splitlines(LegioIIText))
civ.giveTech (killed.owner, civ.getTech (LegionKilled[id].tech))
civlua.createUnit(civ.getUnitType(31),civ.getTribe(0), massacreLocations[31][1], {randomize=false, veteran=true, count=3, homeCity=civ.getCity(Ava Ubiorum)})
end
end)
 
Teleporting between turns is possible. To teleport in the middle of a turn would require an event trigger, such as a unit kill. The only event we have at the moment that can be triggered at will is the civ.scen.onLoad/civ.scen.onSave, which is a hassle.

We'll take it :thumbsup:
 
civ.scen.onUnitKilled(function (killed, killedBy)
local unitType = killed.type.id
if unitType == 54 then
civ.ui.text(func.splitlines(LegioIText))
civ.giveTech (killed.owner, civ.getTech (LegionKilled[id].tech))
civlua.createUnit(civ.getUnitType(31),civ.getTribe(0), massacreLocations[31][1], {randomize=false, veteran=true, count=3, homeCity=civ.getCity(Batavodurum)})
end
end)

Order is correct.

giveTech line should be
civ.giveTech(killed.owner,civ.getTech(LegionKilled[unitType].tech))

the homeCity=civ.getCity(Batavodurum) part is also wrong, but don't feel bad, since we need more machinery to get it right.

civ.getCity() converts an integer into the correct city object. We need a function that converts a string into the correct city object. In which case it would be homeCity=stringToCity("Batavodurum")

We can work around this by using the tile where the city should be to get the city.

local bataTile = {1,2,0}
local Batavodurum = civ.getTile(bataTile[1],bataTile[2],bataTile[3]).city

So, I think this would work
1. local bataTile = {1,2,0}
2. local Batavodurum = civ.getTile(bataTile[1],bataTile[2],bataTile[3]).city
3. civ.scen.onUnitKilled(function (killed, killedBy)
4. local unitType = killed.type.id
5. if unitType == 54 then
6. civ.ui.text(func.splitlines(LegioIText))
7. civ.giveTech (killed.owner, civ.getTech (LegionKilled[unitType].tech))
8. civlua.createUnit(civ.getUnitType(31),civ.getTribe(0), massacreLocations[31][1], {randomize=false, veteran=true, count=3, homeCity=civ.getCity(Batavodurum)})
9. end
10. end)

I suppose we could put lines 1 and 2 inside the civ.scen.onUnitKilled if we wanted to.

My only other concern is that you call civ.scen.onUnitKilled twice in your code. I don't know if this is a problem or not. We may have to bundle them all into one call to civ.scen.onUnitKilled by using a sequence of if statements.
 
One thing that is making this a little challenging to learn is that there are several ways of doing something, and the different reference materials don't appear to use the same one:

"Migrating Events to Lua" talks about what to do if a unit owned by a particular tribe (Barbarians) is killed by any tribe:

Spoiler Migrating Events to Lua :
UnitKilled
----------
Use `civ.scen.onUnitKilled`. The callback receives the defending and attacking units as parameters.

@IF
UNITKILLED
map=0
unit=Warriors
attacker=Anybody
defender=Barbarians
@THEN
...

civ.scen.onUnitKilled(function (defender, attacker)
if defender.type.name == "Warriors" and
defender.z == 0 and
defender.owner.name == "Barbarians" then

...
end
end)


The Sci Fi example just looks at if one particular type of unit is killed, regardless of who owns it and regardless of who kills it.

Spoiler Sci Fi Example :
civ.scen.onUnitKilled(function (killed, killedBy)
local id = killed.type.id
-- If the killed unit is one of the giants
if giantKilled[id] then
justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(giantKilled[id].text))
end)
-- `giveTech` is the replacement for GiveTechnology. It takes a tribe and a tech as parameters.
civ.giveTech(killedBy.owner, civ.getTech(giantKilled[id].tech))
-- Create a replacement unit of the same type as `killed`.
civlua.createUnit(killed.type, civ.getTribe(0), giantLocations[id][1], {randomize=true, veteran=true})
end
end)


But then I search "Migrating Events to Lua" and also the functions library thread and I can't find a single reference to "killedBy," and the only place in the Sci Fi events that has this phrase is the actual function...

Is there a list somewhere that I've missed that includes such phrases that can be used? I mean, "killedBy" is intuitive enough but I'm not sure how intuitive the rest of the options will be.
 
I think we need a little explanation of functions in programming.

A function is a list of commands to be done in order. The function may need some input. For example

function addFive (myInteger)
return myInteger + 5
end

In order to add five to 17, input addFive(17)

The following function also does the exact same thing:

function printString(myString)
return myString +5
end

It is just confusing to anyone that reads it. Use descriptive names when programming.
The functionName can be anything as long as I don't have two functions with the same name.
The variableName can be anything as long as you don't try to have two variables with the same name inside the same function.

for example, I can have
function addSix (myInteger)
return myInteger + 6
end

in the same file, even though myInteger is the name of a variable in both.

function mySubtract (subtractFrom, subtractThis)

return subtractFrom - subtractThis
end

is the same as


function mySubtract ( foo , bar )

return foo - bar
end

In either case, mySubtract(10, 6) would return 4.

addFive is a function that takes one integer as input, and mySubtract is a function that takes two integers as input. Functions can take other kinds of objects as input, such as strings, tables, and the special objects for ToTPP like unit objects and city objects. Functions can take other functions as input, and this frequently happens in the scifi game.

civ.scen.onUnitKilled(instructions) is a function that takes another function as input. In the examples, however, instructions is defined right there, sort of like writing

displayString("This is an example string")

instead of
local myString = "This is an example string"
displayString(myString)

Feel free to ask for additional explanation on this (or any other) point.
 
Last edited:
Honestly we've covered so much I think I just need to re-read what you've written a few times and put it together before we start going in circles... With that said though - you're definitely getting a prominent place in any credits for any scenario I ever make again :lol:

This is not easy! But I see the carrot at the end of the tunnel....
 
Perhaps we should start out by building a "toy" scenario and add different events to it one at a time. This would allow us to check if our code actually works as expected without having to script everything in a full fledged scenario and then play through the whole thing to test it.

This would act as a sort of tutorial for event scripting in lua where the code is easy to follow. We might even build a library of utility functions that make the scripting easier.

I propose two threads. One thread is a discussion/Q&A thread. The other has posts that describe the event we wanted to implement, the code that implemented the event, and links to relevant discussion in the Q&A thread.

After building a toy scenario, then we build/convert a real one.
 
Good idea... but rather than build even a toy let's just reduce Germanicus to two events (mainly because I've numbered the rules.Txt in it so this will make things simpler):

1. An event achievable in the macro.Txt

2. An event that can only be achieved through lua...

For 2, I'd like to try to build an event where if one unit goes to a square, on the next turn, 3 friendly units are created on a different square. (Essentially, a new way to recruit "Allied" units).

Please let me try to write this myself and you can then correct mistakes.
 
I take it this thread is for the trial/error and the second thread is for the finished product that might help someone. In that case, here is a simple event I selected to try and complete:

Spoiler macro.txt event :
@IF
UnitKilled
unit=Temple of Tamfana
attacker=Rhine Legions
defender=Anybody
@THEN
GiveTechnology
technology=26
receiver=Rhine Legions
Text
The Temple of Tamfana, the most revered holy place of the local German tribes, has been razed to the ground by our Legions. Our audacity has convinced our enemies, both foreign and domestic, of our resolve! Local resistance groups should be less likely to rebel, and many Germans can already be seen trembling.
EndText
@ENDIF


Here is my attempt at coding this (I'll upload the lua file too)

Spoiler Lua Attempt :
--Attempt at minor scripts for Germanicus on the Rhine

print "Germanicus on the Rhine: the Lua Test"

local civlua = require "civlua"

local func = require "functions"

local state = {}

local justOnce = function (key, f) civlua.justOnce(civlua.property(state, key), f)
end

local negate = function (f) return function (x) return not f(x) end end

--Text for Event
local TamfanaText = [[The Temple of Tamfana, the most revered holy place of the local German tribes, has been razed to the ground by our Legions. Our audacity has convinced our enemies, both foreign and domestic, of our resolve! Local resistance groups should be less likely to rebel, and many Germans can already be seen trembling.]]

--Text to show and tech to give when a unit is killed
local TempleKilled = {
[63]={text=TamfanaText, tech=26}
}

civ.scen.onUnitKilled(function (killed, killedBy)
local id = killed.type.id
if TempleKilled[id] then
justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(TempleKilled[id].text))
end)
civ.giveTech(if killedBy.owner = civ.getTribe(0) then civ.getTech(TempleKilled[id].tech))
end
end)
--In line 30, did I make it so the only civ that can get this tech is "0" or in this case, the Rhine Legions?


I take it "killed" in line 27 is referencing (killed, killedBy) in line 24.
 

Attachments

  • macro.jpg
    macro.jpg
    125.8 KB · Views: 142
I did end up making a small toy scenario and found that my attempt above (translated for new scenario) didn't work at all. When I deleted my ( if ...) and just put in a copy of the scifi language, I could get the script to load without an error, but I still can't get the actual event to fire when a legion attacks a warrior. I'm attaching a zip file with the scenario and events. This uses the latest ToTPP version.

Any idea what is going on?

The only script I can get to work is 'print "You should see this in lua console if this work" '

Spoiler This is the entire events file :
--Attempt at minor scripts for Test Scenario

print "You should see this in lua console if this worked"

local civlua = require "civlua"

local func = require "functions"

local state = {}

local justOnce = function (key, f) civlua.justOnce(civlua.property(state, key), f)
end

local negate = function (f) return function (x) return not f(x) end end

--Text for Event
local WarriorsKilledText = [[If you are seeing this, then the text portion at least works]]

--Text to show and tech to give (amphibious warfare) when warriors killed
local WarriorsKilled = {
[2]={text=WarriorsKilledText, tech=2}
}

civ.scen.onUnitKilled(function (killed, killedBy)
local id = killed.type.id
if WarriorsKilled[id] then
justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(WarriorsKilled[id].text))
end)
civ.giveTech(killedBy.owner, civ.getTech(WarriorsKilled[id].tech))
end
end)


civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer) end)
civ.scen.onSave(function () return civlua.serialize(state) end)


Edit... From Tootall's guide:

To write Lua events, use the bindings provided by the `civ.scen` library.

I can locate no such file - is it possible this is needed and missing?
 

Attachments

Last edited:
Hi guys,

Just to let you know, I'm following this thread with great interest. I think building a lua script from scratch based on John's "Germanicus" scenario is a great idea. It will let us see the difference between the two languages and how we can translate macro events into lua.

Great initiative!
 
I'm having trouble getting anything to happen when a unit is killed.

I managed to get a text box to display when the game is loaded by using

civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer)
civ.ui.text("Open Game") end)

so some events are working.

Perhaps @TheNamelessOne could provide us with a small example of a working unit killed trigger.
 
You got that to work in the scenario I posted? I can't replicate it. The absolute only thing I can get to work is the print in the lua console itself.

@tootall_2012 - you've played around with this a bit - am I supposed to run any kind of delevent file / is there an actual file called a civ.scen library? My download of ToTPP did not include this?
 
Great thread. I haven't had time to play around with this since I posted in the ToTPP thread a couple of weeks ago about what I believe is the same issue. There's a bug which TNO aims to fix for the next release. In the meantime, try the following: place a dummy events.txt file in your working folder; just an empty text file called events. What happens then?
 
In the meantime, try the following: place a dummy events.txt file in your working folder; just an empty text file called events. What happens then?

This has prompted a change in that instead of only seeing the print script, I now see this error:

Spoiler Error :
Welcome to the TOTPP Lua console.
Running Lua 5.3.1 Copyright (C) 1994-2015 Lua.org, PUC-Rio

Keyboard shortcuts:
Ctrl-F1: Prints this help text
Ctrl-F2: Aborts running script
Ctrl-F5: Reruns last script
Up/Down: Scrolls input history

You should see this in lua console if this worked
D:\Test of Time TOTpp14\lua\functions.lua:5: bad argument #1 to 'gsub' (string expected, got nil)
stack traceback:
[C]: in function 'string.gsub'
D:\Test of Time TOTpp14\lua\functions.lua:5: in function 'functions.split'
(...tail calls...)
D:\Test of Time TOTpp14\Scenario\LUA TEST\events.lua:24: in function <D:\Test of Time TOTpp14\Scenario\LUA TEST\events.lua:21>


And here is the complete script:

Spoiler Script :
print "You should see this in lua console if this worked"

local civlua = require "civlua"

local func = require "functions"

local state = {}

local justOnce = function (key, f) civlua.justOnce(civlua.property(state, key), f)
end

local negate = function (f) return function (x) return not f(x) end end

--Text for Event
local WarriorsKilledText = [[If you are seeing this, then the text portion at least works]]
local IntroText=[[does this work?]]

-- The `onTurn` function runs its argument every turn, with the turn number passed as `turn`.
civ.scen.onTurn(function (turn)
-- Show the intro text on the first turn. I'm omitting `justOnce` since it won't be turn 1 more than once anyway.
if turn == 1 then
civ.ui.text(func.splitlines(introText))
end
end)

--Text to show and tech to give (amphibious warfare) when warriors killed
local WarriorsKilled = {
[2]={text=WarriorsKilledText, tech=2}
}

civ.scen.onUnitKilled(function (killed, killedBy)
local id = killed.type.id
if WarriorsKilled[id] then
justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(WarriorsKilled[id].text))
end)
civ.giveTech(killedBy.owner, civ.getTech(WarriorsKilled[id].tech))
end
end)

civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer)
civ.ui.text("Open Game text--let's see if this actually works") end)


civ.scen.onSave(function () return civlua.serialize(state) end)



(note, line 5 = local civlua = require "civlua")
line 24 = civ.ui.text(func.splitlines(introText))
line 21 = civ.scen.onTurn(function (turn)

I am guessing the issue is with one of these three given the #s in the error - not sure what is the issue though (haven't really been able to look at it closely yet either though - have to go put the kid to bed).
 
Line 5 in functions.lua = string.gsub(s, pattern, function(c)

(of this complete function):

local function split(s, sep)
local fields = {}
local pattern = string.format("([^%s]+)", sep)
string.gsub(s, pattern, function(c)
table.insert(fields, c)
end)
return table.unpack(fields)
end
 
If I take the Lua Test scenario as JPetroski uploaded it, and change

civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer) end)

to

civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer)
civ.ui.text("Open Game") end)

I can get a message when I load a game. Specifically, if I start a new scenario, save the game and then load the game, when I load the game I will get a text box with Open Game.

Catfish's recommendation to include an empty events.txt enables me to get unit killed stuff to happen.

The attached file will include an events.lua that prints text and bestows amphibious warfare to the killer when a warrior is killed.

This works both if the killed warrior is attacking or defending.

I changed the way the event was written to remove the use of a table. I think (though I could be wrong) that the original event was checking if an engineer was killed, not a warrior.
 

Attachments

I changed the way the event was written to remove the use of a table. I think (though I could be wrong) that the original event was checking if an engineer was killed, not a warrior.

I thought units start with "0" but I could be wrong... in any event, if you changed "2" to "3" we'd quickly find out.
 
Back
Top Bottom