Scripted AI and Battle Plans
I have a cunning plan…

To conclude the series on Scripted AI, let us have a deeper look at how our Battle Plans for the computer opponent are generated and scripted.
First, please find links to the earlier articles here:
And now, back to those Cunning Plans.
Steps for Creating a Cunning Computer Opponent Using Scripted AI
Event Engine Lua skeleton
With csmklua.pl updated for 2.00 and 3.00 game versions, the first step is to generate a scenario-specific Lua file based on the Scenario (.scn) and Organization (.org) files.
At the time of writing, csmklua requires these files to be located in the \Scenarios root. A future update will support the new subfolder setup as well.
Let’s run this for Into Africa!, my favorite little 1973 scenario.
~/CSlint$ ./csmklua.pl -h
Usage: csmklua.pl [-h|-help] [{+|-}G|-debug] [-r[everse]] -g[ame] {me|vn|cw|ef|wf|pf} -f <scenario file> -o <lua file>
Note that the previous -a(bbpreviated), -b(locks), and -p(runed) flags are no longer required. You can still enter them, but they are enabled by default. I kept the -r(everse) option, though I can’t see a case where writing the organizations in reverse order would be useful.
The new Scenario Browser front end conveniently provides file names for all scenarios. For Into Africa!, the correct command would be:
csmklua.pl -g me -f EG_731017_Hasan.scn -o EG_731017_Hasan.lua
Here is the resulting EG_731017_Hasan.lua Lua skeleton header:

Battle Plan functions
Now that we have the Lua skeleton in place, here’s a quick recap. Among other things, it generates neat, company-sized blocks of organizations from the .org file, representing the units placed on the map. This is thanks to the now-default -p(rune) option, which removes from the Lua file any units that are not present on the map or available as reinforcements or arrivals.
Here’s a snippet of the Into Africa! Israeli organizations within the Lua file:

What we have here are mnemonics for the various units in play. For example, ALEPH_MECHANIZED_PARACHUTE_INF_COY_71_NAGMASH_116 is a mnemonic for a list that contains units 117, 118, 119, and 120.
Moving further into the Into Africa! Lua skeleton, we reach the Battle Plans section—one for each side, A and B.
Do local units = … End Blocks
What we have in the Battle Plans function at this stage is a Do local units = … End section for all formations in map.
Here’s a snippet of the Israeli Side A Battle Plan, showing how the above formations are integrated:

What the scenario designer would typically do here, for example, with the ALEPH_MECHANIZED_PARACHUTE_INF_COY_71_NAGMASH_116 company block, is assign orders to the unit as a whole. Then, since the company also includes a Heavy Machine Gun platoon, they might have the infantry platoons perform a specific task while positioning the HMG in a support role.
At this stage, however, the Middle East 3.00 Lua skeletons do not yet support manual scripting. In fact, even the full range of autogenerated options has not been fully utilized. This is simply Iteration #1, designed to quickly bring the 130+ Middle East scenarios up to speed with Event Engine integration.
Did I say 130+ scenarios? Well, that’s just the stock scenarios. Add to that the Alan R. Arvold mod scenarios, and we’re looking at another 140+ scenarios now equipped with Event Engine and Lua. Something that would have taken months and months to do manually, one by one.
But I digress. Back to what we have now with Iteration #1.
Instead of any custom code we have a call to a common set_posture(inits, side) with all units passed to it. For ALEPH_MECHANIZED_PARACHUTE_INF_COY_71_NAGMASH_116, that would be the three infantry platoons and the HMG platoon. Let us move on…
Set Posture
The idea behind the set_posture() call above is that even when using custom code, the scenario designer can leave it in place. Simply assign new orders to the units after the call. Remember, you can assign multiple orders to each individual unit in the code, but only the last order will take effect.
This is what the Set Posture function does next:

First, it obtains the mission bias for the current turn, allowing it to make informed decisions. Am I on defense? Should I attack? How aggressively? The mission bias determines the answers to these questions.
Next, it gathers a list of both friendly-owned and hostile objectives for further use.
Then, it cycles through all the units passed to it, assigning them to the appropriate recipient. In this case, we are only dealing with combat units, but we’ll take a closer look at that in a moment.
Before that, one more key function.
Set Counter Measures
Let’s examine one vital function from the Battle Plan: the Set Counter Measures function call.
Both Side A and Side B Battle Plan functions conclude with a call to Set Counter Measures, wrapping up this turn’s Cunning Plan cycle. Here it is:

At the moment, Set Counter Measures is primarily a placeholder.
In the next iteration, it will likely include generic logic, such as implementing countermeasures when an objective is lost, as the name suggests.
Handle Combatant Units
Back to Set Posture! In above picture, we also have the final Do local units = … End blocks—two artillery formations and two off-board aircraft flights.
These represent the two sections of generic Scripted AI now implemented:
- Targeting and use of Indirect Fires
- Targeting and use of Airstrikes

Let us look at both combatant type handler functions next.
Handle Indirect Fire Units
There is actually a lot going on with Indirect fire units.

This is just a snippet. Let me have ChatGPT summarize the indirect fire handler for you:
Handling Indirect Fire Units in Scripted AI
The handle_indirect_fire_unit() function is responsible for managing artillery and indirect fire assets within the Scripted AI system. It determines whether the unit should fire, how it should engage, and under what conditions it should preserve ammunition or pass control to the AI.
Key Steps in the Function:
-
Determine Unit Capabilities & Mission Context:
- Identifies the unit’s location, fire range, and mission bias (offensive or defensive).
- Checks if the unit is part of a combined force with infantry (for tactical support roles).
-
Check for Ammo Availability:
- If the unit is low on ammo, it is released to the Engine AI (EAI) instead of firing.
-
Select Target Priorities:
- Gathers friendly and enemy objectives to define potential targets.
- Builds a priority target list based on known enemy positions.
-
Execute Fire Orders:
-
If the unit is providing fire support for infantry, it cycles through targeting logic, with probabilities assigned to different actions:
- Direct fire at nearest enemy units in LOS.
- Indirect fire at high-priority targets.
- Firing at enemy positions near strategic objectives.
- Holding fire to conserve ammo (35% chance).
-
If the unit is field artillery, it follows a similar logic but prioritizes operational targets over tactical ones.
-
-
Final Decision Loop:
- The function repeats until a valid fire order is set, ensuring an adaptive response based on battlefield conditions.
This approach allows the AI to effectively utilize indirect fire assets, prioritizing key targets, and making realistic tactical decisions without excessive scripting. Future iterations could introduce more sophisticated counter-battery fire and response mechanics to further enhance battlefield dynamics.
^ There you go! And this is iteration #1. Maybe more cunning artillery tricks for the next iteration?
Handle Offboard Aircraft Units
Airstrikes logic is a simplified version of above. The priority target list is a tad simpler, and also there will be a maximum of one new Airstrike ordered per turn.
Here is a similar ChatGPT summary on Airstrikes:
Handling Airstrikes in Scripted AI
The handle_offboard_aircraft_unit() function manages offboard air assets, determining when and where to conduct airstrikes. Unlike indirect fire units, which follow a detailed prioritization loop, airstrikes operate on a simpler, high-impact decision process.
Key Steps in the Function:
-
Chance-Based Decision:
- Each turn, there is a 50% probability that an airstrike will be considered.
-
Identify High-Priority Targets:
- Searches for spotted enemy units that fit specific high-value target criteria, including:
- Headquarters (HQs)
- Supply units
- Indirect fire assets (artillery and mortars)
- Engineers engaged in mine-clearing, laying mines, or bridge-building
- Searches for spotted enemy units that fit specific high-value target criteria, including:
-
Execute Airstrike (if viable targets exist):
- If at least one high-priority target is found, the AI picks one at random for an airstrike.
- The system ensures only one airstrike per side per turn, preventing overuse of air assets.
Strategic Implications:
- This function limits excessive airstrike spam by enforcing a one-per-turn rule.
- It prioritizes logistics disruption and counter-battery roles, making air assets more impactful.
- Future refinements could introduce mission-based prioritization, such as focusing airstrikes on advancing enemy forces or known strongpoints.
This approach ensures that airpower remains a tactical tool rather than a blunt-force weapon, adding depth to AI decision-making.
^ There you go! Could not said it better myself.
Looking Ahead: Iteration #2
Designing, writing, and testing Iteration #1 of the Next Gen csmklua utility was great fun. I initially aimed for a more generic yet cunning and randomized logic for Battle Plans, but getting the framework in place took time.
For the next iteration, I hope to implement a Set Counter Measures system with the following principles:
- When advancing, secure a captured objective before pushing forward to the next.
- In a rearguard action, fall back to the next objective while delaying and disrupting the enemy as much as possible.
For Handle Combatant Units, the plan is to expand support for additional unit types, ensuring more comprehensive AI behavior across different formations.
-
Infantry companies should execute their missions to the best of their abilities. Now that Mission Type is available in Lua, different missions should play out accordingly—Ambush should feel distinct from Rearguard Action, and a Recon Attack should not behave like Mopping Up.
-
Support behavior must be properly implemented. Tanks should operate with dedicated logic, while the real challenge lies in handling IFVs and APCs. These units must behave logically both when loaded with infantry and after disembarkation, adapting to their role on the battlefield.
Refining these mechanics will be key to creating a more responsive and tactical AI, where support elements coordinate with combat units rather than acting independently.
Now, the Engine AI—the legacy AI—isn’t actually bad at handling individual units. It really isn’t. Expanding these behaviors will bring greater depth to Scripted AI, making engagements feel more dynamic and mission-driven.
So much to do—wish me (and us) luck!
Thank you for reading this far, and stay tuned for Vietnam 2.00.01 and Middle East 3.00.01 releases!








