Yurgon Posted February 7, 2014 Posted February 7, 2014 (edited) 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: 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. 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(). 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: 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: If we run the mission again, this is what happens when it runs for 5 seconds: 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: And this is their output to dcs.log: 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. Now imagine a drum roll... and... 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: 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. The second of these triggers fires after 15 seconds and is supposed to display the local variable. And this is what it looks like: The global variable gets displayed okay. But trying to display the local variable makes DCS freeze and shows a message box: 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) Let's test it. I just run the same mission as before. 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. As you can see, the trigger gets executed just fine: 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: lua-users wiki DCS Mission Editor (Hoggit Wiki) Simulator Scripting Engine Documentation (Hoggit Wiki) New Scripting Documentation (Hoggit Wiki) Mission Editor Scripts library (ED Forum) MIssion Scripting Tools (Mist)- enhancing mission scripting Lua Mission Editor - Hot Tips (ED Forum) LUA scripts: workflow ... how to? (ED Forum) DCS Mission Editor (ME) (Eagle Wiki) Lua Development Tools (LDT) Edited December 21, 2016 by Yurgon Added new link LDT to link list 9 9
tintifaxl Posted February 7, 2014 Posted February 7, 2014 Very nice and comprehensive tutorial. 1 Windows 10 64bit, Intel i9-9900@5Ghz, 32 Gig RAM, MSI RTX 3080 TI, 2 TB SSD, 43" 2160p@1440p monitor.
Joe Kurr Posted February 7, 2014 Posted February 7, 2014 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! 4 Dutch Flanker Display Team | LLTM 2010 Tiger Spirit Award
Joe Kurr Posted February 7, 2014 Posted February 7, 2014 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
Yurgon Posted February 7, 2014 Author Posted February 7, 2014 Here are some additions that might come in handy :) Wow, that's really cool, thanks! :thumbup:
Silver_Dragon Posted February 7, 2014 Posted February 7, 2014 great post For Work/Gaming: 28" Philips 246E Monitor - Ryzen 7 1800X - 32 GB DDR4 - nVidia RTX1080 - SSD 860 EVO 1 TB / 860 QVO 1 TB / 860 QVO 2 TB - Win10 Pro - TM HOTAS Warthog / TPR / MDF
Joe Kurr Posted February 18, 2014 Posted February 18, 2014 If you want to reference scripts in your C:\Users\username\Saved Games\DCS\Scripts folder, use lfs.writedir: dofile(lfs.writedir().."/Scripts/yourscript.lua"); Took me quite some time to find it, so I post it here for easy reference :) 1 Dutch Flanker Display Team | LLTM 2010 Tiger Spirit Award
HiJack Posted February 25, 2014 Posted February 25, 2014 I got the altitude above sea level worked out but how can I get a units current above ground level?
towsim Posted February 25, 2014 Posted February 25, 2014 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][/sIGPIC]
HiJack Posted February 25, 2014 Posted February 25, 2014 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:
HiJack Posted February 25, 2014 Posted February 25, 2014 With the AWACS I get a hight difference of 3 meters (12 feet). Does the Unit:getPosition().p return the top of the fuselage?
VIMANAMAN Posted March 22, 2014 Posted March 22, 2014 (edited) 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 March 22, 2014 by VIMANAMAN 1
Yurgon Posted March 22, 2014 Author Posted March 22, 2014 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: 1
HiJack Posted April 8, 2014 Posted April 8, 2014 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?
cichlidfan Posted April 8, 2014 Posted April 8, 2014 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:
HiJack Posted April 8, 2014 Posted April 8, 2014 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 ;)
cichlidfan Posted April 8, 2014 Posted April 8, 2014 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:
Joyride Posted May 29, 2014 Posted May 29, 2014 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.
piXel496 Posted May 29, 2014 Posted May 29, 2014 (edited) 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 May 29, 2014 by piXel496 just if old stuff I made
Joyride Posted May 29, 2014 Posted May 29, 2014 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”?
ajax Posted May 30, 2014 Posted May 30, 2014 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.
ajax Posted May 30, 2014 Posted May 30, 2014 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.
Joyride Posted May 30, 2014 Posted May 30, 2014 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
Recommended Posts