Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Development

Development::Development Forum for development topics and for those interested in EQEMu development. (Not a support forum)

Reply
 
Thread Tools Display Modes
  #1  
Old 04-13-2009, 04:55 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default Say Links

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:

Quote:
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:
Code:
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:
Code:
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:
Code:
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.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!

Last edited by trevius; 04-13-2009 at 01:14 PM..
Reply With Quote
  #2  
Old 04-19-2009, 11:43 AM
warhawk
Sarnak
 
Join Date: Mar 2008
Posts: 47
Default

Trev

This is a great idea. I've always hated the quest system (typing text) in EQ.

Good luck with it.

War
Reply With Quote
  #3  
Old 04-19-2009, 05:01 PM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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:

Code:
quest::say("This is a quest::saylink(link) example.");
And that line would show this when the NPC says it:

Code:
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:

Code:
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.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #4  
Old 04-19-2009, 07:28 PM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

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
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!

Last edited by trevius; 04-20-2009 at 03:33 AM..
Reply With Quote
  #5  
Old 04-19-2009, 09:33 PM
realityincarnate
Developer
 
Join Date: Dec 2007
Posts: 122
Default

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.
Reply With Quote
  #6  
Old 04-20-2009, 01:44 AM
realityincarnate
Developer
 
Join Date: Dec 2007
Posts: 122
Default

I played around with the idea a little bit tonight, and here's what I've come up with as a first attempt.
Code:
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:
Code:
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.");
  }
}
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 06:49 AM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3