Difference between revisions of "CommandLua"
From Baloogan Campaign Wiki
(2 intermediate revisions by one other user not shown) | |||
Line 7: | Line 7: | ||
==Ckfinite's Tutorials== | ==Ckfinite's Tutorials== | ||
− | * [ | + | * [http://commandlua.github.io/tutorials/01-getting-started.md.html Tutorial 1] |
− | * [ | + | * [http://commandlua.github.io/tutorials/02-creation.md.html Tutorial 2] |
− | * [ | + | * [http://commandlua.github.io/tutorials/03-events.md.html Tutorial 3] |
+ | |||
+ | |||
+ | ==Overview== | ||
+ | <nowiki> | ||
+ | Some guidelines for Command Lua scripting | ||
+ | |||
+ | Defensive programming | ||
+ | By this I mean, take some precautions with your scripting. Don't assume that everything will always work as expected. Program for the expected, but plan for the unexpected. This will make the player experience better as they wont be getting errors popping up because something unexpected occurred that could be controlled. | ||
+ | |||
+ | Using an external editor, like ZeroBrane, Notepad++ or Sublime, to build up a long or involved script is suggested. Then copy and paste into console to test, or to the event to complete. I use Notepadd++ with the Lua plugin. It highlights the Lua keywords. | ||
+ | |||
+ | |||
+ | Use of ScenEdit methods in Command | ||
+ | ---------------------------------- | ||
+ | 1. Names of units, groups, missions, etc are case sensitive and must match exactly. | ||
+ | Extra spaces at start or end of a name can cause a ScenEdit function to fail. | ||
+ | The unit names in triggers and events is sensitive to units being renamed. Using the unit GUIDs (most easily found by right click -> Scenario Editor -> Copy Unit ID to Clipboard) is suggested, as it is not effected by unit renaming. | ||
+ | |||
+ | 2. Before performing actions on a unit, check that the unit is still active in the scenario. | ||
+ | This will cover situations were the unit is killed or otherwise removed from play. | ||
+ | if ScenEdit_GetUnit({Side='Blue', Name="Traveller #1"}) ~= nil then -- unit exists | ||
+ | ScenEdit_AssignUnitToMission("Traveller #1", "Ferry mission") -- assign to mission | ||
+ | end | ||
+ | |||
+ | 3. Be careful of names which contain back slash (\), single (') or double (") quotes. | ||
+ | If a name contains double quote characters ("), use single quote (') to enclose the name. The opposite also applies. | ||
+ | In the string below, it contains both types of quotes and will fail with an error regardless of which quote is used. | ||
+ | Another way is to 'escape' the quotes by adding a back slash (\) in front of it so it is treated as normal character. | ||
+ | print("<>?:"{}|,./;'[]\") will fail ERROR: [string "chunk"]:4: ')' expected near '{' | ||
+ | This failed as the " before the { is treated as the end of the string. Add a \ before the quote (") so it is treated as a normal character .. | ||
+ | print("<>?:\"{}|,./;'[]\") now will fail due to a different problem ERROR: [string "chunk"]:4: unfinished string near <eof> | ||
+ | Now it does not see the end of string ". The \ character before the " is treating the " as a character and not the end of the string. To treat the \ as a normal character you need to add another \ before it. | ||
+ | print("<>?:\"{}|,./;'[]\\") now prints all the characters in the string | ||
+ | |||
+ | 4. Side is not controlled by the AI | ||
+ | When a side is not being controlled by the AI, and the option 'Scrub mission if side is human' is ticked, units should not be assigned to the pre-existing missions which may no longer exist. | ||
+ | if ScenEdit_GetUnit({Side='NATO', Name="F 361 HDMS Iver Huitfeldt"}) ~= nil then -- unit exists | ||
+ | if not ScenEdit_GetSideIsHuman('NATO') then -- side is not human | ||
+ | ScenEdit_AssignUnitToMission("F 361 HDMS Iver Huitfeldt", "NATO SeaCon") -- assign to mission | ||
+ | end | ||
+ | end | ||
+ | |||
+ | 5. Mission does not exist | ||
+ | Currently you can't verify the existence of missions so it is important to make sure that the names are correctly recorded. Otherwise, an error will be displayed and the script execution cancelled. | ||
+ | At least, you can check for the non-AI side(s) and not assign missions in that case. | ||
+ | |||
+ | 6. print() is your friend | ||
+ | Make use of print() statements to assist in tracing the process of the script. | ||
+ | Note that print() when using game objects (such as ScenEdit_GetUnit) can only have one parameter; however if you are only printing text or variables, you can combine them as in "a=10;print('variable a= ' .. a)" | ||
+ | The 'print()' statement does not interfere with the running of the script, but if the script console is open, it will show the print message there. In these cases it is advisable to include some sort of text print so that you know what is being printed. | ||
+ | print('Event 1: ');print(unit) | ||
+ | |||
+ | 7. Think about using functions for repeated code blocks | ||
+ | If a block of code is used often in the scripts of a scenario, it may be beneficial to make it a function. A function is passed parameters and returns a value. By making a global function, it is accessible within all the scenario scripts and the code will be in just one place to maintain - rather than fix a bug with the code in half a dozen places, just fix it in one place. | ||
+ | Simplified example, you have a test for a unit belong to a side, and this is repeated several times with just the side and name changing | ||
+ | if ScenEdit_GetUnit({Side='NATO', Name="Dragon #1"}) ~= nil then | ||
+ | if not ScenEdit_GetSideIsHuman('NATO') then | ||
+ | .. do stuff .. | ||
+ | end | ||
+ | end | ||
+ | Create a function that gets the side and name and returns true if the unit exists and the side is not controlled by the player. Include the function in a 'library' script that is loaded on scenario start. | ||
+ | function aiUnit(side_in, unit_in) | ||
+ | local result = false | ||
+ | if ScenEdit_GetUnit({side=side_in, name=unit_in}) ~= nil then -- exist | ||
+ | if not ScenEdit_GetSideIsHuman('NATO') then -- not player controlled | ||
+ | result = true | ||
+ | end | ||
+ | end | ||
+ | return result | ||
+ | Replace the repeating code in the other scripts with | ||
+ | if aiUnit('NATO', "Dragon #1") == true then | ||
+ | .. do stuff .. | ||
+ | end | ||
+ | Another benefit of this is that you can build up a library of common functions that you can use in other scenarios just by having it loaded at scenario start. This 'library' can be loaded from a 'Scenario Is Loaded'. | ||
+ | [An extension to this is that you can create a file with these library functions in it and use ScenEdit_RunScript(script_file) to load it rather than copy into the Scenario Started event. However, the limitation is that the 'script_file' needs to exist in a specific directory which will require the player to copy the file into it.] | ||
+ | |||
+ | 8. When naming an action or condition, it will help you if you add something to the name to indicate that it contains Lua code. If you include the something at the start of the name, then the sorted list will show all the LAU scripts together. As an extension, puttting some sort of prefix on the names would group similiar actions together. | ||
+ | For example, 'LUA - Change status of NATO' Adding 'LUA -' to the LUA scripted action/conditions would group them when sorted | ||
+ | |||
+ | 9. Success or failure conditions. | ||
+ | Most ScenEdit functions return some sort of value. It is a good idea to check the result to ensure that it worked | ||
+ | e.g | ||
+ | ScenEdit_GetUnit() returns a unit object - test if against 'nil' to see if it did | ||
+ | ScenEdit_GetSideIsHuman() returns true if the side is human or false if AI | ||
+ | When dealing with Event Conditions, you need to end the condition with a 'return true' or 'return false' | ||
+ | e.g | ||
+ | local answer = false -- condition fails | ||
+ | if ScenEdit_GetSideIsHuman('NATO') then -- side is human | ||
+ | answer = true -- condition succeeds | ||
+ | end | ||
+ | return answer -- return value of 'answer' | ||
+ | Note that when printing the value of a ScenEdit function that returns true/false, it will show as "Yes" for true, and "No" for false. You still need to test against true/false. | ||
+ | |||
+ | 10. Life of Scenario variables is limited | ||
+ | When a scenario starts, all Lua variables are cleared. If you need to maintain something across a scenario in a save, then you will need to use the ScenEdit_GetKeyValue/ScenEdit_SetKeyValue functions. These are stored in the save. | ||
+ | |||
+ | 11. Validate your Lua script | ||
+ | When building a script, copy the script into the Console and run it to 1) make sure it actually runs without error, and 2) that the unit references you are using are valid (eg that name of the unit is actually what you expect it to be). Often you can't exercise every action while playing a scenario especially if so actions are random or timed. | ||
+ | |||
+ | |||
+ | Lua General | ||
+ | ----------- | ||
+ | |||
+ | 1. Use meaningful names for variables. Add comments. | ||
+ | With meaningful names and comments, it is easier to understand what the script is doing. In conjunction with structuring the code, this is especially helpful when re-visiting the scripts later on. You can add comments in several ways. | ||
+ | a=10 -- this is a comment | ||
+ | -- this is a comment line | ||
+ | --[[ multiple line | ||
+ | comment ]] | ||
+ | |||
+ | 2. Use local variables as far as possible in scripts. | ||
+ | By default, any variable defined is globally accessible. This means that the variable can be used in any script within the current scenario. | ||
+ | A general rule should be to use local variables unless you really need to 'share' a value across multiple function/scripts. | ||
+ | This can lead to problems where a variable is used locally but is actually global. To demonstrate: | ||
+ | One script sets 'a = 10'. Another one sets 'a = 20'. And a third one checks the value 'if a == 10 then do_something end'. When the third script runs, the value of 'a' is 20 and the test fails. | ||
+ | Add the keyword 'local' in front of the variable, and it is only available within the current function/script. | ||
+ | One script sets 'a = 10'. Another one sets 'local a = 20'. And a third one checks the value 'if a == 10 then do_something end'. When the third one runs, the value of 'a' is 10 as the second script used a local variable which dies when the script ends. | ||
+ | |||
+ | 3. Structure the code so that it is readable, indenting lines that are part of blocks such as if, loop, function, etc. | ||
+ | for i=1,16 do | ||
+ | if ScenEdit_GetUnit({Side='Denmark', Name="Birdsong #"..i,}) ~= nil then | ||
+ | ScenEdit_SetUnitSide({Side='Denmark', Name="Birdsong #"..i, newside='NATO'}) | ||
+ | if not ScenEdit_GetSideIsHuman('NATO') then | ||
+ | ScenEdit_AssignUnitToMission("Birdsong #"..i, "NATO CAP") | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | |||
+ | 4. Multiple scans of the same list | ||
+ | Having to scan a list of objects takes time, so you should try to optimize them. If checks are performed in one scan, ensure that they aren't required in the other scans. | ||
+ | Take the following as an example. | ||
+ | The script needs to scan through 16 units, and perform some actions on them. However as it stands at the moment, the first scan changes the side of existing units. The second scan assigns all 16 units to a mission if the side is AI controlled. | ||
+ | for i=1,16 do | ||
+ | if ScenEdit_GetUnit({Side='Germany', Name="Steinhoff #"..i,}) ~= nil then | ||
+ | ScenEdit_SetUnitSide({Side='Germany', Name="Steinhoff #"..i, newside='NATO'}) | ||
+ | end | ||
+ | end | ||
+ | if not ScenEdit_GetSideIsHuman('NATO') then | ||
+ | for i=1,16 do ScenEdit_AssignUnitToMission("Steinhoff #"..i, "NATO CAP") end | ||
+ | end | ||
+ | The second scan does not check to see if the unit still exists before assigning it to a mission. | ||
+ | A better approach is to merge them together as in .. | ||
+ | for i=1,16 do -- scan thru numbers 1 to 16 | ||
+ | if ScenEdit_GetUnit({Side='Germany', Name="Steinhoff #"..i,}) ~= nil then -- unit # exists | ||
+ | ScenEdit_SetUnitSide({Side='Germany', Name="Steinhoff #"..i, newside='NATO'}) -- change side | ||
+ | if not ScenEdit_GetSideIsHuman('NATO') then -- not human side | ||
+ | ScenEdit_AssignUnitToMission("Steinhoff #"..i, "NATO CAP") -- assign mission | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | |||
+ | |||
+ | </nowiki> | ||
==Tomcat's Tutorials== | ==Tomcat's Tutorials== |
Latest revision as of 19:13, 9 September 2016
Welcome to the Command Lua Wiki page. Here a number of useful links and video tutorials have been collected to help explain how to use the Command Lua API in your Command scenarios.
Documentation
Ckfinite's Tutorials
Overview
Some guidelines for Command Lua scripting Defensive programming By this I mean, take some precautions with your scripting. Don't assume that everything will always work as expected. Program for the expected, but plan for the unexpected. This will make the player experience better as they wont be getting errors popping up because something unexpected occurred that could be controlled. Using an external editor, like ZeroBrane, Notepad++ or Sublime, to build up a long or involved script is suggested. Then copy and paste into console to test, or to the event to complete. I use Notepadd++ with the Lua plugin. It highlights the Lua keywords. Use of ScenEdit methods in Command ---------------------------------- 1. Names of units, groups, missions, etc are case sensitive and must match exactly. Extra spaces at start or end of a name can cause a ScenEdit function to fail. The unit names in triggers and events is sensitive to units being renamed. Using the unit GUIDs (most easily found by right click -> Scenario Editor -> Copy Unit ID to Clipboard) is suggested, as it is not effected by unit renaming. 2. Before performing actions on a unit, check that the unit is still active in the scenario. This will cover situations were the unit is killed or otherwise removed from play. if ScenEdit_GetUnit({Side='Blue', Name="Traveller #1"}) ~= nil then -- unit exists ScenEdit_AssignUnitToMission("Traveller #1", "Ferry mission") -- assign to mission end 3. Be careful of names which contain back slash (\), single (') or double (") quotes. If a name contains double quote characters ("), use single quote (') to enclose the name. The opposite also applies. In the string below, it contains both types of quotes and will fail with an error regardless of which quote is used. Another way is to 'escape' the quotes by adding a back slash (\) in front of it so it is treated as normal character. print("<>?:"{}|,./;'[]\") will fail ERROR: [string "chunk"]:4: ')' expected near '{' This failed as the " before the { is treated as the end of the string. Add a \ before the quote (") so it is treated as a normal character .. print("<>?:\"{}|,./;'[]\") now will fail due to a different problem ERROR: [string "chunk"]:4: unfinished string near <eof> Now it does not see the end of string ". The \ character before the " is treating the " as a character and not the end of the string. To treat the \ as a normal character you need to add another \ before it. print("<>?:\"{}|,./;'[]\\") now prints all the characters in the string 4. Side is not controlled by the AI When a side is not being controlled by the AI, and the option 'Scrub mission if side is human' is ticked, units should not be assigned to the pre-existing missions which may no longer exist. if ScenEdit_GetUnit({Side='NATO', Name="F 361 HDMS Iver Huitfeldt"}) ~= nil then -- unit exists if not ScenEdit_GetSideIsHuman('NATO') then -- side is not human ScenEdit_AssignUnitToMission("F 361 HDMS Iver Huitfeldt", "NATO SeaCon") -- assign to mission end end 5. Mission does not exist Currently you can't verify the existence of missions so it is important to make sure that the names are correctly recorded. Otherwise, an error will be displayed and the script execution cancelled. At least, you can check for the non-AI side(s) and not assign missions in that case. 6. print() is your friend Make use of print() statements to assist in tracing the process of the script. Note that print() when using game objects (such as ScenEdit_GetUnit) can only have one parameter; however if you are only printing text or variables, you can combine them as in "a=10;print('variable a= ' .. a)" The 'print()' statement does not interfere with the running of the script, but if the script console is open, it will show the print message there. In these cases it is advisable to include some sort of text print so that you know what is being printed. print('Event 1: ');print(unit) 7. Think about using functions for repeated code blocks If a block of code is used often in the scripts of a scenario, it may be beneficial to make it a function. A function is passed parameters and returns a value. By making a global function, it is accessible within all the scenario scripts and the code will be in just one place to maintain - rather than fix a bug with the code in half a dozen places, just fix it in one place. Simplified example, you have a test for a unit belong to a side, and this is repeated several times with just the side and name changing if ScenEdit_GetUnit({Side='NATO', Name="Dragon #1"}) ~= nil then if not ScenEdit_GetSideIsHuman('NATO') then .. do stuff .. end end Create a function that gets the side and name and returns true if the unit exists and the side is not controlled by the player. Include the function in a 'library' script that is loaded on scenario start. function aiUnit(side_in, unit_in) local result = false if ScenEdit_GetUnit({side=side_in, name=unit_in}) ~= nil then -- exist if not ScenEdit_GetSideIsHuman('NATO') then -- not player controlled result = true end end return result Replace the repeating code in the other scripts with if aiUnit('NATO', "Dragon #1") == true then .. do stuff .. end Another benefit of this is that you can build up a library of common functions that you can use in other scenarios just by having it loaded at scenario start. This 'library' can be loaded from a 'Scenario Is Loaded'. [An extension to this is that you can create a file with these library functions in it and use ScenEdit_RunScript(script_file) to load it rather than copy into the Scenario Started event. However, the limitation is that the 'script_file' needs to exist in a specific directory which will require the player to copy the file into it.] 8. When naming an action or condition, it will help you if you add something to the name to indicate that it contains Lua code. If you include the something at the start of the name, then the sorted list will show all the LAU scripts together. As an extension, puttting some sort of prefix on the names would group similiar actions together. For example, 'LUA - Change status of NATO' Adding 'LUA -' to the LUA scripted action/conditions would group them when sorted 9. Success or failure conditions. Most ScenEdit functions return some sort of value. It is a good idea to check the result to ensure that it worked e.g ScenEdit_GetUnit() returns a unit object - test if against 'nil' to see if it did ScenEdit_GetSideIsHuman() returns true if the side is human or false if AI When dealing with Event Conditions, you need to end the condition with a 'return true' or 'return false' e.g local answer = false -- condition fails if ScenEdit_GetSideIsHuman('NATO') then -- side is human answer = true -- condition succeeds end return answer -- return value of 'answer' Note that when printing the value of a ScenEdit function that returns true/false, it will show as "Yes" for true, and "No" for false. You still need to test against true/false. 10. Life of Scenario variables is limited When a scenario starts, all Lua variables are cleared. If you need to maintain something across a scenario in a save, then you will need to use the ScenEdit_GetKeyValue/ScenEdit_SetKeyValue functions. These are stored in the save. 11. Validate your Lua script When building a script, copy the script into the Console and run it to 1) make sure it actually runs without error, and 2) that the unit references you are using are valid (eg that name of the unit is actually what you expect it to be). Often you can't exercise every action while playing a scenario especially if so actions are random or timed. Lua General ----------- 1. Use meaningful names for variables. Add comments. With meaningful names and comments, it is easier to understand what the script is doing. In conjunction with structuring the code, this is especially helpful when re-visiting the scripts later on. You can add comments in several ways. a=10 -- this is a comment -- this is a comment line --[[ multiple line comment ]] 2. Use local variables as far as possible in scripts. By default, any variable defined is globally accessible. This means that the variable can be used in any script within the current scenario. A general rule should be to use local variables unless you really need to 'share' a value across multiple function/scripts. This can lead to problems where a variable is used locally but is actually global. To demonstrate: One script sets 'a = 10'. Another one sets 'a = 20'. And a third one checks the value 'if a == 10 then do_something end'. When the third script runs, the value of 'a' is 20 and the test fails. Add the keyword 'local' in front of the variable, and it is only available within the current function/script. One script sets 'a = 10'. Another one sets 'local a = 20'. And a third one checks the value 'if a == 10 then do_something end'. When the third one runs, the value of 'a' is 10 as the second script used a local variable which dies when the script ends. 3. Structure the code so that it is readable, indenting lines that are part of blocks such as if, loop, function, etc. for i=1,16 do if ScenEdit_GetUnit({Side='Denmark', Name="Birdsong #"..i,}) ~= nil then ScenEdit_SetUnitSide({Side='Denmark', Name="Birdsong #"..i, newside='NATO'}) if not ScenEdit_GetSideIsHuman('NATO') then ScenEdit_AssignUnitToMission("Birdsong #"..i, "NATO CAP") end end end 4. Multiple scans of the same list Having to scan a list of objects takes time, so you should try to optimize them. If checks are performed in one scan, ensure that they aren't required in the other scans. Take the following as an example. The script needs to scan through 16 units, and perform some actions on them. However as it stands at the moment, the first scan changes the side of existing units. The second scan assigns all 16 units to a mission if the side is AI controlled. for i=1,16 do if ScenEdit_GetUnit({Side='Germany', Name="Steinhoff #"..i,}) ~= nil then ScenEdit_SetUnitSide({Side='Germany', Name="Steinhoff #"..i, newside='NATO'}) end end if not ScenEdit_GetSideIsHuman('NATO') then for i=1,16 do ScenEdit_AssignUnitToMission("Steinhoff #"..i, "NATO CAP") end end The second scan does not check to see if the unit still exists before assigning it to a mission. A better approach is to merge them together as in .. for i=1,16 do -- scan thru numbers 1 to 16 if ScenEdit_GetUnit({Side='Germany', Name="Steinhoff #"..i,}) ~= nil then -- unit # exists ScenEdit_SetUnitSide({Side='Germany', Name="Steinhoff #"..i, newside='NATO'}) -- change side if not ScenEdit_GetSideIsHuman('NATO') then -- not human side ScenEdit_AssignUnitToMission("Steinhoff #"..i, "NATO CAP") -- assign mission end end end
Tomcat's Tutorials
{{#ev:youtube|B4aUjddDHok}} {{#ev:youtube|ayDyeXx79hI}}
Function Examples
Objects | Description |
---|---|
Unit Object | A Ship/Submarine/Facility/Aircraft. |
Reference Point Object | A Reference Point. |
Function Name | Description |
---|---|
ScenEdit_SetWeather | Set temperature, rainfall, cloud percentage and sea state. |
ScenEdit_AddAircraft | Creates a new aircraft in flight. |
ScenEdit_AddShip | Creates a new ship. |
ScenEdit_AddSubmarine | Creates a new submarine. |
ScenEdit_AddFacility | Creates a new facility. |
ScenEdit_AssignUnitToMission | Assigns a unit to a specified mission. |
ScenEdit_SetSidePosture | Sets the posture between two sides. |
ScenEdit_SetEMCON | Sets the state of the EMCON inheritable settings for a side, a missions, a group or a unit. |
ScenEdit_RunScript | Runs a script in your Lua directory. |
ScenEdit_SetKeyValue | Saves a variable into the scenario file. |
ScenEdit_GetKeyValue | Retrieves a variable from the scenario file. |
ScenEdit_AddUnit | Adds a unit. |
ScenEdit_SetUnit | Modifies a unit. |
ScenEdit_DeleteUnit | Deletes a unit. |
ScenEdit_AddReferencePoint | Adds a reference point. |
ScenEdit_SetReferencePoint | Modifies a reference point. |
ScenEdit_DeleteReferencePoint | Deletes a reference point. |
Evaluates an expression and writes it to the console window. | |
ScenEdit_SetDoctrine | Sets a unit's doctrine. |
ScenEdit_GetDoctrine | Gets a unit's doctrine. |
ScenEdit_CurrentTime | Gets the time in the scenario. |