PDA

View Full Version : CONTRIBUTION: Accessing $text, $mname, etc. in a plugin::sub()


Shendare
05-06-2009, 11:59 PM
Background: I'm a veteran (1999) EQ player and hobby programmer who is very new to the EQEmu scene and never looked at Perl until a few days ago, so I apologize ahead of time if I say or do anything n00bish.

It is way cool that one can put common subs into a 'plugins' file and access them from any other .pl file in the quest folders.

It is unfortunate that the 'plugins' subs do not have access to the game variables ($text, $mname, %itemcount, etc.).

I noticed that in order to get around this complication, everyone has been manually passing the required game variables as arguments to the plugin subs so they can be read (and in the case of \%itemcount, manipulated) as needed.

To me this seemed unintuitive and difficult to maintain, so I looked for a better way to get around the problem.

I ran across someone else's post (which I unfortunately cannot find anymore) somewhere on the forums here, which utilized Perl's 'caller(x)' function to access variables in the top-level scope, and knew I was on the right track.

After researching the caller() function and modifying the original post's code, I was able to come up with a simple function that gives any sub in any context access to ALL of the game variables accessible to EVENT_SAY et al ($name, $text, %itemcount, etc.), without having to pass them as arguments anymore!

Function: var($varName) -- Returns a reference to the game variable requested.

# var - Access a quest-related game variable from within any context
# Parameter: varName
sub var
{
my $varName = shift;

# Strip leading $, @, or % symbol from variable name
$varName =~ s/^(\$|@|%)//g;

if ($varName eq 'mob')
{
# Shortcut: Requesting $mob steps into EntityList to get $mob reference for the current NPC.

my $entity_list = plugin::val('entity_list');

return \$entity_list->GetMobID(plugin::val('mobid'));
}
else
{
my(@context, $level, $fullname);

# Step back through the call stack until we get access to the main (non-plugin) variables
for ($level = 1; $level < 10; $level++)
{
@context = caller($level);
last if (($fullname = substr($context[3], 0, index($context[3], ':') + 2)) ne 'plugin::');
}

# Sanity check. If something goes horribly wrong and we can't step out of the plugin:: namespace, just return an empty string.
return '' if ($level >= 10);

$fullname .= $varName;

if ($varName eq 'itemcount')
{
# Hash reference
return \%$fullname;
}
elsif ($varName eq 'NonExistentArrayVariable') # -- Don't know of one off the top of my head, but if there is one, check for it here.
{
# Array reference
return \@$fullname;
}
else
{
# Scalar reference
return \$$fullname;
}
}
}


Now, you can access $text, $mname, $platinum, etc. from inside a global plugin::xyz() sub as easily as in the following example. The example assumes you've put the above var() function in a .pl file in your plugins directory.

In NPCID.pl... (Heck, even default.pl)

sub EVENT_SAY
{
plugin::defaultSay();
}


In plugins/defaultActions.pl (example filename)...

sub defaultSay()
{
my $text = plugin::var('$text');
my $mname = plugin::var('$mname');

if ($$text =~ /test/i)
{
quest::say("Oh, you're performing a test, are you? Alright, then! My name is $$mname!");
}
}


Go ahead and copy/paste and try it out!

Note that because var() returns references, you'll have to use $$text instead of $text, etc. See val() and setval() below for a split-sub workaround if this irritates you.

EXCEPTION: In the case of accessing %itemcount from EVENT_ITEM, you'll have to define and use it as follows:


sub defaultItem()
{
my $itemcount = plugin::var('itemcount');

if ($itemcount->{21779} == 1)
{
quest::say('A bandage? What do I need this for? Here, have it back and take this one some other dummy handed me earlier, too.');

$itemcount->{21779} = 2;
}
}


That's simply the way it works. Check plugins/check_handin.pl's check_handin() sub for corroboration.

Now, since I got that working, I went a step further and made a couple of shortcut functions:


# val - Shortcut that returns the -value- of a quest-related game variable instead of a reference to it
# Parameter: varName
sub val
{
return ${plugin::var($_[0])};
}

# setval - Shortcut that sets a scalar quest-related game variable from within any context
# Parameters: varName, newValue
sub setval
{
${plugin::var($_[0])} = $_[1] if (@_ > 1);
}


If you don't like using $$text and $$mname, you can use the val() function instead as follows:

Replacing the defaultActions.pl->defaultSay() function listed above...

sub defaultSay()
{
my $text = plugin::val('$text');
my $mname = plugin::val('$mname');

if ($text =~ /test/i)
{
quest::say("Oh, you're performing a test, are you? Alright, then! My name is $mname, just as if the defaultSay() code was used directly in EVENT_SAY!");
}
}


Note that if you use val() so you can access the variables with a single $, you will have read-only access to them, because you've gotten copies of the values of the quest variables, instead of references to the variables themselves.

This is not a problem for the game classes ($client, $entity_list, etc.), since they're read-only anyway.

However, if you want to use single $ syntax and still be able to change global variables' values from time to time, you can use the setval() function, as follows:

In NPCID.pl...

sub EVENT_SAY
{
quest::say("BEFORE: $text");

plugin::changeText();

quest::say("AFTER: $text");
}


In a plugins/whatever.pl file...

sub changeText()
{
plugin::setval('$text', 'Change comes from within!');
}


Hailing the NPC should then display:

NPC says, 'BEFORE: Hail, NPCName'
NPC says, 'AFTER: Change comes from within!'

Try it out! Let me know if it looks like I missed something or if anything could be simplified or optimized! (Or if I typoed something in my examples. I tried copy/paste and testing before submitting the post!)

- Shendare