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

Unit Spawn Handler Lua

Discussion in 'Civ5 - Mod Components' started by LeeS, Jun 11, 2015.

  1. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    6,751
    Location:
    Illinois, USA
    Lua Unit Spawn Handler


    Download:

    Download the mod directly from the forum's download database here

    Introduction:
    1. I originally built this for Nokmirt to use in his Guadalcanal Scenario. It didn't seem like it would be much of a deal to write a quick little routine to spawn units into the scenario, but I ran into several non-intuitive issues that caused the scope and scale of the handler to grow, and as it grew I reached a point where I decided I might as well try to make it as universal as possible.
    2. Still, why not just use player:InitUnit() in the code and be done with it?
      • Answer 1: Air Units -- this is the 800lb gorilla in the mists
      • Answer 2: Land Units being spawned in water or lake tiles
      • Answer 3: For Promotions to be added to units, Unit:IsPromotionValid() is not as useful as one might think
    3. Why not simply use Unit:JumpToNearestValidPlot() in the code and thereby simplify everything rather than writing a universal unit spawn-handling routine?
      • Answer 1: Air Units do not work for Unit:JumpToNearestValidPlot() -- that 800lb gorilla just got bigger and has stomped out of the mists
      • Answer 2: Land Units targeted for being spawned in water or lake tiles will jump to the nearest land plot when using Unit:JumpToNearestValidPlot(), and this could literally be half a world away from the intended original plot for a scenario.
    4. Air units that are directly added to a scenario map on a city tile are fine, so long as there are not more Air Units added via the scenario map than that city can 'support'. If more than can be supported in a city are added directly to the map, if the player puts any of those units to sleep, they are no longer accessible without going through the Military Overview. And Other not-very-nice things can happen to such air units when the player executes "Next Turn".
    5. Air units that are directly added to a scenario map on a tile that contains an Aircraft Carrier would seem to be OK, but in reality are not. For Air Units that are directly placed onto the scenario map on the same tile as an Aircraft Carrier, the following applies:
      • If the total number on the Carrier exceeds the Carrier's capacity, the same issue occurs on the starting turn as for too many units within a city.
      • Even when the total number placed on the same tile as the Carrier are within the Carrier's limits of carrying capacity, as soon as the player presses "Next Turn", all such aircraft that were placed directly into the scenario map on the same tile as an AirCraft Carrier are removed from the game.
      • The Aircraft Carrier remains, but the Air Units are gone, *poof*
    6. When I started building this I started with a unit-spawning function that whoward had given (lent ?) me a long time ago, and I used the same function name. I have chosen not to change the function name since Nokmirt has already put so much work into his scenario using that function-name.
      • I don't think this will be much of a deal except in very rare cases where someone else is also using that original function name in one of their mods, and then it will only be a problem if they are using it within the same lua context. In such a case they should either use the UnitSpawnHandler.lua and its version of function SpawnAtPlot() and not use any other version of the same function-name, or they should not be using the UnitSpawnHandler.
      • In any case I think the chances of function-name interference will be rare if they ever occur at all

    So, since this has grown to be a 'univeral' unit spawn handler that can be used in lua unit-spawning, what does it do and where is it best applied?

    What It Does

    Bear in mind that this function was written with especial attention paid to the need for scenarios to sometimes create enemy armies/fleets or offshore embarked enemy invasion forces, so a certain substantial portion of the code is written to conform to those needs. It can also be used just as effectively in 'normal' lua where there will be a need to spawn-in units from lua. It should be of especial use when those 'normal' lua programs are spawning Air Units on a regular or recurring basis.

    1. Handles all the logic for placing a unit on the game-map, including deciding whether the original target tile is no good, and where the unit should be 'diverted' to in such cases.
    2. Allows a specification of the amount of unit experience that should be added as part of the spawning of the unit
    3. Allows a specification of the amount of 'damage' the unit should appear with, if any
    4. Allows a specification of a unique unit name, if desired
    5. Allows a specification of promotion(s) to be added to the unit beyond those the unit would be given for 'free' as an inherent property of the unit, or those the unit would get from adoption of policies, building of wonders, etc.
      • Sorts out whether the promotion is valid for the unit based on whether the unit is a combat unit or a civilian unit
      • Will only give civilian units a promotion that is nowhere listed in the table UnitPromotions_UnitCombats
      • For combat units, will 1st determine if the promotion appears anywhere in the table UnitPromotions_UnitCombats and will only apply the desired promotion in that case if there is a match-up between the unit's UnitCombatType and a row within the table UnitPromotions_UnitCombats for that promotion and unit combat type.
      • If the promotion does not appear anywhere in the table UnitPromotions_UnitCombats, the handler will view that promotion as being valid for all combat units and will apply it to any combat unit when the promotion is specified to be added to the unit as part of the spawning process.
    6. For combat or civilian units, determines if the target tile is 'full' or not for that type of unit, and diverts the unit to a nearby tile when required
    7. For land units that were originally targetted to appear on a water tile, diverts them to a near water tile if at all possible when the original tile is already occupied by a land combat unit or land civilian unit, and sets them as 'embarked'. In order to cover all possibilities, the unit is also auto-given the All Water Embarkation promotion.
    8. For all units, if the target is currently occupied by a unit of another player, the unit being spawned is diverted.
    9. If the target tile is a city tile and the city is now owned by another player, diverts the unit to another tile.
    10. For Air units, if the target tile was a city plot, determines if the city plot is already full of air units. If the city is already full of air units or the city now belongs to another player, the handler program will (in order):
      • Look for the nearest friendly city that is not also full of air units, and place the Air Unit there. This might cause the Air Unit to appear farther from the original target tile than the Air Unit's usual 'Rebase Range'.
      • When no friendly city is available for the Air Unit, the handler will look for an AirCraft Carrier that would be valid (ie, the Carrier is not already full of Air Units), and place the Air Unit there.
      • If both of the above fail, then the Air Unit is not spawned.
    11. If the target tile was originally one with an Aircraft Carrier, but no Aircraft Carrier is now on the plot (ie, the Carrier was diverted to a different tile), the handler will (in order):
      • Look through tiles adjacent to the original target tile for a valid AirCraft Carrier before looking anywhere else for a valid plot to place the Air Unit
      • Look through all the player's aircraft carriers and get the nearest one that can take another Air Unit
      • Look through all the player's cities and get the nearest one that can take another Air Unit
      • If all of the above fail, then the Air Unit is not spawned.

    Where/When It Can Be Used
    1. In scenarios especially
      • For example in a WWI scenario you could mimic the sudden mobilization of Russia, Germany, France, etc., that resulted in summer 1914 from the assassination of Archduke Ferdinand.
      • You could in a 'build-up-to-war' scenario for WWI mimic the various diplomatic tensions (and near declarations of war) that plagued European International Politics from approximately 1908 to the actual outbreak of war in 1914, and thereby create an earlier-than-normal start to WWI than actually occured. The Unit Spawn Handler would allow you to more simply create the mobilization armies that would have been appropriate in, say, 1912.
    2. In 'Normal' lua routines when it is necessary to spawn a unit and you do not feel up to writing all the checks for a plot target being valid, etc.
    3. In lua programs where random events or 'trigger' events are being used, and one of the results of this random event or trigger event is to spawn a series of units in or around a chosen map area.
    4. In any 'context' where you wish to add one or more units but cannot guarantee what units might or might not be occupying a given tile, or where by the point in-game that your lua should spawn units you cannot be sure for example that a city has not changed hands, or an invading enemy is not occupying the target tile(s).

    How To Use It
    1. Add the file UnitSpawnHandler.lua to your mod and set it's file properties as Import Into VFS=true in ModBuddy
    2. At the top of your main lua file, add the line include("UnitSpawnHandler.lua")
    3. Call the function SpawnAtPlot() as needed whenever needed in your main lua program
    4. The statement of SpawnAtPlot() within your main lua to create a new unit is done as:
      Code:
      SpawnAtPlot(pPlayer, iUnitType, PlotX, PlotY, iExperience, iDamage, sUnitName, sPromotionTableName)
      • pPlayer is always required and is the 'player' pointer. Needs to be presented in a format of:
        • Players[iPlayer]
          Code:
          SpawnAtPlot(Players[iPlayer], iUnitType, PlotX, PlotY, iExperience, iDamage, sUnitName, sPromotionTableName)
        • Or, you need to have previously defined a player 'pointer' variable as in
          Code:
          local pPlayer = Players[iPlayer]
          SpawnAtPlot(pPlayer, iUnitType, PlotX, PlotY, iExperience, iDamage, sUnitName, sPromotionTableName)
      • iUnitType is the designation for the unit to be spawned and is always required. It needs to be the unit type's ID#, and can be defined in any of the following methods (using UNIT_CARRIER as an example):
        • GameInfoTypes["UNIT_CARRIER"]
          Code:
          SpawnAtPlot(pPlayer, GameInfoTypes["UNIT_CARRIER"], PlotX, PlotY, iExperience, iDamage, sUnitName, sPromotionTableName)
        • GameInfoTypes.UNIT_CARRIER
          Code:
          SpawnAtPlot(pPlayer, GameInfoTypes.UNIT_CARRIER, PlotX, PlotY, iExperience, iDamage, sUnitName, sPromotionTableName)
        • local iCarrier = GameInfoTypes.UNIT_CARRIER
          Code:
          local iCarrier = GameInfoTypes.UNIT_CARRIER
          SpawnAtPlot(pPlayer, iCarrier, PlotX, PlotY, iExperience, iDamage, sUnitName, sPromotionTableName)
      • PlotX is the 'X' coordinate for the target plot and is always required. This can be directly entered as an integer whole number (in the case of scenarios) or as a variable.
      • PlotY is the 'Y' coordinate for the target plot and is always required. This can be directly entered as an integer whole number (in the case of scenarios) or as a variable.
      • iExperience allows you to specify the unit's starting XP's. If you want to have it spawn without pre-given XP amounts, just state "0". Direct integer values in positive whole numbers can be used, or a variable.
      • iDamage allows you to specify the unit's starting damage amount. If you want to have it spawn without pre-given damage amounts, just state "0". Direct integer values in positive whole numbers can be used, or a variable.
      • sUnitName allows you to specify a unique name for the unit. If you want no unique name, simply state "NONE" or "NO_NAME", otherwise, state as "USS Enterprise" to name a carrier unit as The Big E.
      • sPromotionTableName allows you to specify promotions for the unit. If you want no promotion(s), simply state "NO_PROMOTION" or omit it entirely. Otherwise, state as "PROMOTION_MORALE" to give a unit the Morale Promotion. If you want to give the same unit multiple promotions you 1st create an lua-table with the list of promotions, and then you place the name of the lua-table in this spot. Examples below.
        • For all these examples assume we are placing a Carrier on the map at an X,Y grid location of 41,41. Assume also we are spawning it with 15 experience and 10 damage to the unit, and we are going to call it "USS Enterprise".
        • If I want to give only one 'free' promotion to the unit, such as "PROMOTION_FLIGHT_DECK_1", then I can do it as follows:
          Code:
          SpawnAtPlot(pPlayer, GameInfoTypes["UNIT_CARRIER"], 41, 41, 15, 10, "USS Enterprise", "PROMOTION_FLIGHT_DECK_1")
        • If I want to give all three 'Flight Deck' promotions to the unit ( "PROMOTION_FLIGHT_DECK_1", "PROMOTION_FLIGHT_DECK_2", "PROMOTION_FLIGHT_DECK_3"), then I can do it as follows, by creating a temporary lua-table (a list of items), and then placing the name of that temporary lua-table as the designation for the sPromotionTableName argument:
          Code:
          PromotionList = { "PROMOTION_FLIGHT_DECK_1", "PROMOTION_FLIGHT_DECK_2", "PROMOTION_FLIGHT_DECK_3" }
          SpawnAtPlot(pPlayer, GameInfoTypes["UNIT_CARRIER"], 41, 41, 15, 10, "USS Enterprise", PromotionList)
          The code of the Unit Spawn Handler is written to recognize the difference between lua-tables and character-strings. The example in (b) was using a direct character-string, this example is using a table reference.
      • Trailing arguments for which you do not need or want to specify information can be omitted so long as you do not to need anything that comes after something you do not want or need.
    5. Placing Multiple Units on the same plot (especially with regards Air Units and Aircraft Carriers):
      • You can place multiple units on the same plot in succession, and the 2nd, 3rd, 4th, etc., occurance of the SpawnAtPlot function for the same tile X,Y reference will act no differently than a single occurance of the SpawnAtPlot function for the same tile X,Y reference.
      • The logic of the Unit Spawn Handler fires independantly and completely for each occurance of SpawnAtPlot()
      • To place an Aircraft Carrier with multiple Air Units all on the same tile, state the SpawnAtPlot() function to create the Aircraft Carrier 1st, and then place the Air Units on the same plot:
        Code:
        SpawnAtPlot(pPlayer, GameInfoTypes["UNIT_CARRIER"], 41, 41, 15, 10, "USS Enterprise", "PROMOTION_FLIGHT_DECK_1")
        SpawnAtPlot(pPlayer, GameInfoTypes["UNIT_FIGHTER"], 41, 41, 0, 0, "NO_NAME", "PROMOTION_DOGFIGHTING_1")
        SpawnAtPlot(pPlayer, GameInfoTypes["UNIT_BOMBER"], 41, 41, 0, 0, "NO_NAME", "PROMOTION_AIR_SIEGE_1")
        
    6. Creating a SeaBorne invasion army: You can create an invasion force of land units and have them appear out on the sea.
      • Simply create all the land units on the sea plots, and Unit Spawn Handler will deal with the issues of setting them as being embarked, and ensuring that too many embarked land units are not placed on the same tile.
      • Unit Spawn Handler will give all these units the All Water Embarkation promotion even if the player has not discovered any of the usual techs required to allow land units to embark. This is done as insurance as part of the Spawn Handler so that an embarked land invasion army is not immobilized offshore.
      • After the SpawnAtPlot() command for the final land unit to be created at sea, add the following:
        Code:
        UpdateEmbarkedUnitGraphics(pPlayer)
        • The usage for the player designation should be the same as is being used for SpawnAtPlot(pPlayer, etc)
        • The inclusion of UpdateEmbarkedUnitGraphics(pPlayer) at the end of the commands for spawning the invasion army will cause all embarked graphics for the player to be updated, and thus will eliminate the 'walking on water' appearance that would otherwise occur.
      • Example:
        Code:
        SpawnAtPlot(pPlayer, GameInfoTypes.UNIT_MARINE, 76, 16)
        SpawnAtPlot(pPlayer, GameInfoTypes.UNIT_TANK, 76, 16)
        SpawnAtPlot(pPlayer, GameInfoTypes.UNIT_GREAT_GENERAL, 76, 16, 0, 0, "George Herbert Shaw")
        UpdateEmbarkedUnitGraphics(pPlayer)

    What It Does Not Do

    1. The Unit Spawn Handler does not consider whether the player has sufficient supply, sufficient gold, or sufficient resources to support the unit being spawned. You should handle these issues before calling the SpawnAtPlot() function in your lua.
    2. It never places Stealth Bombers or any other Air Unit that does not have <Special>SPECIALUNIT_FIGHTER</Special> in its <Units> table definition onto an Aircraft Carrier
    3. Will not recognize a unit that has anything besides <SpecialCargo>SPECIALUNIT_FIGHTER</SpecialCargo> in its <Units> table definition as being an Aircraft Carrier
    4. Does not concern itself with whether the designated unit is the correct unique unit from within a unit-class for a particular player, or whether the unit is the default from within the unit-class when the player ought to be getting a unique unit from within that unit-class instead of the default unit. This is done on purpose so that you can have more flexibility in what the Unit Spawn Handler does.

    Mod Compatiblities:

    1. The Unit Spawn Handler will conform to mods that alter the <Units> table to any reasonable degree I can imagine. It sorts how to act primarily based 1st on the specified <Domain> of the unit, and only concerns itself with the unit's <CombatClass> when assigning promotions.
    2. The Unit Spawn Handler determines if a unit is a civilian unit based on the setting (or absence of a setting) for <CivilianAttackPriority>
    3. Assuming no just really oddball stuff in a unit's definition, the Unit Spawn Handler should be able to accomodate any mod additions or changes to the <Units> table.
    4. The Unit Spawn Handler will not recognize nor look for the 'Airfield' tile-improvement system in whoward's dll/mod for adding map-plot 'Airfields' to the game.
    5. For the purposes of recognizing 'Aircraft Carriers', the Unit Spawn Handler will auto-recognize as a valid aircraft carrier any unit that has the following three things in it's <Units> table definition:
      • <Domain>DOMAIN_SEA</Domain>
      • <SpecialCargo>SPECIALUNIT_FIGHTER</SpecialCargo>
      • <DomainCargo>DOMAIN_AIR</DomainCargo>
      Note that properly-defined air units contain <Special>SPECIALUNIT_FIGHTER</Special> in their <Units> table definition to allow them to land on carriers. The big exception to this is Stealth Bombers, which have <Special>SPECIALUNIT_STEALTH</Special> and are specifically not allowed to land on Aircraft Carriers.

    Expansion-Pack Compatiblities:

    As written, the UnitSpawnHandler.lua file will only really work properly for BNW. Firaxis made some really bizarre and not-very-friendly code-alterations between Vanilla/G&K and BNW.

    If there is enough interest I'll look into making a pre-BNW version, or I'll look into making the code back-compatible to Vanilla/G&K.



    Download:

    Download the mod directly from the forum's download database here
     
    Last edited: Apr 13, 2017
  2. nokmirt

    nokmirt Emperor

    Joined:
    Feb 14, 2009
    Messages:
    5,087
    Location:
    Iowa USA
    This is excellent and works great. I plan on using it in future mods. LeeS you have helped me a great deal, and by posting this universally it will help a lot of scenario creators in the future. :)
     
  3. Craig_Sutter

    Craig_Sutter Deity

    Joined:
    Aug 13, 2002
    Messages:
    2,747
    Gender:
    Male
    Location:
    Calgary, Canada
    This looks great... I would like to use it in my Viking scenario. There is a mention about having required unitclasses for certain promotions. In my mod, I have several custom unitclasses... for instance, mercenaryclass. Can new unitclasses that still accommodate new and existing promotions be used. Does the function behave positively or negatively, by which I mean do promotions have to be actively attributed to a unitclass to be added or do there have to be xml that denies them a promotion the unit does not fullfill the criteria for (a melee unit getting a naval promotion for instance)?
     
  4. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    6,751
    Location:
    Illinois, USA
    It is based on what is specified for the Unit's <CombatClass> (ie, UNITCOMBAT_NAVALRANGED, UNITCOMBAT_ARMOR, UNITCOMBAT_MELEE) and whether that CombatClass is listed as valid for that promotion under table UnitPromotions_UnitCombats.

    Does not look to a Unit's <Class> (ie, UNITCLASS_LONGSWORDSMAN etc.)

    Column <CombatClass> in table <Units> references back to column <Type> in table UnitCombatInfos, which is where perhaps the confusion originated given that I was referring to this when I used the term "UnitCombatType"
    ------------------------------------------------------------------
    Promotions that are given to a unit based on what is stated for that unit within the table Unit_FreePromotions are not affected one way or the other by the code. The game always adds these promotions automatically to the correct units.
    -------------------------------------------------------------------
    I'm also working on some streamlining for the code, but it isn't ready yet. When an update to the code is ready, you will be able to copy/paste new versions of UnitSpawnHandler.lua right over previous versions within your mod(s) whether in the built version of the mod in the MODS folder, or the version of the UnitSpawnHandler.lua file that will be in your ModBuddy Project.
     
  5. Craig_Sutter

    Craig_Sutter Deity

    Joined:
    Aug 13, 2002
    Messages:
    2,747
    Gender:
    Male
    Location:
    Calgary, Canada
    Thank-you.
     
  6. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    6,751
    Location:
    Illinois, USA
    I updated the OP somewhat, and added a download link for the latest version. The download link now takes you to the CFC downloads database.

    The updated version of the code includes the majority of the code streamlining I wanted to do, and adds an ability to update the graphics of land units you spawn out to sea so they don't have the "I am walking on water" look.

    There are still some thoughts I have for further code streamlining, but it wil probably be quite some time before I am ready to get at it.
     
  7. Troller0001

    Troller0001 Not an actual Troll

    Joined:
    Mar 9, 2016
    Messages:
    736
    Gender:
    Male
    Location:
    The Netherlands
    So it seems that I've found some obscure bug for the air-unit behaviour.
    In the function LookForValidCarrier on line 432 it is possible to have the method return a plot containing a carrier-unit owned by a player different than pPlayer.
    Moreover, the method also does not seem to prioritise the current plot over other plots (in a similar way to how it prioritises adjacent plots)


    Code:
    function LookForValidCarrier(pPlayer, pPlot)
        local pNearestCarrierPlot = "NONE"
        for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
            local pAdjacentPlot = Map.PlotDirection(pPlot:GetX(), pPlot:GetY(), direction)
            if pAdjacentPlot and pAdjacentPlot:IsUnit() then
                for i = 0, pAdjacentPlot:GetNumUnits() do
                    local pPlotUnit = pAdjacentPlot:GetUnit(i)
                    if pPlotUnit then
                        if CheckForCarrierUnit(pPlotUnit:GetUnitType()) then
                            if not pPlotUnit:IsFull() then -- This does not check the owner of the unit
                                return pAdjacentPlot -- So this can return a plot with an enemy carrier (if one is adjacent)
                            end
                        end
                    end
                end
            end
        end
        -- [snipped]
    end
    
    Still an excellent tool nevertheless! It saved me a lot of time already!

    EDIT: While I cannot expect this to be fixed, I'm still posting this here as for anyone else who might have issues.
     
  8. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    6,751
    Location:
    Illinois, USA
    Erg! I'll try to look into it next week some time after I get my residence-moving completed. I hate it when there's a dangling bug-participle in the logic of my code.
     

Share This Page