OK, I've gotten this to the point where it seems usable. I'd like to submit it directly for consumption into the cvs tree, rather than just post diffs, since I'm sure that there will be many rapid small fixes/changes/suggestions after its release, and having it already in CVS would make application of such changes go much more smoothly. I would appreciate it if a dev would inform me of the preferred way to submit. I have compiled the changes under Linux 2.4 w/ Mandrake RPMs, but have only tested it under Windows w/ VC7 and Activestate Perl. Failing to define EMBPERL or EMBPERL_PLUGIN after merging my changes will result in _almost_ the same code that I started with. I had to make a few changes, either for utility or to get Perl shoehorned in, but they are for the most part minor. Of particular importance to whomever does the merging, though... I had to change the order certain files are #included in, in order to avoid overwriting macros. I wasted like 45 minutes trying to figure out which portions of eqemu's namespace pollution was interfering with Perl's namespace pollution, then gave up. So... take note when making the merge (assuming I haven't scared you off :).
Now, having said all of that, let me tempt you with the benefits. After compiling with the perl options, scripts can/should/must be written in perl. There are currently no facilities for mixing perl and native scripting. Defining EMBPERL_PLUGIN also exposes a simple plugin interface. When zone.exe starts up, all subroutines in ./plugin.pl and ./plugins/*.pl (if they exist) are compiled into package plugin. These subroutines can be called from the EQ client with the #plugin command for all users with high enough access privileges (defaults to >= 200). A #peval command has also been added which allows for direct evaluation of Perl statements.
Writing basic quests w/ embedded Perl (embperl) is almost identical to writing them in the native scripting language. The most significant change is that functions should be prefixed with 'quest::'.
An example:
Code:
EVENT_SAY {
if ($1- =~= "Hail") { say("Hello, $name!");}
}
in native script becomes:
Code:
sub EVENT_SAY {
if ($text=~/Hail/) { quest::say("Hello, $name!");}
}
in embperl.
Here's an example plugin:
Code:
sub calc
{
eval "print PLUGIN $_[0];";
}
Paste this into a file such as .\plugins\calc.pl, and voila! You have a simple calculator. You can use it like: #plugin calc "3*2**2" Note the use of the PLUGIN filehandle? That directs output to the client window. In other respects, it works pretty much like normal perl. Here is a slightly more complex example - it shows that you can use normal modules and do some meaty stuff. You can paste it into a file called .\plugins\iplookup.pl (or whatever). To use it, do #plugin lookup "123.456.678.890" (you can use the #iplookup command to get player ips)
Code:
sub lookup
{
use LWP::Simple;
my $url = "http://ws2.serviceobjects.net/gpp/GeoPinPoint.asmx/GetLocationByIP?IPAddress=".$_[0]."&LicenseKey=WS1-ITN1-XMC1";
my $xml = get($url);
if(!$xml or $xml=~/<Error>/)
{
print PLUGIN "ip lookup failed\n";
return;
}
my $city = $1 if $xml=~/<City>(.*)</gm;
my $state = $1 if $xml=~/<Region>(.*)</gm;
my $country = $1 if $xml=~/<Country>(.*)</gm;
my $lon = $1 if $xml=~/<Longitude>(.*)</gm;
my $lat = $1 if $xml=~/<Latitude>(.*)</gm;
my $cert = $1 if $xml=~/<Certainty>(.*)</gm;
print PLUGIN "($cert % certainty) $_[0] => $city, $state, $country ($lat/lat, $lon/lon)";
}
note... this is quick and dirty. If your plugins depend on slow I/O, like this, you should probably fork so that you don't slow down the whole zone process (pretty cool, though, eh? :)
Crafty quest coders will surely find all kinds of fun ways to utilize embperl, enhancing eqemu and distinguishing their servers. The tie-in between the plugin and quest features makes for interesting possibilities, too. Quests can be turned on or off via global flags, set using #peval, or even completely overridden/implemented right there from the console! eg #peval sub qstdefault::EVENT_ATTACK { quest::say("Help! $name is trying to kill me!"); }. That is power, baby!
p.s. a script like this will go a long way towards converting existing quests. Change it to suit your needs.
Code:
#!/usr/bin/perl -w
#convert scripts from .qst to .pl
#usage: ./convert.pl [questdir]
use File::Find;
use strict;
sub convert
{
my $infile = $_;
(my $outfile = $infile)=~s/qst$/pl/;
print "Converting file: $infile -> $outfile\n";
if(!open IN, "$infile") {
warn $!;
return;
}
if(!open OUT, ">$outfile") {
warn $!;
return;
}
while(<IN>)
{
#remove stray backslashes
s|\\|\?|;
#change /yada yada yada/ comment lines to #yada yada
s|^/(.*)/\s*$|#($1)|;
#prefix each event block w/ "sub"
s/^(EVENT_)/sub $1/;
#change $1/$1- (etc) notation to instead match against $text
s/\s*\$\d-?\s*=~\s*\"(.*?)\"/\$text=~\/$1\/i/;
s/\$\d-?/\$text/;
#change commands to have a quest:: prefix
s/(say|emote|shout|spawn|echo|summonitem|castspell|depop|cumflag|flagnpc|flagclient|exp|level|safemove|rain|snow|givecash|pvp|doanim|addskill|me)\(/quest::$1\(/;
print OUT $_;
}
close IN;
close OUT;
}
@ARGV = qw(.) unless @ARGV;
find( sub {/^.*\.qst\z/s && convert("$_")}, @ARGV);