Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,915
Location
Ontario
This thread is meant to serve two related purposes. The first is to provide a general Q&A thread for Lua and related topics. The second is to provide a link index for questions and their answers, as well as other resources. I'll edit this post periodically to add links to the questions and answers. This way, there is a convenient place to see if a topic has been addressed.

Don't worry about if a question has been addressed elsewhere, feel free to ask it here anyway. If you know of questions and answers elsewhere on the forums, it would be helpful to post a link in this thread, and I'll add it to the Q&A List. Also, if a question asked here turns out to require substantial coding and/or discussion, please move it to its own thread, or the Lua Scripting Possibilities thread, and provide a link. As a rule of thumb, I'd say after ~6 posts, strongly consider if the discussion should be moved elsewhere.

Questions and Answers
How do I convert my existing scenario to Lua, and take advantage of build option restrictions?
How do I add a wonder to a city?
(Delayed Action Module) How can a delayed action use information that is only available when the action is first delayed? (Question, Answer)
How do I move a city?
How do I add or remove a river?
How do I stop certain ground units from being airlifted?
What is the difference between civ.createUnit, civlua.createUnit, and gen.createUnit, and how do I know which one to use?
How do you add/remove an improvement (or wonder) from a city be it either when a specific turn is reached or when a city is taken?
How do I make nested menus?
How do I make an event fire if a unit is destroyed on a specific tile? (Question, Answer)
How do I add/remove pollution from a tile?

Links to Scripts and Resources
How can I copy my scenario to a larger map?
Download the Lua Scenario Template.
 
Last edited:

JPetroski

Deity
Joined
Jan 24, 2011
Messages
4,541
Could you please provide some clarity in working around the globals situation with your template? I know you've done this in the past, but what I'm specifically trying to help Techumseh with is an event where units show up in or around a city at a delayed point in the future. The problem is "city" isn't recognized. I believe what I need to do is include a local definition of city within this code, however:

local city = civ.getCity() needs a key, and CityID isn't working, and I also want this event to happen for every city. Do you have advice on what to put here? I wonder if a list of these sort of things would be useful at some point as well.

Code:
local function createPartisansOnCityCapture(argTable)

local nearbyRecruitmentTiles = gen.nearbyOpenTilesForTribe({city.location.x,city.location.y,city.location.z},5,{0,1,2,3,4,5,6,7,9,11,12,13,14,15},object.pCommunists)
        gen.createUnit(object.uGuerrillaC, object.pCommunists, nearbyRecruitmentTiles, {count=3,randomize=true, veteran=false,scatter=true})
        civ.ui.text("Communists guerillas strengthen their position near "..city.name)
end
delayedAction.makeFunctionDelayable("createUnit",createPartisansOnCityCapture)
(Note this is just testing code and isn't the "correct" civ/units/etc. for the scenario)
 

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,915
Location
Ontario
Could you please provide some clarity in working around the globals situation with your template? I know you've done this in the past, but what I'm specifically trying to help Techumseh with is an event where units show up in or around a city at a delayed point in the future. The problem is "city" isn't recognized. I believe what I need to do is include a local definition of city within this code, however:

local city = civ.getCity() needs a key, and CityID isn't working, and I also want this event to happen for every city. Do you have advice on what to put here? I wonder if a list of these sort of things would be useful at some point as well.

Globals wouldn't help you here if they were available. What you have to do is pass the information through the "argument table." When you use delay.doInFuture, you can specify a table to be used as the argument for the delayed function. Since this table is being saved in the state table, it should only have strings and numbers keys and values. (Values can also be tables with string and number components.)

Here, we'll store the id of the tile that the city is on (just in case the city is destroyed later):
Code:
{cityTileId = gen.getTileId(city.location)}
Other options would be the x,y,z coordinates of the city tile, or the city's id number.

Your code would look something like this (untested):
Code:
local function createPartisansOnCityCapture(argTable)
    local cityLocation = gen.getTileFromId(argTable.cityTileId)
    local city = cityLocation.city
    local nearbyRecruitmentTiles = gen.nearbyOpenTilesForTribe(cityLocation,5,{0,1,2,3,4,5,6,7,9,11,12,13,14,15},object.pCommunists)
    gen.createUnit(object.uGuerrillaC, object.pCommunists, nearbyRecruitmentTiles, {count=3,randomize=true, veteran=false,scatter=true})
        civ.ui.text("Communists guerillas strengthen their position near "..city.name) -- if the city can be destroyed, city.name won't work
end
delayedAction.makeFunctionDelayable("createUnit",createPartisansOnCityCapture)

Code:
function discreteEvent.onCityTaken(city,defender)
    local futureTurn = civ.getTurn()+param.partisanDelay
    delay.doInFuture("createUnit",{cityTileId=gen.getTileId(city.location)},futureTurn,object.pCommunists.id)
end
 

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,915
Location
Ontario
Question (from a PM I received):

What is the difference between civ.createUnit, civlua.createUnit, and gen.createUnit, and how do I know which one to use?

Answer:

The civ.createUnit function is the function that actually tells the game to do something. This information is provided by the Lua Function Reference:

createUnit
civ.createUnit(unittype, tribe, tile) -> unit

Creates a unit of type `unittype`, owned by `tribe`, at the location given by `tile`.

civ.createUnit will create the unit no questions asked, unless it isn't provided a valid unittype, tribe, or tile. It doesn't care if you're trying to create a land unit on the ocean, or if the destination tile is occupied by another tribe. If you want to set the home city or veteran status, you must write lines for that. Want to make multiple units? You must write a loop. Don't want the unit to appear on another tribe's tile? You must write code to check if the tile is clear, and only create the unit if it is. Want the unit to randomly choose one of 4 tiles? Your code will have to select the tile first.

Using civ.createUnit is likely to require you to ask and answer a lot of questions in your code. That means thinking about details and debugging those details. civlua.createUnit and gen.createUnit both allow you to provide some extra information in the arguments, and then handle details behind the scenes.

@TheNamelessOne wrote civlua.createUnit to recreate the functionality and options of the Create Unit macro event. Ground units won't be created at sea, you can provide a list of tiles and the unit will be placed on the first valid one, etc. If you want this kind of functionality, civlua.createUnit does it more easily and reliably than civ.createUnit.

Here is the information from civlua.lua:
Code:
--[[
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).
--]]
local function createUnit(unittype, tribe, locations, options)

civlua.createUnit can more easily handle creating multiple units, or accounting for the fact that a desired tile might be occupied than can civ.createUnit. However, civ.createUnit will create a land unit on water, which civlua.createUnit won't do. (This came up in Over the Reich, when the photo recon plane sometimes tried to create a 'camera' on the ocean, and civlua.createUnit wouldn't do it.)

gen.createUnit is meant to replace civlua.createUnit. It has more options than than civlua.createUnit, and the locations argument now accepts a table of tileObjects (and even a single tile object), not just a table of coordinate triples. Here is the documentation for gen.createUnit:

Code:
gen.createUnit(unitType,tribe,locations,options) --> table of units Creates one or several units.
Meant to provide improve upon civlua.createUnit. Returns a table of units, indexed by integers starting at 1 (unless no units were created, in which case an empty table is returned, and a message printed to the console, but no error is generated). "unitType" is the unit type that will be created. "tribe" is the tribe that will own the unit(s). "locations" specifies where the unit will be created. If a table is provided, this is the order in which to try to place the units, unless a different option is specified in "options." "options" a table with various keys and values.
Valid Arguments:
Code:
unitType: unitTypeObject 
tribe: tribeObject 
locations: a tile object 
        a table of 3 elements (indexed by integers 1,2,3) corresponding to x,y,z coordinate
        a table of tile objects (indexed by integers)
        a table of coordinate triple tables (indexed by integers) 
options: a table with the following keys: 
    count = integer 
        the number of units to create nil means 1 
    randomize = bool or nil 
        if true, randomize the list of locations if false or nil, try to place at the tile with the smallest index in table first 
    scatter = bool or nil 
        if true, and if randomize is true, each unit is created on a random tile in the location table 
    inCapital = bool or nil 
        if true, attempt to place in the capital before other locations in case of multiple capitals, capitals are ranked with smallest city id first randomize/scatter applies to list of capitals if this is selected 
    veteran = bool or fraction in 0-1 or integer or nil 
        if true, make the created unis veteran 
        if a fraction in 0-1, each unit created has that chance of being veteran 
        if number >= 1, this many of the count are veteran (take floor) 
        nil or false means no veterans 
    homeCity = city or true or nil 
        if city, that city is the home city 
        if true, the game selects the home city (probably the same way a city is chosen if you create a unit by using the cheat menu) 
        if nil, no home city 
    overrideCanEnter = bool or nil 
        if true, unit will be placed even if unitType:canEnter(tile) returns false 
        false or nil means follow the restriction civ.canEnter appears to check if the terrain is impassible, or the unit can cross impassible 
    overrideDomain = bool or nil 
        if true, sea units can be created on land outside cities, and land units at sea 
        false or nil means units can only be created where they could travel naturally 
    overrideDefender = bool or nil 
        if true, unit can be placed on tiles with enemy units or cities 
        false or nil means the tile must have no enemy cities, and no enemy defender

So, how to choose between civ.createUnit, civlua.createUnit, and gen.createUnit? First, gen.createUnit will (I'm pretty sure) take any functioning example of civlua.createUnit and do the same thing. Since gen.createUnit has extra features, civlua.createUnit is obsolete for any purpose I can think of.

For choosing between civ.createUnit and gen.createUnit, default to gen.createUnit. However, if what you want to do will require you to go through the list of created units and change things about the created unit(s), it might make more sense to use civ.createUnit. If I'm creating rebels or reinforcements, I'd likely use gen.createUnit. If I'm making a capture the defeated unit mechanic, I'd be more inclined to use civ.createUnit, since I might want to mess with hitpoints or spent movement.
 

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,915
Location
Ontario
Question (from a PM I received):

How do you add/remove an improvement (or wonder) from a city be it either when a specific turn is reached or when a city is taken?

Answer:

Adding improvements to a city is done using the addImprovement function or method:

addImprovement
civ.addImprovement(city, improvement) -> void

Adds city improvement `improvement` to city `city`.
Code:
civ.addImprovement(object.lMyCityLocation.city,object.iSomeImprovement)

addImprovement
city:addImprovement(improvement) -> void

Alias for `civ.addImprovement(city, improvement)`.
Code:
object.cMyOtherCity:addImprovement(object.iDifferentImprovement)

Removing an improvement from a city is done with
removeImprovement
civ.removeImprovement(city, improvement) -> void

Removes city improvement `improvement` from city `city`.
Code:
civ.removeImprovement(object.cThirdCity,object.iImprovementToTake)

removeImprovement
city:removeImprovement(improvement) -> void

Alias for `civ.removeImprovement(city, improvement)`.
Code:
object.lFourthCityLocation.city:(object.iDoomedImprovement)

Adding and taking wonders is a bit different. In that case. Rather than changing the city object, you change the wonderObject:

city (get/set)
wonder.city -> city

Returns the city that has built the wonder, `nil` if not built yet or destroyed.
Code:
object.wWonderToAdd.city = object.cDestinationCity
Code:
object.wWonderToRemove.city = nil

The wonder can also be destroyed using
destroy
wonder:destroy() -> void

Alias for `civ.destroyWonder(wonder)`.

Code:
object.wDoomedWonder:destroy()

Taking an improvement (if it exists) from any city when the Germans capture it from the Russians looks like this (untested code):
Code:
local discreteEvents = require("discreteEventsRegistrar")
local param = require("parameters")
local object = require("object")
local text = require("text")
function discreteEvents.onCityTaken(city,defender)
    if city.owner == object.pGermans and 
        defender == object.object.pRussians and
        city:hasImprovement(object.iFactory) then
        local message = text.substitute("The %STRING1 have captured %STRING2, but the %STRING3 army destroyed the %STRING4 before they left.",
            {object.pGermans.name, city.name,object.pRussians.adjective, object.iFactory.name})
        city:removeImprovement(object.iFactory)
        text.simple(message,"War Minister")
    end
end

Taking away a wonder on a particular turn can be done like this (untested code):
Code:
function discreteEvents.onTurn(turn)
    if turn == param.lostWonderTurn then
        local message = "The "..object.wDoomedWonder.name.." in "..object.wDoomedWonder.city.name.." has crumbled to dust."
        object.wDoomedWonder.city = nil
        text.simple(message,"Domestic Adviser")
    end
end
 

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,915
Location
Ontario
Question (from a PM): How do I make nested menus? In particular, how can I make a menu option that allows me to change a previous selection?

Answer: There is no quick method for doing this. This has occurred to me, but I couldn't come up with a reasonable way to specify a nested menu. (I'm open to ideas if anyone has them.) text.menu can only generate one menu, but that menu can be multiple pages, and you can go back and forth.

To make a system of menus where you can go back to a previous menu, my preferred method is to make a recursive function (a function that makes calls to itself). In the attached code, which can be run (using load script) in any scenario that has access to the text module, I created a menu that lets you choose a unit type. Since there are a lot of unit types, you can choose to filter based on attack or defense values, then choose the range of those values, then select a unit, and finally confirm the unit selection. At each point, there are options to go back to an earlier menu to make a different choice.

In the code, I define the following function

Code:
local function chooseUnitType(menuType,info)
The menuType argument is a string, and that determines what kind of menu will be shown next. ("selectFilter" -- choose attack or defense, "statisticOptions" -- choose a range of values, "filteredOptions" -- select a unit type, "confirmChoice" -- confirm the choice, or make a change)

The info argument is a table that saves information from a previous menu in order to be used in the next one. This way, I pass things like whether the attack or defense is being used as the filter, the range of acceptable values, and the unit that was selected and must now be confirmed.

I'm happy to provide more explanation of the code if anyone wants it.

EDIT: I have added another even simpler nested menu which just lets you go between a couple menus.
 

Attachments

  • nestedMenuSample.lua.zip
    1.1 KB · Views: 7
  • simpleNestedMenu.lua.zip
    443 bytes · Views: 10
Last edited:
Top Bottom