Scripted AI and Battle Plans

I have a cunning plan…

Scripted AI 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:

  1. Targeting and use of Indirect Fires
  2. 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:
  1. 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).
  2. Check for Ammo Availability:

    • If the unit is low on ammo, it is released to the Engine AI (EAI) instead of firing.
  3. Select Target Priorities:

    • Gathers friendly and enemy objectives to define potential targets.
    • Builds a priority target list based on known enemy positions.
  4. 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.

  5. 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:
  1. Chance-Based Decision:

    • Each turn, there is a 50% probability that an airstrike will be considered.
  2. 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
  3. 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!

Scripted AI and Scenario Briefings

Welcome to the second part of my blog series on Scripted AI. In the first post of this series, I discussed Scripted AI in general. Then, before this one, I wrote about the two CSEE How-To documents that Berto created, which will soon be included in the game manual content. Now, let’s dissect the Scenario Briefings.

Scripted AI Scenario Briefing CSEE Lua

Scripted AI Scenario Briefing CSEE Lua

SMEAC

CS Vietnam 2.0 Scenario Description Template

CS Vietnam 2.0 Scenario Description Template

Our Scenario Briefings, courtesy of scenario-specific Campaign Series Event Engine (CSEE) Lua files, loosely follow the five-paragraph field order style: “S” for Situation, “M” for Mission, “E” for Execution, “A” for Administration and Logistics, and “C” for Command and Signal.

As we covered in the first blog, the csmkllua.pl (“make Lua”) utility initially generated what was mostly an empty skeleton for scenario designers to fill with the necessary details. The Scenario Briefing was one of those blank canvases. In the next update, the revised Make Lua utility will take things a step further.

Let’s dive into the details. As an example, I will use a Community mod available via the JSGME Mod Enabler that ships with the tool: Alan R. Arvold’s “Battle of Black Virgin Mountan, Octover 15, 1966, which is one of the three scenarios that are available under the Ode to Divided Ground Vietnam Mods Campaign, once enable.

Greetings, Commander!

Actually, even before the Situation paragraph, there’s a welcoming statement to this particular scenario.vided below. Carry out with accuracy.

With stock scenario files all starting with a two-letter country code as the prefix, csmklua uses that to set the scene. If, for any reason, such a prefix is not identified, the region name is used instead. For example: Welcome to Southeast Asia! Here, instead, as always suggested: I edited the Lua scenario file created by the Make Lua utility to introduce players to this Campaign set instead.

Next, there’s an encouragement to follow orders diligently. This message comes from a text array or table of sentences. Tables are easy to implement in Lua and are also used for other communications. For instance, when an objective falls into your hands after your genius strategy, a random commendation is sent your way.

So here we go:

Commander, welcome to Alan R. Arvold’s Ode to Divided Ground Vietnam Mod! Your SMEAC is provided below. Carry out with accuracy.

There is a wealth of intel about the scenario being opened on the game engine side. Some of this information was already available through the CSEE Lua API (application programming interface, or simply, a function you can call to process or retrieve data). However, since much of it was considered “chrome” (nonessential details), many aspects were not included.

Among the new APIs I wrote for this update, I added a function that retrieves the part of the scenario description template identifying the exact location where the depicted battle took place.

I. SITUATION:

Scenario: Battle of Black Virgin Mountain; 15 October, 1966

In addition, map intel such as Terrain type, Ground conditions, and Visibility is now retrieved and displayed under the Situation paragraph.

a. Area of Operations: Tay Ninh Province, South Vietnam. Terrain: Tropical Rainforest, dry ground. Weather: day, visibility 5000 meters.

For the Friendly and Enemy forces intel, I enhanced the Make Lua utility to include higher-level organizational names and calculate an average morale factor for those levels, alongside the usual unit IDs (trackids) used in CSEE code. This information is now presented to the player as well. It should prove to be valuable, especially when playing under Enhanced Fog of War, where intel on the map is scarce at the best of times. There’s a shout for the available support, typically Air Support, if any. Pay attention to your Command Reports as you play along, as all airstrikes are often not immediately available.

b. Enemy Forces: Elements of the 88th Rifle Regiment, 57th Heavy Mortar Battalion, 43rd NVA Special Action Group, 183rd VC Sniper Group, and Civilians. Enemy morale is assessed
as moderate. Adjust your tactics accordingly.

c. Friendly Forces: Units of the 25th Infantry Division, and Relay Station Garrison.

d. Supporting: Full Air Support available: 4 flights of Close Air Support. Current missions allocated can be found in the Status > Command Report.

Finally, there is a warning regarding indirect fire from or to inhabited hexes or for calling airstrikes there. The CSEE skeleton includes Event Point penalties for such actions, so pay close attention.

e. Civilian Considerations: You will lose Event Points for indirect fire or airstrikes on inhabited areas (villages, cities, suburbs), regardless of enemy presence.

Here’s the complete Lua section that creates the Scenario Briefings for scenarios:

Scripted AI - Lua code behdind the Scenario Briefing

Scripted AI – Lua code behdind the Scenario Briefing

Mission

If Situation was still about “chrome” in some way, Mission is where things get interesting.

Those familiar with our Scenario Editor that comes with all games (alongside the Map and Organization editors) might recall there is a dialog to assign Mission Types for both sides. These mission types were not previously used by the game engine, but now they are being put to good use.

There is now a new CSEE API to retrieve Mission Types for both sides. Each mission type now has a weight attached to it, representing postures that range from tentative recon attacks to strong or even fanatical last-ditch defensive postures. Note another dynamically created phrase to bring some flare to this.

II. MISSION:

Your mission directives are: Mopping Up, from turn 1 to turn 24. Search and Destroy, from turn 1 to turn 24.

Enemy actions are anticipated to focus on: Ambush, from turn 1 to turn 24. Infantry Assault, from turn 1 to turn 24.

Prepare to act on all mission directives.

In addition to the mission weight, or mission posture, there is now a set duration for each mission. By default, since the Scenario Editor does not provide this level of detail, each mission is assigned a duration from turn one to the last turn of the scenario. However, there is a TO DO tag set for the scenario designer to edit these. For example, a Meeting Engagement for the first ten turns, followed by a Rearguard Action for the remaining turns as opposition was found to be too strong.

Which brings us to…

Execution

First, the scenario briefing is quite generic at this stage. To add situation intel, scenario designers are encouraged to chip in.

III. EXECUTION:

Stay agile and modify your approach based on battlefield changes. Pay close attention to CSEE events during combat, as they may affect your strategy.

Victory depends on reaching mission targets while maintaining operational strength. Monitor Status > SITREP for real-time updates on the operational situation.

With mission_posture now available in the CSEE Lua file, it can be put to good use in guiding the Scripted AI to execute according to the mission_posture in play. Weighed die rolls to determine between weak, normal, strong, or fanatical attack() or defend()? Yes, why not.

Mission Posture Set-up Lua

Mission Posture Set-up Lua

The Mission Type itself can also be utilized in CSEE, which is particularly useful if a Rearguard mission is in play. For example, units can fall back to the next objective behind the lines, defend weakly (where the defending unit is more likely to retreat than take casualties), and then fall back again to the next objective in the rear.

This kind of tactical execution was already implemented by scenario designers, such as in Vietnam 1.0, so it is not entirely new. What is new is that this type of execution can and will become more automated as we continue to enhance the Make Lua utility.

Administration and Logistics

“Amateurs talk about tactics, but professionals study logistics.” And why not, as logistics takes on a stronger role even in the mostly tactical battles depicted in Campaign Series scenarios. The new CSEE Lua skeleton now tracks the gradual decline of Ammo and Artillery Ammo levels once the first shots of both types are fired. The decline is slow, but in a long scenario, it can have a noticeable impact. Even at this tactical scale, not all units should be able to go all out all the time, right?

In Vietnam 1.0 scenarios, it was already common for losing a Supply source to decrease Ammo levels. Additionally, there were often scripted means in CSEE to replenish supply. Now, there is added automation for this. For every full set of 10 turns (a default that scenario designers can adjust), a Logistics Round is triggered, increasing Ammo levels by 10 percent.

IV. ADMIN AND LOGISTICS:

Current Supply levels: Ammo: 80%, Artillery Ammo: 80%, Smoke: 12, Flares: 0.

Be aware that supply levels will decrease as combat intensifies, so plan accordingly.

As of the current status, Logistics is prepared to deliver 2 Ammo resupply and 2 Artillery resupply rounds.

As another improvement, there is a new dialog to check for any orders available. The Make Lua utility will now include these Logistics Rounds by default.

Resupply available! Check for Orders dialog in action.

Resupply available! Check for Orders dialog in action.

Scenario designers can expand on them by incorporating other tactical options and considerations.

Command and Signal

Finally, Command and Signal. While the Scenario Briefing keeps this paragraph relatively brief, there is a lot happening on the Command side of the computer opponent, with Scripted AI working in its favor.

V. COMMAND AND SIGNAL:

Refer to the Status > Find Organizations dialog for your complete command structure.

Hold that thought, as the third installment of my Scripted AI blog series will dive into the Battle Plan portion of the new CSEE scenario skeletons.

Good luck, commander!

Thank you for reading this far, and stay tuned!