1. Firaxis celebrates the "Asian American and Pacific Islander Heritage Month", and offers a give-away of a Civ6 anthology copy (5 in total)! For all the details, please check the thread here. .
    Dismiss Notice
  2. We have selected the winners of the Old World random draw and competition. For the winning entries, please check this thread.
    Dismiss Notice

Lua Scripting Possibilities

Discussion in 'Civ2 - General Discussions' started by JPetroski, Jan 21, 2018.

  1. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    Can someone please give me a hand here? I've been tinkering with @McMonkey's "Philip of Macedon" events today and making really good progress, honestly. I've been able to accomplish quite a bit on my own and tweak some events to get them firing the way I want them to. We have one event that is giving me some trouble. Basically, after a certain city is conquered by Macedon, a state is set. The next turn, a dialogue option comes up with a choice of honoring an alliance and turning cities over to your ally or dissolving the alliance and keeping the cities for yourself. I'm trying to get the event to delete Macedonian units within the city if they are turned over to the Ally, but I can't get it to work.


    I have all the potential unit types that could be in the city here:

    Code:
    local MacedonianUnitTypesToBeDestroyed = {14, 15, 23, 25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 119, 110, 112, 114, 9, 93, 94, 95, 96, 97, 98, 90, 91, 92, 34, 22, 21, 106, 107, 99, 101, 102, 103, 100, 105, 104, 108, 109, 86, 88, 51, 14, 63, 64, 17, 26, 65, 24, 19, 20, 16, 50, 52, 121, 122, 123, 0, 1, 2, 3, 4, 8, 13, 40, 46, 47, 48, 49, 120, 124, 125, 126}
    

    I have this (copied and adjusted from Knighttime's code in Napoleon) and have confirmed the coordinates are correct.

    Code:
    local function cityTileWithinChalcidia (tile)
        if tile.z ~= 0 or tile.terrainType & 0x0a == 0x0a then
            return false
        else
            if (tile.x ==  73 and tile.y == 57) or
               (tile.x ==  71 and tile.y == 49) then
                          return true
            else
                return false
            end
        end
    end
    
    I am trying to get this to fire and delete the unit. I can get the dialogue box to show up. I can also get the cities to transfer. But I can't get the Macedonian units to delete. No error messages are being thrown. There is also nothing being printed regarding the deletion of units as the event is not firing.

    Code:
    if state.cityTaken_PotidaeaDecision==1 then
            local ChalcidianCityToReturn = {"Potidaea", "Anthemus"}
                --g -A - Honour agreement with Chalcideans.= Potidaea & Anthemus transferred to Chalcidians
                dialog = civ.ui.createDialog()
                dialog.title = "Alliance!"
                dialog.width = 300
                dialog:addText("King Philipp honors the treaty with the Chalcideans.  Potidaea and Anthemus are returned to Chalcidian control!")
                --dialog:addImage("PicEvents_Option1.bmp")
                dialog:show()
              
                for _, cityName in pairs(ChalcidianCityToReturn) do
                local city = findCityByName(cityName)
                if city.owner ~= Chalcidia then
                    city.owner = Chalcidia
                    print("Gave ownership of " .. cityName .. " to Chalcidia")
                end
            end
                for unit in civ.iterateUnits() do
                if unit.owner == Macedon and unit.type.domain == 0 and (cityTileWithinChalcidia(unit.location))then
                    for _, typeToBeDestroyed in pairs(MacedonianUnitTypesToBeDestroyed) do
                        if unit.type == typeToBeDestroyed then
                            print("Destroyed " .. unit.type.name .. " at " .. unit.x .. "," .. unit.y .. "," .. unit.z)
                            civ.deleteUnit(unit)
                        end
                    end
                end
            end
    
    I appreciate the help!

    Edit - this event is under the civ.scen.onTurn(function (turn) and there is another for unit in civ sequence above the one I can't get to work... I'm curious if this is what is causing the error?

    for unit in civ.iterateUnits() do
    for _, typeToBeDeleted in pairs(unitTypesToBeDeletedEachTurn) do
    if unit.type.id==typeToBeDeleted then
    civ.deleteUnit(unit)
    end
    end
    end
     
    Last edited: Dec 15, 2018
  2. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    I think my problem is I'm missing the .id but will have to go and confirm.... Probably the result of mixing an integer-based system and a text-based system.
     
  3. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    Yep, that was the issue.

    It's.... almost... almost like I'm LEARNING SOMETHING :)
     
  4. Knighttime

    Knighttime Prince

    Joined:
    Sep 20, 2002
    Messages:
    336
    Glad you were able to figure it out on your own! "Hey, I'm learning something!" is always a nice feeling! :thumbsup: Where exactly did the .id need to go?

    In case you're interested in simplifying this further, I think you could combine your two loops into one. To delete the units, your code loops through all units in the entire game, and tests the location of each one using cityTileWithinChalcidia. But there's a faster way to get a set of units, if you know the location(s): tile.units returns an iterator of only the units on that tile -- and city.location returns a tile. So you ought to be able to do something like this:
    Code:
    for _, cityName in pairs(ChalcidianCityToReturn) do
       local city = findCityByName(cityName)
       if city.owner ~= Chalcidia then
           for unit in city.location.units do
               print("Destroyed " .. unit.type.name .. " at " .. unit.x .. "," .. unit.y .. "," .. unit.z)
               civ.deleteUnit(unit)
           end
           city.owner = Chalcidia
           print("Gave ownership of " .. cityName .. " to Chalcidia")
       end
    end
    That doesn't use your MacedonianUnitTypesToBeDestroyed -- it could be enhanced to do that, if necessary, but wouldn't you just want to delete all units in each city, regardless of type? And it completely eliminates the need for the separate for unit in civ.iterateUnits() do block, as well as the cityTileWithinChalcidia function to do a location test.

    I didn't test this code, so it may not be perfect, but you're welcome to try it out and see if you can get it working.
     
  5. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    "if unit.type == typeToBeDestroyed" needed to be if unit.type.id because I'm using an integer-based system (for the units at least) rather than the name-based system you used in Napoleon. I'm actually using your system for the cities because we encountered a strange bug where the game "lost" the integer of one of our cities (it would no longer print in the console) but the backup name version worked. So I have this crazy hybrid of three lua designers that I'm mixing and matching together. Case in point, I want to see if I can take your code for the city transfer from Napoleon and inject Garfield's code for the unit transportation (which we prohibit air protected stacks) to see if I can just get these units to move outside of the city rather than being deleted. I will check out this code you put in place before I do that though.

    I have to say, it's pretty darn cool seeing how functional all of this is and I am finding that I'm able to work through a lot of stuff on my own. I'm using this scenario as a test to see the viability of someone very new to coding using lua for the most part independently (I'm sure I'll need help here and there but I shouldn't be in here every hour).
     
  6. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,818
    Location:
    Ontario
    Did this happen after a city was deleted or destroyed? (Perhaps after a save and load after that event?) Are other cities references no longer "matched" with the appropriate city? If that's the case, it would sound like the saved game got rid of unneeded data, and shifted everything else appropriately. Hopefully this is the case, since then we don't have to worry about it in OTR, because no cities are destroyed in that scenario.

    Whatever the cause of the bug, it is something that can be avoided by using the city location as the reference point (assuming the city is never relocated).

    I think this would work to get the city at (10,20,0), regardless of how the city list may have changed. (untested code)
    Code:
    myCity = function() return civ.getTile(10,20,0).city end
    
    if city == myCity() then
    -- code
    end
    
    We might even be able to make it more seamless in this way (again untested code)
    Code:
    local function getCityAt(x,y,z)
    local function getCity()
    return civ.getTile(x,y,z).city
    end
    return getCity
    end
    
    local myCity = getCityAt(10,20,0)
    
    if city == myCity() then
    -- code
    end
    
     
  7. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    To be honest, we took such a long break from the development that neither of us have any idea what happened. It could be any of the options that you said. The city is present in some save but not the latest and given how much we've done to the save I'm happy to use this workaround.

    Edit- all of the other cities appear to be matched with the correct integer. Printing the city list simply leaves a gap/jump in integers from 182 to 184, for example. 183 is missing.
     
  8. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    @Knighttime , a question if you would for my own education. I'm modifying your treaties from Napoleon for Macedon and am curious why these are all reading "false"? Why don't they say "true"?

    I'm going to go ahead and just copy it as is but I wasn't sure why this says what it does as it is counter-intuitive for me. Thanks!

    Code:
    elseif unit.owner == Austria then
            enforcePeace(Austria, Russia, false)
            enforcePeace(Austria, Prussia, false)
            enforcePeace(Austria, Spain, false)
            enforcePeace(Austria, Ottoman, false)
            enforcePeace(Austria, England, false)
    
     
  9. Knighttime

    Knighttime Prince

    Joined:
    Sep 20, 2002
    Messages:
    336
    Hi @JPetroski -- this was actually a rather troublesome part of the Napoleon scenario, and we went through several rounds of revisions to make this area of the event code more and more strict. Originally, enforcePeace() was written to be called only when we intended to change the status between two nations that we knew had previously been at war. (At that point there was no third parameter of true or false.) Later, in testing, Tootall and I came to realize that despite being blocked from negotiating, the AI was still sometimes declaring war when it shouldn't, or signing a peace treaty when it shouldn't. That's when I added the third parameter to this function, which lets us indicate whether we know we are deliberately making a change to the current diplomatic status (using true) or whether we simply mean to reinforce/repair the same status that should already exist (using false). This allowed us to call it in a lot more situations, while still distinguishing our intent.

    So depending on where you are within the events file, and which two nations you have in mind, you'll see both values being used for the third parameter. It looks like the piece of code you copied is from civ.scen.onActivateUnit(). At that point, false is correct, since we're seeking to repair or confirm existing peace statuses, not deliberately sign a new peace treaty between warring tribes. (If we do so, it's only because the AI broke the peace treaty first, immediately prior to this, when it wasn't supposed to!)

    As you can see within the enforcePeace() function, the true or false are the parameter labeled knownChange, and this only affects messaging -- not the actual changes we make to tribe relationships. But it allows us to detect the case where the AI declared war when we didn't want it to, and then alert the player that the events are reverting the diplomacy changes that were probably just announced to them by the default game engine. On the other hand, if we know we are intentionally making a change (signing a new peace treaty), then we don't need to show those messages, which would just be confusing.

    If you were expecting that enforcePeace...false would set the peace status to false... nope! :) There's a separate function of enforceWar() to do that, which also takes true or false as a third parameter indicating whether we are changing vs. confirming/repairing a war status.
     
    Last edited: Dec 22, 2018
    JPetroski likes this.
  10. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    Thanks for the explanation! We have a pretty simple setup in Macedon where we simply want the AI to always be at peace with each other but occasionally fight Macedon, so we'll be using "false" all of the time for AI vs. AI interaction and apparently "true" quite a bit for the times they need to change status with Macedon.

    I have to say I'm having a fun time building the events file. I'm learning a lot just by trying to copy and paste what you and Prof. Garfield have done. For example, I was able to implement your leader bonus, but I had to go back in three or four times because I kept missing that you had added basic functions to the start of the code. But, by getting these error messages (and importantly, now understanding at least how to decode most error messages and understand what they are trying to tell me), I was able to figure it out. Earlier this summer I would have been posting for help 3-4 times.

    I think that the work you, Garfield, and Grishnach have done will really pay off. I think Tootall and I have an advantage of course because we were so connected to the development and understand what each of you were trying to do, but after about a year of poking around with lua, I'm fairly confident I could create a scenario 100% on my own by cannibalizing bits of code from the three of you. It will horrify a purist and will be "ugly" but it should work.

    I would encourage other designers to get engrossed in Naploeon and Over the Reich, learn what you and Garfield have done in them, and apply this to their own work.
     
    Knighttime likes this.
  11. Knighttime

    Knighttime Prince

    Joined:
    Sep 20, 2002
    Messages:
    336
    Anyone interested in Lua Scripting Possibilities is invited to hop on over to the Scenario Creation forum and check out a new Lua module I just posted there, providing support for abstract supply lines within a scenario. Hopefully this is something the community finds useful and that some designer is able to incorporate into a scenario. Thanks!
     
  12. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    4,506
    @Prof. Garfield - I had a spark of thought regarding some of the work you did on clouds in OTR. You needed me to send you a blank map without any clouds to take a "snapshot" of what it looked like prior to my adding clouds, so that we could restore the terrain and all improvements to the terrain each turn after the clouds passed. I am guessing that the same process could be utilized to take a snapshot of the state of roads and railroads to prevent players from destroying these? I'm thinking this would be a way to eliminate a house rule of not destroying roads (or creating new ones) especially if the event checks for changes to the map each turn. This might be a good way for a pretty scenario-specific tool to see broader use.
     
  13. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,818
    Location:
    Ontario
    Yes, I believe that would work to ensure that terrain improvements are replaced if they are destroyed.
     
  14. PlutonianEmpire

    PlutonianEmpire Socially Awkward Goofball

    Joined:
    Mar 11, 2004
    Messages:
    4,797
    Gender:
    Male
    Location:
    MinneSNOWta
    Yo, any updates? :)
     
  15. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,818
    Location:
    Ontario
    Sorry. Turns out this was easier than I thought and I should have done it a while ago, without waiting for OTR to be complete.

    To use, extract this script somewhere convenient (probably your ToT directory). Start a game with the map you wish to copy, then open the lua console. Use the load script button and run mapcopy.lua.

    This will write a file to your Test of Time directory (the number at the end is to minimize the chance of conflict with another file, by using the Operating System time. If you run mapcopy.lua twice in the same second, there could be trouble).

    Open the map 'destination' in a game, open the lua console, and run the new script (mapCopyScriptXXXXXXX.lua). This should copy the map.

    I tested copying from map 0 to map 0, but it should work for all maps. This was only briefly tested, so let me know if there are any problems, and save backups of the games you are changing, just in case.
     

    Attached Files:

    PlutonianEmpire likes this.
  16. PlutonianEmpire

    PlutonianEmpire Socially Awkward Goofball

    Joined:
    Mar 11, 2004
    Messages:
    4,797
    Gender:
    Male
    Location:
    MinneSNOWta
    Would that also help with that fertility problem I linked?
     
  17. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,818
    Location:
    Ontario
    Oh. Right. That will be an even larger problem, since I'm pretty sure the map will keep the original fertility that was calculated when it was generated, not fertility associated with the new map.

    I don't know how fertility is 'supposed' to be calculated, so you have three options.

    First is for me to take the fertility value generated for the new map, and copy it along with the tiles to the old saved game.

    Second is for you to point me to the 'proper' fertility calculation, so I can provide you with a script to perform the calculation for each square.

    Third is for you to specify how you would like the fertility calculation to be made for your scenario, and I'll write a script to perform the calculation for each square. I believe this has to be between 0 and 15, based on the saved game structure.

    I don't anticipate any of these taking very long (~20 minutes, I would think), unless you want option 2 or 3 and need the placement of specials to be taken into account. There isn't a 'special here' mark on the tiles, so we have to work out how to determine if a special is present. This is necessary work, which I or someone else must do eventually, but finishing up the special target events for OTR is more pressing.
     
  18. PlutonianEmpire

    PlutonianEmpire Socially Awkward Goofball

    Joined:
    Mar 11, 2004
    Messages:
    4,797
    Gender:
    Male
    Location:
    MinneSNOWta
    The Fertility Algorithm I use is the default for the Original Game.

    As for the fertility tiles, I have two sets for two types of maps, which I pretty much use universally throughout ToT:

    Code:
    @FERTILITY2
    Ice Plains (Plains)
    Dirty Ice (Grassland)
    Water Ice Rocks (Tundra)
    Terraformed Pluto (Swamp)
    
    @FERTILITY3
    Plains
    Grassland
    Tundra
    Plutonic Snow (Bbb)
     
  19. PlutonianEmpire

    PlutonianEmpire Socially Awkward Goofball

    Joined:
    Mar 11, 2004
    Messages:
    4,797
    Gender:
    Male
    Location:
    MinneSNOWta
    Also, heres an entirely separate idea: How about a diplomatic condemnation/praise system, maybe even sanctions (such as economic)?

    Imagine a "full" game, that has a bit of storytelling to it. All 7 tribes are playing. Maybe an Industrial or Modern scenario, either real world scenario or fictional mod.

    Imagine Tribes 1 and 2 are notorious for human rights abuses, akin to China or Saudi Arabia (or alternatively, with a nasty reputation). They are also at war with each other. Tribe 1 is also at war with every other tribe, on a Mongolian Rampage. Tribe 4 is very weak, with Tribes 2, 5, 6, and 7 (You) are of moderate to strong armies, but Tribe 2 is still feeling threatened by Tribe 1.

    Now here's where the above idea might come into play. The other tribes approach you, the Player, asking for an alliance against Tribe 1. Now you have a choice. Answering Tribe 2's call for allied help will result in the other Tribes condemning you because Tribe 2 is also a bad guy in their own right, whereas allying with the other tribes might gather more positive reactions from the rest of them.

    Might such a thing be possible?
     
  20. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    3,818
    Location:
    Ontario
    I don't know what the formula is, so I've taken fertility as generated by the game. If you can point me to how the actual calculation is done, that would be appreciated. Since I've taken the fertility rating from the map being copied, you will have to create a new version of your scenario and place the maps as you would if you were remaking the entire thing from scratch. I think that will make the proper fertility calculation. (I don't know for sure, however, since I've never done anything like that before.)

    The 'tribe' object lets us check what the current treaty status is between civs, and also set reputation and attitude. It should, therefore, be possible to tie events (including a simple change in reputation/attitude) to whether you are allied with a 'bad actor.'
     

    Attached Files:

Share This Page