CommandLua

From Baloogan Campaign Wiki
Jump to: navigation, search

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

Global Functions
Objects Description
Unit Object A Ship/Submarine/Facility/Aircraft.
Reference Point Object A Reference Point.


Global Functions
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.
print 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.