1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

[TOTPP] Lua Scenario Template

Discussion in 'Civ2 - Scenario League' started by Prof. Garfield, Jul 5, 2020.

  1. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    I cut and pasted your code in and got a text box (didn't check that all tribes had money spent).

    Do you happen to have multiple lines like this?
    Code:
    function unitKilledEvents.unitKilledInCombat(loser,winner) do
    
    That is, do you define multiple unitKilledEvents.unitKilledInCombat functions? There should only be 1, and all your unit killed events should be in it. (You also don't need the do/end for the function, but it doesn't hurt anything.) If this isn't the problem, please post your events file, and I'll have a look at it.
     
  2. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    Hi there, @Prof. Garfield .

    I did use multiple object.money lines - Which probably didn't help - I attach the event file for you to look at,
    but no big emergency. This is just an excercise and not super essential to the scen.
     

    Attached Files:

  3. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    You have 2 unitKilledEvents.unitKilledInCombat functions:
    Code:
    function unitKilledEvents.unitKilledInCombat(loser,winner) do
    loser.owner.money = math.max(loser.owner.money -campaignCost,0)
    winner.owner.money = math.max(winner.owner.money-campaignCost,0)
        if loser.type == object.uLabourers then
        local newLabourers = civ.createUnit(object.uLabourers,winner.owner,winner.location)
        end
        if loser.type == object.uEngineers then
        local newEngineers = civ.createUnit(object.uEngineers,winner.owner,winner.location)
        end
    end
    
    Code:
    function unitKilledEvents.unitKilledInCombat(loser,winner) do
    loser.owner.money = math.max(loser.owner.money -campaignCost,0)
    winner.owner.money = math.max(winner.owner.money-campaignCost,0)
        if loser.type == object.uMfgGoods then
        object.tRussians.money = object.tRussians.money - param.MfgGoodsKillPenalty
        object.tJapanese.money = object.tJapanese.money - param.MfgGoodsKillPenalty
        object.tGermans.money = object.tGermans.money - param.MfgGoodsKillPenalty
        object.tIndependent.money = object.tIndependent.money - param.MfgGoodsKillPenalty
        object.tAmericans.money = object.tAmericans.money - param.MfgGoodsKillPenalty
        object.tBritish.money = object.tBritish.money - param.MfgGoodsKillPenalty
        object.tFrench.money = object.tFrench.money - param.MfgGoodsKillPenalty
            end
    end   
    end
    
    You should only have one, which would look something like this:

    Code:
    function unitKilledEvents.unitKilledInCombat(loser,winner) 
        loser.owner.money = math.max(loser.owner.money -campaignCost,0)
        winner.owner.money = math.max(winner.owner.money-campaignCost,0)
        if loser.type == object.uLabourers then
            local newLabourers = civ.createUnit(object.uLabourers,winner.owner,winner.location)
        end
        if loser.type == object.uEngineers then
            local newEngineers = civ.createUnit(object.uEngineers,winner.owner,winner.location)
        end
        if loser.type == object.uMfgGoods then
            civ.ui.text("Mfg Goods unit killed")
            object.tRussians.money = object.tRussians.money - param.MfgGoodsKillPenalty
            object.tJapanese.money = object.tJapanese.money - param.MfgGoodsKillPenalty
            object.tGermans.money = object.tGermans.money - param.MfgGoodsKillPenalty
            object.tIndependent.money = object.tIndependent.money - param.MfgGoodsKillPenalty
            object.tAmericans.money = object.tAmericans.money - param.MfgGoodsKillPenalty
            object.tBritish.money = object.tBritish.money - param.MfgGoodsKillPenalty
            object.tFrench.money = object.tFrench.money - param.MfgGoodsKillPenalty
        end
    end
    
    I added an Mfg Goods killed textbox. I also removed the 'do' (and corresponding 'end') at the beginning of the function, since it is not necessary.
     
  4. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    @Prof. Garfield
    Thanks for looking at this for me - I will study this, and be more careful with my code.
    It always seems simple once a pro explains! :)
     
  5. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    Got this to work, messing with a few tweaks...
    Code:
    local campaignCost = 10
    local MfgGoodsPlunder = -150
    
    -- This will only run when a unit is killed in combat (i.e. not when an event
    -- 'kills' a unit)
    -- note that if the aggressor loses, aggressor.location will not work
    --
    
    function unitKilledEvents.unitKilledInCombat(loser,winner)
        loser.owner.money = math.max(loser.owner.money - campaignCost,0)
        winner.owner.money = math.max(winner.owner.money - campaignCost,0)
        if loser.type == object.uLabourers then
            local newLabourers = civ.createUnit(object.uLabourers,winner.owner,winner.location)
        end
        if loser.type == object.uEngineers then
            local newEngineers = civ.createUnit(object.uEngineers,winner.owner,winner.location)
        end
        if loser.type == object.uMfgGoods then
            civ.ui.text("Goods train destroyed! Attackers plunder 150 gold!")
            object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
            object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
            object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
            object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
            object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
            object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
            object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
        end
    end
    Progress for me...Onwards and sideways!

    @Prof. Garfield , just one more request on this...
    What is the best way to make the text appear only to the human player?
     
  6. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    Probably to use civ.getPlayerTribe()

    getPlayerTribe
    civ.getPlayerTribe() -> tribe

    Returns the player's tribe.

    You can use this to check if the intended recipient of the message is the (current) human player. So, maybe you only want to show a combat message if the winner or loser is the human player

    Code:
    if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
        civ.ui.text("Your unit participated in combat.")
    end
    
     
    CurtSibling likes this.
  7. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    Thanks, sir...
    Would the getplayertribe lines be added at the bottom, as a new supplementary command?

    Code:
     if loser.type == object.uMfgGoods then
            object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
            object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
            object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
            object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
            object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
            object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
            object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
    end
    if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
            civ.ui.text("Goods train destroyed! Your men plunder 150 gold.")
    end
    Does this look OK?
     
  8. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    No, you should put it inside the body of the if statement pertaining to a destroyed Mfg Goods unit.

    That is
    Code:
    if loser.type == object.uMfgGoods then
            if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
                    civ.ui.text("Goods train destroyed! Your men plunder 150 gold.")
            end
            loser.money = math.max(loser.money-MfgGoodsPlunder,0)
           winner.money = winner.money+MfgGoodsPlunder
           --object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
            --object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
            --object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
            --object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
           -- object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
            --object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
            --object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
    end
    
     
    CurtSibling likes this.
  9. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    Thanks, ProfGarfield - During testing, Lua is running an error after the unit attacks a Mfg train.

    ...LuaTriggerEvents\UniversalTriggerEvents\onUnitKilled.lua:26: attempt to perform arithmetic on a nil value (field 'money')
    stack traceback:
    ...LuaTriggerEvents\UniversalTriggerEvents\onUnitKilled.lua:26: in function 'UniversalTriggerEvents\onUnitKilled.unitKilledInCombat'
    ...mperialism III Update\LuaTriggerEvents\triggerEvents.lua:113: in function 'triggerEvents.unitKilledInCombat'
    N:\CIV2 TOT\1_Imperialism III Update\events.lua:201: in upvalue 'doOnUnitDefeatedInCombat'
    N:\CIV2 TOT\1_Imperialism III Update\events.lua:269: in function <N:\CIV2 TOT\1_Imperialism III Update\events.lua:252>
     
  10. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    My bad, the plunder code I provided was wrong:

    loser.owner.money = math.max(loser.money-MfgGoodsPlunder,0)
    winner.owner.money = winner.money+MfgGoodsPlunder

    Units don't have a 'money' field (so, they return nil when calling .money), but the tribe objects do have a money field.
     
  11. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    Still getting the same error...I'm doing something wrong here, but cannot see it...

    Code:
    if loser.type == object.uMfgGoods then
            if winner.owner == civ.getPlayerTribe() or loser.owner == civ.getPlayerTribe() then
            civ.ui.text("Goods train destroyed! Your men plunder 150 gold.")
            end
        loser.owner.money = math.max(loser.money-MfgGoodsPlunder,0)
        winner.owner.money = winner.money+MfgGoodsPlunder
            --object.tRussians.money = object.tRussians.money - MfgGoodsPlunder
            --object.tJapanese.money = object.tJapanese.money - MfgGoodsPlunder
            --object.tGermans.money = object.tGermans.money - MfgGoodsPlunder
            --object.tIndependent.money = object.tIndependent.money - MfgGoodsPlunder
            -- object.tAmericans.money = object.tAmericans.money - MfgGoodsPlunder
            --object.tBritish.money = object.tBritish.money - MfgGoodsPlunder
            --object.tFrench.money = object.tFrench.money - MfgGoodsPlunder
        end
     
  12. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    same lines, I just forgot that I needed to add owner on both sides of the assignment...
    loser.owner.money = math.max(loser.owner.money-MfgGoodsPlunder,0)
    winner.owner.money = winner.owner.money+MfgGoodsPlunder

    I think it will work now. Such are the perils of providing untested code...
     
  13. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    No probs - I will test it ASAP - I appreciate all your help to we CIV2 apprentices!
     
  14. CurtSibling

    CurtSibling ENEMY ACE™ SLeague Staff Supporter

    Joined:
    Aug 31, 2001
    Messages:
    28,879
    Gender:
    Male
    Location:
    Innsmouth
    Tested and works fine - Thanks, Prof! Now I can add a table of plunder units that can give the player a gold boost.
     
  15. civ2units

    civ2units King

    Joined:
    Feb 1, 2009
    Messages:
    810
    Location:
    Somewhere in nowhere
    Is it possible to change certain terrain tiles if an event triggers?
     
  16. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    Yes. The "old" method is to set
    Code:
    tile.terrainType= number between 0 and 15
    
    This sometimes gets rid of the river.

    TOTPP v0.16 has new functionality

    baseTerrain (get/set) (since 0.16)
    tile.baseTerrain -> baseterrain

    Returns the baseterrain object associated with the tile.

    terrain (get/set) (since 0.16)
    tile.terrain -> terrain

    Returns the terrain object associated with the tile.

    river (get/set)
    tile.river -> boolean

    Returns `true` if the tile has a river, `false` otherwise.

    getBaseTerrain (since 0.16)
    civ.getBaseTerrain(map, terrainType) -> baseterrain

    Returns the base terrain object for the given map and terrain type.

    getTerrain (since 0.16)
    civ.getTerrain(map, terrainType, resource) -> terrain

    Returns the terrain object for the given map, terrain type and resource.

    I haven't played around with these enough to know exactly how to use them, and whether you should change a tile's terrain or baseTerrain (or, if both are OK). I think the getTerrain and getBaseTerrain take integers as acceptable arguments. My advice would be to open a console and play around a bit with these commands.
     
  17. Knighttime

    Knighttime Warlord

    Joined:
    Sep 20, 2002
    Messages:
    214
    I can see why that feels like a possibility, but in Medieval Millennium (where I change terrain types a lot) I've never experienced this -- rivers were always preserved.

    I've tested these out a little bit from the console, as you recommended. Here's my understanding about what's possible and how the various pieces fit together:

    The big issue related to these is whether or not custom terrain resources have been enabled for the given map. This requires enabling the "Custom resources" patch in the TOTPP launcher, and then also turning it on (per map) using either [Ctrl]-[F8] or by setting map.customResources = true using Lua. Once it is turned on, resource "specials" can be added to or removed from any tile, ignoring the pattern that is ordinarily determined by the map resource seed.

    The base terrain of a tile is what we could change previously by setting tile.terrainType. The value you assign to tile.baseTerrain is a baseterrain object (not just an integer) which you can find by using civ.getBaseTerrain(). This object type has no information about special resources that may or may not exist on that tile, but contains all the details about irrigation, mining, etc. as specified in Rules.txt. Changing the base terrain of a tile with a special resource will result in a tile that has the corresponding special resource of the new terrain type.

    On the other hand, the terrain of a tile contains information about whether or not the tile has a special resource, and what it's production values are. Even if you don't have custom resources enabled, this could be very useful to check production values. If you do have custom resources enabled, then you can assign a terrain object to tile.terrain which specifies not only the underlying terrain type (by inheritance, you could say), but also whether or not it contains a special resource, and which one. You find the appropriate terrain object using civ.getTerrain().

    If you don't have custom resources enabled and try to update tile.terrain, this will work as long as the terrain object you pick has the matching resource value (none, first resource, or second resource) as the tile does currently. If not, you'll get an error message. So for the sake of simplicity, my personal recommendation would be to change tile.baseTerrain when possible, and only resort to changing tile.terrain when you deliberately want to change resources (and have enabled that capability).
     
  18. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,354
    Location:
    Ontario
    I thought there was some sort of problem with this for OTR, but since the terrain change events were written before I got involved, I never looked that closely. @JPetroski would know for sure.

    Thanks for your explanation of the terrain events.
     
  19. civ2units

    civ2units King

    Joined:
    Feb 1, 2009
    Messages:
    810
    Location:
    Somewhere in nowhere
    Many thanks for the explanation, @Prof. Garfield and @Knighttime :thumbsup:

    ok, as an absolute newbie I try to understand what I have to do :D.
    First I think I have to write in my object.lua file the terrain types I'm currently using. I would use the civ.getTerrain() command. As an example I would use this command for the desert terrain tile:
    Code:
    object.rDesert = civ.getTerrain(0)
    (I used the prefix 'r' for terrain because 't' is already forgiven.)

    But after that I unfortunately don't know what to do. One small example of a script code is often enough for me to understand. But please only if someone of you have the time for it. If it is too much time consuming for you than it's not neccessary.
    What I would like to realize is a change of one or two terrain tiles if a certain city or technology is researched.
     
  20. Knighttime

    Knighttime Warlord

    Joined:
    Sep 20, 2002
    Messages:
    214
    Just so it's clear, adding object definitions is never required. But using named objects like this is a good idea and generally a best practice, because it makes code more readable.

    My proposal would be to use the prefix 'b' for baseterrain objects and the prefix 'r' for terrain objects.

    If you want to find the object for "Desert", though, that feels to me like baseterrain 0, not terrain 0. So instead of what you wrote, I would add this line to object.lua:
    object.bDesert = civ.getBaseTerrain(0, 0)

    Note that civ.getBaseTerrain() takes two parameters, not one: the first is the map number, and the second is the terrain type or key. (And civ.getTerrain() takes three parameters: the first is the map number, the second is the terrain type or key, and the third is the resource number: 0 (no resource), 1 (first resource), or 2 (second resource).)

    Setting aside the condition for the moment, here's how to handle the effect.

    First, let's find a tile you want to change, and assign it to a variable:
    local tileToChange = civ.getTile(10, 15, 0)
    The parameters in the call to civ.getTile() are the x, y, and z (map) coordinates of the tile.

    Then, change the base terrain of that tile:
    tileToChange.baseTerrain = object.bDesert

    That's all there is to it!

    In order to set up the condition, there would probably be more references to entries in the object table. But if the right objects were defined, something like this ought to work:
    Code:
    if object.cAthens.owner == object.tRomans then
        local tileToChange = civ.getTile(10, 15, 0)
        tileToChange.baseTerrain = object.bDesert
    end
    But realize this will change the terrain at that tile on every turn (well, every time this piece of code is run) while the city of Athens is owned by the Romans -- over and over again. If the tile is already Desert, you wouldn't notice a change of course, but the event is still running and setting it. So this may not be a reasonable condition for this effect, or you might want to use civlua.justOnce(), etc.

    Hope this is helpful!
     

Share This Page