So, I've translated this tutorial. Sry for my English. By creating JAZZ plugins you can rly extend JAZZ features, customize gameplay and etc. But, JAZZ is server-side only, remember that. I'm assuming that you know C or C++, otherwise just forget about pluginning. For the beginning, you need some sources. Archive can be downloaded here. It contains all the game libs, that is needed for pluginning. Just unpack it to "include" folder of your compiler. Also, you need latest jazz interface header (jassapi.h). You can found it here. Finally, you need the base plugin - download it here. It contains some comments, but it's not enough to understand, how it's working. At first, "engine" is jamp.exe or jampDed.exe or linuxjampDed - whatever you are using. "Mod" is jampgamex86.dll on Windows or jampgamei386 on Linux. It contains most of game source. Engine and mod communicates by cmds, that formally contains 12 or 13 integer arguments. However, do not let that fool you, - this arguments can mean anything, in most it is pointers. First argument is command. It is integer number. All the commands is described inside if g_public. Other arguments can mean anything depending on command.
Main JAZZ API functions (more info inside the jassapi.h):
JASS_WRITEGAMELOG (char * text) - write text to game logs. Simular to g_syscall(G_FS_WRITE, char * text, int len, JASS_GETLOGHANDLE()). You must add \n in the end of string. JASS_WRITEJASSLOG (char * text) - write text to jass.log. You must add \n in the end of string, too. JASS_VARARGS (char * text, ...) - lets you to generate strings. JASS_VARARGS("Say %s","Hi") will return "Say Hi". Works alike sprintf, but this func will return string pointer. Also, it can be used in comparisons because it have 4 buffers. JASS_ENGMSGNAME(int cmd), JASS_MODMSGNAME(int cmd) - translates cmd ID to string with this cmd name. JASS_GETINTCVAR(char * cvarname) - return integer value of cvar. JASS_GETSTRCVAR(char * cvarname) - return pointer to string value of cvar. JASS_GETHOMEPATH - return homepath in linux platform. JASS_GETPLUGINID - return olugin ID by name. JASS_GETPLUGININFO - return plugin info by ID JASS_PLUGINCALL - call JASS_pluginCall of plugin. First param is ID, others - JASS_pluginCall parameters. JAZZ return: JASS_RET_IGNORED(int x) - passes cmd without changing. JASS_RET_SUPERCEDE(int x) - blocks command.
List of mod cmds (more inside g_public.h):
GAME_INIT, // ( int levelTime, int randomSeed, int restart ) - This means game initialization. GAME_SHUTDOWN, // (void); - This means game shutdown. GAME_CLIENT_CONNECT, // ( int clientNum, qboolean firstTime, qboolean isBot ); - This means, that someone is connecting. Returns 0 if players is allowed to connect, otherwise - pointer to string with reason of denying. GAME_CLIENT_BEGIN, // ( int clientNum ); - means, that player entered the game. GAME_CLIENT_USERINFO_CHANGED,// ( int clientNum ); - means, that player changed his/her information (name, model, etc). GAME_CLIENT_DISCONNECT,// ( int clientNum ); - player have disconnected. Bye-bye! GAME_CLIENT_COMMAND,// ( int clientNum ); - player command (access to command by G_ARGV and G_ARGC). Remember that JAZZ is serverside-only, so created by you commands will not be known for client. That means, that completion won't work. GAME_CLIENT_THINK,// ( int clientNum ); - calls on every player actions. GAME_RUN_FRAME,// ( int levelTime ); - Frame. 20 frames per second by default. GAME_CONSOLE_COMMAND,// ( void ); - server command from console or rcon (access to command by G_ARGV and G_ARGC).
List of engine cmds (more inside g_public.h):
G_PRINT, // ( const char *string ); - message in the server console. G_ERROR, // ( const char *string ); - Error! G_MILLISECONDS, // ( void ); - Get the server time in ms. Not recommended, get the time by catching GAME_RUN_FRAME. G_CVAR_REGISTER, // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); - creates cvar. G_CVAR_UPDATE, // ( vmCvar_t *vmCvar ); - updates cvar. G_CVAR_SET, // ( const char *var_name, const char *value ); - changes cvar. G_CVAR_VARIABLE_INTEGER_VALUE, // ( const char *var_name ); - get integer cvar. G_CVAR_VARIABLE_STRING_BUFFER, // ( const char *var_name, char *buffer, int bufsize ); - get string cvar. But easier to use JASS_GETSTRCVAR. G_ARGC, // ( void ); - count of arguments in client/server command. G_ARGV, // ( int n, char *buffer, int bufferLength ); - argument from client/server command. G_FS_FOPEN_FILE, // ( const char *qpath, fileHandle_t *file, fsMode_t mode ); - opens file. G_FS_READ, // ( void *buffer, int len, fileHandle_t f ); - reads file. G_FS_WRITE, // ( const void *buffer, int len, fileHandle_t f ); - writes file. G_FS_FCLOSE_FILE, // ( fileHandle_t f ); - closes file. G_SEND_CONSOLE_COMMAND, // ( int exec_when, const char *cmd ); - sends command to server console. Use EXEC_APPEND for exec_when. G_DROP_CLIENT, // ( int clientNum, const char *reason ); - Drops client. G_SEND_SERVER_COMMAND, // ( int clientNum, const char *fmt, ... ); - sends server command to client (print - print in the console, cp - print in the center). It's not game commands like say, say_team. G_GET_USERINFO, // ( int num, char *buffer, int bufferSize ); - gets player userinfo. G_SET_USERINFO, // ( int num, const char *buffer ); - sets player userinfo. G_GET_SERVERINFO, // ( char *buffer, int bufferSize ); - gets serverinfo.
That's list of usual cmds. Really it's much more of them, but in most times you'll not need any other. Source for example plugin (available for download in download section. Good luck with its search in Russian site ):
Код
/* Base Plugin Downloaded from http://jass.ucoz.net/ This file is base for new plugins */
#include "version.h" //Constants with info about autor //Usual headers: #include <jka/game/q_shared.h> #include <jka/game/g_local.h> #include <jassapi.h> //JAZZ APIs
pluginres_t* g_result = NULL; //Plugin info plugininfo_t g_plugininfo = { "Plugin_base", //Plugin name Plugin_Base_version, //version "Base for plugin", //description Plugin_Base_Builder, //autor "http://www.jass.ucoz.net/", //site 1, //can plugin be paused? 1, //can plugin be loaded via cmd? 1, //can plugin be unloaded via cmd? JASS_PIFV_MAJOR, //interface version (inside the jassapi.h) JASS_PIFV_MINOR }; eng_syscall_t g_syscall = NULL; mod_vmMain_t g_vmMain = NULL; pluginfuncs_t* g_pluginfuncs = NULL;
//First function called by JAZZ. Don't change it C_DLLEXPORT void JASS_Query(plugininfo_t** pinfo) { JASS_GIVE_PINFO(); }
//Second function called by JAZZ //All the code, that must be executed on start, must be written here. // engfunc - pointer to engine's syscall // modfunc - pointer to mod's vmMain // presult - pointer to plugin result // iscmd - method of loading: 0 = loaded via jass.ini, 1 = loaded via "jass load" cmd C_DLLEXPORT int JASS_Attach(eng_syscall_t engfunc, mod_vmMain_t modfunc, pluginres_t* presult, pluginfuncs_t* pluginfuncs, int iscmd) { JASS_SAVE_VARS();
iscmd = 0;
return 1; //return 0; is failure }
//Last function called by JAZZ //All the code, that must be executed on exit, must be written here. // iscmd - method of unloading: 0 = loaded on server shutdown, 1 = loaded via "jass unload" cmd C_DLLEXPORT void JASS_Detach(int iscmd) { iscmd = 0; }
//This function calls before mod's vmMain (Engine - mod) C_DLLEXPORT int JASS_vmMain(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11) { /* if (cmd == GAME_CLIENT_COMMAND) { //Checking the client cmd //Use g_syscall to send commands to Engine //ex.: g_syscall(G_PRINT, "Hi!\n"); //Use g_vmMain to send commands to Mod //List of possible cmd values is inside g_public.h } else if (cmd == GAME_SERVER_COMMAND){ JASS_RET_SUPERCEDE(1); //command will be blocked } */ //g_syscall(G_PRINT, JASS_VARARGS("vmMain(%s)\n", JASS_MODMSGNAME(cmd)));
JASS_RET_IGNORED(1); //command will be passed }
//This function calls before engine's suscall (mod - engine) C_DLLEXPORT int JASS_syscall(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11, int arg12) { /* if (cmd == G_PRINT) { JASS_RET_SUPERCEDE(1); } */ //g_syscall(G_PRINT, JASS_VARARGS("syscall(%s)\n", JASS_ENGMSGNAME(cmd)));
JASS_RET_IGNORED(1); }
//This function calls after mod's vmMain (Engine - mod) C_DLLEXPORT int JASS_vmMain_Post(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11) { //g_syscall(G_PRINT, JASS_VARARGS("vmMain_Post(%s)\n", JASS_MODMSGNAME(cmd))); JASS_RET_IGNORED(1); }
//This function calls after engine's suscall (mod - engine) C_DLLEXPORT int JASS_syscall_Post(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11, int arg12) { //g_syscall(G_PRINT, JASS_VARARGS("syscall_Post(%s)\n", JASS_ENGMSGNAME(cmd))); JASS_RET_IGNORED(1); }
//This function calls when other plugin or JASS by itself invokes it //Place there code that will be shared to other plugins C_DLLEXPORT int JASS_pluginCall(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11, int arg12) { return 0; }
So, I'll describe, how engine communicates with mod more detailed.
Functions we will works with are syscall and vmMain. Engine regularly appeals to mod, , and passing command through the arguments - as if asking: "Friend, there is such a situation, what shall we do?" vmMain, taking the arguments of the situation, decides what to do. In this mod will often cause the engine via the function syscall, as if say: "So, do this, this and this". The same two functions of your plugin is named g_syscall and g_vmMain. Another nuance - engine loads mod only during one session. Ie, when loading map engine loads mod, and then, when the map changes, or restarts, mod will be unloaded and loaded again. But in our case, instead of loading mod, engine will load jazz, and jazz will load plugins.
Example of work engine-jazz-plugins-mod:
1) Server starts. The engine immediately loads mod into memory - or rather, thinks that loads a mod, but loads the JAZZ. Jazz, in turn, loads all the plugins and calls JASS_Query() for each, then loads real mod.
2) Mod is loaded, and engine provokes initialisation by calling vmMain function with GAME_INIT cmd. By the way, cmd passes thru first argument of vmMain, the same with g_syscall. JAZZ responds by calling the plugin function JASS_Attach(). After that JAZZ calls JASS_vmMain with the same arguments as for mod. Ie, variable cmd will be set to GAME_INIT, arg0 - levelTime, arg1 - randomSeed, arg2 - restart. arg3--arg12 us zero, because they are unused in GAME_INIT. Read mod commands list for details.
3) When JASS_vmMain for all the plugins would be called, jazz will call mod's vmMain. vmMain will initialize whatever it wants. No, it's not finish - when initialization will be completed, jazz will call plugin's JASS_vmMain_Post() function with the same attributes. It's finish.
4) So, everything is initialized, and game have begun. Engine will call vmMain many times. For example, someone have connected. It's isn't usual example. Engine will call vmMain with cmd = GAME_CLIENT_CONNECT. JAZZ will call JASS_vmMain with cmd=GAME_CLIENT_CONNECT, arg0=clientNum, arg1=firstTime, arg2=isBot. clientNum is client ID and can be equals to 0-31 (every action on clients uses his ID). firstTime is means, that clients have connected to our server just now. When map changes, mod and plugins unloads and loads again, while players technically reconnects to server. To understand, was client connected just now or he is reconnecting after map restart, this value is used. isBot means that player is bot. And again, jazz calls vmMain() from mod, JASS_vmMain_Post() from plugin. By the way, if you want to do something to client, better to do this in JASS_vmMain_Post(). Also, usually, when blocking cmd by using JASS_RET_SUPERCEDE(), you must write 1 in brackets, but in case GAME_CLIENT_CONNECT you must put there a pointer to string, that contains a reason of denying the connect. Remember, that exit from vmMain and syscall performed by macro JASS_RET*, that is described in jassapi.h: JASS_RET_IGNORED(x) - original function will be called, and it's result will be parsed to engine. JASS_RET_OVERRIDE(x) - original function will be called, but it's result will be changed to x. JASS_RET_SUPERCEDE(x) - original function will not be called, and result will be x. JASS_RET_ERROR(x) - JAZZ shows error message and tech info. JASS_SET_RESULT(x) - changes result. The same as JASS_RET_OVERRIDE, but for _Post functions.
5) Player have connected, entered the game (GAME_CLIENT_BEGIN), and entered some command (like "say Hi!"). Engine call vmMain with cmd=GAME_CLIENT_COMMAND, arg0=clientNum... Wait, where is player command? Everything is simple. Engine have already split the command. How to access it: int argc = g_syscall(G_ARGC); We have called engine's syscall by using g_syscall with cmd=G_ARGC. ARGC is arguments count. We have taken count of arguments, it's stored in argc. After that we will call g_syscall(G_ARGV, 0, command, sizeof(command)); to get first command's arg - command name (it will be "say").After that, using loop 1:argc we can take command arguments. Example - writing every vote in logs.
Код
//crossplatform comparision function #define strcasecmp stricmp int argc = g_syscall(G_ARGC);//arguments count g_syscall(G_ARGV, 0, command, sizeof(command));//command by itself if (!strcasecmp(command,"callvote")){//If it is callvote int i = 0; char tmparg[1024]; JASS_WRITEJASSLOG(command);//Writing to jazz logs JASS_WRITEGAMELOG(command);//Writing to game logs for (i=1; i<argc; ++i){ g_syscall(G_ARGV, i, tmparg, sizeof(tmparg));//argument with i number JASS_WRITEJASSLOG (JASS_VARARGS(" %s",tmparg));//Writing to jazz logs JASS_WRITEGAMELOG (JASS_VARARGS(" %s",tmparg));//Writing to game logs } //And, as I said - never forget about adding the "\n" JASS_WRITEJASSLOG("\n"); JASS_WRITEGAMELOG("\n"); }
If you have created your own command that takes some player id, do not forget to check if id>=0 and id 6) So, we will imagine that mod calls engine's syscall() - we can catch it as well as vmMain. By the way, if you will call g_syscall, it will not be catched by another plugin. For example, mod tries to call with cmd=G_PRINT (ie tries to print something). JAZZ will call JASS_syscall from plugin, cmd=G_PRINT, arg0=string. It is the same as catching commands for JASS_vmMain.
7) So usual situations is described, player had disconnected (GAME_CLIENT_DISCONNECT), it's time to shutdown server. JAZZ calls JASS_vmMain with cmd=GAME_SHUTDOWN without args, then mod's vmMain, then JASS_vmMain_Post(). After that, JAZZ beguns to unload the plugins. It calling JASS_Detach(), and then unloads mod. And then engine unloads jazz.
8) Now about JASS_pluginCall. This func is not created for JA, it is designed to ensure that your plug-in could be usefull for others. If you're not going to provide an interface to other plugins, do not touch this function. If not - create a separate header file with the commands. For example, an interface to a standard plugin AntiDDoS looks like this:
Код
/********************************/ /* Inteface for AntiDDoS plugin */ /********************************/
As you can see, the interface is similar to the cmds of vmMain or syscall. To call JASS_pluginCall from another plugin, use JASS_PLUGINCALL (ID, ...). For example, Protection bans player via AntiDDoS:
Код
int adID=JASS_GETPLUGINID("AntiDDoS"); bool banned=false; if(adID!=-1){ banned = JASS_PLUGINCALL(adID,AD_BAN,(int)(char*)ip,300000); if(banned){ PrintNLog(JASS_VARARGS("AntiFake: Client %d:\"%s\" was banned for 5 mins, ip: %s\n",arg0,getname(arg0),ip)); } }
And here is the interface in the antiddos:
Код
C_DLLEXPORT int JASS_pluginCall(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11, int arg12) { if(cmd==AD_BAN){ //BOOL (char *szIP, size_t ltime); return AddBan((char *)arg0, arg1); }else if(cmd==AD_UNBAN){ //void (char *szIP); if(!strcasecmp((char *)arg0,"all")){ UnbanAll(); }else{ Unban((char *)arg0); } }else if(cmd==AD_BANLIST){ //char *(int num, int count); return (int)ListBan(arg0,arg1); }
return 0; }
9. Debugging. You've created a plugin, it's time to test it. So, server got crashed? Now I'll explain how to debug the plugins. I suppose you have MS Visual Studio. Debugging on Windows: 1) open project properties. 2) Section "Debugging": 2.1) Command - full path to Jedi Academy executable. Ex.: "D:\Games\Jedi Academy\GameData\jampDed.exe" 2.2) Command arguments - arguments. Ex.: "+set net_port 29070 +set dedicated 2 +exec server.cfg +set fs_game JASS_Release" 2.3) Working Directory - full path to GameData. Ex.: "D:\Games\Jedi Academy\GameData" 3) Section "Linker": 3.1) Output file - full plugin file path, here compiled dll will be stored. Ex.: "D:\Games\Jedi Academy\GameData\JASS_Release\Plugins\$(ProjectName).dll". 4) Press F5 and begin debug. Debugging on Linux: 1) Better. 2) debug. 3) on. 4) Windows. 5) 95% of bugs is common for both systems, so I'm creating stable Windows plugin and then just compiling stable plugin for Linux. If you meet Linux-only bug, use the gdb.
I hope you get the point. Feel free to ask any questions
if (cmd == GAME_CLIENT_COMMAND/*GAME_SERVER_COMMAND for server console or rcon*/) { int argc = g_syscall(G_ARGC); g_syscall(G_ARGV, 0, command, sizeof(command)); if (!strcasecmp(command,"cmd")){ char tmparg[1024]; for (int i=1; i<argc; ++i){ //args } }
By the way, arg0 usually is used to store playerID.
Get and set any parameter from userinfo string. String by itself you can get using G_GET_USERINFO and sed using G_SET_USERINFO.
Code
char *Info_ValueForKey( const char *s, const char *key ) { char pkey[BIG_INFO_KEY]; static char value[2][BIG_INFO_VALUE]; // use two buffers so compares // work without stomping on each other static int valueindex = 0; char *o;
if ( !s || !key ) { return ""; }
if ( strlen( s ) >= BIG_INFO_STRING ) { g_syscall( G_PRINT, "Info_ValueForKey: oversize infostring" ); return ""; }
valueindex ^= 1; if (*s == '\\') s++; while (1) { o = pkey; while (*s != '\\') { if (!*s) return ""; *o++ = *s++; } *o = 0; s++;
o = value[valueindex];
while (*s != '\\' && *s) { *o++ = *s++; } *o = 0;
if (!strcmp(key, pkey) ) return value[valueindex];
if ( strlen( s ) >= MAX_INFO_STRING ) { g_syscall( G_PRINT, "Info_RemoveKey: oversize infostring"); }
if (strchr (key, '\\')) { return; }
while (1) { start = s; if (*s == '\\') s++; o = pkey; while (*s != '\\') { if (!*s) return; *o++ = *s++; } *o = 0; s++;
o = value; while (*s != '\\' && *s) { if (!*s) return; *o++ = *s++; } *o = 0;
if (!strcmp (key, pkey) ) { strcpy (start, s); // remove this part return; }
if (!*s) return; }
}
Getting mod name. Use this function instead of getting it directly from fs_game
Code
char base []="base"; const char * gamedir () { const char * game = JASS_GETSTRCVAR("fs_game"); if (!game[0]) return base; return game; }
Check if player is a bot. It is needed because bot are connect to server without GAME_CLIENT_CONNECT (it calls only on bot reconnect when map changes). You need to get list of entities before.
Code
bool is_bot = g_gents[arg0].r.svFlags&SVF_BOT;
Getting pointer to level struct:
Code
if (cmd == G_FS_FOPEN_FILE) { if (arg2 == FS_APPEND || arg2== FS_APPEND_SYNC) { if (!strcasecmp(JASS_GETSTRCVAR("g_log"), (char*)(arg0))) { //level_locals_t *g_level = NULL; g_level=(level_locals_t*)(arg1-20); //arg is a pointer to level.logFile :D, 20 is offset } } }
Suddenly, this method works only if logs are enabled. Also on basejka v1.0.1 on windows pointer to level will always be 0x207EEA60, and for linux - lib entry point+0x0068A3A0. Lib entry point search well-defined in academy5 plugin.