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

Lua Scripting Possibilities

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

  1. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,826
    Location:
    Ontario
    This seems like a perfectly reasonable restriction. Occasionally, when I've written quick and dirty scripts that have had to write files, I've written directly to the TOT directory, since it is easier. However, that stuff hasn't been meant to be used by the end users of scenarios.

    At the moment, if I'm paranoid, I can search for things in the code like 'os.execute' and 'io.write.' If the code is supposed to be writing stuff, then I have to figure out if what is being written is advertised, which could possibly be a bit more involved.

    What we might do, is write a library with all the possible things you could want to overwrite (or, that are considered 'allowed' to be overwritten). For example, overwrite.rules(newRulesName), overwrite.movpiece(newSoundName).
     
  2. Civinator

    Civinator Blue Lion Supporter

    Joined:
    May 5, 2005
    Messages:
    6,490
    Gender:
    Male
    Prof. Garfield, of course you are not paranoid, but very, very helpful and -in contrary to myself - aware about the dangers that such codes can contain. Thank you very much for your answers. :) Nevertheless it would be nice, if the problem of the very simple Civ 2 'tac...tac...tac- sound' could be upgraded to individual sounds for Civ 2 ToT units, while an 'onmovement' trigger is missing in ToTPP.
     
  3. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,856
    Is there no way to add a count, veteran, and randomize to this:

    Code:
    civ.createUnit(object.uCenturion, object.tEurope,object.lHKInvasionPoint1)
    
    I need to use civ.createUnit vs. civlua.createUnit (which allows those options) because this is a naval invasion and I want the ground force to spawn at sea. Randomize isn't critical here but it is a shame I can't do count or vet as there are several units to get through.
     
  4. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,826
    Location:
    Ontario
    You (or someone else) will have to write equivalent code to civlua.createUnit, with the difference being that placing land units on water is allowed.

    Have you had a look at the code for civlua.createUnit and tried to understand it? There are a couple helper functions, and then the loop that generates units. Or, here is some relevant code from my munition library:

    Code:
            for i=1,numToGenerate do
                local newUnit = civ.createUnit(specification.generatedUnitType,
                    generatingUnit.owner,generatingUnit.location)
                newUnit.veteran = specification.giveVeteran or false
                newUnit.veteran = newUnit.veteran or (specification.copyVeteranStatus and generatingUnit.veteran)
                newUnit.homeCity = nil
                if specification.setHomeCityFunction then
                    newUnit.homeCity = specification.setHomeCityFunction(generatingUnit)
                end
                unitTable[i] = newUnit
            end
    
     
  5. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,856
    Is there a way to set parameters around what game years an event can randomly fire? I know how to use random turn, but I want there to be a floor and ceiling based on turn numbers for events to fire. How would I do that?

    Basically say I wanted there to be a 25% chance an event would fire once on any turn between 10 and 20. How would I define the range?
     
  6. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,826
    Location:
    Ontario
    If you want there to be a 25% chance to fire on any given turn between turn 10 and 20, use this condition (and a justOnce)

    Code:
    if turn >=10 and turn <=20 and math.random() < 0.25 then
    
    If you want a 25% chance for an event to ever happen, but guarantee that if it happens, it has an equal chance of happening on any turn between turn 10 and turn 20 (both inclusive) then you want something like this

    Code:
    -- define this counter in appropriate location
    counter.define("turnToTriggerEvent",-2)
    
    -- on the turn you want to decide whether the event will happen or not, and on which turn
    
    if turn == decisionTurn then
         if math.random() < 0.25 then
            counter.setValue("turnToTriggerEvent",math.random(10,20))
        end
    end
    
    -- Also, in onTurn (or, maybe, afterProduction) you check if the turn is the same as the counter value
    
    if turn == counter.value("turnToTriggerEvent") then
        -- do event stuff
    end
    
     
    JPetroski likes this.
  7. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,856
    I'm playing around with a theory that with lua, it's possible to have "unlimited units" in a sense that if you have a scenario taking place over a certain number of years, and you know certain units become obsolete or destroyed after a portion of those, you can recycle the old unit into a new doing something like this, where we might expect Hurricanes to eventually be replaced by Spitfires and the Hurricane slot to then be used for Mustangs under the control of an entirely different civ.

    object.uHurricane =civ.getUnitType(38)
    object.uMustang =civ.getUnitType(38) -- Turn 25 onward

    object.uSpitfire =civ.getUnitType(39) -- all object.uHurricane needs to be deleted on turn 24 and and replaced with object.uSpitfire on that same turn, on that same tile, with the same home city and the same veterancy

    The theory seems sound to me, but I'm having trouble figuring out how to put it into practice. I figured I'd start by trying to take the damaged B-17 mechanism in OTR because it has all of the functionality I need (same home city, same vet status, same tile, etc.) but this is a unitKilled trigger which doesn't work for this event. Instead I need this to be a function that works on a turn. Unfortunately, my attempt below does absolutely nothing. Any idea why? (I put this in "onKeyPress" for now by the way as that is easier to check and trouble shoot than onTurn).

    Code:
    local swapUnitTypes ={
    ["HMS Hood to Battleship"] = {unitType=object.uHood, replacingUnit = object.uBattleship , preserveVetStatus = true, preserveHome=true},}
    
    
    local function unitSwap(unitType)
    for unit in civ.iterateUnits() do
    if unit.type == swapUnitTypes.unitType then 
    if civ.getTile(unit.location.x,unit.location.y,unit.location.z) ~= nil then
            local tile = unit.location
            for __, unitSwapInfo in pairs(swapUnitTypes) do
                if unit.type == unitSwapInfo.unitType then
                    local quantityToProduce = unitSwapInfo.replacingQuantity or 1
                    if math.random() <= (quantityToProduce - math.floor(quantityToProduce)) then
                        quantityToProduce = math.ceil(quantityToProduce)
                    else 
                        quantityToProduce = math.floor(quantityToProduce)
                    end
                    local replacingHome = nil
                    if unitSwapInfo.preserveHome then
                        replacingHome = unit.homeCity
                    end
                    local replacingVetStatus = unitSwapInfo.replacementVetStatus or false 
                    if unitSwapInfo.preserveVetStatus then
                        replacingVetStatus = unit.veteran
                    end
                    for i=1,quantityToProduce do
                        local newUnit = civ.createUnit(unitSwapInfo.replacingUnit,unit.owner,unit.location)
                        newUnit.homeCity = replacingHome
                        newUnit.veteran = replacingVetStatus
                        replacementUnit = newUnit
                    end --1st instance for i=1,quantityToProduce
                    if unitSwapInfo.bonusUnit then
                        quantityToProduce = unitSwapInfo.bonusUnitQuantity or 1
                        if math.random() <= (quantityToProduce - math.floor(quantityToProduce)) then
                            quantityToProduce = math.ceil(quantityToProduce)
                        else 
                            quantityToProduce = math.floor(quantityToProduce)
                        end       
                        for i=1,quantityToProduce do
                            local newUnit = civ.createUnit(unitSwapInfo.bonusUnit,unit.owner,unit.location)
                            newUnit.homeCity = nil
                            newUnit.veteran = false
                        end --2nd instance for i=1,quantityToProduce   
                    end -- end if unitSwapInfo.bonusUnit       
                end -- unit.type == unitSwapInfo.unitType
            end -- for unitSwapInfo in pairs(swapUnitTypes)
            --civ.deleteUnit(unit)
        end--civ.getTile(...
       
        end 
    end 
    end 
    
    Code:
    local function doOnKeyPress(keyCode)
    
    --I snipped out a lot
      
     if keyCode == keyboard.two then
     unitSwap(unitType)
     return
     end 
    end
    
     
  8. Knighttime

    Knighttime Warlord

    Joined:
    Sep 20, 2002
    Messages:
    159
    Well, without trying to run this, I noticed a couple things.

    First, based on he way you defined local swapUnitTypes, this reference isn't valid:
    swapUnitTypes.unitType
    swapUnitTypes is a table, but it doesn't have a key of unitType -- the (only) key that you defined is "HMS Hood to Battleship". The value of that key is also a table, and that table is the one with a key of unitType.

    It looks to me like you can simply delete this line entirely:
    if unit.type == swapUnitTypes.unitType then
    and its corresponding end statement later on. Your line that says
    for __, unitSwapInfo in pairs(swapUnitTypes) do
    will take care of looping through the swapUnitTypes table, but that first if check means you're never getting to that part of the code. The line that says
    if unit.type == unitSwapInfo.unitType then
    is correct.

    Then in onKeyPress(), you're calling the unitSwap function with unitType as a parameter, but that seems unnecessary. Did you find a unit type somewhere within the "I snipped out a lot" section to populate this variable?
    Within the function itself, though, I don't see you using it -- you're just iterating over all units in the game. So I think you could eliminate that parameter both from the function definition and the call to the function.

    Does that get you back on track to implementing this feature?

    I'm curious, though, what name you plan to assign to civ.getUnitType(38) in Rules.txt, if you want it to represent a Hurricane at one point in the game and a Mustang at another. unittype.name is not a writeable field in Lua, so while you can use the type differently, you can't change the name of it dynamically. (The only way to do that is by swapping Rules.txt files.)
     
    Last edited: Sep 12, 2020
  9. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,856
    Thank you a million times over! Yes, that got it working perfectly! I had to play around a little with where i put civ.deleteUnit(unit) to get the Hood to disappear, but I seem to have found a place to tuck it in where it works well.

    Yes, the plan is to have a batch file with rules changes that addresses the different names (as well as what tribes can build them). I think it's an acceptable trade off to have this kind of versatility, especially since the units need to swap out at approximately summer/winter sequences, anyway. There will be some housekeeping issues to deal with (such as dealing with the fact that the AI from tribe1 might still produce the unit even though only tribe2 should be able to at this point), but that should be a pretty simple fix.
     
  10. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,826
    Location:
    Ontario
    I spent some time today trying to write a script to determine where the 'fish' and 'whale' resource tiles are. After spending some time trying to reverse engineer the resource seed pattern, I realized that someone probably did that years ago, and that I should probably just look up that work. However, I couldn't find it, but several references were made to no longer existing apolyton web pages.

    My next idea was to programmatically determine what tiles have specials, and produce a table in a file that could then be referenced later. This would also work when special resources are placed by hand.

    The procedure was for the scenario maker to set desert production to 1 of each resource. The desert "fish special" would be set to 10 food, and the desert "whale special" would be set to 10 shields. By setting the city size to 0 (so only the centre tile is worked), changing terrain to desert and teleporting (or creating/deleting, I tried both) a city to each square and checking production via totalSheild and totalFood to determine if a special exists, and which one.

    However, this doesn't work. I think the reason is that the city is not "calculated" unless either a human looks at the city or the city is processed for taxes/production/etc.

    Does anyone have any ideas? The only one I'm left with is having the scenario creator manually compile the information in some way (I'm thinking use keypress to place one of two units on each of the specials, then looping over units). We've got this far without lua knowing where the specials are, so I suppose it isn't that critical, but I did think this was going to be a relatively easy thing to add...
     
  11. Knighttime

    Knighttime Warlord

    Joined:
    Sep 20, 2002
    Messages:
    159
    I think I poked at this in similar ways, awhile ago, and didn't figure out a good solution. I ran into the same issue you did:
    I may have made my own analysis more difficult because I also wanted to be able to detect specials on new, randomly-generated maps -- not just document ones on a static map used by a scenario designer. I gave up, eventually, but I would certainly be interested in a solution if you can find one.

    Within a scenario, the fact that TOTPP allows special resources to be placed manually makes this more challenging -- it basically means that the documentation related to resource seeds and patterns isn't sufficient. You might be able to calculate where specials ought to be, but I don't know how you could calculate where they actually are, if manual placement is involved. Everything below assumes we're only dealing with the game's native specials.

    Although there isn't any quick way to tell whether a tile contains a special, there is one related piece of information available. If an ocean tile would be a special but is more than two tiles away from any land tile, the special is "suppressed" and this shows up in tile.terrainType. TNO said we should always reference the terrain type as tile.terrainType & 0x0F but if you don't use bitwise operators and just look at the integer value stored in this field, an ocean tile is 10 but an ocean tile with a suppressed special is 74 (it adds 64 to the base value of 10 -- does that mean tile.terrainType & 0x40 would be appropriate to check this?). So you might be able to detect the pattern of all specials based on the locations of these suppressed specials. I don't think it tells you which resource was suppressed though (fish or whales).

    Then I found two pages with formulas that might be useful:
    1. TNO's formula for how the resource seed determines placement of specials: https://forums.civfanatics.com/thre...is-add-resources-on-map.518649/#post-13002282
    2. If the resource seed isn't known (from the map editor) then here's a potential formula for calculating it based on a hut location: http://www.codehappy.net/apolyton/threads/68481-1.htm

    BTW, many of the old Apolyton threads are preserved on that codehappy site, but they're not easy to find or navigate.

    Hope this is helpful. Good luck!
     
    Last edited: Sep 14, 2020
  12. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,826
    Location:
    Ontario
    Thanks @Knighttime , this is very helpful.

    Since TNO mentioned how he stores custom resource locations with a byte for each tile, I'm inclined to think that it would make more sense to figure out how to read a saved game file to extract the information into a table, rather than mess around further trying to figure out how to read it from inside a game. Unless someone comes up with a cool concept that would benefit from identifying special squares, I'm inclined to think that for now it is best to do other stuff and hope that TNO shows up and provides us with the functionality directly.

    I think you would use
    Code:
    tile.terrainType & 0x40 == 0x40
    to check that, but in the General Library, I have functions like
    Code:
    gen.isBit1(tile.terrainType,7)
    to do that kind of job, without worrying directly about bitwise operations. Incidentally, I've been using %16 on terrain type instead of &0x0F, though I suppose the latter might technically be faster, now that it is pointed out to me.

    I think that with enough ocean squares, we could narrow the seed down to 4 possibilities, which would tell us where all the resources are, but not their type.
     

Share This Page