PDA

View Full Version : Sub and Function Files for Perl?


trevius
01-09-2010, 10:14 PM
I was thinking it would be cool if we had an easy way to force our current scripts to read a function or subroutine file prior to reading the actual script file. This would allow some repeatable sections of scripts to be created as functions which are run through 1 or 2 files to handle the sub or function part of the script. This is probably fairly basic stuff for you Perl people out there, but I didn't know it was even possible until recently and I think it would be awesome if I could get it working lol.

The idea is that we might have a file named "perl_functions.pl" in our quest directory and for any NPC script that we wanted to have access to the functions in that file, we would add a line similar to this:

require "./home/eqemu/server/quests/perl_functions.pl";

And then the NPC script would be able to use any of the functions set from that file.

So, for those not familiar with a system like this, here is an example of how it would work:

TestNPC.pl
require "./home/eqemu/server/quests/perl_functions.pl";

Sub EVENT_ATTACK {
&CastSpellOnTarget(9425);
}


perl_functions.pl
sub CastSpellOnTarget {
my $CastSpellID = $_[0]; #Use the Spell ID Supplied to the Function - "$_[0]" means to use the first argument given
my $Cur_Target = $npc->GetTarget(); #Get the current Target for this NPC
if ($Cur_Target) { #Only cast if the NPC actually has a Target
my $My_Target = $Cur_Target->GetID(); #Get the Entity ID of the Target
$npc->CastSpell($CastSpellID, $My_Target); #Cast the requested Spell ID on the NPC Target
}
}

(Note: this section of code would be much more useful in an EVENT_TIMER where the $client is not exported)

With that section of script being set as a function that can then be called at any point, we can reduce the overhead within long scripts that repeat the same section of scripting over and over.

At this point, I just can't get the require to work right to run functions from other files like I need for a system like this. Does anyone know how to do that?

If we can get this working, I think it would allow for some special perl functions to be made that don't necessarily need to be added into the source code. Then, we could even add a page on the wiki that contains a compiled list of functions different people have made so everyone can make use of them just by adding the require line (or whatever is needed) to any NPC scripts they might want to use it on.

pfyon
01-09-2010, 10:18 PM
I would really like all of the perl functions listed like this (if that's possible), particularly since I'd like to be able to overload some of them (quest::say actually).

Admittedly, I don't really understand how the perl scripts interact with the server, the whole C/perl combination thing confuses me.

trevius
01-09-2010, 10:32 PM
Yeah, I don't quite understand how they interact either yet, which is why I posted this lol. But, if we can get it working, I imagine you would use Quest Objects and make functions to use them to replace stuff like quest::say(). For example, you might make something like this:

TestNPC.pl
require "./home/eqemu/server/quests/perl_functions.pl";

Sub EVENT_SAY {
if ($text =~/hail/i)
{
&NPCWhisper("Hello, $name!"); #Call the function from the perl_functions.pl file
}
}


perl_functions.pl
sub NPCWhisper {
my $TextColor = 7; #Set the Text Color for the Message (this one is beige)
my $MyMessage = $_[0]; #Use the Message Supplied to the Function - "$_[0]" means to use the first argument given

if ($client) {
$client->Message($TextColor, "-"); #Spacer between Text messages to make them easier to read
my $NPCName = $npc->GetCleanName(); #Get the clean name of the NPC sending the message
$client->Message($TextColor, "$NPCName whispers, '$MyMessage'"); #Send a message to the player simulating a whisper directly to them from the NPC
}
}

pfyon
01-09-2010, 11:21 PM
I was working on a script to change languages of every npc in the game to something more akin to their race, as part of some customization for my server to make languages feel more important.

I wanted to overload quest::say() to automatically do a lookup on the npc's race and then select a language that I had set in a data structure.

trevius
01-10-2010, 12:53 AM
Yeah, if someone can help me get this system working properly to let NPC quest files access other files, you could probably build a function that includes an array or hash that will convert each race to whatever language you want that race to speak and then have them speak in that language. It might work something like this:

TestNPC.pl
require "./home/eqemu/server/quests/perl_functions.pl";

Sub EVENT_SAY {
if ($text =~/hail/i)
{
&RacialLanguageSay("Hello, $name!");
}
}


perl_functions.pl
sub RacialLanguageSay {

my $MyMessage = $_[0]; #Use the Message Supplied to the Function - "$_[0]" means to use the first argument given
my $NPCRace = $npc->GetRace(); #Get the Race of the NPC that is Speaking

# Create a hash of each language that each race should use
%RaceLanguages = (
1 => 1, # NPC Race - Language Note
2 => 2, # NPC Race - Language Note
3 => 3, # NPC Race - Language Note
4 => 4, # NPC Race - Language Note
5 => 5, # NPC Race - Language Note
6 => 5, # NPC Race - Language Note
7 => 5, # NPC Race - Language Note
8 => 6, # NPC Race - Language Note
9 => 7, # NPC Race - Language Note
10 => 8, # NPC Race - Language Note
11 => 9, # NPC Race - Language Note
12 => 9, # NPC Race - Language Note
13 => 9, # NPC Race - Language Note
14 => 2, # NPC Race - Language Note
15 => 3, # NPC Race - Language Note
16 => 4, # NPC Race - Language Note
17 => 12, # NPC Race - Language Note
18 => 15, # NPC Race - Language Note
19 => 15, # NPC Race - Language Note
20 => 15, # NPC Race - Language Note
21 => 19, # NPC Race - Language Note
22 => 19, # NPC Race - Language Note
23 => 19, # NPC Race - Language Note
24 => 2, # NPC Race - Language Note
25 => 3, # NPC Race - Language Note
26 => 4, # NPC Race - Language Note
27 => 5, # NPC Race - Language Note
28 => 6, # NPC Race - Language Note
);
#Note that the above languages and races are just made up as examples

quest::say($MyMessage, $RaceLanguages{$NPCRace});

}

This would then be usable from any of your scripts as long as you added the require (or whatever is ultimately needed) to get your NPC script to read the perl_functions.pl file. Stuff like this is exactly why I want to get this system working. It could allow more flexibility to servers without requiring updates to source code. It might make perl a bit more load, but I don't really know enough to say if it would be a noticeable impact or not.

BTW, here is a page that explains require(), use(), Libaries, Modules and other similar things that might help in getting this to work.

http://www.perl.com/pub/a/2002/05/14/mod_perl.html?page=3

I have tried a few ways, but no luck so far.

pfyon
01-10-2010, 01:08 AM
Ah right, I understand what you were trying to say now.

Yeah, it would be nice if we could have an included file or (more extensible), a folder that perl would include for user defined functions.

trevius
01-10-2010, 01:17 AM
I haven't ever really played with the quest plugins in the plugins folder, so maybe they already do what I am wanting to do. Though, it is my understanding that you have to restart the whole server to get new plugins to take effect.

If plugins can already do it, then I will just get started on some new plugins I guess lol.

Here is an example plugin I have in my plugins folder (I do not currently use it):

soulbinders.pl

#!/usr/bin/perl

sub soulbinder_say {
my $text = shift;
if($text=~/hail/i){
quest::say("Greetings $name. When a hero of our world is slain their soul returns to the place it was last bound and the body is reincarnated.
As a member of the Order of Eternity it is my duty to [bind your soul] to this location if that is your wish.");
} elsif($text=~/bind my soul/i) {
quest::say("Binding your soul. You will return here when you die.");
quest::selfcast(2049);
}
}


If this is doing what I think it is, then I should be able to call that just by doing the following in any NPC script:

TestNPC.pl
Sub EVENT_SAY {
plugin::soulbinder_say($text);
}

LOL, if that is true, then I will be bonking myself for not looking into plugins a long time ago. Then, we will just need a way to reload the plugins without having to restart the server to start using the changes/additions.

KLS
01-10-2010, 04:45 AM
Yeah plugins basically work like this. It's plugin not pluggin though.

trevius
01-10-2010, 05:13 AM
Oops, typo. I fixed it, thanks.

So, I am guessing that plugins get loaded into memory when the server starts? That would probably explain why they only update when the server is restarted. Any possibility of a way to make them update without a restart? If not, no biggie, but it would be helpful for testing purposes. I plan to add a decent number of plugins now that I know how to use them somewhat.

I will still need to figure out how the "shift" works for using the arguments, but it doesn't seem too hard. I also need to to play around with it and see how well it works for returning data. I have read that for certain perl features similar to this to work, the result of the file must always be true. That is why the files all end with "1;" as the last line. I am not sure that this is required for our plugin system yet, but some testing can probably determine that.

Derision
01-10-2010, 05:24 AM
require does work.

If I put this in /tmp/myfunc.pl

sub MyFunc {
quest::say("This is MyFunc().");
}

return 1;


And this in Guard_Bixby.pl


require ("/tmp/myfunc.pl");

sub EVENT_SAY {
quest::say("Before call to MyFunc().");
MyFunc();
quest::say("After call to MyFunc().");
}


It works fine. Apparently you need the 'return 1' at the end of the required .pl file to tell Perl any initialisation code in that file executed OK, otherwise it returns this error:


/tmp/myfunc.pl did not return a true value at Guard_Bixby.pl line 1.

trevius
01-10-2010, 05:41 AM
Ahh sweet! Thanks Derision!

I kept messing with it, and did try returning a 1 result a couple of times, but I don't think I hit the right combo of how to get it to work. It may just have been that I didn't put a full path in the require and assumed that it would look for the file in the same directory as the NPC script was running from. That was on my list of things to try still, but hadn't gotten that far yet and not at home to test it out.

At the very least, using require should be a perfect way to test out plugins without having to reload the server to do so. Then, you just test them out with require and adjust the needed stuff afterward to use it as a plugin instead for the next restart.

trevius
01-10-2010, 09:43 AM
Yup, I tested the require as Derision posted it, and it works perfectly as far as I can tell. Here is an example one that I made and tested tonight:

/home/eqemu/server/quests/functions/script_functions.pl
(note I added a new folder named "functions" to my quests directory)
# Script file to hold various subroutines and functions for multiple scripts to use

sub NPCWhisper {
my $TextColor = 315; #Set the Text Color for the Message (this one is beige)
my $MyMessage = $_[0]; #Use the Message Supplied to the Function - "$_[0]" means to use the first argument given

if ($client) {
$client->Message($TextColor, "-"); #Spacer between Text messages to make them easier to read
my $NPCName = $npc->GetCleanName(); #Get the clean name of the NPC sending the message
$client->Message($TextColor, "$NPCName whispers, '$MyMessage'"); #Send a message to the player simulating a whisper directly to them from the NPC
}
}

return 1; #always leave this line at the end of the script_function.pl file as it is needed by require()


And here is an example NPC script that makes use of that function:

TestNPC.pl:

require '/home/eqemu/server/quests/functions/script_functions.pl';

sub EVENT_SAY {
if ($text=~/Hail/i)
{
NPCWhisper("Hello, $name. How do you like my whisper?);
}
}

The cool part about this function is that you can open almost any script and do a find/replace and replace the words "quest::say" with "NPCWhipster" and it should automatically convert all say messages into whispers that no one else can hear.

KLS
01-10-2010, 02:42 PM
Plugins should be reloading when you do a #reloadqst.

joligario
01-28-2010, 05:39 PM
Plugins should be reloading when you do a #reloadqst.

FYI: Yes, plugins reload fine from what I tested.

trevius
01-28-2010, 06:07 PM
Yeah, I had always read that they required a reboot and wasn't able to get the ones I was making working properly when I tested, so I assumed that was how it worked. But, after more work and testing, they definitely do update with a #questreload.

The only problem I see with plugins vs a require() function/sub is that $npc, $client and such are not available inside the plugin unless they are passed into the plugin as an argument. I think plugins would be more useful if they could be passed those variables without having to put them in as an argument.

Dibzahab
05-14-2010, 10:22 AM
Late to the party here, but that's always been possible. If load-order is controlled, you can just overwrite the function directly by using a different scope modifier - i.e. sub qstxxxsomescope::sub_whisper. Furthermore, since pretty much everything in Perl is a higher order function, you can make a function or even a closure to pass to another function... this would allow you to, for example, alter a quest on the fly for any mob in the game. You could use this to do stuff like have a central quest giver who assigns dynamic and random tasks - i.e., go talk to this guy or that guy while modifying the target's scripts all on the fly, etc. You can modify the behavior of every mob in the zone using these overrides in as little as one command.