Got a question that the wiki doesn't answer? Ask on the forum (preferred), or join us on IRC.

BeastNode

CommandHelper/Staged/Advanced Guide

From EngineHub.org Wiki
Jump to: navigation, search


If you are wanting to do more dynamic things other than variables, CommandHelper allows you to do this through the use of Turing complete language, MethodScript. If you aren't familiar with any type of programming, you may wish to find resources on languages like Java and PHP. The language mirrors both languages in certain ways, as well as introducing its own methodologies.


Functions

Functions allow for very dynamic scripts to be run. There are many defined functions, including functions that provide control flow functionality. For the full list of functions, see the API. A function is identified by a literal string, followed by parenthesis. So in func(), "func" is the name of the function, and "(" and ")" begin and end the function argument list (in this case, there are no arguments being passed in.) Functions can have zero, one, or two or more arguments passed to them. In the case of two or more arguments, each argument is separated by a comma. For instance:

1   funcWithNoArgs();
2   funcWithOneArg(1);
3   funcWithTwoArgs(1, 2);
4   funcWithThreeArgs(1, 2, 3);


Comments

Comments are a useful way to mark up your code for humans to read later. (With one exception) comments are ignored by the compiler, and you are free to put whatever information you wish in a comment. There are 4 ways to comment code.

The # and // symbols are identical. They are line comments. When either is encountered, the remainder of the line is ignored by the compiler. // is preferred over #, however # is not deprecated, nor will it ever be. The only exception to this preference is when using a hashbang for cmdline code.

Block comments start with /* and end with */ This causes the compiler to ignore everything within the comment block, including newlines.

Smart comments are like block comments, but start with /** (two asterisks) instead. They are currently unused, but are reserved for future use. They are used for documentation generation functionality.

01   code(); # line comment
02   code(); // line comment
03   code();
04   /*
05    *
06    * block comment
07    *
08    */
09   
10   /**
11    * Smart comment. This would generally proceed a procedure or variable declaration,
12    * or similar structure. Note that the * at the beginning of this line is not necessary,
13    * however it serves to help the reader more easily see that this is also part of the comment block.
14    */


Data Types

In a script, there are several types of data. The language is currently loosely typed however, so the string '2' is equivalent to the integer 2, is equivalent to the double 2.0, is equivalent to the boolean true. Values are cast just before they are used. Note that sometimes data cannot be cast, for instance the string 'string' cannot be cast to a number, so a runtime exception will be thrown if a function expects a number, and is given that. Also, arrays are not able to be cast into other data types, but can contain values of any data type.

  • boolean - Created with the "true" or "false" keywords: true
  • int - Any number that doesn't have a decimal point: 2
  • double - Any number that has a decimal point: 2.0
  • string - Any set of characters surrounded by single quotes or double quotes: 'string', "also a string".

\u0000 inside a string will allow for arbitrary unicode characters to be inserted into a string, and \n is a newline. \\ (double slash) inserts a literal slash, and \' will insert a literal quote.

  • array - An array of any other datatypes, including other arrays. Created by using the function array
  • null - Created with the "null" keyword: null
  • void - Some functions return void, which is actually a datatype. When viewed as a string, it is equivalent to an

empty string. Cannot be created directly.

  • ivariable - An ivarible (or simply a variable) is a variable that can be defined and used from within the script.

Constant variables ($var), are assigned by the user at command runtime, and are technically constants as far as the rest of the script is concerned. IVariables can be defined by the script writer and assigned various values that can change throughout the script running. To define and use an ivariable, use the assign() function, or the = operator. If an ivariable is used without first being defined, the value of the variable will be 0, 0.0, '', false, or null, depending on how it is used. Most functions use the value in the ivariable without caring that it is an ivariable, but it is possible that a function requires that a certain argument be an ivariable, such as the for() function.

Exception Handling

Sometimes a script may sometimes cause an error that could be anticipated. Instead of just having the script die, it is possible to catch these errors, and perform alternate functionality. MethodScript mirrors the functionality of languages like PHP and Java with exception handling. For more information about exception handling in MScript, see this page. For a more general discussion on exception handling, see this page on Wikipedia.

main.ms, auto_include.ms, and aliases.msa

Each of these files serve a separate purpose. main.ms is run at server startup, only once. Typically, you use this to register bound events, using the bind() function, or anything else you want to run only once. Keep in mind, this is re-run when you /reloadaliases, but bound events and intervals and such are stopped, so you won't have multiples of anything. main.ms uses pure MethodScript, that is, it has a slightly different syntax than aliases.msa. In aliases.msa, each alias is defined, along with a snippet of MethodScript. For instance, in the alias,

1   /test [$command=''] = >>>
2       console($command);
3   <<<


the /test [$command=''] = >>> <<< part is the alias markup, and is not actual MethodScript, that is, the symbols and characters follow different meanings than inside the alias definition. In this example, console($command); is pure MethodScript.

aliases.msa is where your aliases go, and each alias is a fully separate compilation unit, in fact, even a compile error in one alias will not usually interfere with the other aliases.

The auto_include.ms file is also a pure MethodScript file, and it is as if in each execution unit, it is automatically include()ed for you. There are a few different ways to make an execution unit. The first way is to create a new alias. Each alias is it's own execution unit, and uniquely to the aliases, each alias is also it's own compilation unit. A bound event handler is another execution unit, and the entirety of main.ms is an execution unit. set_interval and set_timeout are also separate execution unit, as well as execution queue elements, and others.

So, what is an execution unit? It is a unit of code that gets run, from top to bottom. You can think of it as an insertion point to your code, or places that will start up your code. An execution unit is often times made up of several parts, so let's create a multifile example, which demonstrates a few execution units.

In auto_include.ms:

1   proc(_my_proc){
2       console('In _my_proc');
3   }


In main.ms:

1   bind(some_event, nullnull@event,
2       _my_proc();
3       console('In some_event');
4   )
5   console('Finished binding all events in main.ms');


In aliases.msa:

1   /test $alias = >>>
2       _my_proc();
3       console('In /test $alias');
4   <<<


Here, we have created 3 separate execution units. main.ms and aliases are implicitly created, simply by their existence, but here we have also created an event handler, which will get run when some_event is triggered. main.ms is triggered upon server startup (and /reloadaliases) and aliases are triggered when a command is run. When this script is compiled, you can essentially visualize the execution units as ending up like this: (with some code removed to make things more readable)

First execution unit, main.ms:

1   proc(_my_proc, ...);
2   bind(some_event, nullnull@event, <CREATION OF EXECUTION UNIT>);
3   console('Finished binding all events in main.ms');


Second execution unit, the /test $alias alias:

1   proc(_my_proc, ...);
2   _my_proc();
3   console('In /test $alias');


Third execution unit, the event handler created in main.ms:

1   proc(_my_proc, ...);
2   _my_proc();
3   console('In some_event');


In this simple example, you can think of each execution path as a line through your code (in reality, it's a tree, but if that doesn't make sense to you, ignore that for now). That line follows very specific rules based on code structure, so how you lay out your code is important to be able to visualize.

reloadaliases

The built in /reloadaliases command is used to reload just CommandHelper, though /reload will also reload CommandHelper. However, using the reloadaliases command can allow for finer grained control of what all gets reloaded. By default, the command reloads the following things about CommandHelper:

  • Scripts - Any changes that you have made to any scripts will be reloaded and applied. Main files are re-run,

and any new commands are re-registered.

  • Globals - Global values, set with export() are cleared out.
  • Tasks - Any tasks set with set_interval() or set_timeout() are cleared.
  • Execution-Queue - Any tasks queued up with the queue_* family of functions is cleared.
  • Persistance-Config - Any changes to the persistance.config file are reloaded
  • Profiler - The profiler.config file is reloaded
  • Extensions - Extensions are reloaded

You can actually reload these sub-modules individually if you want, by passing parameters to the command. There are two modes, whitelist, or by default, blacklist. In blacklist mode, all modules get reloaded except the modules that are blacklisted, which aren't reloaded. In whitelist mode, only the specified modules are reloaded. You use the underlined letter to refer to that specific module. For instance, if you want to reload everything but leave the exported variables and execution queue alone, you can run /reloadaliases -ge. If you ONLY want to reload tasks, you can run /reloadaliases --whitelist -t. Note that reloading individual modules isn't normally encouraged, because it can put your server in an inconsistent and unreproducable state if you aren't careful. Running /reloadaliases by itself (which reloads everything) is recommended. You can also run /reloadaliases -h for the usage instructions and long options list.

Scripting Examples

Here are a few more complex examples to get you started on how to use the advanced features of CommandHelper. Because of the Turing completeness of the plugin, it is possible to do far more advanced things.

01   /**
02    * Better tp. If "/tp name" is used, it implies "/tp thisplayer name"
03    * but if "/tp name1 name2" is used, it implies "/tp name1 name2"
04    */
05   /tp $p1 [$p2=''] = >>>
06      @player = $p1;
07      if($p2 == ''){
08         @player = player();
09      }
10   
11      @playerTo = $p2;
12      if($p2 == ''){
13         @playerTo = $p1;
14      }
15      run('/tp ' . @player . ' ' . @playerTo);
16   <<<
17   
18   /**
19    * Sets the time on the server, and uses english words to set the time
20    * If the english word isn't "day" or "night", it uses the number
21    * Note that the equals operator returns a true value if the two
22    * parameters are the same, and the if/else if chain runs only
23    * one of the code branches, or none, depending on the true/false
24    * value of the arguments.
25    */
26   /time $time = >>>
27      @time = $time;
28      if($time == 'day'){
29         @time = 1;
30      } else if($time == 'night'){
31         @time = 13500;
32      }
33      run('/time set '.@time);
34   <<<
35   
36   /**
37    * Give the player 64 each of gold tools
38    * Note that the data_values function uses the enum values of
39    * the material, and we are taking advantage of the autoconcatenation
40    * principal in the first two runs, but not the third.
41    */
42   /kit gold = >>>
43      run('/give' player() data_values('gold_pickaxe') 64);
44      run('/give' player() data_values('gold_spade') 64);
45      run('/give ' . player() . ' ' . data_values('gold_axe') . ' 64');
46   <<<
47   
48   /**
49    * Shows help information
50    * This demonstrates how to use die() and msg(). They work basically the same, except
51    * die() kills the command if evaluated. They both send a message to the player.
52    */
53   /help [$cmd=''] = >>>
54      if($cmd == ''){
55         die('Do you need help?');
56      } else if($cmd == 'commandhelper'){
57         msg('The best plugin ever!');
58      } else {
59         die('Invalid command specified!');
60      }
61   <<<
62   
63   // Simple hello world scripts
64   
65   /hello world console = console('Hello Console!');
66   /hello world player = msg('Hello Player!');


Symbol Table

In your scripts, there are a few special symbols that are not treated as literals. In the event of a compiler error, it may be helpful to know what each symbol is called. These are as follows:

Symbol Name Meaning
# or // comment Denotes that the rest of this line is a comment
/* ... */ block comment Everything inside this (newlines included) is a comment. The only thing you can't have inside a block comment is a */

(because that ends the comment)

= opt_var_assign or alias_end If inside an optional variable [$opt='val'], it's an opt_var_assign. Otherwise, it's a alias_end
[ and ] lsquare_bracket and rsquare_bracket Denotes that this variable is optional, if included on the left side of an alias, or accesses an element in an array

if used on the right.

, comma Separates arguments in a function
( and ) func_start and func_end The start and end of a function. If a literal proceeds the func_start symbol, it is called func_name
New Line newline An 'enter' in the file
>>> and <<< multiline_start and multiline_end Starts and stops a multiline construct
/ command This symbol followed by a literal denotes a command
\ separator Separates each macro command
$... variable When a single dollar sign, it is final_var, otherwise variable
@... ivariable This is a variable that can be defined and then used on the right side
...: label This is a label for commands
All other characters lit Anything not defined above is a lit

Note that a lit and string are treated the same, however special characters must be put in a string to be treated as literal character. Technically all other special characters are treated as literals, but to make your script compatible with future versions, you must put any non-word character inside a string.

Note that string concatenation happens automatically (known as autoconcatenation). Let's take the following example:

1   if(@i == 0){
2        run('/run' 'this' 'command');
3        die('The' 'command' 'has' 'been' 'run');
4   } else {
5        run('/run' 'another' 'command');
6   }


In the first run function, we see that '/run', 'this', and 'command' are all technically separate arguments, but because they are not separated by commas, operators, or other symbols, they are automatically concatenated together, using the sconcat() function, essentially yeilding: '/run this command', as you would expect. This is also the behavior exhibited by a simple alias that uses non-strict formatting: /cmd = /real command. '/real' and 'command' are automatically sconcatenated together, and then run.

Continued Learning

Many of the scripting concepts are addressed in greater depth in the Learning Trail, shown below. The MScript topics are of great value to go through, and they build off what you have already learned in this tutorial. What is provided in this lesson should be enough to get you started with basic script writing, so start trying to apply these concepts to your own scripts, and continue going down the learning trail!



Navigation menu