The Battle of Midway - Development Thread

Hi John, I think lines 54 and 62 of the code you posted are incorrect -- you need to repeat loser.type == before each of the unitAliases.* references, not just the first one. Compare those lines to lines 56 and 57, which are written correctly.

Thank you - that was definitely part of it. I also had to move the relevant code under "civ.scen.onUnitKilled(function (loser, winner)" to get it to work. I had placed it earlier in the code in error. It's working now. I appreciate the help!
 
I continue to have some issues with two events (well, several, but for starters)... Not sure what has changed between November and now, but the counter doesn't work. (Edit - I have the unitAliases correct but didn't want to clog things up posting all of them).

Counter Issue
-Here are the three parts I believe I need for this to work, yet it does not.

This is added towards the top of the events:

Code:
local specialNumbers ={}
specialNumbers.japanScoreIncrementSinkCarrier = 50
specialNumbers.usaScoreIncrementSinkCarrier = 50
specialNumbers.japanScoreDamageMidway = 10


local function initializeFlagsAndCounters()

createCounter("AlliedScore",0)
createCounter("JapanScore",0)
createFlag("AfterProdTribe0NotDone",true)
createFlag("AfterProdTribe1NotDone",true)
createFlag("AfterProdTribe2NotDone",true)
createFlag("AfterProdTribe3NotDone",true)
createFlag("AfterProdTribe4NotDone",true)
createFlag("AfterProdTribe5NotDone",true)
createFlag("AfterProdTribe6NotDone",true)
createFlag("AfterProdTribe7NotDone",true)

end

initializeFlagsAndCounters()

This part does work:

Code:
local function displayScore()
    scoreTable = civ.ui.createDialog()
    scoreTable.title = "The First to 200 Points Wins"
    scoreTable:addText(func.splitlines("The American Score is "..tostring(counterValue("AlliedScore")..".")))
    scoreTable:addText(func.splitlines("\n^The Japanese Score is "..tostring(counterValue("JapanScore")..".")))
    scoreTable:show()
end

This doesn't seem to work:

Code:
civ.scen.onUnitKilled(function (loser, winner)

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("AlliedScore",specialNumbers.usaScoreIncrementSinkCarrier)
        end
    end

end)

Edit - my second issue was delayed reinforcements but I figured out that the problem was I had the events in the afterProduction event (which is where the initial state set had to be to enable the pop up for player 2 in the MP game) rather than in the onTurn where it needed to be. There may be hope for me yet!
 
Last edited:
One ease of life question before I do something 102 times... Is there an easier way to write a code where each turn a specific, sequential text box will pop up? I have multiple "time" text boxes for this scenario that I want to fire each turn. I assume string could be used but I'm not sure how to set things up to use it. I can get these events working the burdensome way but it would be a neat thing to learn how to avoid.

I want to assign one of each of these lines to a corresponding turn (so Time2 is turn 2, time3 is turn 3)
Code:
textAliases.Time2 = [[4:45 a.m.]]
textAliases.Time3 = [[5:00 a.m.]]
textAliases.Time4 = [[5:15 a.m.]]

Then, I want to fire them onProduction for each player so they'll show up for both sides in a MP game (I suppose it's not probably necessary to use afterProduction for Japan as they are player 1, but why write two things if one thing could suffice?):

Code:
if tribe == tribeAliases.USN and turn == 2 then
    civ.ui.text(func.splitlines(textAliases.Time2))
                       
    end

If this is something I should be able to figure out with a posted lesson already please feel free to say so... I've only read through the afterProduction one so far (I'm negligent, I know).
 
I need someone to pat me on the back and say "good job" :D

While this is not original code, this is the most complicated piece of reworking I have yet been able to accomplish and I'm pretty pleased with it. I took @Prof. Garfield 's code from Over the Reich where a player can choose to combine two battered groups into one full strength battle group. I then tweaked it so that the B5N2 Kate's can rearm with torpedos or bombs at the cost of all their movement points. I made it so this can only take place on an aircraft carrier, but so the aircraft carrier will not be deleted. Units will retain their veteran status. I was also able to make it so that two different types of carriers work (no reason that the third and fourth won't either).

This was really giving me trouble in the main events file so I decided to take a cue from @SorFox and just make a small events file (I actually copied his) and try to exclusively get this to work. This seems to be a good way to go about trying something new, as it is less complicated and less likely to destroy a working file.

There is likely superfluous code in here (and I wouldn't mind a lesson about what more I could cut) but I have confirmed that this exact code works for my precise requirements and I'm thrilled that I could finally get this to work. I still don't think I could actually make "brand new" code from the ground up, but I am pleased that I was able to tweak this in (what I consider anyway) to be a fairly significant fashion.

If I can get the counter working, I think I have all the events that I "need" for this scenario to work, although there are a few quality of life mechanics I still want to try and import from OTR.

Edit - of course now I check and see if multiple kates will rearm if there is more than one, and only the first one will. While the first one will have its movespent, the next active unit doesn't also rearm. I'll have to look into this more.
 

Attachments

  • events.zip
    2.9 KB · Views: 287
Last edited:
One ease of life question before I do something 102 times... Is there an easier way to write a code where each turn a specific, sequential text box will pop up? I have multiple "time" text boxes for this scenario that I want to fire each turn. I assume string could be used but I'm not sure how to set things up to use it. I can get these events working the burdensome way but it would be a neat thing to learn how to avoid.

Divide the turn by 4 and discard the fractional part (math.floor, or divide using // instead of /). That will give you your hour time (after you add 4 or 5 to it).

Then, divide the turn by 4 and take the remainder (using %, e.g. 11%4 == 3, 12%4==0), that will tell you if you need to append "00", "15","30" or "45" to the string.

If you want to use AM, PM, then you'll use hour%12.

Sorry, but I'll have to take a look at the other stuff later. My family seems to have caught 2 different colds over the holidays, so I'm once again sick and somewhat tired at the moment.
 
Sorry, but I'll have to take a look at the other stuff later. My family seems to have caught 2 different colds over the holidays, so I'm once again sick and somewhat tired at the moment.

No worries - hope you feel better. When you are feeling better if you can help me figure out why only one bomber will rearm in the events I attached, I'd appreciate it. I'm not sure what I've done differently than you in the code, but somehow I messed it up.
 
Here's another bit of code I'm having some trouble with. I'm trying to modify the distance to airfield mechanism to show distance to carrier.

Code:
local function OTRUnitTextFunction(unit)
    local unitHPofMax = tostring(unit.hitpoints).." of "..tostring(unit.type.hitpoints).." Hit Points remaining."
    local nearestAirfield = nil
    local distanceToAirfield = 1000
    local nearestCarrier = nil
    local distanceToCarrier = 1000
   
    local function distance(tile1,tile2)
           return math.ceil((math.abs(tile1.x-tile2.x)+math.abs(tile1.y-tile2.y))/2)
        end
  for carrier in civ.iterateUnits() do
    if carrier.owner == unit.owner and civ.getUnitType(28,30,32,34) and 
        distance(carrier.location,unit.location) < distanceToCarrier then
            nearestCarrier = carrier
            distanceToCarrier = distance(carrier.location,unit.location)
    end

end 
   local nearestCarrierText = " "
    if unit.type.domain == 1 then
        nearestCarrierText = "No friendly aircraft Carrier found."
        if nearestCarrier then
            nearestCarrierText = "Nearest aircraft carrier is "..nearestCarrier.unitType.." at a distance of "..tostring(distanceToCarrier).."."
        end
    end 
    return unitHPofMax.."  "..nearestAirfieldText
end

I'm not sure why this is throwing a nil value when I've defined carrier up above as civ.getUnitType (28,30,32,34)

D:\Test of Time\Scenario\Midway\events.lua:117: attempt to concatenate a nil value (field 'unitType')
stack traceback:
D:\Test of Time\Scenario\Midway\events.lua:117: in local 'unitTextFunction'
D:\Test of Time\Scenario\Midway\helpkey.lua:63: in function 'helpkey.helpKey'
D:\Test of Time\Scenario\Midway\events.lua:150: in function <D:\Test of Time\Scenario\Midway\events.lua:148>
 
Code:
nearestCarrierText = "Nearest aircraft carrier is "..nearestCarrier.unitType.." at a distance of "..tostring(distanceToCarrier).."."

Should probably be
Code:
nearestCarrierText = "Nearest aircraft carrier is "..nearestCarrier.type.name.." at a distance of "..tostring(distanceToCarrier).."."

However, there is another issue:

Code:
if carrier.owner == unit.owner and civ.getUnitType(28,30,32,34) and
It looks like you want to check if the 'carrier' actually is a carrier unit. However, civ.getUnitType(id) is a function that returns a unitType object (it will ignore any additional function arguments).

In practice, civ.getUnitType(28,30,32,34) will just be 'true' for the purposes of this if statement, and so won't do anything. You will end up finding the nearest friendly unit.

There might be a function to check if a unit has the carrier flag in the General Library (it is planned, but might not have been implemented yet, but in any case, the tools to check the relevant flag are already implemented).

Or, you can have a table

carrierUnits={}
carrierUnit[unitAliases.MyCarrierUnit.id]=true

and then replace

civ.getUnitType(28,30,32,34) with

Code:
carrierUnit[carrier.type.id]

Also, you'll want to fix this.

Code:
return unitHPofMax.."  "..nearestAirfieldText
should be changed to nearestCarrierText
 
Counter Issue
-Here are the three parts I believe I need for this to work, yet it does not.

I think I'll need the actual events to diagnose this.

While this is not original code, this is the most complicated piece of reworking I have yet been able to accomplish and I'm pretty pleased with it. I took @Prof. Garfield 's code from Over the Reich where a player can choose to combine two battered groups into one full strength battle group. I then tweaked it so that the B5N2 Kate's can rearm with torpedos or bombs at the cost of all their movement points. I made it so this can only take place on an aircraft carrier, but so the aircraft carrier will not be deleted. Units will retain their veteran status. I was also able to make it so that two different types of carriers work (no reason that the third and fourth won't either).

I think you've made life way more difficult for yourself by trying to convert that piece of code than trying to write from scratch. There's a lot of peculiar stuff in there due to the odd facts of the situation (especially, since there was no fixed ratio between depleted battle groups and battle groups, just one less).

Write a function to re-arm one plane at a time. Call this silentReArm Basically, if the plane can be re-armed, create a new plane of the new type, copy veteran status and other attributes, expend its movement, and delete the original plane. If you have a table

Code:
 alternateArmament={}
alternateArmament[unitAliases.kateBomb.id]=unitAliases.kateTorp
alternateArmament[unitAliases.kateTorp.id]=unitAliases.kateBomb

you can use a check along the lines of
Code:
silentReArm(reArmUnit)
if alternateArmament[reArmUnit.type.id] and reArmUnit.moveSpent == 0 and carrierOnTile(reArmUnit.location) then
and then use the information from the same table to make the replacement. If the unit can't be re-armed, the alternateArmament value will be nil, and so will be false. You'll have to write a carrierOnTile function, also. Silent re-arm is good enough if you want to re-arm the active unit via key press and not bother the player about questions.

If you want to ask the player for confirmation, or offer the option to do all planes at once, then write textReArm:
Code:
textReArm(selectedUnit)
-- ask player if they want to re-arm all the units or just the active one
-- if just the active one then, 
silentReArm(selectedUnit)
-- otherwise
for unit in selectedUnit.location.units do
if unit.type == selectedUnit.type then
silentReArm(unit)
end

No worries - hope you feel better. When you are feeling better if you can help me figure out why only one bomber will rearm in the events I attached, I'd appreciate it. I'm not sure what I've done differently than you in the code, but somehow I messed it up.

Code:
    local newPlaneToCreate = math.min(carriersOnTile,planeOnTile) --There was originally a -1 at the end of planeOnTile but I think removing that removes the need to have 2 planes there for this to work
Since there is only one carrier on the tile, only one new plane will be created. This line was there because a train is used up in the conversion from a depleted battle group to a full one. You don't need the math.min here, just the planeOnTile (though you should check what happens if you have both kinds of planes on the carrier at the same time. I haven't looked closely enough to know for sure, but I think they'll all have their movement expended.
 
I might take a stab at trying to just rewrite some stuff as you suggested. I spent about 3 hours converting the reaction code. I got it working by forcing through a ton of stuff that isn't necessary. At the very least, it was a helpful exercise in that it reinforced some concepts and helped me understand others, but the code is now unwieldy. I do have a "working" events file that has what I need to properly playtest as a result, however, which is helpful.
 
It looks like you want to check if the 'carrier' actually is a carrier unit. However, civ.getUnitType(id) is a function that returns a unitType object (it will ignore any additional function arguments).

This worked great - thank you!
 
This is really hard. It's tough sticking with something this difficult for several hours but at least I'm able to work out some issues with all of your help.... I must have spent about 10 or 12 hours over this week trying to get the hang of things and am not making good progress, but at least I'm making a bit!

I was able to get the counters working finally. I didn't use your counter module @Prof. Garfield because I'm not quite there yet with being able to read those and fully understand them, but I did read through it and it helped me understand the issue I was having. So, it was helpful and it got the job done. I also re-read your first two lessons today, which helped as well. I don't want to give you the impression that I'm not trying to grasp this stuff and just running off on my own like a crazy person but you're dealing with someone with 1 year of math... Reverse engineering seems to be the most likely way I can achieve things, or get them close, at least for now.

On the very plus side of things, I am now down to just two events from being able to complete this scenario... The downside is that could mean weeks :lol:

1. I need to get the KateTorp / KateBomb reload mechanism working. I wasn't able to piece that together yet with what you offered, though I'll give it a shot;
2. I need to decide what to do about reactive attacks. I got them working by throwing a ton of code in that simply didn't need to be there (clouds, high alt stuff, area damage, etc.) but then I tried cutting out some stuff and couldn't get it to work. All this scenario needs is something simple where one unit reacts to another unit when a key is pressed. There's no dive, no clouds, no radar-guided munitions necessary, etc. I'm nearly thinking its needs are so less complex that it might just make more sense to attempt to rewrite entirely, not that I'm very likely to do well with that!

Anyway, this brave new world makes creation interesting!
 
I'm pretty sure I told you not to try to use the reaction code in OTR. There were good reasons for that. One being that I was learning at that time, and the other being that most of OTR was written with one more feature syndrome, so I wanted to complete it and get things to work, and I wasn't thinking too carefully about re-use (and I didn't have quite the experience to do that well anyway), so there are likely places with OTR specific stuff.

Now that I'm not spending 45 minutes to an hour per day playtesting, I'll hopefully be able to write a more portable reaction code in relatively short order.

I was able to get the counters working finally. I didn't use your counter module @Prof. Garfield because I'm not quite there yet with being able to read those and fully understand them, but I did read through it and it helped me understand the issue I was having. So, it was helpful and it got the job done. I also re-read your first two lessons today, which helped as well. I don't want to give you the impression that I'm not trying to grasp this stuff and just running off on my own like a crazy person but you're dealing with someone with 1 year of math... Reverse engineering seems to be the most likely way I can achieve things, or get them close, at least for now.

I think you'll make much more progress and be much less frustrated if you go through my lessons and do the practice problems I suggest. Maybe reverse-engineer that code, where I actually explain what it is supposed to do. If you already understand the material, it shouldn't take too long to do the problems. If you don't, then the practice will be worthwhile.

Think of it like driving. There are two parts. One part of driving is actually controlling the vehicle. The other part is knowing where to drive. You can reasonably expect to learn where to go from the passenger's seat of a car, but few people would expect to become competent drivers just by watching someone else drive. You need to spend some time in a parking lot or on the side streets. OTR code is like watching someone drive two blocks from where you want to start to three blocks from where you want to finish, and running several errands along the way, during rush hour.

Re-using code is great if you only have to change a couple lines, or better still, just 'wrap' your own problem around it. It's not so useful if you have to go through the program line by line trying to figure out if something needs to be changed.
 
I'm pretty sure I told you not to try to use the reaction code in OTR.

Yes, you did. You were right, and I should have listened. On the plus side, I am more aware of what is in that code in case changes (to tables and effectiveness) ever need to be made.

Now that I'm not spending 45 minutes to an hour per day playtesting, I'll hopefully be able to write a more portable reaction code in relatively short order.

I will wait for this.

I think you'll make much more progress and be much less frustrated if you go through my lessons and do the practice problems I suggest. Maybe reverse-engineer that code, where I actually explain what it is supposed to do.

I will do this while I am waiting :)
 
Here's a video showcasing some of the development, if anyone is interested:

 
Can someone please take a peek at these events and help me troubleshoot a few delayed events? I know my counters aren't working and I think my states aren't working after a save and reload. I'm not sure what I'm doing wrong.

My counters will work until I save and load the game. Then, they reset. Also, my states have an issue:

Code:
if tribe == tribeAliases.USN and math.random(1, 4) == 4 and state.USNTaskForce17Decision==1 then

The purpose of the event is to randomly spawn Task Force 17 with a 25% chance each turn. If I sit down and just go through several turns in hotseat mode without saving and loading, it works. I have yet to see it fire after saving and loading, but suspect something is wrong in the same sense that the counters are.

This is in my code:

Code:
civ.scen.onLoad(function (buffer)

state.flags = state.flags or {}
state.counters = state.counters or {}
initializeFlagsAndCounters()
state.reactions = state.reactions or {}
state.formationTable = {}
state.formationFlag = false
state.mostRecentMunitionUserID = state.mostRecentMunitionUserID or 0
state.Japan2ndFleetDecision = {}
state.USNTaskForce17Decision = {}
state.USNTaskForce16Decision = {}
end)


I don't have these two lines in my code (they are in OTR), but I don't have them because I think they don't matter for my purposes (I'm not quite sure what lualzw does however).

Code:
civ.scen.onScenarioLoaded(function ()
    --setSubQualities()
    civ.ui.centerView(civ.getTile(407,1,0))
    setSubFlag()
    if civ.getActiveUnit() then
        harbourUnitActivationFunction(civ.getActiveUnit())
    end
    --require("makeCSV")

end)

civ.scen.onSave(function ()
--    return civlua.serialize(state)
    return lualzw.compress(civlua.serialize(state))
end)
 
You have to have civ.scen.onSave() present in your code -- this is what writes the state table into the saved game file. Without it, all your state entries are lost and you start fresh when you reload that saved game.

I think lualzw.compress is something Prof. Garfield was experimenting with in OTR to keep saved game files smaller. He could explain further I'm sure. But you can delete that line, and uncomment the one above it that says return civlua.serialize(state) to revert to more of a standard onSave() trigger that should work just fine.
 
Thank you - I didnt realize that I needed that line if I wasn't going to have any code "beneath/within it."

Appreciate it!
 
I think lualzw.compress is something Prof. Garfield was experimenting with in OTR to keep saved game files smaller. He could explain further I'm sure. But you can delete that line, and uncomment the one above it that says return civlua.serialize(state) to revert to more of a standard onSave() trigger that should work just fine.

This is correct. We have some pretty big tables in Over the Reich, mostly to do with the weather system. Lualzw is a module I found online to compress and uncompress the string representing that data. I think it saves something like 100kB (maybe 400) of data in a save, but I don't really remember. In any case, I don't think it would be of much benefit for Midway, though it won't hurt anything if you have both the compress and uncompress lines.

It also lookes like you took state = civlua.unserialize(buffer) out of onLoad. You'll need that to get the state table back.
 
This scenario is probably not going to have a rearming function :lol: I think this is a little complicated of an event for me to complete on my own. I have gone through four lessons now. I get very limited things. I can follow this well:

Code:
local barbarians = civ.getTribe(0)
local archers = civ.getUnitType(4)
local horsemen = civ.getUnitType(15)

local barbTable = {
{location = {44,12}, unit = archers},
{location = {35,39}, unit = horsemen},
{location = {64,16}, unit = horsemen},
}-- close barbTable

for __, barbCreate in pairs(barbTable) do
   local tile = civ.getTile(barbCreate.location[1],barbCreate.location[2],0)
    if tile.defender == barbarians or tile.defender == nil then
        civ.createUnit(barbCreate.unit,barbarians,tile)
    end
end

I can even follow this pretty well:

Code:
local desertTerrain = 0

for unit in civ.iterateUnits() do
   if unit.location.terrainType % 16 == desertTerrain then
        local newDamage = math.floor(unit.hitpoints/2)
        unit.damage = unit.damage+newDamage
    end
end

I get what you're doing in both of these, and they're pretty simple. I'm not having much luck writing this swap out function on my own from scratch. I can write a function that replaces one unit with another:

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

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

Code:
local ammoSwapUnitTypes = {
    ["KateBomb to KateTorp"] = { unitType=civ.getUnitType(43), unitSwappedTo=civ.getUnitType(42)},
    ["KateTorp to KateBomb"] = { unitType=civ.getUnitType(42), unitSwappedTo=civ.getUnitType(43)}
      
      }

Code:
local currentUnit = civ.getActiveUnit()
local currentTileCoordinates = { {currentUnit.location.x, currentUnit.location.y, currentUnit.location.z } }
          
      
        for ____, newUnitAmmo in pairs(ammoSwapUnitTypes) do
          
            if newUnitAmmo.unitType.id == currentUnit.type.id then
                  
                if newUnitAmmo.unitSwappedTo ~= nil then
                        civlua.createUnit(newUnitAmmo.unitSwappedTo, currentUnit.owner, currentTileCoordinates)
                        civ.deleteUnit(currentUnit)
                      
                end
            end
        end
 
Top Bottom