The Battle of Midway - Development Thread

think you were trying to give me breadcrumbs when you posted the following, but I'm pretty sure a bird landed on my head, ate all the crumbs, and pecked half my brain out with it...

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
alternateArmament={}
alternateArmament[unitAliases.kateBomb.id]=unitAliases.kateTorp
alternateArmament[unitAliases.kateTorp.id]=unitAliases.kateBomb

For whatever reason I just can't follow a table that looks like the above. I used a different style. This is as far as I've gotten tonight and I think my head is hurting too much to go further... At least it's a start.
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.
 
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.

You can use comments to help you keep track of what is going on.
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).
 

Attachments

  • MidwayEvents1-12.zip
    20 KB · Views: 208
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?

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)

But, from looking at the WildeSau code from OTR, I'm pretty sure I actually need it to say:
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.)

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.

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.)
 
: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

Are you trying to include a check for re-armament for units operating from an airbase on Midway? In that case, you want...

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.
 
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?
 
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.
 
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.
 
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.
 
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.
 

Attachments

  • MidwayReaction.zip
    2.1 MB · Views: 211
Thank you! When I get a few hours to myself later I'll play around with this.

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.

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?
 
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:
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
 

Attachments

  • eventquestion.zip
    28.6 KB · Views: 211
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.
 
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?
 
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.
 

Attachments

  • reactionBaseFix.zip
    3.9 KB · Views: 218
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>
 
Top Bottom