Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Please post your after action reports on your battles and campaigns here.

Moderator: Campaign Series Matrix Edition Development Group

User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

For our next example, we will focus on the C1/1st/1st Rifle Company, 1st/1st Binh Xuyen Infantry Battalion. In the VN_550921_Rung_Sat.lua set_org_lists():


Code: Select all

    _C1_1ST_1ST_RIFLE_COY_48_US_6 = {7,8,9,10} -- [C] [2102218] C1/1st/1st Rifle Company 48 - US
    _1ST_VM_RIFLE_48_7 = {7} -- [P] [90,33] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _2ND_VM_RIFLE_48_8 = {8} -- [P] [90,32] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _3RD_VM_RIFLE_48_9 = {9} -- [P] [89,33] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _MORTAR_10 = {10} -- [P] [89,33] [211008] VM 50mm Mortars


Note that unlike the earlier _C2_1ST_5TH_RIFLE_COY_48_US_73, the _C1_1ST_1ST_RIFLE_COY_48_US_6 includes an attached indirect fire unit, _MORTAR_10. This difference is significant, as you will see by and by.

Where the heck is _C1_1ST_1ST_RIFLE_COY_48_US_6? We might start clicking around the game map, looking for those platoon trackids (in the Unit List, with Options > Unit List > Show TrackIDs toggled ON). But we are smarter than that. We use the Find Organization Dialog to do the searching for us. New to CSVN: If you click on a platoon in the Find Organization Dialog, the map will jump to that unit and select (and highlight) it.

CSVN_AAR_AI_AI_RungSat25.jpg
CSVN_AAR_AI_AI_RungSat25.jpg (1.94 MiB) Viewed 3217 times

In the screenshot, we have selected the

2nd VM Rifle 48 8[6]

where the 8 is the platoon's trackid and the [6] indicates the parent org's trackid. That [6] corresponds to the 6 in

_C1_1ST_1ST_RIFLE_COY_48_US_6

What are _C1_1ST_1ST_RIFLE_COY_48_US_6's orders? In battle_plan_b(), we see:


Code: Select all

    -- _C1_1ST_1ST_RIFLE_COY_48_US_6 -- C1/1st/1st Rifle Company 48 - US
    do local units = difference(_C1_1ST_1ST_RIFLE_COY_48_US_6, _MORTAR_10)
        if counter_exists(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST) then
            local objective = counter_hex(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST)
            if not within(units, objective, DOWNLEFTDIR, 2) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)
            end
            if not at(_MORTAR_10, objective) then
                move_rush(_MORTAR_10, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_10, objective, 100, true)
            end
        else
            defend_weak(units)
            fire_indirect_nearest(_MORTAR_10, 100, true)
        end
    end


Before we get too deeply into that, we note that, like the earlier company, _C1_1ST_1ST_RIFLE_COY_48_US_6 has a unit/location to defend, _C1_1ST_1ST_RIFLE_COY_48_US_6_POST. That is defined, in init_variables(), as:


Code: Select all

    _C1_1ST_1ST_RIFLE_COY_48_US_6_POST = random_pick({146, 490}) -- _SUPPLY_CACHE_146, _JUNGLE_FACTORY_490


That is, _C1_1ST_1ST_RIFLE_COY_48_US_6 is to defend either of unit with trackid 146 or unit with trackid 490


Code: Select all

    ...
    _JUNGLE_FACTORY_490 = {490} -- [P] [T1: 90,38] [218809] Jungle Factory
    ...
    _SUPPLY_CACHE_146 = {146} -- [P] [T1: 82,28] [218811] Supply Cache (VP)
    ...


at 50% chance or either. (See the random_pick() description in Manual/LUA_FUNCTIONS_REFERENCE.txt for more details.)

We look up those two supply units in the Schedule Dialog:


CSVN_AAR_AI_AI_RungSat26.jpg
CSVN_AAR_AI_AI_RungSat26.jpg (1.88 MiB) Viewed 3217 times


On Turn 1, they are to arrive as "reinforcements" at or around the magenta circled hexes up to 4 hexes away ("scattered"). In our first quicky auto-test run, we see these placements:

CSVN_AAR_AI_AI_RungSat27.jpg
CSVN_AAR_AI_AI_RungSat27.jpg (721.19 KiB) Viewed 3217 times


In the screenshot, _C1_1ST_1ST_RIFLE_COY_48_US_6 is circled in yellow. The supply units have appeared at hexes 80,29 and 89,39 (magenta circles), each within 4 hexes of its reinforcement default centering hex (light blue squares).

It is important to realize that such placements are random. Due to the Reinforcement system's scattering mechanism, you will never know, from one game to the next (or auto-test to the next), exactly where they will be placed. It varies! Also, the 4 does not imply that the reinforcements will scatter 4 hexes away from the default center; they might. The 4 is the number of steps in a random walk algorithm, where the steps might be outward from the default hex, but can easily be backward steps towards the center. So a 4-step random walk might in fact end up just 1 hex away, possibly even walk back to the center. The random walk algorithm allows for very wide scattering, but in practice tends to favor slight scatter over wide scatter.

It is also important to know that since


Code: Select all

function init_variables ()

    -- initialize values possibly varying through the course of the scenario
    -- called once only, in on_startup()

    ...

    -- logistic defense assignments

    _C1_1ST_1ST_RIFLE_COY_48_US_6_POST = random_pick({146, 490}) -- _SUPPLY_CACHE_146, _JUNGLE_FACTORY_490

    ...

end


once determined (at game startup), _C1_1ST_1ST_RIFLE_COY_48_US_6_POST is unvarying, never again to change through the course of the scenario (or auto-test). (This invariability need not necessarily be the case, but in this case it is.)

Meaning to say, _C1_1ST_1ST_RIFLE_COY_48_US_6 will defend one supply unit or the other. But we won't know which until actual game play (or auto-test).

Here again are _C1_1ST_1ST_RIFLE_COY_48_US_6's orders:

Code: Select all

    -- _C1_1ST_1ST_RIFLE_COY_48_US_6 -- C1/1st/1st Rifle Company 48 - US
    do local units = difference(_C1_1ST_1ST_RIFLE_COY_48_US_6, _MORTAR_10)
        if counter_exists(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST) then
            local objective = counter_hex(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST)
            if not within(units, objective, DOWNLEFTDIR, 2) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)
            end
            if not at(_MORTAR_10, objective) then
                move_rush(_MORTAR_10, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_10, objective, 100, true)
            end
        else
            defend_weak(units)
            fire_indirect_nearest(_MORTAR_10, 100, true)
        end
    end


First off, note the


Code: Select all

    do local units = difference(_C1_1ST_1ST_RIFLE_COY_48_US_6, _MORTAR_10)


(See the difference() description in Manual//LUA_FUNCTIONS_REFERENCE.txt for more details.)


In this orders 'do ... end' code block, 'units' is not the entire company; rather, it is a subset, excluding the mortars, which are handled separately from the infantry units. Why is that? It is because you will want the infantry to man the front lines, to defend or to attack, with the mortars holding back somewhat to the rear. Direct Fire Infantry unit orders are typically different from Indirect Fire unit orders. You will want to define your 'local units =' carefully because of that.

If a supply unit defense post exists


Code: Select all

        if counter_exists(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST) then


which it does (unlike the earlier example, where the supply unit might be UNKNOWN), we determine the hex coordinates (the 'hc') of that defense post


Code: Select all

            local objective = counter_hex(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST)


then, for the infantry units only


Code: Select all

            if not within(units, objective, DOWNLEFTDIR, 2) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)
            end


if the infantry units are not within 2 hexes downward/leftward of the supply unit, they are to move (no rush!) to that objective. In a later turn, if they are within that hex arc, the 'not within() is false, so instead they should


Code: Select all

                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)


strongly defend that place up to 2 hexes downward/leftward but in randomly scattered fashion (ignore for now the '50, false').

Why DOWNLEFTDIR? Look at one of the earlier screenshots. The BX Army has reason to believe the VNA (Vietnamese National Army; not NVA!) will come down the waterways to the southwest and attack from that direction. If instead the VNA arrive by paradrop or come overland by way of the roads to the north, _C1_1ST_1ST_RIFLE_COY_48_US_6 might be defending in the wrong direction. But for now, we are willing to take that chance. Substituting NODIR for the defense directionality is something we might do in future. Even having code determining the NVA attack vector dynamically then specifying DOWNLEFTDIR or UPRIGHTDIR as appropriate, that too is possible. But for now, let's KISS this.

As for _MORTAR_10, its separate orders are to


Code: Select all

            if not at(_MORTAR_10, objective) then
                move_rush(_MORTAR_10, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_10, objective, 100, true)
            end


If the mortar platoon is not at the objective, they are to move there (no rush!). Otherwise, if a later turn finds them there, they are to fire indirect at any enemy nearest that place.

(As always, for fire_indirect_nearest_to_hex() or any other standard function we use in these examples, refer to Manual/LUA_FUNCTIONS_REFERENCE.txt for more details. In a few cases, the function is not described there; it might be defined and briefly documented in user.lua, which see.)

What if counter_exists() is not true, because the supply unit has been destroyed?


Code: Select all

    -- _C1_1ST_1ST_RIFLE_COY_48_US_6 -- C1/1st/1st Rifle Company 48 - US
        if counter_exists(_C1_1ST_1ST_RIFLE_COY_48_US_6_POST) then
            ...
        else
            defend_weak(units)
            fire_indirect_nearest(_MORTAR_10, 100, true)
        end
    end


In that 'else' case, the infantry units are to weakly defend in place, wherever they stand. And the mortars are to fire indirect at any nearest enemy (nearest to them, wherever they happen to be, not to any specific location).
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

_SUPPLY_CACHE_146 or _JUNGLE_FACTORY_490: which is it? We launch an auto-test to find out.

To begin with, here are where the supply units "arrive" (with random scatter):

CSVN_AAR_AI_AI_RungSat28.jpg
CSVN_AAR_AI_AI_RungSat28.jpg (589.36 KiB) Viewed 3197 times

After Turn 1 supply unit "reinforcements", after Turn 2 orders and initial BX Army movement, with a pass file "b" in place (stop at the beginning of every Side B phase), we see this at the start of BX Army Side B phase:

CSVN_AAR_AI_AI_RungSat29.jpg
CSVN_AAR_AI_AI_RungSat29.jpg (572.66 KiB) Viewed 3197 times

Pretty fast going through the Palm Trees.

_C1_1ST_1ST_RIFLE_COY_48_US_6 is moving southward. _JUNGLE_FACTORY_490 it is!

With this orders code


Code: Select all

            if not within(units, objective, DOWNLEFTDIR, 2) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)
            end
            if not at(_MORTAR_10, objective) then
                move_rush(_MORTAR_10, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_10, objective, 100, true)
            end


_C1_1ST_1ST_RIFLE_COY_48_US_6 moves here:

CSVN_AAR_AI_AI_RungSat30.jpg
CSVN_AAR_AI_AI_RungSat30.jpg (588.6 KiB) Viewed 3197 times

Slower going through the Swamps.

Then next turn here

CSVN_AAR_AI_AI_RungSat31.jpg
CSVN_AAR_AI_AI_RungSat31.jpg (585.07 KiB) Viewed 3197 times

Note where the mortars are getting out ahead of the infantry. This is because of the 'move_norush(units, ...)' for the infantry, while the mortars 'move_rush(_MORTAR_10, ...)'. Those mortars are now Fatigued. A possible improvement: have _MORTAR_10 move_norush() also.

Then

CSVN_AAR_AI_AI_RungSat32.jpg
CSVN_AAR_AI_AI_RungSat32.jpg (587.09 KiB) Viewed 3197 times

_MORTAR_10 (and one other infantry unit) has arrived at the _JUNGLE_FACTORY_490 hex. Next turn and thereafter, there should be no further _MORTAR_10 movement, because they were ordered to the objective with no scatter


Code: Select all

                move_rush(_MORTAR_10, objective, NODIR, 0, 100)


where the 0 says exactly at the objective hex, no allowance for scatter; and the actprob 100 allows for no wriggle room. _MORTAR_10 should now stay put!

Next turn:

CSVN_AAR_AI_AI_RungSat33.jpg
CSVN_AAR_AI_AI_RungSat33.jpg (586.13 KiB) Viewed 3197 times

Oh, the entire _C1_1ST_1ST_RIFLE_COY_48_US_6 is stacked at the _JUNGLE_FACTORY_490 hex. Will they stay there?

Next turn:

CSVN_AAR_AI_AI_RungSat34.jpg
CSVN_AAR_AI_AI_RungSat34.jpg (584.98 KiB) Viewed 3197 times

No, one platoon moves forward. Done?

CSVN_AAR_AI_AI_RungSat35.jpg
CSVN_AAR_AI_AI_RungSat35.jpg (587.86 KiB) Viewed 3197 times

And a second infantry platoon moves forward. Now done?

In the next and subsequent turns, I don't see any further movement, or lateral defensive position adjustment. If I wait long enough, run enough additional turns, there might be. Probably will be, because


Code: Select all

                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)


allows some wriggle room with the extent 2 and actprob 50. (See defend_scatter() in user.lua for more details. Also see the explanation of extent & actprob in Manual/LUA_FUNCTIONS_REFERENCE.txt.)

I won't bother to run another test showing _C1_1ST_1ST_RIFLE_COY_48_US_6 movement to defending _SUPPLY_CACHE_146. It would be much the same. (Although you are never quite sure, because of all the constricting waterways and possibly torturous pathfinding from starting point to destination.)
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

On to a more complicated example, or examples, this time involving two different BX Army companies.

(Remember to read the supplementary remarks here.)

In init_variables(), we have


Code: Select all

    _C3_1ST_1ST_RIFLE_COY_48_US_16_POST = random_pick({148, 148, 148, UNKNOWN}) -- _SUPPLY_CACHE_148
    if _C3_1ST_1ST_RIFLE_COY_48_US_16_POST == 148 then
        _C2_1ST_1ST_RIFLE_COY_48_US_11_POST = UNKNOWN
    else
        _C2_1ST_1ST_RIFLE_COY_48_US_11_POST = 148 -- _SUPPLY_CACHE_148
    end


Notice the 'random_pick({148, 148, 148, UNKNOWN})'. There are four elements in that argument list, each with an equal 1/4 chance of being picked. Since three of those elements are the trackid 148, that says there is a 3/4 (75%) chance that _C3_1ST_1ST_RIFLE_COY_48_US_16 will be assigned to defend


Code: Select all

    _SUPPLY_CACHE_148 = {148} -- [P] [T1: 73,25] [218811] Supply Cache (VP)


and a 1/4 (25%) chance that _C3_1ST_1ST_RIFLE_COY_48_US_16_POST will be UNKNOWN.

Now focus on the next five lines of code, the 'if ... else ... end' portion. If _C3_1ST_1ST_RIFLE_COY_48_US_16_POST is the unit with the trackid 148, then for the second company, _C2_1ST_1ST_RIFLE_COY_48_US_11, its defensive assignment will be UNKNOWN. Else if _C3_1ST_1ST_RIFLE_COY_48_US_16_POST is not 148 (in which case its defensive assignment is UNKNOWN), then _C2_1ST_1ST_RIFLE_COY_48_US_11 will be given the assignment to defend _SUPPLY_CACHE_148.

Evidently _SUPPLY_CACHE_148 is deemed to be important! One company or the other, somebody will be assigned to defend it.

This is a good example of one way how you might code randomness, replayability, into your scripts. Jungle warfare (any warfare, really) is about surprises. Let's keep the enemy guessing. Fog of War!

Like the earlier examples, _SUPPLY_CACHE_148 will arrive on Turn 1 as a randomly scattered "reinforcement".

CSVN_AAR_AI_AI_RungSat36.jpg
CSVN_AAR_AI_AI_RungSat36.jpg (1.76 MiB) Viewed 3153 times

In the screenshot (a faked, composite screenshot; you wouldn't actually see all of those open dialogs and hex and unit highlights displaying simultaneously) -- you will observe:

  • _SUPPLY_CACHE_148 was to arrive on Turn 1 scattered within 4 hexes of 73,25. In this sample run, it has scattered one hex north of that, to hex 73,24 (magenta circle, and highlights).
  • _C3_1ST_1ST_RIFLE_COY_48_US_16 (green highlights) is initially posted at hex 66,23.
  • _C2_1ST_1ST_RIFLE_COY_48_US_11 (yellow highlights) is initially posted at hexes 76,28 & 76,29.

(Note: In CSVN v1.20 and before, reinforcement scatter might place arriving units on waterway hexes. This would present problems for units SAI ordered to defend such hexes, since they are generally impassable, they could not be defended, or assaulted. In subsequent releases, reinforcement scatter to waterway hexes is prevented.)

We will focus first on _C3_1ST_1ST_RIFLE_COY_48_US_16's orders:


Code: Select all

    -- _C3_1ST_1ST_RIFLE_COY_48_US_16 -- C3/1st/1st Rifle Company 48 - US
    do local units = difference(_C3_1ST_1ST_RIFLE_COY_48_US_16, _MORTAR_20)
        if counter_exists(_C3_1ST_1ST_RIFLE_COY_48_US_16_POST) then
            local objective = counter_hex(_C3_1ST_1ST_RIFLE_COY_48_US_16_POST)
            if not within(units, objective, DOWNLEFTDIR, 2) then
                move_way_point(units, {"70,25",objective}, NODIR, 0, 100) -- see _JUNKS_163 below
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, true, DEFEND_STRONG)
            end
            if not at(_MORTAR_20, objective) then
                move_way_point(_MORTAR_20, {"70,25",objective}, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_20, objective, 100, true)
            end
        else
            defend_weak(units)
            fire_indirect_nearest(_MORTAR_20, 100, true)
        end

    end


We begin with


Code: Select all

    do local units = difference(_C3_1ST_1ST_RIFLE_COY_48_US_16, _MORTAR_20)


to separate _MORTAR_20 from the rest of the units, the infantry units in the company.

If _C3_1ST_1ST_RIFLE_COY_48_US_16_POST is not UNKNOWN, it has been ordered to defend _SUPPLY_CACHE_148. We determine and assign that objective hex via


Code: Select all

            local objective = counter_hex(_C3_1ST_1ST_RIFLE_COY_48_US_16_POST)


If the (infantry) units of _C3_1ST_1ST_RIFLE_COY_48_US_16 are not within 2 hexes of the objective in a down/left direction, they are to move there by way of intermediate point 70,25. Else if there, generally around the objective, they are to scatter and strongly defend within 2 hexes left/downward.

It is important here to use


Code: Select all

                move_way_point(units, {"70,25",objective}, NODIR, 0, 100)


and not simply


Code: Select all

                move_norush(units, objective, NODIR, 0, 100)


Why? Study the Minor River hexsides in that area (with Map Hints toggled ON):

CSVN_AAR_AI_AI_RungSat37.jpg
CSVN_AAR_AI_AI_RungSat37.jpg (782.92 KiB) Viewed 3153 times

Without specification of a waypoint, the game engine pathfinding AI might take the wrong fork (red arrow) and head towards _SUPPLY_CACHE_148 on the north side of the Minor River -- the wrong side. The game engine pathfinding AI might never figure things out; the units would probably never find their way to the _SUPPLY_CACHE_148 hex. (Going further east, detouring around the endpoint of that MR (Minor River), the pathfinding AI would never consider things that far.)

By nudging the game engine pathfinding AI to first go to waypoint 70,25 (crossing the MR by means of the Junks placed there), that places the units on the south side of the waterway, the right side, because that is where _SUPPLY_CACHE_148 is located.

NOTE: The game engine pathfinding AI generally cannot "see" across waterways. Especially in complex waterway terrains such as the Rung Sat Swamp and other similar areas in the Vietnam game (e.g., the Mekong Delta, the Red River Delta), you will want to make good and frequent use of move_way_point() (also its cousins, attack_way_point(), defend_way_point(), etc.).

Don't overdo it. move_way_point() might be slower than the alternatives. But if you need it, you need it. Sometimes the game engine pathfinding will stubbornly insist on going the wrong way. Solution: set it right with move_way_point() etc.

Similar to the infantry units movement, if _MORTAR_20 (see above) is not at the objective, it is to waypoint move there. Once there, it is ordered to indirect fire at any enemy found nearest to that hex.

Else if _SUPPLY_CACHE_148 is destroyed (its counter doesn't exist), the infantry units of _C3_1ST_1ST_RIFLE_COY_48_US_16 are to defend weakly in place, while _MORTAR_20 is to stay put, indirect firing at any enemy units closest to its current position.

What about the second company, _C2_1ST_1ST_RIFLE_COY_48_US_11? That company's orders:


Code: Select all

    -- _C2_1ST_1ST_RIFLE_COY_48_US_11 -- C2/1st/1st Rifle Company 48 - US
    do local units = difference(_C2_1ST_1ST_RIFLE_COY_48_US_11, _MORTAR_15)
        if counter_exists(_C2_1ST_1ST_RIFLE_COY_48_US_11_POST) then
            local objective = counter_hex(_C2_1ST_1ST_RIFLE_COY_48_US_11_POST)
            if not within(units, objective, DOWNLEFTDIR, 2) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 2, 50, false, DEFEND_STRONG)
            end
            halt(_MORTAR_15)
            if not at(_MORTAR_15, objective) then
                move_rush(_MORTAR_15, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_15, objective, 100, true)
            end
        else
            defend_scatter(units, "76,28", DOWNLEFTDIR, 2, 50, false, DEFEND_NORMAL)
            fire_indirect_nearest(_MORTAR_15, 100, true)
        end
    end


The orders for _C2_1ST_1ST_RIFLE_COY_48_US_11 are much like the orders for _C3_1ST_1ST_RIFLE_COY_48_US_16, but with the following differences.

It was determined in earlier testing that for _C2_1ST_1ST_RIFLE_COY_48_US_11, move_way_point() was not necessary, because the waterways are such and fords (FD) are present where the game engine pathfinding AI was able to get it right, no nudging necessary. So a simple move_norush() suffices.

What is the purpose of this?


Code: Select all

            halt(_MORTAR_15)


To be honest, I don't remember. This scenario was first scripted a year or so ago. At the time, during testing, there was probably some quirk that the halt() order was inserted to fix. <shrug> Removing that (useless?) halt() command is an "improvement" we might try.

Also, with this


Code: Select all

            halt(_MORTAR_15)
            if not at(_MORTAR_15, objective) then
                move_rush(_MORTAR_15, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_15, objective, 100, true)
            end


are we not contradicting ourselves? Is _MORTAR_15 to halt, or move, or fire, or what?

IMPORTANT: Units can be assigned more than one order, many orders even. In all cases, later orders pre-empt earlier orders, subject to possible 'if ... else ... end' conditionality.

In CSEE/SAI scripts, you will see many examples of this. We will explain each case as it comes. For now, remember this rule: Later orders in the command sequence override earlier orders.

If on the other hand _C2_1ST_1ST_RIFLE_COY_48_US_11_POST is UNKNOWN (because _C3_1ST_1ST_RIFLE_COY_48_US_16 has been ordered to defend _SUPPLY_CACHE_148), _C2_1ST_1ST_RIFLE_COY_48_US_11 is ordered to


Code: Select all

            defend_scatter(units, "76,28", DOWNLEFTDIR, 2, 50, false, DEFEND_NORMAL)


What is "76,28"? In init_constants(), we have


Code: Select all

    OBJECTIVES[22] = "76,28" -- 1-40[2/2] 21


To make it clearer, we should probably revise that order to say


Code: Select all

            defend_scatter(units, OBJECTIVES[22], DOWNLEFTDIR, 2, 50, false, DEFEND_NORMAL)


If you look at that second screenshot and compare it with the first, you will see that _C2_1ST_1ST_RIFLE_COY_48_US_11 is initially deployed at OBJECTIVES[22], so all it needs to do (if ...) is to scatter down/leftward and defend.

Either way -- "76,28" vs. OBJECTIVES[22] -- the effect is the same, but the purpose of the latter is more obvious.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Now for a much more complicated example scripting an entire battalion:


Code: Select all

    _TIEU_DOAN_HQ_36 = {36} -- [P] [82,42] [213015] VM Battalion HQ (foot)

    _C1_1ST_2ND_RIFLE_COY_48_US_37 = {38,39,40,41} -- [C] [2102218] C1/1st/2nd Rifle Company 48 - US
    _1ST_VM_RIFLE_48_38 = {38} -- [P] [66,37] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _2ND_VM_RIFLE_48_39 = {39} -- [P] [66,38] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _3RD_VM_RIFLE_48_40 = {40} -- [P] [66,37] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _MORTAR_41 = {41} -- [P] [66,38] [211008] VM 50mm Mortars

    _C2_1ST_2ND_RIFLE_COY_48_US_42 = {43,44,45} -- [C] [2102218] C2/1st/2nd Rifle Company 48 - US
    _1ST_VM_RIFLE_48_43 = {43} -- [P] [76,38] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _2ND_VM_RIFLE_48_44 = {44} -- [P] [76,38] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _3RD_VM_RIFLE_48_45 = {45} -- [P] [76,37] [212040] VM Rifle Platoon 48 (U.S. Arms)

    _C3_1ST_2ND_RIFLE_COY_48_US_47 = {48,49,50} -- [C] [2102218] C3/1st/2nd Rifle Company 48 - US
    _1ST_VM_RIFLE_48_48 = {48} -- [P] [88,50] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _2ND_VM_RIFLE_48_49 = {49} -- [P] [88,49] [212040] VM Rifle Platoon 48 (U.S. Arms)
    _3RD_VM_RIFLE_48_50 = {50} -- [P] [88,49] [212040] VM Rifle Platoon 48 (U.S. Arms)

    _C4_1ST_2ND_DAI_DOI_HOA_LUC_48_81MM_52 = {53,54,55} -- [C] [2102219] C4/1st/2nd Dai Doi Hoa Luc 48 - 81mm
    _MORTAR_53 = {53} -- [P] [82,42] [211015] VM 81mm Mortars
    _MEDIUM_MACHINE_GUN_54 = {54} -- [P] [82,42] [212067] VM Medium Machine Gun (US)
    _MEDIUM_MACHINE_GUN_55 = {55} -- [P] [69,48] [212067] VM Medium Machine Gun (US)


The defensive _POSTs for those companies:


Code: Select all

    _C1_1ST_2ND_RIFLE_COY_48_US_37_POST = random_pick({"71,37", HEXUNKNOWN, HEXUNKNOWN, HEXUNKNOWN}) -- nothing at "71,37"
    _C2_1ST_2ND_RIFLE_COY_48_US_42_POST = random_pick({147, 488}) -- _SUPPLY_CACHE_147, _JUNGLE_FACTORY_488
    _C3_1ST_2ND_RIFLE_COY_48_US_47_POST = random_pick({"91,50", OBJECTIVES[28], OBJECTIVES[32]}) -- _SUPPLY_DEPOT_486 at "91,50"


The set_org_lists() entries for those supply units:


Code: Select all

    _SUPPLY_CACHE_147 = {147} -- [P] [70,33] [218811] Supply Cache (VP)
    _JUNGLE_FACTORY_488 = {488} -- [P] [T1: 77,41] [218809] Jungle Factory
    _SUPPLY_DEPOT_486 = {486} -- [P] [T1: 91,50] [218808] Fixed Supply Depot (VP)


Only two of those supply units, the last two, arrive as Turn 1 "reinforcements":

CSVN_AAR_AI_AI_RungSat38.jpg
CSVN_AAR_AI_AI_RungSat38.jpg (165.96 KiB) Viewed 3139 times

(A reminder: You are not to think of these "reinforcements" as somehow materializing out of thin air on Turn 1. Rather, the scenario designer has used the Reinforcements Random Scatter mechanism to add some variability, some replayability, into the scenario.)

The actual placement of those supply units, and the initial locations of the 1st/2nd BX Armmy Battalion:

CSVN_AAR_AI_AI_RungSat39.jpg
CSVN_AAR_AI_AI_RungSat39.jpg (2.83 MiB) Viewed 3139 times

We will consider the companies one by one, in order.

The 1st Company (the turquoise circle in the screenshot preceding), _C1_1ST_2ND_RIFLE_COY_48_US_37, is assigned to defend:


Code: Select all

    _C1_1ST_2ND_RIFLE_COY_48_US_37_POST = random_pick({"71,37", HEXUNKNOWN, HEXUNKNOWN, HEXUNKNOWN}) -- nothing at "71,37"


There is a 1/4 (25% chance) that _C1_1ST_2ND_RIFLE_COY_48_US_37 will defend hex 71,37 (turquoise square), while the chance it will defend HEXUNKNOWN is 3/4 (3 chances out of the 4 elements random_pick() list, or 75% chance).

The orders for _C1_1ST_2ND_RIFLE_COY_48_US_37:


Code: Select all

    -- _C1_1ST_2ND_RIFLE_COY_48_US_37 -- C1/1st/2nd Rifle Company 48 - US
    do local units = difference(_C1_1ST_2ND_RIFLE_COY_48_US_37, _MORTAR_41)
        if _C1_1ST_2ND_RIFLE_COY_48_US_37_POST == "71,37" then
            local objective = _C1_1ST_2ND_RIFLE_COY_48_US_37_POST
            if not within(units, objective, DOWNLEFTDIR, 1) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 1, 50, true, DEFEND_STRONG)
            end
            if OBJECTIVES14_CAPTURE_TURN and
               turn >= OBJECTIVES14_CAPTURE_TURN + 5 then
                attack_strong(units, OBJECTIVES[14], DOWNRIGHTDIR, 1, 100)
            end
            halt(_MORTAR_41)
            if not at(_MORTAR_41, objective) then
                move_rush(_MORTAR_41, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_41, objective, 100, true)
            end
        else
            defend_strong(units)
            fire_indirect_nearest(_MORTAR_41, 100, true)
        end
    end


What if _C1_1ST_2ND_RIFLE_COY_48_US_37 is assigned to defend 'HEXUNKNOWN'? If so


Code: Select all

        if _C1_1ST_2ND_RIFLE_COY_48_US_37_POST == "71,37" then
            [irrelevant if assigned to defend HEXUNKNOWN]
        else
            defend_strong(units)
            fire_indirect_nearest(_MORTAR_41, 100, true)
        end


then only the 'else' portion of that orders sequence applies. _C1_1ST_2ND_RIFLE_COY_48_US_37 is to strongly defend in place; _MORTAR_41 is to stay put and indirect fire at any nearest enemy.

On the other hand, what if _C1_1ST_2ND_RIFLE_COY_48_US_37 is assigned to defend 71,37. If so


Code: Select all

        if _C1_1ST_2ND_RIFLE_COY_48_US_37_POST == "71,37" then
            local objective = _C1_1ST_2ND_RIFLE_COY_48_US_37_POST
            if not within(units, objective, DOWNLEFTDIR, 1) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, DOWNLEFTDIR, 1, 50, true, DEFEND_STRONG)
            end
            if OBJECTIVES14_CAPTURE_TURN and
               turn >= OBJECTIVES14_CAPTURE_TURN + 5 then
                attack_strong(units, OBJECTIVES[14], DOWNRIGHTDIR, 1, 100)
            end
            halt(_MORTAR_41)
            if not at(_MORTAR_41, objective) then
                move_rush(_MORTAR_41, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_41, objective, 100, true)
            end
        else
            [irrelevant if assigned to defend 71,37]
        end


_C1_1ST_2ND_RIFLE_COY_48_US_37 is to move (no rush) to that objective. Once there, it is to scatter and defend strongly.

If _C1_1ST_2ND_RIFLE_COY_48_US_37 moves away to defend 71,37, it leaves undefended OBJECTIVES[14], its initial billet. If the VNA capture OBJECTIVES[14], five turns after capture _C1_1ST_2ND_RIFLE_COY_48_US_37 has new orders: strongly attack the enemy occupiers at OBJECTIVES[14] (from a down/rightware direction).

What sets OBJECTIVES14_CAPTURE_TURN? It is this:


Code: Select all

function on_objective_capture (hc, value, values, side) -- DO NOT REMOVE

    ...

    if hc == OBJECTIVES[14] and side == ARVN_SIDE then
        OBJECTIVES14_CAPTURE_TURN = current_turn()
    end

    ...

end


(Note: In the above, and elsewhere in the script, we refer to ARVN_SIDE, not VNA_SIDE. Okay, gotcha. The VNA had not yet morphed into the ARVN at this point. Although we refer to the VNA in this AAR, in the script we refer to ARVN. For purposes of this scenario, we use NVA and ARVN interchangeably.)

In the above, we make sure to give _MORTAR_41 special orders separate from the infantry unit orders. Remember this very important CSEE/SAI principle: Later orders in the command sequence override earlier orders. We use


Code: Select all

            halt(_MORTAR_41)


to override, effectively nullify any possibly earlier orders for that mortar unit. Followed by


Code: Select all

            if not at(_MORTAR_41, objective) then
                move_rush(_MORTAR_41, objective, NODIR, 0, 100)
            else
                fire_indirect_nearest_to_hex(_MORTAR_41, objective, 100, true)
            end


Those are the the final orders in the sequence, so they override any earlier orders.

Moving on to the second company.

The 2nd Company (the green circle in the screenshot preceding), _C2_1ST_2ND_RIFLE_COY_48_US_42, is assigned (in init_variables()) to defend, 50/50 chance for each, one or the other of two supply units (green squares, screenshot preceding):


Code: Select all

    _C2_1ST_2ND_RIFLE_COY_48_US_42_POST = random_pick({147, 488}) -- _SUPPLY_CACHE_147, _JUNGLE_FACTORY_488


The orders code for _C2_1ST_2ND_RIFLE_COY_48_US_42:


Code: Select all

    -- _C2_1ST_2ND_RIFLE_COY_48_US_42 -- C2/1st/2nd Rifle Company 48 - US
    do local units = _C2_1ST_2ND_RIFLE_COY_48_US_42
        if counter_exists(_C2_1ST_2ND_RIFLE_COY_48_US_42_POST) then
            local objective = counter_hex(_C2_1ST_2ND_RIFLE_COY_48_US_42_POST)
            if not within(units, objective, UPLEFTDIR, 2) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, UPLEFTDIR, 2, 50, true, DEFEND_STRONG)
            end
        else
            defend_weak(units)
        end
    end


_C2_1ST_2ND_RIFLE_COY_48_US_42 is to move (no rush) to one or the other of the assigned defensive posts, there to scatter and strongly defend in an up/leftward direction.

If its assigned supply unit ceases to exist (counter_exists() returns false), then the company should just weakly defend wherever it finds itself.

Moving on to the third company.

The 3rd Company (the yellow circle in the screenshot preceding), _C3_1ST_2ND_RIFLE_COY_48_US_47, is assigned to defend, 1/3 (33% chance) for each, any of three possible positions (yellow squares, screenshot preceding):


Code: Select all

    _C3_1ST_2ND_RIFLE_COY_48_US_47_POST = random_pick({"91,50", OBJECTIVES[28], OBJECTIVES[32]}) -- _SUPPLY_DEPOT_486 at "91,50"


The orders code for _C3_1ST_2ND_RIFLE_COY_48_US_47:


Code: Select all

    -- _C3_1ST_2ND_RIFLE_COY_48_US_47 -- C3/1st/2nd Rifle Company 48 - US
    do local units = _C3_1ST_2ND_RIFLE_COY_48_US_47
        local objective = _C3_1ST_2ND_RIFLE_COY_48_US_47_POST
        if _C3_1ST_2ND_RIFLE_COY_48_US_47_POST == "91,50" then -- defend _SUPPLY_DEPOT_486 at "91,50"
            if not within(units, objective, NODIR, 0) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, NODIR, 1, 50, false, DEFEND_STRONG)
            end
        elseif _C3_1ST_2ND_RIFLE_COY_48_US_47_POST == OBJECTIVES[32] then
            defend_way_point(units, {"92,51", objective}, DOWNLEFTDIR, 1, 100, DEFEND_STRONG)
        elseif _C3_1ST_2ND_RIFLE_COY_48_US_47_POST == OBJECTIVES[28] then
            if not at(units, objective) then
                move_way_point(units, {"89,56",objective}, NODIR, 0, 100) -- see _JUNKS_165 below
            else
                defend_strong(units, objective, DOWNDIR, 1, 100)
            end
        end
    end


Note the use of way points if moving (defending) to either OBJECTIVES[32] (the eastward yellow square) or OBJECTIVES[28] (the southward yellow square). Without the waypoint move, the game engine EAI was observed to send the units down the "wrong" side of the Minor Rivers (MR); they were unable to find their way to their intended destination. So we employ waypoint movement to nudge the units in the "right" direction. Waypoint movement is not needed when directed to defend the nearby supply unit.

I can see now a slight flaw in the above code. _SUPPLY_DEPOT_486 might scatter on arrival, as indeed it has in the screenshot above, to 91,51. Better to revise the code to show


Code: Select all

        if _C3_1ST_2ND_RIFLE_COY_48_US_47_POST == "91,50" then -- defend _SUPPLY_DEPOT_486 at "91,50"
            local objective = counter_hex(_SUPPLY_DEPOT_486)
            if not within(units, objective, NODIR, 0) then
                move_norush(units, objective, NODIR, 0, 100)
            else
                defend_scatter(units, objective, NODIR, 1, 50, false, DEFEND_STRONG)
            end


By reassigning 'objective' to the actual, scattered location of _SUPPLY_DEPOT_486, _C3_1ST_2ND_RIFLE_COY_48_US_47 will be slightly better positioned.

We have one more company to consider.

The orders code for _C4_1ST_2ND_DAI_DOI_HOA_LUC_48_81MM_52 (magenta circle, screenshot preceding) is simply


Code: Select all

    -- _C4_1ST_2ND_DAI_DOI_HOA_LUC_48_81MM_52 -- C4/1st/2nd Dai Doi Hoa Luc 48 - 81mm
    do local units = _C4_1ST_2ND_DAI_DOI_HOA_LUC_48_81MM_52
        defend_strong(units)
    end
    -- _MORTAR_53 -- VM 81mm Mortars
    fire_indirect_nearest(_MORTAR_53, 100, true)


_C4_1ST_2ND_DAI_DOI_HOA_LUC_48_81MM_52 is to defend strongly in place. _MORTAR_53 is given the overriding order (later in the sequence) to indirect fire at any nearest enemy.

A complex example overall. Some splendid variety and replayability in all of that! (Credit the scenario designer, Jason Petho, for the battle plan. Berto translated Jason's text battle plan into Lua code, with subsequent refinements based on actual testing.)
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Thus far, we have looked at the order assignments for the 1st/1st Binh Xuyen Infantry Battalion, the 1st/2nd Infantry Battalion, and the 1st/5th. We still need to review the battle plan for the 1st/3rd.

CSVN_AAR_AI_AI_RungSat42.jpg
CSVN_AAR_AI_AI_RungSat42.jpg (2.8 MiB) Viewed 3070 times

The Fire Support Company, the Reconnaissance Company, engineer units, and various other HQ, support, and non-combatant units are scattered about that area.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Here is the disposition of the 1st/3rd Infantry Battalion:

CSVN_AAR_AI_AI_RungSat43.jpg
CSVN_AAR_AI_AI_RungSat43.jpg (2.52 MiB) Viewed 3112 times

Unlike the other main-line infantry battalions, the 1st/3rd Battalion will be defending objective hexes, not supply units, jungle factories, etc.

The orders for the 1st, 3rd & 4th Companies are simple and straightforward:


Code: Select all

    -- _C1_1ST_3RD_RIFLE_COY_48_US_99 -- C1/1st/3rd Rifle Company 48 - US
    do local units = _C1_1ST_3RD_RIFLE_COY_48_US_99
        defend_strong(units)
    end
    -- _MORTAR_103 -- VM 50mm Mortars
    fire_indirect_nearest(_MORTAR_103, 100, true)

    ...

    -- _C3_1ST_3RD_RIFLE_COY_48_US_109 -- C3/1st/3rd Rifle Company 48 - US
    do local units = _C3_1ST_3RD_RIFLE_COY_48_US_109
        defend_strong(units)
    end

    -- _C4_1ST_3RD_DAI_DOI_HOA_LUC_48_81MM_114 -- C4/1st/3rd Dai Doi Hoa Luc 48 - 81mm
    do local units = _C4_1ST_3RD_DAI_DOI_HOA_LUC_48_81MM_114
        defend_strong(units)
    end
    -- _MORTAR_115 -- VM 81mm Mortars
    fire_indirect_nearest(_MORTAR_115, 100, true)


The infantry platoons are to stay put, and strongly defend in place.

The mortar platoons are to indirect fire at the nearest enemy, if any.

Notice that in specifying units, we did not use the difference() function to exclude the mortars. For the 1st Company, we did not for example do this:


Code: Select all

    -- _C1_1ST_3RD_RIFLE_COY_48_US_99 -- C1/1st/3rd Rifle Company 48 - US
    do local units = difference(_C1_1ST_3RD_RIFLE_COY_48_US_99, _MORTAR_103)
        defend_strong(units)
    end
    -- _MORTAR_103 -- VM 50mm Mortars
    fire_indirect_nearest(_MORTAR_103, 100, true)


Remember this CSEE maxim: Later orders in the command sequence override earlier orders.

In the first example (the actual code, the version without using difference()), yes, _MORTAR_103 is also ordered to defend_strong(), since it is part of _C1_1ST_3RD_RIFLE_COY_48_US_99, is included in 'units'. But in leaving that 'do ... end' code block, _MORTAR_103 is assigned a subsequent order, fire_indirect_nearest(), that pre-empts the earlier order. So in fact _MORTAR_103 does not defend_strong() (order in effect cancelled); it only does the fire_indirect_nearest().

Another important CSEE maxim: Units can have only one assigned order at a time.

If a second order (later in the sequence) is assigned, it bumps the first order. If the unit is assigned a third order, a fourth, and so on, each successive order bumps the previous one. At the end of the sequence, only the final order applies.

The 2nd Company is billeted in two difference locations, with one infantry platoon (and medium truck) posted at Objective 8, and the remainder of the Company sited at and around Objective 13.

_C2_1ST_3RD_RIFLE_COY_48_US_104 has a rather complex set of orders:


Code: Select all

    -- _C2_1ST_3RD_RIFLE_COY_48_US_104 -- C2/1st/3rd Rifle Company 48 - US
    do local units = _C2_1ST_3RD_RIFLE_COY_48_US_104
        defend_strong(units)
        -- _2ND_VM_RIFLE_48_106 moves to reinforce OBJECTIVES[13] if that place is attacked (50/50 chance; see on_hex_attack())
        -- but if the unit is attacked en route, just (unload and) defend in place ever thereafter (general defend_strong() order (see above) still applies)
        if are_attacked(_2ND_VM_RIFLE_48_106) then
            if are_loaded(_2ND_VM_RIFLE_48_106) then
                unload(_2ND_VM_RIFLE_48_106)
            end
        else
            if _2ND_VM_RIFLE_48_106_REINFORCE == TRUE then
                move_wary_way_point(_2ND_VM_RIFLE_48_106, {"50,6","58,11"}, UPLEFTDIR, 2, 100, 1)
                if at(_2ND_VM_RIFLE_48_106, "48,7") then -- still at initial location, the Military Post at 48,7
                    if not are_carried(_2ND_VM_RIFLE_48_106) then
                        load(join({_2ND_VM_RIFLE_48_106,_MEDIUM_TRUCK_154})) -- if general hold() order
                    end
                elseif within(_2ND_VM_RIFLE_48_106, "58,11", NODIR, 1) then
                    if are_carried(_2ND_VM_RIFLE_48_106) then
                        unload(_2ND_VM_RIFLE_48_106)
                    else
                        defend_strong(_2ND_VM_RIFLE_48_106, OBJECTIVES[13], NODIR, 1, 100)
                    end
                end
            end
        end
    end


The first order in the sequence, defend_strong(), applies to all of the Company's units, wherever they may be. The complications involve the _2ND_VM_RIFLE_48_106, initially at Objective 8, but


Code: Select all

        -- _2ND_VM_RIFLE_48_106 moves to reinforce OBJECTIVES[13] if that place is attacked (50/50 chance; see on_hex_attack())
        -- but if the unit is attacked en route, just (unload and) defend in place ever thereafter (general defend_strong() order (see above) still applies)


For _2ND_VM_RIFLE_48_106, we have


Code: Select all

        if are_attacked(_2ND_VM_RIFLE_48_106) then
            if are_loaded(_2ND_VM_RIFLE_48_106) then
                unload(_2ND_VM_RIFLE_48_106)
            end
        ...


In effect, that says that if _2ND_VM_RIFLE_48_106 is attacked while loaded up and traveling in a truck, it is to unload.

Interesting question: After it unloads, does it defend strong, normal, weak, ..., what? And where does it defend?

Remember that the first order in the _C2_1ST_3RD_RIFLE_COY_48_US_104 sequence is to defend_strong(). After an unload, _C2_1ST_3RD_RIFLE_COY_48_US_104 is ordered to defend_strong() by default in subsequent turns. Where because we don't specify an hc in the 'defend_strong(units)' order, _C2_1ST_3RD_RIFLE_COY_48_US_104 defends strong in place, wherever it finds itself.

Here, BTW, is how Manual/LUA_FUNCTIONS_REFERENCE.txt describes are_attacked():


Code: Select all

FUNCTION

    are_attacked (trackids)

DESCRIPTION

    For the given trackids list, returns whether every trackid in the list has been attacked at least once.

...


Else, if _C2_1ST_3RD_RIFLE_COY_48_US_104 has not been attacked, there is a 505/50 chance it will move to join the rest of the 1st/3rd Company in defending Objective 13:


Code: Select all

    -- _C2_1ST_3RD_RIFLE_COY_48_US_104 -- C2/1st/3rd Rifle Company 48 - US
    do local units = _C2_1ST_3RD_RIFLE_COY_48_US_104
        defend_strong(units)
        ...
        if are_attacked(_2ND_VM_RIFLE_48_106) then
           ...
        else
            if _2ND_VM_RIFLE_48_106_REINFORCE == TRUE then
                move_wary_way_point(_2ND_VM_RIFLE_48_106, {"50,6","58,11"}, UPLEFTDIR, 2, 100, 1)
                if at(_2ND_VM_RIFLE_48_106, "48,7") then -- still at initial location, the Military Post at 48,7
                    if not are_carried(_2ND_VM_RIFLE_48_106) then
                        load(join({_2ND_VM_RIFLE_48_106,_MEDIUM_TRUCK_154})) -- if general hold() order
                    end
                elseif within(_2ND_VM_RIFLE_48_106, "58,11", NODIR, 1) then
                    if are_carried(_2ND_VM_RIFLE_48_106) then
                        unload(_2ND_VM_RIFLE_48_106)
                    else
                        defend_strong(_2ND_VM_RIFLE_48_106, OBJECTIVES[13], NODIR, 1, 100)
                    end
                end
            end
        end
    end


Initially, _2ND_VM_RIFLE_48_106_REINFORCE is indeterminate, defaults to the Lua value 'nil'. How does it become TRUE? in the trigger function on_hex_attack():


Code: Select all

function on_hex_attack (hc, side, nation, attype) -- DO NOT REMOVE

    ...

    if not _2ND_VM_RIFLE_48_106_REINFORCE and -- determine this one time only
       member(hc, hexes_within(OBJECTIVES[13], NODIR, 1)) and
       side == ARVN_SIDE then
        if dieroll(100) <= 50 then -- 50/50 chance
            _2ND_VM_RIFLE_48_106_REINFORCE = TRUE
        else
            _2ND_VM_RIFLE_48_106_REINFORCE = FALSE
        end
    end

end


Translation:

  • if _2ND_VM_RIFLE_48_106_REINFORCE has not yet been defined (that is the default) ...
  • if the attacked hex is within 1 hex of OBJECTIVES[13] ...
  • if the attacking side is the ARVN (i.e., in this scenario, the VNA) ...
  • if a 100-sided die roll results in a number less or equal to 50 (happens half the time), then define _2ND_VM_RIFLE_48_106_REINFORCE as TRUE ...
  • else define _2ND_VM_RIFLE_48_106_REINFORCE to be FALSE

If _2ND_VM_RIFLE_48_106_REINFORCE is in effect, then _2ND_VM_RIFLE_48_106 is assigned an initial order to move warily to hex 58,11 by way of point 50,6.

BUT, if it is still at its initial location (has not been attacked), and is not carried, i.e., is not loaded onto a truck, then load onto the truck conveniently located at Objective 8.

ELSE if it is within 1 hex of hex 58,11, then unload there. And in subsequent turns, defend strongly within 1 hex of OBJECTIVES[13].

Whew! Lots of scripting for that just that one platoon (and its truck unit). Rather than micromanaging that unit to reinforce a (possible) VNA attack on Objective 13, we could instead simply order _2ND_VM_RIFLE_48_106 to defend Objective 8 and be done with it. Is the more complicated order sequence worth all the trouble? Maybe, maybe not. But it's great for variable game play, for replayability. And for purposes of this (tutorial) Rung Sat AAR, it is an interesting example of finely nuanced unit orders.

Does the code work? Is it bug free? The proof is in the testing (and ultimately, in actual human game play). We will get to testing in some later post.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

What about these guys?


Code: Select all

    _FIRE_SUPPORT_COY_50_82MM_128 = {130,131} -- [C] [2102227] Fire Support Company 50 - 82mm
    _MEDIUM_MACHINE_GUN_130 = {130} -- [P] [59,6] [212069] VM Medium Machine Gun (Soviet)
    _MEDIUM_MACHINE_GUN_131 = {131} -- [P] [59,6] [212069] VM Medium Machine Gun (Soviet)

    _RECONNAISSANCE_COY_50_133 = {134,135} -- [C] [2106201] Reconnaissance Company 50
    _THAM_BAO_134 = {134} -- [P] [58,5] [212049] VM Rifle Platoon 50 A (U.S. Arms)
    _THAM_BAO_135 = {135} -- [P] [58,5] [212049] VM Rifle Platoon 50 A (U.S. Arms)

CSVN_AAR_AI_AI_RungSat44.jpg
CSVN_AAR_AI_AI_RungSat44.jpg (2.39 MiB) Viewed 3086 times

In battle_plan_b(), their orders are given as follows:


Code: Select all

function battle_plan_b (turn, side)

    -- standard orders, uncomment as necessary:
    if turn == 1 then
        -- all Side B units hold initially (or maybe use halt())
        hold(ALLB)
    end

    ...

    -- _FIRE_SUPPORT_COY_50_82MM_128 -- Fire Support Company 50 - 82mm
    -- _RECONNAISSANCE_COY_50_133 -- Reconnaissance Company 50
    do local units = join({_FIRE_SUPPORT_COY_50_82MM_128, _RECONNAISSANCE_COY_50_133})
         defend_normal(units)
         if turn >= 25 then
            if objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            else
                -- attack any verified ARVN _3RD_ARTILLERY_BTN_55_105MM_413 units
                attack_org(units, _3RD_ARTILLERY_BTN_55_105MM_413, NODIR, 1, 100, ATTACK_NORMAL, true, false)
            end
        end
    end

...

end


Initially, they like all units are set to hold(). Under such orders, they are neither to move nor to fire (unless fired upon first). The idea is that they are to lurk off to the side, unnoticed, only springing to action later.

Notice use of the join() function to join _FIRE_SUPPORT_COY_50_82MM_128 with _RECONNAISSANCE_COY_50_133 into one combined ad hoc unit.

The combined unit is given the order to normal defend in place. This overrides the general hold() order.

Beginning on Turn 25:

If the VNA (in script terms, in effect the ARVN side) are in possession of OBJECTIVES[13] (yellow circle), in which case the BX Army REC/FS units are to strongly attack any enemy units at that objective.

Else, if OBJECTIVES[13] is not enemy held, the REC/FS units are to normal attack the enemy _3RD_ARTILLERY_BTN_55_105MM_413 wherever they are discovered to be.

What is this attack_org()? It is a so-called "uber function" defined in user.lua:



-

Code: Select all

- attack_org() -- attack the indicated opposing org

function attack_org (trackids, otrackids, dir, radius, actprob, attype, ground_only, unverified)

    if not trackids then return false end
    local tl
    if type(trackids) == "table" then
        if #trackids == 0 then return false end
        tl = trackids
    elseif type(trackids) == "number" then
        tl = {trackids}
    else
        return false
    end

    if not otrackids then return false end
    local ot
    if type(otrackids) == "table" then
        if #otrackids == 0 then return false end
        ot = otrackids
    elseif type(otrackids) == "number" then
        ot = {otrackids}
    else
        return false
    end
    ot = counters_aorg(ot, true, true) -- on map, verified only
    if #ot == 0 then
        return false
    end
    local ot_hexes = counter_hexes(ot)

    dir = dir or NODIR
    radius = radius or -1
    actprob = actprob or 50  -- by default, 50/50 chance of assault
    attype = attype or ATTACK_NORMAL
    ground_only = ground_only or false
    unverified = unverified or false

    local side = current_side()
    local other_side = other_side(side)

    local attacks = 0

    for i=1, #tl do
        local trackid = tl[i]
        local chex = counter_hex(trackid)
        local nearest_ot_hexes = hexes_nearest(chex, ot_hexes)
        local targethex_prev = hexai(trackid)
        local targethex
        local newtarget = true
        for nearest_ot_hexes=1, #nearest_ot_hexes do
            if targethex == targethex_prev then
                newtarget = false
                break
            end
        end
        if newtarget then
            targethex = random_pick(nearest_ot_hexes)
        end

        if attype == ATTACK_WEAK then
            attack_weak({trackid}, targethex, dir, radius, actprob)
        elseif attype == ATTACK_NORMAL then
            attack_normal({trackid}, targethex, dir, radius, actprob)
        elseif attype == ATTACK_STRONG then
            attack_strong({trackid}, targethex, dir, radius, actprob)
        elseif attype == ATTACK_BANZAI or
               attype == ATTACK_FANATICAL then
            attack_banzai({trackid}, targethex, dir, radius, actprob)
        end
        attacks = (attacks or 0) + 1
    end

    return (attacks > 0)

end


Oh! That looks dauntingly complicated!

It rather is. Here's the deal: You don't need to understand the uber functions, much less write your own. They are just there, in user.lua, free to use, or not. To tweak, or not. To roll your own, or not.

At this point, you can ignore what follows. But if you are curious, if you decide you might want to tweak this or make your own uber function(s) some day, let's look at attack_org() in detail.

For the 'trackids' input, this code


Code: Select all

    if not trackids then return false end
    local tl
    if type(trackids) == "table" then
        if #trackids == 0 then return false end
        tl = trackids
    elseif type(trackids) == "number" then
        tl = {trackids}
    else
        return false
    end


says:

  • If 'trackids' are not specified for some reason (like attack_org() was called without any arguments), then 'trackids' evaluates to nil, and the function call immediately terminates, returning the value 'false'.
  • If 'trackids' is a "table", i.e., a list, then set the local variable tl to that list.
  • Else if 'trackids' is a single trackid number, then by enclosing that number with { and } we make it a list (with just one member).
  • Else if 'trackids' is neither a list nor a number, it is something else (a character string, say), that is bad input, and the function terminates, returning the value 'false'.

For the 'otrackids' (opposing trackids) input, the following code


Code: Select all

    if not otrackids then return false end
    local ot
    if type(otrackids) == "table" then
        if #otrackids == 0 then return false end
        ot = otrackids
    elseif type(otrackids) == "number" then
        ot = {otrackids}
    else
        return false
    end
    ot = counters_aorg(ot, true, true) -- on map, verified only
    if #ot == 0 then
        return false
    end
    local ot_hexes = counter_hexes(ot)


is much like the preceding 'trackids' input code, but with these additions:

  • By means of the counters_aorg() function, we determine if the counters are on map (the first 'true') and verified, spotted (the second 'true'). If so, that list of on-map, verified trackids is set to the local variable 'ot'.
  • If the count of members in the 'ot' list is 0, that is to say there are no verified on-map opposing trackids, then there is nothing to attack, the function terminates, returning the value 'false'.
  • On the other hand, if there are verified on-map opposing trackids (for the VNA _3RD_ARTILLERY_BTN_55_105MM_413), then set 'ot_hexes' to the list of hexes occupied by those units.

Following, the code


Code: Select all

    dir = dir or NODIR
    radius = radius or -1
    actprob = actprob or 50  -- by default, 50/50 chance of assault
    attype = attype or ATTACK_NORMAL
    ground_only = ground_only or false
    unverified = unverified or false


sets various input parameter default values, in case any (or all) of them are omitted in the function call. In the actual function call


Code: Select all

                attack_org(units, _3RD_ARTILLERY_BTN_55_105MM_413, NODIR, 1, 100, ATTACK_STRONG, true, false)


we can see that none of the input parameters are omitted, so none of the defaults apply.

With


Code: Select all

    local side = current_side()
    local other_side = other_side(side)

    local attacks = 0


we set the local variable 'side' to the current side (for this phase), the local variable 'other_side' to the opposing side (for this phase). And we initialize the local variable 'attacks' to 0.

Next, we enter a Lua 'for' loop:

  • Beginning with the first unit in 'tl' (the current side's trackids list), for every unit in that 'tl' ...
  • Set the local variable (local to the 'for' loop) 'trackid' equal to the i'th member of the 'tl' list.
  • Set the local variable 'chex' to the location hex of the current (the i'th) unit.
  • Set the local variable 'nearest_ot_hexes' to the 'ot_hexes' nearest to 'chex'.
  • Set the local variable 'targethex_prev' to the hexai() of 'trackid'. (hexai() is an undocumented function internal to init.lua and not meant for ordinary, direct use.) The first time around, hexai() would evaluate to HEXUNKNOWN.
  • Declare a local variable, 'targethex', without any initialized value.
  • Set the local variable 'newtarget' to 'true'. (This resets to true every time around the 'for' loop.)
  • For every one of the 'nearest_ot_hexes', if any of them matches the previous 'newtarget', then we don't need a new target hex, we set 'newtarget' to false, and break out of the inner 'for' loop.
  • If we need a 'newtarget' (where that was not set to 'false' in the preceding 'for' loop), then we set the new 'targethex' to a random pick from the 'nearest_ot_hexes' list.

Whew! Complicated! (But remember: You need not understand the inner workings of the functions you call. Just use them!)

In other words, for each of 'our' guys, we assign them a new target on an as-needed basis from among the nearest targets. The algorithm tries to keep each unit on target, the same target, unless/until the target is destroyed, in which case a new target is assigned.

We then have the current one of 'our guys' attack the target weak, normal, strong, or fanatical as specified.

We also increment the 'attacks' counter by 1.

After for looping around all of 'our guys', the function attack_org() returns the number of assigned attacks.

Does it work? Let's see how many times this uber function has been used thus far:


Code: Select all

rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam/Scenarios
$ egrep -l "attack_org" *.lua
VN_550921_Rung_Sat.lua


Hmm. attack_org() has been used only once thus far (at the time of this writing), in this here VN_550921_Rung_Sat.lua file. That doesn't inspire much confidence! The proof is in the playtesting, but at this time I would guess this scenario hasn't been played all that much yet.

The proof is also in the auto-testing, which we will surely get to later in this Rung Sat AI vs. AI AAR.

If we discover flaws in attack_org(), we will fix them. If we can think of a better implementation, we will revise the function. That's the thing about the functions in user.lua: they are there for you, the user, to tinker with, and maybe add to.

But remember: Leave init.lua untouched!
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Then there are the odds & ends.

CSVN_AAR_AI_AI_RungSat45.jpg
CSVN_AAR_AI_AI_RungSat45.jpg (3.62 MiB) Viewed 3067 times

In the screenshot:

  • Red highlighted units are Supply Caches, Jungle Factories, etc. (Hard to pick out the red highlights, I know.) Their placements will differ, will randomly vary, from one game to another.
  • Unattached RCLRs & AAMGs (white circles).
  • Snipers (no highlight).
  • Junks, Sampans (no highlight), easy to spot since in waterway hexes.
  • Battalion HQs (color highlighted in earlier screenshots), typically positioned with their battalion combat units.

For the supply units, for example,


Code: Select all

    _LOGISTICS_COY_LEVEL_3_48_468 = {482,483,484,485,486,487,488,489,490} -- [C] [2109209] Logistics Company - Level 3 - 48
    _SUPPLY_CACHE_482 = {482} -- [P] [T1: 68,55] [218811] Supply Cache (VP)
    _SUPPLY_CACHE_483 = {483} -- [P] [T1: 60,59] [218811] Supply Cache (VP)
    _SUPPLY_CACHE_484 = {484} -- [P] [T1: 60,27] [218811] Supply Cache (VP)
    _SUPPLY_DEPOT_485 = {485} -- [P] [74,59] [218808] Fixed Supply Depot (VP)
    _SUPPLY_DEPOT_486 = {486} -- [P] [T1: 91,50] [218808] Fixed Supply Depot (VP)
    _SUPPLY_DEPOT_487 = {487} -- [P] [T1: 51,53] [218808] Fixed Supply Depot (VP)
    _JUNGLE_FACTORY_488 = {488} -- [P] [T1: 77,41] [218809] Jungle Factory
    _JUNGLE_FACTORY_489 = {489} -- [P] [T1: 59,47] [218809] Jungle Factory
    _JUNGLE_FACTORY_490 = {490} -- [P] [T1: 90,38] [218809] Jungle Factory


their orders are typically:


Code: Select all

    -- _LOGISTICS_COY_LEVEL_3_48_468 -- Logistics Company - Level 3 - 48
    do local units = _LOGISTICS_COY_LEVEL_3_48_468
        hold(units)
    end


That is, just hold in place, no move; supply units can't move (or fire) anyway.

For the RCLRs & AAMGs


Code: Select all

    _AAMG_491 = {491} -- [P] [75,57] [212065] VM Anti-Aircraft Machine Gun
    _AAMG_492 = {492} -- [P] [77,53] [212065] VM Anti-Aircraft Machine Gun
    _AAMG_493 = {493} -- [P] [77,43] [212065] VM Anti-Aircraft Machine Gun
    _AAMG_494 = {494} -- [P] [67,29] [212065] VM Anti-Aircraft Machine Gun
    _57MM_RCLR_500 = {500} -- [P] [84,44] [219006] 57mm Recoilless Rifles
    _57MM_RCLR_501 = {501} -- [P] [52,37] [219006] 57mm Recoilless Rifles


they are typically ordered to:


Code: Select all

    -- _AAMG_491 -- VM Anti-Aircraft Machine Gun
    do local units = _AAMG_491
        defend_normal(units)
    end

    ...

    -- _57MM_RCLR_500 -- 57mm Recoilless Rifles
    do local units = _57MM_RCLR_500
        defend_normal(units)
    end


They are to defend normally. Not strongly (much less fanatically) because they are not defending objectives or supply units. Rather, they hold tactically important positions along waterways, usually to intercept passing VNA riverines.

The snipers


Code: Select all

    _SNIPER_495 = {495} -- [P] [45,42] [212027] VM Sniper
    _SNIPER_496 = {496} -- [P] [44,51] [212027] VM Sniper
    _SNIPER_497 = {497} -- [P] [96,24] [212027] VM Sniper
    _SNIPER_498 = {498} -- [P] [58,26] [212027] VM Sniper
    _SNIPER_499 = {499} -- [P] [52,31] [212027] VM Sniper


are to, for example:


Code: Select all

    -- _SNIPER_495 -- VM Sniper
    do local units = _SNIPER_495
        defend_weak(units)
    end


Defend weakly in place. Fire at approaching enemy, but fade away if at risk of being assaulted.


The junks and sampans


Code: Select all

    _SAMPANS_157 = {157} -- [P] [74,29] [216808] Sampans
    _SAMPANS_158 = {158} -- [P] [75,32] [216808] Sampans
    _SAMPANS_159 = {159} -- [P] [74,30] [216808] Sampans
    _SAMPANS_160 = {160} -- [P] [86,41] [216808] Sampans
    _SAMPANS_161 = {161} -- [P] [88,45] [216808] Sampans
    _JUNKS_162 = {162} -- [P] [73,56] [216809] Junks
    _JUNKS_163 = {163} -- [P] [69,25] [216809] Junks
    _JUNKS_164 = {164} -- [P] [78,47] [216809] Junks
    _JUNKS_165 = {165} -- [P] [88,56] [216809] Junks
    _JUNKS_166 = {166} -- [P] [73,59] [216809] Junks


are generally to, for example:


Code: Select all

    -- _SAMPANS_161 -- Sampans
    do local units = _SAMPANS_161
        defend_weak(units)
    end

    -- _JUNKS_162 -- Junks
    do local units = _JUNKS_162
        defend_weak(units)
    end


Also defend weakly in place. Their purpose is to serve as handy ferries for the BX Army land units. But if threatened, they too are to yield.

A couple of Sampans


Code: Select all

    -- _JUNKS_163 -- Junks
    do local units = _JUNKS_163
        hold(units) -- ferries _C3_1ST_1ST_RIFLE_COY_48_US_16 across river
    end

    ...

    -- _JUNKS_165 -- Junks
    do local units = _JUNKS_165
        hold(units) -- ferries _C3_1ST_2ND_RIFLE_COY_48_US_47 across river
    end


are to help BX Army land units, _C3_1ST_1ST_RIFLE_COY_48_US_16 for example, to waypoint move down a waterway if called for. See for instance the _C3_1ST_1ST_RIFLE_COY_48_US_16 orders at

https://www.matrixgames.com/forums/view ... 0#p5001350

where it says


Code: Select all

                ...
                move_way_point(units, {"70,25",objective}, NODIR, 0, 100) -- see _JUNKS_163 below
                ...


Although attached to and positioned with units of their respective battalions, the battalion HQs are typically ordered to:


Code: Select all

    -- _TIEU_DOAN_HQ_36 -- VM Battalion HQ (foot)
    do local units = _TIEU_DOAN_HQ_36
        defend_weak(units)
    end


HQs too are to weakly defend if threatened by approaching enemy units.

If we have done things right, the initial Turn 1 general order


Code: Select all

    -- standard orders, uncomment as necessary:
    if turn == 1 then
        -- all Side B units hold initially (or maybe use halt())
        hold(ALLB)
    end


will be overridden, for every unit, on a company by company or platoon by platoon basis. (See all of the unit specific orders described above and in previous posts.)

How can we be sure that every unit has an overriding order beyond the initial hold() default? There is a standard CSEE method to check for that. But we will save it until after we have discussed auto-test runs.

That pretty much covers the BX Army battle_plan_b(). Is it a good battle plan, overall and in the details? In the course of this Rung Sat AAR, we aim to find out!
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

It is time to run our first auto-test!

Quoting the preceding post:

"How can we be sure that every unit has an overriding order beyond the initial hold() default? There is a standard CSEE method to check for that."

In user.lua, in the csee_check() function definition, we have this:


Code: Select all

-- csee_check() -- do diagnostic QA checks (usually called in on_next_phase()) (for diagnostic purposes; not intended for ordinary scripter/modder use)

function csee_check (turn, side, memceil)

    ...

    -- assume called from on_next_phase()
    log(APPLOG, LOG_DEBUG, "in " .. function_name() .. ", turn " .. turn .. ", for side " .. side .. ", counters with UNKNOWN_ORDER: " .. list_dump(counters_with_order(counters_all(side, true), UNKNOWN_ORDER)))
    log(APPLOG, LOG_DEBUG, "in " .. function_name() .. ", turn " .. turn .. ", for side " .. side .. ", counters with NO_ORDER: " .. list_dump(counters_with_order(counters_all(side, true), NO_ORDER)))
    log(APPLOG, LOG_DEBUG, "in " .. function_name() .. ", turn " .. turn .. ", for side " .. side .. ", counters with HOLD ORDER: " .. list_dump(counters_with_order(counters_all(side, true), HOLD)))
    log(APPLOG, LOG_DEBUG, "in " .. function_name() .. ", turn " .. turn .. ", for side " .. side .. ", counters with HALT ORDER: " .. list_dump(counters_with_order(counters_all(side, true), HALT)))

    ...

end


With every new phase, we log to APPLOG, engine.log, a list of trackids assigned the orders halt() or hold(), assigned an unknown order, or not assigned any order at all.

In init.ai, we have these definitions:


Code: Select all

UNKNOWN_ORDER        = -1
NO_ORDER             =  0
HOLD                 =  1
HALT                 =  2
DEFEND_WEAK          =  3
DEFEND_NORMAL        =  4
DEFEND_STRONG        =  5
DEFEND_FANATICAL     =  6
MOVE_ROAM            =  7
MOVE_RECON           =  8
MOVE_NORUSH          =  9
MOVE_RUSH            = 10
ATTACK_WEAK          = 11
ATTACK_NORMAL        = 12
ATTACK_STRONG        = 13
ATTACK_FANATICAL     = 14
LOAD                 = 15
UNLOAD               = 16
...


You don't need to concern yourself too much with this. We just note these details here for your reference.

After the early game "reinforcements" have arrived -- in particular, for Side B, after the BX Army supply units have been (randomly) placed -- we will want to check to see if all Side B units have assigned orders. If any units have not been assigned orders, that is a problem, potentially. It might be deliberate; it may be due to forgetfulness; in either case, we want to check for this.

We set a pass file



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "5b" > pass



to have the auto-test stop at the beginning of the Turn 5 Side B phase.

For now, we are focusing on the Side B scripting, the battle_plan_b() code. For now, we don't need the distraction of Side A movements. In VN_550921_Rung_Sat.lua, we deactivate all Side A movements by means of


Code: Select all

function battle_plan_a (turn, side)

    -- standard orders, uncomment as necessary:
    if turn >= 1 then
        -- all Side A units hold initially (or maybe use halt())
        hold(ALLA)
    end

--[[

    ...  [the Side A battle plan code]

--]]

end


That is, using the Lua '--[[' and '--]]' block comment markers, we comment out all of Side A's unit orders. Except for the general hold() order (which in normal course might be overridden).

It is important to note that the hold(), and the order deactivation, will not stop Side A reinforcements from arriving. They will arrive, but after arrival they will hold() in place.

With the preliminaries accomplished, we launch the auto-test with



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ ./vnengine.exe -W -7 -R -T -L3 VN_550921_Rung_Sat.scn



This says:

  • -W, run the game in windowed mode
  • -7, display at Zoom 7 (hotkey 7)
  • -R, log to the Logs folder in the RAM drive -- an advanced feature that you can ignore for now; in your auto-tests, just omit the '-R' option
  • -T, have the game auto-run in TestTrialPlay mode, i.e., run an auto-est
  • -L3, ratchet up the logging to LogLevel 3 (level 1 is the default)
  • VN_550921_Rung_Sat.scn, the scenario to auto-test

With that command, the auto-test commences, runs through the first four scenario turns and half of the fifth, then stops -- remember the pass file "5b" mentioned above -- at the start of Turn 5, Side B phase.

We now inspect the contents of the engine.log:



rober@Rob10rto /cygdrive/r/Temp/Logs
$ egrep "counters with.*ORDER" engine.log | egrep "for side b"
2022-06-11 07:46:53 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 1, for side b, counters with UNKNOWN_ORDER: {}
2022-06-11 07:46:53 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 1, for side b, counters with NO_ORDER: {}
2022-06-11 07:46:53 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 1, for side b, counters with HOLD ORDER: {2,3,5,7,8,9,10,12,13,14,15,17,18,19,20,22,23,24,25,36,38,39,40,41,43,44,45,48,49,50,53,54,55,67,69,70,71,74,75,76,79,80,81,82,84,85,86,95,96,98,100,101,102,103,105,106,107,110,11\
1,112,115,116,117,127,130,131,134,135,147,150,153,154,157,158,159,160,161,162,163,164,165,166,485,491,492,493,494,495,496,497,498,499,500,501}
2022-06-11 07:46:53 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 1, for side b, counters with HALT ORDER: {}
2022-06-11 07:47:21 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 2, for side b, counters with UNKNOWN_ORDER: {}
2022-06-11 07:47:21 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 2, for side b, counters with NO_ORDER: {}
2022-06-11 07:47:21 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 2, for side b, counters with HOLD ORDER: {15,74,75,76,95,146,147,163,165,485,488,489}
2022-06-11 07:47:21 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 2, for side b, counters with HALT ORDER: {}
2022-06-11 07:48:35 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 3, for side b, counters with UNKNOWN_ORDER: {}
2022-06-11 07:48:35 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 3, for side b, counters with NO_ORDER: {}
2022-06-11 07:48:35 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 3, for side b, counters with HOLD ORDER: {15,95,146,147,148,149,163,165,482,483,484,485,486,487,488,489,490}
2022-06-11 07:48:35 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 3, for side b, counters with HALT ORDER: {}
2022-06-11 07:49:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 4, for side b, counters with UNKNOWN_ORDER: {}
2022-06-11 07:49:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 4, for side b, counters with NO_ORDER: {}
2022-06-11 07:49:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 4, for side b, counters with HOLD ORDER: {95,146,147,148,149,163,165,482,483,484,485,486,487,488,489,490}
2022-06-11 07:49:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 4, for side b, counters with HALT ORDER: {}
2022-06-11 07:50:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 5, for side b, counters with UNKNOWN_ORDER: {}
2022-06-11 07:50:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 5, for side b, counters with NO_ORDER: {}
2022-06-11 07:50:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 5, for side b, counters with HOLD ORDER: {95,146,147,148,149,163,165,482,483,484,485,486,487,488,489,490}
2022-06-11 07:50:44 vnengine.exe: [DEBUG ID 10] (lua.cpp, line 856, l_log()) in csee_check(), turn 5, for side b, counters with HALT ORDER: {}



For Turn 5, the Side B forces have no units without orders (no NO_ORDER); no units with unknown orders (no UNKNOWN_ORDER); no units with the halt() order (no HALT ORDER).

We do, however, have a number of units with the hold() order. Let's see what they are:



rober@Rob10rto /cygdrive/r/Temp/Logs
$ for u in `echo "95,146,147,148,149,163,165,482,483,484,485,486,487,488,489,490" | sed 's/,/ /g'`; do egrep "_$u[^0-9]* = " "$VN"/Scenarios/VN_550921_Rung_Sat.lua; done
_SUPPLY_CACHE_95 = {95} -- [P] [42,55] [218811] Supply Cache (VP)
_SUPPLY_CACHE_146 = {146} -- [P] [T1: 82,28] [218811] Supply Cache (VP)
_SUPPLY_CACHE_147 = {147} -- [P] [70,33] [218811] Supply Cache (VP)
_SUPPLY_CACHE_148 = {148} -- [P] [T1: 73,25] [218811] Supply Cache (VP)
_JUNGLE_FACTORY_149 = {149} -- [P] [T1: 71,67] [218809] Jungle Factory
_JUNKS_163 = {163} -- [P] [69,25] [216809] Junks
_JUNKS_165 = {165} -- [P] [88,56] [216809] Junks
_SUPPLY_CACHE_482 = {482} -- [P] [T1: 68,55] [218811] Supply Cache (VP)
_SUPPLY_CACHE_483 = {483} -- [P] [T1: 60,59] [218811] Supply Cache (VP)
_SUPPLY_CACHE_484 = {484} -- [P] [T1: 60,27] [218811] Supply Cache (VP)
_SUPPLY_DEPOT_485 = {485} -- [P] [74,59] [218808] Fixed Supply Depot (VP)
_SUPPLY_DEPOT_486 = {486} -- [P] [T1: 91,50] [218808] Fixed Supply Depot (VP)
_SUPPLY_DEPOT_487 = {487} -- [P] [T1: 51,53] [218808] Fixed Supply Depot (VP)
_JUNGLE_FACTORY_488 = {488} -- [P] [T1: 77,41] [218809] Jungle Factory
_JUNGLE_FACTORY_489 = {489} -- [P] [T1: 59,47] [218809] Jungle Factory
_JUNGLE_FACTORY_490 = {490} -- [P] [T1: 90,38] [218809] Jungle Factory



Okay, that is some advanced Linux command-line wizardry. We don't expect you to understand that. If you like, you can make note of that command line and reuse it for your own purposes later. (If so, copy-paste the above; then copy-paste in your own trackids list, substituting for the '95,146,...,489,490' above.)

The supply units (that can't move or fire anyway) all have hold() orders. Also two junks, immobilized at key waterway crossings, there to serve as land unit ferries.

The results look good. It seems that, beyond the general hold() order, all non-supply etc. units have been assigned movement, combat etc. orders. We haven't overlooked any units. No unit has been left out.

If you recall the earlier discussion, we have


Code: Select all

    -- _FIRE_SUPPORT_COY_50_82MM_128 -- Fire Support Company 50 - 82mm
    -- _RECONNAISSANCE_COY_50_133 -- Reconnaissance Company 50
    do local units = join({_FIRE_SUPPORT_COY_50_82MM_128, _RECONNAISSANCE_COY_50_133})
        defend_normal(units)
        if turn >= 25 then
            if objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            else
                -- attack any verified ARVN _3RD_ARTILLERY_BTN_55_105MM_413 units
                attack_org(units, _3RD_ARTILLERY_BTN_55_105MM_413, NODIR, 1, 100, ATTACK_NORMAL, true, false)
            end
        end
    end


At Turn 25, Side B phase, the _FIRE_SUPPORT_COY_50_82MM_128 and _RECONNAISSANCE_COY_50_133 kick into action. Before they do that, that seems a good place to pause, to inspect if the other units have followed their orders properly (and if those orders are sensible). Accordingly, we substitute a new 'pass' file:



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "25b" > pass



We restart the auto-test action by means of toggling ON AI > Activate AI, else by clicking the Next Turn button.

The auto-test then plays out ... after a while stopping at Turn 25, Side B phase as directed.

We can follow along, observe the action as it happens. But in this instance, we just walk away, do other things. We are not focusing on process right now. For now, we are just considering the end results. Have the Side B units executed orders as expected? (And are those orders good ones?)

... [the auto-test runs, totally hands-off, for 5-10 minutes]

With the auto-test stopped at Turn 25, Side B phase, we see this:

CSVN_AAR_AI_AI_RungSat46.jpg
CSVN_AAR_AI_AI_RungSat46.jpg (3.03 MiB) Viewed 3049 times

On close inspection, it seems that everything went according to plan, all orders were carried out properly.

We did have one concern, however. _C2_1ST_3RD_RIFLE_COY_48_US_104 had been ordered to


Code: Select all

    -- _C2_1ST_3RD_RIFLE_COY_48_US_104 -- C2/1st/3rd Rifle Company 48 - US
    do local units = _C2_1ST_3RD_RIFLE_COY_48_US_104
        defend_strong(units)
        ...
    end


strongly defend in place, where that place is OBJECTIVES[13] (yellow circle in the screenshot). Why the move south? It's because at the very end of battle_plan_b() there is this:


Code: Select all

    if turn >= 8 then
        -- erstwhile OBJECTIVES[13] defenders
        local units = join({_1ST_VM_RIFLE_48_105, _3RD_VM_RIFLE_48_107, _MEDIUM_MACHINE_GUN_116, _DAC_CONG_127})
        if counter_exists(_SUPPLY_CACHE_484) then
            defend_strong(units, counter_hex(_SUPPLY_CACHE_484))
        else
            defend_strong(units)
        end
    end


The first two members of that units list are


Code: Select all

    _C2_1ST_3RD_RIFLE_COY_48_US_104 = {105,106,107} -- [C] [2102218] C2/1st/3rd Rifle Company 48 - US
    _1ST_VM_RIFLE_48_105 = {105} -- [P] [61,13] [212040] VM Rifle Platoon 48 (U.S. Arms)
    ...
    _3RD_VM_RIFLE_48_107 = {107} -- [P] [61,12] [212040] VM Rifle Platoon 48 (U.S. Arms)


(The _2ND_VM_RIFLE_48_106 is off to the west defending OBJECTIVES[8], unless called to reinforce the OBJECTIVES[13] defense.)

In effect, on Turn 8 (and thereafter), _C2_1ST_3RD_RIFLE_COY_48_US_104 & co. (green circles, preceding screenshot) are to abandon their defense of OBJECTIVES[13] and move southward to defend _SUPPLY_CACHE_484.

That Turn 8 is hard-coded; there is no conditionality. On Turn 8, what if OBJECTIVES[13] is under attack? Or what if the above group is attacked along the way to _SUPPLY_CACHE_484? Should they continue doggedly onward to _SUPPLY_CACHE_484, or instead defend in place in the interim? Or perhaps do something else? These are questions we might want to return to, to reconsider later.

We have stopped the game action at Turn 25. We set a new auto-test breakpoint to stop the game at the scenario's final turn with



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "40b" > pass



Then restart the action by clicking the Next Turn Toolbar button.

...

Over the course of the next 15 turns, we see the Side B BX Army units occasionally making slight defensive redeployments, but no major movements.

At the beginning of Turn 40, Side A phase, the action stops. This is something to know: Whether or not you have manually set an auto-test breakpoint at scenario's end, an auto-test will always stop at the final turn, Side A phase, also the subsequent Side B phase. This is so you can assess the situation from both sides at the end of the scenario.

The Side A deployments at scenario's end:

CSVN_AAR_AI_AI_RungSat47.jpg
CSVN_AAR_AI_AI_RungSat47.jpg (3.37 MiB) Viewed 3049 times

The yellow circled formations are paratroopers arriving as reinforcements in the scenario's early turns. They and any other Side A VNA units have not moved etc., because that side's battle plan orders have all been commented out (deactivated), remember?

We resume the auto-test action by toggling ON AI > Activate AI.

...

As expected, at the beginning of Turn 40, Side B phase (not shown), there is little significant change from Turn 25 before. (See the earlier screenshot.)

Two important things to note:

  • In auto-tests, (Enhanced) Fog of War applies. At any breakpoint, and at scenario's end, you want to see the game situation as one side or the other "sees" it. It will make less sense if the SAI plays half-blinded with full FOW while you are all-seeing. In auto-tests, you observe the action from the SAI's perspective (with full FOW).
  • No two auto-tests will play out exactly the same.

In this VN_550921_Rung_Sat scenario, the BX Army supply units are randomly placed. There is other randomness to shape the action. Bear that in mind in future auto-tests. Things will play out differently, especially when we reactivate the Side A action, of course.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

At this juncture, with Side B SAI'ed, we could designate this a Side A human player only scenario, call it quits.

We might then, as Side A human player, playtest this scenario vs. the scripted Side B AI.

We might do that. But we won't. For greater scenario replayability, for purposes of teaching deep CSEE/SAI technique, let's take on the challenge of scripting this scenario both ways!

We will focus on the easiest scripting first. To begin, we will SAI the Side A VNA (not NVA!) 5th Parachute Battalion, circled in light blue in the screenshot following:

CSVN_AAR_AI_AI_RungSat48.jpg
CSVN_AAR_AI_AI_RungSat48.jpg (2.17 MiB) Viewed 2740 times

The 5th Parachute Battalion orgs and trackids, as specified in the VN_550921_Rung_Sat.lua set_org_lists():


Code: Select all

    _5TH_PARACHUTE_BTN_55_A_441 = {442,462,444,445,446,448,449,450,452,453,454,456,457,459} -- [B] [1112228] 5th Parachute Battalion 55 - A

    _HQ_442 = {442} -- [P] [T4: 67,12] [113021] VNAF Airborne Battalion HQ (foot)

    _5TH_PARACHUTE_462 = {462} -- [P] [T4: 67,12] [114016] Airborne Commander 2

    _1ST_5TH_PARACHUTE_COY_55_A_443 = {444,445,446} -- [C] [1102243] 1st/5th Parachute Company 55 - A
    _1ST_PLT_444 = {444} -- [P] [T3: 66,12] [112022] Parachute Platoon 55 A
    _2ND_PLT_445 = {445} -- [P] [T3: 66,12] [112022] Parachute Platoon 55 A
    _3RD_PLT_446 = {446} -- [P] [T3: 66,12] [112022] Parachute Platoon 55 A

    _2ND_5TH_PARACHUTE_COY_55_A_447 = {448,449,450} -- [C] [1102243] 2nd/5th Parachute Company 55 - A
    _1ST_PLT_448 = {448} -- [P] [T3: 67,13] [112022] Parachute Platoon 55 A
    _2ND_PLT_449 = {449} -- [P] [T3: 67,13] [112022] Parachute Platoon 55 A
    _3RD_PLT_450 = {450} -- [P] [T3: 67,13] [112022] Parachute Platoon 55 A

    _3RD_5TH_PARACHUTE_COY_55_A_451 = {452,453,454} -- [C] [1102243] 3rd/5th Parachute Company 55 - A
    _1ST_PLT_452 = {452} -- [P] [T3: 67,11] [112022] Parachute Platoon 55 A
    _2ND_PLT_453 = {453} -- [P] [T3: 67,11] [112022] Parachute Platoon 55 A
    _3RD_PLT_454 = {454} -- [P] [T3: 67,11] [112022] Parachute Platoon 55 A

    _5TH_PARACHUTE_WEAPONS_COY_55_455 = {456,457,459} -- [C] [1102245] 5th Parachute Weapons Company 55
    _ENGINEER_456 = {456} -- [P] [T4: 66,11] [112080] Parachute Engineer Platoon
    _MMG_457 = {457} -- [P] [T4: 66,11] [112077] Parachute M1919A4 Machine Gun
    _81MM_MORTAR_459 = {459} -- [P] [T4: 66,11] [111033] Parachute M1 81mm Mortars

Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Note that, as with Side B (see previous posts), Side A's units, all of them, are initially given the general order to hold(), i.e., not move or fire or take any other action:


Code: Select all

function battle_plan_a (turn, side)

    -- standard orders, uncomment as necessary:
    if turn >= 1 then
        -- all Side A units hold initially (or maybe use halt())
        hold(ALLA)
    end

    ...

end


Further down battle_plan_a() (bypassing other unit orders), here is the code (as of CSVN 1.21) directing the _1ST_5TH_PARACHUTE_COY_55_A_443 orders:


Code: Select all

    -------------------------------------------------------------------
    -- _5TH_PARACHUTE_BTN_55_A_441 -- 5th Parachute Battalion 55 - A --
    -------------------------------------------------------------------

    if not _5TH_PARACHUTE_BTN_55_A_441_READY_TURN and -- set one time only
       are_not_disrupted(_5TH_PARACHUTE_BTN_55_A_441) then
        _5TH_PARACHUTE_BTN_55_A_441_READY_TURN = turn
    end

    ...

    -- _1ST_5TH_PARACHUTE_COY_55_A_443 -- 1st/5th Parachute Company 55 - A
    do local units = _1ST_5TH_PARACHUTE_COY_55_A_443
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
--          not the following, because after being forced out of OBJECTIVES[13], enemy units might remain in the vicinity
--            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
--              attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif objective_owner(OBJECTIVES[8]) == BX_SIDE then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_WEAK)
            end
        end
    end


Refer to this screenshot in the commentary that follows:

CSVN_AAR_AI_AI_RungSat49.jpg
CSVN_AAR_AI_AI_RungSat49.jpg (2.18 MiB) Viewed 2739 times

_5TH_PARACHUTE_BTN_55_A_441 (circled in light blue) arrives as reinforcements several turns into the scenario:


Code: Select all

    ...

    _1ST_5TH_PARACHUTE_COY_55_A_443 = {444,445,446} -- [C] [1102243] 1st/5th Parachute Company 55 - A
    _1ST_PLT_444 = {444} -- [P] [T3: 66,12] [112022] Parachute Platoon 55 A
    _2ND_PLT_445 = {445} -- [P] [T3: 66,12] [112022] Parachute Platoon 55 A
    _3RD_PLT_446 = {446} -- [P] [T3: 66,12] [112022] Parachute Platoon 55 A

    ...


Note where the _1ST_5TH_PARACHUTE_COY_55_A_443 platoons are paradropped on Turn 3 (T3) scattered around hex 66,12. All paradropped units are disrupted on arrival. Only when 'are_not_disrupted(_5TH_PARACHUTE_BTN_55_A_441)' is true, and only if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN has no previously assigned value, only then is _5TH_PARACHUTE_BTN_55_A_441_READY_TURN assigned the value 'turn', where 'turn' is the first parameter input to 'battle_plan_a (turn, side)'. In subsequent turns, _5TH_PARACHUTE_BTN_55_A_441_READY_TURN has a value, is not nil, therefore 'not _5TH_PARACHUTE_BTN_55_A_441_READY_TURN' is not true. This is a mechanism to ensure that _5TH_PARACHUTE_BTN_55_A_441_READY_TURN is "set one time only" (see the comment in the code above).

When the code processing gets to


Code: Select all

    do local units = _1ST_5TH_PARACHUTE_COY_55_A_443
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            [orders code]
        end
    end


the orders code is given, the body of the 'if ... end' is entered, only if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN has a value, is non nil ('nil' is equivalent to 'false', in this context).

(Bear with me if the code's meaning is obvious ... to you. Not everybody will have previous coding experience. Probably most of the people reading this are new to coding, and to Lua scripting. The patient and careful -- and verbose! -- explanation of every little thing might be necessary. Better if this were a classroom, and if we could whiteboard and body language things. Answer questions. Repeat and maybe reword as needed. But this is what it is.)

To make a long story short, _5TH_PARACHUTE_BTN_55_A_441 is to stay put, not spring to action until all units in the organization have undisrupted.

It doesn't have to be this way. It could be that individual units go into action when ready, with slow to undisrupt units joining the fray at some later turn. But that is committing to attack piecemeal, which is generally a bad idea. No, we won't have this (or any other) company move to attack until every platoon in the battalion is ready to move and attack also.

Earlier in this scenario's script development, the code had been


Code: Select all

            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
                ...
            end


If the BX Army Side B owns (occupies) OBJECTIVES[13] (the nearer yellow circle in the screenshot), then _1ST_5TH_PARACHUTE_COY_55_A_443 is to strongly attack any enemy at that location unless and until the objective is taken.

In subsequent auto-testing, it was observed where the enemy might be pushed away from _1ST_5TH_PARACHUTE_COY_55_A_443 but remain lurking somewhere nearby, maybe to return and retake a possibly ungarrisoned OBJECTIVES[13]. We also don't want _1ST_5TH_PARACHUTE_COY_55_A_443 to leave prematurely if the fight around _1ST_5TH_PARACHUTE_COY_55_A_443 still rages. These are common SAI problems.

One solution is instead to


Code: Select all

            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
--          not the following, because after being forced out of OBJECTIVES[13], enemy units might remain in the vicinity
--            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
--              attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif ... then
                ...
            end


That is, don't just attack units at OBJECTIVES[13]. Attack (strongly) any units found to be within 2 hexes of OBJECTIVES[13]. If enemy units are three hexes away (or the erstwhile defenders have all been elminated), only then consider the orders in the subsequent 'elseif ... end'.

Note where we have left in place the earlier orders, but commented them out. This serves as a reminder why the simpler 'attack_strong(...)' is not a good idea in this instance.

If no Side B enemy unit is closer to OBJECTIVES[13] than 3 hexes, _1ST_5TH_PARACHUTE_COY_55_A_443 is to consider the 'elseif ...' code:


Code: Select all

            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                ...
            elseif objective_owner(OBJECTIVES[8]) == BX_SIDE then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_WEAK)
            end


If OBJECTIVES[8] (in the screenshot, the farther yellow circle) is BX Army Side B owned (whether or not currently enemy occupied), then _1ST_5TH_PARACHUTE_COY_55_A_443 is to move to that objective hex by way of hex 50,6. From there, launch a weak attack and attempt to take OBJECTIVES[8].

Then what? you might be wondering. What if OBJECTIVES[8] and OBJECTIVES[13] have both been taken? What is _1ST_5TH_PARACHUTE_COY_55_A_443 then supposed to do? Good questions. Without any further instructions, _1ST_5TH_PARACHUTE_COY_55_A_443 would revert to the general hold() order, applied at Turn 1 and repeated by default every turn thereafter. The hold() order says not to move but also not to fire. Hmm, it may be a better idea to instead have battle_plan_a() to begin with


Code: Select all

function battle_plan_a (turn, side)

    -- standard orders, uncomment as necessary:
    if turn >= 1 then
        halt(ALLA)
    end

    ...

end


Because if any enemy approaches, we want the ability to fire back, at least. (Beyond that, on a case by case basis, we might want to give individual companies the flexibility to move in addition to fire. We shall see.)

Changing the Side A general order from hold() to halt(): we will make that change!
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Next up, we consider the _3RD_5TH_PARACHUTE_COY_55_A_451 SAI orders.

In set_org_lists(), we have:


Code: Select all

    _3RD_5TH_PARACHUTE_COY_55_A_451 = {452,453,454} -- [C] [1102243] 3rd/5th Parachute Company 55 - A
    _1ST_PLT_452 = {452} -- [P] [T3: 67,11] [112022] Parachute Platoon 55 A
    _2ND_PLT_453 = {453} -- [P] [T3: 67,11] [112022] Parachute Platoon 55 A
    _3RD_PLT_454 = {454} -- [P] [T3: 67,11] [112022] Parachute Platoon 55 A


Refer to this screenshot in the commentary that follows:

CSVN_AAR_AI_AI_RungSat50.jpg
CSVN_AAR_AI_AI_RungSat50.jpg (2.18 MiB) Viewed 2605 times

Similar to _1ST_5TH_PARACHUTE_COY_55_A_443, the _3RD_5TH_PARACHUTE_COY_55_A_451 (circled in light blue, screenshot preceding) is paradropped at Turn 3 scattered about hex 67,11 (different from the other company's 66,12 central drop point).

Because _3RD_5TH_PARACHUTE_COY_55_A_451 is a component sub-organization of its parent battalion, _5TH_PARACHUTE_BTN_55_A_441, this too applies:


Code: Select all

    -------------------------------------------------------------------
    -- _5TH_PARACHUTE_BTN_55_A_441 -- 5th Parachute Battalion 55 - A --
    -------------------------------------------------------------------

    if not _5TH_PARACHUTE_BTN_55_A_441_READY_TURN and -- set one time only
       are_not_disrupted(_5TH_PARACHUTE_BTN_55_A_441) then
        _5TH_PARACHUTE_BTN_55_A_441_READY_TURN = turn
    end


Again (see the earlier discussion), this serves to ensure that _3RD_5TH_PARACHUTE_COY_55_A_451 doesn't take action unless/until all of the battalion's companies are undisrupted (after the disrupting initial paradrop).

The orders code for _3RD_5TH_PARACHUTE_COY_55_A_451:


Code: Select all

    -- _3RD_5TH_PARACHUTE_COY_55_A_451 -- 3rd/5th Parachute Company 55 - A
    do local units = _3RD_5TH_PARACHUTE_COY_55_A_451
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_way_point(units, {"67,7", OBJECTIVES[17]}, DOWNDIR, 2, 50, ATTACK_WEAK)
            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_weak(units, OBJECTIVES[16], DOWNDIR, 2, 50)
            end
        end
    end


This company's orders are simpler than the earlier company's. If the BX Army Side B owns OBJECTIVES[13], then _3RD_5TH_PARACHUTE_COY_55_A_451 is, together with the rest of the battalion, to strongly attack that objective. But with this important difference: Don't stick around if enemy units remain within 2 hexes of OBJECTIVES[13]. Rather, if OBJECTIVES[13] is taken, shift focus immediately to the next objective. It is assumed that the other companies will carry on the fight around OBJECTIVES[13].

_3RD_5TH_PARACHUTE_COY_55_A_451's secondary objective is OBJECTIVES[17]. In order to get there, the company way point moves via hex 67,7. This is tricky. When the primary objective, OBJECTIVES[13], is taken, is _3RD_5TH_PARACHUTE_COY_55_A_451 on the west side or east side of the minor river (MR)? If on the west side (likely), we need to provide the hint to way point move by way of hex 67,7. If the order instead was more simply to


Code: Select all

            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_weak(units, {OBJECTIVES[17]}, DOWNDIR, 2, 50)


there is a chance that _3RD_5TH_PARACHUTE_COY_55_A_451 would remain stuck on the west side of the minor river. The EAI's (Engine AI's) path finding is unable to "see" across rivers and other major waterways. In this case, we need to give the EAI an assist by using attack_way_point().

If on the east side of the minor river, no way point hint should be needed.

Only if OBJECTIVES[17] is taken, only then redirect the company to


Code: Select all

            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_weak(units, OBJECTIVES[16], DOWNDIR, 2, 50)


If OBJECTIVES[17] was earlier taken, since _3RD_5TH_PARACHUTE_COY_55_A_451 is already in the vicinity, there is no need to use attack_way_point() in this final case.

Pay attention to the use of DOWNDIR in these attacks on OBJECTIVES[17] and OBJECTIVES[16], especially in the former case. If we are not careful, the EAI might have _3RD_5TH_PARACHUTE_COY_55_A_451 position in between the two objectives, thus the VNA units would be taking fire from BX Army enemy both to the front and from the rear. In fact, we might even do this


Code: Select all

            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_way_point(units, {"67,7", OBJECTIVES[17]}, DOWNRIGHTDIR, 2, 50, ATTACK_WEAK)
            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_weak(units, OBJECTIVES[16], DOWNLEFTDIR, 2, 50)


changing DOWNDIR to DOWNRIGHTDIR in the first case, and DOWNDIR to DOWNLEFTDIR (or even UPLEFTDIR!) in the second. This would ensure maximum distance away from one objective when attacking the other.

OBJECTIVES[17] and OBJECTIVES[16] are strongholds, the former surrounded by stone walls (SN), the latter surrounded by crests (CR). It is unlikely that by itself a single company, _3RD_5TH_PARACHUTE_COY_55_A_451, can take one of the objectives, much less both of them. This is true even though, as we shall later see, the battalion's weapons company will be supporting _3RD_5TH_PARACHUTE_COY_55_A_451's attacks. Rather than attack_strong() these two objectives, which might be far too bloody, we have _3RD_5TH_PARACHUTE_COY_55_A_451 attack_weak(). No taking either of these objectives at all cost!

Also note the use of 2 as the attack radius. When doing an attack_weak(), we don't need to, or want to, draw closer to an objective when the purpose is really to harass (hence the attack_weak). (The 50 is the so-called actprob value, which anyway defaults to 50 in the attack_weak() case. More on actprob some other time.)

As will be demonstrated later, the _2ND_5TH_PARACHUTE_COY_55_A_447 has a difficult assignment attacking objectives far to the south. An interesting alternative would be to have _2ND_5TH_PARACHUTE_COY_55_A_447 joing together with _3RD_5TH_PARACHUTE_COY_55_A_451 to attack_normal() or even attack_strong() OBJECTIVES[17] and OBJECTIVES[16]. We will keep this in mind, maybe implement this alternative strategy depending on the results of later auto-tests (or even human playtests). In fact, we will likely make this change, to increase the scenario's replayability, also as an interesting SAI exercise.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Since _3RD_5TH_PARACHUTE_COY_55_A_451 has a difficult assignment, we will order _5TH_PARACHUTE_WEAPONS_COY_55_455 to assist the 3rd/5th Company in their attack on OBJECTIVES[17] and OBJECTIVES[16].

For _5TH_PARACHUTE_WEAPONS_COY_55_455, in set_org_lists() we have


Code: Select all

    _5TH_PARACHUTE_WEAPONS_COY_55_455 = {456,457,459} -- [C] [1102245] 5th Parachute Weapons Company 55
    _ENGINEER_456 = {456} -- [P] [T4: 66,11] [112080] Parachute Engineer Platoon
    _MMG_457 = {457} -- [P] [T4: 66,11] [112077] Parachute M1919A4 Machine Gun
    _81MM_MORTAR_459 = {459} -- [P] [T4: 66,11] [111033] Parachute M1 81mm Mortars


Refer to this screenshot in the commentary that follows:

CSVN_AAR_AI_AI_RungSat51.jpg
CSVN_AAR_AI_AI_RungSat51.jpg (2.18 MiB) Viewed 2593 times

_5TH_PARACHUTE_WEAPONS_COY_55_455 (circled in light blue, screenshot above) is paradropped at Turn 4 -- a turn later than the other 5th Parachute Battalion companies -- scattered about hex 66,11 (also different).

The same comments about arriving disrupted (and scattered), not going into action until the entire battalion is undisrupted etc. apply. See above.

The _5TH_PARACHUTE_WEAPONS_COY_55_455's orders code (and omitting for now the special mortars orders):


Code: Select all

    -- _5TH_PARACHUTE_WEAPONS_COY_55_455 -- 5th Parachute Weapons Company 55
    do local units = _5TH_PARACHUTE_WEAPONS_COY_55_455
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_way_point(units, {"67,7", OBJECTIVES[17]}, DOWNRIGHTDIR, 2, 50, ATTACK_WEAK)
            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_weak(units, OBJECTIVES[16], DOWNRIGHTDIR, 2, 50)
            end
        end
    end
    ...  [special mortars orders]


Hmm, that looks familiar. It should. Those are the exact same orders we have for _3RD_5TH_PARACHUTE_COY_55_A_451.

Since they are identical, why don't we shorten the code, combining the two companies' orders into a single 'do ... end' code block:


Code: Select all

    -- _3RD_5TH_PARACHUTE_COY_55_A_451 -- 3rd/5th Parachute Company 55 - A
    -- _5TH_PARACHUTE_WEAPONS_COY_55_455 -- 5th Parachute Weapons Company 55
    do local units = join(_3RD_5TH_PARACHUTE_COY_55_A_451, _5TH_PARACHUTE_WEAPONS_COY_55_455)
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_way_point(units, {"67,7", OBJECTIVES[17]}, DOWNDIR, 2, 50, ATTACK_WEAK)
            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_weak(units, OBJECTIVES[16], DOWNDIR, 2, 50)
            end
        end
    end


Note the use of the join() function to join the two company org lists into one single, combined list.

Combined, that has the benefit of reducing code bloat. Shorter code is usually more readable code. Shorter code is less likely to be buggy.

Those are the pros. The con is less flexibility. Maybe we want the weapons company to take slightly different action from the infantry company, attack_normal() instead of attack_weak() for instance, or maybe attack from a different direction (DOWNDIR vs. DOWNRIGHTDIR maybe), or at a different attack radius (or different actprob).

We will keep them separate for now, anticipating _2ND_5TH_PARACHUTE_COY_55_A_447 possibly attacking northward vs. attacking southward, as mentioned in the previous post. With that option, we would have three companies attacking OBJECTIVES[17] and OBJECTIVES[16]. With more units, we have more possibilities. Thus, we keep _5TH_PARACHUTE_WEAPONS_COY_55_455's orders separate from _3RD_5TH_PARACHUTE_COY_55_A_451's.

We reserve the right to second all of this based on auto-testing, and eventual human playtesting.

Whether combined or not, we will definitely want to separate out the mortars orders code. We don't want mortars getting too close to the front-line action, much less do we want them assaulting.

For the mortars we have:


Code: Select all

    -- _81MM_MORTAR_459 -- Parachute M1 81mm Mortars
    if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
        fire_indirect_nearest_to_hex(_81MM_MORTAR_459, OBJECTIVES[13], 100)
    else
        if not at(_81MM_MORTAR_459, "69,6") then
            move_way_point(_81MM_MORTAR_459, {"66,7", "69,6"}, NODIR, 0, 100)
        else
            fire_indirect_nearest_to_hex(_81MM_MORTAR_459, "71,3", 100)
        end
    end


After landing (arriving as reinforcements via paradrop), and so long as the fight around OBJECTIVES[13] rages, _81MM_MORTAR_459 is to stay put wherever it landed, indirect firing at any enemy units nearest to that objective.

Else if the fight for OBJECTIVES[13] and environs is effectively finished, _81MM_MORTAR_459 is to way point move to hex 69,6 by way of intermediate hex 66,7 (a hint to the EAI with possible minor river (MR) crossing). Once there, _81MM_MORTAR_459 is to fire at any BX Army Side B units nearest to hex 71,3, the hex in between OBJECTIVES[16] and OBJECTIVES[17].

For the VNA 5th Parachute Battalion, three companies down, one more to go ...
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

For _2ND_5TH_PARACHUTE_COY_55_A_447, set_org_lists() shows


Code: Select all

    _2ND_5TH_PARACHUTE_COY_55_A_447 = {448,449,450} -- [C] [1102243] 2nd/5th Parachute Company 55 - A
    _1ST_PLT_448 = {448} -- [P] [T3: 67,13] [112022] Parachute Platoon 55 A
    _2ND_PLT_449 = {449} -- [P] [T3: 67,13] [112022] Parachute Platoon 55 A
    _3RD_PLT_450 = {450} -- [P] [T3: 67,13] [112022] Parachute Platoon 55 A


Refer to this screenshot in the commentary that follows:

CSVN_AAR_AI_AI_RungSat52.jpg
CSVN_AAR_AI_AI_RungSat52.jpg (2.17 MiB) Viewed 2557 times

_2ND_5TH_PARACHUTE_COY_55_A_447 (circled in light blue, screenshot above) is paradropped at Turn 3 scattered about hex 67,13 (also different).

The same comments about arriving disrupted (and scattered), not going into action until the entire battalion is undisrupted, etc. apply. See above.

_2ND_5TH_PARACHUTE_COY_55_A_447 is ordered thusly:


Code: Select all

    -- _2ND_5TH_PARACHUTE_COY_55_A_447 -- 2nd/5th Parachute Company 55 - A
    do local units = _2ND_5TH_PARACHUTE_COY_55_A_447
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
--          not the following, because after being forced out of OBJECTIVES[13], enemy units might remain in the vicinity
--            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
--              attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif not_occupied("67,29", ARVN_SIDE) then
                attack_way_point(units, {"67,15", "66,18", "68,22", "67,26", "67,29"}, NODIR, 1, 50, ATTACK_WEAK)
            else
                if not _ARVN_6729_GARRISON then -- one time only selection
                    _ARVN_6729_GARRISON = random_pick(counters_weakest(units))
                end
                if _ARVN_6729_GARRISON then
                    if not_occupied("63,31", ARVN_SIDE) then
                        attack_weak(difference(units, _ARVN_6729_GARRISON), "63,31", NODIR, 1, 50)
                    else
                        attack_scatter(difference(units, _ARVN_6729_GARRISON), "61,28", NODIR, 5, 50, false, ATTACK_WEAK)
                    end
                end
            end
        end
    end


Like the rest of the 5th Parachute Battalion, the 2nd/5th is to join in the attack on OBJECTIVES[13]. Like the 1st/5th (see above), the 2nd Company is to stick around long enough to ensure that the enemy is cleared out -- that no enemy units remain within 2 hexes of the objective -- before moving on to its secondary objective. (Note where here too we have left in place, as comments, the earlier orders coding for this company.)

_2ND_5TH_PARACHUTE_COY_55_A_447 helps to secure OBJECTIVES[13]. Once secured, the company is thereafter to weakly attack the bunker far to the south at hex 67,29 (the easternmost hex among the three yellow circled hexes near the bottom of the screenshot).

Questions: How does the 2nd/5th know that there is a bunker at that location? Why would the VNA consider that hex an area of special interest? (Side A, aka the ARVN_SIDE, would know whether it occupies that hex, of course.)

Good questions. A quick check in the CSVN scenario editor, with Map > Toggle Awareness Overlay set to ON, shows:

CSVN_AAR_AI_AI_RungSat53.jpg
CSVN_AAR_AI_AI_RungSat53.jpg (307.58 KiB) Viewed 2561 times

That is, hex 67,29 (also hex 63,31) is set to 'B', i.e., Side B awareness only. Side A is formally unaware of that hex, the bunker, and anything else within it. (If Side A had awareness, it would show 'A' or perhaps even 'AB'.)

Why then does the first screenshot above display the bunker there? It is because in auto-test mode, although the auto-test mechanism does not display enemy units, it does display hidden terrain features such as bunkers and other fortifications. If we are observing an auto-test and we see units moving to a place, we want to see a possible reason why. In actual game play, a Side A human player would not see the bunker displayed. (I know this having checked, in the game engine, what Side A sees in H2H play.)

But that still begs the question: How does Side A know a bunker is there? For purposes of this scripting exercise, it just does. We might assume that is so by way of spies or by some other military intelligence. (It might be interesting to SAI this scenario differently, where the Side A VNA forces are entirely ignorant of bunkers etc.; where the _2ND_5TH_PARACHUTE_COY_55_A_447 wanders generally southward without any specific destination in mind. We leave that scripting exercise to the reader. <wink>)

If you study the map terrain (see the screenshot above), you might see where the route from OBJECTIVES[13] down to hex 67,29 runs alongside or over (at fords) minor rivers (MR). If instead we had


Code: Select all

            elseif not_occupied("67,29", ARVN_SIDE) then
                attack_weak(units, "67,29", NODIR, 1, 50)


the EAI pathfinding might misdirect _2ND_5TH_PARACHUTE_COY_55_A_447 into cul-de-sacs. So instead we use attack_way_point() giving helpful hints where the 2nd/5th should move. In particular, we need to make sure to have hex 67,26 in the list of waypoints because of the ford (FD) there. Without that all important waypoint, the EAI could not guide _2ND_5TH_PARACHUTE_COY_55_A_447 through the final leg of its journey. (The EAI cannot "see" across waterways, remember?)

Else if the ARVN_SIDE (the Side A VNA) occupy hex 67,29 (have taken and hold the bunker), then


Code: Select all

            else
                if not _ARVN_6729_GARRISON then -- one time only selection
                    _ARVN_6729_GARRISON = random_pick(counters_weakest(units))
                end
                if _ARVN_6729_GARRISON then
                    if not_occupied("63,31", ARVN_SIDE) then
                        attack_weak(difference(units, _ARVN_6729_GARRISON), "63,31", NODIR, 1, 50)
                    else
                        attack_scatter(difference(units, _ARVN_6729_GARRISON), "61,28", NODIR, 5, 50, false, ATTACK_WEAK)
                    end
                end


The first time around, the logical flag _ARVN_6729_GARRISON has no value, is by default the Lua value 'nil'. For this one time only, we set _ARVN_6729_GARRISON to the trackid returned by 'random_pick(counters_weakest(units))'. That is, we set _ARVN_6729_GARRISON to the by then weakest of the three platoons in _2ND_5TH_PARACHUTE_COY_55_A_447. After this assignment, thereafter 'not _ARVN_6729_GARRISON' can never evaluate to 'true', so the first of the above 'if not ... end' will not be re-entered. The garrison is a one time only determination.

Now knowing which platoon (the weakest) to leave behind defending the bunker at hex 67,29, we have the rest of the company, the 'difference(units, _ARVN_6729_GARRISON)', move on to weakly attack the bunker at hex 63,31.

But look at the map. Without aid of riverine transports (preoccupied elsewhere, with other assigned missions), there is no straightforward land route from 67,29 to 63,31. There is no ford or bridge etc. to cross the intervening river. There is certainly no way the EAI pathfinding would know to march back northward to the vicinity of OBJECTIVES[13], and from there march southward again, but this time on the west side of the river. Not to mention that with only 40 turns in this scenario, there is insufficient time for that.

Oops!

Now, more than two years after Jason first wrote this scenario's battle plan (not as code, but as text); now, a year or two after I scripted this scenario in this fashion -- it is hard now to remember why things were SAI'ed in this way.

What to do?

In the scenario editor, we might simply add a ford (FD) crossing between 67,29 and 63,31. We can KISS our way out of this situation.

We might just remove the above 'else ...' code block. _2ND_5TH_PARACHUTE_COY_55_A_447 just attacks the bunker at hex 67,29, and that's the end of it.

Or starting out from OBJECTIVES[13], we might craft an alternate attack southward towards the western bunker (ignoring the eastern bunker). With one or the other attack order selected randomly.

We might even give _2ND_5TH_PARACHUTE_COY_55_A_447 a third option: join forces with _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 to attack OBJECTIVES[16] and OBJECTIVES[17] to the north. After all, without additional support, it is unlikely that those two companies alone could take one or the other of those two strongholds, much less take them both. (So we suppose.)

One purpose of this Rung Sat AAR is to fix any flaws in the earlier scripting, to improve on earlier efforts. Another purpose is to demonstrate the scenario scripting development process, and varieties of coding technique.

The earlier scripting mistake must not be allowed to stand. We should strive to improve VN_550921_Rung_Sat.lua. Let's go for it!
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Some changes are in order. I make those changes. Then, following my own standard advice -- run csluachk.pl after every significant Lua file edit -- I do just that:



rober@Rob10rto ~/cslint
$ ./csluachk.pl -n +w +e -g vn -f VN_550921_Rung_Sat.lua
WARNING: for VN_550921_Rung_Sat.lua:552: _LOGISTIC_55_LEVEL_2_263 possible orgtrk 263 track 266 mismatch
ERROR: for VN_550921_Rung_Sat.lua:1111: mistaken DIR: GHTDIR
WARNING: in VN_550921_Rung_Sat.lua, referenced just once: OBJECTIVES10_CAPTURED
WARNING: in VN_550921_Rung_Sat.lua, referenced just once: UPRiGHTDIR
WARNING: for VN_550921_Rung_Sat.lua, possible missing or mistaken assault_attack_aggressiveness_effect adjustment



More oops! Most of those WARNINGs are false positives, of no real concern. But not the highlighted ones. That typo (wrong lower case 'i') in UPRiGHTDIR


Code: Select all

                           attack_normal(units, OBJECTIVES[17], UPRiGHTDIR, 2)


could definitely screw up the works. We fix it (changing it to upper case 'I').

Repeating: Run csluachk.pl after every significant Lua file edit.

Especially before running any auto-test (or any real hands-on human playtest). You want to catch your mistakes immediately, not twenty minutes into an auto-test (or actual game). Save yourself time, and grief. Run csluachk.pl sooner rather than later!

Refer to this screenshot in the commentary that follows:

CSVN_AAR_AI_AI_RungSat54.jpg
CSVN_AAR_AI_AI_RungSat54.jpg (1.43 MiB) Viewed 2541 times

Here are the revised orders for _2ND_5TH_PARACHUTE_COY_55_A_447, fixing the earlier mistake (no waterway crossing from hex 67,29 to hex 63,31), and adding also the option to join in the attack against OBJECTIVES[16] and OBJECTIVES[17] to the north:


Code: Select all

    -- _2ND_5TH_PARACHUTE_COY_55_A_447 -- 2nd/5th Parachute Company 55 - A
    do local units = _2ND_5TH_PARACHUTE_COY_55_A_447
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
            else
                if not _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then -- set one time only
                    _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER = random_pick({1,1,2,3})
                end
                if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then
                    if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 1 then
                        if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                            attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                        elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                            attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                        end
                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 2 then
                        if not_occupied("67,29", ARVN_SIDE) then
                            attack_way_point(units, {"67,15", "66,18", "68,22", "67,26", "67,29"}, NODIR, 1, 50, ATTACK_WEAK)
                        end
                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 3 then
                        if not_occupied("63,31", ARVN_SIDE) then
                            attack_way_point(units, {"59,18", "61,23", "63,26", "63,31"}, NODIR, 1, 50, ATTACK_WEAK)
                        end
                    end
                end
            end
        end
    end


One time only, we determine the _2ND_5TH_PARACHUTE_COY_55_A_447 attack order #:


Code: Select all

                if not _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then -- set one time only
                    _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER = random_pick({1,1,2,3})
                end


We could instead do this:


Code: Select all

                    _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER = dieroll(3)


But that would give equal chance to order #s 1, 2 and 3. By using the random_pick(), we give added weight to the #1 option. With 'random_pick({1,1,2,3})', there is a 2 out of 4, 50% chance that #1 will be picked, and a 25% chance each that #2 or #3 might be picked.

That is one way to pick the attack order. We might instead do it in init_variables():


Code: Select all

function init_variables ()

    -- initialize values possibly varying through the course of the scenario
    -- called once only, in on_startup()

    ...

    _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER = random_pick({1,1,2,3})

    ...

end


That too would set _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER one time only. Whether we do this in init_variables() or in battle_plan_b(), in the 'do local units = _2ND_5TH_PARACHUTE_COY_55_A_447 ... end' block -- either will work. But we prefer the latter, because the meaning is clearer that way, keeping in one place all variables and orders code pertaining to each unit.

(It should be clear that if you set _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER in init_variables(), the above 'if not _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then -- set one time only ... end' is rendered unnecessary.)

We need to enclose the actual order code within


Code: Select all

                if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then

                    ...

                end


that is, we need to check first whether _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER has been assigned a value, because otherwise a subsequent


Code: Select all

                    if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 1 then


would cause a Lua error, "attempt to compare nil with number". (When reaching the '[else]if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == # then' tests, by then _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER is guaranteed to have an assigned value. But we take the 'if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then' precaution anyway.)

With a picked #1 attack order


Code: Select all

                    if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 1 then
                        if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                            attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                        elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                            attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                        end


we have _2ND_5TH_PARACHUTE_COY_55_A_447 normal attack OBJECTIVES[16] by way of intermediate hexes 67,7 (the river crossing) and 66,1 (the assembly point), if the Side B BX Army still owns (and occupies) that objective. Note that we specify attacking from the UPLEFTDIR, so as to keep away from possible enemy fire coming from OBJECTIVES[17].

Else if OBJECTIVES[16] is taken, _2ND_5TH_PARACHUTE_COY_55_A_447 is to normal attack OBJECTIVES[17]. In this latter attack, now from the UPRIGHTDIR, because only from that direction are there paths (PT) leading through the stone walls (SN) encircling that objective.

Or attack order #2 has been picked, in which case


Code: Select all

                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 2 then
                        if not_occupied("67,29", ARVN_SIDE) then
                            attack_way_point(units, {"67,15", "66,18", "68,22", "67,26", "67,29"}, NODIR, 1, 50, ATTACK_WEAK)
                        end


which is basically the same orders assigned in the previous post (in the earlier, no options, only attack southward version).

Or attack order #3 has been picked, in which case


Code: Select all

                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 3 then
                        if not_occupied("63,31", ARVN_SIDE) then
                            attack_way_point(units, {"59,18", "61,23", "63,26", "63,31"}, NODIR, 1, 50, ATTACK_WEAK)
                        end


In this last case, _2ND_5TH_PARACHUTE_COY_55_A_447 is way point moved down the west side of that minor river (MR) to attack the bunker at hex 63,31. (Note that compared to the earlier version, see the previous post, there too it just stops. No further attack_scatter() from there.)
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Before we auto-test, we have several loose ends to tie.

First, we need to revise the _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 orders to coincide better with the new (possible) _2ND_5TH_PARACHUTE_COY_55_A_447 attack north orders.


Code: Select all

    -- _3RD_5TH_PARACHUTE_COY_55_A_451 -- 3rd/5th Parachute Company 55 - A
    do local units = _3RD_5TH_PARACHUTE_COY_55_A_451
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
            end
        end
    end

    -- _5TH_PARACHUTE_WEAPONS_COY_55_455 -- 5th Parachute Weapons Company 55
    do local units = _5TH_PARACHUTE_WEAPONS_COY_55_455
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif objective_owner(OBJECTIVES[16]) == BX_SIDE then
                attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
            end
        end
    end
    -- _81MM_MORTAR_459 -- Parachute M1 81mm Mortars
    if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
        fire_indirect_nearest_to_hex(_81MM_MORTAR_459, OBJECTIVES[13], 100)
    else
        if not at(_81MM_MORTAR_459, "69,5") then
            move_way_point(_81MM_MORTAR_459, {"66,7", "69,5"}, NODIR, 0, 100)
        else
            fire_indirect_nearest_to_hex(_81MM_MORTAR_459, "71,3", 100)
        end
    end


Before, those forces were attacking OBJECTIVES[17] then OBJECTIVES[16] (opposite sequence vs. the new orders) from DOWNRIGHTDIR (opposite from the new general direction of attack). It makes better sense to first attack OBJECTIVES[16], surrounded by crests (CR), than to attack OBJECTIVES[17], surrounded by stone walls (SN).

Note where _81MM_MORTAR_459 is ordered to hex 69,5 -- a possibly exposed, forward position. This is to get it within 3 hexes of the bunkers at OBJECTIVES[16] then OBJECTIVES[17].

The F2 Unit Handbook page for _81MM_MORTAR_459:

CSVN_AAR_AI_AI_RungSat55.jpg
CSVN_AAR_AI_AI_RungSat55.jpg (242.6 KiB) Viewed 2530 times

Already weak vs. hard-target bunkers, the 81mm Mortars effectiveness is cut in half beyond 3 or 4 hexes from target. (See the Range chart.) We need to move _81MM_MORTAR_459 close to the action, else its participation in this battle is next to useless. We might worry about the BX Army forces sallying forth to attack the mortars, in which case we should consider maybe stacking the mortars with its companion platoon _MMG_457. We shall see.

Second loose end:


Code: Select all

    -- _HQ_442 -- VNAF Airborne Battalion HQ (foot)
    -- _5TH_PARACHUTE_462 -- Airborne Commander 2
    do local units = join({_HQ_442, _5TH_PARACHUTE_462})
        if objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
            defend_normal(units, OBJECTIVES[13])
        end
    end


If OBJECTIVES[13] has been taken (is newly owned by the ARVN_SIDE, aka the Side A VNA), then have the _HQ_442 VNAF Airborne Battalion HQ and its commander, _5TH_PARACHUTE_462, defend_normal() at, and direct the later fights from, OBJECTIVES[13].

The third loose end:


Code: Select all

    if objective_owner(OBJECTIVES[13]) == ARVN_SIDE then
        -- have the weakest unit among the 3/5 and Weapons coys garrison and defend OBJECTIVES[13]
        if not _ARVN_OBJECTIVES13_GARRISON then -- one time only selection
            _ARVN_OBJECTIVES13_GARRISON = random_pick(counters_weakest(difference(join({_3RD_5TH_PARACHUTE_COY_55_A_451, _5TH_PARACHUTE_WEAPONS_COY_55_455}), _81MM_MORTAR_459)))
        end
        if _ARVN_OBJECTIVES13_GARRISON then
            defend_normal(_ARVN_OBJECTIVES13_GARRISON, OBJECTIVES[13], NODIR, 0, 100)
        end
    end


We select (one time only selection) the weakest surviving unit from among _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455, excluding from consideration _81MM_MORTAR_459. We order that selected unit to join the HQ and battalion commander in normal defending OBJECTIVES[13]. Not strongly defending that place, since force preservation takes priority, and defend_normal() gives greater leeway to retreat than would defend_strong() (or even defend_fanatical()).

Defending at OBJECTIVES[13] places the battalion HQ rather far from subsequent action, especially any action to the north. This has bad consequences for keeping the fighting units supplied. In later revisions, we might consider moving the battalion HQ northward to shorten the supply lines.

That then is the VNA 5th Parachute Battalion battle plan. Is it any good? One way to find out is to auto-test it...
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

For the initial auto-tests, I won't be showing any screenshots (much less video footage) here. Why not? It's because showing any gross mistakes or miscalibrations is not very informative. For now, it's enough to tell about them. Screenshots (and maybe video footage) to come later.

In the initial auto-tests, I let the entire BX Army Side B SAI run, since it is mostly about static defense of objective hexes and stationary supply dumps etc.

For the VNA Side A, since only the 5th Parachute Battalion has been scripted and reviewed thus far, I comment out other forces' SAI battle plan code thusly:


Code: Select all

function battle_plan_a (turn, side)

    -- standard orders, uncomment as necessary:
    if turn >= 1 then
        -- all Side A units halt initially (or maybe use hold())
        halt(ALLA)
--        hold(ALLA)
    end

--[[

    [other units' orders code]

--]]

    [5th Parachute Battalion orders code]

--[[

    [other units' orders code]

--]]

end


Because the '[other units' orders code]' is enclosed by the Lua block comment markers '--[[' and '--]]', the CSEE Lua interpreter effectively ignores it; to the CSEE Lua interpreter, it is as if the commented out code doesn't exist. Only the uncommented '[5th Parachute Battalion orders code]' is processed; only the 5th Parachute Battalion moves and fights, as directed by the CSEE SAI.

Having said that, note the general 'halt(ALLA)' order, initially applied to all Side A units. It is essential to keep that uncommented, because otherwise, apart from the 5th Parachute Battalion, all other units would have no SAI orders, they would be under the control of the EAI, which might have them moving to the nearest objective hexes, wander off, etc.

Before beginning the auto-test, I set it to stop at the beginning of Turn 5, Side A phase via



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "5a" > pass



(Note that the pass file must be in the top-level game folder. In my case, this would be in my development 'vietnam' folder, as shown above. Your Mileage May Vary.)

I launch the auto-test with these commands:



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ rm /cygdrive/r/Temp/Logs/*

rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ ./vnengine.exe -W -7 -T -R -L3 VN_550921_Rung_Sat.scn



The 'rm ...' is to empty the Logs folder of any earlier files, most especially any earlier error.log. (By means of the '-R' option, I am logging to my RAM drive. An advanced feature, you would normally not do this; you would log to the standard game Logs folder.) The '-L3' ratchets up the logging to LogLevel 3, which slows down the auto-test somewhat. This is standard practice for me as the game developer. You, the ordinary gamer/modder/scripter, would not typically do this.

With the second command, the game engine then launches, runs through the first four game turns, then stops.

With the game stopped, I have the opportunity to look around, make note of the current situation, especially any mistakes. I also have the opportunity to check for any Logs/error.log files. If something has blown up -- figuratively! -- if I can already observe show stopper mistakes, for any reason, I might start over again.

In my first auto-test, not seeing anything really bad, I let the show go on.

I set the next auto-test breakpoint at Turn 10, Side A phase via



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "10a" > pass



I restart the auto-test by toggling ON AI > Activate AI. Game play resumes...

... At Turn 10, Side A phase, again I have the opportunity to look around, make note, compare the action on screen with the CSEE/SAI code, etc.

The process continues like this. I set auto-test breakpoints every five turns with 'echo "15a" > pass', 'echo "20a" > pass', 'echo "25a" > pass', and so on. I could refine these breakpoints, but for now stopping every five turns suffices.

The auto-test stops, and I manually restart it, every five turns, eventually stopping at Turn 40, Side A phase. The auto-test situation, as Side A sees things (obscured by FOW):


CSVN_AAR_AI_AI_RungSat56.jpg
CSVN_AAR_AI_AI_RungSat56.jpg (1.19 MiB) Viewed 2516 times


In the center (turquoise circle), the VNA Side A has taken and occupies OBJECTIVES[13].

To the north (green circle), 3RD_5TH_PARACHUTE_COY_55_A_451 was able to take OBJECTIVES[16], but then destroyed itself attempting to take also OBJECTIVES[17]. Of _5TH_PARACHUTE_WEAPONS_COY_55_455, only _81MM_MORTAR_459 survives.

To the south (white circle), a single surviving platoon of _2ND_5TH_PARACHUTE_COY_55_A_447 struggles vainly to take bunker at hex 67,29.

To the west (yellow circle), a mostly intact _1ST_5TH_PARACHUTE_COY_55_A_443 continues to attack OBJECTIVES[8].

_1ST_5TH_PARACHUTE_COY_55_A_443 is an interesting special case. Why hasn't it succeeded taking OBJECTIVES[8]? It's because after moving to that place and launching an attack there, it was forced to backtrack to OBJECTIVES[13], because BX Army forces (re)appeared in that vicinity. If you look at the code above (see previous posts), you will see where _1ST_5TH_PARACHUTE_COY_55_A_443 only attacks OBJECTIVES[8] if enemy forces are not within 2 hexes of OBJECTIVES[13]. Evidently (more than that, I observed it happen in real time with my own eyes), the BX Army returned to the vicinity of OBJECTIVES[13]. By the code logic, this had _1ST_5TH_PARACHUTE_COY_55_A_443 return to OBJECTIVES[13] to drive the enemy away again. Succeeding at that, _1ST_5TH_PARACHUTE_COY_55_A_443 then returned to OBJECTIVES[8] to attack that place a second time. Adjustments are clearly in order.

After restarting the auto-test, it plays through the Side A phase, then automatically stops at the beginning of the Side B phase. The auto-test situation, from the perspective of Side B (also obscured by FOW):


CSVN_AAR_AI_AI_RungSat57.jpg
CSVN_AAR_AI_AI_RungSat57.jpg (1.19 MiB) Viewed 2516 times


In the center (turquoise circle), no BX Army presence whatsoever.

To the north (green circle), Side B continues to hold OBJECTIVES[17] (only).

To the south (white circle), Side B is successfully defending the hex 67,29 bunker.

To the west (yellow circle), Side B likewise continues a successful defense of OBJECTIVES[8].

Hmm. VNA losses are unacceptably high. Especially when down to the last few SPs, no sense sustaining suicidal attacks. Adjustments are needed.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

Based on these first auto-test results, I will make the following adjustments.

The revised 1st/5th orders:


Code: Select all

    -- _1ST_5TH_PARACHUTE_COY_55_A_443 -- 1st/5th Parachute Company 55 - A
    do local units = _1ST_5TH_PARACHUTE_COY_55_A_443
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
            elseif (loss_rate(units) > 65) then
                defend_normal(units)
            elseif not attack_nearest(units) and
                   (objective_owner(OBJECTIVES[8]) == BX_SIDE) then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_NORMAL)
            end
        end
    end


This is new:


Code: Select all

            elseif (loss_rate(units) > 65) then
                defend_normal(units)


If the _1ST_5TH_PARACHUTE_COY_55_A_443 loss rate exceed 65%, then the company is to cease offensive operations, just defend normal in place wherever finds itself.

This is revised:


Code: Select all

            elseif not attack_nearest(units) and
                   (objective_owner(OBJECTIVES[8]) == BX_SIDE) then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_NORMAL)


Else if the 1st/5th is not attacking any nearby enemy units, and if the Side B BX Army owns (and occupies) OBJECTIVES[8], then _1ST_5TH_PARACHUTE_COY_55_A_443 is to proceed up the road to the northwest and normal attack OBJECTIVES[8] by way of hex 50,6.

That 'elseif not attack_nearest(units)' seems rather strange. 'attack_nearest(units)' -- meaning to say, _1ST_5TH_PARACHUTE_COY_55_A_443 is to attack any enemy forces nearest to it; 'units' refers to the 1st/5th -- that is an action statement, but we are using it also as a logical test: is the 1st/5th in fact attacking? What's with that?

Without going into too much detail, we look at the definition of the attack_nearest() function, found in user.lua:


Code: Select all

-- attack_nearest() -- move to attack the nearest enemy units

function attack_nearest (trackids, dir, radius, actprob, attype, ground_only, unverified)

    ...

    return (attacks > 0)

end


attack_nearest() tallies a count of individual platoon attacks. If there are no attacks, 'attacks' is 0, '(attacks > 0)' is therefore false, and the attack_nearest() function returns that value. So the preceding 'elseif' becomes in effect


Code: Select all

            elseif not [false] and
                   (objective_owner(OBJECTIVES[8]) == BX_SIDE) then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_NORMAL)


That is, if the 1st/5th is not stopping to attack along the way, continue onward to attack OBJECTIVES[8].

On the other hand, if 1st/5th has found nearby units to attack, '(attacks > 0)' is therefore true, and the attack_nearest() function returns that value. So the preceding 'elseif' becomes in effect


Code: Select all

            elseif not [true] and
                   (objective_owner(OBJECTIVES[8]) == BX_SIDE) then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_NORMAL)


Which is to say


Code: Select all

            elseif [false] and
                   (objective_owner(OBJECTIVES[8]) == BX_SIDE) then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_NORMAL)


and the body of the 'elseif' is not entered, i.e., don't proceed with attacking OBJECTIVES[8], no, do the nearby attacks.

More detail. (Okay, we lied.) There is this also in the attack_nearest() function definition:


Code: Select all

function attack_nearest (trackids, dir, radius, actprob, attype, ground_only, unverified)

    if not trackids then return false end
    local tl
    if type(trackids) == "table" then
        if #trackids == 0 then return false end
        tl = trackids
    elseif type(trackids) == "number" then
        tl = {trackids}
    else
        return false
    end
    dir = dir or NODIR
    radius = radius or -1
    actprob = actprob or 50  -- by default, 50/50 chance of assault
    attype = attype or ATTACK_NORMAL
    ground_only = ground_only or false
    unverified = unverified or false

    ...

end


In the 'elseif not attack_nearest(units)' (see above), we omit specifying the optional parameters dir, radius, ..., unverified. The unspecified input parameters are therefore assigned default values. In particular, the 'unverified' parameter is given the value 'false' by default. Which is to say that the 1st/5th Company is only to attack verified units, i.e., spotted units. If you refer to

https://www.matrixgames.com/forums/view ... 6#p5004386

you will see where BX Army reconnaissance and fire support platoons are lurking in the jungle to the north (later to surprise attack southward). You will see (in the screenshot), but in game, the 1st/5th will not see. (Well, not until later.) 1st/5th will not stop to attack what it does not see. The SAI is not omniscient here. (We could make it all knowing. But that would be cheating. We won't do that!)

It is important that you understand the technique of using action function return values in 'if ... elseif' logical tests. Not all CSEE action functions return values. It is also important to check the Manual/LUA_FUNCTIONS_REFERENCE.txt (maybe also unit.lua or user.lua) to see which of the functions return values, and which don't. Don't assume!

Similarly, we have _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 cease offensive operations if casualties exceed 65%; and they too are to stop forward movement and attack any perceived nearby enemy forces:


Code: Select all

    -- _3RD_5TH_PARACHUTE_COY_55_A_451 -- 3rd/5th Parachute Company 55 - A
    do local units = _3RD_5TH_PARACHUTE_COY_55_A_451
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif (loss_rate(units) > 65) then
                defend_normal(units)
            elseif not attack_nearest(units) then
                if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                    attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                    attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                end
            end
        end
    end

    -- _5TH_PARACHUTE_WEAPONS_COY_55_455 -- 5th Parachute Weapons Company 55
    do local units = _5TH_PARACHUTE_WEAPONS_COY_55_455
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            elseif (loss_rate(units) > 65) then
                defend_normal(units)
            elseif not attack_nearest(units) then
                if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                    attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                    attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                end
            end
        end
    end
    -- _81MM_MORTAR_459 -- Parachute M1 81mm Mortars
    if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
        fire_indirect_nearest_to_hex(_81MM_MORTAR_459, OBJECTIVES[13], 100)
    else
        if owned(OBJECTIVES[16], BX_SIDE) and
           not at(_81MM_MORTAR_459, "69,5") then
            move_way_point(_81MM_MORTAR_459, {"67,7", "69,5"}, NODIR, 0, 100)
        elseif owned(OBJECTIVES[16], ARVN_SIDE) then
            move_rush(_81MM_MORTAR_459, OBJECTIVES[16])
        else
            fire_indirect_nearest_to_hex(_81MM_MORTAR_459, "71,3", 100)
        end
    end


In the case of _81MM_MORTAR_459, we have added code to have them move to the relative safety of OBJECTIVES[16] if the ARVN_SIDE (the VNA Side A) have taken it.

For _2ND_5TH_PARACHUTE_COY_55_A_447


Code: Select all

    -- _2ND_5TH_PARACHUTE_COY_55_A_447 -- 2nd/5th Parachute Company 55 - A
    do local units = _2ND_5TH_PARACHUTE_COY_55_A_447
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
            elseif not attack_nearest(units) then
                if not _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then -- set one time only
                    _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER = random_pick({1,1,2,3})
                end
                if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then
                    if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 1 then
                        if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                            attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                        elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                            attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                        end
                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 2 then
                        if (loss_rate(units) < 50) and
                           not_occupied("67,29", ARVN_SIDE) then
                            attack_way_point(units, {"67,15", "66,18", "68,22", "67,26", "67,29"}, NODIR, 1, 50, ATTACK_WEAK)
                        else
                            defend_normal(units)
                        end
                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 3 then
                        if (loss_rate(units) < 50) and
                           not_occupied("63,31", ARVN_SIDE) then
                            attack_way_point(units, {"59,18", "61,23", "63,26", "63,31"}, NODIR, 1, 50, ATTACK_WEAK)
                        else
                            defend_normal(units)
                        end
                    end
                end
            end
        end
    end


for the attack orders #2 and #3, we have added code with the effect: Only proceed southward to attack the BX Army bunkers if the 2nd/5th loss rate is less than 50%. If casualties are 50% or greater, don't bother; just normal defend in place.

There is one other tweak. At OBJECTIVES[28] (yellow circle, screenshot following)


CSVN_AAR_AI_AI_RungSat58.jpg
CSVN_AAR_AI_AI_RungSat58.jpg (105 KiB) Viewed 2269 times


the BX Army _C3_1ST_2ND_RIFLE_COY_48_US_47 was observed to be "dancing" around the objective, from one turn to the next deploying just to the south then returning, over and over again. Let's look at the code:


Code: Select all

    -- _C3_1ST_2ND_RIFLE_COY_48_US_47 -- C3/1st/2nd Rifle Company 48 - US
    do local units = _C3_1ST_2ND_RIFLE_COY_48_US_47
    ...
        elseif _C3_1ST_2ND_RIFLE_COY_48_US_47_POST == OBJECTIVES[28] then
            if not at(units, objective) then
                move_way_point(units, {"88,56",objective}, NODIR, 0, 100) -- see _JUNKS_165 below
            else
                defend_strong(units, objective, DOWNDIR, 1, 100)
            end
        end
    end


If _C3_1ST_2ND_RIFLE_COY_48_US_47 are at the objective, 'not at' is false, so they do the 'else ...', strongly defend up to one hex outward in a down direction. But that places them not at the objective. The next time around, they do the 'if ...' move_way_point() back to the objective. Where again, next time around, the 'not at' is false ... And on and on in a never ending cycle of moving back and forth.

The fix is to replace 'not at(units, objective)' with 'not within(units, objective, DOWNDIR, 1)' thusly:


Code: Select all

    -- _C3_1ST_2ND_RIFLE_COY_48_US_47 -- C3/1st/2nd Rifle Company 48 - US
    do local units = _C3_1ST_2ND_RIFLE_COY_48_US_47
    ...
        elseif _C3_1ST_2ND_RIFLE_COY_48_US_47_POST == OBJECTIVES[28] then
            if not within(units, objective, DOWNDIR, 1) then
                move_way_point(units, {"88,56",objective}, NODIR, 0, 100) -- see _JUNKS_165 below
            else
                defend_strong(units, objective, DOWNDIR, 1, 100)
            end
        end
    end


This glitch is a hard one to spot. I have revised csluachk.pl to report this as a WARNING:



rober@Rob10rto ~/cslint
$ ./csluachk.pl -n +w +e -g vn -f VN_550921_Rung_Sat.lua
...
WARNING: for VN_550921_Rung_Sat.lua:1758: suspicious use of at(): if not at(units, objective) then
...



With those adjustments applied, we are all set to conduct our second auto-test.
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

For the next round of auto-tests, like the previous one, at each stopping point, we increment the next stopping point by 5 turns:



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "5a" > pass

[we launch the auto-test, it runs, stopping at Turn 5, Side A]

rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "10a" > pass

[we resume the auto-test, it runs, stopping at Turn 10, Side A]

rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "15a" > pass

[we resume the auto-test, it runs, stopping at Turn 15, Side A]

rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ echo "20a" > pass

...



Like previously, we launch the auto-test via



rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ rm /cygdrive/r/Temp/Logs/*

rober@Rob10rto /cygdrive/c/Games/Matrix Games/Vietnam/vietnam
$ ./vnengine.exe -W -7 -T -R -L3 VN_550921_Rung_Sat.scn



As always, at each stopping point (and after perhaps updating the pass file), we resume the auto-test via the menu option AI > Activate AI.

At the first stopping point, Turn 5, Side A phase, we see this:

CSVN_AAR_AI_AI_RungSat59.jpg
CSVN_AAR_AI_AI_RungSat59.jpg (1.57 MiB) Viewed 2227 times

The 5th Parachute Battalion (and other VNA forces) has landed.

After a restart, at the second stopping point, Turn 10, Side A phase, the situation:

CSVN_AAR_AI_AI_RungSat60.jpg
CSVN_AAR_AI_AI_RungSat60.jpg (1.57 MiB) Viewed 2227 times

Good. The 5th Parachute Battalion is moving to attack enemy forces at and around OBJECTIVES[13].

After a restart, at the third stopping point, Turn 15, Side A phase, the situation:

CSVN_AAR_AI_AI_RungSat61.jpg
CSVN_AAR_AI_AI_RungSat61.jpg (1.57 MiB) Viewed 2227 times

In driving away the surviving OBJECTIVES[13] enemy defenders, it seems the fight drew the 5th Battalion southward.

After a restart, at the fourth stopping point, Turn 20, Side A phase, the situation:

CSVN_AAR_AI_AI_RungSat62.jpg
CSVN_AAR_AI_AI_RungSat62.jpg (1.57 MiB) Viewed 2227 times

Looks good. The various companies have turned around, are moving off to attack the other objectives to the northwest and northeast.

After a restart ... WTF?! We stop the auto-test (by toggling OFF AI > Activate AI). The situation:

CSVN_AAR_AI_AI_RungSat63.jpg
CSVN_AAR_AI_AI_RungSat63.jpg (1.57 MiB) Viewed 2227 times

The VNA _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 have halted their advance towards OBJECTIVES[16] and OBJECTIVES[17] to the northeast, have instead pivoted to attacking OBJECTIVES[8] to the west. What's going on here?

What's going on: Actually, following the order


Code: Select all

            elseif not attack_nearest(units) then


has them attacking the nearest (known) enemy units, which just so happen to be at OBJECTIVES[8]. Any enemy units at OBJECTIVES[16] and OBJECTIVES[17] have not revealed themselves, their (presumed) presence is hidden to the VNA Side A. The latest order is to attack the nearest known enemy units, which by chance happen to be in the wrong direction.

Indeed, as currently formulated, the nearest enemy units revealing themselves might be anywhere, any direction, any distance away. With the above simplistic order, our attacks could go off on wild tangents hither and yon.

Oops! That's not our intention. Back to the drawing board. We again need to revise the script code!
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
User avatar
berto
Posts: 21461
Joined: Wed Mar 13, 2002 1:15 am
Location: metro Chicago, Illinois, USA
Contact:

Re: Forest of Assassins - Battle of Rung Sat - 9/21/55 - AAR (AI vs. AI)

Post by berto »

The 1st/5th orders, slightly revised:


Code: Select all

    -- _1ST_5TH_PARACHUTE_COY_55_A_443 -- 1st/5th Parachute Company 55 - A
    do local units = _1ST_5TH_PARACHUTE_COY_55_A_443
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
            elseif (loss_rate(units) > 65) then
                defend_normal(units)
            elseif not attack_nearest_arc(units, UPLEFTDIR, 2, ATTACK_NORMAL, true, true) and
                   (objective_owner(OBJECTIVES[8]) == BX_SIDE) then
                attack_way_point(units, {"50,6", OBJECTIVES[8]}, UPDIR, 2, 50, ATTACK_NORMAL)
            end
        end
    end


The only change from before is


Code: Select all

            elseif not attack_nearest_arc(units, UPLEFTDIR, 2, ATTACK_NORMAL, true, true) and


attack_nearest_arc() is a new uber function created for this occasion. In the updated user.lua:


Code: Select all

-- attack_nearest_arc() -- move to attack the nearest enemy units within the specified arc and extent

function attack_nearest_arc (trackids, dir, extent, attype, ground_only, verified_only)

    ...

end


The revised _1ST_5TH_PARACHUTE_COY_55_A_443 orders code above


Code: Select all

           elseif not attack_nearest_arc(units, UPLEFTDIR, 2, ATTACK_NORMAL, true, true) and


says to attack the nearest units, but only within 2 hexes of the UPLEFTDIR. And because UPLEFTDIR is not enclosed within { }, that is to say UPLEFTDIR but also DOWNLEFTDIR (one directional shift to the left) and UPDIR (one directional shift to the right). To be clear, this is not a one-time attack order determination. Rather, at the beginning of each new (Side A) phase, and at each new location, if there are enemy within that directional arc up to 2 hexes out, the nearest among them is to be attacked.

In the above orders code, note that there is no preventing: if OBJECTIVES[13] is later attacked, _1ST_5TH_PARACHUTE_COY_55_A_443 abandons its operations against OBJECTIVES[8] and returns to fight over OBJECTIVES[13].

The revised _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 orders code:


Code: Select all

    -- _3RD_5TH_PARACHUTE_COY_55_A_451 -- 3rd/5th Parachute Company 55 - A
    do local units = _3RD_5TH_PARACHUTE_COY_55_A_451
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if not _3RD_5TH_PARACHUTE_COY_55_A_451_ATTACK_ORDER and
               objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            else
                if not _3RD_5TH_PARACHUTE_COY_55_A_451_ATTACK_ORDER then -- set one time only
                    _3RD_5TH_PARACHUTE_COY_55_A_451_ATTACK_ORDER = 1
                end
                if (loss_rate(units) > 65) then
                    defend_normal(units)
                elseif not attack_nearest_arc(units, UPRIGHTDIR, 2, ATTACK_NORMAL, true, true) then
                    if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                        attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                    elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                        attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                    end
                end
            end
        end
    end

    -- _5TH_PARACHUTE_WEAPONS_COY_55_455 -- 5th Parachute Weapons Company 55
    do local units = _5TH_PARACHUTE_WEAPONS_COY_55_455
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if not _5TH_PARACHUTE_WEAPONS_COY_55_455_ATTACK_ORDER and
               objective_owner(OBJECTIVES[13]) == BX_SIDE then
                attack_strong(units, OBJECTIVES[13], NODIR, 1, 100)
            else
                if not _5TH_PARACHUTE_WEAPONS_COY_55_455_ATTACK_ORDER then -- set one time only
                    _5TH_PARACHUTE_WEAPONS_COY_55_455_ATTACK_ORDER = 1
                end
                if (loss_rate(units) > 65) then
                    defend_normal(units)
                elseif not attack_nearest_arc(units, UPRIGHTDIR, 2, ATTACK_NORMAL, true, true) then
                    if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                        attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                    elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                        attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                    end
                end
            end
        end
    end
    -- _81MM_MORTAR_459 -- Parachute M1 81mm Mortars
    if not _5TH_PARACHUTE_WEAPONS_COY_55_455_ATTACK_ORDER and
       units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
        fire_indirect_nearest_to_hex(_81MM_MORTAR_459, OBJECTIVES[13], 100)
    else
        if owned(OBJECTIVES[16], BX_SIDE) and
           not at(_81MM_MORTAR_459, "69,5") then
            move_way_point(_81MM_MORTAR_459, {"67,7", "69,5"}, NODIR, 0, 100)
        elseif owned(OBJECTIVES[16], ARVN_SIDE) and
               not at (_81MM_MORTAR_459, OBJECTIVES[16]) then
            move_rush(_81MM_MORTAR_459, OBJECTIVES[16])
        else
            fire_indirect_nearest_to_hex(_81MM_MORTAR_459, "71,3", 100)
        end
    end


In addition to use of


Code: Select all

                elseif not attack_nearest_arc(units, UPRIGHTDIR, 2, ATTACK_NORMAL, true, true) then


note the use of _5TH_PARACHUTE_WEAPONS_COY_55_455_ATTACK_ORDER. Only if that has no assigned value, only then join in the OBJECTIVES[13] attack. After that objective falls to the Side A VNA, only then is _5TH_PARACHUTE_WEAPONS_COY_55_455_ATTACK_ORDER assigned the value 1. By this means (follow the logic), both _3RD_5TH_PARACHUTE_COY_55_A_451 and _5TH_PARACHUTE_WEAPONS_COY_55_455 will persist in attacking OBJECTIVES[16] and OBJECTIVES[17], no backtracking to OBJECTIVES[13].

The revised _2ND_5TH_PARACHUTE_COY_55_A_447 code:


Code: Select all

    -- _2ND_5TH_PARACHUTE_COY_55_A_447 -- 2nd/5th Parachute Company 55 - A
    do local units = _2ND_5TH_PARACHUTE_COY_55_A_447
        if _5TH_PARACHUTE_BTN_55_A_441_READY_TURN then
            if not _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER and
               units_within_count(OBJECTIVES[13], NODIR, 2, BX_SIDE) > 0 then
                attack_nearest_to_hex(units, OBJECTIVES[13], NODIR, 1, 100, ATTACK_STRONG)
            else
                if not _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then -- set one time only
                    _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER = random_pick({1,1,2,3})
                end
                if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER then
                    if _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 1 then
                        if not attack_nearest_arc(units, UPRIGHTDIR, 2, ATTACK_NORMAL, true, true) then
                            if objective_owner(OBJECTIVES[16]) == BX_SIDE then
                                attack_way_point(units, {"67,7", "66,1", OBJECTIVES[16]}, UPLEFTDIR, 2, 50, ATTACK_NORMAL)
                            elseif objective_owner(OBJECTIVES[17]) == BX_SIDE then
                                attack_normal(units, OBJECTIVES[17], UPRIGHTDIR, 2)
                            end
                        end
                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 2 then
                        defend_normal(units)
                        if (loss_rate(units) < 50) then
                            if not attack_nearest_arc(units, {DOWNDIR}, 2, ATTACK_NORMAL, true, true) then
                                if not_occupied("67,29", ARVN_SIDE) then
                                    attack_way_point(units, {"67,15", "66,18", "68,22", "67,26", "67,29"}, NODIR, 1, 50, ATTACK_WEAK)
                                end
                            end
                        end
                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 3 then
                        defend_normal(units)
                        if (loss_rate(units) < 50) then
                            if not attack_nearest_arc(units, {DOWNDIR}, 2, ATTACK_NORMAL, true, true) then
                                if not_occupied("63,31", ARVN_SIDE) then
                                    attack_way_point(units, {"59,18", "61,23", "63,26", "63,31"}, NODIR, 1, 50, ATTACK_WEAK)
                                end
                            end
                        end
                    end
                end
            end
        end
    end


Similar use of attack_nearest_arc(), and no backtracking, but with the difference, in the case _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER:


Code: Select all

                           if not attack_nearest_arc(units, {DOWNDIR}, 2, ATTACK_NORMAL, true, true) then


Note the use of '{DOWNDIR}'. By enclosing DOWNDIR with the curly braces (effectively forming a single member list), this says to include in the order the more narrowly defined DOWNDIR 60 degree arc. (Without the { and }, the directional arc would extend outward to 180 degrees both left and right.)

Pay special attention also to, for instance


Code: Select all

                    elseif _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER == 2 then
                        defend_normal(units)
                        if (loss_rate(units) < 50) then
                            if not attack_nearest_arc(units, {DOWNDIR}, 2, ATTACK_NORMAL, true, true) then
                                if not_occupied("67,29", ARVN_SIDE) then
                                    attack_way_point(units, {"67,15", "66,18", "68,22", "67,26", "67,29"}, NODIR, 1, 50, ATTACK_WEAK)
                                end
                            end
                        end


In that code, by default we order _2ND_5TH_PARACHUTE_COY_55_A_447_ATTACK_ORDER to normal defend in place. But if the company's loss rate is less than 50%, we override the default order with the subsequent attack order(s).

Let's see if the above code does any better...
Campaign Series Legion https://cslegion.com/
Campaign Series Lead Coder https://www.matrixgames.com/forums/view ... hp?f=10167
Panzer Campaigns, Panzer Battles Lead Coder https://wargameds.com
Post Reply

Return to “After Action Report”