The Hinge of Fate - Development Thread

Finally, you may have already indicated so but I'm uncertain, will there be summer/winter seasons?

Yes, there is summer and winter. They mostly just affect offroad conditions though, making the road system all the more important/strategic. As usual, the airplane movement necessitates a lower road multiplier (2) and reducing it to 1 makes things go far too slow. However, tanks and halftracks move quite fast, and halftracks can carry artillery and infantry with them, so if you invest in mechanization, you can move at a pretty good clip. If you don't, your foot soldiers plod along as in real life.

How about using lua to inflict hit points on any German, or Italian (provided it is at war), naval or submarine unit that is on an ocean tile within x number of tiles of an Allied carrier unit. For example:
  • Any Axis surface vessel that starts the turn on an ocean tile and is say within 8 tiles of a carrier could randomly suffer between 10 to 30 hit points damage
  • Any Axis submarine unit that is within 5 tiles of a carrier could suffer 0 to 20 hit points damage to reflect the more stealthier mode (Type XXII submarines could be even harder to hit).
Distance and damage could also be changed based on the season (higher distance and damage rates in summer versus winter time).

I seem to remember that Kobyashi handled AI carriers pretty well in his Pacific scenario. They had no planes, but a high attack factor, and doubled against air attack. Could see subs. Something like that. The map scale was quite a bit different though.

There's a few different ways it could go. I had an idea where a carrier senses there are surface units near it, and then it launches planes towards them. The planes are then deleted after they attack or at the end of the turn (whichever happens) so the map isn't filled with them. That's beyond my lua skill set though. I do think an idea along those lines is useful but I'd almost be tempted to have a separate thread just entitled, "what to do about aircraft carriers" to ask the coders for help after the designers settle on what is the best method, as everyone is in agreement that the base game CVs don't work, thus this is a fix that is needed by all, thus it is a module that I wouldn't feel bad asking for. Really, we should have a thread that simply discusses "shortcomings of the base game" to see what can be fixed with lua that hasn't already.
 
As usual, the airplane movement necessitates a lower road multiplier (2) and reducing it to 1 makes things go far too slow.

You could use onActivateUnit to change the movement multipliers based on whether an air, land, or sea unit is active. That would give you greater range for your air units without sacrificing the road multiplier.

For human players, you could also replenish movement on a key press. I did this for ships in A Soaring Spirit. Suppose an aggregate multiplier of 6. At the start of the turn, expend 1 movement point from each relevant unit. So, a ship with 21 mp has 20 5/6 movement points, and spends them to 19 5/6, 18 5/6, etc. At some point, the player presses a key, and the ship's movement is increased to 20 4/6. Further key presses restore movement to 20 3/6, 20 2/6, 20 1/6, and 20. Once there is no fractional movement point left, movement can't be restored.
 
You could use onActivateUnit to change the movement multipliers based on whether an air, land, or sea unit is active. That would give you greater range for your air units without sacrificing the road multiplier.

So basically if it's a domain 0 unit (land) I can have the roads do 4, or whatever? That might be helpful indeed. Thanks for the tip!
 
Hi John,

Just a few follow up questions:

Yes, actually. I've playtested the Battle of France, Battle of Britain, and Operation Sea Lion 4-5 times now. It is absolutely possible to conquer France in 3 turns (you only need to capture Paris, Cherbourg, and Brest - much like the victory requirements you have in Napoleon). However, you may find it challenging to conquer France that quickly and annihilate the BEF at Dunkirk during the same time (and you must - you only have 3 turns to destroy the BEF or else England gets bonuses that make Sea Lion much more difficult).

Based on your comments above there's clearly a consequence for not defeating the BEF within 3 turns. As such as a German player wouldn't I want to prioritize this objective first before trying to defeat France?

In essence, is there a similar consequence for not defeating France within the same time frame, either in the form of having to deal with extra French reinforcements or Vichy colonies going Free French after the downfall of France?

Yes, there is summer and winter. They mostly just affect off-road conditions though, making the road system all the more important/strategic.

Does Germany suffer from any kind of penalty during it's first winter in Russia (maybe similar to the attrition effect in Napoleon)?

... However, tanks and halftracks move quite fast, and halftracks can carry artillery and infantry with them, so if you invest in mechanization, you can move at a pretty good clip. If you don't, your foot soldiers plod along as in real life.

I'm very intrigued by this part. Did you implement SorFox's "Lua code snippet to use land units as a transporter" (located here) or some variant of it?

Though very interesting, I know that it had some issues, like the transported units were only identified by their ID's rather then their unit names (which made it difficult to determine exactly which unit your transporter was carrying).

There was also the issue, once you loaded a unit it was "teleported" to a secondary map and put in sleep mode. If the transport unit itself was destroyed while carrying the unit the teleported unit still remained on the secondary map.

156 Unit Slots & Over 500 skins
You didn't read that wrong - there are 156 unit slots "of sorts" in this scenario. No, they aren't simply reskins. With lua's ability to transform, delete, and teleport units, I've brought minor nations to life for the short time they exist and managed to reuse slots with multiple nations. Further, I've done my best to make Patine's requests not go down in vain by using as many skins as possible throughout the scenario from our great artists.

I'll be very intrigued to see how that works out.

BTW, do you recall if @TheNamelessOne ever indicated whether 127 units slots was the maximum number of units that could be added through ToTPP? If not we could ask him if it's possible to increase that by a few extra rows (144 or 157).
 
Last edited:
Based on your comments above there's clearly a consequence for not defeating the BEF within 3 turns. As such as a German player wouldn't I want to prioritize this objective first before trying to defeat France?

In essence, is there a similar consequence for not defeating France within the same time frame, either in the form of having to deal with extra French reinforcements or Vichy colonies going Free French after the downfall of France?

France starts with an extremely powerful army that is hard to encircle. This scenario uses Knighttime's supply lines module. You can cut the Brits off from supply easily enough by encircling them near Dunkerque. This effectively takes them out of the fight until they ship out or are destroyed. Concentrating on destroying them will make it easier to attack England, but it also means you aren't dealing with the (now) more pertinent threat of a powerful France with good tanks, sizeable artillery and air force that can attrite you more than you'd like. So, depending on how the AI moves and where you place what unit, they both have issues.

I'd say that what you concentrate on depends on your strategy. If I was going to try and force Sea Lion early, I'd definitely take out the BEF. However, if I was planning on letting England stay back so I could really maximize my efforts towards Russia, I wouldn't.

Does Germany suffer from any kind of penalty during it's first winter in Russia (maybe similar to the attrition effect in Napoleon)?

Yes, there are problems in the first winter for Germany - a little bit different than the attrition mechanism you used, but the bottom line is it gets hard to prosecute the war effort the first winter. Think of things like units randomly not moving (frozen engines), units randomly taking attrition, units randomly triggering local counter attacks, etc. It won't be easy.

I'm very intrigued by this part. Did you implement SorFox's "Lua code snippet to use land units as a transporter" (located here) or some variant of it?

Though very interesting, I know that it had some issues, like the transported units were only identified by their ID's rather then their unit names (which made it difficult to determine exactly which unit your transporter was carrying).

There was also the issue, once you loaded a unit it was "teleported" to a secondary map and put in sleep mode. If the transport unit itself was destroyed while carrying the unit the teleported unit still remained on the secondary map.

I did implement Sorfox's mechanism. I have noticed the difficulty in determining the unit names though I wonder if that isn't possible to fix. I haven't tried fixing it yet. The units do go to a secondary map where I suppose they will remain if the halftrack is damaged, but, it's an acceptable issue in my mind. The functionality of the half tracks is fun and useful, especially given the way one can encircle enemies - you want to be able to break past enemy lines with your panzers and ring large concentrations of enemy troops. I'd rather have that gameplay mechanism than worry too much about some extra units hanging around.

BTW, do you recall if @TheNamelessOne ever indicated whether 127 units slots was the maximum number of units that could be added through ToTPP? If not we could ask him if it's possible to increase that by a few extra rows (144 or 157).

As far as I recall, he had said we are limited to 127 for most of everything for some programming reason, unfortunately. I think he mentioned that if he ever increased the technological advances, they too would be capped at 127, but this is from memory, and several years ago, so who knows if it remains true. I thought it had something to do with the mathematical way civ2 was designed.
 
I did implement Sorfox's mechanism. I have noticed the difficulty in determining the unit names though I wonder if that isn't possible to fix. I haven't tried fixing it yet. The units do go to a secondary map where I suppose they will remain if the halftrack is damaged, but, it's an acceptable issue in my mind. The functionality of the half tracks is fun and useful, especially given the way one can encircle enemies - you want to be able to break past enemy lines with your panzers and ring large concentrations of enemy troops. I'd rather have that gameplay mechanism than worry too much about some extra units hanging around.
Isn't it simply a table of table with [transporterid (Key), transportedid1, transportedid2 ...] ?

Then one could simply check for transpoterids when a unit is destroy to clean other units (or do other stuff, like leaving some barely alive at the place of the transporter) ?
Same way, one could assign movepoint to every transportedid defined units at each beginning of turn to lock them ?
 
Have you ever seen Kelly's Heroes? Per Oddball: "I just drive 'em, I don't know what makes 'em run" lol. But here is the code:

Spoiler :

Code:
-- UNIT TRANSPORTER
-- [[ By Sorfox]]
local transport = {}

local gen = require("generalLibrary")
--state.loadedTrucks = {}                                                        -- we have a nested table that holds following data: loadedTrucks[truck_ID] = {loadedUnit_ID1, loadedUnit_ID2, ...}
--
local activeTruck = nil                                                             -- a global variable is defined

local state = "stateTableNotLinked"

-- 'traditional' link state system
function transport.linkState(stateTable)
    state = stateTable
    state.loadedTrucks = state.loadedTrucks or {}
end

-- link state not requiring access to onLoad
function transport.putInScenarioLoaded()
    state = gen.getState()
    state.loadedTrucks = state.loadedTrucks or {}
end



function transport.firstTimeFakeLoadingOnTruck()
    print("Within the function: firstTimeFakeLoadingOnTruck.")
    local activeUnit = activeTruck                                                -- update the local variable (ID of the truck) with the data of the gloal variable, which was "filled" in the triggering functions (below) and which reference this function
    local unitsOnTruck = state.loadedTrucks[activeUnit.id]                         -- initialize local variable 'unitsOnTruck' [always first action in order to be able to use it] and connect it with exactly this truck; KEY for the state.table = activeUnit/currentUnit.id
    if unitsOnTruck == nil then
       print("This truck (ID: " .. activeUnit.id .. ") has never been loaded before, so we do it by means of Lua artificially.")
       unitsOnTruck = {[1] = "firstUnitFakeLoading", [2] = "secondUnitFakeLoading"}
       state.loadedTrucks[activeUnit.id] = unitsOnTruck
       print("Case ZERO - state.table successfully updated!")                -- just for checking if formula worked
    end
end                                                                            -- 'end' of function firstTimeFakeLoadingOnTruck()
local firstTimeFakeLoadingOnTruck = transport.firstTimeFakeLoadingOnTruck

-----------------------------------------------------------------------------------------------------------------------
function transport.loadUnitsOnTruck()
    print("Within the function: loadUnitsOnTruck.")
    local unitTypeAllowedToBeLoaded = civ.getUnitType(21)                        -- defines which unit(s) can be loaded on the truck -> modifiable (!) -> references the position of '@UNITS' in 'rules.txt' file
    local supplyTruckType1 = civ.getUnitType(26)                                  -- defines which unit(s) count as 'truck' (= land transporter / carrying vehicle) -> modifiable (!) -> references the position of '@UNITS' in 'rules.txt' file
    local supplyTruckType2 = civ.getUnitType(118)
 
    local transportVehicleType = {}
    transportVehicleType[26]=true
    transportVehicleType[118]=true
 
    local cargoUnitType = {}
    cargoUnitType[1]=true
    cargoUnitType[27]=true
    cargoUnitType[37]=true
    cargoUnitType[24]=true
    cargoUnitType[45]=true
 
 
    local successfullFakeLoading = 0                                            -- helper local variable (0 = false / 1 = true)
    local activeUnit = civ.getActiveUnit()
    local activeTile = nil
 
 
 
    if activeUnit then                                                             -- check that there is an active unit
       activeTile = civ.getActiveUnit().location                                 -- gets the active unit and selects the tile
    else                                                                         -- if there is no active unit, then
       print("No active unit.")                                                    -- 1) show this info in the console
       return                                                                    -- 2) and end the function, since there is no point in doing any further calculation
    end
    if not(cargoUnitType[activeUnit.type.id]) then                             -- if there is no unitTypeAllowedToBeLoaded /cagoUnitType on the tile then there is no point to continue the code -> Therefore: return
       print("Active unit is NOT a unitTypeAllowedToBeLoaded.")
       return
    end
    print("Active unit is a unitTypeAllowedToBeLoaded.")                    -- if this line is reached (not broken up before) there must be an active unit which is a unitTypeAllowedToBeLoaded
    for currentUnit in activeTile.units do                                     -- do a 'for loop' over every unit on the active tile, where (we already know) units of unitTypes allowed to be loaded exist
        --if currentUnit.type == supplyTruckType then
        if (currentUnit.type == supplyTruckType1 or currentUnit.type == supplyTruckType2) then
           print("loadFunction - checking the truck ID " .. currentUnit.id) -- if this line is reached (not broken up before) there must be an active unit equal to supplyTruckType
           local unitsOnTruck = state.loadedTrucks[currentUnit.id]            -- "fill" the local variable 'unitsOnTruck' with data about the loaded units (IDs) on exactly this truck; KEY for the state.table = currentUnit.id
           activeTruck = currentUnit                                        -- update the global variable, i.e. ID of the truck used right at this moment; since this funtion - in one case - jump to the funtion 'firstTimeFakeLoadingOnTruck()', where the ID is needed
            if unitsOnTruck == nil then                                        -- if this line is reached (not broken up before) there must be an active unit equal to supplyTruckType (=2nd condition) BUT it still can be a "virgin" truck, which will always result in code errors
               print("unitsOnTruck == nil | This truck (ID: " .. currentUnit.id .. ") has never been loaded before, so we do it by means of Lua artificially.")
               firstTimeFakeLoadingOnTruck()                                -- jump to and then execute this function
               successfullFakeLoading = 1                                    -- toggle local variable "on" = proof that each place of the truck is at least "fake loaded", since the line before 'function firstTimeFakeLoadingOnTruck()' did run
            end
            if unitsOnTruck ~= nil or successfullFakeLoading == 1 then        -- only now we can introduce the variables ('firstUnit'+'secondUnit') in the code, earlier there would be an error "attempt to index a nil value (global 'unitsOnTruck')" -> covered by 'firstTimeFakeLoadingOnTruck()'
               unitsOnTruck = state.loadedTrucks[currentUnit.id]
               activeTruck = nil                                            -- delete content of the global variable
               local firstUnit = unitsOnTruck[1]                            -- data of the first unit will be stored by means of their ID -> 'local' = initialize the variable within this function
               local secondUnit = unitsOnTruck[2]
                if firstUnit == nil or firstUnit == "firstUnitFakeLoading" then    -- if there is NO first - or only a "firstUnitFakeLoading" (which is also NO) - unit on the truck => LOAD IT!
                   civ.ui.text("Loading first unit on the truck.")
                   unitsOnTruck = {[1] = activeUnit.id, [2] = secondUnit}    -- 1st step of "table magic": update the locale variable 'unitsOnTruck' with the newest info about the activeUnit.id, which is now loaded in the table
                   civ.teleportUnit(activeUnit, civ.getTile(2, 184, 2)) -- code to teleport the currently loaded unit to a certain square on night map
                   activeUnit.order = 3                                        -- after teleportation to 2. map -> unit is set to 'sleep'
                   state.loadedTrucks[currentUnit.id] = unitsOnTruck        -- 2nd step of "table magic": update the state.table with the newest info -> which was stored in the locale variable table 'unitsOnTruck' - done during the 1st step
                   currentUnit:activate()                                    -- activates the truck, after loading the 1st unit, clearing its orders, and, if it has a human owner and movement points left, selects it on the map
                     break
                  elseif secondUnit == nil or secondUnit == "secondUnitFakeLoading" then -- checking if there is still space on the selected truck (secondUnit == nil, or "secondUnitFakeLoading"), if yes => LOAD IT!
                   civ.ui.text("Loading second unit on the truck.") 
                   unitsOnTruck = {[1] = firstUnit, [2] = activeUnit.id}    -- same procedure + code as with firstUnit, but of course here the 2nd unit must be stored in the table differently => [2] = activeUnit.id
                   civ.teleportUnit(activeUnit, civ.getTile(2, 184, 2))
                   activeUnit.order = 3
                   state.loadedTrucks[currentUnit.id] = unitsOnTruck
                   currentUnit:activate()                                    -- activates the truck, after loading the 2nd unit, clearing its orders, and, if it has a human owner and movement points left, selects it on the map
                     break
                elseif (firstUnit ~= nil and firstUnit ~= "firstUnitFakeLoading") and (secondUnit ~= nil and secondUnit ~= "secondUnitFakeLoading") then
                     civ.ui.text("The truck is full, due to the fact that it is only possible to load two units on it.") 
                end
            end
        end
    end
end                                                                            -- 'end' of function loadUnitsOnTruck()

-----------------------------------------------------------------------------------------------------------------------
function transport.unloadUnitsOffTruck()
    print("Within the function: unloadUnitsOffTruck.")
    local supplyTruckType1 = civ.getUnitType(26)                       
    local supplyTruckType2 = civ.getUnitType(118)
    local successfullFakeLoading = 0
    local activeUnit = civ.getActiveUnit()
    local activeTile = nil
 
    if activeUnit then                                                             -- check that there is an active unit
       activeTile = civ.getActiveUnit().location                                 -- gets the acive unit and selects the tile
    else                                                                         -- if there is no active unit, then
       print("No active unit.")                                                    -- 1) show this info in the console
       return                                                                    -- 2) and end the function, since there is no point in doing any further calculation
    end
 
 
 
    if (activeUnit.type ~= supplyTruckType1 and activeUnit.type ~= supplyTruckType2) then -- if there is no truck on the tile then there is no point to continue the code -> Therefore: return
       print("Active unit is NOT the supplyTruckType.")
       return
    else
       print("UN-loadFunction - checking the truck ID " .. activeUnit.id)     -- if this line is reached (not broken up before) there must be an active unit equal to supplyTruckType
       local unitsOnTruck = state.loadedTrucks[activeUnit.id]                -- "fill" the local variable 'unitsOnTruck' with data about the loaded units (IDs) on exactly this truck; KEY for the state.table = currentUnit.id
       activeTruck = activeUnit                                                -- update the global variable, i.e. ID of the truck used right at this moment; since this funtion - in one case - jump to the funtion 'firstTimeFakeLoadingOnTruck()', where the ID is needed
        if unitsOnTruck == nil then                                            -- if this line is reached (not broken up before) there must be an active unit equal to supplyTruckType (=2nd condition) BUT it still can be a "virgin" truck, which will always result in code errors
           print("unitsOnTruck == nil | This truck (ID: " .. activeUnit.id .. ") has never been loaded before, so we do it by means of Lua artificially.")
           firstTimeFakeLoadingOnTruck()                                    -- from that code line on -> each place of the truck is at least "fake loaded"
           successfullFakeLoading = 1
        end
        if unitsOnTruck ~= nil or successfullFakeLoading == 1 then            -- only now we can introduce the variables ('firstUnit'+'secondUnit') in the code, earlier there would be an error "attempt to index a nil value (global 'unitsOnTruck')" -> covered by 'firstTimeFakeLoadingOnTruck()'
           unitsOnTruck = state.loadedTrucks[activeUnit.id]
           activeTruck = nil                                                -- delete content of the global variable
           local firstUnit = unitsOnTruck[1]                                -- data of the first unit will be stored by means of their ID -> 'local' = initialize the variable within this function
           local secondUnit = unitsOnTruck[2]
            if (firstUnit == nil or firstUnit == "firstUnitFakeLoading") and (secondUnit == nil or secondUnit == "secondUnitFakeLoading") then    -- Case ONE: the respective truck is empty
               civ.ui.text("There are NO units to unload from this (ID: " .. activeUnit.id .. ") empty truck.")
               return
            elseif firstUnit ~= nil and firstUnit ~= "firstUnitFakeLoading" then
               local firstUnitComplete = civ.getUnit(firstUnit)
               civ.teleportUnit(firstUnitComplete, activeTile)                 -- code to teleport the currently loaded unit to exactly the same tile as where the truck stands (first map)
               firstUnitComplete:activate()                                    -- activates the unloaded unit, clearing its orders, and, if it has a human owner and movement points left, selects it on the map
               unitsOnTruck[1] = "firstUnitFakeLoading"                        -- removes the value (ID of the firstUnit) from the table; Be aware: We do the procedure NOT on variable 'firstUnit' since it's just used for easier reading the code - i.e. a representation of 'unitsOnTruck[1]' => the direct table entry, which we have to change here
               state.loadedTrucks[activeUnit.id] = unitsOnTruck                -- update the state.table
            elseif secondUnit ~= nil and secondUnit ~= "secondUnitFakeLoading" then
               local secondUnitComplete = civ.getUnit(secondUnit)
               civ.teleportUnit(secondUnitComplete, activeTile)             -- code to teleport the currently loaded unit to exactly the same tile as where the truck stands (first map)
               secondUnitComplete:activate()                                -- activates the unloaded unit, clearing its orders, and, if it has a human owner and movement points left, selects it on the map
               unitsOnTruck[2] = "secondUnitFakeLoading"                    -- removes the value (ID of the secondUnit) from the table; Be aware: We do the procedure NOT on variable 'secondUnit' since it's just used for easier reading the code - i.e. a representation of 'unitsOnTruck[2]' => the direct table entry, which we have to change here
               state.loadedTrucks[activeUnit.id] = unitsOnTruck                -- update the state.table
            end
        end
    end
end                                                                            -- 'end' of function unloadUnitsOffTruck()

-----------------------------------------------------------------------------------------------------------------------
function transport.onActivateEveryUnit(activeUnit, source)
    print("Within the function: onActivateEveryUnit.")
    local supplyTruckType1 = civ.getUnitType(26)                       
    local supplyTruckType2 = civ.getUnitType(118)
    local successfullFakeLoading = 0
 
    if (activeUnit.type ~= supplyTruckType1 and activeUnit.type ~= supplyTruckType2) then                                    -- if the currently activeUnit is no truck then there is no point to continue the code -> Therefore:
       print("Active unit is NOT the supplyTruckType.")                            -- 1) show this info in the console
       return                                                                    -- 2) and end the function
    else
       print("Active unit (ID: " .. activeUnit.id .. ") is supplyTruckType.")     -- if this line is reached (not broken up before), the currently activeUnit must be a truck
       local unitsOnTruck = state.loadedTrucks[activeUnit.id]                    -- "fill" the local variable 'unitsOnTruck' with data about the loaded units (IDs) on exactly this truck; KEY for the state.table = activeUnit/currentUnit.id
       activeTruck = activeUnit                                                    -- update the global variable, i.e. ID of the truck used right at this moment; since this funtion - in one case - jump to the funtion 'firstTimeFakeLoadingOnTruck()', where the ID is needed
        if unitsOnTruck == nil then                                                -- if this line is reached (not broken up before) there must be an active unit equal to supplyTruckType (=2nd condition) BUT it still can be a "virgin" truck, which will always result in code errors
           print("unitsOnTruck == nil | Therefore: Execute funtion: firstTimeFakeLoadingOnTruck.")
           firstTimeFakeLoadingOnTruck()                                         -- from that code line on -> each place of the truck is at least "fake loaded"
           successfullFakeLoading = 1
        end
        if unitsOnTruck ~= nil or successfullFakeLoading == 1 then                -- only now we can introduce the variables ('firstUnit'+'secondUnit') in the code, earlier there would be an error "attempt to index a nil value (global 'unitsOnTruck')" -> covered by 'firstTimeFakeLoadingOnTruck()'
           unitsOnTruck = state.loadedTrucks[activeUnit.id]
           activeTruck = nil                                                    -- delete content of the global variable
           local firstUnit = unitsOnTruck[1]                                    -- data of the first unit will be stored by means of their ID -> 'local' = initialize the variable within this function
           local secondUnit = unitsOnTruck[2]
            if (firstUnit == nil or firstUnit == "firstUnitFakeLoading") and (secondUnit == nil or secondUnit == "secondUnitFakeLoading") then            -- Case ONE: the respective truck is empty
               civ.ui.text("Empty truck (ID: " .. activeUnit.id .. ").")
               return
            elseif (firstUnit ~= nil and firstUnit ~= "firstUnitFakeLoading") and (secondUnit == nil or secondUnit == "secondUnitFakeLoading") then        -- Case TWO: if there is ONLY the firstUnit on the truck
               civ.ui.text("Currently only unit " .. firstUnit .. " is on the truck " .. activeUnit.id .. ".")
               return
            elseif (firstUnit == nil or firstUnit == "firstUnitFakeLoading") and (secondUnit ~= nil and secondUnit ~= "secondUnitFakeLoading") then        -- Case THREE: if there is ONLY the secondUnit on the truck
               civ.ui.text("Currently only unit " .. secondUnit .. " is on the truck " .. activeUnit.id .. ".")
               return
            elseif (firstUnit ~= nil and firstUnit ~= "firstUnitFakeLoading") and (secondUnit ~= nil and secondUnit ~= "secondUnitFakeLoading") then    -- Case FOUR: truck is full / loaded with "real" units
               civ.ui.text("Currently the units: " .. firstUnit .. ", " .. secondUnit .. " are on the truck " .. activeUnit.id .. ".")
            end
        end
    end
end 

return transport


I'm not sure if you could add any qualifiers to "firstUnit" "secondUnit" such as "firstUnit.unit.type" - I haven't tried it, though I might after work.

Edit - in this part, just for clarity. This is where it's telling you a unit is on the truck so I wonder if it could be modified to tell you the unit type as that would be desirable.

elseif (firstUnit == nil or firstUnit == "firstUnitFakeLoading") and (secondUnit ~= nil and secondUnit ~= "secondUnitFakeLoading") then -- Case THREE: if there is ONLY the secondUnit on the truck
civ.ui.text("Currently only unit " .. secondUnit .. " is on the truck " .. activeUnit.id .. ".")
return
 
France starts with an extremely powerful army that is hard to encircle. This scenario uses Knighttime's supply lines module. You can cut the Brits off from supply easily enough by encircling them near Dunkerque. This effectively takes them out of the fight until they ship out or are destroyed. Concentrating on destroying them will make it easier to attack England, but it also means you aren't dealing with the (now) more pertinent threat of a powerful France with good tanks, sizeable artillery and air force that can attrite you more than you'd like. So, depending on how the AI moves and where you place what unit, they both have issues.

I'd say that what you concentrate on depends on your strategy. If I was going to try and force Sea Lion early, I'd definitely take out the BEF. However, if I was planning on letting England stay back so I could really maximize my efforts towards Russia, I wouldn't.



Yes, there are problems in the first winter for Germany - a little bit different than the attrition mechanism you used, but the bottom line is it gets hard to prosecute the war effort the first winter. Think of things like units randomly not moving (frozen engines), units randomly taking attrition, units randomly triggering local counter attacks, etc. It won't be easy.



I did implement Sorfox's mechanism. I have noticed the difficulty in determining the unit names though I wonder if that isn't possible to fix. I haven't tried fixing it yet. The units do go to a secondary map where I suppose they will remain if the halftrack is damaged, but, it's an acceptable issue in my mind. The functionality of the half tracks is fun and useful, especially given the way one can encircle enemies - you want to be able to break past enemy lines with your panzers and ring large concentrations of enemy troops. I'd rather have that gameplay mechanism than worry too much about some extra units hanging around.



As far as I recall, he had said we are limited to 127 for most of everything for some programming reason, unfortunately. I think he mentioned that if he ever increased the technological advances, they too would be capped at 127, but this is from memory, and several years ago, so who knows if it remains true. I thought it had something to do with the mathematical way civ2 was designed.

Very interesting concepts you are using in your scenario. I'm really looking forward to play it.
 
I'm not sure if you could add any qualifiers to "firstUnit" "secondUnit" such as "firstUnit.unit.type" - I haven't tried it, though I might after work.
Rather than stocking that information, you may find it at anytime ?


"if state.loadedTrucks[TransportUnit.id][1] ~= nil and state.loadedTrucks[TransportUnit.id][1] ~= "firstUnitFakeLoading" then
- - - firstUnitType = civ.getUnit(state.loadedTrucks[TransportUnit.id][1]).type
end"

The second unit type can be extracted the same way (with 2 instead of 1) ?
 
Dadais is right, you certainly could get more information about the loaded units if you wanted to display it, and you'll need to use civ.getUnit(), in some way or another, to do so.

I think this entire module could be rewritten so that loaded units aren't teleported to a different map, but are instead stored solely in memory. In other words, they would be truly deleted when loaded, and then recreated as new units when unloaded. But that would mean that cities no longer pay support for them while they're loaded, so maybe keeping them in existence (as the module does) is actually preferable.

It also seems to me that all of the references in the code to "firstUnitFakeLoading" and "secondUnitFakeLoading" are a workaround for which there would be a simpler and more concise coding solution. I don't mean to be critical -- SorFox should surely be congratulated for producing a working module that solves a longstanding shortcoming of the base game. But I think further refinements are possible.
 
I think this entire module could be rewritten so that loaded units aren't teleported to a different map, but are instead stored solely in memory.
Wouldn't that bring issues and risks with unit number limits ?
An other point is, that would bring a three-layer table, more complicated for use to casual programming designers ?
 
Wouldn't that bring issues and risks with unit number limits ?
TOTPP lets you configure the maximum number of units using the "City & Unit limits" patch. TheNamelessOne recommended setting this to 4096 units, but I think the game could probably handle more than that -- it just might make for very slow turns. So I don't think this is a risk to be concerned about.

An other point is, that would bring a three-layer table, more complicated for use to casual programming designers ?
Well, that's possible. Let's see:
  1. state is a table
  2. It contains the named key loadedTrucks which is a table
  3. The keys in loadedTrucks are unit IDs of the transport vehicles. Each of those is also a table
  4. The keys in that table are integers [1] and [2] for the (up to 2) units present on the truck, and they contain one integer each (the ID of the unit being transported)
So Sorfox's design is already four levels deep. Doing this solely in memory would add a fifth layer: instead of [1] and [2] containing integer IDs, they would again be tables. We'd need to track the type of the unit, its home city, and probably also it's HP... maybe some other things as well. (That brings up an interesting point, though: by teleporting loaded units to another map, they will gradually heal while onboard; doing this in memory would force us to deal with how healing should be handled, and mimicking the game's healing mechanism would take additional effort.)

But I don't think this extra layer would have to make it more complicated for a designer to implement, if it was well-written. Most of the complexity is, or could be, hidden so that the areas for the designer to customize would be at least as straightforward as they are now. There are still pros and cons to implementing this, though, so perhaps the current solution is satisfactory.
 
(That brings up an interesting point, though: by teleporting loaded units to another map, they will gradually heal while onboard; doing this in memory would force us to deal with how healing should be handled, and mimicking the game's healing mechanism would take additional effort.)

Healing is described here. It shouldn't be that hard to replicate.

We'd need to track the type of the unit, its home city, and probably also it's HP... maybe some other things as well.

I recently wrote a script to copy the contents of a game into another game (to expand map size). This (plus a "toTriple" helper function) is what it takes to store unit information.

Code:
local unitData = {}
for unit in civ.iterateUnits() do
    unitData[unit.id] = {attributes = unit.attributes, 
    -- for carriedBy, there are sometimes phantom units carrying other units, so check that the unitid
    -- of the carriedBy corresponds to a real unit
    carriedById = (unit.carriedBy and civ.getUnit(unit.carriedBy.id) and unit.carriedBy.id), damage = unit.damage,
        domainSpec = unit.domainSpec, gotoTile = toTriple(unit.gotoTile), homeCity = (unit.homeCity and toTriple(unit.homeCity.location)),
        originalId = unit.id, location = toTriple(unit.location), moveSpent = unit.moveSpent, order = unit.order,
        owner = unit.owner, type = unit.type, visibility = unit.visibility}
end

There isn't that much to do, especially since many of these categories are irrelevant to our use case. However, looking at this there is one substantial issue: support from the home city.

Now that human owned units are activated every move, I wonder if we couldn't just "drag" some sleeping units (via teleportation) along with the supply truck when it moves.
 
Healing is described here. It shouldn't be that hard to replicate.
True, but to my knowledge no one has written Lua for this yet. Hence, "additional effort". :)

However, looking at this there is one substantial issue: support from the home city.
Yep, mentioned that in passing:
But that would mean that cities no longer pay support for them while they're loaded, so maybe keeping them in existence (as the module does) is actually preferable.
I expect that this could also be worked around, in some fashion, depending on the preferences of the designer. But again, additional effort.

Now that human owned units are activated every move, I wonder if we couldn't just "drag" some sleeping units (via teleportation) along with the supply truck when it moves.
So would you use onActivateUnit() for the supply truck as the trigger to teleport all the units it carries onto its current tile? My initial thought is that this would work great until the supply truck makes it last move, after which it doesn't fire onActivateUnit() anymore. Wouldn't that leave its loaded units one tile behind? I'm sure there would be a way to catch this too, but (familiar theme) "additional effort".

It feels to me like we have a working solution, thanks to Sorfox. However, there are many other types of solutions, or variations on them, that would also be possible. Maybe it's up to designers who want to implement this concept in their scenario to start providing more specifics about what they need?

@JPetroski Sorry if we hijacked your thread! To bring this back to HoF: if you have specific needs related to supply trucks that aren't being met by your implementation of Sorfox's module, feel free to provide details.
 
I'll be honest, I brought up this subject because there are at least two tactical level scenarios I have been laying the foundation for which would greatly benefit from a more robust transportation mechanism.

It's an implementation that TheNamelessOne had proposed to undertake as part of ToTPP in the past (see this thread) but it seems he never had the opportunity to complete it.

If this is something that could be handled by lua instead that would provide designers with a very interesting new set of tools to work with.
 
@JPetroski Sorry if we hijacked your thread! To bring this back to HoF: if you have specific needs related to supply trucks that aren't being met by your implementation of Sorfox's module, feel free to provide details.

Oh no worries at all. For my purposes, it works very well. There's a simple key press to load, a simple one to unload, etc. The only change I intend to pursue is to change the reporting - I'm hoping I can figure out what @Dadais provided just to give the player a little more information because I think that is definitely needed. "Unit 1458" is worthless information after all :)

Would the module be better if the units that are tucked away were deleted when the unit dies? Sure, but right now they're on a tiny island on the night map where nothing can reach them and they can't move. The only units that can use the transporter are available to be deleted, so there's no real need to fix it much :)

If there's an easier way to write it to enhance functionality and someone wants to help @tootall_2012 with his projects and write it, that's cool. It's definitely something I intend to use going forward in scenarios as appropriate, but I think for HoF, more or less what SorFox provided is sufficient with the minor naming convention tweak.
 
The only change I intend to pursue is to change the reporting - I'm hoping I can figure out what @Dadais provided just to give the player a little more information because I think that is definitely needed. "Unit 1458" is worthless information after all :)
There's a line in Sorfox's code that you posted which looks like this:
civ.ui.text("Currently only unit " .. firstUnit .. " is on the truck " .. activeUnit.id .. ".")
... plus another similar line that references secondUnit, and then one that references both firstUnit and secondUnit.
Both of those variables are unit IDs. So at that point in the code, all you have to do is call civ.getUnit(firstUnit) to get the unit itself, and then you can display whatever fields you want. So for example, the name of the first unit's type would be:
civ.getUnit(firstUnit).type.name
Dadais showed how you could get the unit type at any point in your event code, by pulling it from the state table where it's ultimately stored. What I'm showing is just a simpler form that takes advantage of those variables, but then of course my code will only work within their scope.

Would the module be better if the units that are tucked away were deleted when the unit dies? Sure, but right now they're on a tiny island on the night map where nothing can reach them and they can't move. The only units that can use the transporter are available to be deleted, so there's no real need to fix it much :)
That would be a pretty easy improvement... it would just need another short function to add to the transport module itself, and then one call to that function from the onUnitKilled() trigger. Seems like it might be worthwhile to do that, though, otherwise those units will exist and their home city will pay support for them for the remainder of the game.

If there's an easier way to write it to enhance functionality and someone wants to help @tootall_2012 with his projects and write it, that's cool. It's definitely something I intend to use going forward in scenarios as appropriate, but I think for HoF, more or less what SorFox provided is sufficient with the minor naming convention tweak.
@tootall_2012 Maybe in a different thread, you could give some examples of what you mean by "a more robust transportation mechanism" to kick off the conversation. There's another thread out there right now for Shortcomings of the Base Game - Lua Fixes? which is focused (so far) on aircraft carriers. Land transports seem like another example of a topic where it would be good to have an exchange of ideas regarding what designers would like to be able to do, including expectations for the AI. TNO keeps giving us more and more tools in Lua to tackle complex problems like this, but it's hard to know what functionality would actually be exciting and most valuable to designers.
 
I woke up early this morning and cranked out a good number of events for the American Theater. I don't know if other designers have played around with some of the general library functions for creating units, but they make things go much, much quicker.

For example, I wanted to ensure that the US Army would show up in force once German ground troops enter the map. Using a "for city in civ.iterateCities" loop coupled with the "nearbyTiles" function in the general library saved me days of work. I chose to give different garrisons (and field troops outside the city) based on the city's size, but of course there's many other ways to approach it. I have a somewhat random/somewhat planned dispersal in one event without having to write down (or potentially screw up) a single coordinate. It's a very, very, very useful tool that is also in Boudicca I'd anyone wants to check it out.

I'm hoping that once the US events are complete I'll be able to start a dedicated playthrough to try and catch bugs past the first 20 turns. I want to play the scenario through a few times before I hand it over to anyone else as this is one of the largest scenarios I've built and there's much that can go wrong.
 
I was running through a playtest where I was going to leave the B.E.F. alone, yet despite the B.E.F. having far more than the needed 3 units remaining, I'm getting the message that I've destroyed them. I originally thought I'd fixed this in an earlier playtest where I was always seeing the message that I had failed, so the problem seems to have reversed itself and I must be getting the logic wrong somewhere in this chain. It is replicated multiple times so I'm hoping someone can help me out so I can make the necessary adjustments.

I'm hoping it is a simple solution but here's all the code (note the actual event is in onTurn and the rest of all of this is just towards the top of the events, outside of a specific civ function.

Code:
--Here I define what I want the minimum number of British army units to be
local minArmyInFrance = 3

--Here I define what units qualify as British Army
local britishArmy = {}
britishArmy[object.uBritishInfantry.id] = 1
britishArmy[object.uBritishTankI.id] = 1
britishArmy[object.uBritishTankII.id] = 1
britishArmy[object.uBritishFieldArtillery.id] = 1
britishArmy[object.uAlliedMobileArtillery.id] = 1
britishArmy[object.uAlliedMobileFlak.id] = 1
britishArmy[object.uVickersMkVI.id] = 1

--Here I have the coordinates for France via a polygon
local francePolygonCoordinates = {{101,55},{107,67},{106,80},{97,81},{92,86},{84,76},{84,70},{91,59},}

--I think I need this function to help with this sequence, ie if the tile is in France.
local function inFrance(tile)
    return gen.inPolygon(tile,francePolygonCoordinates)
end

--Here's where I'm attempting to count how many units are in a particular area, starting with a local value of 0
local function unitsInRegion(regionFn)
    local value = 0
    for unit in civ.iterateUnits() do
        if britishFighters[unit.type.id] and regionFn(unit) then
            value = value + britishFighters[unit.type.id]
            print("Successfully counted British fighters")
        end
        if britishArmy[unit.type.id] and regionFn(unit) then
            value = value + britishArmy[unit.type.id]
            print("Successfully counted British Army")
        end
        if malteseDefenders[unit.type.id] and regionFn(unit) then
            value = value + malteseDefenders[unit.type.id]
            print("Successfully counted Maltese Defenders")
        end 
        if germanDefenders[unit.type.id] and regionFn(unit) then
            value = value + germanDefenders[unit.type.id]
            print("Successfully counted German Defenders")
        end 
    end
    return value
end

--Here's where I want to see if there are greater than or equal to (in this case 3) British Army units in France
local function enoughArmiesInFrance()
    return minArmyInFrance >= unitsInRegion(inFrance)
end

--And here is the actual event that isn't working appropriately

--Dunkirk
if turn == 3 and flag.value("Calais Captured") == true and enoughArmiesInFrance()  then 
    civ.ui.text("Goering's Luftwaffe has failed to annihilate the British Expeditionary Force trapped at Dunkirk! To our amazement, many of the soldiers were evacuated by all types of small water craft sent from England.  In a speech, Churchill declares this a 'Miracle of Deliverence!'")
        flag.setTrue("The Miracle at Dunkirk")
        for unit in civ.iterateUnits() do
            if unit.owner == object.tBritain and unit.x >= 86 and unit.y >= 62 and unit.x <= 102 and unit.y <= 80 and unit.z == 0 then
            civ.teleportUnit(unit, object.lLondon)
            end
        end
    elseif turn == 3 and flag.value("Calais Captured") == true and not enoughArmiesInFrance()  then 
    civ.ui.text("We have successfully managed to destroy most of the B.E.F. that was trapped near Dunkirk.  While several British fishing boats and other small water craft assembled in a brave attempt to evacuate their stranded soldiers, the might of our Luftwaffe and Panzer Divisions proved too much for them.")
        for unit in civ.iterateUnits() do
            if unit.owner == object.tBritain and unit.x >= 86 and unit.y >= 62 and unit.x <= 102 and unit.y <= 80 and unit.z == 0 then
            civ.teleportUnit(unit, object.lLondon)
            end
        end
end

Any ideas on where I've gone wrong? Thanks!
 
Back
Top Bottom