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

The Battle of Midway - Development Thread

Discussion in 'Civ2 - Scenario League' started by JPetroski, Oct 27, 2019.

  1. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    This is the code I was thinking of (not tested, so there may be some debugging to do). Note that you also need a function 'carrierOnTile' .

    Code:
    local text = require("text")
    
    local function silentReArm(reArmUnit)
        if alternateArmament[reArmUnit.type.id] and reArmUnit.moveSpent == 0 and carrierOnTile(reArmUnit.location) then
            local newPlane = civ.createUnit(alternateArmament[reArmUnit.type.id],reArmUnit.owner,reArmUnit.location)
            newPlane.homeCity = reArmUnit.homeCity
            newPlane.moveSpent = newPlane.type.move
            newPlane.attributes = reArmUnit.attributes -- this includes veteran status
            civ.deleteUnit(reArmUnit)
        end
    end
    
    
    local function textReArm(selectedUnit)
        -- these are messages for if the unit can't re-arm
       
        if not (alternateArmament[selectedUnit.type.id]) then
            text.simple(selectedUnit.type.name.." units do not have an alternate munition.","Defense Minister")
            return
        elseif not carrierOnTile(selectedUnit.location) then
            text.simple("Units can only be rearmed on a carrier.","Defense Minister")
            return
        elseif not (selectedUnit.moveSpent == 0 ) then
            text.simple("Units can only be rearmed if they have full movement points.","Defense Minister")
            return
        end
    
        -- this offers the choice of whether to re arm or not
        local altUnitType = alternateArmament[selectedUnit.type.id]
        local menuText = "Do you wish to change the armament of our "..selectedUnit.type.name.."?  This will expend all movement points for the current turn."
        local menuTable = { [1] = "Keep our "..selectedUnit.type.name.." as it is.",
                            [2] = "Change this "..selectedUnit.type.name.." unit to a "..altUnitType.name.." unit.",
                            [3] = "Change all "..selectedUnit.type.name.." units on this square to "..altUnitType.name.." units.",}
        local choice = text.menu(menuTable,menuText,"Defense Minister")
        -- execute the chosen outcome
        if choice == 1 then
            -- munition swap declined
            return
        elseif choice == 2 then
            -- rearm the active unit only
            silentReArm(selectedUnit)
            return
        elseif choice == 3 then
            local convertType = selectedUnit.type
            for plane in selectedUnit.location.units do
                if plane.type == convertType then
                    -- if the plane can't be re-armed, the silentReArm check will catch it
                    silentReArm(plane)
                end
            end
            return
        end
    end
    
    If you want to use a different kind of table that's fine, but one of the advantages of using the key for information is that you don't have to use a loop to search the table for the entry you want. The table I suggested can also be expressed as
    Code:
    alternateArmament = {
    --[[KateBomb to KateTorp]] [unitAliases.kateBomb.id] = --[[unitSwappedTo]] unitAliases.kateTorp,
    --[[KateTorp to KateBomb]] [unitAliases.kateTorp.id]= --[[unitSwappedTo]] unitAliases.kateBomb,
    }
    
    You can use comments to help you keep track of what is going on. Each 'data entry' has only two pieces of information, the original unit type and the new unit type. Tables are key-value pairs, so you might as well use one piece of information as the key, and the other as the value, and simplify some code. If you really want, I'm pretty sure you can actually use unit type objects as keys also, rather than the integer id numbers.
     
  2. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    Thank you. I'm hoping that by plugging away at this it'll start making more sense. I think I need to buy a second monitor so I can keep your lessons up on one and then split the second's screen with an example events and the events I'm working on. So much to keep track of.

    this was quite a helpful sentence to help me understand.

    So I took a stab at putting everything in and writing a "CarrierOnTile" (just with the Kaga for now) but the game is throwing me an index error

    Code:
    D:\Test of Time\Scenario\Midway\Events.lua:865: attempt to index a nil value (field 'kateBomb')
    stack traceback:
        D:\Test of Time\Scenario\Midway\Events.lua:865: in main chunk
    
    which relates to this:

    local alternateArmament={}
    alternateArmament[unitAliases.kateBomb.id]=unitAliases.kateTorp
    alternateArmament[unitAliases.kateTorp.id]=unitAliases.kateBomb

    I don't get it because I thought when I saw an issue like this, it would mean that the value (the second part here - right? the =unitAliases.kateTorp) would be mispelled or missing somewhere, but it is not? Such a unitAliases is clearly defined early in the events?

    I've attached the events in case I'm missing something. The total event "chain" starts at 864.

    868 is my attempt at carrierOnTile, which I'm hoping I got right, but I think all I want to do is check if it is "true" that a carrier is there. I'm not sure if it'll work until I can get past the current error. Note that carrierUnits is a table earlier in the code that I'm hoping to reuse.

    Code:
    local function carrierOnTile(unit)
        if not carrierUnits[unit.type.id] then
            return
        else
            for unitOnTile in unit.location.units do
                if unitOnTile.type == unitAliases.Kaga then
                    carrierOnTile = true 
                    return
                end
            end
        end
    end
    
    883 is where I've placed the code you've given me.

    At 987 I put this:

    Code:
    civ.scen.onKeyPress(function(keyID)
    
    if keyID == specialNumbers.munitionSwapKey then
       
        silentReArm(reArmUnit)
        textReArm(selectedUnit)
       
        end
    
    But, from looking at the WildeSau code from OTR, I'm pretty sure I actually need it to say:

    Code:
    civ.scen.onKeyPress(function(keyID)
    
    if keyID == specialNumbers.munitionSwapKey then
       
        silentReArm(civ.getActiveUnit()) 
        textReArm(civ.getActiveUnit())
       
        end
    
    Or, I could be totally off base on a large number of things, but I'm hoping I'm on the right track with the carrierOnTile and also the civ.scen.onKeyPress section (especially the correction I think I've made but can't yet test).
     

    Attached Files:

  3. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    kateBomb is misspelled; it should be KateBomb based on your unit aliases definition early in the code. kateTorp should also be KateTorp, but the program evaluates from left to right, so it is first trying to evaluate unitAliases.kateBomb.id, which, since kateBomb is not a key in unitAliases, means that the program is trying to evaluate nil.id, which obviously doesn't make sense.

    Code:
    local function carrierOnTile(unit)
       if not carrierUnits[unit.type.id] then
            return
        else
            for unitOnTile in unit.location.units do
                if unitOnTile.type == unitAliases.Kaga then
                    carrierOnTile = true
                    return
                end
            end
        end
    end
    
    I realize now I should have explained this more. You have the basic idea for the code, but need the details.

    1. This function should return true or false, depending on whether a carrier is on the tile or not. So, you want to have lines
    Code:
    return true
    
    or
    Code:
    return false
    
    Just using a return line ends the function but doesn't provide anything to the 'outside' program. If we're not expecting anything from it (for example, if we're just changing something on the 'game board'), then that doesn't matter. But carrierOnTile is supposed to provide information to be used.
    Code:
       if not carrierUnits[unit.type.id] then
            return
        else
    
    Are you trying to include a check for re-armament for units operating from an airbase on Midway? In that case, you want

    Code:
       if not carrierUnits[unit.type.id] then
            return unit.location.city
        else
    
    Code:
            for unitOnTile in unit.location.units do
                if unitOnTile.type == unitAliases.Kaga then
                    carrierOnTile = true
                    return
                end
            end
    
    Should be changed to
    Code:
            for unitOnTile in unit.location.units do
                if unitOnTile.type == unitAliases.Kaga then
                    return true
                end
            end
    return false
    
    You don't need to do anything with a variable carrierOnTile, just return true if you find a valid unit.
    If you don't find a valid unit, then you want to return false. However, if you also want to include the ability to rearm in a city, then replace
    Code:
    return false
    
    with
    Code:
    return unit.location.city
    
    If the unit is in a city, then this is equivalent to true (anything not nil or false is 'true' for logic in Lua). If there is no city, it will be nil, equivalent to false.

    Currently, you are only checking for one kind of carrier with this line:
    Code:
    if unitOnTile.type == unitAliases.Kaga then
    
    You can put in a bunch of or conditions, checking if the unit is one of your 6 or 8 carrier types. Another solution is

    Code:
    if carrierUnits[unitOnTile.type.id] and unitOnTile.type.domain == 2 then
    
    This checks if the unitOnTile is a carrier unit and if it is also a sea unit. I think the only units that satisfy both those conditions are your carriers.

    One last thing:

    Your version of carrierOnTile takes a unit as an argument, not a tile. That's fine (and allows generalization to re-arming in a city), but requires a change in the code I wrote:
    Code:
    carrierOnTile(reArmUnit.location)
    carrierOnTile(selectedUnit.location)
    
    should be
    Code:
    carrierOnTile(reArmUnit)
    carrierOnTile(selectedUnit)
    
    The substitution of civ.getActiveUnit() is correct. However, you don't need (and it might be harmful, by changing the unit before your textReArm) the silentReArm line. (Unless, you only want the player to change one unit at a time, and not bother with warnings/questions, in which case you don't need textReArm.)

    Yes, a second monitor can be a huge help in programming (and can be convenient generally). I've used two in the past, though I only have one on my current setup. Another option is to use virtual desktops, which are part of Windows 10.

    https://www.howtogeek.com/197625/how-to-use-virtual-desktops-in-windows-10/

    They're not quite as good as spare monitors, but still quite useful. I usually keep one desktop with my text editor, one with the Lua Function Reference page open, and one with Civ II open, so I can test my code.

    You can see my workflow in this video (I recorded this for a lesson, though I'm not sure how useful it ended up being. The idea seemed better before I did it than after I finished.)
     
    McMonkey likes this.
  4. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    :thumbsup:

    Most of this is now working (I couldn't get the event to change all planes at once, so I just edited it out. This is sufficient for my purposes).

    I really appreciate the help. It makes a lot more sense looking at what I messed up and how you fixed it and why.


    You did mention:

    Code:
      if not carrierUnits[unit.type.id] then
            return
        else
    
    I was basically trying to just confirm that the unit was not a carrier unit, so I guess this is how I was looking at "return true" down later in the code. It now makes more sense why I didn't need that. This one's going in oneNote.
     
  5. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    Can you tell me what features you need for reactions for this scenario? Since there is only one map, I presume you don't need bonuses or penalties for different maps. I also presume you don't need bonuses for technologies. Is this correct? I'd like to have a 'target' for making examples and testing.

    I presume you need to be able to specify how far away a unit can react to another unit (maybe have range dependant on the exactly what unit is being reacted to), and be able to specify how much damage is done to each type of unit. I think you also wanted a limited number of reactions per unit per turn (like OTR), though another option is a limited number of reactions per trigger instead.

    Do you need other stuff?
     
  6. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    For midway I just need one unit to react to another at different ranges. I do need damage schedules of some kind so that a unit can do more damage against some units (example bombers) than others (example fighters). I do want to be able to limit the reactions per turn, and I want reactions to also kill any ammo called up.
     
  7. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    Can you post (or PM) your current midway files, so that I can use them to run tests for reactions? I'm not quite done as I write, but I'm hoping to complete at least a basic reaction module by the end of the week.
     
  8. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    Here you are, Sir.
     

    Attached Files:

  9. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    The basic reaction module is on its way. I'm at the point where I've gotten reactions to happen, which is a big milestone for code that has to be 'complete' before it can be tested. However, I haven't tested all the parts yet. I'm hoping to complete tests tomorrow.
     
  10. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    Here's Midway integrated with reactions. The file simpleReactions.lua is where you will make changes to the table reactInfo.

    I also added files diplomacy.lua and reactionBase.lua to support simpleReactions.lua. I moved unitAliases to unitAliases.lua, so it could also be referenced by simpleReactions.lua (I left a commented version in Events.lua). I also saved some games while doing tests (which you don't have to leave in)

    Events.lua was modified to integrate with reactions. This shouldn't have changed any other functionality. I also uncomented log.onUnitKilled, since I'm pretty sure you want combat reporting, and I happened to notice it.

    Something else I noticed which I DIDN'T change is that the code to determine whether or not carriers can carry particular air units is only functioning for one Japanese carrier. Let me know if you want help fixing it.

    If you need more documentation for reactions, or if you run into any reaction bugs (or, other bugs you want help with), let me know.
     

    Attached Files:

  11. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    Thank you! When I get a few hours to myself later I'll play around with this.

    I took a *very* quick look and think i simply haven't added them in yet. Is this the part?

    Code:
     -- p.g. code for making carriers only carry specific units see useCarrier table above
        -- munitions also use carrier, since otherwise the carrier is air protected
        if useCarrier[unit.type.id] or unit.type.flags & 1<<12 == 1<<12  then
            unitAliases.Akagi.flags = specialNumbers.defaultCarrierFlags
        else
            unitAliases.Akagi.flags = specialNumbers.doNotCarryCarrierFlags
        end
        rearmCarrierUnit(unit)
    
    Wouldn't I just add

    or unitAliases.Kaga.flags = specialNumbers.defaultCarrierFlags

    and (after "else")

    unitAliases.Kaga.flags = specialNumbers.doNotCarryCarrierFlags

    and so on and so forth?
     
  12. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    Yes, that is what you would have to do.
     
  13. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    Any idea why aircraft can no longer go over land tiles? Just curious if something in the reaction code would do that?

    Edit - I'm an idiot. It was a rules issue. I thought I had each unit that needed overrides impassable as such but I was wrong. Hope you didn't spend any time researching this and sorry if you did.
     
    Last edited: Mar 30, 2020
  14. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    By John Petroski, McMonkey, and Grishnach
    Requires TOTPP v. 15.1 or higher
    112, 90, 0
    2, 0
    2, 0
    .\log.lua:140: attempt to index a nil value (field '?')
    stack traceback:
    .\log.lua:140: in function 'log.onUnitKilled'
    D:\Test of Time\Scenario\Midway\Events.lua:2218: in function <D:\Test of Time\Scenario\Midway\Events.lua:2216


    This is a new one to me (field '?')

    This is occurring when a dauntless drops a bomb on an aircraft carrier. My best guess is that this is telling me that the bomb unit that was killed isn't defined anywhere? So it is the nil value? And that I'd need to add something to this section that probably just ignores situations when something other than those units enumerated here is killed?

    if loser.owner == tribeAliases.USN then

    if loser.type == unitAliases.BurningYorktown or loser.type == unitAliases.BurningHornet or loser.type == unitAliases.BurningEnterprise then
    incrementCounter("JapanScore",specialNumbers.japanScoreIncrementSinkCarrier)
    elseif loser.type == unitAliases.RaiderC or loser.type == unitAliases.RaiderD or loser.type == unitAliases.MarineRifle22
    or loser.type == unitAliases.MarineRifle23 or loser.type == unitAliases.FuelTanks then
    incrementCounter("JapanScore",specialNumbers.japanScoreDamageMidway)
    end
    end
    if loser.owner == tribeAliases.IJN then
    if loser.type == unitAliases.BurningAkagi or loser.type == unitAliases.BurningKaga or loser.type == unitAliases.BurningSoryu or loser.type == unitAliases.BurningHiryu then
    incrementCounter("AmericaScore",specialNumbers.usaScoreIncrementSinkCarrier)
    end
    end
    return replacementUnit
    end
     

    Attached Files:

  15. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    You haven't 'linked' the state table to log.lua.

    In Events.lua, you want something like
    Code:
    state.logState= state.logState or {}
    log.linkState(state.logState)
    
    Search for linkState in events.lua. There is simpleReactions.linkState, which should provide an example of what you need to do, and where you need to put it.

    "Attempt to index a nil value" means that you are trying to get a table value from something that isn't a table (usually a nil). A common way for this to happen is if you have nested tables, but you haven't initialized each 'layer' of the nest.

    E.g.
    myNestedTable={}
    myNestedTable[1][2]=12

    This won't work, because myNestedTable[1] is not a table, it is a nil.
     
    JPetroski likes this.
  16. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    Thank you, I got it working with your advice.
     
  17. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    This is throwing out when I believe an AAA gun should be reacting. It doesn't seem to be an issue when I test in a save file for single player "testing" but is an issue in a hotseat game.

    Code:
    169, 129, 0
    110, 124, 0
    table
    160, 128, 0
    D:\Test of Time\Scenario\Midway\reactionBase.lua:298: attempt to compare number with userdata
    stack traceback:
        D:\Test of Time\Scenario\Midway\reactionBase.lua:298: in function 'reactionBase.clearReactionsIfNecessary'
        D:\Test of Time\Scenario\Midway\reactionBase.lua:313: in function 'reactionBase.getReactionsMade'
        D:\Test of Time\Scenario\Midway\simpleReactions.lua:554: in upvalue 'canReact'
        D:\Test of Time\Scenario\Midway\reactionBase.lua:73: in local 'getReactionUnits'
        D:\Test of Time\Scenario\Midway\reactionBase.lua:202: in function 'reactionBase.reactionEngine'
        D:\Test of Time\Scenario\Midway\simpleReactions.lua:694: in function 'globalDoReaction'
        D:\Test of Time\Scenario\Midway\events.lua:1163: in function <D:\Test of Time\Scenario\Midway\events.lua:995>
    
    It kind of looks like it might be an issue with the unit id's?
     
  18. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    Here's a fix (I hope).

    You will have to start a new game for this fix to work. In state, I stored a 'tribe' instead of a tribe ID number, which caused the error.

    I haven't tested the fix, but I'm 99% sure it will work.
     

    Attached Files:

  19. JPetroski

    JPetroski Deity

    Joined:
    Jan 24, 2011
    Messages:
    2,457
    This threw out after the first turn in which there were reactions by the Japanese civ. It did not show up after there were reactions by the U.S. civ. Japan goes first and the U.S. go second in a turn.

    Code:
    D:\Test of Time\Scenario\Midway\reactionBase.lua:303: attempt to index a nil value
    stack traceback:
        D:\Test of Time\Scenario\Midway\reactionBase.lua:303: in function 'reactionBase.clearReactionsIfNecessary'
        D:\Test of Time\Scenario\Midway\simpleReactions.lua:700: in function 'simpleReactions.doAfterProduction'
        D:\Test of Time\Scenario\Midway\events.lua:2210: in upvalue 'afterProduction'
        D:\Test of Time\Scenario\Midway\events.lua:2388: in function <D:\Test of Time\Scenario\Midway\events.lua:2332>
    D:\Test of Time\Scenario\Midway\reactionBase.lua:303: attempt to index a nil value
    stack traceback:
        D:\Test of Time\Scenario\Midway\reactionBase.lua:303: in function 'reactionBase.clearReactionsIfNecessary'
        D:\Test of Time\Scenario\Midway\simpleReactions.lua:700: in function 'simpleReactions.doAfterProduction'
        D:\Test of Time\Scenario\Midway\events.lua:2210: in upvalue 'afterProduction'
        D:\Test of Time\Scenario\Midway\events.lua:2388: in function <D:\Test of Time\Scenario\Midway\events.lua:2332>
    
     
  20. Prof. Garfield

    Prof. Garfield Deity Supporter

    Joined:
    Mar 6, 2004
    Messages:
    2,635
    Location:
    Ontario
    Try this.
     

    Attached Files:

Share This Page