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
  #16  
Old 08-08-2008, 06:09 AM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Quote:
Originally Posted by KLS View Post
Actually it would probably be harder to integrate it into an old system than to create a new one as the system would probably be fairly straight forward really.

Yes, I was looking at the quest system this morning, and that was my thought.

While I am at work, I thought I would start sketching out how to implement this,
and welcome any thoughts you have on my tentative design choices.

First the tables. Global tables for the stuff that doesn't change (once the task is written),
and character tables to record their progress.


Code:
CREATE TABLE GlobalTasks (
	id int(11) unsigned NOT NULL,
	duration int(11) unsgined NOT NULL,
	description varchar(4000) NOT NULL,
	reward varchar(100) NOT NULL,
	startzone int(11) NOT NULL,
	PRIMARY KEY(id)
)

CREATE TABLE GlobalActivities (
	id int(11) unsigned NOT NULL,
	activityid int(11) unsigned default '0',
	activitytype tinyint unsigned,   // Kill, Loot, Deliver, Explore, etc
	text1 VARCHAR(100) default '',   // For Deliver tasks, this is the NPC name to deliver to, or name of mob for kill tasks etc
	text2 VARCHAR(100) default '',   // For Deliver tasks, this is the text name of the item to deliver, or item to loot for loot tasks
	itemid int(11),			 // item number for loot tasks.
	npcid int(11),                   // npcid for kill tasks
	exploreid int(11) default 0,     // For explore tasks, this is a unique number to identify the proximity event that needs to be triggered
        goalcount tinyint default 1,     // How many things to kill, deliver, loot, etc
        zoneid int(11),                  // The ID number of the zone that this task is performed in
	PRIMARY KEY(id, activityid)
)

I am unsure as to whether the text1/text2 fields are required,
or whether I should just pull the npc name/item name as
required based on their id. The npc name/item name is sent as
text in the Task packets. Keeping the text fields as well would
allow the Task designer to be a bit vaguer/ more 'roleplay' in
their objectives if they wished, e.g. 'Kill the master of Karnor'
rather than 'Kill Venril Sathir'.


CREATE TABLE CharacterTasks (
	charid int(11) unsigned NOT NULL,
	taskid int(11) unsigned NOT NULL,
	acceptedtime int(11) unsigned,               // Timestamp of when the player accepted the task
	completedtime int(11) unsigned default '0',  // 0 for an unfished task
	PRIMARY KEY(charid, taskid)
)

CREATE TABLE CharacterActivities (
	charid int(11) unsigned NOT NULL,
	taskid int(11) unsigned NOT NULL,
	activityid int(11) unsigned,
	donecount tinyint default '0',
	completed tinyint(1) default '0', // Not strictly needed, but probably quicker than cross referencing the Global Activity table to check if an activity is complete
        PRIMARY KEY(charid, taskid, activityid)
)
The active and completed task information is not retained by the client, certainly not across
logins, and probably not across zoning, so needs to be resent after zoning.

As I see it, zone will need to load the global task and activity tables in their entirety on
zone bootup. This may become an issue later on if their are lots of tasks written. As this is
global static data, it could be a candidate for shared memory, but I will leave that for the
future.

The next issue is how to represent the Tasks in memory. Each Task has a unique ID. I am leaning
toward #defining a MAXTASKID (say 10000 ?), and using a fixed size array ...

TaskInformation* Tasks[MAXTASKID]

The benefit I see of doing this is fast cross referencing to the Task/Activity information from
the per-character data. I think this would also make it easier to #reloadtask a single task
during development.

Onto the per-character info. Should I restrict a character to a maximum number of active tasks, e.g. 20 ?
The benefit I see to this is it constrains the amount of checking that needs to be done on killing/looting
to see if there is a matching goal in the characters active activities.

Pseudo Code:

On NPC Kill or Loot:
for each active task the character has:
for each open activity in that Task:
if ActivityGoal == Kill This NPCType or Loot this item.id Increment Goal Count

It did occur to me that an extra flag could be added to the npc_types and item tables, 'involvedintask',
so that the code could check this flag and if it is set to No, not bother checking the characters
active tasks.

Task initiation will be done through the existing Perl quest system, so the usual checks
for class, race, faction can be done before the NPC initiates the Task Chooser. (Also provide
a function so that a check can be made in Perl to see if the player has already completed
a particular task, so as not to offer it again).

I also see Task Completion/Reward as being handled by the Perl quest system, i.e. the last
activity in a Task would be go speak to an NPC who would then do something along the lines
of:

Code:
if quest::ActiveTask(1234) && quest::TaskActivityComplete(1234,10) .... give reward, quest::TaskComplete(1234)
I would be interested in any links to quests on Alla that use the Task system. I've
been using http://everquest.allakhazam.com/db/q...tml?quest=3237 as a guide, as
that is the one in the packet collect I was looking at.
Reply With Quote
  #17  
Old 08-08-2008, 10:19 AM
Andrew80k
Dragon
 
Join Date: Feb 2007
Posts: 659
Default

Looks like you have a good handle on it, and your initial approach seems viable to me. I would think that limiting the number of active quests a toon can have is wise at this point. Once you get started implementing it, I'm sure you'll come across other challenges. As for tasks, I'm mostly familiar with these...

This is an example of a collect task:

http://everquest.allakhazam.com/db/q...tml?quest=4320

This is a combo kill/collect:

http://everquest.allakhazam.com/db/q...tml?quest=3065

I know we're not up to DoN yet, but these tasks are examples.
Reply With Quote
  #18  
Old 08-08-2008, 04:54 PM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

Quote:
Task initiation will be done through the existing Perl quest system, so the usual checks
for class, race, faction can be done before the NPC initiates the Task Chooser. (Also provide
a function so that a check can be made in Perl to see if the player has already completed
a particular task, so as not to offer it again).
See this to me presents a unique problem in that it would be cumbersome if not impossible to write quests to include all the tasks a person has from a given npc. Keep in mind some task givers are giving out lots and lots of tasks, especially the ones for shards and such give out literal boatloads of tasks.

Personally I'd do something like:

Code:
CREATE TABLE TaskSet (
	id int(11) unsigned NOT NULL,
	globaltaskid int(11) unsgined NOT NULL,
	PRIMARY KEY(id, globaltaskid)
)
Would allow you to group tasks together into tasksets and the quest code could be simplified to quest::BringUpTaskWindow(taskset). Different npcs offer different tasks and more than 1 at a time if you qualify for them on live.

Would also need some way of enabling the tasks if one were to go with such an implementation.
Quote:
CREATE TABLE CharacterTasksEnabled (
charid int(11) unsigned NOT NULL,
taskid int(11) unsigned NOT NULL,
enabled tinyint(1) default '0',
PRIMARY KEY(charid, taskid)
)
Could then offer quest::EnableTask(taskid), quest::DisableTask(taskid), etc to make it much simpler to write quests that use the task system.

Putting it in shared memory is also probably a better option as the structures are probably going to need to be in some cases quite large and with the assumed 10000 entries it would really add up for a server that loads quite a few zones. A couple MB per zone doesn't seem like a big deal until you load up 50.
Reply With Quote
  #19  
Old 08-09-2008, 03:22 AM
wexford
Sarnak
 
Join Date: Jul 2008
Location: Dell Datacenter
Posts: 31
Default

Ok I have to say this is awesome! Ive been waiting for tasks to work since i came to eqemu! Awesome work derision
Reply With Quote
  #20  
Old 08-09-2008, 05:37 AM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Quote:
Originally Posted by KLS View Post
Keep in mind some task givers are giving out lots and lots of tasks, especially the ones for shards and such give out literal boatloads of tasks.
I already have the feature coded to call the task chooser from Perl with up to 10 tasks (could be increased by changing a #define):

Code:
$args ="1,2";
quest::taskchooser(eval($args));
So the initiating quest can build up the $args string with the list of Tasks the player is eligible for. When I've got the whole system working end-to-end, I'll revisit this to see about implementing it as you suggest.

Shared memory I will leave til last, as it's not something I've ever worked with.
Reply With Quote
  #21  
Old 08-09-2008, 03:37 PM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default

First, allow me to say that your findings are very promising. A good Quest Journal was really a missing part of EQEmu (and in fact of EQ for some quests).

I did not play with the task system very much on Live, it really came when I was taking a break from the game. There is one thing you should be careful of : may be some Live tasks have simultaneaous activities. Something like "Make 7 flame Posts" and "Get banned for one week", that you need to complete before you can progress to the next activity. As you see I am sticking to your examples spirit

Regarding the design, you could make the Quest Journal have even more reach by separating the low-level Journal handling from the Task system. In short, it could allow quest designers to track quests in the Journal even for quests that are not formatted like tasks. Let me take an example, this time from a quest idea I actually started implementing through the normal Perl system..

"The young gnome had been playing a few pranks to several personalities in Ak'anon, following the directions of the enchanter guild mistress. The last one had put him in hot waters though : passing for a half-elf had been easy with the illusion spell the enchantress had given him, but he was still shaking about the thought of what would have happened had he answered to the necromancer in the wrong way. But the real problem was : what to do with the letter the necromancer asked him to deliver to the dark elf necromancer guild master in Neriak ! Clearly he had gotten more than the enchantress had hoped for, surely she sent him there on purpose, not just to play one more prank. He could certainly bring the letter to her, or may be actually delivering the letter to its actual recipient would make him rich. Neriak was a wealthy city."

At some point the character can choose one of two major paths in a quest. Having the Task Task system handle that could be tricky. Handling arbitrary quest progress triggers (like we do in Perl sometimes) would be overkill. It is made for simple tasks and let's face it, many quests are like that. On the other hand it would be very nice to allow regular Perl quests to benefit from the Journal, given the quest writer accepts the extra work naturally.

You could achieve that by untying the actual quest progress (task progress) from the Task system logic. The tables would be unchanged, except the comment for completed would not apply : for arbitrary quests donecount has no meaning. The activity would be complete when the completed field is filled.

What does actually change then ? Answer : the way the tables are managed. At the moment may be you are thinking of wiring the table management into the task system. Instead you could separate the table management, and expose all "atomic" operations on those tables as quest:: xxx calls in Perl scripts, on top of using them for the task system of course.

Quest writers could then decide when to :
* quest::start_task(taskid, activityid) : the quest could start at any activity, defined by the Perl script
* quest::complete_activity(taskid, activityid) : completed date set by the server
* quest::start_activity(taskid,activityid) : this activity is started. Several activities could be up at the same time.
* quest::complete_task(taskid) : completed date set by the server

Of course those "manual" activities would not track progress , the Perl script does. But the player would at least see the quests he is on and the text for the current activity.

It would be the quest writer responsibility to start the next step(s) after completing the previous one(s), to detect when an activity is complete etc. A few checks could be added to avoid inconsistencies, like tasks completed whereas some activities are not complete for this task. But the code need not ensure an open task has an ongoing activity. The Perl scripts pilot the Quest Journal, and try to reproduce faithfully the facts. The Task system in this case is not active. With an activity_type of "Unmanaged" or somesuch it would be easy to deactivate the task system for those "quest" tasks and activities.

Ultimately this could lead to a flexible, semi-automated quest/task system : quests/tasks and their steps would be defined in the DB, with the transitions from a set of steps to the next ones. Each activity/step could either be handled by Perl, disabling any automated counting of objectives, or be fully supported by the engine for easy activities (loot, kill, explore). This would just depend on the activity_type. But it can be built one step at a time

By doing that we could have an emu better than Live with regards to quest tracking : even old quests could be tracked by the Quest Journal, given they are reworked of course.

I suggest this because I doubt it would be much more work to structure the code to allow the Task system to be optional with regards to the Quest Journal. Most of the things to write are the same, you just need to let the Task System pilot the Quest Journal, not intermix them and make them dependent on one another.

I hope this did not read like gibberish
Reply With Quote
  #22  
Old 08-09-2008, 04:00 PM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Quote:
Originally Posted by Bulle View Post
There is one thing you should be careful of : may be some Live tasks have simultaneaous activities. Something like "Make 7 flame Posts" and "Get banned for one week", that you need to complete before you can progress to the next activity.
That's a good point. The task I have been using as my guide has a sequential set of activities, where one must be completed before the next is revealed.

There are two types of task activity packet. One for completed and 'in progress' tasks which sends the details of the goal, and progress toward completion.

The second type is really just a placeholder, which consists just of an Activity Number, but no details of what the task is. These show up as ??? in the Task Journal.

It would be easy to alter the table and code to add a predecessor activity field. I know, (and you can see from the example in my first post), that it is possible to have more than one activity in progress displayed at the same time.

As for your other points, I skimmed over them, but they sound interesting, and I will look them over again and see what's possible as I continue to implement the system
Reply With Quote
  #23  
Old 08-10-2008, 02:38 PM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

I've been beavering away at this since Friday and thought I would provide an update on what I have functional so far:

Database Load routine for global task data
Database Load/Save for character task data

The database save is synchronous, but it only writes out anything that has been changed. I'll have to see if this can be done asynchronously like the saving of the other character data is.

Active task information is sent to the client when it enters a zone and so is retained across logins and zoning.

Initial Perl interface to bring up the Task Chooser with a list of up to 10 tasks:

The way I have it working right now, is the NPC has a list of all the tasks he can offer ($tasklist). It then checks whether the player already has each task in the list already assigned, (quest::istaskactive) and thus builds up a list of tasks it will offer ($tasksoffered), not including any tasks already active. quest::taskchooser then brings up the task selector window.

Code:
@tasklist =(1,2);
$tasksoffered = "";
foreach $task(@tasklist) {
	if(!quest::istaskactive($task)) {
		if($tasksoffered eq "") {
			$tasksoffered = $task;
        	}
                else {
                	$tasksoffered = $tasksoffered . "," . $task;
	        }
	}
}
if($tasksoffered ne "") {
	quest::taskchooser(eval($tasksoffered));
}
I have also added a new sub EVENT_TASKACCEPTED. This tells the NPC what task you accepted, so it can respond appropriately, summon an item etc. (When you accept the Children of the Fay task, you are given an Elven Bottle of Wine that you have to deliver).

Code:
sub EVENT_TASKACCEPTED {
        quest::say("You accepted task $task_id");
        if($task_id eq "1") {
                quest::summonitem(36078);
        }
}
You can remove a task by clicking the Remove button in the Task Journal window.

Two more Perl quest functions, quest::istaskactivityactive(taskid, activityid) and quest::flagtaskactivitycomplete(taskid, activityid).

This code checks when you hand in the Elven Bottle of Wine to see if you are on activity 0 of the Children of the Fay task (task 1), and if so, accepts the item, and flags that activity complete, sending the 'Task Stage completed' message, and unlocking the next activity:

Code:
sub EVENT_ITEM {
        if (plugin::check_handin(\%itemcount, 36078 => 1)) {
                if(quest::istaskactivityactive(1,0)) {
                        quest::say("Thank You!");
                        quest::flagtaskactivitycomplete(1,0);
                }
I'm still undecided about how to implement the predecessor activity requirements. E.g. say a task has 10 activities and when you completed activity 1, activities 2 and 3 open up. Task 4 then won't open up until tasks 2 and 3 are complete.

As I see it, I can either put a limit on how many predecessors an activity can have (let's say 4), and then have four columns in the table, predecessor1, predecessor2, etc.

Or, have a text field which could contain a comma separated list of predecessors, e.g. "2,3". I dislike this because it means I have to parse the string to get at the predecessor info.

I'm leaning toward option 1 (a fixed, small number of predecessors and having a column for each).

Still lots left to do!
Reply With Quote
  #24  
Old 08-11-2008, 12:40 AM
AndMetal
Developer
 
Join Date: Mar 2007
Location: Ohio
Posts: 648
Default

Quote:
Originally Posted by Derision View Post
I'm still undecided about how to implement the predecessor activity requirements. E.g. say a task has 10 activities and when you completed activity 1, activities 2 and 3 open up. Task 4 then won't open up until tasks 2 and 3 are complete.

As I see it, I can either put a limit on how many predecessors an activity can have (let's say 4), and then have four columns in the table, predecessor1, predecessor2, etc.

Or, have a text field which could contain a comma separated list of predecessors, e.g. "2,3". I dislike this because it means I have to parse the string to get at the predecessor info.

I'm leaning toward option 1 (a fixed, small number of predecessors and having a column for each).

Still lots left to do!
I think something like the "_entries" or "_metadata" tables currently in the database would work best, similar to what KLS recommended above. Here's an example:
Code:
CREATE TABLE TaskOrder (
	taskid int(11) unsigned NOT NULL,
	activityid int(11) unsgined NOT NULL,
	step int(11) unsigned NOT NULL,
	PRIMARY KEY(taskid, activityid)
)
"step" could be used to group each stage, so using your example, activity 1 would be step 1, activities 2 & 3 would be step 2, and activity 4 would be step 3:
Code:
taskid		activityid	step
1		1		1
1		2		2
1		3		2
1		4		3
That way, it's more dynamic, and doesn't create a large table if you want something more than 4 prerequisites deep.

Hope this gives you some ideas, the whole task system seems REAL promising
__________________
GM-Impossible of 'A work in progress'
A non-legit PEQ DB server
How to create your own non-legit server

My Contributions to the Wiki
Reply With Quote
  #25  
Old 08-11-2008, 01:39 PM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Quote:
Originally Posted by AndMetal View Post
"step" could be used to group each stage
Thanks! I think just adding a 'step' column to the activity table, rather than creating another table should suffice.

Anyway, I've hit upon another snag. I've just implemented the code to update 'Kill' and 'Loot' activities, based on NPCTypeID or itemid respectively, which works great, however ...

I wanted to add a Kill activity to kill 5 Orc Centurions, but there are 40 entries for Orc Centurions in the npc_types table!

As they say, no plan survives contact with the enemy! I would welcome ideas on this one. Doing a lookup against a list of 40 NPC IDs per kill seems a bit OTT. Maybe some sort of sub-string match again the NPC name may be a less expensive way to go.
Reply With Quote
  #26  
Old 08-11-2008, 02:07 PM
Andrew80k
Dragon
 
Join Date: Feb 2007
Posts: 659
Default

Hmm. Could be you just let any of them meet the requirements. Or you could just filter them out like you say. It probably makes more sense to filter them out and then let any of the resulting id's after you filter them to your satisfaction, meet the requirements for the task.
Reply With Quote
  #27  
Old 08-11-2008, 02:32 PM
AndMetal
Developer
 
Join Date: Mar 2007
Location: Ohio
Posts: 648
Default

Quote:
Originally Posted by Derision View Post
As they say, no plan survives contact with the enemy! I would welcome ideas on this one. Doing a lookup against a list of 40 NPC IDs per kill seems a bit OTT. Maybe some sort of sub-string match again the NPC name may be a less expensive way to go.
I think an easy way to do this would be to allow either a # (NPC ID for a specific mob) or a string. Then, the server can check to see which it is. If it's a #, use the specific NPC ID. If it's a string, allow multiples based on the string (SELECT name FROM npc_types WHERE name LIKE '%an_orc%'). I think it would be a lot easier to utilize overall (an_orc for example, instead of an_orc_warrior, an_orc_oracle, or an_orc_pawn). However, in the same token, I believe it could cause problems for mobs that have different names, but are different (lower) levels (read: exploit).

As much as I like the first option, I think the "better" way to do it would be to create a similar lookup table like I mentioned above. Yes, it is another table, but it makes it much more powerful overall. That way, you could also include named mobs that are rare spawns to include towards the kill target. Yeah, it seems like more work in the database, but that's also what relational databases are for: creating complex relationships between data sets
__________________
GM-Impossible of 'A work in progress'
A non-legit PEQ DB server
How to create your own non-legit server

My Contributions to the Wiki
Reply With Quote
  #28  
Old 08-11-2008, 02:45 PM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

Use quests for the kill component =p
Reply With Quote
  #29  
Old 08-11-2008, 03:00 PM
AndMetal
Developer
 
Join Date: Mar 2007
Location: Ohio
Posts: 648
Default

Quote:
Originally Posted by KLS View Post
Use quests for the kill component =p
*smacks forehead* I should have had a V8!
__________________
GM-Impossible of 'A work in progress'
A non-legit PEQ DB server
How to create your own non-legit server

My Contributions to the Wiki
Reply With Quote
  #30  
Old 08-11-2008, 03:27 PM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Quote:
Originally Posted by KLS View Post
Use quests for the kill component =p
Thanks

I've now mapped out some more of the activity types:

Code:

Activity Type Codes:

1 Deliver x to y
2 Kill n MobX
3 Loot n ItemA
4 Speak With x
5 Explore y
6 Create n ItemA using Tradeskills
7 Fish for n ItemA
8 Forage n ItemA
9 Use on 
10 Use on
11 Touch ItemA
So 1, 2, 4 and 5 can be handled through quests.

3 I already have handled by hooking into Corpse::LootItem, and I imagine 6, 7 and 8 should be done by hooking into the code as well.

I'm guessing that the 'Touch' activity means clicking on a clickable-object, like a door or something. I found a task on Alla that has 'touch' as an activity:

http://everquest.allakhazam.com/db/q...tml?quest=3151

But it's not clear what it means.
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:36 PM.


 

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