Small Questions Thread

Check your rules.txt file, in particular the UNITS_ADVANCED section. There is some problem when the changeRules module reads that section of the rules.txt.

I'm not sure if I'll be able to post for the next day or two.
OK, this helped me figure it out! The rules.txt loads and plays fine without the Lua Template, but the Lua Template wanted the # of units in UNITS and the # of units in UNITS_ADVANCED to match! I made sure that was the case, and now the template loads fine. Now to editing stuff! Hopefully. Thanks Prof.
 
Is there a Lua function to force the AI to use missile flagged units are normal?
I am wondering if there is something from the "Over the Reich" development that may be a factor?
You could not give it the missile flag, and just kill it with a unit killed event. Or, maybe use Lua to remove the flag on unit activation, and restore it as part of onInitiateCombat.
 
In my scenario I had previously had some "onunitkilled" triggers, which I removed by copying over the edited file with the vanilla file.

Now when I go to found a new city, but cancel, I get this message:

1692088133910.png

Is there something in the triggers file I am missing?
 
In my scenario I had previously had some "onunitkilled" triggers, which I removed by copying over the edited file with the vanilla file.

Now when I go to found a new city, but cancel, I get this message:

[IMG alt="1692088133910.png"]https://forums.civfanatics.com/attachments/1692088133910-png.669849/[/IMG]
Is there something in the triggers file I am missing?
In your triggerEvents.lua file, under triggerEvents.onCityFounded (probably the last event), add a return line
Code:
    return function() end

In a version of your scenario that I have, the modified code looks like this:
Code:
function triggerEvents.onCityFounded(city)
    context[getContext()]["onCityFounded"](city)
    universal["onCityFounded"](city)
    return function() end
end

In version 18 of the TOTPP, the onCityFounded event was given the ability to return a function to be performed when the city was cancelled:

onCityFounded
civ.scen.onCityFounded(function (city) -> optional (function () -> void)) -> void

Registers a function to be called every time a city is founded. The callback takes the city as a parameter, and can optionally return a function (since 0.18) that is called to perform cleanup when the user cancels founding the city.

If, for example, your code changed the city centre tile, the returned function could undo that. This is supposed to be optional, but apparently it is not. I presume this means that some older Lua scenarios (at least ones where you can build cities) can cause errors on TOTPP v0.18.
 
Another pops up!
I have a little error that seems to be tied to the "onturn" events and perhaps joined with the legacy events...
I suspect a problem with the units spawned each turn in the Soviet cities, as this one pops up during the open of the Russian turn:
1692114969117.png

I am wondering if a conflict can happen if the civ is spawning units, but the city ownership is not clearly defined?
Here is a typical Soviet unit event here:
Code:
--If the Soviet civ has the "Patriotic War" tech, the event will spawn Red Army forces forces in major cities...
if civ.hasTech(object.tRussians, object.aRusPatrioticWar) and object.tRussians.isHuman == false then
    --Red Army reinforcements from Moscow
    civlua.createUnit(object.uRedArmy, object.tRussians, {{30,30,0}}, {count=9, randomize=false, veteran=false})
    --Red Army reinforcements from Leningrad
    civlua.createUnit(object.uRedArmy, object.tRussians, {{25,21,0}}, {count=9, randomize=false, veteran=false})
    --Red Army reinforcements from Stalingrad
    civlua.createUnit(object.uRedArmy, object.tRussians, {{34,42,0}}, {count=9, randomize=false, veteran=true})
end
In Macro the enemy owning a spawn city will simply null the event, but I realise it might not be so simple with Lua?
Just wondering if anyone can spot something I am missing here?
(I can post files if needed) :)

Everything else has been running nicely, this is an immensely fun experiment with Lua so far.
(now if I can only get the missile units to balance!)
 
Having looked at and around line 627 in legacyEventEngine.lua, I recommend looking for a legacy event which doesn't have a "@THEN" section. I think I'm going to need to write code to validate legacy events. Fortunately, I think I've written most of the tools necessary for this already.
 
My bug-hunt detective hat is now on. :)
Hold on. Send me your file, since I can add a couple lines of code to do the check, rather than have you manually look for something. Just in case the error is caused by something different.
 
Hello! My thanks, Prof., for all your help thus far. I'm back at it as of today. I have two questions regarding the mod I'm working on, and then I am in need of some advice for a fix for another piece of code that isn't behaving correctly (probably because of that X,Y issue).

1. Below is code that registers where a goodie hut was, changes that terrain upon the goodie hut disappearing, and then also gradually alters the terrain adjacent to that goodie hut tile. I'd like to form some stipulations, though:

- I want the system to "log" what terrain existed before it changed to "TribeTerrain" -- and then for there to be a chance that it can revert back to that original terrain during the game.
- I'd also like these transformations on either end, both adjacent terrain turning into "TribeTerrain" and the chance of reverting back to the original terrain, to only happen once when a certain tech (say, "XYZ") has been researched -- effectively meaning that if that tech exists anywhere in the game at any point, the "TribeTerrain" will stop generating, and will begin to permanently revert back to its original terrain.

Would these be possible?

Here is the current code:

Spoiler :
Code:
local postTribeTerrain = civ.getBaseTerrain(0,12)
local goodieHutTribeTerrainChance = 0.005


local villageTypeThresholdTable = gen.makeThresholdTable({
    [0.66] = unitAliases.VillageEcon,
    [0.33] = unitAliases.VillageInfra,
    [0] = unitAliases.VillageMili,
})


local postGoodieHutTerrain = civ.getBaseTerrain(0,12)
local goodieHutVillageChance = 0.01
discreteEvents.onTurn(function(turn)
    local state = gen.getState()
    if not state.hutTable then
        state.hutTable = {}
        for tile in civlua.iterateTiles() do
            if tile.hasGoodieHut then
                state.hutTable[gen.getTileID(tile)] = true
            end
        end
    end
    for tileID,_ in pairs(state.hutTable) do
        local tile = gen.getTileFromID(tileID)
        if not tile.hasGoodieHut and tile.baseTerrain == postGoodieHutTerrain then
            if math.random() < goodieHutVillageChance and tile.defender == nil then
                local villageType = villageTypeThresholdTable[math.random()]
                civ.createUnit(villageType,civ.getTribe(0),tile)
            end
                for _,adjacentTile in pairs(gen.getAdjacentTiles(tile)) do
                if math.random() < goodieHutTribeTerrainChance and tile.defender == civ.getTribe(0) then
                adjacentTile.baseTerrain = postTribeTerrain
                end
            end
        end
    end
end)

2. Since this is a mod, and not a scenario, how can I register city lists in the middle of the game?
For example, say I want a list of player cities to come up in a menu, and the player selects a city, and then a unit or something appears in that city.

3. Here is some code for generating food/production onEnterTile. It keeps going to the capital city... or at least, not the "nearest city." (Ignore the TitheTable part, that is functioning as intended as it does not require nearest city.)

Spoiler :
Code:
local function nearestFriendlyCity(unit)
    local bestCitySoFar = nil
    local bestDistanceSoFar = 1000000
    local function distance(tileA,tileB)
        local xDist = tileA.x
        local yDist = tileA.y
        return math.sqrt(xDist^2+yDist^2)
    end
    for city in civ.iterateCities() do
        if city.owner == unit.owner and distance(city.location,unit.location) < bestDistanceSoFar then
            bestCitySoFar = city
            bestDistanceSoFar = distance(city.location,unit.location)
        end
    end
    return bestCitySoFar
end
console.nearestFriendlyCity=nearestFriendlyCity

local function nearestFriendyCity(tile,tribe)
local bestCity = nil
local bestDistance = 1000000
for city in civ.iterateCities() do
    if city.owner == tribe then
    local distance = gen.distance(city.location,tile)
    if distance < bestDistance then
        bestCity = city
        bestDistance = distance
    end
end
end
return bestCity
end

local function addShieldsToNearestCity(tile,tribe,shields)
    local city = nearestFriendlyCity(tile,tribe)
    if city then
    city.shields = city.shields + shields
    end
end

local function addFoodToNearestCity(tile,tribe,food)
    local city = nearestFriendlyCity(tile,tribe)
    if city then
    city.food = city.food + food
    end
end




local function bestowShieldBonus(city,bonus,turns)
    cityData.counterSetValue(city,"bonusShields",bonus)
    cityData.counterSetValue(city,"remainingTurnsForBonusShields",turns)
    local city = nearestFriendlyCity(tile,tribe)
end

local shepherdFoodTable = gen.makeThresholdTable({
    [0.75] = 3,
    [0.50] = 5,
    [0.25] = 7,
    [0] = 10,
})

local rangerShieldsTable = gen.makeThresholdTable({
    [0.75] = 3,
    [0.50] = 5,
    [0.25] = 7,
    [0] = 10,
})




discreteEvents.onEnterTile(function (unit, previousTile, previousDomainSpec)
    if math.random() < 0.25 then
        if unit.type == unitAliases.Shepherd and unit.location.terrainType == 0 then
            addFoodToNearestCity(unit.location,unit.owner,shepherdFoodTable[math.random()])
            if unit.owner.isHuman then
            civ.playSound("Food.wav")
            end
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Shepherd and unit.location.terrainType == 1 then
            addFoodToNearestCity(unit.location,unit.owner,shepherdFoodTable[math.random()])
            if unit.owner.isHuman then
            civ.playSound("Food.wav")
            end
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Ranger and unit.location.terrainType == 3 then
            addShieldsToNearestCity(unit.location,unit.owner,rangerShieldsTable[math.random()])
            if unit.owner.isHuman then
            civ.playSound("Production.wav")
            end
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Ranger and unit.location.terrainType == 4 then
            addShieldsToNearestCity(unit.location,unit.owner,rangerShieldsTable[math.random()])
            if unit.owner.isHuman then
            civ.playSound("Production.wav")
            end
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Statesman1 and unit.location.improvements then
            unit.owner.money = unit.owner.money + titheTable[math.random()]
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Statesman2 and unit.location.improvements then
            unit.owner.money = unit.owner.money + titheTable[math.random()]
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Statesman3 and unit.location.improvements then
            unit.owner.money = unit.owner.money + titheTable[math.random()]
        end
    end
    if math.random() < 0.25 then
        if unit.type == unitAliases.Statesman4 and unit.location.improvements then
            unit.owner.money = unit.owner.money + titheTable[math.random()]
        end
    end
end)
 
3. Here is some code for generating food/production onEnterTile. It keeps going to the capital city... or at least, not the "nearest city." (Ignore the TitheTable part, that is functioning as intended as it does not require nearest city.)
This has a quick answer.

This function calculates the distance from the (0,0) tile to tileA, not the distance between tiles A and B.

Code:
    local function distance(tileA,tileB)
        local xDist = tileA.x
        local yDist = tileA.y
        return math.sqrt(xDist^2+yDist^2)
    end

The general library provides distance functions gen.distance, gen.tileDist, gen.gameMechanicDistance.
 
2. Since this is a mod, and not a scenario, how can I register city lists in the middle of the game?
For example, say I want a list of player cities to come up in a menu, and the player selects a city, and then a unit or something appears in that city.
You will have to generate the option list programmatically. Here's an example using text.menu

Code:
local menuTable = {}
local offset = 3
for city in civ.iterateCities() do
    if city.owner == civ.getCurrentTribe() then
        menuTable[offset+city.id] = city.name
    end
end
local choice = text.menu(menuTable,"Choose a City.","Menut Title")
local chosenCity = nil
if choice >= offset then
    chosenCity = civ.getCity(choice-offset)
end

text.menu requires the menuTable to have all indices at least 1 (but you can have gaps in the array). However, city ID numbers start at 0, so the trick is to add and subtract an "offset" so that all menu options are at least 1. By choosing an offset that is more than 1, you can also have places for other options. Note that text.menu automatically splits the menu into multiple "pages" if there are a lot of options.
 
Thank you Prof. I'll tinker with this!

Here is the error I get when I add "-tileB.x" and "-tileB.y" in my code:

Spoiler :
Code:
Global variables are disabled
Enter console.commands() to see a list of keys in the console table.  Some give access to functions in modules, others will run event code.
...strator\Desktop\Ancient ToT With Lua\Original\events.lua:1478: attempt to index a nil value (local 'tileB')
stack traceback:
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:1478: in local 'distance'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:1483: in upvalue 'nearestFriendlyCity'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:1515: in upvalue 'addFoodToNearestCity'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:1572: in field '?'
    ...oT With Lua\Original\LuaCore\discreteEventsRegistrar.lua:396: in field 'performOnEnterTile'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:419: in upvalue 'onEnterTile'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:445: in upvalue 'executeOnEnterTile'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:511: in upvalue 'doOnUnitActivation'
    ...strator\Desktop\Ancient ToT With Lua\Original\events.lua:543: in function <...strator\Desktop\Ancient ToT With Lua\Original\events.lua:534>
 
Based on that error, unit.location is nil, which means that you're not providing the function with a unitObject when you call it.

In the code, you've also defined nearestFriendlyCity twice, except that one is 'nearestFriendyCity'. So, you're probably calling nearestFriendlyCity with a tile object instead of a unit object. In any case, in Lua you can't "overload" function names by defining them with different arguments. Rather, the last one defined is the one that will be used.
 
Based on that error, unit.location is nil, which means that you're not providing the function with a unitObject when you call it.

In the code, you've also defined nearestFriendlyCity twice, except that one is 'nearestFriendyCity'. So, you're probably calling nearestFriendlyCity with a tile object instead of a unit object. In any case, in Lua you can't "overload" function names by defining them with different arguments. Rather, the last one defined is the one that will be used.
Such a silly mistake. I edited that one, and removed the first, and it seems to be working great since then. Thank you for spotting that!
 
1. Below is code that registers where a goodie hut was, changes that terrain upon the goodie hut disappearing, and then also gradually alters the terrain adjacent to that goodie hut tile. I'd like to form some stipulations, though:

- I want the system to "log" what terrain existed before it changed to "TribeTerrain" -- and then for there to be a chance that it can revert back to that original terrain during the game.
- I'd also like these transformations on either end, both adjacent terrain turning into "TribeTerrain" and the chance of reverting back to the original terrain, to only happen once when a certain tech (say, "XYZ") has been researched -- effectively meaning that if that tech exists anywhere in the game at any point, the "TribeTerrain" will stop generating, and will begin to permanently revert back to its original terrain.
I think you want something like this:
Spoiler :

Code:
local postGoodieHutTerrain = civ.getBaseTerrain(0,12)
local goodieHutVillageChance = 0.01
local goodieHutTribeTerrainChance = 0.005
local xyzTech = civ.getTech(0)
local function anyTribeHasXYZTech()
    for tribe in civlua.iterateTribes() do
        if tribe:hasTech(xyzTech) then
            return true
        end
    end
    return false
end

discreteEvents.onTurn(function(turn)
    local state = gen.getState()
    if not state.hutTable then
        state.hutTable = {}
        for tile in civlua.iterateTiles() do
            if tile.hasGoodieHut then
                state.hutTable[gen.getTileID(tile)] = true
            end
        end
    end
    state.originalTerrainType = state.originalTerrainType or {}
    for tileID,_ in pairs(state.hutTable) do
        local tile = gen.getTileFromID(tileID)
        if not tile.hasGoodieHut and tile.baseTerrain == postGoodieHutTerrain then
            if math.random() < goodieHutVillageChance and tile.defender == nil then
                local villageType = villageTypeThresholdTable[math.random()]
                civ.createUnit(villageType,civ.getTribe(0),tile)
            end
            if anyTribeHasXYZTech() then
                for _,adjacentTile in pairs(gen.getAdjacentTiles(tile)) do
                    local tileID = gen.getTileID(adjacentTile)
                    if math.random() < goodieHutTribeTerrainChance
                    and state.originalTerrainType[tileID]
                    then
                        local origType = state.originalTerrainType[tileID]
                        adjacentTile.baseTerrain = civ.getBaseTerrain(0,origType)
                        state.originalTerrainType[tileID] = nil
                    end
                end
                
            else
                for _,adjacentTile in pairs(gen.getAdjacentTiles(tile)) do
                    if math.random() < goodieHutTribeTerrainChance and tile.defender == civ.getTribe(0) then
                        state.originalTerrainType[gen.getTileID(adjacentTile)] = adjacentTile.baseTerrain.type
                        adjacentTile.baseTerrain = postTribeTerrain
                    end
                end
            end
        end
    end
end)

This may not be exactly right (partly 'cause I'm not completely sure of the exact functionality you want), but the key idea is to store the base terrain 'type' (similar to id for most objects, except 'type' is recycled for each map) in a part of the state table.

Make sure the table has been initialized:
Code:
 state.originalTerrainType = state.originalTerrainType or {}

Add the tile's base terrain type to the storage table before changing the terrain.
Code:
                        state.originalTerrainType[gen.getTileID(adjacentTile)] = adjacentTile.baseTerrain.type
                        adjacentTile.baseTerrain = postTribeTerrain
When changing the terrain back, use the stored value in the table, then delete the value when it is no longer relevant:
Code:
                        local origType = state.originalTerrainType[tileID]
                        adjacentTile.baseTerrain = civ.getBaseTerrain(0,origType)
                        state.originalTerrainType[tileID] = nil

Note: instead of maintaining section of the state table yourself for this feature, you could use a tileData counter (you'd probably want a -1 default value). The reason to maintain a separate table is if you want to loop over all the converted tiles, since iterating over every tile in the map can cause a bit of lag, at least for large maps. This was why I created a hutTable instead of making a flag, and looping over all tiles and checking if the flag was true.
 
Top Bottom