Posted: Tue Dec 11, 2007 12:52 pm Post subject: Scripting Tutorial
Note: code boxes won't always show code how I see it. To do that copy and paste it into Notepad and turn Word Wrap off.
I'm making this topic for two reasons. Firstly, to teach all of you who don't already know how to write 'scripts' for use in Enemy Territory (though any Q3 game has this ability), but secondly because I'm rather proud of my config, and wouldn't mind a chance to show it to the world. So, with that in mind, shall we get started?
If it feels like the tutorial is moving too fast for you, keep re-reading the bit that's confusing you, and try to work out what the game will do when things in the script happen.
Okay, in case you haven't already noticed, there is a key on your keyboard (usually above the TAB key) that, when pressed in ET will pull down a load of text from the top of the screen. This is called the console, and can be used to view in-game events and give the game written commands. eg. /quit (no prizes for guessing what that does)
Most players will refer to the console key as the ~ key. This is because on American keyboards the key used to call down the console is ~, and because the game files use the American keyboard layout the name has stuck.
All console commands must be started with a slash (back or forward slash, doesn't matter) or else your soldier will speak the thing you just typed into the console. This was originally to make noob single-player gamers have to work harder to find cheats, but now it just gets in the way. It's sometimes very important to remember this detail, because sometimes you will be asked to enter a password into the console to gain access to the server's special functions, and if you forget the slash the whole server will see your carefully thought up password and you will have to (very quickly, before they use it) change it to something else.
When typing something in the game, if you are saying something or setting your name up, you can put in colours. This is done by putting a ^ symbol in, then following it with any character from the keyboard. The colour it sets is based entirely on this character you put after the ^. A list of possible colours can be found here.
Most ET scripters use a file called "autoexec.cfg". This file, if put in your "etmain" ET directory, will be run by the game as soon as the game loads. This file is really easy to create. It's just a text file with a .cfg extention. To create one just open Notepad, go to File, then Save As, and type in "C:\Program Files\Wolfenstein - Enemy Territory\etmain\autoexec.cfg", and hit Save. To make sure that the file isn't saved as autoexec.cfg.txt, surround the save location in quotation marks ("") or rename the file after it is created.
Now, you have an autoexec, but currently it doesn't do anything. Let's add a line of script to bind your M key to a vsay. For this example I'm going to bind it to that ultra-spammable evil laugh that we know and love. To do this, type this in your autoexec:
bind M "vsay evillarf"
Now let's have a look at what we did there. First we have the command "bind". This command tells the game that we want to assign a key to do something for us. The bind statement is used like this: "bind <key> <command>". Now you may have noticed, but in the autoexec we aren't using backslashes. This is only because we are using a cfg file, and the game already knows that everything contained in it is a command, so we don't need to tell it. (Note: You do not need backslashes in the binded key function area, even in the console. So don't do "/bind x /vsay hello")
So, now you know what bind does, but what about this "vsay evillarf" part? Here we have another command being used, the 'vsay' command. This command is used like this: "vsay <vsay name>" (vsay_team is used in the same way). Vsay stands for voice-say, and is your way of making the game speak something from the vsay menu constantly used throughout the game. For example, v54 is "Hello!", and would be vsaid by "vsay Hello".
A full list of vsay names is available here, and a list of bindable keys slighty further up the page.
Now the next command we will use is the "set <cvar> <value>". Here is some code to change your brightness:
set "r_gamma 2"
Let's take a quick look at what we did there. First we told the game what command we are usin (set), then we told it what variable setting we wanted to change (r_gamma). Then we gave it the new value for that variable, which it would then put in place of the old variable. If we wanted to find out what the variable was before we changed it, we would simply go into the game and open the console (Remember, the key above TAB) and type in "/r_gamma". Here we have not given the game a command, so it will simply show us that variable's current setting.
Another helpful command is "toggle <cvar>". This will switch between that cvar being 1 and 0, helpful for things like cg_drawgun.
A full list of cvars and what they do can be found here.
Okay, so now we have a few commands in our autoexec that will be useful to us. I said before that ET will run the autoexec automatically when it loads, and this is true, but if you are going to be connecting to a server that has been modded to ETpub, ETpro or any other modification you will need to run the autoexec again, for that mod. To do this, open the console and type in "/exec autoexec.cfg". This here is the command to load a list of commands (a cfg), and then the list name. The game will then search through your the folder for your currently activated mod and the "etmain" one, and run "autoexec.cfg" when it finds it.
Great, now your cfg has been imported into the game, and all commands in it will have been run, binding and setting as much as you want.
Up a notch
/bind x "kill; forcetapout; team_say Goodbye cruel world!"
Here we go, new command, and a triple bind! Let's explain how this works. The new command I've introduced here is "kill". This command has no key or setting to be typed after it, as it asks the game to perform an action. What it does, is instantly kills your character, and unless you're in warmup or the game glitches, sends you straight to limbo (where you can choose spawn and class and watch other players until you spawn). Now it is possible for the game to glitch and leave your wounded. If you hit X just as your team was about to respawn, being wounded could cause you to miss your spawn and have to watch other peopel for 30 seconds, so I use this second command too: "forcetapout". This command is basically a "force tap-out", meaning it taps you out if you are wounded, sending you to limbo, making sure the game can't glitch. (also meaning you could use to tap out after being killed). The third command is "team_say". This simply sends something in team chat, in this case sending "Goodbye cruel world!".
Now here comes a more complicated command, which will be the building-block of some of my stupidly complicated configs later on. This command is "vstr". vstr simple executes a line of code that you have written before. It's like a mini cfg inside another cfg. Let's say you wanted a key to change your FOV (field of view) between 90 and 120. This would be impossible without using vstrs. Here is the code to do that taken from my own cfg:
// Toggle between 120 and 90 fps
seta FOV1 "cg_fov 120; set p_FOVtoggle vstr FOV0"
seta FOV0 "cg_fov 90; set p_FOVtoggle vstr FOV1"
seta p_FOVtoggle "vstr FOV1"
bind x "vstr p_FOVtoggle"
Now let's take a look at this line-by-line.
Line 1. // Toggle between 120 and 90 fps - This line does nothing. Nothing at all. Any line written with // at the beginning is ignored by the game. Why use it then? Well it is very helpful for explaining what your cfg is doing to people reading it, and because of this I use them a lot when explaining how scripting works, so you will be seeing a lot more of them.
Line 2. seta FOV1 "cg_fov 120; set p_FOVtoggle vstr FOV0" - Straight away we have a nwe command, 'seta'. This is exactly the same as the 'set'command, but it also makes sure to write the setting into your game-generated config files straight away. Some people don't use it, but I prefer to. Next we have a variable being set, called 'FOV1'. Now if you look at a lit of cvars you won't find that one anywhere on it. This is because it is not a cvar, but it is a user-made variable. When the game cannot find the cvar in it's list it creates one for me with that name. Then I put "cg_fov 120; set p_FOVtoggle vstr FOV0" inside this newly created variable. This code here means that as soon as FOV1 is executed a variable called "p_FOVtoggle" will have it's data changed to read "vstr FOV0". Oh dear, this does seem complicated doesn't it, but don't worry. I promise it will make sense by the end of this section of this guide.
Line 3. So similar to line 2 that I won't bother explaining it again.
Line 4. Setting up a new variable "p_FOVtoggle" with the data "vstr FOV1"
Line 5. Binding x to "vstr p_FOVtoggle". This code here means that when X is pressed, the game will run whatever code is inside "p_FOVtoggle". 'vstr' runs code inside player-made variables.
Now let's look at what will happen when the user presses x (check back to the code above to make sure you understand why these things are happening):
X is pressed -> Game executes p_FOVtoggle -> p_FOVtoggle executes FOV1 -> FOV1 changes the setting cg_fov to be 120 -> FOV1 changes p_FOVtoggle to "vstr FOV0" -> X is pressed again -> Game executes p_FOVtoggle -> p_FOVtoggle executes FOV0 (it was changed remember?) -> FOV0 changes the setting cg_fov to be 90 -> FOV0 changes p_FOVtoggle to "vstr FOV1"
As you can see this would loop again and again switching between the two FOV settings by changing which of the two variables will be executed each time.
This code could also be written to change what x is binded to, rather than changing p_FOVtoggle, but it is not a good idea to bind like that, as if you had to change what key you were using to change FOV you would need to change the key several times in the script. It is better to bind to a variable that you can then change as much as you want.
If that doesn't make sense to you, read through it again and follow what bits of code are doing what in my little timeline.
Now we're getting somewhere
Before we move on, I'd recommend going to your "etmain" folder and opening "etconfig.cfg" in Wordpad (notepad doesn't understand the line-breaks used in the file). This file is the game's default settings, and can be a good way to start your cfg off. If you now go and read through that list of cvars here, and see if there are any you wish to change. In that list you will find many things that, when changed, will make the game much more playable. A word of caution though, it is a good idea to try setting cvars in a server using the console first, to check if punkbuster has any restrictions down on it. If you have a config full of PB restricted cvars you'll have a hard job sorting them all out before you get kicked.
Now let's stop working from solutions, and start working with problems. First problem I can think of, is that when you load a mod you have to open the console and exec your autoexec manually. So, let's think of a way we can do this easily. We need to open the console to do something again and again. Sounds like a simple binding problem to me, so let's bind it. What I did was this line of code here:
bind = "exec autoexec"
Now that works well, except in game when you hit = (after execing the autoexec 1 time first to bind the key) you have no idea if your config has executed or not. So we want the game to tell us when it's done. The easiest way to do this is to add a line of code anywhere in the cfg (usually put at the bottom) that says:
echo "^aAutoexec.cfg ^2Loaded"
This code will write "Autoexec Loaded" in pretty colours whenever the code in the autoexec is run.
Another problem, which is rather more important than the last one, is that once you put a bind or a setting in your autoexec and execute it, it is then in the game files for ever. Just removing it from your autoexec won't fix it. You would have to use your autoexec to change it to something else to fix it. That is why the etconfig file is so important, as it has your default (or 'safe') settings stored in it for you to copy and use. The way that I made my config was the copy the entire contents of my etconfig file into my autoexec and then just change settings in there. Because that meant ALL my binds would be listed in that file from now on, I could make use of the "/unbindall" command. This command does exactly what it says on the tin, and unbinds all your keybinds, INCLUDING THE CONSOLE KEY. It is very important you do not use that key without having all your bindings following it in the file. So, using the etconfig (or, if your game is already slightly customised, your mod(etpub)/profiles/name/etconfig.cfg) and copying out all the settings from in there is a good way to fix this problem.
Next problem: IPs are damn hard to remember. We need some way to store them and use them. Solution? We could always store them in vstrs.... but you can't use keybinds in the menu screen so how would that be helpful? Well here's the code I use:
This code is simple. When I'm at the menu and i need to connectI just open the console and type in whatever word I use to remember that server by. Let's say I want to play on our lovable Moo server. I would then type in "/vstr moo", and the game would instantly connect, ensuring me hours of fun with the gang.
A lot of players find that the gun is distracting, and so play with cg_drawgun turned off. This works well, unless you take out an item like a syringe. Either you will forget you have it out and be unable to shoot people, or you will think you have it out when you don't really, and end up shooting the person you're aiming to revive. So, we need to make it so that just the MP40 is hidden, and the other guns aren't. This is as simple as a double bind:
This simply makes it so that when you press a button to bring out a weapon, it hides it if it is in slot 3. Simple huh?
If you sprint in a firefight you become a lot harder to hit, but sometimes you forget to hold down shift. So, why not use scripting to help? What we need to do here is bind the MOUSE1 key to fire and sprint at the same time. You would expect this would be as simple as typing
bind MOUSE1 "+attack; +sprint"
Unfortunately, this doesn't work. Because you have put two commands in, the game will activate attack and sprint when you press the button, but will not de-activate them when you release it, as it normally does with +commands. You need to tell it exactly what you want, using a +vstr. Here's how to use one:
// Just in case I forget to sprint in a firefight
bind MOUSE1 "+vstr SprintAT1 SprintAT2"
seta SprintAT1 "+attack; +sprint"
seta SprintAT2 "-attack; -sprint"
Again, this was taken straight from my CFG, though this time I modified it as it is part of a more complicated bind in there. Here, we have some interesting code. In line 2 we have used +vstr, which is used like this: "+vstr <keydown> <keyup>". Now if you look, you can see that on keydown it runs SprintAT1, which activates attack and sprint, and then on keyup de-activates them both, fixing the problem easily.
Problem: Screen sometimes becomes dark and setting gamma to 2 doesn't help. Solution: The reason setting it doesn't help is because the game doesn't realise it's dark. To change it back to bright you have to lower the gamma, and then raise it again. Easy:
This code lowers the brightness, waits 30 frames (if you were running at 30fps it would wait 1 second, at 60 it would wait 1/2 a second, etc) and raises the brightness again. It is a vstr so that I can either bind it to a key, or call it using the console whenever it is needed, then it is executed when the cfg runs just in case the game is dark when it loads.
Now another problem: You've run out of keys to bind. Well, if you think about it, could you make it so that you could have some ALT+key binds? Most people would say no, but you can if you put your mind to it. If you bound ALT to a +vstr, when it is held down the vstr would be activated, maybe changing some of your binds. Here's some of my ALT+key binds from my CFG:
// ALT+key binding
bind ALT "+vstr ALTdn ALTup"
seta ALTdn "vstr setfdopsbinoc; vstr setexit; vstr settopstats" // List all ALT+set vstrs here
seta ALTup "vstr unsetfdopsbinoc; vstr unsetexit; vstr unsettopstats"
seta settopstats "bind z vstr topstats"
seta unsettopstats "bind z +prone"
seta topstats "vstr topstats1"
seta topstats1 "+topshots; +stats; set topstats vstr topstats2"
seta topstats2 "-topshots; -stats; set topstats vstr topstats1"
// [[* End of ALT+key binds *]]
This will take a lot of explaining, but it will make sense. Don't worry about how long and complicated it looks, just work through it line-by-line, as the game will. Here is what I see will happen:
Autoexec executed -> ALT bound to "+vstr ALTdn ALTup" -> ALT pressed -> ALTdn executed -> vstrs "setfdopsbinoc", "setexit" and "settopstats" executed -> each change some binds -> changed binds can be pressed to do anything, or: -> Alt released -> ALTup executed -> vstrs "unsetfdopsbinoc", "unsetexit" and "unsettopstats" executed -> each change binds back to normal
As you can see, while ALT is held down all these other binds become ready to use. (You should be able to see what those other binds do)
You're on your own now
From here on out you know pretty much as much as me when it comes to scripting. Most things are now possible if you just think about it carefully enough and find the needed commands in that list. For example, here is a class selector. It is very complicated, but if you think clearly and work through it line-by-line as the game would, you can figure out what it is doing:
// Class Selector (frickin complicated - creds for the original to Yvo, found here: http://ftp.freenet.de/pub/4players/hosted/et/scripts/Yvo's%20Script%20Bin.htm)
seta setselector "bind 1 vstr teamtoggle; bind 2 vstr medic; bind 3 vstr field; bind 4 vstr eng; bind 5 vstr cov; bind 6 vstr sol; bind 7 team s; bind 8 vstr menu"
seta unsetselector "bind 1 vstr wb1; bind 2 vstr wb2; bind 3 vstr wb3; bind 4 vstr wb4; bind 5 vstr wb5; bind 6 vstr wb6; bind 7 vstr wb7; bind 8 vstr wb8; vstr weaponresets"
seta weaponresets "set alliesengineer vstr allieseng1; set alliescovert vstr alliescov1; set alliessoldier vstr alliessol1; set axisengineer vstr axiseng1; set axiscovert vstr axiscov1; set axissoldier vstr axissol1"
seta menu "echo ^0>^7 1^0: ^7ALLIES/AXIS ^0| ^72^0: ^7MEDIC; echo ^0>^7 3^0: ^7FIELD OPS ^0| ^74^0: ^7ENGINEER; echo ^0>^7 5^0: ^7COVERT OPS ^0| ^76^0: ^7SOLDIER; echo ^0>^7 7^0: ^7SPECTATOR ^0| ^78^0: ^7MENU"
seta teamb "set teamtoggle vstr teamr; vstr setallies; echo ^0> ^7********** ^0ALLIES ^7**********"
seta teamr "set teamtoggle vstr teamb; vstr setaxis; echo ^0> ^7*********** ^0AXIS ^7***********"
seta teamtoggle "vstr teamb"
seta medic ""
seta field ""
seta eng "" // To hold which team keys are binded to when you release ALT and clear number binds
seta cov ""
seta sol ""
seta setallies "set medic vstr alliesmedic; set field vstr alliesfield; set eng vstr alliesengineer; set cov vstr alliescovert; set sol vstr alliessoldier"
seta alliesmedic "team b 1 8 37;echo ^0> ^7Medic^0/^7Thompson"
seta alliesfield "team b 3 8 37;echo ^0> ^7Field Ops^0/^7Thompson"
seta allieseng1 "team b 2 8 37;echo ^0> ^7Engineer^0/^7Thompson; set alliesengineer vstr allieseng2"
seta allieseng2 "team b 2 24 37;echo ^0> ^7Engineer^0/^7M1 Garand; set alliesengineer vstr allieseng1"
seta alliesengineer "vstr allieseng1"
seta alliescov1 "team b 4 25 37;echo ^0> ^7Covert Ops^0/^7Garand; set alliescovert vstr alliescov2"
seta alliescov2 "team b 4 33 37;echo ^0> ^7Covert Ops^0/^7FG42; set alliescovert vstr alliescov3"
seta alliescov3 "team b 4 10 37;echo ^0> ^7Covert Ops^0/^7Sten; set alliescovert vstr alliescov1"
seta alliescovert "vstr alliescov1"
seta alliessol1 "team b 0 5 8;echo ^0> ^7Soldier^0/^7Panzerfaust; set alliessoldier vstr alliessol2"
seta alliessol2 "team b 0 31 8;echo ^0> ^7Soldier^0/^7MG42; set alliessoldier vstr alliessol3"
seta alliessol3 "team b 0 35 8;echo ^0> ^7Soldier^0/^7Mortar; set alliessoldier vstr alliessol4"
seta alliessol4 "team b 0 6 8;echo ^0> ^7Soldier^0/^7Flamethrower; set alliessoldier vstr alliessol1"
seta alliessoldier "vstr alliessol1"
seta setaxis "set medic vstr axismedic; set field vstr axisfield; set eng vstr axisengineer; set cov vstr axiscovert; set sol vstr axissoldier"
seta axismedic "team r 1 3 38;echo ^0> ^7Medic^0/^7MP40"
seta axisfield "team r 3 3 38;echo ^0> ^7Field Ops^0/^7MP40"
seta axiseng1 "team r 2 3 38;echo ^0> ^7Engineer^0/^7MP40; set axisengineer vstr axiseng2"
seta axiseng2 "team r 2 23 38;echo ^0> ^7Engineer^0/^7K43; set axisengineer vstr axiseng1"
seta axisengineer "vstr axiseng1"
seta axiscov1 "team r 4 32 38;echo ^0> ^7Covert Ops^0/^7K43; set axiscovert vstr axiscov2"
seta axiscov2 "team r 4 33 38;echo ^0> ^7Covert Ops^0/^7FG42; set axiscovert vstr axiscov3"
seta axiscov3 "team r 4 10 38;echo ^0> ^7Covert Ops^0/^7Sten; set axiscovert vstr axiscov1"
seta axiscovert "vstr axiscov1"
seta axissol1 "team r 0 5 3;echo ^0> ^7Soldier^0/^7Panzerfaust; set axissoldier vstr axissol2"
seta axissol2 "team r 0 31 3;echo ^0> ^7Soldier^0/^7MG42; set axissoldier vstr axissol3"
seta axissol3 "team r 0 35 3;echo ^0> ^7Soldier^0/^7Mortar; set axissoldier vstr axissol4"
seta axissol4 "team r 0 6 3;echo ^0> ^7Soldier^0/^7Flamethrower; set axissoldier vstr axissol1"
seta axissoldier "vstr axissol1"
// End of Class Selector
Just remember, the commands in your autoexec are only executed ONCE. After that they have to be called with a bind or a vstr. (so putting an echo in the middle of a class selector would only call that echo when the autoexec was loaded, unless it was inside a vstr or a bind)
Since you now know as much as me, it's only fair that you humour me and take a look at my cfg. You can download it here.
I recently noticed that if, using my config, I quit the game using ALT-0, when I re-loaded the game my keys would all be bound to their ALT-key binds, which would get annoying. Eventually (I have a slow mind) I realised that this is because the game has ALTdn activated when the quit command is given, so a simple fix to that would be to change the 'setexit' vstr to allow for this:
This makes sure the game resets the binds to their normal state before exiting the game. Pretty simple, but it took me a while to think of so I thought I should add it anyway.
Turning many cfg files into one
Often, when you are finding scripts on the internet, they will be in 2 or 3 cfg files. If you're like me, you only like having 1, so that you can easily send it to people and feel proud of your hard work. I'm now going to quickly teach you how to do this. This is the same method I used to turn the class selector that Froster posted into 1 file, and the ALT+key script.
To make these all into your autoexec you need to think about what a cfg file does. When you exec it, it does everything in it's list in order. So, we need to make a different command that does the same thing. The way we do this is the make a vstr with commands in it to do a load of commands. Here, we have 2 cfg files we are turning into 1, so we will need 2 commands that act like an exec. Let's call these "vstr cfg1" and "vstr cfg2" for now.
cfg1 will have to execute several bits of code, so we do that like this:
Hopefully you can read through that code above and see how it works. But, as you can see, you can't use extra " marks within the statements, which can complicate things. For example, if you looked at cfg2, it needs to set commands which have already in them several commands. How can your script know which commands are part of the set, and which ones are the next set when they are all separated by the same ; symbol? For example:
seta cfg2 "vsay Hello; set name ^6Daisy"; bind MOUSE1 vstr mou; set mou vstr mou1; set mou1 say ^7123; set mou vstr mou2; set mou2 say ^7321; set mou vstr mou1"
If you read through that statement above, you can see that when you exec your autoexec it will change what 'mou' means 3 times, because you have 3 "set mou ... " statements in there. The game can't tell which ones of those statements are to be executed now, and which ones are part of the binds. You could try adding extra " marks where they are needed, but the game sees them as the end " marks, which confuses it even more. If it sees:
bind 1 "do 1 thing; do another thing; bind this to do: "this; then this"; do a 4th thing"
It will think the second " is the end one, and will not know what do with the 'this; then this' part of the statement since they are outside " marks.
To solve this problem you need to stick everything inside little vstrings:
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum