[TOTPP] Lua Scenario Template

@Prof. Garfield I found a minor typo/bug in legacyEventEngine.

Line 441 has perform misspelled: 'perfromEventOnTurn'

I've updated the template and attached the updated file here.

The bug is in the "delay" event, and has probably been in the Legacy Event Engine since the beginning, so if anyone has been using the legacy version of delay events, they should update the file.

I also ran a code analyser*, and discovered two further typos, but these are unlikely to have any impact. One is for legacy events that modify transport settings, and the other was in an if statement that it looks like will never be triggered if the macro.txt specifications are followed.

*The ZeroBrane Studio text editor comes with a code analyser. I've made a couple modifications to better suit TOTPP here, and it seems to be working, but I used it enough to be confident everything is working properly (which is why I haven't properly announced it yet). There is also a script (Scripts\makeZeroBraneAPI.lua) in new versions of the Template which generates tooltips and autocomplete suggestions based on your object file.
 

Attachments

Hi, guys - I was eager to pick your brains over what I assume is a simple one...

I wanted to create a function event like the one below...
Code:
if civ.hasTech(object.tRussians, object.aSovAsianFront) and object.tRussians.isHuman == false then
        gen.justOnce("SovietsAttackChina",function()

But for random repeatable attacks, if the AI has certain techs...What "gen" function do I use, instead of "justonce"?
 
Hi, guys - I was eager to pick your brains over what I assume is a simple one...

I wanted to create a function event like the one below...
Code:
if civ.hasTech(object.tRussians, object.aSovAsianFront) and object.tRussians.isHuman == false then
gen.justOnce("SovietsAttackChina",function()
But for random repeatable attacks, if the AI has certain techs...What "gen" function do I use, instead of "justonce"?

If you want it to happen every time, you would write
Code:
if civ.hasTech(object.tRussians, object.aSovAsianFront) and object.tRussians.isHuman == false then
    -- stuff to do
end

If you want it to happen a few times, you use
Code:
-- gen.limitedExecutions(key,maxTimes,limitedFunction)--> void
e.g, for five times at most
Code:
if civ.hasTech(object.tRussians, object.aSovAsianFront) and object.tRussians.isHuman == false then
    gen.limitedExecutions("SovietsAttackChina",5,function()
        --stuff to do
    end)
end
 
@Prof. Garfield
Thanks! But what I need is the event to trigger randomly, like a 1 on 5 chance, every turn.

It is to represent the AI launching surprise counterstrikes.

I'd like to make the following macro event in Lua, using "isHuman==false" feature.
Code:
@IF
RECEIVEDTECHNOLOGY
technology=94
receiver=German Reich
@AND
RANDOMTURN
denominator=7
@THEN
CREATEUNIT
unit=T-34
owner=Soviet Union
veteran=yes
homecity=None
locations
70,30
endlocations
@ENDIF
The only thing I cannot work out is the way to make a random function?
 
math.random is the Lua command you want. math.random(n) will return a random number in the range of 1 through n. To simulate the way "denominator" used to work, you could write:
Code:
if math.random(7) == 1 then
    < put the code you want to run here >
end
That would give a 1 in 7 chance that the code inside the block will run.

But 1/2 = 50%, 1/3 = 33%, 1/4 = 25%, 1/5 = 20%, etc. If you want, let's say, a 40% chance that something will happen (or any percent that can't be obtained with 1 over any whole number denominator), I'd recommend using 100 in the call to math.random and testing it against your desired percent, like this:
Code:
if math.random(100) <= 40 then
    < put the code you want to run here >
end
Note that now it has to be a <= comparison, not a == comparison like in the first example.

Hope that helps!
 
Last edited:
I now set math.randomseed(os.time()) in events.lua, to ensure that a new random seed is chosen each time the game is loaded. I'm not sure that matters in practice for a civ game (though I know it did in the old version of the custom music template).

I also updated the copyGamePieces script shipped with the template to fix the issue that @Pablostuka found a few days ago (the script attached to this post was already updated).

The big update is that I've written the "strategic targets" module, which actually turned out to be a bit more difficult than expected, since I wasn't writing for a specific desired functionality, but rather tools for a more flexible approach. (I did include a 'builder' which does a lot of the work for an experience quite similar to OTR's strategic system.)

The basic idea is that I've created a "target object" similar to the unitObject and cityObject that keeps track of the target unit (so the same unit type can be used for multiple targets), and provides some functionality automatically (like being destroyed when the relevant city no longer has the improvement in question). If you want to, you can even have a targetObject without a target unit, so that building an improvement changes nearby terrain, and selling the improvement changes it again, but there is no outside unit to be vulnerable.

Here's the documentation that I wrote, which is also in the LuaDocumentationFolder:
Spoiler Strategic Targets Documentation :

The strategicTargets module provides tools for associating city improvements
with terrain types and 'target' units that can be attacked.
Linking the module:
local strat = require("strategicTargets")
The Target Object:
The bulk of this module's work is done with the targetObject, which functions much like the other TOTPP objects.
targetObject:
Properties (* means a term will be described later):
active (get)
target.active -> boolean
True if the targetLostFunction* hasn't been run yet, false if it has.
id (get)
target.id -> integer
Id of the target
unit (get)
target.unit -> unitObject or nil
Returns the unit that is acting as the target, or nil if the unit was
destroyed, or if no unit was registered as the target. If a unit is
registered with the target, but is no longer there, the target will
be lost* the next time it is verified*.
hasRegisteredUnit (get)
target.hasRegisteredUnit -> boolean
Returns true if a unit was registered as the target, and false if not. (You might choose not to register a unit if you want a tile's terrain to be
tied to a city improvement, but not be vulnerable to attack.)
owner (get/set)
target.owner -> tribeObject
Returns the tribe that owns the target.
city (get)
target.city -> cityObject or nil
Returns the city registered with the target. If a city is registered
with the target, but no longer exists, the target will be lost* the
next time it is verified*.
cityLocation (get)
target.cityLocation -> tileObject or nil
Returns the tile where the registered city is supposed to be, or nil
if there is no registered city.
improvement (get)
target.improvement -> improvementObject or wonderObject or nil
Returns the improvement or wonder that is associated with the target,
or nil if no improvement is registered with this target. If the target
has a registered city and improvement, but the improvement is missing
from that city, the target will be lost* the next time it is verified*.
customData (get)
target.customData -> stateSavableValue
Returns a stateSavableValue, which can contain custom information about
that target, such as terrain changes that must be made when the target
is lost*. A stateSavableValue is a string, number, boolean, nil, or table
with string or number keys, and stateSavable values. Verified with
gen.isStateSavable(item) -> bool.
targetLocation (get)
target.targetLocation -> tileObject or nil
Returns the tile where the target's unit is located. Returns nil if the
target has no unit registered.
captureWithCity (get)
target.captureWithCity -> boolean
Returns true if an intact target is captured with its registered city,
and false otherwise. That is, when the target is verified* and the
registered city's owner is different from the target's owner, the target
will be transfered to the city's owner if captureWithCity is true, and
lost* if it is false.
class (get)
target.class -> string
Returns a targets 'class'. The class can be used to differentiate between
different 'types' of target, which may be useful for your
targetVerificationFunction* or targetLostFunction*.
The default workings of the module do not rely on the target class.
Note: the createTarget function assigns a class of "default" if no class is specified.
Losing/Destroying a Target, targetLostFunction
A target can be destroyed by using the function strat.destroyTarget(target),
or automatically when a target fails some sort of verification*.
The strategic targets module also automatically registers a
unitDefeated event that destroys the associated target when appliccable.
When a target is destroyed, the targetLostFunction(target) --> void
is executed. The default function does nothing, so you must register
a function with the effects that you like. You can use target properties
(such as the improvement and customData) to provide data to the function.
Use strat.registerTargetLostFn(tLostFn) to register the targetLostFunction.
The targetLostFunction will only run once, even if it is called multiple
times for the same target.
Destroying a target removes its information from the state table and
makes strat.getTarget(id) return nil for that id.
Verifying a Target
It is possible for things to happen that should destroy a target, but
that the game can't immediately detect (for example, selling the associated
city improvement). To handle these cases, we periodically 'verify'
targets, in order to destroy them if they are not in order.
A target can be verified by using strat.verifyTarget(target) ->bool,
which returns true if the target is verified, and false if it is not.
The verification procedure checks if the unit registered with the target
is still alive (if a unit was registered), if the registered city still
exists (if a city was registered), and if the registered improvement/wonder
still exists (if registered). If any of these conditions are false,
the target is destroyed with strat.destroyTarget, and false is returned.
The verification procedure also checks if the city's owner is the
same as the target's owner, and either changes ownership of the target
or destroys the target, depending on target.captureWithCity.
The above checks can't be overridden (except by not registering the
relevant property), but you can also add additional checks that
must be satisfied by creating a function
targetVerificationFunction(target) --> boolean
return true if the target is verified, and false if it should be destroyed
and register it using strat.registerTargetVerificationFn(tVerifyFn)
Targets are verified on these occasions:
All targets are verified when a target is destroyed.
All targets registered to a city are verified when the city is captured.
All targets owned by a tribe are verified at the start of that tribe's turn.
This module also has code to verify all targets registered to a city when
onCalculateCityYield is executed.
Other Parts of a Strategic Target System
You will almost certainly need to make an onCityProduction event to
create targets when relevant city items are produced. For that,
you will need the strat.createTarget function:
-- strat.createTarget(tile,targetType,cityOrOwner,
class="default",improvement=nil,customData=nil,captureWithCity=false)
-- creates a target on the supplied tile, with unit type given by targetType,
-- associated with the provided city (or no city, but just an owner if a tribe is supplied instead)
-- an improvement or wonder that the target is associated with
-- (if a city is specified, the target will be destroyed if the
-- city no longer has that improvement)
-- if nil is supplied, the target is not associated with any particular
-- improvement
-- class is a string to facilitate giving targets different 'types'
-- customData is a string, number, nil or (nested) table of string, number, nil.
-- captureWithCity is a boolean, true if the target is captured when
-- a city is captured, and false if it is destroyed when the city
-- is captured
You are likely to also want to add extra conditions to whether a city can
build strategic items, based on whether there is actually a place for them
to change the terrain (or some other condition). The canBuild module
provides a function to register supplemental conditions outside of the
canBuildSettings module:
canBuild.registerSupplementalCondition(item,
function(defaultBuildFunction,city,item) --> boolean)
Additionally, you may want to specify what happens to other units
on a tile when a target is created or captured. By default, all units
not owned by the targetOwner are moved to an adjacent square using
gen.moveUnitAdjacent. However, you can register some other function with
strat.registerMoveUnitsAfterTargetCreatedOrCapturedFn(
function(tile,target)--> void)
The function takes the tile the target is on, as well as the target as arguments.
This module provides a builder function for a strategic system
-- strat.basicStrategicFunctions(isStrategicItemFn,tileEffectsFn)
-- --> targetLostFunction(target) --> void,
-- targetVerificationFunction(target) --> void,
-- registerSupplementalConditionsFunction() --> void,
-- cityProductionEventFunction(city,item) --> void
--
-- isStrategicItemFn
-- function(item) --> boolean
-- item: improvement or wonder
-- return true if the item is strategic
-- (creates targets/changes terrain upon construction/destruction)
-- false otherwise
--
-- tileEffectsFn(city,item) --> false or table of
-- {
-- tile = tileObject
-- a tile where something will happen
-- constructionBaseTerrain = baseTerrainObject or nil
-- change the base terrain to this upon construction
-- nil means no change to terrain
-- constructionResource = 0,1,2 or nil
-- change the terrain resource to this upon construction
-- nil means no change to the resource
-- (ignored if grassland is the baseTerrain of the tile,
-- after the constructionBaseTerrain change is made)
-- destructionBaseTerrain = baseTerrainObject or nil
-- change the base terrain of the tile to this
-- when the target is destroyed
-- nil means no change to the terrain
-- destructionResource = 0,1,2, or nil
-- change the terrain resource to this upon target destruction
-- nil means no change to the resource
-- (ignored if grassland is the baseTerrain of the tile,
-- after the destructionBaseTerrain change is made)
-- targetUnitType = unitTypeObject or true or nil
-- if unitTypeObject, create a target with that unit type
-- on this tile. nil means no target on this tile.
-- true means create a 'target' without a unit, for example
-- if you want to tie a terrain change to whether or not
-- the item is still in the city
-- captureWithCity = bool or nil
-- if true, the target on this tile is caputured with the city
-- provided the item is still intact. False or nil means it
-- is destroyed instead. For basicStrategicFunctions,
-- if any target is destroyed, the item is removed, so all
-- other targets will be destroyed. If you want a target
-- to be capured, all the targets for the item should be true
-- }
-- return false if the item can't be constructed in the city
-- (this will only be called on items where isStrategicItemFn(item) returns true)
Provided Functions:
strat.registerTargetLostFn(targetLostFunction) --> void
strat.registerTargetVerificationFn(targetVerificationFunction) --> void
strat.registerMoveUnitsAfterTargetCreatedOrCapturedFn(moveUnitsFunction) --> void
strat.destroyTarget(target) --> void
strat.verifyTarget(target) --> boolean
-- If a target should be destroyed
-- e.g. unit missing, city missing, improvement missing
-- then the target is destroyed and false is returned
-- otherwise, true is returned
-- if the target should be captured, but hasn't been, change
-- the owner to city's owner
-- if the target should be destroyed upon capture, but hasn't been,
-- it is destroyed
strat.isTarget(possibleTarget) --> boolean
-- returns true if the possibleTarget actually is a target, false otherwise
strat.createTarget(tile,targetType,cityOrOwner,class="default",improvement=nil,customData=nil,captureWithCity=false) --> targetObject
-- creates a target on the supplied tile, with unit type given by targetType,
-- associated with the provided city (or no city, but just an owner if a tribe is supplied instead)
-- an improvement or wonder that the target is associated with
-- (if a city is specified, the target will be destroyed if the
-- city no longer has that improvement)
-- if nil is supplied, the target is not associated with any particular
-- improvement
-- class is a string to facilitate giving targets different 'types'
-- customData is a string, number, nil or (nested) table of string, number, nil.
-- captureWithCity is a boolean, true if the target is captured when
-- a city is captured, and false if it is destroyed when the city
-- is captured
strat.getTarget(id) --> targetObject or nil
strat.iterateTargets(filterItem)
-- if filterItem is nil, iterate through all targets
-- if filterItem is a city, return only targets associated with that city
-- if filterItem is a tribe, return only targets owned by that tribe
-- if filterItem is a string, return only targets with that class
-- if filterItem is a unitType, return only targets of that unit type (including active targets where the unit has been destroyed
-- if filterItem is a tile, return targets on that tile
-- if filterItem is an improvement or wonder, return targets associated with that improvement/wonder


If anyone needs help using this (or wants me to do it for them), or has bugs to report, let me know.
 
Last edited:
Hi, guys!
I made this event:
Code:
--When the Soviet civ is given "Great Patriotic War", the event will spawn Soviet veteran partisan forces to attack the German invaders...
if civ.hasTech(object.tRussians, object.aGreatPatrioticWar) and math.random(3) == 1 and object.tRussians.isHuman == false then
        --Soviet first line partisans
        civlua.createUnit(object.uPartisans, object.tRussians, {{22,34,0},{25,41,0},{31,45,0}}, {count=9, randomize=true, veteran=true})
    civlua.createUnit(object.uShockTroops, object.tRussians, {{22,34,0},{25,41,0},{31,45,0}}, {count=3, randomize=true, veteran=true})
    civlua.createUnit(object.uArtillery, object.tRussians, {{22,34,0},{25,41,0},{31,45,0}}, {count=3, randomize=true, veteran=true})
    end
end

But I am getting this error:

Code:
error loading module 'triggerEvents' from file 'C:\CIV2 Games\CIV2 TOT\Overlords Max\LuaTriggerEvents\triggerEvents.lua':
    ...IV2 TOT\Overlords Max\LuaTriggerEvents\triggerEvents.lua:269: <eof> expected near 'end'

Line 269 is the 2nd "end" after the event contents...What am I doing wrong here? (Lua file attached for reference)
 

Attachments

Hi, guys!
I made this event:
Code:
--When the Soviet civ is given "Great Patriotic War", the event will spawn Soviet veteran partisan forces to attack the German invaders...
if civ.hasTech(object.tRussians, object.aGreatPatrioticWar) and math.random(3) == 1 and object.tRussians.isHuman == false then
        --Soviet first line partisans
        civlua.createUnit(object.uPartisans, object.tRussians, {{22,34,0},{25,41,0},{31,45,0}}, {count=9, randomize=true, veteran=true})
    civlua.createUnit(object.uShockTroops, object.tRussians, {{22,34,0},{25,41,0},{31,45,0}}, {count=3, randomize=true, veteran=true})
    civlua.createUnit(object.uArtillery, object.tRussians, {{22,34,0},{25,41,0},{31,45,0}}, {count=3, randomize=true, veteran=true})
    end
end

But I am getting this error:

Code:
error loading module 'triggerEvents' from file 'C:\CIV2 Games\CIV2 TOT\Overlords Max\LuaTriggerEvents\triggerEvents.lua':
    ...IV2 TOT\Overlords Max\LuaTriggerEvents\triggerEvents.lua:269: <eof> expected near 'end'

Line 269 is the 2nd "end" after the event contents...What am I doing wrong here? (Lua file attached for reference)
If not wrong, <eof> error should mean there's code where the program is looking for the End Of File.
There, looking at your code, it seems to be one lonely "end" :

exemple2.png


what does this "end" close ? I see nothing related to it on its incrementation line ?
 
Last edited:
@CurtSibling
My syntax highlighter tells me that you have an end for the function declaration line (158) of
Code:
function triggerEvents.afterProduction(turn,tribe)
at line 261.

It looks like you've added an extra "end" for every event after that.

You need the 'extra' end when using justOnce, since using justOnce involves defining an extra function, which requires an 'end' to finish it off. When you're not using justOnce, you only need an 'end' to finish the if-then line.
No just once:
Code:
if condition then
    --instructions
end
with just once
Code:
if condition then
    gen.justOnce("SomeKey",
        function()
            --instructions
        end -- close the function definition two lines above
    )-- close gen.justOnce function call
end -- close if condition then
 
What's going on here is that in Lua, functions are "first class values." That is, they can be used the same way as numbers, strings, and tables. In particular, this means that they can be passed as arguments to other functions.

In this situation, we find it convenient to define the function just as we need it, by writing
Code:
mySpecialFunction(function() 
-- some instructions
end)
just as we often find it convenient to define a string right when we need it
Code:
civ.ui.text("my message")

In fact, if you want, you can define functions in this way:
Code:
myFunctionName = function(arg1,arg2)
    -- some code
end
instead of
Code:
function myFunctionName(arg1,arg2)
    -- some code
end
 
I've written a "navy" module, though at the moment it should probably be called the "sea transportation" module.

Ground unit transportation:

In this module, you can control the circumstances under which units can board or disembark ships outside of cities. You can restrict generic "onto the beach" landing (for example, only allowing it if the ship has full movement for the current turn), as well as special cases for unloading or loading near a city with whom you are at peace or allied.

I've also added the ability to prevent ships from carrying certain units even from port, by cancelling the sleep orders of incompatible units (and teleporting them off ship if that is circumvented).

Air unit transportation:

Carrier status of units is enabled and disabled to control what units can actually use the carrier. Units that can use the carrier can be set based on each individual carrier type, if there is more than one carrier unit.

If anyone wants this in an existing scenario, the navySettings.lua file must be required somewhere. Unless your scenario uses a recent copy of the template, you will have to delete this code from navy.lua
Code:
function discreteEvents.linkStateToModules(state,stateTableKeys)
    local keyName = "navyModuleState"
    if stateTableKeys[keyName] then
        error('"'..keyName..'" is used as a key for the state table on at least two occasions.')
    else
        stateTableKeys[keyName] = true
    end
    -- link the state table to the module
    state[keyName] = state[keyName] or {}
    navy.linkState(state[keyName])
end
function discreteEvents.onActivateUnit(unit,source,repeatMove)
    navy.unitActivationSetCarrierStatus(unit,source,repeatMove)
    navy.beachSettingsUnitActivation(unit)
end
function discreteEvents.onTurn(turn)
    navy.onTurnMaintenance(turn)
end
and add equivalent events in appropriate places in your code. Let me know if you need help with any of this, or if you run into and bugs or other problems.
 

Attachments

Just a little Lua question about "civlua.createUnit" events...

The event is for the German civ, who will have random reinforcement units pop up in certain Russian cities that may be in German hands.
In macro, these sort of events will only fire when the AI civ in question owns the city. If the German loses the city, no units are made.

I assume this rule is the same for Lua? Just so German units are not dumped into Soviet-owned cities.

The event, just for ref...
Code:
--When the German civ is given the "Eastern Front" tech, the event will randomly spawn German army forces in conquered cities...
if civ.hasTech(object.tGermans, object.aEasternFront) and math.random(5) == 1 and object.tGermans.isHuman == false then
        --German Conquest Reinforcements
        civlua.createUnit(object.uGrenadiers, object.tGermans, {{20,28,0},{22,34,0},{22,46,0},{24,30,0},{25,41,0},{25,47,0},{26,32,0},{29,41,0},{30,30,0},{32,36,0}}, {count=3, randomize=false, veteran=true})
end
 
I am wondering if I have to assign "object.cCity.owner" commands for the event...
Which is more work than macro...But willing to do it. :)
 
I am wondering if I have to assign "object.cCity.owner" commands for the event...
Which is more work than macro...But willing to do it. :)
Going into civlua.lua file to look at it,

the (civlua.)createUnit(unittype, tribe, locations, options) function call in various way considering the location option the
isValidUnitLocation(unittype, tribe, tile) function defined in the same file.

This function has as commentary -- Returns true if `tile` is a valid location for a unit of type `unittype`, with owner `tribe`, false otherwise.

Based on that (which I verified with a rapid look at the code composing the function), I guess the soviet cities would be excluded from the location list given.
 
@Dadais is correct. The civlua.createUnit function takes into account whether the tile is owned by the tribe receiving the unit. As far as I know, civlua.createUnit functions exactly like the create unit macro action. The function civ.createUnit, however, will just put down a unit and not worry about any of those pesky details. (This is important if you're trying to create invaders at sea, for example, and want to place land units on sea tiles along with transports.) The function gen.createUnit expands upon civlua.createUnit somewhat (the linked function will be at the very top of the page, and easy to miss, since it will have to be expanded and there is a title right below it).
 
So could the event will just ignore invalid cities? If that is the case - Perfect. :)
 
Back
Top Bottom