Announcement

Collapse
No announcement yet.

Tutorial: Introduction to Lua scripting

Collapse
This is a sticky topic.
X
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Tutorial: Introduction to Lua scripting

    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.

    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:

    Code:
    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:

    Click image for larger version

Name:	01_hello_world.jpg
Views:	1
Size:	192.2 KB
ID:	6693279

    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.

    Click image for larger version

Name:	02_hello_world_missing.jpg
Views:	1
Size:	175.2 KB
ID:	6693280

    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().

    Click image for larger version

Name:	03_hello_world_again.jpg
Views:	1
Size:	174.3 KB
ID:	6693281

    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

    Code:
    C:\Users\Yurgon\Saved Games\DCS\Logs\dcs.log
    and it looks like this:

    Click image for larger version

Name:	04_log.png
Views:	1
Size:	138.9 KB
ID:	6693282

    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:

    Click image for larger version

Name:	05_info_w_messageBox.jpg
Views:	1
Size:	173.8 KB
ID:	6693283

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

    Click image for larger version

Name:	06_messageBox.png
Views:	1
Size:	8.2 KB
ID:	6693284

    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.

    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:

    Click image for larger version

Name:	07_env_info_warning_error.jpg
Views:	1
Size:	174.6 KB
ID:	6693285

    And this is their output to dcs.log:

    Click image for larger version

Name:	08_log_info_warning_error.png
Views:	1
Size:	141.8 KB
ID:	6693286

    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.

    Click image for larger version

Name:	09_trigger_action_outText.jpg
Views:	1
Size:	173.0 KB
ID:	6693287

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

    Click image for larger version

Name:	10_outText_displayed.jpg
Views:	1
Size:	213.8 KB
ID:	6693288

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


    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:

    Click image for larger version

Name:	11_scope.jpg
Views:	1
Size:	180.3 KB
ID:	6693289
    Click image for larger version

Name:	12_scope_okay.jpg
Views:	1
Size:	215.0 KB
ID:	6693290

    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.

    Click image for larger version

Name:	13_scope.jpg
Views:	1
Size:	178.6 KB
ID:	6693291

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

    Click image for larger version

Name:	14_scope.jpg
Views:	1
Size:	178.0 KB
ID:	6693292

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

    Click image for larger version

Name:	15_scope_global_okay.jpg
Views:	1
Size:	216.1 KB
ID:	6693293

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

    Click image for larger version

Name:	16_scope_local_not_okay.png
Views:	1
Size:	16.4 KB
ID:	6693294

    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:

    Code:
    env.setErrorMessageBoxEnabled(false)
    Click image for larger version

Name:	17_setErrorMessageBoxEnabled_false.jpg
Views:	1
Size:	177.6 KB
ID:	6693295

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

    Click image for larger version

Name:	18_messageBox_suppressed.jpg
Views:	1
Size:	220.4 KB
ID:	6693296

    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.

    Click image for larger version

Name:	19_action_after_error.jpg
Views:	1
Size:	183.1 KB
ID:	6693297

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

    Click image for larger version

Name:	20_okay_after_error.jpg
Views:	1
Size:	211.0 KB
ID:	6693298

    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.


    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:
    Last edited 12-21-2016, 03:25 AM. Reason: Added new link LDT to link list

    #2
    (Reserved)

    Comment


      #3
      Very nice and comprehensive tutorial.
      Windows 10 64bit, Intel i9-9900@5Ghz, 32 Gig RAM, Palit RTX 2080 TI, 1 TB SSD, 43" 2160p@1440p monitor.

      Comment


        #4
        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.

        Code:
        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:

        Code:
        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:

        Code:
        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.

        Code:
        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:

        Code:
        MyObject1:IncrementP2(5)
        This is the same as:

        Code:
        MyObject1.IncrementP2(MyObject1, 5)
        Consequently, our IncrementP2 function can also be written like this:

        Code:
        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!
        Dutch Flanker Display Team | LLTM 2010 Tiger Spirit Award

        Comment


          #5
          Small addition to the above, here is some documentation I found while writing my cross-country airrace script:

          Pragmatic LUA basics in 30 minutes
          Pragmatic Lua - Error Handling, OOP, Closure and Coroutine
          Dutch Flanker Display Team | LLTM 2010 Tiger Spirit Award

          Comment


            #6
            Originally posted by Joe Kurr View Post
            Here are some additions that might come in handy
            Wow, that's really cool, thanks!

            Comment


              #7
              great post
              More news to the front
              Wishlist: ED / 3rd Party Campaings
              My Rig: Intel I-5 750 2.67Ghz / Packard Bell FMP55 / 16 GB DDR3 RAM / GTX-1080 8 GB RAM / HD 1Tb/2Tb / Warthog / 2 MDF / TFPR

              DCS: Roadmap (unofficial):https://forums.eagle.ru/showthread.php?t=116893
              DCS: List of Vacant models: https://forums.eagle.ru/showthread.p...91#post4076891
              21Squad DCS: World News: https://www.facebook.com/21Squad-219508958071000/
              Silver_Dragon Youtube

              Comment


                #8
                If you want to reference scripts in your C:\Users\username\Saved Games\DCS\Scripts folder, use lfs.writedir:

                Code:
                dofile(lfs.writedir().."/Scripts/yourscript.lua");
                Took me quite some time to find it, so I post it here for easy reference
                Dutch Flanker Display Team | LLTM 2010 Tiger Spirit Award

                Comment


                  #9
                  I got the altitude above sea level worked out but how can I get a units current above ground level?


                  Windows 7 64 bit FTW!
                  My DCS Tools:
                  Quick Airfield information , Better trim with the X52 Pro , Tacview Glideslopes

                  Spoiler:

                  ASUS Z97-PRO, Socket-1150
                  Intel® Core i7-4770K Processor overclocked to @ 4.20 GHz
                  32GB Corsair Dominator Platinum DDR3 2133MHz CL9 4x8GB
                  Sapphire Radeon TRI-X R9 390X 8GB "OC"
                  Samsung SSD 850PRO 1TB SSD
                  WD 2TB Black 3,5", SATA64MB Cache, Dual Processor, 7200RPM
                  Windows 7 64-bit
                  Philips 40" 4K LED BDM4065UC, ASUS 27” and Dell 24” screens

                  Comment


                    #10
                    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.
                    sigpic

                    Comment


                      #11
                      Originally posted by towsim View 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.
                      Great, thanks! I found it on the same page as we speak. That will help me along
                      Windows 7 64 bit FTW!
                      My DCS Tools:
                      Quick Airfield information , Better trim with the X52 Pro , Tacview Glideslopes

                      Spoiler:

                      ASUS Z97-PRO, Socket-1150
                      Intel® Core i7-4770K Processor overclocked to @ 4.20 GHz
                      32GB Corsair Dominator Platinum DDR3 2133MHz CL9 4x8GB
                      Sapphire Radeon TRI-X R9 390X 8GB "OC"
                      Samsung SSD 850PRO 1TB SSD
                      WD 2TB Black 3,5", SATA64MB Cache, Dual Processor, 7200RPM
                      Windows 7 64-bit
                      Philips 40" 4K LED BDM4065UC, ASUS 27” and Dell 24” screens

                      Comment


                        #12
                        With the AWACS I get a hight difference of 3 meters (12 feet). Does the Unit:getPosition().p return the top of the fuselage?
                        Windows 7 64 bit FTW!
                        My DCS Tools:
                        Quick Airfield information , Better trim with the X52 Pro , Tacview Glideslopes

                        Spoiler:

                        ASUS Z97-PRO, Socket-1150
                        Intel® Core i7-4770K Processor overclocked to @ 4.20 GHz
                        32GB Corsair Dominator Platinum DDR3 2133MHz CL9 4x8GB
                        Sapphire Radeon TRI-X R9 390X 8GB "OC"
                        Samsung SSD 850PRO 1TB SSD
                        WD 2TB Black 3,5", SATA64MB Cache, Dual Processor, 7200RPM
                        Windows 7 64-bit
                        Philips 40" 4K LED BDM4065UC, ASUS 27” and Dell 24” screens

                        Comment


                          #13
                          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

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

                          EDIT - wont let me add rep - sorry.
                          Last edited 03-22-2014, 04:22 PM.

                          Comment


                            #14
                            Originally posted by VIMANAMAN View Post
                            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

                            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 )

                            On to another post:

                            Originally posted by towsim View 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.

                            Comment


                              #15
                              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?
                              Windows 7 64 bit FTW!
                              My DCS Tools:
                              Quick Airfield information , Better trim with the X52 Pro , Tacview Glideslopes

                              Spoiler:

                              ASUS Z97-PRO, Socket-1150
                              Intel® Core i7-4770K Processor overclocked to @ 4.20 GHz
                              32GB Corsair Dominator Platinum DDR3 2133MHz CL9 4x8GB
                              Sapphire Radeon TRI-X R9 390X 8GB "OC"
                              Samsung SSD 850PRO 1TB SSD
                              WD 2TB Black 3,5", SATA64MB Cache, Dual Processor, 7200RPM
                              Windows 7 64-bit
                              Philips 40" 4K LED BDM4065UC, ASUS 27” and Dell 24” screens

                              Comment


                                #16
                                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

                                Comment


                                  #17
                                  Yes thats what I meant. I did find some for android but wanted to hear if there is a recommended one for windows. Didn't like the android ones
                                  Windows 7 64 bit FTW!
                                  My DCS Tools:
                                  Quick Airfield information , Better trim with the X52 Pro , Tacview Glideslopes

                                  Spoiler:

                                  ASUS Z97-PRO, Socket-1150
                                  Intel® Core i7-4770K Processor overclocked to @ 4.20 GHz
                                  32GB Corsair Dominator Platinum DDR3 2133MHz CL9 4x8GB
                                  Sapphire Radeon TRI-X R9 390X 8GB "OC"
                                  Samsung SSD 850PRO 1TB SSD
                                  WD 2TB Black 3,5", SATA64MB Cache, Dual Processor, 7200RPM
                                  Windows 7 64-bit
                                  Philips 40" 4K LED BDM4065UC, ASUS 27” and Dell 24” screens

                                  Comment


                                    #18
                                    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

                                    Comment


                                      #19
                                      Thanks. Ill try it out on some snippets.
                                      Windows 7 64 bit FTW!
                                      My DCS Tools:
                                      Quick Airfield information , Better trim with the X52 Pro , Tacview Glideslopes

                                      Spoiler:

                                      ASUS Z97-PRO, Socket-1150
                                      Intel® Core i7-4770K Processor overclocked to @ 4.20 GHz
                                      32GB Corsair Dominator Platinum DDR3 2133MHz CL9 4x8GB
                                      Sapphire Radeon TRI-X R9 390X 8GB "OC"
                                      Samsung SSD 850PRO 1TB SSD
                                      WD 2TB Black 3,5", SATA64MB Cache, Dual Processor, 7200RPM
                                      Windows 7 64-bit
                                      Philips 40" 4K LED BDM4065UC, ASUS 27” and Dell 24” screens

                                      Comment


                                        #20
                                        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.

                                        Comment

                                        Working...
                                        X