[TOTPP] Lua Scenario Template

Just wondering if Lua can allow the restriction of what units can be airlifted with the airport improvement?
 
Just wondering if Lua can allow the restriction of what units can be airlifted with the airport improvement?

This works for me (potential issue below). (Note, if you have an old version of the template, you'll have to put this code in an appropriate place in the keyPressSettings.lua rather than rely on the discreteEvents. Let me know if you need help.)

Code:
local noAirlift = {
[gen.original.uSettlers.id]=true
}
function discreteEvents.onKeyPress(keyCode)
    local activeUnit = civ.getActiveUnit()
    if keyCode == keyboard.l and activeUnit then
        if noAirlift[activeUnit.type.id] then
            local origDomain = activeUnit.type.domain
            activeUnit.type.domain = 2
            civ.sleep(100)
            activeUnit.type.domain = origDomain
        end
    end
end

This code does rely on Lua being "faster" than the game, so that the active unit's domain is changed to 2 before the airlift detection happens. This is probably not going to be an issue in practice, since this code has to check 1000 key events before changing the domain, and it still works:

Code:
local noAirlift = {
[gen.original.uSettlers.id]=true
}
for i=0,1000 do
    function discreteEvents.onKeyPress(keyCode)
        if keyCode == keyboard.j then
            print("me")
        end
    end
end
function discreteEvents.onKeyPress(keyCode)
    local activeUnit = civ.getActiveUnit()
    if keyCode == keyboard.l and activeUnit then
        if noAirlift[activeUnit.type.id] then
            local origDomain = activeUnit.type.domain
            activeUnit.type.domain = 2
            civ.sleep(100)
            activeUnit.type.domain = origDomain
        end
    end
end

However, this code, which uses the relatively slow print command, does fail:

Code:
local noAirlift = {
[gen.original.uSettlers.id]=true
}
for i=0,1000 do
    function discreteEvents.onKeyPress(keyCode)
            print("me")
    end
end
function discreteEvents.onKeyPress(keyCode)
    local activeUnit = civ.getActiveUnit()
    if keyCode == keyboard.l and activeUnit then
        if noAirlift[activeUnit.type.id] then
            local origDomain = activeUnit.type.domain
            activeUnit.type.domain = 2
            civ.sleep(100)
            activeUnit.type.domain = origDomain
        end
    end
end

If it turns out this is unreliable, there are a couple other options. One is to use a unitActivation trigger to change the status of all airports to "used" when an ineligible unit is activated (and keep track in a table) and change them back later using these functions.
Code:
gen.isUsedAirport(city)-->boolean
gen.setUsedAirport(city)-->void
gen.clearUsedAirport(city)-->void

Another option is to implement an "airlift" system in events, which we did in Over the Reich. I can provide examples of these possibilities if desired.
 
Thanks! - I will think over the options when I get to the suitable phase of the upgrade.
I was thinking of making the airfields into naval transport docks, and tying them to ports with Lua, so it would mean things
like tanks being transported make sense, as it is kind of silly to airlift Tiger tanks in a WW2 setting. Changing the improvement
would mean a lot of CanBuildSetting edits, so I will weight the options...(Probably will go for Lua, in the end)...

Basically would aim at making everything but infantry non-airlift.
 
I've updated the template. navy.lua and promotion.lua have some fixes.

The template now has "Lua Scenario Template Rules" integrated. Pressing Ctrl+Shift+F4 in your game will give you the option to generate a basic rules_lst.txt file. If you want to play around with these rules, you can just extract the TemplateTestingFiles into a downloaded version of the template. At the moment, you can play around with promoting units to a new type, or demoting units instead of destroying them. There is also combat bonuses that has already been discussed here.

Let me know if you have any trouble.
 

Attachments

Updated the Legacy Event Engine to fix delayed events not working, and to destroy cities and terrain improvements on tiles subject to the ChangeTile action.
 
Is there a place in the template that has aircraft defend first? I'm having a hard time figuring out where it is and want to include it in Tech's scenario. I wasn't sure if this was added as a toggle in more recent versions of the template.
 
Is there a place in the template that has aircraft defend first? I'm having a hard time figuring out where it is and want to include it in Tech's scenario. I wasn't sure if this was added as a toggle in more recent versions of the template.

In MechanicsFiles\combatSettings.lua, there is a function register.onChooseDefender, where you can program that the flying units defend first against fighters. I think I gave you sample code for that for Cold War, but let me know if you need me to program it for you, and I can do that.
 
I think I gave you sample code for that for Cold War, but let me know if you need me to program it for you, and I can do that.

I've been looking through the Cold War stuff and am not sure where it is - I do see the air superiority file you made for me (which was a pretty cool thing indeed) but it doesn't seem to include this functionality. Can you help out please? Honestly, this is one of those things that probably everyone wants, so I'd recommend including it in the main template upload as well (it's annoying to have fighters attack strong ground units instead of their counterparts). I'd say this is one of those things that would be used far more often than not.
 
@JPetroski

Here's the code to add to combatSettings.lua. The existing code to remove in the 1937 scenario's version of the file is commented out at the bottom of this code.

Code:
-- use this function (defenderValueModifier) to add to (or subtract from) the calculated
-- defender value in order to change onChooseDefender
-- e.g. if you add 1e8 (100 million) to all air in air protected stacks
-- when attacked by a fighter, air units will always defend first if they
-- are available.
-- If the combat calculator gives an attacker an attack value of 0,
-- this is converted to a defenderValue of 1e7 (10 million)
-- If you want this to defend (and cancel the attack) instead of the air unit
-- (presuming it isn't an air unit itself), you could add 1e6 (1 million) instead
-- calculated defenderValues are defenderStrength/attackerStrength*healthPercentage.
-- This should be less than 10,000 (127*8 = 1016), unless you have defense multipliers 
-- of 10 or more, and an attacker with 1 attack and facing a 1/8 penalty.
local function tileHasCarrierUnit(tile)
    for unit in tile.units do
        if gen.isCarryAir(unit.type) then
            return true
        end
    end
    return false
end
local function defenderValueModifier(defender,tile,attacker)
    if gen.isAttackAir(attacker.type) and defender.type.domain == 1 
        and defender.type.range >= 2 and
        not gen.hasAirbase(tile) and not tile.city and
        not tileHasCarrierUnit(tile) then
        return 1e8
    else
        return 0
    end
end
-- register.onChooseDefender
--Registers a function that is called every time a unit is chosen to defend a tile.
--The first parameter is the default function as implemented by the game.
--It takes `tile` and `attacker` as parameters. You can call this to produce a 
--result for cases you don't need to handle yourself. The second parameter 
--is the tile that's being considered, the third is the attacking unit, and the 
--fourth, `isCombat`, is a boolean that indicates if this invocation will be 
--followed by combat. This function is also called by the AI to determine its 
--goals, in which case `isCombat` is false.
function register.onChooseDefender(defaultFunction,tile,attacker,isCombat)
    local bestDefenderValue = -math.huge
    local bestDefender = nil
    for possibleDefender in tile.units do
        local attackerStrength, attackerFirepower, defenderStrength, defenderFirepower
            = computeCombatStatistics(attacker,possibleDefender,false)
        -- below is what appears to be the standard civ II calculation
        --local defenderValue = defenderStrength*possibleDefender.hitpoints//possibleDefender.type.hitpoints
        -- instead of defender strength, however, defenderStrength/attackerStrength is used to account
        -- for attack buffs/debuffs (which are very few in original game)
        local defenderValue = nil
        if attackerStrength == 0 then
            defenderValue = 1e7 -- 10 million
        else
            defenderValue = (defenderStrength/attackerStrength)*possibleDefender.hitpoints//possibleDefender.type.hitpoints
        end
        defenderValue = defenderValue + defenderValueModifier(possibleDefender,tile,attacker)
        if defenderValue > bestDefenderValue or 
            (defenderValue == bestDefenderValue and possibleDefender.id < bestDefender.id) then
            bestDefenderValue = defenderValue
            bestDefender = possibleDefender
        end
    end
    return bestDefender
    --return defaultFunction(tile,attacker)
end
--[[
-- register.onChooseDefender
--Registers a function that is called every time a unit is chosen to defend a tile.
--The first parameter is the default function as implemented by the game.
--It takes `tile` and `attacker` as parameters. You can call this to produce a 
--result for cases you don't need to handle yourself. The second parameter 
--is the tile that's being considered, the third is the attacking unit, and the 
--fourth, `isCombat`, is a boolean that indicates if this invocation will be 
--followed by combat. This function is also called by the AI to determine its 
--goals, in which case `isCombat` is false.
function register.onChooseDefender(defaultFunction,tile,attacker,isCombat)
    local bestDefenderValue = -math.huge
    local bestDefender = nil
    for possibleDefender in tile.units do
        local attackerStrength, attackerFirepower, defenderStrength, defenderFirepower
            = computeCombatStatistics(attacker,possibleDefender,false)
        local defenderValue = defenderStrength*possibleDefender.hitpoints//possibleDefender.type.hitpoints
        if defenderValue > bestDefenderValue or 
            (defenderValue == bestDefenderValue and possibleDefender.id < bestDefender.id) then
            bestDefenderValue = defenderValue
            bestDefender = possibleDefender
        end
    end
    return bestDefender
    --return defaultFunction(tile,attacker)
end
--]]

The template will have a simpleSettings.lua option to do this automatically.

This system of adding a big number to modify the defence order (which is different than I've done before for making air units defend first) will make it easier to use this system in other situations. For example, I think the Midway scenario had a problem with torpedoes attacking aircraft on the carrier instead of the carrier itself. This could fairly easily correct that also.
 
Template has been updated, mostly with the "enhanced rules." I've posted details of that here.
 
I have added an onEnterTile 'execution point' to the template.

Code:
-- onEnterTile(unit,previousTile)
-- executes when a unit successfully enters a tile (so not when it attacks
-- a unit or fails to enter a tile because it lacks movement points)
function events.onEnterTile(unit,previousTile)
    if _global.eventTesting then
        civ.ui.text("consolidated.onEnterTile: "..unit.type.name.." has entered tile ("..text.coordinates(unit.location)..") from tile ("..text.coordinates(previousTile)..").")
    end
end

Mostly, this leverages the unitActivation event, and the fact that it activates for every tile. Basically, it checks if the last unit activated and the tile it was on when it activated, and if that unit is now on a different tile, it infers that the unit moved.

An issue that came up is what happens if no unit subsequently activates. For the AI, I just rely on the endTribeTurn execution point. However, I did something more for human players. The TOTPP has a function "civ.scen.onDrawTile" which apparently runs every time a tile is drawn. Presumably, TNO hasn't completed work on that function, but for my purposes, it provides an execution point that is always being checked. So, if no unit is active for a couple seconds, onDrawTile notices this and performs an onEnterTile event if necessary (it also checks for unit promotions as well). I can provide more details if anyone cares.
 
Try putting a few extra newline (\n) characters in your text. Without the ^, it shouldn't display as a new line, but I think it will help the game display the text properly.
Hello @Prof. Garfield !

Your code there is great to shape dialogs a little.

I met with the same issue as @civ2units with the dialogbox, and adding a \n at the end of the string won't solve the issue :

exemple2.png


Maybe are you having in hand other magical solutions, with much thanks ? :)

edith: Just realized the same bug occurs when I'm pushing the "width" property too far (400px is okay, 500px or more get this display bug).
I guess it has to do with the dialog box limits and there's not much to do about that ?
 
Last edited:
I met with the same issue as @civ2units with the dialogbox, and adding a \n at the end of the string won't solve the issue :

Not at the end, in the middle.

The macro.txt file that explains the macro style events says the following about text:

Text:
This simply presents a pop--up text box to the player. The box includes whatever text you put between the Text and EndText lines. You can enter up to 10 lines of 255 characters per line, but keep in mind both the memory limits and the amount of text that will fit on the screen at one time. Short messages are generally best. The optional No Broadcast parameter (note the space between the words--it's required) specifies that this message should be shown only to the triggering civilization ("who").

I'm guessing that there may be some difficulty parsing longer sequences of characters without newlines, though I haven't properly tested this.
 
Not at the end, in the middle.

The macro.txt file that explains the macro style events says the following about text:

Text:
This simply presents a pop--up text box to the player. The box includes whatever text you put between the Text and EndText lines. You can enter up to 10 lines of 255 characters per line, but keep in mind both the memory limits and the amount of text that will fit on the screen at one time. Short messages are generally best. The optional No Broadcast parameter (note the space between the words--it's required) specifies that this message should be shown only to the triggering civilization ("who").

I'm guessing that there may be some difficulty parsing longer sequences of characters without newlines, though I haven't properly tested this.
Ho, sorry, I wasn't thinking about a "long line" issue,

but about the "extended font masking the "ok" button in the bottom" ?
 
Ho, sorry, I wasn't thinking about a "long line" issue,

but about the "extended font masking the "ok" button in the bottom" ?

I think they are both related in the sense that if the text box is too wide, the OK button at the bottom doesn't get drawn, just as the top border doesn't get drawn.

I'm not sure what causes that, but it is not 255 characters without a newline, based on tests that I just did. 255 without a space causes a crash, but with spaces they are fine.
 
I think they are both related in the sense that if the text box is too wide, the OK button at the bottom doesn't get drawn, just as the top border doesn't get drawn.

I'm not sure what causes that, but it is not 255 characters without a newline, based on tests that I just did. 255 without a space causes a crash, but with spaces they are fine.
Much thanks prof !
 
I think they are both related in the sense that if the text box is too wide, the OK button at the bottom doesn't get drawn, just as the top border doesn't get drawn.

I'm not sure what causes that, but it is not 255 characters without a newline, based on tests that I just did. 255 without a space causes a crash, but with spaces they are fine.
Correct -- it's caused by the total width of the box, including images (if there are any) and text. There seems to be a hard limit in the game's UI code (maybe a total width of 1024 pixels? That's just a guess, but it looks like about half the width of my screen which is running 1920x1080 resolution) beyond which the border and buttons of the box get messed up. @Dadais your example has a good-sized image, so that's going to require your text lines to be relatively short. You could get longer text lines to fit by scaling down the image, of course.
 
I guess in the end I'll go with a limited selected Width and let the dialog object adapt by itself the return to lines.
I'll try to adapt texts so they are not too heavy to digest by the user then.

That shall look like that then as an exemple, and I'm rather ok with that :

exemple2.png


If it has shortcomings, it means it does exists, and I'm already really happy with that and much thankfull to TheNamelessOne !
 
Hi @Prof. Garfield, I'm attempting to help another user integrate some code that I wrote into the LST, and I have a question for you. (Sorry that my limited experience with and knowledge of the LST is causing me to bother you!)

If I want to "require" another custom Lua file, I know that I can place the command to do so at the top of one of the "on___.lua" files within the EventsFiles folder, and that works fine to make my separate module (consisting of multiple local variables and functions) available in that file. But if I want my code to be available to several such files, and I place a separate "require" statement at the top of each corresponding file, this is basically re-reading my file each time, and -- here's my issue -- creating a separate instance of it in memory. This means the contents of the local variables I'm defining there have limited scope -- they aren't being broadly shared.

What I want to do is set it up so there is a single instance of my code module which is loaded once, "upstream" from the point at which all of the files in EventsFiles are loaded, so that all of them inherit shared access to that single instance. This way, my local variables could be set within one trigger file, and then a separate trigger file could see the values stored there and take appropriate action. (Side note: I realize that the state table would provide a way to share data, but what I'm generating could include a rather large table and I don't really need to worry about preserving this data across save/load, so it seems like unnecessary overhead rather than the simplest solution to my problem.)

I'm pretty sure that if I added the "require" statement to the end of the events.lua file in the main scenario folder, this would work. But I'm not sure if that's "good form" according to your structure. Is there a better place where I should set up this type of shared access to a module that should be be required only once, and then be accessible from all other designer-customizable files in the LST?
 
Last edited:
Back
Top Bottom