DCS Mission Editing Tools and Discussion

Yup. A ‘wrapper’ is unavoidable.

I was forced to roll my own as, at least at the time, nothing else had a text-to-speech engine (among other divergences). So I wrote one in lua. Shocked it works as well as it does.

When I finished with that I realized I knew enough about lua/DCS API that I didn’t need to learn another system.

I was looking at those but much of them are marked “depricated” on the documentation. LIke the A2A dispatcher has been replaced with OPS EasyGCICap, but there’s no demo for that on the Github. It might be on their Discord buried in some pinned message but having to scrounge info from 3 different sources (one of them being the disorganized mess Discord naturally is) just to do one thing is a right pain in the arse. I was hoping they (or someone) had an ELI5 tutorial somewhere to help me figure out when I need to do what with the lines of code.

I’ve slowly gotten some of the coding working but at these point I’m tempted to resort to vibe coding with ChatGPT if it would stop using the deprecated functions and use the newer OPS stuff in MOOSE. A lot of the problems I’m still running into are:

  • There are no demo missions/scripts for the specific task I want to do; the relatively simple tasks I want to do requires stringing together multiple different functions but I have no way of inferring which functions I need. Nor the order in which I need them to run. Nor which ones can be on the same lua file and which ones need to be on separate lua files.
  • The demo missions/scripts that do showcase things I need are either too complex or too limited. Let’s say I want to learn how to add an airwing to a carrier. I have to dig through a massive alpha strike demo lua that has a couple hundred lines of crap I don’t need. Let’s say I want to generate BAI flights to search-and-destroy in a specific zone. Well there’s a demo lua of BAI flights attacking a specific group and that’s it. Can I replace the target group with a target zone? If so, how? Let’s say I want to schedule events for repeat. There’s a demo lua but it only shows how to repeat an on-screen message. How do I get it to repeat more tasks than that?
  • There’s multiple functions that appear to do the same thing. Let’s say I want to send AI on CAP missions. I can do OPS/EasyGCICAP, or OPS/Chief/A2A dispatcher, or OPS/Auftrag/CAP. Why are there three of the same thing?

I suppose most of my second point is trial-and-error playing with different things but that is a massive waste of time.

At some point, though, I would like to be able to create a simple linear-progression, persisting, fictionalized Marianas WW2 campaign where you start on the Essex in support of the invasion of Saipan, capture the port and airfields, then can operate out of either the Essex or Saipan in support of the invasions of Tinian and Guam, capturing those airfields and ports.

Well, the short answer is: you’re gonna have to get your hands dirty - more dirty than you may want. There’s as many ways to skin a cat as there are, well, cat skinners.

The lua language, in this context, allows you to structure things a few ways. It is more programming language than a scripting language - everything that’s not assembler is a scripting language, really, but I digress…

What it sounds like you want to do is complex. Especially if you want it to be transferable, which it will be using lua. I stopped counting at ~55 THOUSAND lines of code (lua and C#). It’s way more than that now. Lines of code is kind of a nebulous term but, well…

My thoughts on your more detailed questions, assuming you are planning to do this all in lua:

You have create the task (table) then change the task (see: setTask & pushTask from the API linked above). I think in the mission editor (ME) the former is a ‘Perform task’, the latter an ‘enroute task’.

Start here:
https://wiki.hoggitworld.com/view/Mission_Editor:_AI_Tasking
…in the Perform Task vs Enroute Task section.

I had to read that several times and ‘play’ with it to grok it all.

You can start with simple stuff in a test mission; setup some triggers (via radio item so you can control when they happen). Once it works there then lookup the in API the corresponding function that is used. I have over time had dozens of these simple ‘scrap’ missions with just the one or two objects I want to focus on.

Moving on…

I’m going to assume it used timer.scheduleFunction(). This is a VERY powerful function. If they didn’t have this I would have ended up writing it myself.

The API gives you some guidance but…let me use pieces of ‘fireAtPointRepeatedly’ algo as an example (this isn’t the full code - too much to cram in here - but should give you a start:

standby…need to switch computers…

1 Like

New post:

I reuse a lot of this for anything that can shoot something, from TLAM’s from a missile cruiser to mortars attacking my base [when I jump into the cockpit]

This may not format well here; the lines are too long…it won’t ‘just work’ if you try to plug into something as there were a LOT of steps before all this.

Maybe I should’ve used pseudo code but, well…

JDCS.Task.FireAtPointRepeatedly = function(fireAtParams)

– API function here
if Group.getByName(fireAtParams.groupName) == nil then
return nil
end

– take the first shot
JDCS.Task.FireAtPoint(fireAtParams)

if fireAtParams.repeatInterval then
return timer.getTime() + fireAtParams.repeatInterval
end
return nil
end

The above is just one branch, called by this:

– earlier in the flow…

if not someCondition then
– just fire one time
timer.scheduleFunction(JDCS.Task.FireAtPoint, params, firstShotDelay)

else
– fire for n-iterations at some time interval
timer.scheduleFunction(JDCS.Task.FireAtPointRepeatedly, params, firstShotDelay)

end

– It’s all based on the 2nd parameter to .scheduleFunction() See the API

That parameter can be ANYTHING. I store a few items but, in this example, I store a ‘count’ and ‘maxCount’ variable (that you can randomize) that iterates every time the above is peformed:

.> …params.count = params.count + 1

if params.count > parsm.maxCount then
return nil – <<< this terminates this repeating function.
else
return timer.getTime() + params.shotIntervalInSeconds
end

– the PARAMS parameter is a table, that might look like this:

local fireAtParams = {
count = 0,
maxCount = 4,
shotIntervalInSeconds = math.random(120)
targetX =
targetY = <the Y (or z, depends on the source>
…more stuff you may need
}

So, timer.scheduleFunction( fireAtSomething, params, timer.getIme() + 1)

‘fireAtSomething’ is a function
‘params’ was created above
'timer.getime() + 1 means [start] calling ‘fireAtSomething’ 1 second from now.

Hope this helps, at least a little. Yeah, this stuff is, if nothing else, time consuming.

But, the beauty of it is: you only have to do it once (maybe)

1 Like

And, from the FWIW department. You can do things in lua you can’t in the ME trigger, like shoot friendlies! During an early test of my JFO (Joint Forward Observer) code, where the AI flights in the ‘stack’ were let loose on a list of targets collected by the JFO AI - I noticed they were attacking friendlies units (via AttackGroup).

The code to detect targets will give you EVERYTHING in the area requested. I forgot to filter by coalition.

Can you say, fratricidal!

1 Like

Big picture yea, small picture not really. For example with MOOSE “EasyGCICAP” all I have to do is place a group of fighters, a static warehouse at the airbase, and a trigger zone. In the lua code I only need like 5 lines and that’s it: I have endless CAPs flying for the defined on-station duration, with new aircraft being sent out immediately after the last CAP returns. No scheduler function required.

But if I want to do the same with a BAI search-and-destroy in zone, I apparently have to deal with an order of magnitude more lines of code, with a scheduler function, and no indication that I can do it with a zone at all (only examples being to target a specific group, or static object; the only zone usage example I’ve found is for carpet bombing). Despite being essentially the same task just A2G instead of A2A. The inconsistency makes it more difficult for me to figure out what I need to do.

You’ve hinted at why I don’t use things like Moose. Those ‘wrappers’ make things easy…until you want to do more. They have done all the work to build the structure (ie, a house) - their own way; if you want to add a different type of door to that house you’re out of luck. Unless you either a) hack Moose or b) build a shed out back with a kind of door you want.

See:

EngageTargetsInZone

Keep in mind this is an ‘Enroute’ task, not a ‘PerformTask’, task - I didn’t write the API, talk to Eagle Dynamics. The behavior of the AI will be different (see link above on Tasking). I’ve found it easier not to mix the two.

The above (Enroute vs Perform [task]) may explain things about why Moose has, or doesn’t have, certain things.

Then there’s the task ‘stack’ you have to deal with, unless you use something like Moose (AFAIK).

Yeah, it’s a PITA. But it’s the only reason I stick with DCS - I can create my own ‘experience’, if I’m willing to be annoyed[1]. BMS? I don’t think you have any mechanism to customize things (I like BMS, just not as much, anymore).

[1] I was forced to ‘roll my own’ since I needed the AI to “talk” to me, setting off a chain reaction…

need an interface to ‘my’ AI…learn how to manage the F10 menu…create a vocabulary so I can…build sentences…need audio clips for each word…build an offline audio parser…find an app that does text to speech using different voices (and chops them up into individual words)…get this all into DCS…work the timing to stitch the words together in lua…more…

All to get ‘George’ to read me a briefing or ‘Suzy’ to transmit the location of the nearest tanker.

It’s a lot of work. Unless you stick with an ‘off the shelf’ solution, which comes with limits.

1 Like

Today I tried a little AI prompting where I ask an online LLM to write me something in Lua for something I want to achieve in the simulator, but do not have the skills for to write myself. To start I asked it to calculate an intercept heading in Lua and it the headings it was giving was good enough to make the AI jink out of the way.

I assume the scope of whats possible with an LLM is more limited than what is possible with tools that are being shown here or moose and the like, but perhaps it can help people to bring Lua into missions and make something that’s more fun and better without a terrible amount of programming and mission making knowledge.

2 Likes

Yup. Be nice when this is integrated with the sim…

I asked my LLM (that would be me):

“Give me a set of missions spanning multiple time frames, with multiple aircraft, using multiple maps, with targets and objects spanning the entire map; create multiple AI control agencies that I can have bi-directional communications with audio; assign multiple tasks dependent upon the aircraft capabilities, for myself and AI, working in cooperation when necessary…yadda, yadda, yadda. And extract from the internet data and documentation so I don’t have to. For a start. Oh, and have it not hurt performance”.

It did, it just took about 4 years. Due to time limits (the ‘engine’ is there but the data! …sheesh, takes a while) it only gave me the CliffNotes version. So I’m still talking to myself :slight_smile:

3 Likes

Yeah I think that’s another part of the issue. Certain things I need (like carrier auto-turn-into-wind) are super simple through Moose compared to using other tools.

What always gets me with documentation like this is there is so much missing information. Like:

  • Do I need to create a trigger zone named “Vec2” in that example?
  • targetTypes = array of AttributeName has a link to all the attributes, but are they listed in the same format as they have to be input? E.g. [“Trucks”] = {“Unarmed vehicles”,}
  • Which of the above attributes is the one I actually use, [“Trucks”] or {“Unarmed vehicles”} ?

I apologize ahead of time here as I don’t know your background in this stuff. Based on that question you are new to it…

‘Vec2’ is a 2 dimensional point. By convention referred to as X and Y (could be any letter but its a math thing).

However, most conventions I’ve used (decades ago to be sure), when talking about cartesian coordinates - on a 2D surface, like a map, x/y are:
x = east/west
y = north/south.

Guess what? ED flips these x = north/south, etc. It get screwier.

But DCS is a 3D world. Some values you get from the API give you: X, Y, Z. - a 3D coord where Y is now the altitude above mean sea level (all values are in meters BTW). So, given that funciton…

EngageTargetsInZone = {
id = ‘EngageTargetsInZone’,
params = {
point = Vec2,
zoneRadius = Distance,
targetTypes = array of AttributeName,
priority = number
}
}

Vec2 is a 2D point, relative to the origin (set by the devs’ that built the map).

If the Vec2 = x = 1234, y = 4678

Then the point is 1,234 meters north and 4,678 meters east (both are positive values here if you negate them then the point is South/West of the origin) from the origin. x = 0, y = 0. But you kinda don’t care you just want the value of, say a trigger.

You get this with:

local MyZone = trigger.misc.getZone(‘Name of some trigger’)

MyZone is a table with a bunch of values within. We want the coords of the point (center) of that zone:

local point3d = MyZone.point << the point portion of MyZone table.

Now, this is a 3D vector but EnageTargetsInZone wants a vector with 2 components:

params = {
point = point = {x = point3d.x, y = point3d.z}, << Z here, not Y
zoneRadius = zone.radius, << another value in the MyZone table.

}

We’ll ignore priority (that can complicate things and isn’t often, for me, necessary).

targetTypes = array of AttributeName

where AttributeName can be a lot of things and each is just text (a string - a key really but we don’t care about that right now), some examples:

Attribute describe a thing, if you will. Here’s how a Hawk search radar is described via its attributes table…

…not including all the Hawk SR attributes here, there are a couple dozen:

attribute = {…“Ground Units”, “Air Defence”, “SAM related”, …},

It tells you, most importantly for this example, that a Hawk SR is ‘SAM related’ - has something to do with SAM’s. It is also an ‘Air Defence’ object, etc.

So, back to targetTypes = array of AttributeName

params = {
point = point = {x = point3d.x, y = point3d.z}, << Z here, not Y
zoneRadius = zone.radius, << another value in the MyZone table.
targetTypes = {“SAM related”}
}

If we ‘pushTask’ using those values the group will search about that point, within radius meters and only target things that have something to do with SAMs. Note the AI has other priorities - wish they would tell us more on this but, alas…

From a Google AI search example (kinda handy I’d say):

local engageZone = {
    id = 'EngageTargetsInZone',
    params = {
        point = {x = 123456, y = 789012}, -- Coordinates of the zone center
        zoneRadius = 10000, -- Radius of the engagement zone in meters
        targetTypes = {'Air', 'Ground', 'Sea'}, -- Types of targets to engage
        priority = 10 -- Task priority (lower number = higher priority)
    }
}

-- Assign this task to a group named "MyFighterGroup"
**>>> NOTE THIS CODE FROM THE AI IS NOT CORRECT <<<<**
local myGroup = Group.getByName("MyFighterGroup")
if myGroup then
    myGroup:pushTask(engageZone)
end

NOTE the above code: myGroup:pushTask(engageZone) is NOT correct. Based on the API reference (I’ve never tried it however)

So, replace this

targetTypes = {‘Air’, ‘Ground’, ‘Sea’}, <<< this is basically saying “look for EVERYTHING”

with this

targetTypes = {‘SAM related’}, – only things having to do with SAMs

Buried deep within DCS there’s a conditonal branch like so:

FOR ALL targets NEAR point…
If target.attribute contains the symbol ‘SAM related’ THEN
Store it somewhere for later use…
END

2 Likes

Okay, so in that particular system I have to define the actual point in space with raw numbers instead of telling the system to look for a trigger zone with a specific name? Kinda like reading the Matrix code, if you will?

It only cares about the x,y components, the numbers. How you get those numbers is up to you, via trigger is common. Or the position of a unit (if you want to look for a carrier give it the coords of that ship). Or an airbase. This requires an API function; have to dig deeper to get that info.

You could create trigger zones wherever you want then when needed use:

local zone = trigger.misc.getZone(‘NameOfMyZone’)

See: getZone

Shoot, with scripting you could have an F10 item that tells some AI uint to “Look here” for ‘SAM’ related objects. When you click ‘Look Here’ you get/pass your current position. The Ai will then look where you are. Just off the top of my head. The API to get your position is the same as any other unit (unit:getPoint())

1 Like

Major breakthrough for me, I figured out that damned “local” thing. Any time I’m defining something with an = I need to use “local.”

I can also get stuff to work under the hood, with no scripting errors in the logs, but the desired results aren’t appearing in the actual mission. The example luas and miz files for Moose aren’t helping and I finally pinpointed why:

To @jross’s point about there being more than one way to skin a cat, the structure and flow of each example lua is different. Lack of consistent structure interferes with pattern recognition. Without pattern recognition a learner can’t bloody well learn. Of course pointing this out to them results in indignation and being dismissed with a reduction to absurdity like “well not everyone can be Shakespeare.” Assuming the reader is less knowledgeable and adjusting content accordingly was basic high school level stuff last I went though, and assuming most DCS gamers who want to try MOOSE aren’t going to be intimately familiar with code is basic common sense. Ergo, making this easier to navigate and consistent should be basic common sense but instead it’s gate kept behind coders’ refusal to consider the reader.

The newfound activity in this thread got me working on an old DCS tool project of mine: interactive connection to DCS scripting engine using jupyter-console or even more powerful jupyter-lab interfaces. It has been working prototype for a while, but now I decided to package and publish it for the others to use and contribute to.

It would be great to get feedback if you are willing to try it out. The github repository is now public, and the package is published to PyPI for simple installation outlined in the project instructions readme.

Any feedback is appreciated :saluting_face:

Edit: Now uploaded to PyPI and the install instructions seem to work.

1 Like

It’s about ‘scope’ (accessibility to the value in programming terms). From Google AI, term “lua local”

In Lua, the local keyword is used to declare local variables, which have a restricted scope compared to global variables.

  • Scope: Local variables are only accessible within the code block where they are declared. This can be a function, a loop (for, while), a conditional block (if), or a do...end block. A variable declared without local (a global variable) is accessible from anywhere in the program.
  • Best Practices: Using local variables is generally recommended according to Lua documentation. This helps prevent unintentional interactions between different code parts, making the program easier to understand and debug.
  • Performance: Local variables are accessed faster than global variables in Lua. This is because global variables require a hash table lookup, while local variables are stored in registers and can be accessed directly by index.
  • Memory Management: Local variables are more memory-efficient. They are created when their block is executed and destroyed when the block finishes, releasing the memory they used.

Leaving ‘x’ global, if nothing else, makes managing any ‘system’ you create more difficult and hard to keep track of (using ‘x’ inadvertently in multiple places, see below). Leading to funky bugs.

This applies, generally, to all the programming languages I am familiar with. FWIW, the performance bit is something i keep in mind all the time, ie; my entire code base is an entry in a table. The root member of that table is global - just the way lua works. So, in a function when I want to access -use- a value in that table (system) I will ‘alias’ it, ie;

Given a layout like this:

MyCode.subSytemA.x = 1234.56

In a function called:

doSomethingThatUsesX()

local x = MyCode.subSytemA.x – alias

– ‘local x’ is now in a register – very fast access. so In a [contrived] loop that uses x 1,000 times…

for y = 1, 1000 do
local z = y * x – faster…
–versus
local z = y * MyCode.subSytemA.x – slower
end

Every ‘.’ [period] in MyCode.subSytemA.x can be thought of as another ‘long distance’ jump to another location in system memory[1], versus grabbing ‘x’ from a register. This goes way deeper but should be generally correct.

[1] The author’s of the lua interpreter (the thing that converts your code into lower level (faster, more efficient) code, has control over this but, a) it makes sense, and b) it has been explained (documented) as a best practive - if you care about performance.

At the risk of sounding like a smart-azz: programming languages are just that: languages. You are free to ‘express’ yourself in a given language in any manner. But first YOU have to understand what it is you are trying to ‘say’. Now, that may not make any sense at all to ME.

Most examples are, hopefully, bare-bones, minimalist examples of: with Input A you get output B. It’s up to me to take that tidbit and express myself within the context of my work to meet my needs.

I just ran across this yesterday: I want to read the DCS debrief.log file (why would take too long to explain). My current system to parse a lua file (the one generated byt the mission editor) works but I wanted something that was more ‘universal’/general.

In doing all that (this is data afterall) the term C.R.U.D in programming:

Googl AI
In programming, CRUD is an acronym that stands for Create, Read, Update, and Delete. These four operations are fundamental to how applications manage data

The API example of saving my Udated results didn’t work. Their example was too concise - I had to dig into the source code to find out why. I’ve set that aside for now as it is lower priority, but all I wanted to know was: what is the correct input to your SaveToFile() function. They didn’t explain it, at all.

Love the idea! Alas, while I’m pretty sure I could grok python pretty quickly (based on a quick review) I have to set that aside until my current task is complete; automating the populating of new maps with the basic engine code. More like organizing it; I can get the basics of a new campaign setup in a day or two but it is still too tedious for my liking.

ie; not enough time in each day. :slight_smile:

I have wanted a method to ‘trigger’ things during a test run - a proxy for you, the player, if you will; since the ‘bubble’ system of course is based on your current position I have to fly around to do this. F10 entries help, a little, and have worked well enough but something ‘slicker’ would be nice. Time. Time Time.

1 Like

Thank you for the feedback,

feel free to try it when you have the time. I can totally relate to having lots of cool things to do, but limited time :wink:

I think this would be perfect tool for that kind of purpose. In fact, you don’t write a single line of Python code here. You can just send lua code lines or blocks to the live DCS mission to be executed.