Difference between revisions of "Writing Advanced Modules In C"

From ASSS Wiki
Jump to: navigation, search
m (fixed spacing, might replace strtok with strsplit later)
(wrote callback tutorial and interface tutorial)
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
This tutorial explains how to write advanced modules in C. It is assumed you know how to code and are familiar with how the ASSS code works.
+
This tutorial explains how to write advanced modules in C.  
 +
It is assumed you know how to code and are familiar with how the ASSS code works.
  
 
This tutorial is a continuation of [[Writing Modules In C]].
 
This tutorial is a continuation of [[Writing Modules In C]].
Line 9: Line 10:
  
 
http://www.cplusplus.com/reference/
 
http://www.cplusplus.com/reference/
 +
 +
http://www.cprogramming.com/tutorial.html
  
  
Line 45: Line 48:
 
}
 
}
 
</pre>
 
</pre>
 +
  
 
== Passing Multiple Arguments To Commands ==
 
== Passing Multiple Arguments To Commands ==
  
Since the words are read one by one, then checked against the whole list, they can be in any order!
+
Since the words are read one by one against the whole list, they can be in any order!
  
 
<pre>
 
<pre>
// define macros for comparing strings that work for both linux and windows
+
// define macros for comparing strings that work on linux
#ifdef WIN32
+
#ifndef WIN32
#define STRCASECMP stricmp
+
//a case insensitive comparison that returns 0 if both are the same
#define STRNCASECMP strnicmp
+
#define stricmp(x,y) (strcasecmp(x,y) == 0)
#else
+
//a case insensitive comparison that returns 0 if both are the same for the first N letters
#define STRCASECMP strcasecmp
+
#define strnicmp(x,y,n) (strncasecmp(x,y,n) == 0)
#define STRNCASECMP strncasecmp
 
 
#endif
 
#endif
  
 
local void examplecommand(const char *command, const char *params, Player *p, const Target *t)
 
local void examplecommand(const char *command, const char *params, Player *p, const Target *t)
 
{
 
{
char *sentence=strdup(params); //strdup clones a string because strtok replaces " " with a \0
+
chat->SendMessage(p,"Sentence: %s",params);
char *word=strtok(sentence," "); //strtok reads everything until first \0 into word
 
 
 
while(word) //while word != null
+
char buf[255];
 +
char *word=NULL;
 +
const char *tmp=NULL;
 +
while(strsplit(params," ,:",buf,sizeof(buf),&tmp))
 
{
 
{
if(!STRNCASECMP(word,"a=",2)) //STRNCASECMP is a case insensitive comparison that returns 0 if the same, with a number
+
word=buf; //move start of word to beginning
 +
chat->SendMessage(p,"Word: %s",word); //the word currently being reviewed
 +
 
 +
if(strnicmp(word,"a=",2)) //remember, the N means it is checking only first 2 letters
 
{
 
{
 
//do stuff if first 2 letters of word are 'a' then '='
 
//do stuff if first 2 letters of word are 'a' then '='
Line 74: Line 82:
 
int check=atoi(word); //then read a number
 
int check=atoi(word); //then read a number
 
}
 
}
else if(!STRNCASECMP(word,"bc=",3))
+
else if(strnicmp(word,"bc=",3)) //3 this time
 
{
 
{
 
//do stuff if first 3 letters of word are 'b' then 'c' then '='
 
//do stuff if first 3 letters of word are 'b' then 'c' then '='
Line 81: Line 89:
 
int check=atoi(word); //then read a number
 
int check=atoi(word); //then read a number
 
}
 
}
else if(!STRCASECMP(word,"-de")) //thats STRCASECMP with no N, it checks the whole thing
+
else if(stricmp(word,"-de")) //no N, it just checks the whole thing
 
{
 
{
 
//do stuff if word is "-de"
 
//do stuff if word is "-de"
 
}
 
}
word=strtok(NULL," "); //advance to next word, or set word to null if none left
 
 
}
 
}
 
afree(sentence); //since we allocated a string with strdup, we must free the associated memory manually
 
 
}
 
}
 
</pre>
 
</pre>
 +
 +
  
 
== Creating Callbacks ==
 
== Creating Callbacks ==
  
Write me!
+
In a header somewhere public, you will want to declare your callback constant so people writing modules that use it can compile properly.
 +
An acceptable solution to this is distributing a whatever.h file with your module if your module is closed source.
 +
At a minimum, you should have a comment describing your callback, the callback constant definition, and a function prototype the caller will need to match.
 +
You can pass variables to the calling functions.
  
 
<pre>
 
<pre>
//example code goes here
+
 
 +
//in whatever.h
 +
 
 +
//when u load my module
 +
#define CB_MY_MODULE_JUST_ATTACHED "mymodulejustattached-1"
 +
typedef void (*MyModuleJustAttachedFunc)(Arena *a, int num);
 +
 
 +
</pre>
 +
 
 +
You will then need to use the DO_CBS macro in your code to call the callback functions other people have written.
 +
You can find the definition of this macro at the end of module.h
 +
 
 +
<pre>
 +
 
 +
//in whatever.c
 +
 
 +
local Imodman *mm;
 +
local Ilogman *lm;
 +
 
 +
int bestnumber=42;
 +
 
 +
EXPORT int MM_cbstest(int action, Imodman *mm2, Arena *a)
 +
{
 +
if(action == MM_LOAD)
 +
{
 +
mm=mm2;
 +
lm=mm->GetInterface(I_LOGMAN,ALLARENAS);
 +
if(!lm) return MM_FAIL;
 +
 +
lm->Log(L_ERROR,"<cbstest> Callback Test Module has loaded.");
 +
 +
return MM_OK;
 +
}
 +
else if(action == MM_ATTACH)
 +
{
 +
lm->LogA(L_ERROR,"cbstest",a,"Callback Test Module has attached to the arena.");
 +
 
 +
//this triggers registered callback functions to all arenas with a pointer to the arena you just attached to and a number
 +
DO_CBS(CB_MY_MODULE_JUST_ATTACHED, ALLARENAS, MyModuleJustAttachedFunc, (a, bestnumber));
 +
     
 +
return MM_OK;
 +
}
 +
else if(action == MM_DETACH)
 +
{
 +
lm->LogA(L_ERROR,"cbstest",a,"Callback Test Module has detached from the arena.");
 +
       
 +
return MM_OK;
 +
}
 +
else if(action == MM_UNLOAD)
 +
{
 +
lm->Log(L_ERROR,"<cbstest> Callback Test Module has unloaded.");
 +
 +
mm->ReleaseInterface(lm);
 +
 +
return MM_OK;
 +
}
 +
return MM_FAIL;
 +
}
 +
 
 
</pre>
 
</pre>
 +
  
  
 
== Creating Interfaces ==
 
== Creating Interfaces ==
  
Cover overwriting existing interfaces to replace old modules.
+
The alternative to creating callbacks for other modules to use is creating an interface.<br>
Write me!
+
In callbacks, you run their code. In interfaces, they run your code.<br>
 +
Just like in callback writing, you will need to have a public header to compile code against.<br>
 +
In this header you will need an interface constant defined and a prototype struct of function prototypes.<br>
 +
It is good practice to have easy to read, clear function names and a comment describing what each function does.<br>
 +
 
 +
<pre>
 +
 
 +
//in whatever.h
 +
 
 +
#define I_BANANAS "bananas-1"
 +
 
 +
//the bananas interface struct
 +
typedef struct Ibananas
 +
{
 +
INTERFACE_HEAD_DECL
 +
 
 +
//say bananas in chat
 +
void (*SayBananas)(void);
 +
 
 +
//count bananas
 +
int (*CountBananas)(int howmany);
 +
 
 +
//say player is bananas
 +
int (*PlayerIsBananas)(Player *p);
 +
} Ibananas;
 +
 
 +
</pre>
 +
 
 +
You will then need to create the functions that the interface will call.
 +
After that, you will need to create the actual interface struct.
  
 
<pre>
 
<pre>
//example code goes here
+
 
 +
//in whatever.c
 +
 
 +
local Imodman *mm;
 +
local Ilogman *lm;
 +
 
 +
local void SayBananas(void)
 +
{
 +
lm->Log(L_ERROR,"<inttest> bananas!");
 +
}
 +
 
 +
local void CountBananas(int howmany)
 +
{
 +
lm->Log(L_ERROR,"<inttest> %u bananas!",howmany);
 +
}
 +
 
 +
local void PlayerIsBananas(Player *p)
 +
{
 +
lm->Log(L_ERROR,"<inttest> %s is bananas!",p->name);
 +
}
 +
 
 +
local Ibananas bananasint=
 +
{
 +
INTERFACE_HEAD_INIT(I_BANANAS,"bananas")
 +
SayBananas, CountBananas, PlayerIsBananas
 +
};
 +
 
 +
EXPORT int MM_inttest(int action, Imodman *mm2, Arena *a)
 +
{
 +
if(action == MM_LOAD)
 +
{
 +
mm=mm2;
 +
lm=mm->GetInterface(I_LOGMAN,ALLARENAS);
 +
if(!lm) return MM_FAIL;
 +
 +
lm->Log(L_ERROR,"<inttest> Interface Test Module has loaded.");
 +
 +
return MM_OK;
 +
}
 +
else if(action == MM_ATTACH)
 +
{
 +
lm->LogA(L_ERROR,"inttest",a,"Interface Test Module has attached to the arena.");
 +
 
 +
return MM_OK;
 +
}
 +
else if(action == MM_DETACH)
 +
{
 +
lm->LogA(L_ERROR,"inttest",a,"Interface Test Module has detached from the arena.");
 +
       
 +
return MM_OK;
 +
}
 +
else if(action == MM_UNLOAD)
 +
{
 +
lm->Log(L_ERROR,"<inttest> Interface Test Module has unloaded.");
 +
 +
mm->ReleaseInterface(lm);
 +
 +
return MM_OK;
 +
}
 +
return MM_FAIL;
 +
}
 +
 
 
</pre>
 
</pre>
  

Latest revision as of 20:07, 5 July 2015

This tutorial explains how to write advanced modules in C. It is assumed you know how to code and are familiar with how the ASSS code works.

This tutorial is a continuation of Writing Modules In C.


Some useful references:

http://qnxcs.unomaha.edu/help/product/neutrino/lib_ref/summary.html

http://www.cplusplus.com/reference/

http://www.cprogramming.com/tutorial.html


Passing Data To Timers

typedef struct ThisIsData
{
	Player *p;
	int number;
} ThisIsData;

local int timerfunc(void *vp) //vp is void pointer, just an address that can point anywhere
{
	ThisIsData *tid=(ThisIsData*)vp; //we know it points to our data
	
	if(tid->number == 10)
	{
		//if it worked anything in here will work too
	}

	int returnValue=0; //return 1 if you want timer to run again, or 0 if you want it to be removed
	if(!returnValue) afree(tid); //if zero, free the data we have previously allocated
	return returnValue;
}

anotherfunction()
{
	ThisIsData *tid=amalloc(sizeof(ThisIsData)); //we must allocate memory because anything in this function is destroyed when it ends

	tid->number=10

	//now set timer to activate in 1000 centiseconds, then repeat every 100.
	//we are also sending the address of the memory we just allocated.
	ml->SetTimer(timerfunc,1000,100,tid,0);
}


Passing Multiple Arguments To Commands

Since the words are read one by one against the whole list, they can be in any order!

// define macros for comparing strings that work on linux
#ifndef WIN32
//a case insensitive comparison that returns 0 if both are the same
#define stricmp(x,y) (strcasecmp(x,y) == 0)
//a case insensitive comparison that returns 0 if both are the same for the first N letters
#define strnicmp(x,y,n) (strncasecmp(x,y,n) == 0)
#endif

local void examplecommand(const char *command, const char *params, Player *p, const Target *t)
{
	chat->SendMessage(p,"Sentence: %s",params);
	
	char buf[255];
	char *word=NULL;
	const char *tmp=NULL;
	while(strsplit(params," ,:",buf,sizeof(buf),&tmp))
	{
		word=buf; //move start of word to beginning
		chat->SendMessage(p,"Word: %s",word); //the word currently being reviewed

		if(strnicmp(word,"a=",2)) //remember, the N means it is checking only first 2 letters
		{
			//do stuff if first 2 letters of word are 'a' then '='

			word+=2; //like move start of word 2 letters forward
			int check=atoi(word); //then read a number
		}
		else if(strnicmp(word,"bc=",3)) //3 this time
		{
			//do stuff if first 3 letters of word are 'b' then 'c' then '='

			word+=3; //make sure you move it 3 letters and not 2
			int check=atoi(word); //then read a number
		}
		else if(stricmp(word,"-de")) //no N, it just checks the whole thing
		{
			//do stuff if word is "-de"
		}
	}
}


Creating Callbacks

In a header somewhere public, you will want to declare your callback constant so people writing modules that use it can compile properly. An acceptable solution to this is distributing a whatever.h file with your module if your module is closed source. At a minimum, you should have a comment describing your callback, the callback constant definition, and a function prototype the caller will need to match. You can pass variables to the calling functions.


//in whatever.h

//when u load my module
#define CB_MY_MODULE_JUST_ATTACHED "mymodulejustattached-1"
typedef void (*MyModuleJustAttachedFunc)(Arena *a, int num);

You will then need to use the DO_CBS macro in your code to call the callback functions other people have written. You can find the definition of this macro at the end of module.h


//in whatever.c

local Imodman *mm;
local Ilogman *lm;

int bestnumber=42;

EXPORT int MM_cbstest(int action, Imodman *mm2, Arena *a)
{
	if(action == MM_LOAD)
	{
		mm=mm2;
		lm=mm->GetInterface(I_LOGMAN,ALLARENAS);
		if(!lm) return MM_FAIL;
		
		lm->Log(L_ERROR,"<cbstest> Callback Test Module has loaded.");
		
		return MM_OK;
	}
	else if(action == MM_ATTACH)
	{
		lm->LogA(L_ERROR,"cbstest",a,"Callback Test Module has attached to the arena.");

		//this triggers registered callback functions to all arenas with a pointer to the arena you just attached to and a number
		DO_CBS(CB_MY_MODULE_JUST_ATTACHED, ALLARENAS, MyModuleJustAttachedFunc, (a, bestnumber));
      
		return MM_OK;
	}
	else if(action == MM_DETACH)
	{
		lm->LogA(L_ERROR,"cbstest",a,"Callback Test Module has detached from the arena.");
        
		return MM_OK;
	}
	else if(action == MM_UNLOAD)
	{
		lm->Log(L_ERROR,"<cbstest> Callback Test Module has unloaded.");
		
		mm->ReleaseInterface(lm);
		
		return MM_OK;
	}
	return MM_FAIL;
}


Creating Interfaces

The alternative to creating callbacks for other modules to use is creating an interface.
In callbacks, you run their code. In interfaces, they run your code.
Just like in callback writing, you will need to have a public header to compile code against.
In this header you will need an interface constant defined and a prototype struct of function prototypes.
It is good practice to have easy to read, clear function names and a comment describing what each function does.


//in whatever.h

#define I_BANANAS "bananas-1"

//the bananas interface struct
typedef struct Ibananas
{
	INTERFACE_HEAD_DECL

	//say bananas in chat
	void (*SayBananas)(void);

	//count bananas
	int (*CountBananas)(int howmany);

	//say player is bananas
	int (*PlayerIsBananas)(Player *p);
} Ibananas;

You will then need to create the functions that the interface will call. After that, you will need to create the actual interface struct.


//in whatever.c

local Imodman *mm;
local Ilogman *lm;

local void SayBananas(void)
{
	lm->Log(L_ERROR,"<inttest> bananas!");
}

local void CountBananas(int howmany)
{
	lm->Log(L_ERROR,"<inttest> %u bananas!",howmany);
}

local void PlayerIsBananas(Player *p)
{
	lm->Log(L_ERROR,"<inttest> %s is bananas!",p->name);
}

local Ibananas bananasint=
{
	INTERFACE_HEAD_INIT(I_BANANAS,"bananas")
	SayBananas, CountBananas, PlayerIsBananas
};

EXPORT int MM_inttest(int action, Imodman *mm2, Arena *a)
{
	if(action == MM_LOAD)
	{
		mm=mm2;
		lm=mm->GetInterface(I_LOGMAN,ALLARENAS);
		if(!lm) return MM_FAIL;
		
		lm->Log(L_ERROR,"<inttest> Interface Test Module has loaded.");
		
		return MM_OK;
	}
	else if(action == MM_ATTACH)
	{
		lm->LogA(L_ERROR,"inttest",a,"Interface Test Module has attached to the arena.");

		return MM_OK;
	}
	else if(action == MM_DETACH)
	{
		lm->LogA(L_ERROR,"inttest",a,"Interface Test Module has detached from the arena.");
        
		return MM_OK;
	}
	else if(action == MM_UNLOAD)
	{
		lm->Log(L_ERROR,"<inttest> Interface Test Module has unloaded.");
		
		mm->ReleaseInterface(lm);
		
		return MM_OK;
	}
	return MM_FAIL;
}


Sending Packets To Players

Cover position packets, weapon packets, clientset stuff, etc. Write me!

//example code goes here


Using Advisors

Write me!

//example code goes here


Creating Advisors

Write me!

//example code goes here