Warning, /games/kmuddy/Scripting-HOWTO is written in an unsupported language. File is not indexed.
0001 KMuddy Scripting HOWTO 0002 ---------------------- 0003 0004 This document will try to teach you the basics of external scripting for KMuddy. 0005 It assumes no programming experience, though it would be helpful... 0006 0007 KMuddy scripts can be written in many different languages, both compiled 0008 and interpreted ones. For this document, I've chosen to use the Perl 0009 language, because it's easy at the beginnings, yet very powerful once you get 0010 more into it. 0011 0012 1. Basic script structure 0013 ------------------------- 0014 0015 There are more script types possible, but one that you'll probably want to 0016 use most is a script that reads output from the MUD line after line, looking 0017 for certain patterns that you instruct it of, and executing some commands 0018 after a certain text comes. 0019 This document will describe this type of script, because it's probably the 0020 most useful one. 0021 0022 The basic structure of such script looks as follows: 0023 0024 #!/usr/bin/perl 0025 $| = 1; 0026 0027 #initialization goes here 0028 0029 while (<STDIN>) 0030 { 0031 #process line here 0032 0033 } 0034 0035 0036 The first line informs the OS, that the script should be processed by perl. 0037 The second line switches output into unbuffered mode, which is needed to prevent 0038 commands from being sent to the MUD too late. 0039 Lines beginning with a #, are comments (first one is a bit special). 0040 Next, we can do some initialization (nothing for now), and the while-cycle 0041 waits for output from the MUD, reads it line after line, and processes it. 0042 (Note that this simple script never ends. More on this later.) 0043 0044 The script should be saved in a file somewhere. Preferably to a directory 0045 dedicated for your scripts. Then, set the script to be executable. This can be done 0046 using your favourite file manager, or from the console by typing 0047 "chmod +x scriptfile" (without quotes, replacing scriptfile with the name 0048 of your script). Then add the new script into KMuddy, setting name to something 0049 reasonable, executable path should point to your script. 0050 Don't modify any of the options, you won't need those in the beginning. 0051 0052 The script can be executed by typing 0053 /exec scriptname 0054 where scriptname is the name given to the script in the Edit script dialog. 0055 You can execute scripts from the input line, or from an alias/trigger, as 0056 you want. 0057 0058 0059 2. Pattern matching and command execution 0060 ----------------------------------------- 0061 0062 The most important feature of a script is the ability to parse text coming 0063 from the MUD. The most common way of doing this is by using regular 0064 expressions. I'm not giong to explain regexps here, refer to KMuddy 0065 documentation for a documentation about regexps and back-references. You may 0066 also want to read some more advanced regexp documentation somewhere, once 0067 the basics described in KMuddy's manual are not sufficient for you, but these 0068 basics should be sufficient for now. 0069 0070 As a simple example, let's say that you want to send "eat bread", whenever 0071 "You are hungry." comes from the MUD. This could be done via triggers, but 0072 this can also be the basis of more advanced scripting. :) 0073 0074 #!/usr/bin/perl 0075 $| = 1; 0076 while (<STDIN>) 0077 { 0078 if (/You are hungry\./) 0079 { 0080 print "eat bread\n"; 0081 } 0082 } 0083 0084 So what does this do? 0085 The first part is the same as the basic script mentioned above. 0086 The line beginning with 'if', takes the line received from the MUD and compares it with 0087 the regular expression given in between the '//' characters (note that the 'period' has to 0088 be protected by a backslash, because a 'period' is a special character of regular 0089 expressions). 0090 When the corresponding text arrives, the command after 'if' is executed - in our 0091 case it's the print command. This command prints something to the standard 0092 output of the script, which is caught by KMuddy and sent to the MUD. 0093 The \n sequence is very important here, it represents a newline. It is 0094 equivalent to pressing the ENTER key by the script, so it needs to be 0095 given after every command that your script wants to send. 0096 The {} sequence needs to be written after every if-command (C programmers beware) 0097 Finally, all commands need to be terminated with a semicolon ;. ('if' and 0098 'while' and the '#!/usr/bin/perl' lines are not commands, but structural blocks) 0099 0100 Note that multiple commands can be executed as well: 0101 0102 if (/You are hungry\./) 0103 { 0104 print "eat bread\n"; 0105 print "say I ate bread.\n"; 0106 } 0107 0108 The indentation is not required, perl will understand it anyway, but it makes 0109 your script more readable. 0110 0111 If you've read the regexp docs, you know that the pattern is not good, because 0112 it looks for that line everywhere, including the middle of the sentence, and 0113 that is not good. So, we modify it a bit: 0114 0115 if (/^You are hungry\.$/) 0116 0117 Now it's correct :) 0118 0119 0120 3. Multiple matching blocks 0121 --------------------------- 0122 0123 The first script was nice, but not very useful. A trigger can do the same thing. 0124 Now, instead of emulating one trigger though, we'll learn how to emulate two 0125 or more of them. Also, we'll learn how to make it so that only some matching 0126 occurs if some other script was successful and/or unsuccessful. 0127 0128 So, let's assume that you want to write an idiotical (read: example) script 0129 that will make you say nuts whenever a word "nuts" comes from the MUD, or say 0130 "apples" when the word apples comes, but only if that word wasn't said by 0131 another player (to prevent the infinite loop). 0132 So, the script looks like this (the basic structure is omitted, only 0133 things inside while() are written: 0134 0135 if (/say/) 0136 { 0137 # do nothing in this case 0138 } 0139 else 0140 { 0141 if (/apples/) 0142 { 0143 print "say apples\n"; 0144 } 0145 if (/nuts/) 0146 { 0147 print "say nuts\n"; 0148 } 0149 } 0150 0151 Execution happens from top to the bottom, so the apples and nuts get 0152 looked for in this order, and both matching occurs regardless on their 0153 results. The print commands only get executed if their respective test was 0154 successful, obviously. 0155 0156 New construction in this example is the "else" part. This gets executed if 0157 the condition tested in its associated "if" is false. In our case, it's 0158 false if the matching says no, it's not there. 0159 0160 Since the {} block is required after if/else even if there's only one command, 0161 you won't have the problems with a misplaced 'else'. 0162 0163 Another thing to remember is that if you want to put another condition immediately 0164 after else, you can't use if(){} else if (){}. You need to use either: 0165 0166 if (foo) {x;} 0167 else { 0168 if (foo2) 0169 {x;} 0170 } 0171 0172 or use the "elsif" statement: 0173 0174 if (foo1) {x1;} 0175 elsif (foo2) {x2;} 0176 0177 and so on... 0178 0179 0180 3. Script termination 0181 ---------------------- 0182 0183 Tired of having to terminate your scripts by hand? Well, there's a better 0184 way - the exit statement. 0185 Example tells it all (only contents of while are included) 0186 0187 if (/text1/) 0188 { 0189 print "oh yeah\n"; 0190 } 0191 if (/this text quits the script/) 0192 { 0193 print "say script terminated\n"; 0194 exit; 0195 } 0196 0197 0198 4. Variables 0199 ------------ 0200 If you know variables in KMuddy, you know the standard variables in perl as 0201 well (but there's more than just those). So, instead of explaining variables 0202 again here, I refer you to the appropriate section of KMuddy Handbook. 0203 0204 An important thing to remember is, that while the 'principles' of variables are 0205 the same for KMuddy variables and script variables, they are not the same. 0206 Variables defined in one script cannot be used in another script, and they 0207 cannot be used in KMuddy either. Variables defined in KMuddy cannot be used 0208 as you would use normal script variables. KMuddy's variables are accessible 0209 via the variable server, but there's no client part for it written yet that 0210 could be used in perl scripts - only C/C++ client exist as of now. So, until 0211 this changes, there's no way you could access KMuddy's variables off your 0212 scripts (only that you can send /set commands and friends). 0213 0214 Okay, now that you understand what variables are, let's have a look at how to 0215 use them in perl. 0216 0217 Example (whole script this time): 0218 0219 #!/usr/bin/perl 0220 $ != 1; 0221 my $var = 0; 0222 print "search herbs\n"; 0223 while (<STDIN>) 0224 { 0225 if (/^You have found some herbs.$/) 0226 { 0227 $var = 0; 0228 print "search herbs\n"; 0229 } 0230 else 0231 { 0232 if (/^You have found nothing.$/) 0233 { 0234 $var = $var + 1; 0235 } 0236 } 0237 if ($var == 3) { 0238 exit; 0239 } 0240 } 0241 0242 We're getting into more interesting stuff here, this script is already quite 0243 usable. :-) Explanation of some lines: 0244 0245 my $var = 0; 0246 This statement defines a new variable called $var and assigns zero to it. 0247 It's best to declare all variables before the "while" cycle for now. After 0248 you become more skilled, you may want to declare them elsewhere, but there are 0249 some things that one must be aware of, so better stay with this method for now. 0250 0251 $var = 0; 0252 Here we take an existing variable of $var and assign zero to it. 0253 (Note that this line (without the "my"), could have been used in the above 0254 described line as well, but it just isn't a good programming practice :) 0255 0256 $var = $var + 1; 0257 Write this in your Math course and you're in for trouble. :) But it's okay 0258 here, since it's not an equation. What this line does is that it takes 0259 everything to the right of the "=" sign, evaluates it (in our case, it takes 0260 what's stored in $var and adds 1), then it stores the result in the variable 0261 on the left, $var in our case. 0262 Note that typing $var2 = $var + 1; won't modify the $var variable. :) 0263 0264 if ($var == 3) 0265 Until now, we've only used the // operator (if (/blah/)), but the if statement 0266 can do much more. In this case, we are checking whether $var 'is equal to' 3. If 0267 it is, the command (or all commands inside these {}) will be executed, just as it 0268 was when the pattern matched. 0269 Note that we're comparing with "==", not plain "=", to avoid confusion, as the 0270 interpreter doesn't know whether we want to assign or to compare (okay, it's 0271 obvious in this case, but it wouldn't be in more complex expressions). 0272 In addition to "==", you can also use <, <=, >, >= and != for comparison. 0273 The last one '!=' means not-equal-to. 0274 0275 Oh, in case you haven't figured it out by now, the script calls the 0276 "search herbs" command again and again, until it fails three times in a row. 0277 Remember this structure, you'll probably use it quite often :) 0278 0279 6. Finite state machines 0280 ------------------------ 0281 We are now getting to the topic that is of great importance for any bigger 0282 script(s). Without getting into too much detail, a finite state machine (FSM) 0283 is a device that has an input and a set of states. One of these states is 0284 initial, some of them (maybe none) are terminating states. What the machine 0285 does is that it reads input, and depending on what it gets and on the current 0286 state, it may change its state. 0287 Actually, our scripts are more powerful than this, because we can have 0288 multiple states and we also write some output, but this abstraction will do 0289 for now. 0290 0291 A bigger example of how all this can be used: 0292 0293 Let's assume that you want to write a script that converts all your money 0294 to more valuable currencies. Let the MUD's commands/output be as follows: 0295 0296 money - command to view your money 0297 You are broke. - when ya have nothing 0298 You have 54 bronze, 17 silver, 20 gold and 4 platinum coins in your pockets. 0299 - output of the 'money' command when you aren't broke. 0300 convert XX bronze to silver - command convert bronze to silver. 0301 Next, say that the coin values are as follows: 0302 1 platinum = 5 gold 0303 1 gold = 6 silver 0304 1 silver = 10 bronze 0305 And assume that you need to provide exact numbers for conversion, as the 0306 bank doesn't return change. 0307 Also say that you don't want to exchange more than 100 coins a once, 0308 because you'd have to pay a fee for larger amounts. 0309 0310 Of course, this is just an example that would probably work nowhere "as is", 0311 as every MUD is different. But it's sufficient to show you how things work. 0312 0313 So, we'll have the following states: 0314 0315 1. exchanging bronze -> silver 0316 2. exchanging silver -> gold 0317 3. exchanging gold -> platinum 0318 4. done 0319 0320 Here we'll be proceeding in linear order, 1->2->3->4. More complex order 0321 is possible too, including unpredictable ones (changing state to more 0322 possible ones depending on what the MUD sends), depending on what you want. 0323 0324 So, the script should look like this (couldn't test anywhere, so it may 0325 contain bugs...) 0326 0327 #!/usr/bin/perl 0328 $| = 1; 0329 my $state = 1; 0330 my $amount; 0331 #start the loop 0332 print "money\n"; 0333 while (<STDIN>) 0334 { 0335 #dot needs to be protected with \, remember? :) 0336 if (/^You are broke\.$/) 0337 { 0338 exit; 0339 } 0340 #exchanged something - start another round 0341 if (/^You exchange/) 0342 { 0343 print "money\n"; 0344 } 0345 #only parse the lines with correct prefix/suffix 0346 if (/^You have .* coins in your pockets\.$/) 0347 { 0348 if ($state == 1) 0349 { 0350 #how much bronze? stores number in back-reference 0351 #note that we don't have to check if the line really contains the 0352 #correct prefix/suffix, as we did that already 0353 if (/(\d+) bronze/) 0354 { 0355 #closest smaller number divisible by 10 0356 $amount = $1 - $1 % 10; 0357 if ($amount > 100) { $amount = 100; } 0358 #too few coins, proceed with silver 0359 if ($amount == 0) { 0360 $state = 2; 0361 } 0362 else 0363 { 0364 print "exchange $amount bronze for silver\n"; 0365 } 0366 } 0367 else { 0368 #none, proceed with silver 0369 $state = 2; 0370 } 0371 } 0372 #IMPORTANT: we'll immediately process first silver, if there's not anough 0373 #bronze (because $state is now 2). This behaviour may not always be correct. 0374 #In such case, use "else" statement to prevent this behaviour. e.g. 0375 #if ($state == 1) {...} else if ($state == 2) {...} else if ($state == 3) {...} 0376 if ($state == 2) 0377 { 0378 if (/(\d+) silver/) 0379 { 0380 $amount = $1 - $1 % 6; 0381 if ($amount > 100) { $amount = 100; } 0382 if ($amount == 0) { 0383 $state = 3; 0384 } 0385 else 0386 { 0387 print "exchange $amount silver for gold\n"; 0388 } 0389 } 0390 else 0391 { 0392 $state = 3; 0393 } 0394 } 0395 if ($state == 3) 0396 { 0397 if (/(\d+) gold/) 0398 { 0399 $amount = $1 - $1 % 5; 0400 if ($amount > 100) { $amount = 100; } 0401 if ($amount == 0) { 0402 $state = 4; 0403 } 0404 else 0405 { 0406 print "exchange $amount gold for platinum\n"; 0407 } 0408 } 0409 else { 0410 $state = 4; 0411 } 0412 } 0413 #state 4 - terminate the script 0414 if ($state == 4) 0415 { 0416 exit; 0417 } 0418 } 0419 } 0420 0421 0422 Kinda long, isn't it? :) I believe it shouldn't be difficult to understand 0423 how this script works. 0424 0425 7. Future reading 0426 ----------------- 0427 After you gain some experience with all this stuff, you may want to go for 0428 some more advanced stuff. I would then recommend reading 0429 man perlintro 0430 which contains almost everything mentioned here, and much more. 0431 Then, you may want to go to 0432 man perl 0433 which contains a list of everything that could be of interest :) 0434 0435 0436 So, this is the end of this HOWTO. I hope you liked it, and wish you happy 0437 scripting :) 0438 0439 0440 / Tomas Mecir 0441 kmuddy@kmuddy.net 0442 0443