Difference between revisions of "Tutorial - Basic Scripting"
m (typo) |
m (mostly commas added) |
||
(3 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | As scripting seems to be something difficult for most people to even start utilizing | + | As scripting seems to be something difficult for most people to even start utilizing, I thought it might be a good idea to write a short tutorial about the issue. Basic understanding of Lua or practically any other scripting or coding language is helpful, but is not actually required as there is an abundance of well-written guides to scripting in the Internet. Some of these have been gathered to the Links section in the [[Scripting.tbl]] page. Especially the [http://lua-users.org/wiki/TutorialDirectory Tutorial Directory] in [http://lua-users.org/wiki/ Lua-Users wiki] is very useful in learning to use Lua scripting with FreeSpace Open. |
<br><br> | <br><br> | ||
Simple mission with single fighter placed alone to the game. Intended for testing the use of scripts in FreeSpace Open, [http://koti.mbnet.fi/vekkup/FS2/FSPage/Scriptingtest.rar Scriptingtest.rar] | Simple mission with single fighter placed alone to the game. Intended for testing the use of scripts in FreeSpace Open, [http://koti.mbnet.fi/vekkup/FS2/FSPage/Scriptingtest.rar Scriptingtest.rar] | ||
Line 7: | Line 7: | ||
==Very Basics== | ==Very Basics== | ||
− | First take a good look at the [[Scripting.tbl]] page. From there you can see the hooks you can use for scripting. | + | First, take a good look at the [[Scripting.tbl]] page. From there you can see the hooks you can use for scripting. Also in [[Command-Line_Reference#-output_scripting|Scripting.html]] ''(link to command line option for outputting the scripting.html)'' are listed several conditional hooks. In addition to these, it is possible to use scripting also with subsystem animation. |
<br><br> | <br><br> | ||
All the hooks require square brackets after them into where the actual script is inserted. | All the hooks require square brackets after them into where the actual script is inserted. | ||
Line 20: | Line 20: | ||
==First Example== | ==First Example== | ||
− | + | Let's start with creating a small script that writes text to the screen. That is the very old '''Hello, World''' example. To achieve that effect we first need to create a scripting.tbl file and then also to create the needed sections. In order to achieve that goal, first create a plain text document using (for example) Notepad or similar non-autoformatting text editor. | |
*Name the empty text file as scripting.tbl | *Name the empty text file as scripting.tbl | ||
*Move it to '''''data/tables''''' directory | *Move it to '''''data/tables''''' directory | ||
Line 32: | Line 32: | ||
===Draw Text to Screen=== | ===Draw Text to Screen=== | ||
− | To write text (or rather a text string) to the screen we need to use certain Lua functions. The needed function '''''drawString()''''' is located in the '''Graphics''' library (abbreviated '''gr'''), [http://fs2source.warpcore.org/temp/scripting.html#Graphics Graphic library in Scripting.html]. To use that function we first need to create a '''''$HUD:''''' hook where we can place it. The function also needs some arguments and we ought to give it the required arguments. First we need the string ''''Hello, World'''' followed by the location coordinates (pixels) where the game is instructed to draw the string. Also note that as the function is from the '''Graphics''' library we need to add '''''gr.''''' before the function (that is to write gr.drawString instead of just drawString). | + | To write text (or rather a text string) to the screen, we need to use certain Lua functions. The needed function '''''drawString()''''' is located in the '''Graphics''' library (abbreviated '''gr'''), [http://fs2source.warpcore.org/temp/scripting.html#Graphics Graphic library in Scripting.html]. To use that function we first need to create a '''''$HUD:''''' hook where we can place it. The function also needs some arguments and we ought to give it the required arguments. First we need the string ''''Hello, World'''' followed by the location coordinates (pixels) where the game is instructed to draw the string. Also note that as the function is from the '''Graphics''' library, we need to add '''''gr.''''' before the function (that is to write gr.drawString instead of just drawString). |
<br><br> | <br><br> | ||
− | Notice that we can comment (the rest of) the line away with double dashes, like so '''''– –'''''. This is | + | Notice that we can comment (the rest of) the line away with double dashes, like so '''''– –'''''. This is useful for adding notes to the scripts for later reference or for preventing certain lines of the script to be read. |
<br><br> | <br><br> | ||
'''Scripting.tbl''' | '''Scripting.tbl''' | ||
Line 52: | Line 52: | ||
===Drawing in Different Locations=== | ===Drawing in Different Locations=== | ||
− | In the first example we draw the string so that it started from the position 100, 100. Now | + | In the first example we draw the string so that it started from the position 100, 100. Now let's draw the same string elsewhere on the screen. |
<br><br> | <br><br> | ||
'''Scripting.tbl''' | '''Scripting.tbl''' | ||
Line 72: | Line 72: | ||
===Drawing in Different Colors=== | ===Drawing in Different Colors=== | ||
− | Now that we have several text strings drawn to the screen we might want to try to alter their colors. For this we need another function from the '''Graphics''' library, '''''setColor()''''' that accepts | + | Now that we have several text strings drawn to the screen, we might want to try to alter their colors. For this, we need another function from the '''Graphics''' library, '''''setColor()''''' that accepts three arguments. These are basic RGB. |
<br><br> | <br><br> | ||
'''Scripting.tbl''' | '''Scripting.tbl''' | ||
Line 87: | Line 87: | ||
gr.setColor(0, 0, 255) | gr.setColor(0, 0, 255) | ||
gr.drawString('Hello, World', 160, 220) | gr.drawString('Hello, World', 160, 220) | ||
− | gr.setColor(255, 255, 255 | + | gr.setColor(255, 255, 255) |
gr.drawString('Hello, World', 190, 280) | gr.drawString('Hello, World', 190, 280) | ||
Line 94: | Line 94: | ||
#End</pre> | #End</pre> | ||
<br><br> | <br><br> | ||
− | NOTE: Some of the functions (mainly from graphics library) are specific to certain global hooks. That is if you try to use them in a wrong hook the script | + | NOTE: Some of the functions (mainly from graphics library) are specific to certain global hooks. That is, if you try to use them in a wrong hook, the script won't do anything. The game doesn't actually warn about this, so you may have to figure this one out on simple trial-and-error method. |
==Drawing Particles== | ==Drawing Particles== | ||
− | Next we will try drawing particles to the screen. Again we need the scripting.tbl for this | + | Next, we will try drawing particles to the screen. Again, we need the scripting.tbl for this |
<br><br> | <br><br> | ||
'''Scripting.tbl''' | '''Scripting.tbl''' | ||
Line 107: | Line 107: | ||
===Scripted Particles=== | ===Scripted Particles=== | ||
− | To draw (or render) particles to the screen we need to use other Lua functions than what we used in the text example. The required function is '''''createParticle()''''' from '''Testing''' library (abbreviated '''ts.''') and to place it into the '''$Simulation:''' hook. This function requires several arguments but for this example we only need to use few of them. As can be seen from the [http://fs2source.warpcore.org/temp/scripting.html#Testing scripting.html] the function uses vector objects for the first two arguments followed by two numerical ones and several others. | + | To draw (or render) particles to the screen, we need to use other Lua functions than what we used in the text example. The required function is '''''createParticle()''''' from '''Testing''' library (abbreviated '''ts.''') and to place it into the '''$Simulation:''' hook. This function requires several arguments, but for this example, we only need to use few of them. As can be seen from the [http://fs2source.warpcore.org/temp/scripting.html#Testing scripting.html] the function uses vector objects for the first two arguments followed by two numerical ones and several others. |
<br><br> | <br><br> | ||
− | This means however that before we can actually start using the function we need to get some vector objects. Function to create these objects can be found from ''' | + | This means, however, that before we can actually start using the function we need to get some vector objects. Function to create these objects can be found from '''Base''' (abbreviated '''ba.''') library ('''''newVector()'''''). We also need to assign a type for the particle, for this purpose the 'debug particle' is good enough. To get the debug particle, we must use [http://fs2source.warpcore.org/temp/scripting.html#Enumerations enumerations] for setting the particle type ('''PARTICLE_DEBUG'''). Other values can be set to be whatever we wish them to be. It's good to remember, though, that the script is executed every frame. |
<br><br> | <br><br> | ||
'''Scripting.tbl''' | '''Scripting.tbl''' | ||
Line 119: | Line 119: | ||
--1st create a new vector object and assign it as variable | --1st create a new vector object and assign it as variable | ||
− | nullvec = | + | nullvec = ba.newVector(0,0,0) |
--2nd draw the particle | --2nd draw the particle | ||
− | ts.createParticle(nullvec,nullvec,0.25,20, | + | ts.createParticle(nullvec,nullvec,0.25,20,PARTICLE_DEBUG) |
] | ] | ||
Line 130: | Line 130: | ||
===Attaching Bitmaps to Particles=== | ===Attaching Bitmaps to Particles=== | ||
− | Now that we have managed to draw a particle to the screen we might want to start modifying it. | + | Now that we have managed to draw a particle to the screen, we might want to start modifying it. Let's begin with changing the 'Debug' particle into something else. Let's select for example '''laserglow03''' for this example. For this, we first need to load the graphics into a texture handle, and that can be done with '''''loadTexture''''' function from the '''Graphics''' library. Then we must change the particle script a bit by adding several new (optional) arguments to it. This is done by first changing the earlier '''PARTICLE_DEBUG''' into '''PARTICLE_BITMAP''' followed by ''', -1, false''' and finally by the texture. |
<br><br> | <br><br> | ||
Line 142: | Line 142: | ||
nullvec = ma.newVector(0,0,0) | nullvec = ma.newVector(0,0,0) | ||
− | --load a new graphic | + | --load a new graphic and set it as 'particletexture' |
particletexture = gr.loadTexture('laserglow03') | particletexture = gr.loadTexture('laserglow03') | ||
− | ts.createParticle(nullvec,nullvec,0.25,20, | + | --use the newly defined 'particletexture' when drawing the particles |
+ | ts.createParticle(nullvec,nullvec,0.25,20,PARTICLE_BITMAP, -1, false, particletexture) | ||
] | ] | ||
Line 153: | Line 154: | ||
===Animations and Moving Particles=== | ===Animations and Moving Particles=== | ||
− | We can load animations as particle graphics and use these for creating new effects. This is done simply by adding to '''gr.loadTexture()''' function a new argument. We can also try moving both the particle effect and the changing the velocity of the spawned particle effect. Procedure is very similar to static and centered effect but this time we need create new vector objects for the particle velocity and the particle position. | + | We can load animations as particle graphics and use these for creating new effects. This is done simply by adding to '''gr.loadTexture()''' function a new argument. We can also try moving both the particle effect and the changing the velocity of the spawned particle effect. Procedure is very similar to static and centered effect, but this time we need create new vector objects for the particle velocity and the particle position. |
<br><br> | <br><br> | ||
'''Scripting.tbl''' | '''Scripting.tbl''' | ||
Line 163: | Line 164: | ||
--Create the new vectors | --Create the new vectors | ||
− | movevec = | + | movevec = ba.newVector(0,20,0) |
− | velvec = | + | velvec = ba.newVector(20,0,1000) |
− | --Load animation, true is needed for loading the animation | + | --Load animation, true is needed for loading the animation, see scripting.html |
particletexture = gr.loadTexture('exp04',true) | particletexture = gr.loadTexture('exp04',true) | ||
− | ts.createParticle(movevec,velvec,1,20, | + | ts.createParticle(movevec,velvec,1,20,PARTICLE_BITMAP, -1, false, particletexture) |
] | ] | ||
Line 177: | Line 178: | ||
==Using Statements== | ==Using Statements== | ||
− | Now that we can handle basics of creating graphics on the screen we can move on to actually controlling them. For this it | + | Now that we can handle basics of creating graphics on the screen, we can move on to actually controlling them. For this, it is useful to read both [http://lua-users.org/wiki/ControlStructureTutorial Control Structure] and [http://lua-users.org/wiki/ForTutorial For Tutorial]s from Lua-Users wiki first. |
===Drawing Ship List on Screen=== | ===Drawing Ship List on Screen=== | ||
− | In first example we draw a list of ship using data read from the game with scripts. To do this we need access every | + | In the first example, we draw a list of ship using data read from the game with scripts. To do this, we need to access every ship's status on every frame and therefore we will use '''for - do''' statement. We will need to place the script to '''$HUD:''' hook as we want to draw the results on the HUD screen. Then, we will need to define the limits of the '''for - do - end''' statement, this we can do using a function '''''#Ships''''' from '''Mission''' library (abbreviated '''mn.'''). Now we can access all ships simply by using '''''Ships''''' function. As we do this, we need to access several variables from ship object data ([http://fs2source.warpcore.org/temp/scripting.html#ship scripting.html]). |
<br><br> | <br><br> | ||
Line 192: | Line 193: | ||
--For statement and its limits | --For statement and its limits | ||
− | for i=1,mn. | + | for i=1,mn.#Ships do |
− | --we need to use the same letter (i) as in for statement | + | --we need to use the same letter (i) as used in the for statement |
--then we place the ship object to variable named 'ship'. | --then we place the ship object to variable named 'ship'. | ||
− | ship = mn. | + | ship = mn.Ships[i] |
− | --we wanted to draw the ships names | + | --we wanted to draw the ships' names, so we need to access that data |
shipname = ship.Name | shipname = ship.Name | ||
− | -- | + | --let's draw shipname string |
--to avoid drawing the names on top of each other | --to avoid drawing the names on top of each other | ||
--add a calculation (80 + 20*i) to the arguments | --add a calculation (80 + 20*i) to the arguments | ||
gr.drawString(shipname,100,80+20*i) | gr.drawString(shipname,100,80+20*i) | ||
+ | |||
+ | --This script goes through every ship in the mission | ||
+ | --Draws their names according to their order in the ship index | ||
+ | --Avoids drawing the names on top of each other | ||
+ | --1st name starts at 100,100, 2nd 100,120, etc. | ||
--for statement has to end | --for statement has to end | ||
Line 213: | Line 219: | ||
#End</pre> | #End</pre> | ||
<br><br> | <br><br> | ||
− | NOTE: String may seem to be invisible but this can be easily fixed with the | + | NOTE: String may seem to be invisible, but this can be easily fixed with the scripting functions that we [[Tutorial_-_Basic_Scripting#Drawing_in_Different_Colors|tried earlier]]. |
===Adding More to the List=== | ===Adding More to the List=== | ||
− | Other data can also be drawn in similar way. | + | Other data can also be drawn in similar way. Let's take for example the remaining hitpoints of the ship and the class of the ship. |
<br><br> | <br><br> | ||
Line 227: | Line 233: | ||
[ | [ | ||
− | for i=1,mn. | + | for i=1,mn.#Ships do |
− | ship = mn. | + | ship = mn.#Ships[i] |
shipname = ship.Name | shipname = ship.Name | ||
− | --Get ship hitpoints | + | --Get ship's current hitpoints |
shiphp = ship.HitpointsLeft | shiphp = ship.HitpointsLeft | ||
Line 250: | Line 256: | ||
===Using If Statement=== | ===Using If Statement=== | ||
− | We can still add to the script for example a rudimentary IFF selective color coding. For this effect we | + | We can still add a lot of different things to the script for example a rudimentary IFF selective color coding. For this effect we can use '''if - then - end''' statement. |
<br><br> | <br><br> | ||
Line 260: | Line 266: | ||
[ | [ | ||
− | for i=1,mn. | + | for i=1,mn.#Ships do |
− | ship = mn. | + | ship = mn.Ships[i] |
shipname = ship.Name | shipname = ship.Name | ||
shiphp = ship.HitpointsLeft | shiphp = ship.HitpointsLeft | ||
shipclass = ship.Class.Name | shipclass = ship.Class.Name | ||
− | --we need to access ship | + | --we need to access ship team data |
shipteam = ship.Team.Name | shipteam = ship.Team.Name | ||
Line 275: | Line 281: | ||
elseif shipteam == "Hostile" then | elseif shipteam == "Hostile" then | ||
gr.setColor(255,0,0) | gr.setColor(255,0,0) | ||
− | else | + | else |
+ | |||
gr.setColor (0,255,255) | gr.setColor (0,255,255) | ||
end | end |
Latest revision as of 21:32, 9 January 2009
As scripting seems to be something difficult for most people to even start utilizing, I thought it might be a good idea to write a short tutorial about the issue. Basic understanding of Lua or practically any other scripting or coding language is helpful, but is not actually required as there is an abundance of well-written guides to scripting in the Internet. Some of these have been gathered to the Links section in the Scripting.tbl page. Especially the Tutorial Directory in Lua-Users wiki is very useful in learning to use Lua scripting with FreeSpace Open.
Simple mission with single fighter placed alone to the game. Intended for testing the use of scripts in FreeSpace Open, Scriptingtest.rar
Please post any comments or questions concerning the basic scripting tutorial to the talk page
Very Basics
First, take a good look at the Scripting.tbl page. From there you can see the hooks you can use for scripting. Also in Scripting.html (link to command line option for outputting the scripting.html) are listed several conditional hooks. In addition to these, it is possible to use scripting also with subsystem animation.
All the hooks require square brackets after them into where the actual script is inserted.
Scripting.tbl
$Simulation: [ ... ]
First Example
Let's start with creating a small script that writes text to the screen. That is the very old Hello, World example. To achieve that effect we first need to create a scripting.tbl file and then also to create the needed sections. In order to achieve that goal, first create a plain text document using (for example) Notepad or similar non-autoformatting text editor.
- Name the empty text file as scripting.tbl
- Move it to data/tables directory
- Add the needed #Global Hooks and #End to the table.
Scripting.tbl
#Global Hooks #End
Draw Text to Screen
To write text (or rather a text string) to the screen, we need to use certain Lua functions. The needed function drawString() is located in the Graphics library (abbreviated gr), Graphic library in Scripting.html. To use that function we first need to create a $HUD: hook where we can place it. The function also needs some arguments and we ought to give it the required arguments. First we need the string 'Hello, World' followed by the location coordinates (pixels) where the game is instructed to draw the string. Also note that as the function is from the Graphics library, we need to add gr. before the function (that is to write gr.drawString instead of just drawString).
Notice that we can comment (the rest of) the line away with double dashes, like so – –. This is useful for adding notes to the scripts for later reference or for preventing certain lines of the script to be read.
Scripting.tbl
#Global Hooks $HUD: [ --Draws 'Hello, World' to the HUD gr.drawString('Hello, World', 100, 100) ] #End
Drawing in Different Locations
In the first example we draw the string so that it started from the position 100, 100. Now let's draw the same string elsewhere on the screen.
Scripting.tbl
#Global Hooks $HUD: [ gr.drawString('Hello, World', 100, 100) gr.drawString('Hello, World', 130, 160) gr.drawString('Hello, World', 160, 220) gr.drawString('Hello, World', 190, 280) ] #End
Drawing in Different Colors
Now that we have several text strings drawn to the screen, we might want to try to alter their colors. For this, we need another function from the Graphics library, setColor() that accepts three arguments. These are basic RGB.
Scripting.tbl
#Global Hooks $HUD: [ gr.setColor(0, 255, 0) gr.drawString('Hello, World', 100, 100) gr.setColor(255, 0, 0) gr.drawString('Hello, World', 130, 160) gr.setColor(0, 0, 255) gr.drawString('Hello, World', 160, 220) gr.setColor(255, 255, 255) gr.drawString('Hello, World', 190, 280) ] #End
NOTE: Some of the functions (mainly from graphics library) are specific to certain global hooks. That is, if you try to use them in a wrong hook, the script won't do anything. The game doesn't actually warn about this, so you may have to figure this one out on simple trial-and-error method.
Drawing Particles
Next, we will try drawing particles to the screen. Again, we need the scripting.tbl for this
Scripting.tbl
#Global Hooks #End
Scripted Particles
To draw (or render) particles to the screen, we need to use other Lua functions than what we used in the text example. The required function is createParticle() from Testing library (abbreviated ts.) and to place it into the $Simulation: hook. This function requires several arguments, but for this example, we only need to use few of them. As can be seen from the scripting.html the function uses vector objects for the first two arguments followed by two numerical ones and several others.
This means, however, that before we can actually start using the function we need to get some vector objects. Function to create these objects can be found from Base (abbreviated ba.) library (newVector()). We also need to assign a type for the particle, for this purpose the 'debug particle' is good enough. To get the debug particle, we must use enumerations for setting the particle type (PARTICLE_DEBUG). Other values can be set to be whatever we wish them to be. It's good to remember, though, that the script is executed every frame.
Scripting.tbl
#Global Hooks $Simulation: [ --1st create a new vector object and assign it as variable nullvec = ba.newVector(0,0,0) --2nd draw the particle ts.createParticle(nullvec,nullvec,0.25,20,PARTICLE_DEBUG) ] #End
Attaching Bitmaps to Particles
Now that we have managed to draw a particle to the screen, we might want to start modifying it. Let's begin with changing the 'Debug' particle into something else. Let's select for example laserglow03 for this example. For this, we first need to load the graphics into a texture handle, and that can be done with loadTexture function from the Graphics library. Then we must change the particle script a bit by adding several new (optional) arguments to it. This is done by first changing the earlier PARTICLE_DEBUG into PARTICLE_BITMAP followed by , -1, false and finally by the texture.
Scripting.tbl
#Global Hooks $Simulation: [ nullvec = ma.newVector(0,0,0) --load a new graphic and set it as 'particletexture' particletexture = gr.loadTexture('laserglow03') --use the newly defined 'particletexture' when drawing the particles ts.createParticle(nullvec,nullvec,0.25,20,PARTICLE_BITMAP, -1, false, particletexture) ] #End
Animations and Moving Particles
We can load animations as particle graphics and use these for creating new effects. This is done simply by adding to gr.loadTexture() function a new argument. We can also try moving both the particle effect and the changing the velocity of the spawned particle effect. Procedure is very similar to static and centered effect, but this time we need create new vector objects for the particle velocity and the particle position.
Scripting.tbl
#Global Hooks $Simulation: [ --Create the new vectors movevec = ba.newVector(0,20,0) velvec = ba.newVector(20,0,1000) --Load animation, true is needed for loading the animation, see scripting.html particletexture = gr.loadTexture('exp04',true) ts.createParticle(movevec,velvec,1,20,PARTICLE_BITMAP, -1, false, particletexture) ] #End
Using Statements
Now that we can handle basics of creating graphics on the screen, we can move on to actually controlling them. For this, it is useful to read both Control Structure and For Tutorials from Lua-Users wiki first.
Drawing Ship List on Screen
In the first example, we draw a list of ship using data read from the game with scripts. To do this, we need to access every ship's status on every frame and therefore we will use for - do statement. We will need to place the script to $HUD: hook as we want to draw the results on the HUD screen. Then, we will need to define the limits of the for - do - end statement, this we can do using a function #Ships from Mission library (abbreviated mn.). Now we can access all ships simply by using Ships function. As we do this, we need to access several variables from ship object data (scripting.html).
Scripting.tbl
#Global Hooks $HUD: [ --For statement and its limits for i=1,mn.#Ships do --we need to use the same letter (i) as used in the for statement --then we place the ship object to variable named 'ship'. ship = mn.Ships[i] --we wanted to draw the ships' names, so we need to access that data shipname = ship.Name --let's draw shipname string --to avoid drawing the names on top of each other --add a calculation (80 + 20*i) to the arguments gr.drawString(shipname,100,80+20*i) --This script goes through every ship in the mission --Draws their names according to their order in the ship index --Avoids drawing the names on top of each other --1st name starts at 100,100, 2nd 100,120, etc. --for statement has to end end ] #End
NOTE: String may seem to be invisible, but this can be easily fixed with the scripting functions that we tried earlier.
Adding More to the List
Other data can also be drawn in similar way. Let's take for example the remaining hitpoints of the ship and the class of the ship.
Scripting.tbl
#Global Hooks $HUD: [ for i=1,mn.#Ships do ship = mn.#Ships[i] shipname = ship.Name --Get ship's current hitpoints shiphp = ship.HitpointsLeft --Get ship class shipclass = ship.Class.Name gr.drawString(shipname,100,80+20*i) gr.drawString(shiphp,200,80+20*i) gr.drawString(shipclass,300,80+20*i) end ] #End
Using If Statement
We can still add a lot of different things to the script for example a rudimentary IFF selective color coding. For this effect we can use if - then - end statement.
Scripting.tbl
#Global Hooks $HUD: [ for i=1,mn.#Ships do ship = mn.Ships[i] shipname = ship.Name shiphp = ship.HitpointsLeft shipclass = ship.Class.Name --we need to access ship team data shipteam = ship.Team.Name --here we start the if statement if shipteam == "Friendly" then gr.setColor(0,255,0) elseif shipteam == "Hostile" then gr.setColor(255,0,0) else gr.setColor (0,255,255) end gr.drawString(shipname,100,80+20*i) gr.drawString(shiphp,200,80+20*i) gr.drawString(shipclass,300,80+20*i) end ] #End