Page 1 of 1

Creating your own HLDJ commands

Posted: Wed Nov 24, 2010 9:12 pm
by Renegade
** code featured in this topic is highly subject to change!

Now that HLDJ's source is out and in the open, the natural question is "how can I include my own HLDJ commands?".
Here is an example of how to include just such a custom command that outputs a random message to console along with date & time.

* For most purposes, all modifications can be done in src/hldj_api.c.
Firstly, head over to the function hldj_CreateCommandScanThread, and add your command (string) and an identifier (integer) to the command dictionary:

Code: Select all

hldj_commandScanThread_t *
hldj_CreateCommandScanThread( gameEnv_t *pGameEnv, void (*handleMessage)(int) )
{
    ...
    rht_insert( thread->pCmdTrie, MY_CUSTOM_CMD,    (void *)MY_CUSTOM_CMD_ID,    !0 );
    ...
}
Typically, you want to define these in an included header file, such as hldj.h or one you include from hldj.h:

Code: Select all

#define MY_CUSTOM_CMD         "my_custom_cmd"
#define MY_CUSTOM_CMD_ID      0xFF  // make sure the id is unique
#define MY_CUSTOM_CMD_OUT_CFG "my_custom_cmd_out.cfg"   // you'll generate this file later on, to communicate back to the game
Next, in hldj_WriteHLDJConfig, add your command to the game as an alias:

Code: Select all

unsigned short
hldj_WriteHLDJConfig( const gameEnv_t *gameEnv, unsigned short numFileErrors )
{
    ...
    /* my custom alias - this will pass the command to HLDJ, then exec a generated file  */
    fprintf( config, "\nalias "MY_CUSTOM_CMD" \""BINDCMD" %s "MY_CUSTOM_CMD"; "SELECTCMD"; exec "MY_CUSTOM_CMD_OUT_CFG"\"", gameEnv->game->commandRelayKey );
    ...
You can place this towards the end of the function, after all the HLDJ commands have been defined, or better still, you can generate your own initialization script, and call it from HLDJ's.

Notice the script you exec at the end? It is the same you defined earlier, and will be used to communicate back to the game. The general flow of sending commands is: send your command to HLDJ (by binding it to the command relay key and writing the config file), 'wait' a bit while HLDJ processes it, then 'exec' some output file that you will generate in HLDJ.

Finally, you need to handle your command in HLDJ (including generating the output file). This is done in handleCommand_ThreadFunc, add a case to handle your command's id:

Code: Select all

static unsigned __stdcall
handleCommand_ThreadFunc( void *pArgs )
{
  ...
     switch( msg )
     {
       ...
       case MY_CUSTOM_CMD_ID:
         my_custom_cmd_do_stuff( thread->pGameEnv ); break;
     }
  ...
}
In this example, we're going to implement do_stuff to generate a random message and display the date & time.
(but in theory you could have it do anything you want! from retrieving stats from a website, to starting a command on your computer, to controlling something remotely on another computer)

Code: Select all

#include <time.h>
// generates random message
void my_custom_cmd_do_stuff( const gameEnv_t *pGameEnv )
{
    // first, build a path to the generated file
    char fileName[ MAX_PATH+1 ];
    strcpy( fileName, gameEnv->game->gamePath );
    addTerminatingPathBackslash( fileName );

    // if it's a Source game, the configs reside in the 'cfg\' folder
    if( gameEnv->game->engine == HL2ENGINE )
        strcat( fileName, CFGFOLDER );

    strcat( fileName, MY_CUSTOM_CMD_CFG );
    FILE *config = fopen( fileName, "w" );

    if( config == NULL)
        return 1;

     // choose a random message and echo it to console
     const char *msg[4] = { "Hello World", "Goodbye World", "foo", "bar" };
     const int i = rand() % 4;
     fprintf( config, "\necho \"%s\"", msg[i] );

     // display the time
     time_t rawtime;
     struct tm * timeinfo;
     time( &rawtime );
     timeinfo = localtime( &rawtime );
     fprintf ( config, "\necho \"Generated on: %s\"", asctime( timeinfo ) );

     fclose( config );
}

Compiling & Building HLDJ

The HLDJ source can be compiled using any C99-compliant compiler (GNU GCC preferred). To build the entire project, it is recommended to have the MingW64 compiler tools (along with a 'make' utility) on path. Modify the makefile in the indicated place (under the "# compiler" section) to match your specific compiler name & prefix. Notice the prefixes are different for 32- and 64-bit builds, this allows you to have both 32- and 64-bit compilers on path with different prefixes.
For example, you will notice from the makefile the compiler base name is 'gcc', but the 32-bit prefix is 'i686-w64-mingw32-' and 64-bit prefix is 'x86_64-w64-mingw32-' (as per the MingW64 compilers). Again you should modify these to whatever makes sense for your compilation environment so that the makefile has no problems invoking your compiler.

To start the build process, run 'make' on the command line from the hldj root directory.


If you have any questions, comments, corrections, or suggestions, feel free to add them to this thread!

Re: Creating your own HLDJ commands

Posted: Sun Nov 28, 2010 5:19 pm
by jxl180
So, our commands must be hard-coded into the hldj files? We cannot just call upon the functions in our own programs?

Re: Creating your own HLDJ commands

Posted: Mon Nov 29, 2010 7:56 pm
by Renegade
At this point it's the easiest way, but I can add hooks to call custom handlers that you pass in. Let me know if this would be easier for you.

Re: Creating your own HLDJ commands

Posted: Mon Nov 29, 2010 9:02 pm
by jxl180
Don't go by me because I won't be attempting to make any modifications anytime soon (busy with uni. work); however, I think hooks will make it a lot easier because mods can be distributed without having to redistribute hldj files. Also, what if a user wants to apply multiple mods to hldj that they did not create? Also, if hooks are used in this fashion, HLDJ can be used as an engine, as well.

Re: Creating your own HLDJ commands

Posted: Tue Nov 30, 2010 9:06 am
by Renegade
Yep, it's been in the TODO for a while; a full engine (probably a rewrite too) would be ideal, in the meantime I can make smaller modifications, but I'm trying to gauge the time spent based on interest/use.

Re: Creating your own HLDJ commands

Posted: Tue Dec 21, 2010 9:21 pm
by jxl180
How do I ensure the message (assuming HLDJM is HLDJ message) has a unique id? I mean I guess I can increment it (the next would be 0x08), but how can I ensure uniqueness? For example, if many people start adding 3rd party commands, how do we make sure there will be no conflicts? Just use a really random hex number (sort of like randomly assigning a port number and hoping for the best)?

Re: Creating your own HLDJ commands

Posted: Wed Dec 22, 2010 2:03 am
by Renegade
For now incrementing is ok (but give some room for HLDJ commands to grow). Ideally with a plugin system, it will query each plugin for a list of commands and handlers/callbacks. Let me know if you're actually starting to add your commands to HLDJ now, I'll bump the plugin system to the head of the TODO list.

Re: Creating your own HLDJ commands

Posted: Wed Dec 22, 2010 2:11 am
by jxl180
Yeah, I've been playing around with the api for a few hours and learning c from your code as I progress lol. I'm writing a command hldj_local <0/1>. If players in the server are annoyed by the sounds being broadcast, enabling this will turn HLDJ into a personal music player. Only the hldj user can hear the sound. hldj_local 0 (default) will enable broadcasting (now that I think of it, hldj_broadcast seems more appropriate).

I am having trouble finding out how to get the arguments (again, I'm very new to c). Would it be cmd, cmd[1], *p, or am I completely off?

Should I just do two seperate aliases: hldj_broadcast_on and hldj_broadcast_off to toggle it?

Re: Creating your own HLDJ commands

Posted: Wed Dec 22, 2010 2:56 am
by Renegade
unfortunately aliases can't have arguments, but you can get creative. For an on/off command, you can just use a toggle:

Code: Select all

alias hldj_toggle_broadcast hldj_broadcast_on
alias hldj_broadcast_on "<do stuff>; alias hldj_toggle_broadcast hldj_broadcast_off"
alias hldj_broadcast_off "<do stuff>; alias hldj_toggle_broadcast hldj_broadcast_on"
the Source engine also has built-in support for toggles (BindToggle and incrementvar).
You might even be able to use commands like 'play' or 'mp3' to implement this entirely through scripting (i.e. broadcast_on might set voice_scale to 0 and call 'play'), or I guess your plan is to have HLDJ start playing it in an external media player?

Re: Creating your own HLDJ commands

Posted: Wed Dec 22, 2010 3:05 am
by jxl180
My plan was to use the play command; however it will play what ever is currently loaded (by executing "play '..\..\voice_input.wav" I intend to do this in HLDJ for practice and so that the same bind key can be used to play songs.

EDIT: Also, It will allow the user to still use the microphone (unless maybe hldj_muteall/hldj_unmuteall is called, or something similar).

Re: Creating your own HLDJ commands

Posted: Wed Dec 22, 2010 11:26 pm
by Renegade
Using the custom aliases, you could do:

Code: Select all

// hldj_broadcast_on.cfg
alias hldj_custom_playaudio_on "-voicerecord; <play the song>"
alias hldj_custom_playaudio_off "<stop the song>"
Have hldj_broadcast_on exec the above, and hldj_broadcast_off unalias it. Then place your commands/configs in the custom folder.

There's not much for HLDJ to do in this case, but if you want to modify it just for practice, you could add your own hldj_Write...Config function that generates any commands/configs that you might need.