
Quake DeveLS - Perl Interface
Author: Chris Hilton
Difficulty: Medium
Foreword
by SumFuka
If you are a Perl virgin,
you may be wondering what the hell Perl actually is. Well, it's a high level
language that is used mainly in website programming. You've been to all those
shopping websites where you can 'buy' things and put them in your 'shopping
trolley' ? Those websites are probably powered by programs written in Perl. You
can use Perl for lots of things, but it is ideally suited to string processing.
The Perl example Chris has written for this tutorial simply capitalized the
first letter of each word in a string, and turns the string green (by playing
with ASCII codes). Other applcations ? Sure ! A frag log for a server would be
a good example. Or a password file. You could retrieve a user's password with
one line of Perl code ... rather than coding 30 lines of C code to open the
file, read it line by line, look for the user name... etc etc. Enjoy. [/SumFuka].
Let's Do
It...
Someone had to do it! In
my continuing quest to become a Perl guru, let me tell you about embedding Perl
in your Quake 2 DLL. I've implemented the calls to Perl in what I hope is a
simple API - use it or modify it as you prefer.
Note: This is not a
tutorial on Perl! Get the "Learning Perl"
(http://www.ora.com/catalog/lperl2/index.html) and "Programming Perl"
(http://www.ora.com/catalog/pperl2/index.html) books from O'Reilly and
Associates. These are THE books on Perl. At least basic familiarity with
programming Perl is assumed for the rest of this tutorial.
First, you'll need Perl
installed on your system. If using Windows, you must download Gurusamy
Sarathy's binary version of Perl for Win32 at
ftp://ftp.digital.com/pub/plan/perl/CPAN/ports/win32/Standard/x86.
ActiveState's build of Perl for Win32 is great, but embedding Perl with their
library is vastly different from Unix builds, so we'll use Sarathy's version
for maximum portability.
Okay, now that you have
Perl installed, let's look at the code for implementing this Perl API.
//// cch_perlq2.c//// Adding a perl interpreter to Quake 2 #include // from the Perl distribution #include // ditto static PerlInterpreter *myPerl; void InitPerl(){ char *argv[] = { "", "perlq2.pl" }; int argc = 2; myPerl = perl_alloc(); perl_construct(myPerl); perl_parse(myPerl, NULL, argc, argv, NULL); perl_run(myPerl);} I32 PerlEval(char *string){ return perl_eval_sv(newSVpv(string, 0), G_DISCARD);} int GetPerlInt(char *string){ return SvIV(perl_get_sv(string, FALSE));} float GetPerlFloat(char *string){ return SvNV(perl_get_sv(string, FALSE));} char *GetPerlString(char *string){ STRLEN length; return SvPV(perl_get_sv(string, FALSE), length);} void FreePerl(){ perl_destruct(myPerl); perl_free(myPerl);}
Actually,
most of this code is program independent, so knock yourself out imbedding Perl
into all of your programs. :) If the specifics of those functions look
overwhelming, don't worry too much about them. Mostly they refer to stuff in
the Perl library that you'll never have to bother with because you'll be using
these nifty little API functions instead. Let's go over those functions.
InitPerl() sets everything
up for us. perl_alloc and perl_construct ready the Perl interpreter. perl_parse
will parse the file 'perlq2.pl' which should be located in the current working
directory. You should put any Perl functions or other Perl code you want
initiated at startup in this file. perl_run actually runs the code so that, at
this point, your Perl interpreter should be fully initialized. This should be
added to the InitGame() function in g_save.c, I prefer toward the end of the
function at about line 190 (+'s indicate lines added).
InitClientResp (&game.clients[i]); } globals.num_edicts = game.maxclients+1;++ // CCH: Initialize perl interpreter+ InitPerl(); }
PerlEval()
is where you will be feeding Perl code to the interpreter. Just call PerlEval()
with any string containing Perl code and the interpreter will evaluate it.
That's right, any Perl code should work. As the perlembed manpage/documentation
says, "Your string can be as long as you wish; it can contain multiple
statements; it can employ 'use', 'require', and 'do' to include external Perl
files." The world is your oyster. Perl. Get it? Anyway...
The next three GetPerl*()
functions are intended to provide an easy interface for retrieving scalar
values from the Perl interpreter. If you need the integer equivalent of the
scalar value '$time', just use GetPerlInt("time"). Need that as a
float, you say? GetPerlFloat("time"). Never mind, you're going to
print it out instead? GetPerlString("time"). Time, time, time, see
what's become of me...
PerlFree() deallocates the
Perl interpreter and frees up the memory. It's always good to clean up after
yourself. This should be added to the ShutdownGame() function in g_main.c, at
about line 80.
gi.FreeTags (TAG_LEVEL); gi.FreeTags (TAG_GAME);++ // CCH: Free the perl interpreter+ FreePerl(); }
Finally,
to use all these functions, you'll have to add the following to g_local.h.
+// CCH: PerlQ2 includes & functions+#include // from the Perl distribution ++void InitPerl();+I32 PerlEval(char *string);+int GetPerlInt(char *string);+float GetPerlFloat(char *string);+char *GetPerlString(char *string);+void FreePerl();
"handy.h"
is included to resolve the I32 reference that PerlEval() returns. You'll also
need to set your compiling options so that the Perl library, perl.lib, is
included in your project and the include files can be found. In MSVC 5.0, this
entails going to Tools->Options, selecting the Directories tab, and adding
your Perl CORE directory (probably "C:\Perl\Lib\CORE") to the Include
and Library directories. You should also open your Project, go to
Project->Settings, select "All Configurations" in the "Settings
For:" field, select the Link tab, then add 'perl.lib' to the
Object/library modules. Hopefully, those of you using other compilers can
figure out your own implementation, but I recommend reading the perlembed
manpage/documentation if you run into trouble.
Okay, we know all the
details now, let's try an example. Here's a perlq2.pl to put in your Quake 2
directory.
## perlq2.pl# sub CapAndColor { my($string) = @_; $string =~ s/\b\w/pack("c",ord(uc($&))|128)/eg; return($string);}
This file
contains one Perl function called CapAndColor(). It takes a string as its first
argument, capitalizes the first letter of every word and sets that letter to
print in green text in Quake 2. It then returns this new string.
Now, we need a way to call
this Perl code. After adding all the appropriate code from above for setting up
the Perl interpreter, we add a new command to g_cmds.c.
+/*+=================+Cmd_JAPH_f+CCH: function for JAPH command+=================+*/+void Cmd_JAPH_f (edict_t *ent)+{+ char *japh = "just another perl hacker";+ char perlCommand[64];++ sprintf(perlCommand, "$return = &CapAndColor('%s');", japh);+ PerlEval(perlCommand);+ gi.centerprintf(ent, "%s\n", GetPerlString("return"));+}
This
function simply sprintf's our japh variable and the Perl code we want to call
into a temporary perlCommand buffer. Notice how we have '$return' receive the
return value from &CapAndColor(); we could call &CapAndColor()
directly, but we'd have no way to get to the result! After having PerlEval()
evaluate our command, we then use GetPerlString() to retrieve the result of the
&CapAndColor() call and centerprint it to the player.
One last tidbit we need is
a way to call this command function, so we add the following to ClientCommand()
in g_cmds.c.
Cmd_Kill_f (ent); else if (Q_stricmp (cmd, "putaway") == 0) Cmd_PutAway_f (ent);+ + // CCH: japh command+ else if (Q_stricmp (cmd, "japh") == 0)+ Cmd_JAPH_f (ent);+ else if (Q_stricmp (cmd, "wave") == 0) Cmd_Wave_f (ent); else if (Q_stricmp (cmd, "gameversion") == 0)
Now, you
should be able to compile this up, load quake2 with your new gamex86.dll, and
type 'cmd japh' at the console and see 'Just Another Perl Hacker' centerprinted
on your screen with J, A, P, and H in green, the rest of the text in white. For
you paranoid types and Intel lawyers (I repeat myself?), this is not 'hacker'
in the media sense. Just thought I'd mention that for Randal's sake.
Anyway, I think this is a
pretty simple but fairly powerful API to Perl (it seems like it's hard NOT to
do something powerful with Perl). If anyone finds this useful and would like to
see me work more on it (like, oh, adding ERROR HANDLING, maybe, figuring out
what PerlEval() is returning...), drop me a line so I'll know there's interest.
Speaking of error handling, at this point, there isn't any, so try running your
Perl scripts from the command line first if you're having problems. Also, get
Perl running with a simple script (like the above example) first before you go
wild with your 2000 lines of Perl code, okay?
Full source, patch file,
and DLL at http://www.jump.net/~dctank.
|
This site, and all
content and graphics displayed on it, |