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

[TOT] [TOTPP] The Munitions Library

Discussion in 'Civ2 - Scenario Creation' started by Prof. Garfield, Jul 20, 2019.

  1. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,416
    Location:
    Ontario
    The Munitions Library is a module that provides some functionality for generating munitions or recruiting units based on a key press when a "spawning" unit is active.

    Feel free to post requesting extra features or bug fixes, or even to implement extra features or bug fixes.

    An example of usage is at the top of the file.

    Features:

    --specificationTable[unitType.id]={
    --
    -- goldCost = integer or nil
    -- amount of gold it costs to generate a unit
    -- absent means 0
    -- minTreasury = integer or nil
    -- minimum amount of gold in treasury to generate a unit
    -- absent means refer to gold cost
    -- (tribe will generate and be set to 0 if treasury is less than goldCost)
    -- treasuryFailMessage = string or nil
    -- A message to be displayed if the unit fails to spawn a unit due to an
    -- insufficient treasury
    -- nil means no message

    -- There are three ways to specify move costs, in full movement points,
    -- in "atomic" movement points, and as a fraction of total movement points for the
    -- unit type. Use only one kind per unit type

    -- moveCost = integer or nil
    -- movement points to be expended generating the unit
    -- "full" movement points
    -- absent means 0
    -- minMove = integer or nil
    -- minimum remaining movement points to be allowed to generate a unit
    -- "full" movement points
    -- absent means any movement points for land/sea, 2 "atomic" for air units
    -- postGenMinMove = integer or nil
    -- a unit will be left with at least this many movement points after
    -- the generation function
    -- absent means 0 for land/sea, 1 "atomic" for air units
    -- moveCostAtomic = integer or nil
    -- movement points to be expended generating the unit
    -- refers to the unit.moveSpent movement points
    -- absent means 0
    -- minMoveAtomic = integer or nil
    -- minumum remaining movement points to be allowed to generate a unit
    -- referes to the unit.moveSpent movement points
    -- absent means any movement points for land, 2 "atomic" for air units
    -- postGenMinMoveAtomic = integer or nil
    -- a unit will be left with at least this many movement points after
    -- the generation function
    -- absent means 0 for land/sea, 1 "atomic" for air units
    -- moveCostFraction = number in [0,1] or nil
    -- fraction of unit's total movement points expended generating the unit
    -- round up to nearest "atomic" movement point
    -- absent means 0
    -- minMoveFraction = number in [0,1] or nil
    -- fraction of unit's total movement points that must remain to be allowed
    -- to generate a unit
    -- absent means any movement points for land, 2 "atomic" for air units
    -- round up to nearest "atomic" movement point
    -- postGenMinMoveFraction = number in [0,1] or nil
    -- a unit will be left with at least this many movement points after
    -- the generation function, round up to nearest "atomic" move point
    -- absent means 0 for land/sea, 1 "atomic" for air units
    -- roundFractionFull = bool or nil
    -- fractional movement cost and minimum are rounded up to full movement point
    -- instead of atomic movement point
    -- nil/false means don't
    -- roundFractionFullDown = bool or nil
    -- fractional cost and minimum are rounded down to full move point
    -- nil/false means don't
    -- minMoveFailMessage = string or nil
    -- a message to be displayed if a unit is not generated due to insufficient
    -- movement points.
    -- nil means no message

    -- allowedTerrainTypes = table of integers or nil
    -- a unit may only be generated if the tile it is standing on
    -- corresponds to one of numbers in the table
    -- nil means the unit can be generated on any terrain type
    -- terrainTypeFailMessage = string or nil
    -- message to be displayed if a unit is not generated due to standing
    -- on the incorrect terrain

    -- requiredTech = tech object or nil
    -- the generating civ must have this technology in order to generate
    -- the unit
    -- nil means no requirement
    -- techFailMessage = string or nil
    -- message to be displayed if a unit is not generated due to not having
    -- the correct technology

    -- payload = boolean or nil
    -- if true, unit must have a home city in order to generate munitions
    -- and generating munitions sets the home city to NONE
    -- payloadFailMessage = string or nil
    -- message to be displayed if a unit is not generated due to the
    -- payload restriction
    -- payloadRestrictionCheck = nil or function(unit)-->boolean
    -- If function returns false, the home city is not valid for giving the
    -- unit a payload. This will be checked when the unit is activated, when
    -- the unit is given a new home city with the 'h' key and when the unit
    -- tries to generate a munition
    -- nil means no restriction
    -- payloadRestrictionMessage = nil or string
    -- message to show if a unit fails the payloadRestrictionCheck

    -- canGenerateFunction = nil or function(unit)-->boolean
    -- This function applied to the generating unit must return true in order
    -- for a unit to be spawned. All other conditions still apply.
    -- any failure message should be part of canGenerateFunction
    -- absent means no extra conditions

    -- generateUnitFunction = nil or function(unit)-->table of unit
    -- This function creates the unit or units to be generated
    -- and returns a table containing those units
    -- Ignore any specifications prefixed with * if this is used
    -- nil means use other specifications

    --*generatedUnitType = unitType
    -- The type of unit to be generated
    -- can't be nil unless generateUnitFunction is used instead

    --*giveVeteran = bool or nil
    -- generated unit(s) given veteran status if true
    -- nil or false means don't give vet status
    -- if true, overrides copyVeteranStatus

    --*copyVeteranStatus = bool or nil
    -- generated unit(s) copy veteran status of generating unit if true
    -- nil or false means don't give vet status

    --*setHomeCityFunction = nil or function(generatingUnit)-->cityObject
    -- determines what home city the spawned unit should have
    -- nil means a home city of NONE

    --*numberToGenerate = nil or number or thresholdTable or function(generatingUnit)-->number
    -- if nil, generate 1 unit in all circumstances
    -- if integer, generate that many units (generate 0 if number less than 0)
    -- if number not integer, generate floor(number), and 1 more with
    -- probability number-floor(number)
    -- if thresholdTable, use remaining hp as the key, to get the number to create
    -- if function, get the number as the returned value of the function

    -- activate = bool or nil
    -- Activates one of the generated units if true. If generateUnitFunction was used,
    -- the unit at index 1 is activated, if index 1 has a value. (if not, any unit in
    -- the list might be chosen)
    --
    -- successMessage = string or nil
    -- message to show if a unit (or units) is created
    --
    --spawnUnit(generatingUnit,specificationTable,unitActivationCode)-->table of unitCreated
     

    Attached Files:

    JPetroski likes this.
  2. JPetroski

    JPetroski Emperor

    Joined:
    Jan 24, 2011
    Messages:
    1,964
    I've been looking at this a bit today and am (surprise, surprise) a bit confused.

    In one of your examples you have this:

    Code:
    local function doOnKeyPress(keyID)
      if keyID == 75 --[[k]] and civ.getActiveUnit() then
           munitions.doMunition(civ.getActiveUnit(), munitionSpecificationTable,doOnActivation)
           return
      end
    
    But these examples appear to be the only place that has munitionSpecificationTable. Everywhere else, it is simply "SpecificationTable." Is this supposed to be so? Or should the code above really be SpecificationTable?

    I'm also confused with how to establish the table as it appears to be different than the code in OTR... Here you have:

    Code:
    specificationTable[unitType.id]={ }
    
    There, it is:

    Code:
    local artilleryUnitTypes = { ["NameOfUnit"] = {unitType=civ.getUnitType(63),}}
    
    So I guess I'm just thrown by the brackets [ ] being in a totally different position and not totally following along.

    If the purpose of these modules is to make this more "plug and play" for people who aren't very familiar with lua, I would suggest that in addition to the sample code that you have provided, you might provide a sample table of one or two sample entries.
     
  3. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,416
    Location:
    Ontario
    You need to provide the function with a table with the specifications for firing munitions. What you call this table doesn't matter. You certainly don't have to give it the same name that was used to write the function. Below, I call it kAttack. If my primary attack specification table is called kAttack, then
    Code:
    munitions.doMunition(civ.getActiveUnit(), kAttack,doOnActivation)
    
    There are different ways to define a table. If I want to define a single entry at a time (or change an entry), and I already have a table, I can use

    Code:
    myTable = {}
    myTable["myNewKey"]=myNewValue -- this is the same as myTable.myNewKey=myNewValue
    myTable[10]=myValueForKey10
    
    If I want to define my table with the values already in it,
    Code:
    myOtherTable = {["myNewKey"]=myNewValue,[10]=myValueForKey10,}
    
    It is different than what is in OTR. In OTR, the table key is irrelevant, the table is simply searched until the a table value has the appropriate unit as one of its values. Here, the unit type id number is used to find the munition information for that unit.

    Code:
    object.aRocketry                = civ.getTech(73)
    object.uSubmarine               = civ.getUnitType(41)
    object.uCruiseMsl               = civ.getUnitType(44)
    
    kAttack[object.uSubmarine.id] = {goldCost = 500,moveCost = 3,allowedTerrainTypes={10,},
            treasuryFailMessage = "This munition requires 500 gold to fire.",
            terrainTypeFailMessage = object.uSubmarine.name.." units can only fire "..object.uCruiseMsl.name.." units at sea.",
            requiredTech = object.aRocketry,
            techFailMessage = object.uSubmarine.name.." units cannot fire fire "..object.uCruiseMsl.name.." units until we have discovered "..object.aRocketry.name..".",
            generatedUnitType = object.uCruiseMsl, activate = true,}
    
    We're not really at 'plug and play' with Lua yet. There just hasn't been enough time put into 'general use' stuff yet. My libraries generally assume that you are at least comfortable at writing basic code. While they lower the skills required (and saves duplication of effort for those with the skills), they don't eliminate the skill requirement.
     
    JPetroski likes this.
  4. JPetroski

    JPetroski Emperor

    Joined:
    Jan 24, 2011
    Messages:
    1,964
    Hi,

    I'm trying to convert Germanicus right now and am attempting to use this. It's more or less working, so thank you for your efforts! Your example table was very helpful. I did find one issue. I was originally seeing this error:

    Code:
        D:\Test of Time\Scenario\Germanicus\events.lua:61: in main chunk
    D:\Test of Time\Scenario\Germanicus\munitions.lua:351: attempt to call a nil value (local 'onUnitActivateFn')
    stack traceback:
        D:\Test of Time\Scenario\Germanicus\munitions.lua:351: in function 'munitions.doMunition'
        D:\Test of Time\Scenario\Germanicus\events.lua:78: in function <D:\Test of Time\Scenario\Germanicus\events.lua:76>
    
    onUnitActivateFn is only found in the munitions.lua - I can't find a reference anywhere else - I was able to fix this and avoid the error by changing onUnitActivateFn to doOnActivateUnit, which is what is in my events. However, if I attempt to change line 351 to the same thing, it throws an error. Instead, I also have to edit out this line in the munitions (351):

    if unitTable[1] and specification.activate then
    unitTable[1]:activate()
    onUnitActivateFn(unitTable[1],true) --I have to edit this line out for the code to not throw an issue. Attempting to change this to doOnActivateUnit as well does not fix the problem.
    end

    My fix has solved the issue for my purposes with this scenario (my archer fires the arrow up to two times per turn and the arrow activates first, which is all I need), but I thought I'd report it. I'm probably just missing something but you never know.
     
  5. JPetroski

    JPetroski Emperor

    Joined:
    Jan 24, 2011
    Messages:
    1,964
    Oh, and just some general feedback for you: as you're well aware, I'm not the best at this, but I've now successfully added 'k' units into three scenarios via two different methods:

    1. I added them to Midway and Rise of Macedon by copying them from OTR or Caesar;
    2. I've added them to Germanicus by using your munitions library.

    Although it was a bit of a learning curve for me, when I sat down and focused on using this today, it was very quick. This is a much easier method than I used with Midway or Macedon. I'm very happy you took the time to sit down and craft this. Hopefully Germanicus will serve as a decent "example" of how you can convert an old scenario to ToTPP and add in at least one module.
     
  6. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,416
    Location:
    Ontario
    You shouldn't have to change anything inside munitions.lua

    In your events, you need to do the following:

    1. Get the name of the function that you are supplying to civ.scen.onActivateUnit
    1a. If you're defining the function inside civ.scen.onActivateUnit, e.g.
    Code:
    civ.scen.onActivateUnit(function(unit,source) civ.ui.text("Unit Activated!") end)
    
    Re-write so that the function has a name, e.g.
    Code:
    local JPActivate = function(unit,source) civ.ui.text("Unit Activated!") end
    civ.scen.onActivateUnit(JPActivate)
    
    1b. You only need the code that is supposed to run every time a unit is activated. For example, you don't need the afterProduction code. So, you might have
    Code:
    civ.scen.onActivateUnit(function(unit,source)   
        -- this if statement makes doAfterProduction work
        if flag.value("tribe"..tostring(civ.getCurrentTribe().id).."AfterProductionNotDone") then
            doAfterProduction(civ.getTurn(),civ.getCurrentTribe())
            flag.setFalse("tribe"..tostring(civ.getCurrentTribe().id).."AfterProductionNotDone")
        end
        JPActivate(unit,source)
    end)
    
    You only need JPActivate

    2. Find the munition function in events.lua, and supply your activation function (in this case, JPActivate) as the third argument
    Code:
      if keyID == 75 --[[k]] and civ.getActiveUnit() then
           munitions.doMunition(civ.getActiveUnit(), munitionSpecificationTable,JPActivate)
           return
      end
    
    The reason for this is that the function unit:activate doesn't run the activation code. So, for example, any leader bonus will not be applied to munitions automatically activated by the munition module, unless you have that line. This may not be relevant to Germanicus, but it could be relevant to others, and is rather easy to overlook.
     
    Last edited: Jan 12, 2020
  7. JPetroski

    JPetroski Emperor

    Joined:
    Jan 24, 2011
    Messages:
    1,964
    @Prof. Garfield, thank you very much for this explanation. I have corrected the code so that it doesn't require any meddling with the munitions library (and therefore doesn't throw anyone off), and it works like a charm.
     

Share This Page