Scripted AI in Campaign Series
This article has been updated to reference the upcoming versions as 2.00 and 3.00 instead of 1.40 and 2.40. This change reflects a full reimage that includes new graphics, a redesigned main menu, and use of latest .NET and C++ redistributable libraries.
With the next 2.00 / 3.00 Updates due in the beginning of next year, let us again go under the hood on the Lua based Campaign Series Event Engine and the scenario designer access of it via Scripted AI.
Here are the previous introductions on CSEE at the time of Vietnam 1.0: https://cslegion.com/category/csee/
Here’s my plan for further elaborating on CSEE and Scripted AI over the coming weeks:
- Part 1: A Brief introduction to Scripted AI
- Part 2: SMEAC – Situation, Mission, Execution, Administration and Logistics, and Control and Signal
- Part 3: Battle Plans – The Cunning Plan (no pencils required!)
So here goes!
Note: this is a lot of text with a lot of links to existing documentation and to previous CS Legion and Matrix Games forum posts and articles. Not a lot pictures, either. But I really want to cover these before going on into actual scenario, with hopefully plenty of pictures and real life examples.
A Brief introduction to Scripted AI
Lua in Computer Games
When Berto, our lead programmer took a job to introduce a scripted AI module to Campaign Series, to make its very first appearance with Campaign Series: Middle East in 2018, he had a good look around.
I believe CMANO was Berto’s role model where he got interested in providing the scenario designers with a user friendly programming language. CMANO is hardly alone, so we, too, are in very good company!
At the time, I was in awe as how quickly Berto was able to churn out new CSEE functions, and now, continuing his work as the CS programmer, I also came to appreciate how fast the integration works in real time. No delays, immediate action. Turns out, as I started to study the subject, this is how Lua was designed from the very beginning: a language and run-time that is very easy to embed with applications and is very efficient in itself as well. Here’s the Official Lua Site, and Here’s the C API from the Lua Documentation.
I will not dwell on that though, that is for the programmers. Let us instead look at this from Scenario Designer’s point of view.
Lua in Campaign Series
As briefly introduced in Triggering Events, one of the previous CSEE poststhe events taking place during a scenario play are caught in the accompanying scenario Lua file.
And to have that scenario Lua file, we are providing an accompanying CSlint utility: csmklua.pl (pl for a Perl script). Once you execute the script, an empty skeleton file to place all the Lua code is automatically made available. Except for the next update, that template will not be an empty skeleton any more. Enter the csmklua.pl version 2.
Csmklua v2 puts in play some of the best practices we’ve learned while scripting the AI for the existing Vietnam scenarios. Scripting a scenario can be a laborious task, so any automation is appreciated. I will go under the hood of csmklua v2 in the coming posts.
Before we do that, let us look at the Campaign Series Event Engine function library for the next update.
Campaign Series Event Engine 2.00 / 3.00
As our appetite grows to take more and more of the AI from the C++ game engine side of things, the Engine AI (EAI), the more we need situational awareness in the Lua side of things as well.
What does that mean? Anatomy of a scenario phase (one turn of the game, per each side) is as follows:
- At the turn of the phase, game engine sends all the map (hex) and unit (counter) information to CSEE (for you programmers out there: to Lua stack). Now, Scripted AI has the latest situational awareness from the beginning of the phase.
- Next, the Lua CSEE engine calls each side’s battle plan section, for any assignments that will take place for the turn, or in case of for example indirect fire, or reinforcements, for the next turn. Again, this is worth a separate post, so my Post #3 of the series will touch base with this.
- Then, as you play along, various events can take place in the scenario, and the respective trigger is evoked. Objective was captured, a unit was killed, a mine exploded, an air strike took place. When that happens, you can react to that by calling various CSEE functions.
New CSEE functions for 2.00 / 3.00 UPDATE includes a lot of new things.
For general information, for the Scenario Briefing for instance, there are new functions such as
- scenario_title () — Returns the scenario title
- scenario_date () — Returns the scenario date from scenario description
- scenario_oparea () — Returns the scenario operational area description from scenario description
- scenario_location () — Returns the scenario location full name based on the file naming convention.
- scenario_force_nations (side) — Return a table of unique nation IDs from FORCE_NAMES based on side
- scenario_force_friend_name (side) — Return the names of the friendly forces in FORCE_NAMES based on side
- scenario_force_foe_name (side) — Return the names of the enemy forces in FORCE_NAMES based on side
I will revisit these on Part #2 the Scenario Briefing.
For the actual situation awareness, there are new functions such as
- hex_concealment_mod (hc) — returns the concealment modifier value of the hex
- hex_combat_mod (hc) — returns the combat modifier value of the hex
- hex_morale_mod (hc) — returns the morale modifier value of the hex
- visible_hex (from, to) — returns true if the hex is visible from the other hex (ground level check only)
These will come handy when a unit figures out their next action I am sure. For instance, when on defence, look for a hex with good concealment, combat and morale modifiers – they often come together – and dig in!
To determine what each unit is capable of, we have these new functions:
- counter_softrange (trackid) — returns the maximum range of a soft attack
- counter_hardrange (trackid) — returns the maximum range of a hard attack
Weapons range intel will become handy to especially long range field artillery units, as they perform operational fire missions. Together with Line-of-sight check now available, we can plan direct-fire missions now better as well.
Little things, but they add up. Hence, some of the existing combat related functions are enhanced to take the unit weapons range and line-of-sight into account. Now you can set these orders in confidence that if the unit is not able to execute on given orders, you are told so, and you can loop to next order:
- fire_direct (trackids, hc, dir, radius, actprob, maxrange, los_check) — Added optional ‘maxrange’ and ‘los_check’ parameters. If either fails for any trackid, returns false.
- fire_indirect (trackids, hc, dir, radius, actprob, maxrange) — Added optional ‘maxrange’ parameter for the weapon range check. If check fails for any trackid, returns false.
- fire_special (trackids, hc, dir, radius, actprob, maxrange) — Added optional ‘maxrange’ parameter for the weapon range check. If check fails for any trackid, returns false.
- fire_direct_area (trackids, hc, dir, radius, actprob, verified_only, maxrange, los_check) — Direct fire at a random enemy unit in the designated area. If unit weapons range provided, uses it. Optional boolean los_check parameter to verify LOS.
- fire_direct_nearest (trackids, actprob, ground_only, maxrange, los_check) — Direct fire at the nearest enemy unit. Targets verified (spotted) units only. If unit weapons range provided, uses it. Optional boolean los_check parameter to verify LOS.
- fire_direct_nearest_to_hex (trackids, hc, actprob, verified_only, maxrange, los_check) — Direct fire at the enemy units nearest to the given hex. If unit weapons range provided, uses it. Optional boolean los_check parameter to verify LOS.
- fire_indirect_area (trackids, hc, dir, radius, actprob, verified_only, maxrange) — Perform indirect fire at a random enemy unit within a designated area by each unit in trackids. If unit weapons range provided, uses it.
- fire_indirect_nearest (trackids, actprob, ground_only, maxrange) — Perform indirect fire at the nearest enemy unit for each unit in trackids. If unit weapons range provided, uses it.
- fire_indirect_nearest_from (trackids, hc, actprob, ground_only, maxrange) — Move units to a specified hex and then perform indirect fire at the nearest enemy unit (if unit weapons range provided, uses it).
- fire_indirect_nearest_to_hex (trackids, hc, actprob, verified_only, maxrange) — Indirect fire at the enemy units nearest to the given hex by each unit in trackids. If unit weapons range provided, uses it.
A long list, point being that unit weapons range and from-to line-of-sight checking is handled as part of these functions now, instead of the scenario designer needing to implement them per particular situation.
Again, a lot of text, but hopefully all this becomes clearer in the next blogs where we look at Scenario Briefing SMEACs and the Cunning Battle Plans themselves:

Scripted AI: New CSEE API in Action
So what goes on, above? That will be explained in part #3 of the Scripted AI articles, where we have a deeper look at how battle plans are dynamically handled.
Thank you for reading this far and until the next time!




