Jump to content

Tutorial: Introduction to Lua scripting


Recommended Posts

While I was creating my first large mission, I had one task that I couldn't achieve with the Mission Editor but that should be easy enough with a bit of Lua scripting magic. It took me a while to familiarize myself with Lua scripting in the ME. I hope this introduction to the topic will allow other mission builders a smoother start at it.

 

This tutorial is aimed at mission designers with a basic understanding of the DCS Mission Editor (ME) who want to find out about Lua scripting inside the ME.

 

This introduction currently refers to DCS 1.2.7 and may be incorrect or outdated for older or newer versions.

 

I'm by no means an expert on the matter and will gladly correct any mistakes, incorporate links and extend this guide. Just write a reply to this thread. :thumbup:

 

This tutorial is far from complete, it merely scratches the surface. For the time being, it is however the introductory tutorial that I wish someone else had written so that I didn't have to figure all of this stuff out for myself. Then again, I've already learned a huge amount of stuff about Lua scripting in DCS by writing this guide. :)

 

Without further ado, let's get started! :)

 

 

What is Lua?

 

Quoted from the Lua homepage, "Lua is a powerful, fast, lightweight, embeddable scripting language."

 

 

DCS and Lua

 

DCS makes heavy use of Lua. The Simulator Scripting Engine "provides mission designers with access to game world and game objects using scripts. It's possible to change object properties, control object behavior and read object properties."

 

 

Getting started with Lua

 

If you're new to Lua, lua-users.org/wiki/LuaDirectory is an incredibly helpful resource to get an overview. If you have prior programming experience in other languages, you can get a decent overview in just 30 minutes. But even if you have no prior experience with programming languages, Lua is a very simple language to learn and therefore a very good language to start with.

 

You should have an environment where you can run Lua scripts, test ideas and check if the code you write is even formally valid (or, as we programmers like to call it, "syntactically correct").

 

The easiest way to do this is to use the live demo on the official Lua site that runs in every browser: www.lua.org/cgi-bin/demo.

 

But feel free to download and install the Lua interpreter on your local computer so that you become independent of the Lua web site.

 

Okay, now run this very simple Lua script in your local Lua interpreter or in the live demo:

 

sim = "DCS"
version = "1.2.7"

-- Use two dashes to start a comment. This line will be ignored by the interpreter
opinion = "rocks" -- Oh, and comments can also follow after some code

print(sim .. " " .. version .. " " .. opinion)

 

This is typically called the "Hello World" example, I just chose some other words. Congratulations, you've just run your first Lua script! :)

 

You will also find that a decent editor is a very helpful tool for any kind of programming. If you don't have a favorite editor yet, now would be a good time to install Notepad++. It's free and it's a very good editor to begin with. And if you ever intend to edit a DCS config file, Notepad++ will also come in very handy.

 

Hey, by the way, most DCS config files are written in Lua as well, so if you want to see live examples of Lua in action, you can go and check some of them out now. :)

 

 

Adding Lua into a mission

 

Now that you have a basic understanding of Lua, it's time to add Lua to a mission.

 

I'll be using a very simple mission with a single player controlled Su-25T. The focus will be on how to add scripts to a mission, not how to build a useful or good mission. That's up to you. :)

 

Now let's fire up the ME. You should place a player controlled unit to get the unit's cockpit view.

 

This is what we'll do:

 

01_hello_world.thumb.jpg.e4506539640d0e2f5be731423c435223.jpg

 

The same in writing, follow these steps:

  • Click the "Set rules for trigger" button on the left side
  • On the left column (the "Type" Column), click "New". Set Type to "ONCE" and Event to "NO EVENT". It's also always good practice to name this trigger.
  • On the center column (the "Conditions" column), click "New". Set Type to "TIME MORE" and Seconds to 5.
  • On the right column (the "Actions" column), click "New". Set Action to "DO SCRIPT". In the Text box, enter the code sample from above.

 

Now save the mission and fly it.

 

02_hello_world_missing.thumb.jpg.1c11430c00bca9ef6765568a5a455c69.jpg

 

As you can see, the mission goes right through the 5 second mark and nothing special happens.

 

 

Getting Lua to log stuff into dcs.log

 

Lua's print() function doesn't have any meaning in DCS, so let's try something else. Let's make use of the Simulator Scripting Engine environment now. We'll simply change print() to env.info().

 

03_hello_world_again.thumb.jpg.a11799f7c80b6f7974b20f284682f4a0.jpg

 

If you run the mission again, it seems as if nothing happens. You just have to know where to look. :)

 

Calls to env.info() get logged to dcs.log in the Saved Games directory. In my case, the path is

 

C:\Users\Yurgon\Saved Games\DCS\Logs\dcs.log

 

and it looks like this:

 

04_log.thumb.png.2684240c087e593f47d6e2ca22ddf2c7.png

 

Note the highlighted line that contains our "Hello World" string. When your scripts get more complicated over time, this will be where you log all kinds of stuff. Before we go on, let's delve into this some more.

 

env.info() takes an optional second parameter called showMessageBox. This is how it is set:

 

05_info_w_messageBox.thumb.jpg.61209c7ea885938b4267505a38dbf1be.jpg

 

If we run the mission again, this is what happens when it runs for 5 seconds:

 

06_messageBox.png.7aed471f8ab0484a54c461ac8b2628b1.png

 

In addition to writing the output to dcs.log, the game freezes and Windows displays this messageBox window. Unfortunately (at least in my case), I can't even see the messageBox until I Alt+Tab out of DCS. As a rule of thumb... do not set the showMessageBox parameter to true, you won't make many friends if you incorporate this into your missions. :music_whistling:

 

One more thing on the topic of environment functions: Besides env.info() there are also env.warning() and env.error(), and this is how they work:

 

07_env_info_warning_error.thumb.jpg.4c17dc09c60a83693de482bac25f8b32.jpg

 

And this is their output to dcs.log:

 

08_log_info_warning_error.thumb.png.eb1f6a5e08a66736eb21a2c32860b2df.png

 

As you can see, the only difference is in the second column of dcs.log. If you have large scripts with lots of debugging, it may come in handy to use these different logging functions.

 

 

Doing something useful for a change

 

Okay, let's use a real function now: trigger.action.outText(string text, Time delay)

 

We'll go back to our "Hello World" text for this example.

 

09_trigger_action_outText.thumb.jpg.95b92e34650ce1de08018638b59449c0.jpg

 

Now imagine a drum roll... and...

 

10_outText_displayed.thumb.jpg.37ac365dc93fd46ac4925c054bbc13d4.jpg

 

Cool, isn't it? You've just displayed your first in-game message by using a Lua script, congratulations. :beer:

 

 

Understanding variable scope

 

If you've looked at the Lua tutorial, you might have heard the term "Variable scope". This topic is a tiny bit advanced and you'll probably not run into it right away. On the other hand, if you want your scripts to perform well, you'll want to limit variable scope whenever possible so that "old" variables can be wiped from memory as soon as they're not needed anymore.

 

It's not obvious at first glance how DCS handles this variable scope, that's why I think it's important to bring this topic up so soon.

 

By default, any variable is global. If you want a variable to be a local variable, it has to be prefixed with the keyword local.

 

Inside of one script container, we can mix global and local variables like so:

 

11_scope.thumb.jpg.f1580ce0f32d4c5cba317c67d12a216e.jpg

12_scope_okay.thumb.jpg.f731f52bf0669b27e12039f4416f2ba8.jpg

 

That was easy enough. For the next example, we will add two triggers. The first of these triggers fires after 10 seconds and displays the global variable.

 

13_scope.thumb.jpg.67ca9285ec9cb9f12c00cdaa51cfa06a.jpg

 

The second of these triggers fires after 15 seconds and is supposed to display the local variable.

 

14_scope.thumb.jpg.f1b29d4908da94d32239a0e56f147d69.jpg

 

And this is what it looks like: The global variable gets displayed okay.

 

15_scope_global_okay.thumb.jpg.aadf792ed0b73ce9f4617196ccc7487c.jpg

 

But trying to display the local variable makes DCS freeze and shows a message box:

 

16_scope_local_not_okay.png.4143e991f1ac95d37b27e1010d039b46.png

 

So keep in mind that the scope of local variables in DCS is limited to one DO SCRIPT container. They'll simply cease to exist once that code has been executed, and you cannot use them in another DO SCRIPT container.

 

If you omit the local keyword, variables are available across DO SCRIPT containers and can be accessed at a later time with no problems.

 

 

Preventing DCS from freezing when a Lua error occurs

 

We've just introduced our first script error into a mission. Such a thing can obviously happen very quickly. While you develop a mission, that's a good thing because you'll know right away that something is wrong.

 

Once you release a mission to the public, you may want to prevent this from happening. Players are not accustomed to their game just locking up for no apparent reason and then read something about nil values or some such.

 

Here's the good news: It's really easy to prevent DCS from displaying a message box when it encounters an error in a Lua script: Simply set env.setErrorMessageBoxEnabled(boolean on) to false:

 

env.setErrorMessageBoxEnabled(false)

 

17_setErrorMessageBoxEnabled_false.thumb.jpg.fce898d06ded4730c8b6e8a43b6060e3.jpg

 

Let's test it. I just run the same mission as before.

 

18_messageBox_suppressed.thumb.jpg.cea183be9ff8305990ef1498356a24c5.jpg

 

In the example screenshot, I placed it in my first trigger, which is good enough for now. I'll leave it to you to place it in a trigger at MISSION START and possibly set a variable for the parameter so that you can easily toggle between debugging and live environments.

 

One question remains: Will all future scripts be broken because of such errors? Well, let's just test it. I keep the previous mission, and after 20 seconds fire another trigger that, once again, displays my global variable msg1.

 

19_action_after_error.thumb.jpg.a2b6f2ca87ecf576db7bbd845ce4f036.jpg

 

As you can see, the trigger gets executed just fine:

 

20_okay_after_error.thumb.jpg.54458b8ef2dbf4d294c5b56a2b6695a5.jpg

 

That means that a Lua script error will halt execution inside of one script container, but it will not prevent other script containers from getting executed.

 

Of course that doesn't mean that your scripts won't suffer from such a problem if different containers rely on each other. :smartass:

 

 

Conclusion

 

What we've discussed so far was an introduction into Lua scripting with the Simulator Scripting Engine and how to inject very simple and basic scripts into a mission using the DO SCRIPT trigger action.

 

There are other ways to include scripts that I may (or may not) cover in a second part of this tutorial. In any case I hope this tutorial served as a primer so that you can get into DCS Lua scripting easier than I did.

 

 

Further reading

 

There are lots of extremely useful resources that go into much more detail. In no particular order (and repeating some that have already been mentioned before), some of them are:

 


Edited by Yurgon
Added new link LDT to link list
  • Like 9
  • Thanks 9
Link to comment
Share on other sites

Very nice start indeed!

 

Here are some additions that might come in handy :)

 

It is possible to write object-oriented code in Lua-script. In fact, if you're scripting for DCS, you're already using objects.

 

Let's go through some basics.

 

To define a 'class', or object template, you can do this.

 

First, we need to create a table (all objects in lua are tables), with the properties of our class and their default values.

 

MyClass = {
Property1 = "Hello World",	-- a string
Property2 = 15,			-- a number
Property3 = {}			-- a table
}

 

Now we need to define a constructor, so we can use this as a proper class:

 

function MyClass:New(p1, p2, p3)
local obj = {
	Property1 = p1,
	Property2 = p2,
	Property3 = p3
}
setmetatable(obj, { __index = MyClass })
return obj
end

 

To use this class, we can define some variables, and assign new instances of our class to them:

 

MyObject1 = MyClass:New("First object", 1, { "one", "two", "three" })
MyObject2 = MyClass:New("Second object", 2, { 4, 5, 6 })

env.info(MyObject1.Property1) -- Writes "First object" to dcs.log
env.info(MyObject2.Property1) -- Writes "Second object" to dcs.log

 

The real use of these objects starts when we add some functions to them.

Let's add a function to increment Property2 by a given number.

 

function MyClass:IncrementP2(value)
self.Property2 = self.Property2 + value
end

 

Notice the use of the keyword self here.

It is a hidden parameter that automatically gets passed a reference to the object if you call the function using a colon:

 

MyObject1:IncrementP2(5)

 

This is the same as:

 

MyObject1.IncrementP2(MyObject1, 5)

 

Consequently, our IncrementP2 function can also be written like this:

 

function MyClass.IncrementP2(self, value)
self.Property2 = self.Property2 + value
end

 

This is a major pitfall in developing lua scripts, as calling MyObject1.IncrementP2(5) or MyObject1:IncrementP2(MyObject1, 5)won't throw an error, it simply does nothing (at best) or something unexpected.

If you're using objects from DCS and your code doesn't work, check this first. It will save you a lot of debugging time!

  • Like 4
Dutch Flanker Display Team | LLTM 2010 Tiger Spirit Award
Link to comment
Share on other sites

  • 2 weeks later...
Position_vec3 = YourObject:getPoint()

elevation = land.getHeight{x = Position_vec3 .x, y = Position_vec3 .z}

now you can subtract the elevation from your position z value.

Great, thanks! I found it on the same page as we speak. That will help me along :thumbup:

Link to comment
Share on other sites

  • 4 weeks later...

Very useful Yurgon, thanks - I've been wanting to take the first few steps down this (initially at least), quite daunting road.

 

I couldn't find how / where to start with this stuff, so nice one :thumbup:

 

More reading required by me, but good to have made a start. TY

 

EDIT - wont let me add rep - sorry.


Edited by VIMANAMAN
  • Like 1
Link to comment
Share on other sites

Very useful Yurgon, thanks - I've been wanting to take the first few steps down this (initially at least), quite daunting road.

 

I couldn't find how / where to start with this stuff, so nice one :thumbup:

 

More reading required by me, but good to have made a start. TY

 

EDIT - wont let me add rep - sorry.

 

Thanks. :)

 

I'm not entirely sure about the rep system, but I think posts can only be rep'ed for a short period of time, or the same post can't be rep'ed too often or so. If you find newer posts by the same member, it might be possible to rep them instead (like this one, hint, hint :music_whistling: :D)

 

On to another post:

 

Position_vec3 = YourObject:getPoint()

elevation = land.getHeight{x = Position_vec3 .x, y = Position_vec3 .z}

now you can subtract the elevation from your position z value.

 

Huh, when you posted that, it looked like a lot of magic to me. In the meantime, I've done some scripting in this area, and that land.getHeight() looks like an awesome little helper function. :thumbup:

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...

Is everyone using the Notepad++ to code? I use the Programmers Notepad (almost the same as Notepad++) but there is no way to run and debug small pieces of the code in any of these notepads. Is there a more powerful coding compiler for lua?

Link to comment
Share on other sites

I believe you need a LUA interpreter, not a compiler.

 

http://www.lua.org/download.html

ASUS ROG Maximus VIII Hero, i7-6700K, Noctua NH-D14 Cooler, Crucial 32GB DDR4 2133, Samsung 950 Pro NVMe 256GB, Samsung EVO 250GB & 500GB SSD, 2TB Caviar Black, Zotac GTX 1080 AMP! Extreme 8GB, Corsair HX1000i, Phillips BDM4065UC 40" 4k monitor, VX2258 TouchScreen, TIR 5 w/ProClip, TM Warthog, VKB Gladiator Pro, Saitek X56, et. al., MFG Crosswind Pedals #1199, VolairSim Pit, Rift CV1 :thumbup:

Link to comment
Share on other sites

There is always an online version.

 

http://www.compileonline.com/execute_lua_online.php

ASUS ROG Maximus VIII Hero, i7-6700K, Noctua NH-D14 Cooler, Crucial 32GB DDR4 2133, Samsung 950 Pro NVMe 256GB, Samsung EVO 250GB & 500GB SSD, 2TB Caviar Black, Zotac GTX 1080 AMP! Extreme 8GB, Corsair HX1000i, Phillips BDM4065UC 40" 4k monitor, VX2258 TouchScreen, TIR 5 w/ProClip, TM Warthog, VKB Gladiator Pro, Saitek X56, et. al., MFG Crosswind Pedals #1199, VolairSim Pit, Rift CV1 :thumbup:

Link to comment
Share on other sites

  • 1 month later...

Short version: how to you make a function name dynamic?

 

Long version:

 

I need to create an event handler within a function, but may have several of these event handlers going concurrently for different "CAS taskings". In all of the examples I've seen event handlers are global. Thus, is there a way with lua to create a dynamic variable name for the event handler?

 

For example, the event handler checks for blue-on-blue shots and is called BlueDamageHandler. This is called within function "CAS_1" which has a variable "Task" (function CAS_1(Task). So, task #1 is running and calls BlueDamageHandler to start checking for blue-on-blue shots.

 

Meanwhile, CAS task #2 is activated using the same overall function, and also needs to call on this BlueDamageHandler but with different units to check on, etc. So how do I make the damage handler specific to each task? What I want to do is have the damage handler be called "BlueDamageHandler_01" or something for the first task, "BlueDamageHandler02" for the second, and so on.

Link to comment
Share on other sites

Short version: how to you make a function name dynamic?

 

Long version:

 

I need to create an event handler within a function, but may have several of these event handlers going concurrently for different "CAS taskings". In all of the examples I've seen event handlers are global. Thus, is there a way with lua to create a dynamic variable name for the event handler?

 

For example, the event handler checks for blue-on-blue shots and is called BlueDamageHandler. This is called within function "CAS_1" which has a variable "Task" (function CAS_1(Task). So, task #1 is running and calls BlueDamageHandler to start checking for blue-on-blue shots.

 

Meanwhile, CAS task #2 is activated using the same overall function, and also needs to call on this BlueDamageHandler but with different units to check on, etc. So how do I make the damage handler specific to each task? What I want to do is have the damage handler be called "BlueDamageHandler_01" or something for the first task, "BlueDamageHandler02" for the second, and so on.

 

 

Hi Joyride,

 

If I understand the idea you want to script. I think you have to keep your eventhandler global, since it wil fire on that level in in the ED-world. And you have to create the dynamic requirement to make your script behave the way you like within the eventhandler. you could make a filter within your eventhandler that distiquishes for instance "if unitSet_1 then" and "if unitSet_2 then" or "if with_as_much_flavours_as_you_like then". To make it readable you could even send each if, to there own function outside the eventhandler.

 

 

 

 

 

.


Edited by piXel496
just if
Link to comment
Share on other sites

piXel496:

 

There will be too many variables to accommodate in a single event handler, I could potentially have many running at the same time checking blue-on-blue (example) for numerous group/unit combinations. I just want to be able to create a unique event handler for each of the those to run globally, but concurrently. All is generated by a single script though, thus the need to pass a variable into the name of the event handler function itself.

 

Getting back to LUA, what I want to do is similar to how you’d pass a variable into a text string.

Variable = blah

trigger.action.outText(‘message is: ‘ .. variable .., 10)

 

So that produces text message = “message is: blah”

 

How can I similarly pass a variable into a function or table name?

For example, the basic start of the event handler function creates the table:

 

BlueDamageHandler = {}

 

How do I make that dynamic? Using the LUA text string as a (bad) example…is something like this possible?

 

Variable = Task01

BlueDamageHandler .. Task01 .. = {}

 

So that when this event handler is created, it is create as “BlueDamageHandlerTask01”?

Link to comment
Share on other sites

Wouldn't all the functions used for the various event handlers have to have already been defined before you assign them as event handlers?

 

That is:

 

function BlueHandler1(eventdata)

...

end

 

function BlueHandler2(eventdata)

...

end

 

etc.

 

must be defined before

 

world.addEventHandler(BlueHandler1)

 

world.addEventHandler(BlueHandler2)

 

etc.

 

It seems to me that you need a single event handler that incorporates a "dispatcher" of sorts that initiates a coroutine or schedules another function to actually do the "tasks".

 

Now, my knowledge of coroutine usage is next to nothing, but from what I've read it might be more suited for what your trying to do.

Link to comment
Share on other sites

Also,

 

I could potentially have many running at the same time checking blue-on-blue (example) for numerous group/unit combinations.
I don't think event handlers work that way. They are event driven not polling functions.
Link to comment
Share on other sites

Also,

 

I don't think event handlers work that way. They are event driven not polling functions.

 

The example would be where one event handler is checking to see if Group A is fired upon by client A-10s and another comes on line later to check if Group B is fired upon by client A-10s. The problem is that one event handler can't handle both, because when the first one is created the second group "Group B" is not known yet. Plus, all of the other variables used are specific and within a unique function created for that local CAS scenario.

 

So, without posting hundreds of line of code to explain that further, the question still stands if functions can have variables within their name to make the function name dynamic. Seeing stuff on LUA sites related to "loadstring" and use if "_G". Sounds like what I want to do it possible, just don't understand the syntax.

 

Here is an example that gets very close to what I am doing, but I don't understand the answers provided in this thread :D:

 

http://stackoverflow.com/questions/1791234/lua-call-function-from-a-string-with-function-name

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...