Skip to content
eaxs edited this page Dec 30, 2011 · 8 revisions

Creating a module is pretty easy and even if you didn’t understand how the framework works, I advise you to just follow this tutorial step by step and eventually you’ll get the AHA!-effect. Learning by doing! Before you can start coding though, you must have the compiler installed and ready to use. Once you have that, continue reading.

The goal of this tutorial is to create a basic module which has a function that sends a welcome message to a specific player on the server. It will be more complex than just a simple “SendMessage” command ;)

With that in mind, let’s get started!


Module structure setup

The compiler reads all the available modules from the “modules” directory. So this is where you have to start: Navigate to the location on your PC where you installed the compiler, into the “modules” folder. As you can see there are already a few modules in there.

  1. We are going to call our module “Hello”. So now you have to create a new folder called “Hello” right next to the other module folders.
  2. Inside the “Hello” folder, we need two more folders called “methods” and “vars”; create them!
  3. And finally, we need a file called: module.ini. This file is needed for the compiler.

If you did everything right, you should now have the following structure in place:

  • /modules/Hello/
  • /modules/Hello/methods/
  • /modules/Hello/vars/
  • /modules/Hello/module.ini

Module ini file

Before we head on to the methods and variables, let’s start with the easiest and most important part of the module. The module ini file is used by the compiler to identify the module and its underlying methods. It includes everything from names to descriptions and parameters; everything must be registered here first. You’re essentially defining what’s inside the module and what it does.

Go ahead and open the blank file, then copy the following code template into it. Note that the lines starting with a semi-colon (;) are comments – please read them!


; Basic package information
;     Note: You may use "\n" in the description to create a new line
; 
;     Very important: All information entered in this file is case sensitive.
;     Make sure you write everything correctly! Case sensitive means that
;     "Hello" is not equal "hello".
; 
[module]
name    = "Hello"
desc    = "Module to send a custom message to a specific player."
author  = "eaxs"
version = "1.0.0"
date    = "12-29-2011"
website = "https://github.com/eaxs/K2-Scripting-Framework"


; Module dependencies
;     List the names of the modules which are used by this module
;     You dont have to list K2 and Array here as they are part of the core
; 
[dependencies]
module[] = ""


; Module method list
;     List all public methods including description, except magic functions.
;     Magic functions are: "__construct", "__exec" and "__destruct".
;     Please make sure you have description for each method! You can use "\n"
;     to create a new line.
;
;     Example:
;         name[] = "myMethod"
;         desc[] = "This is my method"
; 
[methods]
name[] = "message"
desc[] = "Sends a message to a player"


; Module options list
;     For each of your public methods, list the available options
;     including name, description, type, and required.
;     For each method, you have to create a new section.
; 
;     Example:
;         [opt:myMethod]
;         name[] = "-p"
;         desc[] = "This is the -p option and is not optional"
;         type[] = "string"
;         req[]  = "0"
;         ... repeat ...
;  
[opt:message]
name[] = "message"
desc[] = "The message to send"
type[] = "string"
req[]  = "1"

name[] = "-p"
desc[] = "The player id to which to send the message to"
type[] = "int"
req[]  = "1"


; Config variables
;    List all config variables that are used in your module including:
;    name, description and default value
;    You may use "\n" in the description to create a new line
;
[config]
name[] = "hello_checkdouble"
desc[] = "Set this to 0 if you want to avoid sending the message twice"
value[] = "1"

The code in this file follows the INI-file guidelines and has a number of sections for grouping:

  • [module]
  • [dependencies]
  • [methods]
  • [opt:x]
  • [config]

If you look at the code, you’ll see that I’ve already registered a method called “message” for you. The method takes two parameters: “message” and “-p” (see [opt:message]). Both of these parameters are mandatory (req[] = “1”).

If this doesn’t make any sense to you now, maybe this will help (we’ll get to that more in-depth later): In the Savage 2 console or in your script code, you can call the method like this:

ExecScript K2 Hello.message "Welcome to my server!" -p 0

I’ve also created a configuration variable for you: hello_checkdouble. We will use this later in the tutorial to set whether to avoid sending the same message to the same person twice or not. All config variables are available just like regular script variables. The example below will print out “1” in the console:

Echo #hello_checkdouble#

Private variables

Now that we have the module defined, lets continue setting up our private variables by creating a file called “priv.cfg” in the “vars” folder. Once you have that, open the file and copy the following code:


#$# hello_message_sent "hello_message_sent";
#$# hello_message_sent_datatype "array";
#$# hello_message_sent_idx -1;

This code creates 3 variables to form an empty array object called “hello_message_sent”. Normally we would create arrays using the “Array.new” method (this is part of the Array module); but due to technical reasons we can’t call this method in this file. So we have to create the object manually. An array is essentially a variable that holds a list of values. For example a list of fruits: Apple, Banana, Orange, etc.. We need this particular array “hello_message_sent” later in our code to check if we are sending a message no more than once to a player.

Note that variables you declare here will be reset after each map change. Also note that you should prefix your variables with “module_method_” (“hello_message_” in this example) to prevent conflicts with other modules!


Method code

Now comes the biggest part, the method code! If you haven’t understood much of the tutorial yet, this is the part where everything comes together and hopefully makes sense – so read carefully! If the code looks odd to you, note that I’m using shorthand variables (the #$# and #$e#’s).

Navigate to the “methods” folder of your Hello module and create a new file called “message.cfg”. Then open the file and copy the following code (will be explained afterwards):


// Check if the parameter "-p" is given
if #StringLength(|#GetScriptParam(-p)|#)# #VOID#;
else "#$e# #self# log \"Hello::message: Player not specified\" -e 1; #@# \"Hello::__destruct{\"";


// Get the player name
#$# hello_message_name #GetClientNameFromClientNum(|#GetScriptParam(-p)|#)#;


// Check if the player name is empty
if #StringLength(|#hello_message_name|#)# #VOID#;
else "#$e# #self# log \"Hello::message: Player does not exist\" -e 1; #@# \"Hello::__destruct{\"";


// Replace the placeholder (%name%) in the message with the actual player name
#$e# #self# srep #GetScriptParam(Hello.message)# -s %name% -r #hello_message_name# -v hello_message_txt


// Check for double message?
if #hello_checkdouble# #VOID#;
else "#@# \"Hello::message__checkpassed{\"";

#$# hello_message_id #hello_message_txt#_#GetScriptParam(-p)#


// Search the message id
#$e# #self# Array.find hello_message_sent -s #hello_message_id# -v hello_message_found


// Log a warning message and abort the sending if the message has already been sent
if [hello_message_found >= 0] "#$e# #self# log \"Hello::message: This message has already been sent to \"#hello_message_name# -w 1; #@# \"Hello::__destruct{\"";


// Check passed OK. Now send the message
@Hello::message__checkpassed{
#># #GetScriptParam(-p)# #hello_message_txt#;


// Add the message to sent stack?
if #hello_checkdouble# #@# "Hello::message__store{";
else "#@# \"Hello::__destruct{\"";


// Store the message
@Hello::message__store{
#$e# #self# Array.add hello_message_sent -d0 #hello_message_id#;

This is the entire code we need for our method to work. Now lets go through it step by step with some explanations on what is going on there.

// Check if the parameter "-p" is given
if #StringLength(|#GetScriptParam(-p)|#)# #VOID#;
else "#$e# #self# log \"Hello::message: Player not specified\" -e 1; #@# \"Hello::__destruct{\"";

The 2nd and 3rd line is an if…else statement. In the second line, we check if the parameter “-p” is given. Remember we declared it as mandatory in the ini-file. To do this, we use the StringLength function which is a native K2 function. If the parameter length (meaning that the value 0 would still have a length of 1) is not empty, then do nothing (#VOID#). #VOID# is simply a variable that does nothing and is defined in the K2 core module.

In the 3rd line – the “else” part – we call the K2 core method “log” to log the error if “-p” is not provided. After that, we cancel the script by going to the class destructor Hello::__destruct{. Note that the 3rd line holds two separate statements, separated by a semi-colon (;). The first statement logs the error until the semi-colon and the second statement jumps to the class destructor. Note that I’m using #self# instead of “K2” to call the log method. This is recommended to keep the code as dynamic as possible (should someone decide to not call the world trigger “K2”).

// Get the player name
#$# hello_message_name #GetClientNameFromClientNum(|#GetScriptParam(-p)|#)#;

Here we get the player name from the provided client id using the K2 native GetClientNameFromClientNum function. The result is stored in the variable hello_message_name. Note how I keep using the pattern “module_method_” for the variable name.

// Check if the player name is empty
if #StringLength(|#hello_message_name|#)# #VOID#;
else "#$e# #self# log \"Hello::message: Player does not exist\" -e 1; #@# \"Hello::__destruct{\"";

This part is very similar to the first code block. This time I’m double-checking if the player name exists and cancel the script if it doesn’t.

// Replace the placeholder (%name%) in the message with the actual player name
#$e# #self# srep #GetScriptParam(Hello.message)# -s %name% -r #hello_message_name# -v hello_message_txt

I’ve built in the ability to include the player name in the message. To do this, the message must contain a placeholder for the name (name). srep is a method of the K2 core module and means “search and replace”. The resulting string is stored in the callback variable hello_message_txt.

// Check for double message?
if #hello_checkdouble# #VOID#;
else "#@# \"Hello::message__checkpassed{\"";

Here we decide whether to skip the double-sending check or not. This feature may be useful if you don’t want to greet a re-joining player again. Remember the config variable hello_checkdouble that was declared in the ini-file? I’m using it in the second line. Notice the label naming pattern in the 3rd line!

#$# hello_message_id #hello_message_txt#_#GetScriptParam(-p)#

Here I generate a message id by concating the message text and the player id. It will be used in the next code block to find out whether the current message has already been sent or not.

// Search the message id
#$e# #self# Array.find hello_message_sent -s #hello_message_id# -v hello_message_found

Remember the empty array we created in the priv.cfg file? In this line we’re searching the message id we just generated by using Array.find. The search result will be the stored in the callback variable hello_message_found. The result is the index number of the value. If the value has not been found, the value of the callback var will be -1.

// Log a warning message and abort the sending if the message has already been sent
if [hello_message_found >= 0] "#$e# #self# log \"Hello::message: This message has already been sent to \"#hello_message_name# -w 1; #@# \"Hello::__destruct{\"";

If the message has already been sent once, log a warning message and cancel the script.

// Check passed OK. Now send the message
@Hello::message__checkpassed{
#># #GetScriptParam(-p)# #hello_message_txt#;

The 2nd line is a label we need if the config var hello_checkdouble is 0 so we can skip the checks above. The third line sends the message to the player.

// Add the message to sent stack?
if #hello_checkdouble# #@# "Hello::message__store{";
else "#@# \"Hello::__destruct{\"";

Here I’m checking if the config var hello_checkdouble is 1, then we must store the current message id in the array so that we know the message has already been sent. If the config var is set to 0, we cancel the script in line 3.

// Store the message
@Hello::message__store{
#$e# #self# Array.add hello_message_sent -d0 #hello_message_id#;

This is where the message id is added to the hello_message_sent array. Remember, that’s the array we setup in the priv.cfg file. After that, the code is complete and will stop on its own.


In Action!

Now you’ll want to see your first module in action I presume. If you open up the compiler in your browser and if the “Hello” folder is in the “modules” folder, then you should now see it in the compiler list. Select it and compile the script. Then proceed with with install instructions provided by the compiler.

Once you have all that, launch Savage 2 and start a local match with the map that has the K2 script. When you’re in the game, open the console (CTRL+F8) and type:

#$e# K2 Hello.message "hi there" -p 0

You should now see a message in the chat that says “hi there”. If you don’t, you probably did something wrong during this tutorial :(

If you got the message, congratulations on your first success! You can try to get your name into the message by typing:

#$e# K2 Hello.message "my name is: %name%" -p 0

You can also see tracing in action if you like. Tracing keeps trace of most K2 script calls. This is tremendously helpful when encountering bugs in the code. To do this, type these two commands to enable debugging and tracing:

#$# k2_cfg_debug 1
#$# k2_cfg_trace 1

Now type again (you should not receive another chat):

#$e# K2 Hello.message "my name is: %name%" -p 0

Then type:

#$e# K2 tracedump 1

Why this is worth the effort

Going through this tutorial has probably consumed a good bit of your time and you may be left wondering if this is worth learning and practicing in-depth. I can tell you though (granted, I’ve created this framework) that once you get over the learning curve, you’ll be able to create modules and methods within minutes.


The framework provides a lot of useful features and makes code sharing and re-using so much easier across different scripts and projects. Furthermore it has some built-in mechanics to fight some of the K2 quirks. For example, the framework automatically protects against concurrent script calls (often causing game crashes or variables being overwritten mid-processing) and has great debugging tools built-in.

Clone this wiki locally