[TOTPP] Prof. Garfield's Lua Code Thread

Gah! Silly mistake. Thanks for pointing that out, and will keep in mind moving forward.
The code has no error now, but the Envoy unit is not regenerating in the tribe's capital during testing. I'll try starting a new game to see if that might work?
 
Gah! Silly mistake. Thanks for pointing that out, and will keep in mind moving forward.
The code has no error now, but the Envoy unit is not regenerating in the tribe's capital during testing. I'll try starting a new game to see if that might work?
I'm surprised you're not getting an error, since I just realised that I gave you more bad code. tribeData.flagSetValue isn't a real function.
Code:
discreteEvents.onUnitKilled(function (loser, winner, aggressor, victim, loserLocation, winnerVetStatus, loserVetStatus)
    if loser.type == object.uEnvoy then
        tribeData.flagSetValue(loser.owner,"envoyKilled",true)
    end
end)
Code:
discreteEvents.onUnitKilled(function (loser,winner,aggressor,victim,loserLocation,winnerVetStatus,loserVetStatus)
    if loser.type == unitAliases.TribalEnvoy then
        tribeData.flagSetValue(loser.owner,"envoyKilled",true)
    end
end)
The line
Code:
tribeData.flagSetValue(loser.owner,"envoyKilled",true)
should be
Code:
tribeData.flagSetTrue(loser.owner,"envoyKilled")

I should probably add in flagSetValue, since it mirrors counterSetValue and phraseSetValue, and I seem to be trying to use it anyway.
 
Got it! That's interesting, too. What would be the value of doing that in the future, just out of curiosity?

This is working now -- I also had to place the onUnitKilled discreteEvent into the civ.scen section for it to work properly, and then shuffle the require line up. But it's working like a charm now, doing exactly what it should be!

One step closer to release... Thanks Prof.!
 
Got it! That's interesting, too. What would be the value of doing that in the future, just out of curiosity?
Code is more likely to work correctly without having to be debugged. Either I wrote flagSetValue without realising that wasn't a function, or GitHub Copilot suggested it to me and I didn't notice. In either case, flagSetValue is a sensible way to want to write the code, and if I can't even remember that it is the wrong way, and I wrote the code, why not just add the function and save everyone a bit of trouble?
 
Ah OK, that makes sense!

I'm through a test playthru at the moment. Things are humming along. One thing I overlooked, though: siege weapons like battering rams, which can attack units in the field! I know in Knighttime's mod, he had them change to a new unit when adjacent to a city. I'm wondering if there is any way with current lua knowledge to make these units unable to attack single units, and only able to attack tiles with a city. This is not gamebreaking on my end... I don't mind these siege units attacking in-field and sort of consider their slow movement to give cavalry more of a purpose! But I am curious.
 
I'm wondering if there is any way with current lua knowledge to make these units unable to attack single units, and only able to attack tiles with a city. This is not gamebreaking on my end... I don't mind these siege units attacking in-field and sort of consider their slow movement to give cavalry more of a purpose! But I am curious.
Try adding this to registerCombatModifiers.lua
Code:
combatMod.registerCombatModificationRule({
    attacker = "attackCitiesOnlyTrait",
    customCheck = function(attacker,defender)
        return not not defender.location.city
    end,
    aCutomMult = 0
})
And give some units the "attackCitiesOnlyTrait" trait (or, have a table of units instead).
If an attack with a unit is set to 0, the attack is cancelled by the template (we ran into a bug due to this functionality a little while ago).
Note that the AI probably won't be able to use this kind of unit very well.
 
Thank you, Prof.! I'll tinker. And if not for this iteration of the mod, perhaps something in the future.

Note that the AI probably won't be able to use this kind of unit very well.

Indeed! It would be nice to have a whole thread dedicated to "making AI smarter with lua," or something along those lines -- implementing strategies to make a more formidable AI through lua events.
 
Apologies if this is obtuse, but there is a justOnce function -- is there a "continue doing this" function, as well?

I have a situation where, if a flagValue is set to true, and math.random is 5%, something will happen. How can I get this to happen more than once, though (as in, 5% of the time, all the time, instead of just once?)? Is it a matter of putting the code in however many times I want the event to happen?
 
Apologies if this is obtuse, but there is a justOnce function -- is there a "continue doing this" function, as well?

I have a situation where, if a flagValue is set to true, and math.random is 5%, something will happen. How can I get this to happen more than once, though (as in, 5% of the time, all the time, instead of just once?)? Is it a matter of putting the code in however many times I want the event to happen?
I'm not sure I understand the question.

If you want to do the same (or very similar) thing several times, you can use a loop.

If you want to do the same thing in multiple different situations, you define a helper function, and call it in each situation. Something like this:
Code:
flag.define("someEventFlag",false)
local function someEvent()
    if flag.value("someEventFlag") and math.random() <0.05 then
        civ.ui.text("Some event happened")
    end
end

discreteEvents.onTurn(function(turn)
    -- Something happened
    -- also, someEvent sometimes happens
    someEvent()
end)

discreteEvents.onUnitKilled(function (loser, winner, aggressor, victim, loserLocation, winnerVetStatus, loserVetStatus)
    -- Something else happened
    -- also, someEvent sometimes happens
    someEvent()
end)
 
Thank you, Prof.! Here is my event. It is not working -- but also, no error. This happens to me more and more lately. Any idea what I'm doing wrong? Kind of trying to mimic the function of the rebelCity from before -- this time, if the player chooses the Bureaucrat, I want a future choice to come up 5% of the time (I'll adjust this, just testing for now) -- but not only once. I want it to keep coming up based on previous choices. So as you see, for certain choices (like "pay the bureaucrat,") it resets the flag so the conundrum may re-appear. Does this make sense?

Here is my code:

Spoiler :
Code:
for i=0,7 do
    flag.define("DivinedPowerEventOngoing"..i,false)
    flag.define("BureaucratCityEventOngoing"..i,false)
end
for i=0,7 do
    counter.define("DivinedPowerCityLocationID"..i,-1)
    counter.define("BureaucratCityLocation"..i,-1)
end



-- PT I

    local StandardizedRites = civ.ui.loadImage("Images/standardizedrites.bmp")
    if tribe:hasTech(techAliases.StandardizedRites) then
        gen.justOnce("SRReceivedBy"..tostring(tribe.id),function()
        local choice = nil
        if tribe.isHuman then
        local dialog = civ.ui.createDialog()
                dialog.title = "Select a Local Leader"
                dialog.width = 500
                dialog:addImage(StandardizedRites)
                local multiLineText = "Advisors come to you, bringing two prominent individuals from local tribes: one bureaucrat and one priest.\n^\n^'These leaders seek greater status in your realm. What would you have us do?'\n^\n^"
                text.addMultiLineTextToDialog(multiLineText,dialog)
                    dialog:addOption("Select the bureaucrat to lead a city", 1)
                    dialog:addOption("Select the priest to lead a city", 2)
                    dialog:addOption("Put them to death. This is not a democracy!",3)
                 choice = dialog:show()
        else
             choice = math.random(1,3)    
        end
                if choice == 1 then
                local chosenCity2 = nil
                    if tribe.isHuman then
                        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 bureaucratChoice = text.menu(menuTable,"Select a city for the bureaucrat to lead","A Competent Bureaucrat")
                        if bureaucratChoice >= offset then
                            chosenCity2 = civ.getCity(bureaucratChoice-offset)
                    end
                else
                    local aiChoiceList = {}
                    local index = 1
                    for city in civ.iterateCities() do
                        if city.owner == tribe then
                            aiChoiceList[index] = city.id
                            index = index + 1
                        end
                    end
                    if index > 1 then
                    local aiChoice = aiChoiceList[math.random(1,index-1)]
                    chosenCity2 = civ.getCity(aiChoice)
                    end
                end
                if civ.isCity(chosenCity2) then
                    chosenCity2:addImprovement(improvementAliases.Improvement16)
                    flag.setTrue("BureaucratCityEventOngoing"..tribe.id)
                    counter.setValue("BureaucratCityLocation"..tribe.id,gen.getTileID(chosenCity2.location))
                    if tribe.isHuman then
                    civ.ui.text("Your decision to place a competent bureaucrat in power of "..chosenCity2.name.." has made the people there more productive.")
                    end
                end
            elseif choice == 2 then
                local chosenCity1 = nil
                    if tribe.isHuman then
                        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 priestChoice = text.menu(menuTable,"Select a city for the priest to lead","An Inspiring Priest")
                        if priestChoice >= offset then
                            chosenCity1 = civ.getCity(priestChoice-offset)
                    end
                else
                    local aiChoiceList = {}
                    local index = 1
                    for city in civ.iterateCities() do
                        if city.owner == tribe then
                            aiChoiceList[index] = city.id
                            index = index + 1
                        end
                    end
                    if index > 1 then
                    local aiChoice = aiChoiceList[math.random(1,index-1)]
                    chosenCity1 = civ.getCity(aiChoice)
                    end
                end
                if civ.isCity(chosenCity1) then
                chosenCity1:addImprovement(improvementAliases.Tournament)
                flag.setTrue("PriestMayor")
                    if tribe.isHuman then
                    civ.ui.text("Your decision to place an inspiring priest in power of "..chosenCity1.name.." has made the people there happier!")
                    end
                end
            elseif choice == 3 then
            gen.createUnit(unitAliases.EliteGuard,civ.getCurrentTribe(),{0,0},{count = 2, randomize = false, scatter = false, inCapital = true, veteran = true, homeCity = nil, overrideCanEnter = false, overrideDomain = false, overrideDefender = false})
            flag.setTrue("KilledMayors")
                if tribe.isHuman then
                civ.ui.text("Elite Guard units have been placed in your capital to prevent charlatans from seeking power in the future.")
                end
            end
        end)
    end
    
    
    
-- PT II


discreteEvents.onCityProcessed(function (city)
    local tribeID = city.owner.id
    if not flag.value("BureaucratCityEventOngoing"..tribeID) then
        return
    end
    if flag.value("BureaucratCityEventOngoing"..tribeID) and math.random() < 0.90 then
    local choice = nil
    local chosenCity2 = gen.getTileFromID(counter.value("BureaucratCityLocation"..tribeID)).city
        if tribe.isHuman then
        local dialog = civ.ui.createDialog()
                dialog.title = "A Local Leader Grows Restless"
                dialog.width = 500
                local multiLineText = "The leader of "..chosenCity2.name..", a prominent bureaucrat who you previously selected, has come to you with terms. 'Give me more gold for my services, or I shall take them elsewhere!'"
                text.addMultiLineTextToDialog(multiLineText,dialog)
                    if tribe.money >= 100 then
                    dialog:addOption("Pay this local leader. They have done a fine job. (-100 gold)", 1)
                    end
                    dialog:addOption("We cannot afford these terms. Send them on their way.", 2)
                    if math.random() < 0.50 then
                    dialog:addOption("This leader enjoys a comfortable lifestyle. Threaten them, and see what happens.", 3)
                    end
                    dialog:addOption("It is time to send a message to other chieftains. Find me a spike for this 'leader's' head.",4)
                 choice = dialog:show()
        else
             choice = math.random(1,4)    
                 if tribe.money < 100 then
                    choice = 1
                end
        end
        if choice == 1 then
            tribe.money = math.max(0,tribe.money - 100)
            flag.setTrue("BureaucratCityEventOngoing"..tribeID)
            if tribe.isHuman then
            civ.ui.text("Your payment has satiated the leader of "..chosenCity2.name..".")
            end
        elseif choice == 2 then
            chosenCity2:removeImprovement(improvementAliases.Improvement16)
            flag.setFalse("BureaucratCityEventOngoing"..tribeID)
            if tribe.isHuman then
            civ.ui.text("Dissatisfied, this leader has chosen to leave "..chosenCity2.name.." for greener pastures.")
            end
        elseif choice == 3 then
        tribe.money = math.max(0,tribe.money + 100)
            if tribe.isHuman then
                civ.ui.text("Embarrassed by their demands, this leader has not only opted to stay, but has also given tribute to your realm.")
            end
        elseif choice == 4 then
            chosenCity2:removeImprovement(improvementAliases.Improvement16)
            flag.setFalse("BureaucratCityEventOngoing"..tribeID)
            tribe.money = math.max(0,tribe.money + 1000)
                if tribe.isHuman then
                    civ.ui.text("Troops have raided the estate of this leader, taking in countless treasures, along with their head. This bureaucrat had grown fat on the riches of their constituents. Good riddance!")
                end
        end
    end
end)
 
Try adding a print statement to debug:
Code:
discreteEvents.onCityProcessed(function (city)
    print(city.name,flag.value("BureaucratCityEventOngoing"..tribeID))
This will at least tell you if the event is being triggered at all, and if the flag is true or not.

If the event is registering and the flag is true, then you should put more print statements to see if values are what you expect them to be. I didn't notice an error when reading the code, so it may take some detective work.

If the discrete event isn't registering at all, that might be a template problem. If that's the case, only do a little troubleshooting (e.g. to make sure that the file this is all in has been "required") and I'll have a look in the template to see if it is a mistake on my end.
 
Hi Prof. I tried the print statement.
Here is the line I got:
"No units placed, since no valid location."

I'm not trying to place units in this code, though -- so I wonder if my location flag is what's messing the code up?
 
Hi Prof. I tried the print statement.
Here is the line I got:
"No units placed, since no valid location."

I'm not trying to place units in this code, though -- so I wonder if my location flag is what's messing the code up?
That is a message from gen.createUnits, so that if units aren't placed when you're testing an event, you know that the event actually took place.

If you put the print statement where I suggested, that means that onCityProcessed is not happening (or the discreteEventsRegistrar is not working for onCityProcessed). Have you at some point called civ.scen.onCalculateCityYield to register a city yield calculation? If so, that's the reason onCityProcessed is not working, since it uses that function.
 
Interesting. I am using calculcateCityYield.lua -- to change the happiness given by an improvement, in another event. Otherwise, not using that function at the moment.
I'm not using onCityProcessed.lua, either, but rather a discreteEvent for it, within onCityProcessingComplete.lua (so that it keeps the same flags intact). Could this be an issue?
 
Would you mind uploading your code to GitHub and either making the repository public or adding me as a collaborator? Here's a tutorial. This way I can look at (and run) the code as it currently is, and future updates only require synchronising the changes (not downloading images etc. again).
 
Turned out that the problem was in template code. I've pushed a fix for events.lua to your repository, and I plan to update the template shortly.

The onCityProcessed leverages the onCalculateCityYield execution point, but keeps a table so that it is only done once per turn. For some reason, I failed to purge the table before each player's turn. Presumably, I tested once, found everything to work, and failed to test two turns in a row.

However, can you please upload rules/art/etc. to your repository? That way, if I have to debug code again in the future, I can actually start or load a game. I may have been unclear about what I wanted earlier. Using GitHub means that only changes are uploaded/downloaded, so art/etc. doesn't have to be transferred each time a line of code is changed.

P.S. in calculateCityYield.lua, you define
Code:
cityYield.onCalculateCityYield
twice.

You should only have one cityYield.onCalculateCityYield function, and include everything needed in that. Otherwise, only the last one will have effect.
 
Prof., can't thank you enough! This is great. Will upload art and all else at some stage soon, as well. The fix worked perfectly on my end, and I also repaired the onCalculateCityYield part.
 
One issue I am finding with population limits for units is that, if the city is producing the unit, it will continue to regardless of the limit, as long as the player does not change the production queue. Any ideas on how to circumvent this?
 
One issue I am finding with population limits for units is that, if the city is producing the unit, it will continue to regardless of the limit, as long as the player does not change the production queue. Any ideas on how to circumvent this?
You can check city.currentProduction, and change it if the current selection is obsolete.
 
Top Bottom