[TOTPP] Prof. Garfield's Lua Code Thread

Another quick Q: What does an error message like this mean?


Does this mean I haven't IDed that elsewhere in the file or code? This is from trying the rivers code above.
for tile in civ.iterateTiles() do
This should be civlua.iterateTiles() (there were two instances of this).

"Attempt to call" means you're treating something like a function. In this example, civ.iterateTiles is nil, but the code is trying to evaluate it as a function.

It can also happen in loops that the thing you're looping over isn't an iterator. (In practice, an iterator is a type of function.) So, for example, I might accidentally write
Code:
for unit in tile do
and there will be a similar error of "attempt to call civ.tile" (or something to that effect). The correct line would be
Code:
for unit in tile.units do
I hope this helps. Let me know if you still have trouble.
 
This may not be the right place to ask this, but I'm wondering if there is any way to eliminate a unit's zone of control?
I know it is easy to edit Rules.txt to ask units to "ignore ZoC" but is there any way to instead do the opposite and take away a unit's ZoC?
 
This may not be the right place to ask this, but I'm wondering if there is any way to eliminate a unit's zone of control?
I know it is easy to edit Rules.txt to ask units to "ignore ZoC" but is there any way to instead do the opposite and take away a unit's ZoC?
There isn't a way to do it with the rules.

A quick test tells me that you can change the "owner" of a tile to any tribe you like, and if you change it to the tribe trying to move through the ZoC, the unit will not be stopped.

In principle, this means you could do an onTribeTurnBegin event, go through every unit in the game, and for any no-ZoC unit that doesn't share a tile with a regular unit, change the tile's ownership to the active tribe.

There's a problem with this, however. The problem is that changing tile.owner also changes tile.defender, and tile.defender is the normal way to see which tribe owns the unit(s) on the tile. So, this method would break other code. Even if I tracked down every instance of tile.defender in the Lua Template and replaced it with something robust to this change, the scenario designer would still have to know not to use tile.defender.
 
That's an interesting thought, though, and brings up some other ideas. But yes, it seems the method here would interfere with some of the other code even that I've already put into this mod. I have some ideas to mitigate the issue, though, namely to provide AI with a unit to attack air units early on in the game (earlier than the human player). My issue is that the tribal village units, movement 0 and provide special effects for the player, are read as enemy units, so even if they spawn next to a city, it makes navigating them sort of cumbersome. However, this could be attributed to tribal interference, and sort of makes sense in the universe. But not at the expense of a fun experience. For the human player though, 2 of the 3 types can be changed into legendary districts, which remove the unit/chance of another one appearing permanently. The AI may be less inclined to select this, as their selection is random. Perhaps I could code in that they "prefer" that option if it is adjacent to a city.
 
New thought, too:
In lua, is there a way to limit 1 unit to attacking only specific other units, instead of all units? (basically making another domain)
In MechanicsFiles\combatSettings.lua, there is a function 'computeCombatStatistics'. If 0 is returned as the attackerStrength, the attack will be cancelled. We ran into this feature earlier with the barbarian units not attacking cities. I don't know how well the AI will deal with this limitation.

There are a couple ways to access this feature, without modifying combatSettings.lua directly.

You can use CTRL+SHIFT+F4 to build the rules_lst.txt file, which will let you set combat effectiveness attacking or defending against groups of units. If the attack is set to 0, the unit won't attack.

You could also register a combat modifier (registerCombatModifiers.lua) where aCustomMult = 0, which would also cancel the attack.
 
Interesting! This will be fun to tinker with. Thank you Prof.

I'll be AFK for a while starting this coming week. I had hoped to post a rough version of my mod before then. We'll see if I get around to it, it may have to wait until a bit later. There are definitely some kinks to work out. Thanks for all of your help.
 
OK, I do have one more question! Again, not sure if this belongs here or could be a lua function, or something else entirely.
What would be "lua language" for city radius? Is there something "easy" already in place (e.g., "tile.cityradius"), or is it something that would need to be written / already has been written?
I have code for generating a unit on a tile, but I want the unit to stop generating if that tile falls in city limits (perhaps it would use similar mechanics as happens when civs get mad if you have a unit in their territory?).
 
OK, I do have one more question! Again, not sure if this belongs here or could be a lua function, or something else entirely.
What would be "lua language" for city radius? Is there something "easy" already in place (e.g., "tile.cityradius"), or is it something that would need to be written / already has been written?
I have code for generating a unit on a tile, but I want the unit to stop generating if that tile falls in city limits (perhaps it would use similar mechanics as happens when civs get mad if you have a unit in their territory?).
You want to check for cities in the tiles generated by gen.cityRadiusTiles . By symmetry, if there are no cities within the "city radius" of the tile, that tile is not within a city radius.
 
I've been tinkering with some "gen" functions, and running into issues. I think I am not operating these correctly.
In the first instance below, I want to create a "Hero" unit when all tribes research X tech, but then want that Hero to die after a certain # of turns (in this case, 20).

In the second instance, I'm trying to use replaceUnit, to replace the Hero unit with a sea unit graphic if it enters ocean, and to revert back to a land graphic if it goes onto land.
Note that these are both under disecreteEvents headings, which have other pieces that are operating fine -- just letting you know why I'm posting truncated code here. Any insight on this would be much appreciated!

Spoiler :
Code:
    if tribe:hasTech(techAliases.EpicPoetry) then
        gen.justOnce("EpicPoetryReceivedBy"..tostring(tribe.id),function()
        gen.createUnit(unitAliases.Hero,civ.getCurrentTribe(),{0,0},{count = 1, randomize = false, scatter = false, inCapital = true, veteran = true, homeCity = nil, overrideCanEnter = false, overrideDomain = false, overrideDefender = false})
        if tribe.isHuman then
        local dialog = civ.ui.createDialog()
                dialog.title = "Heroes of Epic Poetry"
                dialog.width = 500
                dialog:addImage(EpicPoetry)
                local multiLineText = "A legendary figure has emerged amongst your people! This hero is devoted\n^to the ideals of your kingdom, yet seeks splendor and glory in\n^far away lands. When this hero's life comes to an end, you are certain\n^many epics will be written of their exploits.\n^\n^^'Be strong, saith my heart;\n^^I am a soldier;\n^^I have seen worse sights than this.\n^\n^- Homer, The Iliad\n^\n^A Hero unit has been placed in your capital city. This only happens once.\n^Heroes have only 20 turns before they vanish permanently. For a full\n^list of what Heroes can do, see the Civliopedia or Readme."
                text.addMultiLineTextToDialog(multiLineText,dialog)
                dialog:show()
                end
        end)
        local EpicPoetryTurn = civ.getTurn()
        if turn == EpicPoetryTurn+20 then
            gen.killUnit(unitAliases.Hero: unitObject), -- How to make this for each tribe?
        end
    end
    

    
    

    if unit.type == unitAliases.Hero and unit.location.terrainType == 10 then
         gen.replaceUnit(deletedUnit:unitAliases.Hero, replacementUnit:unitAliases.HeroTrireme)
        -> unitObject
    end
    if unit.type == unitAliases.HeroTrireme and unit.location.terrainType == 0,1,2,3,4,5,6,7,8,9,11,12,13,14,15 then
        gen.replaceUnit(deletedUnit:unitAliases.HeroTrireme, replacementUnit:unitAliases.Hero)
    end
 
I've been tinkering with some "gen" functions, and running into issues. I think I am not operating these correctly.
In the first instance below, I want to create a "Hero" unit when all tribes research X tech, but then want that Hero to die after a certain # of turns (in this case, 20).

In the second instance, I'm trying to use replaceUnit, to replace the Hero unit with a sea unit graphic if it enters ocean, and to revert back to a land graphic if it goes onto land.
Note that these are both under disecreteEvents headings, which have other pieces that are operating fine -- just letting you know why I'm posting truncated code here. Any insight on this would be much appreciated!

Looks like you've misunderstood how to read the documentation. Don't worry, you're not the only one.
Code:
gen.replaceUnit(deletedUnit:unitAliases.HeroTrireme, replacementUnit:unitAliases.Hero)

Should be

Code:
gen.replaceUnit(unit, unitAliases.Hero)
The documentation looks like this:
Code:
function gen.replaceUnit(oldUnit: unitObject, replacementType: unitTypeObject)
  -> unitObject
This isn't a usage example. oldUnit is the name of the parameter, and oldUnit: unitObject tells you that the function expects a unitObject in that position when it is called.

Similarly, replacementType: unitTypeObject tells you that the second parameter is called 'replacementType' (so you know what that parameter is for, if it is named well), and that the function expects a unitTypeObject in that position when it is called.

The -> unitObject tells you that the returned value will be a unitObject.
 
In the first instance below, I want to create a "Hero" unit when all tribes research X tech, but then want that Hero to die after a certain # of turns (in this case, 20).
Try this. I introduced a unitData counter to keep track of when a unit should retire.

Code:
unitData.defineCounter("unitRetirementTurn",10000)

if tribe:hasTech(techAliases.EpicPoetry) then
    gen.justOnce("EpicPoetryReceivedBy"..tostring(tribe.id),function()
    local createdUnits = gen.createUnit(unitAliases.Hero,civ.getCurrentTribe(),{0,0},{count = 1, randomize = false, scatter = false, inCapital = true, veteran = true, homeCity = nil, overrideCanEnter = false, overrideDomain = false, overrideDefender = false})
    if createdUnits[1] then
        unitData.counterSetValue(createdUnits[1],"unitRetirementTurn",civ.getTurn()+20)
    end
    if tribe.isHuman then
    local dialog = civ.ui.createDialog()
            dialog.title = "Heroes of Epic Poetry"
            dialog.width = 500
            dialog:addImage(EpicPoetry)
            local multiLineText = "A legendary figure has emerged amongst your people! This hero is devoted\n^to the ideals of your kingdom, yet seeks splendor and glory in\n^far away lands. When this hero's life comes to an end, you are certain\n^many epics will be written of their exploits.\n^\n^^'Be strong, saith my heart;\n^^I am a soldier;\n^^I have seen worse sights than this.\n^\n^- Homer, The Iliad\n^\n^A Hero unit has been placed in your capital city. This only happens once.\n^Heroes have only 20 turns before they vanish permanently. For a full\n^list of what Heroes can do, see the Civliopedia or Readme."
            text.addMultiLineTextToDialog(multiLineText,dialog)
            dialog:show()
            end
    end)
end

discreteEvents.onCityProcessingComplete(function(turn,tribe)
    for unit in civ.iterateUnits() do
        if unit.owner == tribe and unitData.counterGetValue(unit,"unitRetirementTurn") <= turn then
            gen.killUnit(unit)
        end
    end
end)
 
Thank you, @Prof. Garfield ! Everything is working splendidly. And the "replaceUnit" function works well, too, with the changes; it even tracks the unit between its sea/land iterations and "deletes" the unit after the 20 turns regardless of which form it has taken. Fantastic! About the "replaceUnit" function, though, is it possible to replace the unit with a unit from another tribe? Or would another function be better for that?

One area I'm having trouble figuring out is how to randomly generate a barbarian unit(s) in a mod where the map is different each time. E.g., if I wanted to generate a naval unit, how can I find sea? Would it be possible to make an "as far away from a city" -- now we have nearestCity, but what about the reverse? Trying to think about having randomly generated maps, and how to create units from events that are not always in capital cities.
 
About the "replaceUnit" function, though, is it possible to replace the unit with a unit from another tribe? Or would another function be better for that?
You would have to write a new function for that. Here's the code for gen.replaceUnit:
Code:
function gen.replaceUnit(oldUnit,replacementType)
    local newUnit = civ.createUnit(replacementType,oldUnit.owner,oldUnit.location)
    gen.copyUnitAttributes(oldUnit,newUnit)
    gen.deleteUnit(oldUnit,newUnit)
    return newUnit
end
You can just change the unit owner in in the createUnit function to the new tribe (and add a suitable argument).

One area I'm having trouble figuring out is how to randomly generate a barbarian unit(s) in a mod where the map is different each time. E.g., if I wanted to generate a naval unit, how can I find sea?
You could generate random numbers between 0 and mapWidth-1, and 0 and mapHeight-1, get the tile associated with those numbers (check if the tile exists, since only half will), and then check if the tile is ocean or not. Something like this:
Code:
local function getRandomOceanTile()
    local tile = nil
    local width,height,maps = civ.getAtlasDimensions()
    repeat
        local xVal = math.random(0,width-1)
        local yVal = math.random(0,height-1)
        tile = civ.getTile(xVal,yVal,0)
    until tile and tile.baseTerrain.type == 10
    return tile
end
This might not work well if you have very little ocean to find.

Would it be possible to make an "as far away from a city" -- now we have nearestCity, but what about the reverse? Trying to think about having randomly generated maps, and how to create units from events that are not always in capital cities.
"As far away from" a particular city probably means a square on the north or south pole, or a corner on a flat world.

Finding the square with the greatest distance between it and any city means checking every tile, finding the nearest city for that tile, and choosing the best one. Not hard to code, but relatively expensive to run (proportional to #tiles*#cities), so it could cause noticeable lag, especially if the map is large. Also, since tribes tend to build new cities close to existing ones, the tile furthest away from cities won't typically change very much.

My advice would be to choose a tile at random, see if it meets certain criteria (perhaps just not too close to a city, and possibly not too far either) and, if it does, generate the barbarians there. If not, try another tile. Perhaps count tries and use the break command just in case conditions are not actually met or common.
 
Thanks Prof. This is all interesting. I also appreciate the notes about creating lag. I want to be wary of this.

Here is the code I wrote with your advice above. The unit does get replaced in the game, but the owner is still human. I then thought of an issue... What if the unit moves onto a tile with another unit present? Then, which tribe occupies the tile? Could this cause an error? [I do put an "end" on the discreteEvents heading below, but there is more below it that is operating fine.]

Spoiler :
Code:
function gen.servileWar(oldUnit,replacementType)
    local newUnit = civ.createUnit(replacementType,tribeAliases.Barbarians,oldUnit.location)
    gen.copyUnitAttributes(oldUnit,newUnit)
    gen.deleteUnit(oldUnit,newUnit)
    return newUnit
end


discreteEvents.onEnterTile(function (unit, previousTile, previousDomainSpec)
    if unit.type == unitAliases.Settlers and unit.location.improvements then
        gen.servileWar(unit, unitAliases.ArmedWorkers)
    end
 
Here is the code I wrote with your advice above. The unit does get replaced in the game, but the owner is still human. I then thought of an issue... What if the unit moves onto a tile with another unit present?

It is probably best to use the function gen.moveUnitAdjacent to move the unit to a different tile, or to use gen.getAdjacentTiles to pick a different tile to create the unit on in the first place. Note that the second argument for gen.moveUnitAdjacent is optional (that's why it has a question mark in the documentation). It is there to allow you to specify the 'rank' of each adjacent tile, so the game can choose the tile with the smallest rank when choosing where to move the unit.

I don't remember exactly what happens when units of different tribes share the same tile. If you're interested, run the experiment.

Thanks Prof. This is all interesting. I also appreciate the notes about creating lag. I want to be wary of this.
Here's a discussion of a lag issue.
 
These are good ideas, thanks Prof. I'll tinker and report back.

Do you happen to know the function the game uses when partisans emerge from a captured city? I'd essentially like to do this, but focused around a unit instead of a city.
 
Do you happen to know the function the game uses when partisans emerge from a captured city? I'd essentially like to do this, but focused around a unit instead of a city.
From the Apolyton archive (post 57 of linked page):

shortcuts from rules.txt:
Communism=Cmn, Conscription=Csc, Guerrilla Warfare=Gue, Gunpowder=Gun

CityLevel = RoundDown[( CitySize+4)/8 ]
CitySize is the size of the city before the capture.

BNP: basic number of partisans
TypeMultiplier: a multiplier that affects the result

defender's techs​
attacker has Gue​
BNP​
TypeMultiplier​
Gue​
no​
CityLevel -1​
2​
Gue​
yes​
CityLevel​
1​
Cmn+Gun​
irrelevant​
CityLevel -1​
1​

CscModifier: Conscription modifier

defender has Csc​
CscModifier​
yes​
1​
no​
0​

GovtModifier
: Government modifier
GovtModifier=1/2*abs(govindexD-govindexA)
(abs...absolute value)

govindexD- defender's government index value
govindexA- attacker's government index value
Index values:
Anarchy=1,Desp=2, Mon=3, Com=4, Fund=5, Rep =6, Dem=7

The formula:
Partisans = (BNP + CscModifier + GovtModifier*CityLevel) * TypeMultiplier
(the result is rounded down)


Notes:
Partisans can pop up only on empty ground squares in "the working city radius" (squares that may be worked in the city window).
The max. number of partisans is 20 - i.e. only one partisan may pop up on one square.
If city was founded by attacker then no partisans pop up.
The Guerilla Warfare obtained by conquering the city counts, in other words it affects the number of partisans (partisans pop up after the attacker chooses a defender's tech).
No effect: unhappy, content, happy people; wltxd, city improvements, killed defender/attacker, decrease of inhabitants by siege, type of terrain.
The defensive value of a terrain affect where the emerging partisans are placed: first partisan to the best devensive terrain and so on. There is a special pattern of placing partisans if all terrain is the same: first square is north-east from the city, then clockwise around the city (this pattern is equal to the pattern of barbs coming from huts and to the pattern of cultivating squares around cities by AI).


A simplified conclusion:
There are two situations when partisans arise: the defender has Guerilla Warfare or the defender has Gunpowder+Communism. Guerilla Warfare produces twice more partisans than the combination Gunpowder+Communism.
The effect of Guerilla Warfare and Gunpowder+Communism is not cumulated: only Guerilla Warfare applies.
Conscription adds 1 or 2 partisans.
Every point of "governments difference" raises the number of partisans by 50%.
The number of partisans depends on the city size: The smallest city that produces them has size 4. Next turning points are 12, 20, 28, 36 etc.
Guerilla Warfare is the only attacker's tech that affects the number of partisans.

Overview (for equal governments):

defender's techs / attacker's techsguegue+csccmn+guncmn+gun+csc
number of partisans for
citylevel1 citylevel2 citylevel3 citylevel4...
0 2 4 6 ...2 4 6 8...0 1 2 3...1 2 3 4...
gue/guegue+csc/gue
1 2 3 4...2 3 4 5...
 
Back
Top Bottom