View Full Version : Say Links
trevius
04-13-2009, 04:55 AM
This is an interesting idea I had a while back, but never really put much thought into it until recently. Basically, the idea is that we could make a way for quest text from NPCs to have links in them that would essentially look like item links, but instead of showing the item when you click them, it would make the NPC respond as if you had said what ever the name of the link was.
For example, I could have an NPC that says something like this:
Soandso says,'Would you like to test a say link?'
And, if you clicked the word "test" there, it would reply as if you had typed "/say test".
So far, I have the basic concept working. I have been able to get this to work with a predefined catch phrase. If we could figure out how to get the actual link name from the quest script, I think it would be pretty easy to make this fully functional.
Here is what I have so far:
NOTE: This is test code, not a finished product yet. Just for development purposes
In client_packet.cpp, replace the following:
void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
{
if(app->size != sizeof(ItemViewRequest_Struct)){
LogFile->write(EQEMuLog::Error, "Wrong size on OP_ItemLinkClick. Got: %i, Expected: %i", app->size, sizeof(ItemViewRequest_Struct));
DumpPacket(app);
return;
}
DumpPacket(app);
ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer;
//todo: verify ivrs->link_hash based on a rule, in case we don't care about people being able to sniff data from the item DB
const Item_Struct* item = database.GetItem(ivrs->item_id);
if (!item) {
Message(13, "Error: The item for the link you have clicked on does not exist!");
return;
}
ItemInst* inst = database.CreateItem(item, item->MaxCharges, ivrs->augments[0], ivrs->augments[1], ivrs->augments[2], ivrs->augments[3], ivrs->augments[4]);
if (inst) {
SendItemPacket(0, inst, ItemPacketViewLink);
safe_delete(inst);
}
return;
}
With this:
void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
{
if(app->size != sizeof(ItemViewRequest_Struct)){
LogFile->write(EQEMuLog::Error, "Wrong size on OP_ItemLinkClick. Got: %i, Expected: %i", app->size, sizeof(ItemViewRequest_Struct));
DumpPacket(app);
return;
}
DumpPacket(app);
ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer;
//todo: verify ivrs->link_hash based on a rule, in case we don't care about people being able to sniff data from the item DB
const Item_Struct* item = database.GetItem(ivrs->item_id);
if (!item) {
if (ivrs->item_id == 999999)
{
this->ChannelMessageReceived(8, 1, 100, "test");
return;
}
else
{
Message(13, "Error: The item for the link you have clicked on does not exist!");
return;
}
}
ItemInst* inst = database.CreateItem(item, item->MaxCharges, ivrs->augments[0], ivrs->augments[1], ivrs->augments[2], ivrs->augments[3], ivrs->augments[4]);
if (inst) {
SendItemPacket(0, inst, ItemPacketViewLink);
safe_delete(inst);
}
return;
}
Then, to test that, you just set an NPC to use this example quest script:
sub EVENT_SAY {
my $clientver = $client->GetClientVersion(); #this is just so the example works for all clients
my $test_sof = sprintf("%c%06X%s%s%c",0x12,999999,"00000000000000000000000000000000000000000000","test",0x12);
my $test = sprintf("%c%06X%s%s%c",0x12,999999,"000000000000000000000000000000000000000","test",0x12);
if($text=~/hail/i && $clientver == 3) {
quest::say("Would you like to $test_sof a say link?"); }
if($text=~/hail/i && $clientver != 3) {
quest::say("Would you like to $test a say link?"); }
if ($text =~/test/i) {
quest::say ("This test was a success!"); }
}
Then, simply hail the NPC and click the "test" link in the message and it will reply appropriately.
I am sure we could make a quest command that would make creating say links very quick. It could be something like quest::saylink(message), where "message" would be the name of the link. The only hard part would be figuring out a way to pass the message string into the Handle_OP_ItemLinkClick function. I don't know if it would even be possible, but if so, it should be easy to finish this off. It is too bad that the name of the item link isn't passed from the client when they click a link, or this would be extremely simple to finish off. I just can't think of any way to possibly pass that string into this function.
This definitely isn't a high priority or anything, but I thought it would be pretty cool to see working. I am actually surprised SOE hasn't done something like this yet. I think it would definitely bump the EQ quest system a little closer to being current. Not that I mind typing them out, but I bet some players would really like it. Of course, players would still be able to type it out if they preferred to do so.
If anyone has any suggestions on how to finish this off, I am sure some people would enjoy playing with it :)
Edit:
The only way I can think of to maybe get this working would be to have the quest::saylink() command set to automatically generate unique bogus item IDs for every saylink used in each quest file and store them to memory when the server boots up. It would basically be like generating a list of fake items, accept it shouldn't take too much memory since it only needs an item ID and then the message, not all other item stats. As long as it started at item ID 500k on up, I don't think we would ever have to worry about real item IDs ever overlapping into the say link ones. This would be above my coding skill level to write though, but maybe there is a simpler way to do it.
warhawk
04-19-2009, 11:43 AM
Trev
This is a great idea. I've always hated the quest system (typing text) in EQ.
Good luck with it.
War
trevius
04-19-2009, 05:01 PM
I have been thinking about this a bit more and I think I know of a way to make it work smoothly with the minimal amount of work. Here is what I am thinking:
1. Add a new table named "saylinks" that has 2 fields: ID and Phrase. The ID field would just be an auto-incrementing ID number. The Phrase field would be a the string for the actual phrase we are wanting to create a link fro.
2. Add an in-game command that will allow admins to add a phrase to the new saylink table. This command would be called something like #addsaylink <message> and it would just add the new message and assign it to the auto-incremented ID. This way, an admin can go through their quest prompts from in game and add the links as they see them. This is optional, but I think it would be a nice feature to help simplify the process. Of course, people could simple update the table manually if they preferred.
3. Create a new quest command that would generate the hash of the item link directly into a script text message. For example:
quest::say("This is a quest::saylink(link) example.");
And that line would show this when the NPC says it:
Soanso says,'This is a link example'
This may be one of the hardest parts, because I am not sure how to have it convert the quest command directly into the hash when it is inside of a string like that. I am pretty sure that quest::ChooseRandom() works inside of a string like that, so I may have to do some looking into how it works for that command. Otherwise, we would have to do something like this instead:
my $link = quest::saylink(link);
quest::say("This is a $link example.");
Which should be easy enough to do, but for scripting purposes, it would make things much quicker and simpler to do it the first way inside the actual string.
4. The quest::saylink() command would be set to query the saylink table in the database. Since the ID numbers would start at 1 and go up from there, the saylink quest function would then simple add 1 million to that number to create the item_id number for the hash. So, if the table had the first phrase as ID 1, that would be converted to 1000001 as the ID number. Then, the Handle_OP_ItemLinkClick would just be set to look for any item_id over 1 million and handle any that are as a say link.
5. Once the Handle_OP_ItemLinkClick finds a hash that needs to be treated as a say link, it will just do a simple database query in the saylink table for the phrase that matches that item ID. Since the item ID had 1 million added to it, it would need to subtract 1 million before doing the query. Then, it would simply parse it like it does in the code I first submitted here.
I think the hard parts are going to be getting the quest command to output an item link from directly within a string, and also to get the queries working perfectly. I am not much of a query guru, so I may need help to make sure the query is set properly. Basically it would just be querying the string that would be the hard part. We would need to make sure that it finds the exact match, and not just a partial or one that just contains the string.
The saylink table would be set to use the phrase as the key, so there wouldn't be multiple entries for the same phrase. There isn't any need to have more than 1 entry for the same string anyway.
Last, there would just need to be a few error messages set in place to report if something is missing. Like, if you try to use quest::saylink() without actually having that string in the saylink table. It could have the NPC give some warning about needing to update the database.
I don't know how many admins would use a system like this. I just think it would be something interesting and new. I have a feeling that once people actually see it working, it will grow on them quickly.
trevius
04-19-2009, 07:28 PM
Just thought of a way to make this considerably more simple to use. Basically, everything would remain the same, accept you would no longer need to update the saylink table manually or via commands at all. The only thing people wanting to use this system would need to do would be to add the quest::saylink() functions into their quest::say() messages to replace the current ones they have set.
The idea is that the quest::saylink() command would do it's query of the saylink table to see if it had a matching string. If it doesn't find a match, the new idea is that it would simply create one itself at the time the link is generated. So, once the scripts have the quest::saylink() command added, as players go through the quest prompts in-game, it will populate the saylink table automatically and then save them for future use.
I think this is probably about as simple as it can get. Now, if only I can figure out how to actually make it do all of that properly :P
By using this system along with the $client->Message function, you could in theory have complete conversations between the player and an NPC without any other player seeing a single message. This seems like it might be useful in highly populated city areas to reduce spam considerably. It also might make some players feel more comfortable to go through all of an NPC's quest prompts. I know at least a few people who feel like they are spamming others when they speak with NPCs, and so they are shy about doing it. I can't wait to try this system out if it ever gets completed. Any suggestions are more than welcome :)
realityincarnate
04-19-2009, 09:33 PM
I might be missing something, but why mess with the database at all? What about structuring it something like:
quest::saylink(text,value)
and have the function return a link to a fictitious item. You could use your plan of using item values over 1,000,000, or maybe negative item values if you wanted to be sure of never interfering with anything. Then you could adjust the code to activate a ChannelMessageReceived function based on the value you entered.
So, for example quest::saylink("test",1) would create a link on the word "test." When you clicked it, it would send a message of SayLink1, or something along those lines. That way, you could have multiple saylinks in one quest without too much complexity. I won't have a chance to look at the source for a bit, but I have a basic idea of how it might be coded.
realityincarnate
04-20-2009, 01:44 AM
I played around with the idea a little bit tonight, and here's what I've come up with as a first attempt.
Index: zone/client_packet.cpp
================================================== =================
--- zone/client_packet.cpp (revision 437)
+++ zone/client_packet.cpp (working copy)
@@ -2163,8 +2166,17 @@
const Item_Struct* item = database.GetItem(ivrs->item_id);
if (!item) {
- Message(13, "Error: The item for the link you have clicked on does not exist!");
- return;
+ if (ivrs->item_id > 1000000)
+ {
+ char response[20];
+ sprintf(response,"SayLink%u",ivrs->item_id - 1000000);
+ this->ChannelMessageReceived(8, 1, 100, response);
+ return;
+ }
+ else {
+ Message(13, "Error: The item for the link you have clicked on does not exist!");
+ return;
+ }
}
ItemInst* inst = database.CreateItem(item, item->MaxCharges, ivrs->augments[0], ivrs->augments[1], ivrs->augments[2], ivrs->augments[3], ivrs->augments[4]);
@@ -7155,7 +7167,7 @@
if(!p_timers.Load(&database)) {
LogFile->write(EQEMuLog::Error, "Unable to load ability timers from the database for %s (%i)!", GetCleanName(), CharacterID());
}
-
+
#ifdef _EQDEBUG
printf("Dumping inventory on load:\n");
m_inv.dumpInventory();
Index: zone/perlparser.cpp
================================================== =================
--- zone/perlparser.cpp (revision 437)
+++ zone/perlparser.cpp (working copy)
@@ -2446,7 +2446,31 @@
XSRETURN_UV(quantity);
}
+XS(XS__SayLink);
+XS(XS__SayLink) {
+ dXSARGS;
+ if (items != 2)
+ Perl_croak(aTHX_ "Usage: saylink(text, saylinkID)");
+ dXSTARG;
+ Const_char * RETVAL;
+ char text[250];
+ uint16 ID;
+
+ strcpy(text,(char *)SvPV_nolen(ST(0)));
+
+ ID = (int)SvUV(ST(1));
+
+ RETVAL = quest_manager.SayLink(text,ID);
+
+ sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG;
+ XSRETURN(1);
+}
+
+
+
+
+
/*
This is the callback perl will look for to setup the
quest package's XSUBs
@@ -2617,6 +2641,7 @@
newXS(strcpy(buf, "updatespawntimer"), XS__UpdateSpawnTimer, file);
newXS(strcpy(buf, "MerchantSetItem"), XS__MerchantSetItem, file);
newXS(strcpy(buf, "MerchantCountItem"), XS__MerchantCountItem, file);
+ newXS(strcpy(buf, "saylink"), XS__SayLink, file);
XSRETURN_YES;
}
Index: zone/questmgr.cpp
================================================== =================
--- zone/questmgr.cpp (revision 437)
+++ zone/questmgr.cpp (working copy)
@@ -1821,3 +1821,10 @@
return Quant; // return the quantity of itemid (0 if it was never found)
}
+
+const char * QuestManager::SayLink(char* text, uint16 value) {
+ char linktext[250];
+ sprintf(linktext,"%c%06X%s%s%c",0x12,1000000+value,"000000000000000000000000000000000000000",text,0x12);
+ strcpy(text,linktext);
+ return text;
+};
Index: zone/questmgr.h
================================================== =================
--- zone/questmgr.h (revision 437)
+++ zone/questmgr.h (working copy)
@@ -197,6 +197,8 @@
void MerchantSetItem(int32 NPCid, int32 itemid, int32 quantity = 0);
int32 MerchantCountItem(int32 NPCid, int32 itemid);
+
+ const char* SayLink(char* text, uint16 value);
//not in here because it retains perl types
//thing ChooseRandom(array_of_things)
It only does the Titanium formating at the moment, and it will break if there are ever legitimate item ids over 1 million, but I tested a couple of different things and it seems to do all right. There's not much in the way of error checking, so it'll probably crash the zone if anything too strange gets entered as parameters.
The syntax is quest::saylink(message,n)
And when the link is clicked, it sends a message "SayLinkn". Here was my simple test script:
my $test1 = quest::saylink("trying,1");
my $test2 = quest::saylink("forget,2");
sub EVENT_SAY {
if($text=~/Hail/i) {
quest::say("I've noticed that sometimes certain words I say just feel... different. Should I keep $test1 to see what I can do with them, or just $test2 the whole thing?");
}
if($text=~/SayLink1/) {
quest::say("You're right, I bet I can make something out of them.");
}
if($text=~/SayLink2/) {
quest::say("I knew it was too much to hope for.");
}
}
trevius
04-20-2009, 04:52 AM
Thanks for the info :)
I had thought about doing something like that, but it wouldn't be quite as ideal as what I am wanting to do with it. Basically, I want to make the saylinks an option to players. So, if they prefer clicking the links, they can click them, but if they prefer to just type the phrase, they can do that as well. And, it wouldn't require any extra scripting to handle the saylinks or normal say messages. I know at least a few players who play almost solely with the keyboard and only ever have to touch their mouse when they are looting a corpse.
The idea you had would work fine, but it would make quest scripting a bit more involved, which is one of the main things I am trying not to do. If I can't get the way I am wanting to do it working, we could definitely put it in the way you have it coded though.
Here is the difference of what it would take to write the same exact quest to use your code as it would for what I am suggesting (including the option to /say the phrase):
Your idea:
my $test1 = quest::saylink("test,1");
sub EVENT_SAY {
if($text=~/Hail/i) {
quest::say("Would you like to $test1 a say link?");
}
if($text=~/SayLink1/) {
quest::say("The test was a success!");
}
if($text=~/test/i) {
quest::say("The test was a success!");
}
}
My idea:
my $test1 = quest::saylink("test");
sub EVENT_SAY {
if($text=~/Hail/i) {
quest::say("Would you like to $test1 a say link?");
}
if($text=~/test/i) {
quest::say("The test was a success!");
}
}
Not really a huge deal, but it would get tougher to manage if you have long scripts with lots of phrases and text.
I really appreciate the help on this though. I think it will be something cool to play with when it is all done. I bet players will be really surprised to see it when it first shows up on servers. And, I bet it isn't long before EQLive has something similar lol.
janusd
04-20-2009, 07:39 AM
What're you saying Trev? That we've had some great ideas that showed up on Live? Why, that just means SoE listens to its fanbase. ;)
realityincarnate
04-20-2009, 12:04 PM
Ok, that's a good point. You could also probably cram a second string into the space that's normally used for augments and that sort of thing. It would mean you're limited to just under 40 characters for the response string, but that doesn't seem like it would be a problem very often.
trevius
04-20-2009, 07:34 PM
I thought about using the augments section for handling the phrase, but coding that is out of my skill range. It would have to be able to take the string and then fill in the rest of the nulls after it to make sure that it builds the link properly. Then, after the link is clicked, it would need to have a way to take each of those fields and tie them together and then convert them back into a string.
If you know how to do that, I am sure 40 characters would be plenty for almost any scenario. That would probably be the best possible way to handle say links. By putting the string into the actual link, the item ID wouldn't matter at all. So, the item ID that the quest::saylink() command creates could be hard set to 999999 (or any other invalid item ID number) for all links it creates.
Also, an a related topic, I was playing with the code you posted to handle saylinks and was trying to get it to work where you only input the item ID, and have it create a normal item link from just that. There is already a quest command for quest::itemlink(), but that command causes the NPC to send a tell to the player with a link to the item. Something I have wanted for a long time is a very simple way to create an item link to an actual item that can be set as a variable for use in the middle of a say message. On Storm Haven, we currently have to do something like this to create an itemlink:
my $cloth_cap = sprintf("%c%06X%s%s%c",0x12,1001,"000000000000000000000000000000000000000","Cloth Cap",0x12);
sub EVENT_SAY {
if ($text =~/Hail/i) {
quest::say ("Here is a link to a $cloth_cap."); }
}
While that isn't overly complex to do, it would be nice if we could do something like this instead:
my $cloth_cap = quest::itemlink(1001);
sub EVENT_SAY {
if ($text =~/Hail/i) {
quest::say ("Here is a link to a $cloth_cap."); }
}
The way you wrote the quest::saylink() command above actually works nicely for this. But it could be simplified a bit more by having it do a GetItem() and then an item->name to automatically populate the name of the itemlink. I worked with what you wrote for a while last night, but everything I tried to do was crashing the zone lol. Not trying to derail this thread at all, but figured I would mention this since I worked on it a while last night.
realityincarnate
04-21-2009, 11:04 AM
I haven't gotten a chance to look at using the augments section to store anything yet, but I did come up with some code for storing a link as a perl variable. The syntax is just $var = quest::itemlink2(item id).
My source is way too messed up for a diff right now, but here's the changes I made for this. The total string, including all the link variables and stuff, is limited to 250 chars (totally arbitrary number) right now, so no ridiculously long item names.
questmgr.h add:
const char* ItemLink2(char* perltext, int item_id);
questmgr.cpp:
const char* QuestManager::ItemLink2(char* perltext, int item_id) {
const ItemInst* inst = database.CreateItem(item_id, 20, 99999, 99999, 99999, 99999, 99998);
if (!inst) return 0; // return an empty string if the item is invalid
char* link = 0;
char* tempstr = 0;
if (initiator->MakeItemLink(link, inst)) { // make a link to the item
MakeAnyLenString(&tempstr, "%c%s%s%c", 0x12, link, inst->GetItem()->Name, 0x12); // format the string that the client receives
strncpy(perltext, tempstr,250); // the perl string is only 250 chars, so make sure the link isn't too large
safe_delete_array(tempstr); // MakeAnyLenString() uses new, so clean up after it
}
safe_delete_array(link); // MakeItemLink() uses new also
return perltext;
}
perlparser.cpp:
XS(XS__ItemLink2);
XS(XS__ItemLink2) {
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: itemlink2(itemID)");
dXSTARG;
Const_char * RETVAL;
char text[250];
uint16 itemID;
itemID = (int)SvUV(ST(0));
RETVAL = quest_manager.ItemLink2(text, itemID);
sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG;
XSRETURN(1);
}
and at the bottom of perlparser add:
newXS(strcpy(buf, "itemlink2"), XS__ItemLink2, file);
By the way, the most likely cause of your zone crashes is in passing (char *) strings back and forth between functions. It's really easy to accidently write more to the char array than you allocated and overwrite other stuff on the stack. At least, that's what seemed to cause all of my problems.
trevius
04-21-2009, 05:06 PM
Thanks, realityincarnate! I will get that tested and submitted if it tests good. I may rename it to something like quest::varlink() so it is easier to remember that it is for creating variables with item links if that is ok with you.
realityincarnate
04-21-2009, 07:28 PM
Change anything you want, I just wasn't feeling especially creative when I tried to think of what to call it. Definitely give it some real testing; all I did was try with a couple of newbie items and one fake item id to make sure it wasn't crashing anything. I'm sure there's a more efficient way to handle the string as well, but I just wanted something that didn't take much work and wouldn't cause an overflow.
I played around with the say links a little bit more, also, and I was able to force a test string into the augments, lore, etc. variables, but clicking the item link no longer triggered any response from the server at all. The normal item links still worked fine, so I'm not sure if the client does some checking of it's own, or if I just broke something. In any case, I probably won't have much chance to take a look at it for a few days, but I'm cautiously optimistic for now.
And while we're talking about links, I have what's probably a really dumb question. How do you actually create a link to an item in game? I know I've done it before, but I can't for the life of me remember how.
trevius
04-21-2009, 10:59 PM
:) You just right click the item to show it's properties and then click on the icon for the item in the item stats window. That will send a link to your chat window if that is what you were asking about.
Sounds like you are making some interesting progress on putting the string into the say link. I figured it wouldn't be too hard to get the string in there. But, getting it out correctly would be the hard part. Maybe you can change it slightly to have it make the NPC do a say message with whatever it things the string from the say link is. That should work to help debug what is happening when you click the link.
Since the packet breaks down the link into the ItemViewRequest_Structure for use in the link click handling, the string that you put in there would be split into multiple chunks. Then they just need to be put back together into a string that we can use. Maybe that wouldn't be all that hard to do afterall. We would need to get the info in Hex form, and then convert it from hex into a section of a string. Then, just create a string by adding them all together end-to-end back into 1 string.
If you get time to post what you have so far, I could look at the packet being sent by the client and see if it is sending the right string. If so, then it probably just isn't being put back together properly after it gets to the link click handling. If you are too busy though, no rush. This isn't high priority or anything :)
trevius
04-22-2009, 05:55 AM
Hmm, this is interesting; I tried your itemlink2 code and it works for the most part. There is only 1 issue I have found with it. The issue is that it only works for item IDs that are less than 65536. It appears that it is only allotting and int16 worth of space for the item ID. Maybe it is cutting it off after that for some reason. This happens on both SoF and Titanium clients.
If I use this from within the script:
my $test3 = sprintf("%c%06X%s%s%c",0x12,71709,"000000000000000000000000000000000000000","Mask of Defiant Rage",0x12);
That works perfectly fine to show the correct item link.
But, if I try to use this:
my $mask = quest::varlink(71709);
The link returns item ID 6173 (Acrylia Reinforced Sleeves). And if I convert 6173 to hex, I get 181D. And if I convert 71709 to hex, I get 1181D. So, it makes sense that it is cutting off the first 1 in 1181D. I don't know why it would do this. The only thing I can think of is that maybe it is an issue with the MakeItemLink() function.
For any item ID less than 65536 it works great though. So, once this issue is resolved, I think this command should be ready to go.
trevius
04-22-2009, 07:31 AM
Just figured out a pretty cool bonus to this new varlink quest command. Since it only requires the item ID, it makes it very easy to create arrays of items to use for a script that can be called on for creating itemlinks in say messages, or for item turn ins. So, you could have a single array that handles both, which would make things more simple and less likely to have mistakes.
Here is an example:
%epic = ("Warrior" => 60321, "Rogue" => 52347, "Monk" => 61025, "Berserker" => 18398, "Shadowknight" => 50003, "Paladin" => 64031, "Ranger" => 62627, "Bard" => 77631, "Beastlord" => 52911, "Cleric" => 9955, "Druid" => 62863, "Shaman" => 57400, "Wizard" => 12665, "Magician" => 19092, "Enchanter" => 52952, "Necromancer" => 62581);
sub EVENT_SAY {
my $class_itemlink = quest::varlink($epic{$class});
if($text=~/hail/i) {
quest::say("The $class epic 1.5 is $class_itemlink. Turn in a Cloth Cap to get yours!"); }
}
sub EVENT_ITEM {
my $class_epic = $epic{$class};
#Extremely simple Epic 1.5 quest (turn in a cloth cap!)
if (plugin::check_handin(\%itemcount, 1001 => 1)) {
quest::summonitem($class_epic);
quest::exp(45000);
quest::say ("It doesn't get much easier than that!");
}
}
That is just a basic example and I am sure it could be written much better. But the main point is that it works and could really make handling a large number of items for turn ins and item links in say messages fairly quick to do.
realityincarnate
04-22-2009, 11:17 AM
Oh yeah, it looks like I used an int16 in the perl XS part. I have no idea why I did that... I'm not sure this whole early morning/late night coding thing is such a great idea.
In perlparser.cpp change
uint16 itemID;
to
uint32 itemID;
And for the sake of consistency and to avoid any problems with much later item ids, you should probably change "int item_id" to "uint32 item_id" in the function headers in questmgr.h and .cpp too.
trevius
04-22-2009, 05:42 PM
LOL, thanks! I was looking through for where that int16 was getting set, but I didn't think to look in the perl part!
I will get those changes done and run 1 final test on it, but I am sure that will fix it. That should pretty much finish up the new way to put item links into a variable. I guess this thread can get back to the original SayLink topic :P
trevius
04-23-2009, 10:56 PM
I got the quest::varlink() command added to the SVN and also updated the wiki (here and the one I am working on to convert to MediaWiki) with the new quest command. Many thanks, realityincarnate! It works great, and that fix did correct the issue from before.
Not to figure out how to do those conversions into the aug fields and back into a string for the quest::saylink() command :)
realityincarnate
04-25-2009, 04:39 PM
I finally got another chance to look at forcing another string into the link, and it's a lot more messed up than I thought.
For some reason, the client likes to receive a text representation of the hex values for augments, etc. I probably should have realized it from looking at how the item id is handled in the original sprintf statement used to generate the links, but it just didn't sink in. So just trying to put a string in there directly causes crashes because it doesn't know what to do with, for example, a hex number represented by "ST".
I was able to trick it into holding some information, but it's much less efficient than I had hoped. I used the string "test", which has a hex representation of 74 65 73 74. The link string allocates five characters for each augment, so we can put two letters in each.
sprintf(out_text, "%c%06X""%4X%1X%4X%1X""%s%s%c",0x12,999999,'te',0,'st',0,"00000000000000000000000000000","test link",0x12);
will produce an item link that will have "te" in aug0 and "st" in aug1.
Putting them back together is more fun. Each aug slot has two letters, now represented by hex bytes, and in the wrong order. I got them back out using a struct.
struct AugIn {
char let2;
char let1;
};
and running this to build the string.
AugIn* test;
char response2[250];
sprintf(response,"%c",0x00);
for (int i = 0 ; i < 5; i++) {
test = (AugIn*) &augments[i];
sprintf(response,"%s%c%c", response, test->let1, test->let2);
}
A similar struct method could be used to make it easier to get the string into the augments in the first place. It works, but leaves us with a limit of 10 characters for the response string. This could be increased by using the other variables (evolving item info, etc), but those variables aren't even being decoded by the emulator at the moment.
In other words, I'm beginning to doubt how much advantage this has over your the original suggestion of just using the database. I'll probably continue playing with it, out of stubbornness more than anything else, but I don't think the results will be anything spectacular.
trevius
04-25-2009, 05:32 PM
Interesting stuff. I am not sure I understand why it is that only 2 letters can be used per aug slot. If it has 5 bytes per aug, couldn't we set 5 chars per aug? Then break the string into sections of 5 and maybe run some conversions on it like do atohex, then hextoi. I don't think all of the needed functions exist yet, but hextoi does in MiscFunctions.h. Then, after that, we would just do the reverse when the link was clicked. I dunno what it would take to do all of that though, but it sounds at least possible. Unless there is another reason for them only allowing 2 letters. If that is the case, then the database is probably the best way to go and should be pretty simple to do.
realityincarnate
04-25-2009, 06:52 PM
The limitation is that the client wants the link sent as text. So, to send the letter "t", which is 74 in hex, you need to send the client the characters "7" and "4". So each character we want actually needs two bytes sent to the client. Since they're in groups of five, it ends up wasting the last byte. We could probably come up with a way to combine and use those too, but it seemed like a lot of work at the time. ;)
trevius
04-26-2009, 03:08 AM
Shouldn't whatever we send be exactly what we get back when the client clicks the link? If so, we should be able to fill those characters with anything. Maybe we could simply place the string directly in there without doing anything at all to it other than making sure it doesn't exceed the 40 chars we have for it.
I think the client is going to want to look at them as ints or something, otherwise, how can you fit an aug with an itemID higher than 9999 in the string? Still not sure I am following what you are saying.
realityincarnate
04-26-2009, 01:00 PM
That's understandable, I'm not sure I know exactly what it's doing myself. But it does look like the client gets the information as text and sends it back to the server as integers.
For example, I made a link to a silver jasper ring (item 14628, 0x3924 hex). The link gets the item id as a string "03924", five bytes long: 30 33 39 32 34. When I click on the link, it sends the item id back to the server as a 4 byte integer: 24 39 00 00. The same thing happens with the augment ids. That means that the highest id # it can deal with is 1048575 (0xFFFFF).
So if you try to just fill it with whatever characters you want, it runs into trouble with anything other than 0-9 and A-F. I'm not sure exactly what it does, but when I forced the alphabet into the link string, I got back bytes: AB CD EF 00 and the zone crashed. The same thing happened whenever I used letters after "f". If I filled the evolving and hash areas with letters higher than "f", the link still appeared purple, but clicking it didn't send anything to the server at all.
If we can figure out exactly what it's doing to the other bytes, we might be able to work around it and use them, but right now I don't even know what it's sending; the zone crashes in the middle of the packet dump.
trevius
04-26-2009, 07:12 PM
Well yeah it should be sending the info back as ints because that is how we have the structure set to interpret them. It should just be a matter of converting that int back into hex and then into a part of a string and combining them.
If what you are saying about the IDs and strings are true, that is really weird! If so, then yeah, it is pretty useless to even try doing the item name in the string.
Sounds like that database way might be the easiest way to handle it. Should be simple enough to handle that part. Just need to play with it some I guess.
trevius
05-20-2009, 07:56 AM
Ok, I think I have this almost working. So far, I have it able to create the link properly. It will update the table with a new phrase and ID automatically if one doesn't exist. The only problem left is that I don't know how to pull a string out of a query result to use for when you click the actual link.
Here is what I have so far. Feel free to make fixes, comments, or suggestions. Also note that I have a few extra messages being sent to the client for testing purposes. Those can be removed when this is finished.
Here is the part that is not working:
client_packet.cpp
void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
{
if(app->size != sizeof(ItemViewRequest_Struct)){
LogFile->write(EQEMuLog::Error, "Wrong size on OP_ItemLinkClick. Got: %i, Expected: %i", app->size, sizeof(ItemViewRequest_Struct));
DumpPacket(app);
return;
}
DumpPacket(app);
ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer;
//todo: verify ivrs->link_hash based on a rule, in case we don't care about people being able to sniff data from the item DB
const Item_Struct* item = database.GetItem(ivrs->item_id);
if (!item) {
if (ivrs->item_id > 500000)
{
char* response;
int sayid = ivrs->item_id - 500000;
if (sayid && sayid > 0)
{
const char *ERR_MYSQLERROR = "Error in saylink phrase queries after clicking the link";
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `phrase` FROM saylink WHERE `id` = '%i'", response),errbuf,&result))
{
if (mysql_num_rows(result) >= 1)
{
while((row = mysql_fetch_row(result)))
{
response = new char[strlen(row[0]) + 1];
strcpy(response, row[0]);
}
mysql_free_result(result);
}
mysql_free_result(result);
safe_delete_array(query);
}
else
{
Message(13, "Error: The saylink (%s) was not found in the database.",response);
safe_delete_array(query);
return;
}
}
Message(13, "The Respone is %s.",response);
this->ChannelMessageReceived(8, 1, 100, response);
return;
}
else {
Message(13, "Error: The item for the link you have clicked on does not exist!");
return;
}
}
ItemInst* inst = database.CreateItem(item, item->MaxCharges, ivrs->augments[0], ivrs->augments[1], ivrs->augments[2], ivrs->augments[3], ivrs->augments[4]);
if (inst) {
SendItemPacket(0, inst, ItemPacketViewLink);
safe_delete(inst);
}
return;
}
Here is the rest that seems to work just fine, but could probably use some looking over by a more experienced coder:
questmgr.cpp
const char* QuestManager::saylink(char* Phrase) {
const char *ERR_MYSQLERROR = "Error in saylink phrase queries";
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
int sayid = 0;
// Search for existing saylink table entry for this phrase
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `id` FROM `saylink` WHERE `phrase` = '%s'", Phrase),errbuf,&result))
{
if (mysql_num_rows(result) >= 1)
{
while((row = mysql_fetch_row(result)))
{
sayid = atoi(row[0]);
}
mysql_free_result(result);
}
else // Add a new saylink entry to the database and query it again for the new sayid number
{
mysql_free_result(result);
safe_delete_array(query);
database.RunQuery(query,MakeAnyLenString(&query,"INSERT INTO `saylink` (`phrase`) VALUES ('%s')", Phrase),errbuf);
safe_delete_array(query);
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `id` FROM saylink WHERE `phrase` = '%s'", Phrase),errbuf,&result))
{
if (mysql_num_rows(result) >= 1)
{
while((row = mysql_fetch_row(result)))
{
sayid = atoi(row[0]);
}
}
mysql_free_result(result);
safe_delete_array(query);
}
else
{
LogFile->write(EQEMuLog::Error, ERR_MYSQLERROR, errbuf);
safe_delete_array(query);
}
safe_delete_array(query);
}
safe_delete_array(query);
}
char linktext[250];
if (initiator->GetClientVersion() == EQClientSoF)
{
sprintf(linktext,"%c%06X%s%s%c",0x12,500000+sayid,"00000000000000000000000000000000000000000000",Phrase,0x12);
}
else
{
sprintf(linktext,"%c%06X%s%s%c",0x12,500000+sayid,"000000000000000000000000000000000000000",Phrase,0x12);
}
strcpy(Phrase,linktext);
return Phrase;
}
questmgr.h
const char* saylink(char* Phrase);
perlparser.cpp
XS(XS__saylink);
XS(XS__saylink) {
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: saylink(phrase)");
dXSTARG;
Const_char * RETVAL;
char text[250];
strcpy(text,(char *)SvPV_nolen(ST(0)));
RETVAL = quest_manager.saylink(text);
sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG;
XSRETURN(1);
}
And here is the SQL for the table:
DROP TABLE IF EXISTS `saylink`;
CREATE TABLE `saylink` (
`id` int(10) NOT NULL auto_increment,
`phrase` varchar(64) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;
This won't be a required table unless you want to use saylinks.
I think saylink is pretty close to done now. Once this is working and hopefully gets reviewed by a decent coder here, I will get it added to the SVN unless someone else wants to beat me to it. I can't wait to try this out :)
drakelord
05-20-2009, 09:52 AM
Can't you just create a new temporary string, set the query result to it, then come up with a substring from that and pass it to the variable? Might be easier than trying to edit the query directly.
trevius
05-20-2009, 05:19 PM
Drakelord, I think you are overestimating my code knowledge lol. I work purely from examples, so if I can't find something that does nearly exactly what I want to do from somewhere else in the source, I probably can't do it. I have learned a ton in the past year, but still have no technical training at all lol. I think determination is the only reason why I can even code at all.
Basically, I am asking that you give me an example of what you mean. I can't find anywhere in the source where we query to get a string and use it. Maybe if I think about it more I can figure out a place that might do that.
EDIT:
Checked into it a bit more from other stuff in the source and maybe I can just do this:
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `phrase` FROM saylink WHERE `id` = '%i'", sayid),errbuf,&result))
{
if (mysql_num_rows(result) == 1)
{
row = mysql_fetch_row(result);
strcpy(response, row[0]);
}
mysql_free_result(result);
safe_delete_array(query);
}
I will try that later and see what happens.
Oh, and I just realized that I had "response" instead of "sayid" in that query, so I corrected that. I am sure that was my problem the whole time lol (or the main one anyway).
trevius
05-21-2009, 02:25 AM
Yup, that did it! It works now :D
I am just going to clean it up a bit and get it on the SVN. If there are issues found with it at some point, they can be fixed at any time I am sure, but it seems pretty rock solid from my testing so far.
One thing I am still debating is if I should have it say the "You say" messages as if you had actually said it. Right now, I just have it set to send that message to the player that actually clicks the link, so it still seems similar to normal EQ, but that can be removed. The only concern I have with it not showing who is clicking the link is if people start spamming with it and no one can tell for sure who is doing it. If this gets to be popular, I guess we could always add a rule so admins could decide if they want the "You say" messages or whatever.
With this and $client->message, you could actually have a complete conversation with an NPC without anyone else even seeing the conversation at all. I think that has positive and negative benefits, but is definitely something interesting.
trevius
05-21-2009, 05:26 AM
I did some more testing and found that the code I had needed a couple other tweaks and stuff to prevent possible crashes, but it looks to be working perfectly now. I added this to the SVN. All you need to do is add the required SQL and it should work just fine.
To use the new quest function, simply follow this example:
sub EVENT_SAY {
my $test = quest::saylink("test a saylink");
my $click = quest::saylink("click");
my $say = quest::saylink("say");
if($text=~/hail/i)
{
quest::say("Hello, $name. Would you like to [$test]? If so, simply [$click] on the pink text and wait for my response. You may also simply /$say the message like normal if you prefer that method instead.");
}
if($text=~/test a saylink/i)
{
quest::say("The test was a huge success! Well done :P");
}
if($text=~/click/i)
{
quest::say("Yes, click the pink text exactly like you just did!");
}
if($text=~/^say$/i)
{
quest::say("Saying or clicking the text is the same thing to me!");
}
}
It's pretty cool if you ask me. Now let's see if EQLive shows up with something similar soon :p
I just added quest::saylink() to the wiki and went ahead and wrote a quick wiki page for using it as well:
http://www.eqemulator.net/wiki/wikka.php?wakka=SayLink
I have some say links setup on Storm Haven if anyone wants to log in to check them out. I have them in both the Titanium and SoF starting areas. The areas aren't full of say links yet, but the nearby NPCs from where you start will have them.
drakelord
05-21-2009, 09:54 PM
Works great Trev, except for one thing, :);
I looked through the diff files, and noticed that instead of actually sending the text to the zone channel, you send it directly to the client and to the targeted npc. The problem with this is now that we have the option for npcs to respond without being targeted, this wouldn't work if they tried to implement say links at the same time.
So, it should be an easy fix, right? Just have it actually say the reply instead of sending it directly to the npc. It will be visible to everyone sadly, but it will circumvent this problem.
trevius
05-21-2009, 10:11 PM
I don't really know how hard it would be to do that. Without a target, it crashes the zone as it is currently set. Maybe a messageclose would work, but I haven't tried that yet. Though, I think it would be a pretty extremely rare case that someone would use both proximity say and say links at the same time. Feel free to submit a working change to the code if you want though.
Everyone already sees the message as long as an NPC is actually targeted when the link is clicked. The only way to not have it send the message is to simply parse it instead of doing the message sending stuff. I originally had it set to do that, but there may be issues with doing it that way.
char* response;
int sayid = ivrs->item_id - 500000;
if (sayid && sayid > 0)
{
const char *ERR_MYSQLERROR = "Error in saylink phrase queries after clicking the link";
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `phrase` FROM saylink WHERE `id` = '%i'", sayid),errbuf,&result))
{
if (mysql_num_rows(result) == 1)
{
row = mysql_fetch_row(result);
strcpy(response, row[0]);
}
This is at best a crash and at worse something far more dangerous. You can't simply strcpy to memory that does not exist. While there's a chance because you don't initialize response that the memory does exist you're going to be overwriting random data and it's going to do *bad* things to your server.
char response[64];
Also it better not be crashing the zone just because of something as trivial as not having a target, there should be sanity checks in place to counter that.
cavedude
05-22-2009, 04:25 PM
This is at best a crash and at worse something far more dangerous.
*And by that she means the complete and total destruction of the universe. -editor
Shendare
05-22-2009, 04:55 PM
Ooh, yeah, some unstable referencing there.
Using char response[64]; instead of char* response; will solve the main problem by allocating 63 bytes for the phrase (plus one for the null terminator).
The second step would be changing strcpy() so that if someone gets funny and passes a phrase larger than 63 characters you won't be looking at a buffer overflow:
if (mysql_num_rows(result) == 1)
{
row = mysql_fetch_row(result);
strncpy_s(response, sizeof(response), row[0], _TRUNCATE);
}
trevius
05-22-2009, 05:13 PM
Yeah, Derision had already pointed out the Memory Allocation issue to me in a PM lol. I was gonna fix it on the next update I did. Simple fix anyway. And the code I posted here isn't the final code. I did add a sanity check to have it verify that they have a target before it tries to send the say message to the target. I will put the truncate in there too, thanks Shendare.
I was hoping for someone to review the code, but since it was already working and not a required feature by any means, I figured I would get what I had up there and add in any needed fixes later. Thanks for the info.
Edit: NM, I see KLS already got the mem allocation thing fixed. Thanks, KLS.
Is there any way to make it where when the player clicks a saylink, the npc they are talking to responds as if they said that but the player doesn't actually say anything?
Also, can you make the text you click on different than what is said/sent to npc?
trevius
06-06-2009, 06:30 PM
You can't make the name of the link different than what is said when it gets clicked. But, if needed, you should be able to change what your script is waiting to be said so that it catches things however you want to say them. An example would be if your NPC said "bind your soul", most players would type "bind my soul", but if you clicked the link, it would say "bind your soul". In this example, you could just have the NPC look for either "bind" or "soul" to be said so that both phrases would work as replies.
It is possible to code it so the players don't actually say anything, but it may be better that they do. All we would have to do is do the parse of the text right there instead of sending the message as a say. It probably wouldn't be bad to do it that way, other than people maybe clicking the links to annoy people and no way to tell for sure who it is causing the spam.
Krugus
06-06-2009, 11:04 PM
Just wondering if saylink is working in the Branch version ?
I see the code for it there. Table in the DB is there but its not working for me when I follow the examples in this thread or the wikki you have linked hrmmm.
Just compiled ver 635 of the Instance Branch version today. Guess I'll take a look at the regular SVN and see if there is a difference in code for the saylink.
cavedude
06-06-2009, 11:30 PM
Branch is no longer used, we're back on the trunk.
Krugus
06-06-2009, 11:37 PM
thanks for the info Cavedude :)
trevius
06-07-2009, 03:49 AM
Have you tried reading the little wiki page on Say Links?
http://www.eqemulator.net/wiki/wikka.php?wakka=SayLink
That might help a bit. They are really easy to use. Once you get the first one working, it is extremely easy to get any extra ones added.
Krugus
06-07-2009, 01:31 PM
Yes I did, but Cavedude pointed me in the right direction.
Updated and compiled the lastest Rev from the Trunk and saylinks are working now.
Thanks guys for the help :)
I think it would help a lot if there was at least an option to make the saylink not actually make the player say something. If you combined that with using $client->Message("(npc) says ...") instead of quest::say("..."), you could have completely private/spam-free interactions with npcs. It would also help a lot for my passcode script idea. (see below)
Also, I came up with a kind of cheap way in perl to make the text you click different from the text that is said:
sub Saylink
{
my $saylink = quest::saylink($_[0]);
my $length = length(substr($saylink,46)) - 1;
if($_[1]) { substr($saylink,46,$length,$_[1]); }
return $saylink;
}
It basically just creates a new saylink with the text you want it to say and then edits the string that quest::saylink() returns to change the text that you click.
So for example you could do $saylink = Saylink("option1","[Ask about the recent attacks.]"); and it would make a saylink titled "[Ask about the recent attacks.]" and when you click it, it would make you say "option1". This can help in some cases but doesn't have much practical use until you get to more advanced stuff: Like my passcode script idea.
-Passcode script idea-
If saylinks didn't actually make the player say something, you could use them for private npc interactions. One example of this would be to set a passcode that is required in order for something to happen (being able to open a door, zone somewhere, summon an item, spawn a mob, enter an instance, etc). I tried doing this in game and it works fine. The script just creates saylinks for the digits (0-9) and then a saylink for resetting the passcode and a saylink for submitting the passcode. When you click one of the digits, it adds that digit to $passcode[$cid] which is an array of passcodes where the index is the character's id (This is so any number of players can use the script at the same time without messing each other up). When you click [reset passcode], it sets $passcode[$cid] back to empty and when you click [submit passcode], it checks $passcode[$cid] against $check, the variable that the correct passcode is stored in. If they match, access is granted and you can flag the player or do whatever you wanted to do. If they don't match, it denies them access.
$check = 52213; # the passcode to check against: can also load it from a global/file/etc
sub EVENT_SAY
{
$cid = $client->CharacterID(); # gets the character's id and uses it as the index for the @passcode array
@args = split(/ /, $text); # split up $text by spaces and store each word/number/etc in the @args array
if($args[0]=~/hail/i)
{
InitSaylinks();
ShowMenu();
}
if($args[0]=~/push/i)
{
$passcode[$cid] = $passcode[$cid] . $args[1];
ShowMenu();
}
if($args[0]=~/reset/i)
{
$passcode[$cid] = "";
ShowMenu();
}
if($args[0]=~/enter/i)
{
if($passcode[$cid] eq $check)
{
$client->Message(15, "Access granted!");
$passcode[$cid] = "";
# now do whatever you wanted to do: open door, move player to location, move player to another zone, spawn a mob, summon an item, etc
}
else
{
$client->Message(13, "Wrong passcode! Access denied!");
$passcode[$cid] = "";
ShowMenu();
}
}
}
sub InitSaylinks
{
my $count = 0;
while($count < 10) {
$sl_push[$count] = Saylink("push " . $count, $count);
$count++;
}
$sl_reset = Saylink("reset","[Reset passcode]");
$sl_enter = Saylink("enter","[Submit passcode]");
}
sub Saylink
{
my $saylink = quest::saylink($_[0]);
my $length = length(substr($saylink,46)) - 1;
if($_[1] ne "") { substr($saylink,46,$length,$_[1]); }
return $saylink;
}
sub ShowMenu
{
if($passcode[$cid]) { $client->Message(4, "You have entered: $passcode[$cid]"); }
$client->Message(4, "Enter passcode: @sl_push $sl_reset $sl_enter");
}
It would end up looking something like this (correct passcode is 52213 in this example):
http://img268.imageshack.us/img268/4854/passcodeexample.th.jpg (http://img268.imageshack.us/i/passcodeexample.jpg/)
trevius
06-11-2009, 05:19 PM
That passcode idea is pretty neat. I am not sure of any places where I would use something like that, but I always love to see innovative ideas to push the limits of what we can do with the emulator systems available.
As for making the saylinks so they don't actually say what they click, that isn't too hard. I think we can just do the parse of the text right in the place were we currently send the message to the NPC. Though, it might take a bit of extra code to ensure that it doesn't cause other issues. Using the parse at that point is exactly how I originally tested saylinks (mentioned at the beginning of this thread), so I know it works.
I have been thinking about adding in a rule to allow admins to decide whether they want their saylinks to cause the players to actually say the message or not. But, another route might be to add in a second variation to the command. Maybe something like "ssaylink()" for Silent Saylink, or something to that effect. Then, one could chose either type for any scenario instead of having to chose 1 or the other for the entire server. The coding is already all there, so it would mostly just be adding the new command and the parse code. I think to differentiate between the 2 types of saylinks, we could just have the silent saylinks set their item ID to a much higher range. Since normal saylinks are set in the 500k range, the silent ones could be set to use the 1mil range or something like that.
I will try to look into getting this change added sometime soon, but I have a lot on my plate atm as usual, so no ETA. Glad to see people using the saylink system. I think it is a pretty neat little feature.
Shendare
06-11-2009, 05:27 PM
I like the idea of having the option for silent as well as visible saylink responses.
If one is not using silent saylinks, I also like the idea of being able to specify the text being said by the player when they click on the link, instead of making it always the text of the link itself.
"Hail, Player! Would you be interested in helping me with this [task]?"
*click [task]* "What task is that?"
...as opposed to:
*click [task]* "task"
- Shendare
trevius
06-11-2009, 06:30 PM
Sion already mentioned a way to do that with Perl, but I do agree that it would be nice to have the option to do it directly with the command.
The only reason to have a table for saylinks is so the server knows what phrase to associate with whichever saylink is being clicked. So, basically whatever gets put into that table is what the server will use to parse (or have the client /say) when the link is clicked.
This means that we should be able to set a second argument for the command that is optional. If the second argument is set, then that second phrase/argument would be the one that appears in the actual link in the text of the NPC. I am not that great with coding optional arguments for quest commands yet, but I can maybe try to add that in as well at some point. Again, most of the code needed for this to work should already be there. Basically, the saylink() command would just need to check for a second argument and if one exists, it uses that argument for creating the actual itemlink, but it still does the query of the saylink table based on the first argument.
If this functionality was added, it should work something like this:
sub EVENT_SAY {
my $test = quest::saylink(""Yes, I would like to test a saylink","test a saylink");
my $click = quest::saylink("I need to click something?","click");
my $say = quest::saylink("say");
if($text=~/hail/i)
{
quest::say("Hello, $name. Would you like to [$test]? If so, simply [$click] on the pink text and wait for my response. You may also simply /$say the message like normal if you prefer that method instead.");
}
if($text=~/test a saylink/i)
{
quest::say("The test was a huge success! Well done :P");
}
if($text=~/click/i)
{
quest::say("Yes, click the pink text exactly like you just did!");
}
if($text=~/^say$/i)
{
quest::say("Saying or clicking the text is the same thing to me!");
}
}
Where the values put into the saylink table (as well as what the character would appear to say when clicking the link) would be the first argument and the values in the second argument would be only for the text that shows up in the link itself when the NPC says it.
trevius
07-19-2009, 06:53 AM
I added a new bool option for the saylink command to allow them to be silent. More info can be found on the wiki page here about it:
http://www.eqemulator.net/wiki/wikka.php?wakka=SayLink
trevius
07-21-2009, 09:28 AM
I like the idea of having the option for silent as well as visible saylink responses.
If one is not using silent saylinks, I also like the idea of being able to specify the text being said by the player when they click on the link, instead of making it always the text of the link itself.
"Hail, Player! Would you be interested in helping me with this [task]?"
*click [task]* "What task is that?"
...as opposed to:
*click [task]* "task"
- Shendare
I updated Saylinks again so this is now an option. If you wanted to set a saylink to do what Shendare is showing in this quote, you would do this:
my $task = quest::saylink("What task is that?", 0, "task");
quest::say("Hail, $name! Would you be interested in helping me with this [$task]?");
The 0 in the middle is the bool for being silent or not, so when making different responses from what the link shows, it only makes sense to set it as 0 so the player says the message.
vBulletin® v3.8.11, Copyright ©2000-2025, vBulletin Solutions Inc.