Lua Scripting Possibilities

JPetroski

Deity
SLeague Staff
Joined
Jan 24, 2011
Messages
4,794
Now that TNO is back and stabilizing ToTPP 1.4, I thought I’d start a thread for designers to run through some ideas about how they’d like to use lua.

I want to know if it would be possible to use lua to cut down on the tedious tasks in one of my projects.

I’ve been working on a multiplayer scenario that depicts the Allied strategic bombing campaign over Germany. It has gone through a few versions. The latest version was grand, but not much fun. The reason it wasn’t fun is that it became tedious due to the large number of units that had nothing to do with the air war.

I tried to simulate the German economy / bomber offensive against it by having this sequence:

-Events check to see if Germany controls a city in occupied territories (France, Denmark, etc.)
-Each turn, “raw materials” units spawn in those cities
-raw materials are brought to a central location/killed – this gives German civ money
-money is spent to buy expensive trade units which then trade with “industry” cities to fuel research
-research is entitled “production sequence” – researching it spawns “finished goods” unit
-finished goods unit is brought to central location/killed, this gives Luftwaffe civ money to buy aircraft

By forcing a deficit on the Luftwaffe, I forced the player to go through this industrial sequence to keep the lights on and buy more aircraft.

Did it work? Yes. Was it fun? No. It made a scenario that was supposed to be about the air campaign instead a scenario where maybe 10 minutes was spent in the air, and 50-60 was spent moving around rail cars.

Again, could lua be used to automate tedious tasks? Specifically:

1.
-If TNO does add transport capabilities for air units to carry air units (bombs), then I’d want bombers to carry bomb units. Loading them up would be a tedious task. Imagine trying to put 1-3 bombs on each of 30-50 bombers about to leave. It would take forever. It wouldn’t be fun.


-Is it possible to have a scrip that basically:

@IF
Bomber in City
@And
Doesn’t have a bomb attached
@THEN
Create (# of) Bomb Unit(s) and attach to bomber?

So, basically any time that a bomber is in a friendly city at the start of a turn, it gets loaded with a bomb automatically, and all the player has to do is take off and go destroy something.

2.
-Is it possible to abstractly represent the French Resistance (and also create a real need for the German player to build ground units, but not necessarily have to move them each turn) by doing something like this:


@IF
Unit type=German Army
Threshold=100
@And
Location [map coordinate box, like an area in France--but any of the 100 units could be anywhere in the box so long as 100 are somewhere in the box]
@THEN
Give Money
Who=German
How much=$10,000

BUT

@IF
Unit type=German Army
Threshold=80
@And
Location [map coordinate box, like an area in France]
@THEN
Give Money
Who=German
How much=$8,000 (less money because less German army units to keep resistance at bay)

Basically, making it so that the Allies have a reason to attack German army units and the Germans have a reason to build them and station them in France (or wherever).

3.
-Is it possible to automate the movement of certain units every turn before the player touches them? I would want the game to check for a unit (like a train) and move it along a certain path to its destination without making the player interact with it to save time.


If #1 is possible, then it makes sense to incorporate bomb-carrying bombers into this scenario. If it isn’t, then that feature would be “cool” but will also make the scenario a pain to play.

If #2 is possible, then I can basically use it to simulate the entire economy without having to have a million units move around (I could check for factories, sub pens, whatever I want).

If #3 is possible, then I can still have opportunities for Allied fighter bombers to attack trains without making the movement of those trains too burdensome for the Germany player.

TNO has gone to tremendous trouble to give us this tool so I would love to understand it better and incorporate it!
 
While this seems like a splendid idea, and a valid exercise in time-saving, perhaps the micro-management of the scenario is too intense in any case.
I would respectfully remind that we don't wish to over-load TNO with scenario-specific requests, as that sort of selfish to everyone else who wants more general features addressed.

That said...Air/Land carrier units, setting load limits on carrying units, are things CIV2 could use. I too would welcome them. But first, bug fixing and scenario tools.
 
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.

Secondly, I noticed he asked if anyone has used lua in a scenario yet:

As for my future plans for the project, I would really like to see the Lua integration in action in a well-designed scenario. Have there been any attempts to integrate it already?

I’m guessing he asked because he went to all the trouble to include lua in this wonderful gift to us, and may have been a bit disappointed to find that no one has really used it in the few years since he took sabbatical. I know I would be disappointed.

I have a few guesses for why no one(?) has built one yet:

1. We don’t understand how to use it very well;
2. The ToTPP version that supported it had some stability issues that TNO is now fixing, so some may have been hesitant to learn something that might break their work;
3. We don’t really know what exactly is the great benefit over the events text (but would love to learn).

There are—what--about five of us left building scenarios? How many of us are likely to finish one in the next four to six months? Maybe two? If anyone who knows how to work lua wants to see it in action in a scenario, now is the time to teach us.

If someone is willing to walk me through, I’m willing to incorporate it in Over the Reich.

It is a scenario that could be released soon. It has unique issues that perhaps a unique scripting engine could help solve. It also has unique possibilities that scripting could really enhance. I think it is an ideal candidate to be a scenario that brings lua to the forefront, and hopefully helps to keep the game going as ToTPP has.

So there’s my pitch.

Now as for you, Curt, I know you mean well. But would you please stop acting like the poor guy (deified as he may well deserve to be) is just some apparition that will blow away in the wind the second someone shows an interest in his work? I’m pretty sure an audience is part of the reason we all create.

Editing some of my morning snippiness away with apologies if it was read.
 
Last edited:
Anyway... Back to the thread... To expand on Scenario 1 from my first post...

TNO has stated that he intends to add different unit transport capabilities in the next major release. This is awesome in and of itself. It solves so many problems, particularly ranged attack and one unit being destroyed in each combat.

Currently, designers give a land unit the ability to carry aircraft, but this only means aircraft can land on them - they don't follow them around, so you have to awkwardly "fly" your ammunition onto your artillery unit. This patch should solve that.

Currently, there's really no way to prevent one unit from being destroyed in combat every time. But with this patch, you could have units carry "ammo" (be it bombs, bullets, arrows, cannonballs, whatever). The ammo could be too weak to kill a full strength unit, but could damage it. In this way, one unit could attack/shoot at another, and both could live.

These are both awesome things that transport capabilities will open up. But they'll both ultimately only delay the problem of having to "fly" ammo to a unit. While the unit (in this case an artillery gun) can move from a home city with ammo attached, what about when it is deep in enemy territory and needs more ammo? It either must be brought up by some different unit and then "flown" to the artillery unit, or we need to find some way to render it where we want it via scripting.

Basically, what I'd like to tell the script to do is:

1. Select a particular map to scan (0,1,2, or 3)
2. Scan that map for a particular type of terrain - (in my scenario, "bomber airfield," though I'd like to choose a terrain type, or "any")
3. Scan that terrain for a particular type of unit (in my scenario, a B-17)
4. Scan those units for ones that are "empty" (meaning they don't have ammo attached, in this case "250lb bombs")
5. Create a 250lb bomb unit on the same square that the B-17 unit is located (I want it to do this for every B-17 unit on one of these squares).
6. Set that 250lb bomb's status to "sleep" (so it will take off with the unit, assuming the same mechanism is used for this as there is for ships).

Ideally, between steps 4 and 5 above, there'd be an "AND" search:

AND
A. Select a particular map to scan (0,1,2, or 3)
B. Scan that map for a particular type of unit (in my scenario, a "munitions factory")
C. Determine if there is a certain number or threshold of those "munitions factory" units present on the map (let's say 5)
D. If there are 5 munitions factories on the map,then move to step 5 above (Create a 250lb bomb unit on the same square that the B-17 unit is located).

So in this way, we've replicated the supply chain without forcing players to actually physically move the supplies (which is no fun).

Some might take the above process so far as to have all units use ammo. Others might restrict it simply to ranged attack units. Regardless, this seems like the sort of script that would convince designers to use lua.

In Over the Reich, I'd have certain cities built on "bomber airfield" or "fighter airfield" terrain and use it this way.

If I were building another Roman scenario, I might have engineers transform terrain into "siegeworks" and then park a ballista on that, and have ammo show up there.

If I were building a ground combat game with artillery, I'd use the feature to differentiate between siege artillery and mobile artillery by using the Roman method above for siege artillery, and by having the script "Scan the map for a particular type of terrain = all" for mobile artillery. It would really differentiate the mobile AA (which might be weaker, but could be deployed quickly turn by turn) vs. siege artillery (which would be stronger, but had to "set up" before being restocked).

Endless possibilities! But is the "restocking script" really possible?
 
I've attached some Lua code that has the logic for implementing request 2. I haven't programmed in Lua before (in fact nearly all of my programming experience is in MATLAB) so I'm not 100% sure about the syntax (the specifics would also need to be changed, obviously).

Automating the economy is probably doable using the teleport unit option, but that would mean either hard coding the path of each train or implementing path finding into lua

Automatically loading bombers seems to be a problem, because there has to be some trigger to tell the game to run a lua script. It may just be easier to let bombers die on missions and re-create them with a certain probability.
 

Attachments

  • OTR.lua.zip
    880 bytes · Views: 159
If #1 is possible, then it makes sense to incorporate bomb-carrying bombers into this scenario. If it isn’t, then that feature would be “cool” but will also make the scenario a pain to play.

If #2 is possible, then I can basically use it to simulate the entire economy without having to have a million units move around (I could check for factories, sub pens, whatever I want).

If #3 is possible, then I can still have opportunities for Allied fighter bombers to attack trains without making the movement of those trains too burdensome for the Germany player.

TNO has gone to tremendous trouble to give us this tool so I would love to understand it better and incorporate it!

Now, #1 is not (yet) possible of course, since that feature hasn't been completed yet, #2 and #3 are exactly the kinds of event that will make Lua shine in a scenario, for example:

Code:
function isInSquare(left, top, right, bottom)
  return function (unit)
    return unit.x >= left and unit.x <= right and unit.y >= top and unit.y <= bottom
  end
end

function moveAlongPath(unit, path)
  for _, coordinate in ipairs(path) do
    local x, y = table.unpack(coordinate)
    local nextTile = civ.getTile(x, y, unit.z)
    civ.teleportUnit(unit, nextTile)
    civ.ui.centerView(nextTile)
    civ.sleep(500)
  end
end

civ.scen.onTurn(function (turn)
  -- Some made-up numbers
  local isInFrance = isInSquare(20, 24, 60, 70)
  local germans = civ.getTribe(2)
 
  -- Give the Germans 100 gold for each unit currently in France
  for unit in civ.iterateUnits() do
    if unit.owner == germans and isInFrance(unit) then
      germans.money = germans.money + 100
    end
  end

  -- Move a specific unit along some path
  local unit = civ.getActiveUnit()
  -- Here, the path is represented as a list of x,y coordinates
  moveAlongPath(unit, {{108, 150}, {108, 152}, {107, 153}, {106, 154}, {105, 155}, {104, 156}})
  -- And possibly spend the unit's movement allowance
  unit.moveSpent = unit.type.move
end)
 
Last edited:
Automatically loading bombers seems to be a problem, because there has to be some trigger to tell the game to run a lua script. It may just be easier to let bombers die on missions and re-create them with a certain probability.

Could the trigger be "Turn=-1" (i.e., check every turn) like in events?

I really appreciate both of your help here.
 
Yes, you'd just put it in (the function executed by) civ.scen.onTurn, so it would run every turn.
 
Ok... Well... I think that while you're working on your projects, what I will do to try and learn this is maybe take my old, completed scenario, Germanicus on the Rhine and try to convert it into a lua scenario complete with a few of the things above... Please don't let me derail you, but I am very interested in using this - it was just a bit daunting with you gone and also there were rumors that 1.14 would crash. Thanks for everything you've done here - I didn't think I'd ever play this game again until I stumbled across your program.
 
I took a stab at one event from Germanicus on the Rhine. Not totally certain I understand all of this but some of it (I think) came together. Posting here for 1) help :) and 2) to help others...

Spoiler Events.txt event :

@IF
UnitKilled
unit=I "Germanica"
attacker=Anybody
defender=Rhine Legions
@THEN
PlayWaveFile
disaster.wav
CreateUnit
owner=Rhine Legions
unit=massacre
count=1
veteran=no
homecity=Batavodurum
locations
2,2
endlocations
GiveTechnology
technology=22
receiver=Rhine Legions
Text
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.
EndText
@ENDIF


This is the lua version (not using a spoiler because I embedded questions with the "--" which I take it mean the same thing as ";" in the macro.txt

print "Germanicus on the Rhine"
--I don't understand what this does

local civlua = require "civlua"
--I take it this basically locates the civlua program you created?
local func = require "functions"
--not quite sure what to make of this - I assume it locates some sort of code?
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
--I am totally lost by the three lines. Breaking down what they are actually saying would be very helpful.

--Various texts used in scenario
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 LegionKilled = {
[54]={text=LegioIText, tech=22},}

local massacreLocations = {[31]={{2,2,0}}
--How many of the { do I need to bracket this given that I only have one unit that I'm trying to create? What does each bracket represent in your example for the science game?

--So, is basically everything up until this point creating a "file" so to speak, that any ".onUnitKilled" is going to look to?

civ.scen.onUnitKilled(function (killed, killedBy)
local id = killed.type.id
if LegionKilled[id] then
--does [id] have to be [id] or [54] in this case? Is [id] telling it to look for whatever is in brackets? (In case I wanted to have several unit killed events in game?
--Are lines 24-26 basically "talking" to lines 18-19?

justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(LegionKilled[id].text))
end)
--what does "splitlines" mean?
civ.giveTech(killed.owner, civ.getTech(LegionKilled[id].tech))
civlua.createUnit([31],civ.getTribe(0), massacreLocations[id][1], {randomize-false, veteran=false})
end
end)
--I am not sure what the "[1]" at the end of massacreLocations means?
--Is this structure (lines 26 - 38 with my commentary) basically the "IF unitkilled?"
--Also, not sure how or where to add home city?
--(I did not use the playwavefile here, either...)
--Is there an order/sequence that these must be done, like in the events.txt?

(Edit to upload screenshot since I reference lines - also to show what it looks like in editor to those who haven't seen it).
 

Attachments

  • first try.jpg
    first try.jpg
    166.3 KB · Views: 202
Last edited:
This is a good idea. I learned the Macro event system years ago by reverse engineering other peoples events files. Learning how the flags and masks was a steep learning curve, but once again, I used other peoples scenarios to work out what was going on (as well as the excellent guides available).

I'm very keen to learn Lua, but have found it a daunting prospect. Creating events has always been my least favourite aspect of scenario design and a massive job. I've improved with practice but I still dread it. Someone has to be the first to jump in and then the rest will follow.
 
If you haven't done it already, Drew, go and download a Lua Editor if only because when you start writing one of these events in it, different words are different colors and you start to see (kind of) how they all work together. (Kind of like if @IF showed up blue and ";" showed up green, etc.)

It is definitely daunting which is why I've started this thread. A google search isn't particularly helpful. I've located an enormous document about lua coding, but it might as well be in Greek to me right now - I need a bit of it translated to "civ" to take full use. Hopefully, we can build up some autonomy here.

I think updating Germanicus will be a better way to learn than building a new scenario. It's also single player so more likely more people will play it/see it (TNO could even have a scenario to play ;) ). My plan is to transition all current events to lua and then add random other features we haven't had before. For all I know this will destroy game play balance on an already difficult scenario but that's not really what I'm after - I want to see what this program can do. It could make good use of scenarios #2 and #3 above, as well as #1 if that feature is implemented in the future.

The immediate thing that jumps to mind is that this is a powerful engine to create an "actual" revolt risk (like in paradox titles). Civ2 "revolts" are pretty much a joke -- lose city production for a turn or two. From what I'm seeing, scenario #2 above could easily be tweaked to have an actual revolting army spring up and start causing havoc behind your lines. Imagine a scenario about Caesar in Gaul... He was basically marching around all over the place and had to put down several revolts behind his back over the years (Sacrovir, Vercingetorix). With events.txt, we're somewhat limited in how to simulate this as it tends to be caused by static situations. With lua, it seems they could be much more fluid, which should enhance replayability greatly.

I'm so excited, I even told my wife. (She's not impressed).
 
I tend to get blank stares when I enthusiastically explain the latest Civ2 innovation to the Mrs. They just see maps and little pixel armies. To us, it is out art medium. A thing of beauty. An outlet for our creativity.
 
I took a stab at one event from Germanicus on the Rhine. Not totally certain I understand all of this but some of it (I think) came together. Posting here for 1) help :) and 2) to help others...

print "Germanicus on the Rhine"
--I don't understand what this does

Looks like this line prints "Germanicus on the Rhine" when you open the Lua Console

local civlua = require "civlua"
--I take it this basically locates the civlua program you created?
This line lets you access functions defined in the file "civlua.lua".

For example, the "justOnce" function below was defined in "civlua.lua", so to use it in this script we write civlua.justOnce(...)

local func = require "functions"
--not quite sure what to make of this - I assume it locates some sort of code?
See above. In this case we are accessing functions in "functions.lua"
Somewhere below, the function "splitlines" is accessed by writing func.splitlines


local state = {}

state is an object (list?) that will keep track of information that must be stored as part of the saved game file. At the bottom of events.lua, there are lines that facilitate this:

-- `onLoad` is responsible for restoring scenario state from a string. `onSave` is responsible for returning the state as a string. The implementations given here should suffice as a basis for most scenarios.
civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer) end)
civ.scen.onSave(function () return civlua.serialize(state) end)

local justOnce = function (key, f) civlua.justOnce(civlua.property(state, key), f) end
This is making a function named justOnce that takes a string "key" and a function "f" and runs "f" if justOnce has never been called with "key" before. Most of the functionality is defined in the civlua.lua file

local negate = function (f) return function (x) return not f(x) end end
--I am totally lost by the three lines. Breaking down what they are actually saying would be very helpful.

negate takes an input function and outputs another function which returns the negation of the first.

just below that, isAlien is defined, and returns true if the tribe is Alien, and false otherwise

then, isHuman is defined to give true when isAlien is false, and false when isAlien would return true

justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(LegionKilled[id].text))
end)
--what does "splitlines" mean?

It looks like it inserts newline characters so that your output text doesn't run off the side of the display window but instead starts on the next line like you would expect.

This (incomplete) explanation was the result of some web searches and searching in the lua files to see how variables were used later. Hopefully someone with lua experience (TNO?) will correct me if I've made some mistakes.[/QUOTE]
 
--Various texts used in scenario
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 LegionKilled = {
[54]={text=LegioIText, tech=22},}

local massacreLocations = {[31]={{2,2,0}}
--How many of the { do I need to bracket this given that I only have one unit that I'm trying to create? What does each bracket represent in your example for the science game?

Here are a couple links that explain tables in lua
https://www.lua.org/pil/2.5.html
https://www.lua.org/pil/3.6.html

The { and } are used to create a table. So, LegionKilled is a table, which contains another table. Key 54 of the LegionKilled table (which can be accessed by LegionKilled[54]) is a table of two elements, the LegiolText string, with key text, and an integer with key tech. These can be accessed with .text or .tech

So LegionKilled[54].text yields LegiolText

As far as I can tell, if your table is indexed by integers or strings, then you use the [] to get the associated table element, otherwise you use the . to get it.

e.g.
myTable = {"one"=1,"two" =2}

myTable["one"] == 1

yourTable = {one = 1, two = 2}

yourTable.one == 1

If you are keeping a table of massacreLocations, then you need one pair of {} for the table massacreLocations, and another pair of {} for each location, i.e. the 2,2,0 square as {2,2,0} since the location information is stored as a 3 element table.

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

The original file giantLocations had three tables, each of those tables had three lists of locations, and each of those locations was specified by a table of three elements.


--So, is basically everything up until this point creating a "file" so to speak, that any ".onUnitKilled" is going to look to?

Yes.

civ.scen.onUnitKilled(function (killed, killedBy)
local id = killed.type.id
if LegionKilled[id] then
--does [id] have to be [id] or [54] in this case? Is [id] telling it to look for whatever is in brackets? (In case I wanted to have several unit killed events in game?

Let's not use id as our variable. Let's call it unitType

civ.scen.onUnitKilled(function (killed, killedBy)
local unitType = killed.type.id
if LegionKilled[unitType] then

So, we're loading an integer into the unitType variable, and then feeding it into LegionKilled[unitType].

If unitType == 54, then we call LegionKilled[54], which the if statement presumably evaluates as true since there is something in the LegionKilled table with key 54 . The if statement body is then executed.

If some other unit were killed, say unitType == 33, then LegionKilled[unitType] == LegionKilled[33] and the if statement would be false unless LegionKilled had an entry with key 33.

If you want to have multiple unit killed events in the game, you can also make a list of if statements checking for each appropriate unit. If you want to do something similar in each case, a table might be an easier way to write and change the function.

--Are lines 24-26 basically "talking" to lines 18-19?
justOnce("killed" .. killed.type.name, function ()
civ.ui.text(func.splitlines(LegionKilled[id].text))
end)

Yes, the LegionKilled[id].text is getting information from lines 18-19. Incidentally, the justOnce function is checking and perhaps writing information to the state table at line 10.

civ.giveTech(killed.owner, civ.getTech(LegionKilled[id].tech))
civlua.createUnit([31],civ.getTribe(0), massacreLocations[id][1], {randomize-false, veteran=false})
end
end)
--I am not sure what the "[1]" at the end of massacreLocations means?

massacreLocations[54][1] means take the table which is the 54th element of massacreLocations and extract the first element of that table. In this case it would evaluate to 2. You don't want this. giantLocations[id][1] was a table containing 10 potential places to spawn giants. giantLocations[id] was three tables, so the [1] chose the first table.

It looks like civlua.createUnit needs a table of locations to spawn units. So, if we want to spawn at {2,2,0}, we need to pass it {{2,2,0}}. So, we should change the massacreLocations definition to

local massacreLocations = {[31] = {{2,2,0}}} and then insert massacreLocations[id] to create units when id == 31.

--Is this structure (lines 26 - 38 with my commentary) basically the "IF unitkilled?"

Yes

--Also, not sure how or where to add home city?

from the civlua.lua file:

--[[
Create (a) unit(s) of type `unittype`, owned by tribe `tribe`. This is the Lua implementation of the CreateUnit action.
`locations` is a list of tile coordinates, like {{0, 0, 0}, {1, 1, 0}, ...}.
`options` is a table with any of the following keys:
- `count`, the number of units to create;
- `randomize`, randomize the list of locations;
- `inCapital`, create unit(s) in the tribe's capital city;
- `veteran`, sets the veteran flag on the created unit(s);
- `homeCity`, the home city for the unit(s).
--]]

So, change {randomize-false, veteran=false} to {randomize=false, veteran = false, homeCity = "MyCity"}

--Is there an order/sequence that these must be done, like in the events.txt?

Let's look at this piece of code

civ.scen.onUnitKilled(function (killed, killedBy)
local unitType = killed.type.id
if LegionKilled[unitType] then
-- some stuff to do
end -- ends the if statement
end) -- ends the function started by function (killed, killedBy), which we are writing, and closes civ.scen.onUnitKilled

The game runs this code whenever a unit is killed, to check if anything must be done, starting at the top and going down line by line

1. Creates variable unitType, gives it the value of the killed unit type's id (an integer)
2. Checks if LegionKilled[unitType] is in the LegionKilled table.
3. If LegionKilled[unitType] is in the table (more generally, if the if statement is true), executes code line by line
4. End closes the if statement. If the condition was false, the computer starts executing code here
5. Ends the function the computer is executing

Now consider this code

civ.scen.onUnitKilled(function (killed, killedBy)
if LegionKilled[unitType] then
-- some stuff to do
end -- ends the if statement
local unitType = killed.type.id
end) -- ends the function started by function (killed, killedBy), which we are writing, and closes civ.scen.onUnitKilled

The code
1. Checks if LegionKilled[unitType] is in the LegionKilled table.
This is a problem, because we can't evaluate LegionKilled[unitType], since we don't yet know what unitType is supposed to be. I don't know enough about Lua to know what will happen, but it won't be what we want. The computer won't look further down the code to figure out what unitType is.

Hope this helps, got to go for now.
 
This is outstandingly helpful! Thank you very much! I owe you!

Just to reinforce my understanding on a few points (I do feel like it's coming together but I'm sure some of these are simple/stupid questions):

-- `onLoad` is responsible for restoring scenario state from a string. `onSave` is responsible for returning the state as a string. The implementations given here should suffice as a basis for most scenarios.
civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer) end)
civ.scen.onSave(function () return civlua.serialize(state) end)

What does (buffer) do? I see it brackets state = civlua.unserialize

For that matter - and I'm JUST taking a stab at this...

civ.scen.onLoad --telling the code to consider next statements when scenario loaded?
(function (buffer) state = civ.unserialize(buffer) --telling the code to consider things... Basically as a blank slate that haven't had a defined order/history yet?
end) --ending the function/sequence for when a scenario is loaded

civ.scen.onSave--telling the code to consider next statements when scenario saved, so one can go back to it?
(function ()--not sure about "()" did you mean to type "{}"?
return civlua.serialize(state) end) -- Is this telling the code to remember the sequence of events that has occurred in the save file?

If so, I'm confused because it seems like .onLoad is saying wipe the sequence, and .onSave is saying preserve it... If that makes sense...

Let's not use id as our variable. Let's call it unitType

Did you change the variable from id to unitType for illustrative purposes, or could I change [id] to [unitType] or [pancakes] or [whateverichoose]?

As far as I can tell, if your table is indexed by integers or strings, then you use the [] to get the associated table element, otherwise you use the . to get it.

-So, in Civ terms - because technologies, improvements, (and apparently now at least in lua, units and probably terrain) are all represented by an integer - if I'm talking about any of those things, I could bracket that integer with "[ ]" -- definitely easier than typing out the unit names...


I'm not totally certain I understand what a string is... And it seems pretty critical to understand other things you are explaining. I tried looking it up myself. The problem with that lua.org is that it explains things in a way that people who are good at math understand. I took one class in college, barely passed, and only understand things in Civ :) :

from https://www.lua.org/pil/2.4.html
2.4 – Strings
Strings have the usual meaning: a sequence of characters. Lua is eight-bit clean and so strings may contain characters with any numeric value, including embedded zeros. That means that you can store any binary data into a string. Strings in Lua are immutable values. You cannot change a character inside a string, as you may in C; instead, you create a new string with the desired modifications, as in the next example:

a = "one string"
b = string.gsub(a, "one", "another") -- change string parts
print(a) --> one string
print(b) --> another string

All gobblygook to me!!!

massacreLocations[54][1] means take the table which is the 54th element of massacreLocations and extract the first element of that table. In this case it would evaluate to 2.

Ok ... to reword/check understanding:

massacreLocations[31] (I think you typed 54 by mistake) - 31st element corresponds to 31st unit, in this case Massacre unit (unit in the 31st unit slot)... first element of table would be the first "2" of 2,2,0, because there is only one location?

What if there were three:

For three possible locations:
local massacreLocations = {[31]={{2,2,0},{2,4,0},{2,6,0}}}

In that case, if I wanted to select {2,4,0}, would [2] be appropriate? And if I wanted {2,6,0} would [3]?

So, change {randomize-false, veteran=false} to {randomize=false, veteran = false, homeCity = "MyCity"}

So, assuming I had the three locations above, if after a unit in the 54th spot was killed, I wanted text to pop up, I wanted to create 27 units that are in the 31st unit spot, I wanted them to show up at 2,4,0, I wanted them to be veteran, and I wanted their home city to be none, then:

civ.scen.onUnitKilled(function (killed,killedBy)
local unitType = killed.type.id
ifLegionKilled[unitType] then
civlua.createUnit([31],civ.getTribe(0), massacreLocations[id][1], {randomize=false, veteran=true, count=27, homeCity=none})
end -- would end the IF statement
end) would end the function for (killed, killedBy)

BUT... For this all to work, I'd need the following three things to be somewhere in the code:

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 LegionKilled = { --Legio I "Germanica" killed
[54]={text=LegioIText, tech=22},}
local massacreLocations = {[31]={{2,2,0},{2,4,0},{2,6,0}}}

Is that right?

Does it matter where all of these things are in the code? Or they simply must be present? I see TNO listed the text up front - does that matter? It might be easier to learn to do it that way - keep the functions in one place - but if one wanted to keep each event "compartmentalized," with the whole thing written out in one little area, would that be OK too?


Hope I am at least starting to get this... I really, really appreciate your help.
 
I guess part of what is throwing me is in the above example you almost have @THEN in two places...

ifLegionKilled[unitType] then, obviously (where I'm creating the massacre units)

But also in the bold below

local LegionKilled = { --Legio I "Germanica" killed
[54]={text=LegioIText, tech=22},}

Correct?
 
-- `onLoad` is responsible for restoring scenario state from a string. `onSave` is responsible for returning the state as a string. The implementations given here should suffice as a basis for most scenarios.
civ.scen.onLoad(function (buffer) state = civlua.unserialize(buffer) end)
civ.scen.onSave(function () return civlua.serialize(state) end)
What does (buffer) do? I see it brackets state = civlua.unserialize

For that matter - and I'm JUST taking a stab at this...

civ.scen.onLoad --telling the code to consider next statements when scenario loaded?
(function (buffer) state = civ.unserialize(buffer) --telling the code to consider things... Basically as a blank slate that haven't had a defined order/history yet?
end) --ending the function/sequence for when a scenario is loaded

civ.scen.onSave--telling the code to consider next statements when scenario saved, so one can go back to it?
(function ()--not sure about "()" did you mean to type "{}"?
return civlua.serialize(state) end) -- Is this telling the code to remember the sequence of events that has occurred in the save file?

If so, I'm confused because it seems like .onLoad is saying wipe the sequence, and .onSave is saying preserve it... If that makes sense...

TNO will have to explain the onLoad and onSave functionality and how specifically to use it. onSave turns the table state into a string (which I will explain below), and presumably writes it into the .sav file you get when you save a game. onLoad takes the string written in the save file and turns it back into a table to be used and modified as the game is played.

Presumably, you could do other things when the game is saved or loaded by expanding the functions.

function () means we're defining a function that doesn't need any variable input.

Let's not use id as our variable. Let's call it unitType
Did you change the variable from id to unitType for illustrative purposes, or could I change [id] to [unitType] or [pancakes] or [whateverichoose]?

You can name a variable pretty much whatever you like (the programming language will have some restrictions) as long as it doesn't create conflicts. As far as I know, the code I wrote should run just the same.

-So, in Civ terms - because technologies, improvements, (and apparently now at least in lua, units and probably terrain) are all represented by an integer - if I'm talking about any of those things, I could bracket that integer with "[ ]" -- definitely easier than typing out the unit names...

bracketing with "[]" is specifically used to get an element from a table. myTable[4] gives me an object from myTable based on the key 4. If I feed an integer into a function, I don't use the "[]". For example, if I have a function addOne that takes an integer, I write addOne(4) and not addOne([4]).

We would have to look at how specific functions are written to know if you can write 1 instead of "Settler".

All gobblygook to me!!!

Here's a little programming 101:

When we program we're writing instructions for the computer to follow.

We can define variables by giving them a name and value, for example

x = 17 -- This is basically telling the computer to get a piece of memory and put 17 in it so it can be used later

Then, when I write x, the computer looks at the piece of memory labeled x and finds 17

x = 32 -- This is saying erase whatever was in the place we labeled x and put 32 there instead

local x = 32 limits the parts of our program that can make changes to x, and sets x to 32

We can have integers as data. We can also have boolean values, which are true and false. We can also have strings, which are just a list of characters.

"seventeen" (note the quotation marks) is a piece of data that can be stored in the variable seventeen.

seventeen = "seventeen"

but I can also have

seventeen = 17

or

seventeen = 13

meaning the integer 13 is stored in the memory location marked 17.

seventeen + 4

would then return 17.

Functions are instructions for the program to follow. myFunction(myInput) tells the program to find the instructions called myFunction and run them with the data found at myInput. If my function doesn't need any input, then I write myFunction(). We can discuss this more later.

Note that you can have comments in your code, anything after -- in lua, that people can read but won't be executed.

Ok ... to reword/check understanding:

massacreLocations[31] (I think you typed 54 by mistake) - 31st element corresponds to 31st unit, in this case Massacre unit (unit in the 31st unit slot)... first element of table would be the first "2" of 2,2,0, because there is only one location?

massacreLocations[31][1] would evaluate to 2.

It's because instead of having a table of locations, we have a location. We could have {{2,2,0}} which is a table of locations that happens to only have one location.

What if there were three:

For three possible locations:
local massacreLocations = {[31]={{2,2,0},{2,4,0},{2,6,0}}}

In that case, if I wanted to select {2,4,0}, would [2] be appropriate? And if I wanted {2,6,0} would [3]?

massacreLocations[31][2] would, indeed, select {2,4,0}

%%%%%%%%%%%%%%%%%%%%%%

I'll answer some more later.
 
Prof. Garfield - beyond helpful - this is really starting to come together. THANK YOU.

I don't want to hijack my own thread (though I kind of did by taking "lua possibilites" into "teach this dummy how to program" :) ) but I did want to side step for one minute because I'm hoping to get some other designers interested in this, and I think, scouring through THIS thread I've found something that will really intrigue people...

Multiple Maps
-Currently they stack one on top of the other. This works great for Over the Reich and @tootall 's "Vietnam," or any scenario where you're going to have low-alt, high-alt situations. I think I speak for pretty much every designer on earth, however, when I say what we've really wanted to be able to do all these years is have maps flow east/west and north/south.
-Currently that is awkward. You can either have one map be a mirror image (picture the USA facing backwards, and Europe facing the correct way) to allow native transport/teleport sites to match up, or you can give certain cities teleport improvements (but they can only transport one unit per turn each).

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})?
Regardless:

"Teleports (i.e. moves at no cost) unit 'unit' to tile 'tile'. The unit is moved regardless of whether it is a valid location for the unit. To check this, see 'civ.canEnter' and civ.lua.isValidUnitLocation.'

Now, I *believe* you would use this in conjunction with:

location (get)
unit.location -> tile

Returns the unit's location.

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

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.

With this function, we finally have true north/south, east/west, or even random point/other random point map transport relationship capabilities.

TNO is a friggin genious!!!!!!!

See the crude screen shot for the idea... Unit enters "A" on east map, they automatically teleport to "1" on the west map... They enter "Z" on the west map, they automatically teleport to "9" on the east map...



@McMonkey , using this, you could extend Fortress Europe's playing field to include the entire Atlantic over to the US east coast... Or east to include more of Russia... Or have a totally different map elsewhere (perhaps 2 European Theatre maps and 2 Pacific Theatre maps for a 2194 days of war redone in greater detail).

Using this, there'd be no need to have a scenario about the "French and Indian War" -- you could cover the entire Seven Years' War instead...

A Caesar scenario could follow his entire career, instead of just Gaul... Win in Gaul-->fight civil war-->siege of Alexandria-->Veni, Vedi, Vici

This is getting very cool indeed.
 
So, assuming I had the three locations above, if after a unit in the 54th spot was killed, I wanted text to pop up, I wanted to create 27 units that are in the 31st unit spot, I wanted them to show up at 2,4,0, I wanted them to be veteran, and I wanted their home city to be none, then:

civ.scen.onUnitKilled(function (killed,killedBy)
local unitType = killed.type.id
ifLegionKilled[unitType] then
civlua.createUnit([31],civ.getTribe(0), massacreLocations[id][1], {randomize=false, veteran=true, count=27, homeCity=none})
end -- would end the IF statement
end) would end the function for (killed, killedBy)

We haven't specified who gets the units, but in this case it will be tribe 0.

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)

I changed line 3 because if we only want to do this for the unit in slot 54, we don't need to have a table for that. If, say, you had 5 different units that triggered a similar create unit result, then we might want to check if there is an entry in the table, and record the specifics of the create unit instructions in that table. the == operation returns true if both sides are exactly the same, and false otherwise.

Line 4: displays the text associated with the event

Line 5: A unittype is a specific kind of object, like an integer or boolean. we use civ.getUnitType(31) to convert the index to the unittype object.
Line 5: I think the chage of the Massacre locations is self explanatory.
Line 5: I'm not sure about the home city thing. I think we need to specify the correct city object. I think the 0 index corresponds to the NONE city, but this should be checked. I may write a function converting a city name in string form to the city object, because it would make specifying things much easier.

BUT... For this all to work, I'd need the following three things to be somewhere in the code:

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 LegionKilled = { --Legio I "Germanica" killed
[54]={text=LegioIText, tech=22},}
local massacreLocations = {[31]={{2,2,0},{2,4,0},{2,6,0}}}

Is that right?

I changed the code so you wouldn't need the LegionKilled table, but, yes, those things need to be in the code to be referenced.

Does it matter where all of these things are in the code? Or they simply must be present? I see TNO listed the text up front - does that matter? It might be easier to learn to do it that way - keep the functions in one place - but if one wanted to keep each event "compartmentalized," with the whole thing written out in one little area, would that be OK too?

When you specify the instructions in a function, order is extremely important. However, I don't know if a lua function can reference functions or variables that appear below the function in the events file. That will have to be tested.

You could specify everything just before you need it, but it might clutter up the code.

Probably the best way to compartmentalize would be to specify two functions for each event. For example, if you have 5 events that could happen after a unit is killed, for the 5th event have two functions

UnitKilledEventFive(killed,killedBy) -> boolean --(Tells you if the conditions are met for the 5th event)
doUnitKilledEventFive(killed,killedBy) --This has the instructions for the fifth unit killed event

Then, near the bottom to trigger, we have

civ.scen.onUnitKilled(function (killed, killedBy)
if UnitKilledEventOne(killed,killedBy) then
doUnitKilledEventOne(killed,killedBy)
end
if UnitKilledEventTwo(killed,killedBy) then
doUnitKilledEventTow(killed,killedBy)
end
if UnitKilledEventThree(killed,killedBy) then
doUnitKilledEventThree(killed,killedBy)
end
if UnitKilledEventFour(killed,killedBy) then
doUnitKilledEventFour(killed,killedBy)
end
if UnitKilledEventFive(killed,killedBy) then
doUnitKilledEventFive(killed,killedBy)
end
end)

I guess part of what is throwing me is in the above example you almost have @THEN in two places...

ifLegionKilled[unitType] then, obviously (where I'm creating the massacre units)

But also in the bold below

local LegionKilled = { --Legio I "Germanica" killed
[54]={text=LegioIText, tech=22},}

Correct?

Yes, the logic is split over several places. This is because in the scenario TNO converted there are multiple giantKilled events, and they were all put together into a couple functions instead of specified individually. It is actually kind of elegant, but more difficult to learn from if you are new to programming.
 
Top Bottom