Running daemons for quests

For some quests, we may want to apply some special effects on the players before they finish the quest. We may want to restrict them from going somewhere, adding special visual effects to their screens, or may be disable some attributes of the avatars.

Take the Fantastic Age virtual world as an example. There is a quest that is telling the story about the mad scientist's invention of a machine that makes the whole virtual world cloudy and stormy. A cloudy visual effect is then added to the players who are working on this quest until they finish it by destroying the machine.

Running daemons for quests

Moreover, each daemon class is also in charge of keeping track of the progress of the quest. Quest progress is advanced when the player plays in the game. The daemon's role is to track the progress and determine when to show something and when to hide it to make the quest run smoothly. Also it will determine if the quest is completed.

Before coding our daemons, we need some server-side extensions to help us access the database. The commands we need are getAllInProgressQuests, loadCompletedQuestList, getQuestStatus, updateQuestProgress, and completeQuest. We will go through them one by one now.

We will need to get all in-progress quests from database so that we can restore the quests every time that the players log in.

function hgetAllInProgressQuests(cmd, param, user, fromRoom) {
var res = new Object();
res.cmd = cmd;
res.daemons = [];
res.inProgressList = [];
var quests = [];
var sql = "SELECT quest_id FROM avatar_quests WHERE avatar_name='"+user.getName()+"' AND status=1";
var resultCount = getResultArray(sql, quests);
for(var i=0;i<quests.length;i++){
var quest = [];
var sql = "SELECT daemon,id FROM quests WHERE id="+quests[i].quest_id;
getResultArray(sql,quest);
res.daemons.push(quest[0].daemon);
res.inProgressList.push(quest[0].id);
}
sendResponseBack(res, user);
}

We will load the completed quest from server. The status is marked as 2 for completed quest.

function hloadCompletedQuestList(cmd, param, user, fromRoom) {
var res = new Object();
res.cmd = cmd;
res.completedList = [];
var sql = "SELECT quest_id FROM avatar_quests WHERE status=2 AND avatar_name='"+user.getName()+"'";
var resultCount = getResultArray(sql, res.completedList);
sendResponseBack(res, user);
}

For a quest daemon, it will need to load the status and progress of a specific quest. The daemon needs the status to check if the working quest has fulfilled the completed criteria.

function hgetQuestStatus(cmd, param, user, fromRoom) {
var res = new Object();
res.cmd = cmd;
res.status = -1;
var quest = [];
var sql = "SELECT * FROM avatar_quests WHERE quest_id="+param.questId+" AND avatar_name='"+user.getName()+"'";
var resultCount = getResultArray(sql, quest);
if (resultCount > 0){
res.status = quest[0].status;
res.progress = quest[0].progress;
}
sendResponseBack(res, user);
}

While players are playing in the virtual world, the daemon will track the changes of the quests and update the progress in the server. The updated progress will be saved in the database so that the user can restore the progress at next login.

function hupdateQuestProgress(cmd,param,user,fromRoom) {
var res = new Object();
res.cmd = cmd;
var sql = "UPDATE avatar_quests SET progress="+param.progress+" WHERE quest_id="+param.questId+" AND avatar_name='"+user.getName()+"'";
var success = dbase.executeCommand(sql);
sendResponseBack(res, user);
}

We will mark the quest as complete in database after the user fulfills the completion criteria. There are several ways to complete a quest and we will discuss that later.

function hcompleteQuest(cmd,param,user,fromRoom) {
var res = new Object();
res.cmd = cmd;
var sql = "UPDATE avatar_quests SET status=2 WHERE quest_id="+param.questId+" AND avatar_name='"+user.getName()+"'";
var success = dbase.executeCommand(sql);
sendResponseBack(res, user);
}

Let's move to the client-side. We create a daemon base class for all daemons to extend it. The Daemon class contains the following properties and functions:

Properties or function name

Type

Description

progress

Number

Stores the progress of the quest.

status

Number

States whether the quest is in-progress or completed.

name

String

The quest name.

run

Function

Periodic running logic that is related to the quest.

onAccept

Function

Called once when the player accepts the quest.

onRemove

Function

Called once when the player abandons the quest.

Managing quest daemons

Daemon manager is a class to manage all quest daemons at one central place. It is also responsible to keep a list of completed quests and in-progress quests. There is a timer in the Daemon Manager that runs every second. This timer will call every monitoring daemon to run a periodic logic which is useful for some quests that require some periodic checking.

Property name

Type

Description

daemons

Array

A list of active quest daemon references.

timer

Timer

A timer to execute the periodic logic of active daemons.

inProgressList

Array

A list of quest IDs for the quest that is in-progress.

completedList

Array

A list of quest IDs for the quest that is completed.

The daemon manager will load the completed and in-progress quests from server when the user logs in to the virtual world.

public function restoreRunningDaemon():void {
_daemons = new Array();
var params:Object = {};
SmartFox.sfs.sendXtMessage("virtualWorld", "getAllInProgressQuests", params, "json");
params = {};
SmartFox.sfs.sendXtMessage("virtualWorld", "loadCompletedQuestList", params, "json");
}

The daemon manager provides a function for other classes to register a daemon into the monitoring list and a function to remove it from the active quest list.

public function registerDaemon(d:String):Daemon {
// dynamically initialize the daemons class from string
// the daemon classes are placed inside npcs | daemons classpath.
var classReference:Class = getDefinitionByName("npcs.daemons."+d) as Class;
var daemon = new classReference(_stage);
_daemons.push(daemon);
// start the timer when there is any active quest.
if (_daemons.length == 1){
_timer.start();
}
return daemon;
}
public function unregisterDaemon(daemonName:String):void{
for(var i:int=0;i<_daemons.length;i++){
var d:Daemon = _daemons[i];
if (d.name == daemonName){
d.onRemove();
_daemons.splice(i,1);
d = null;
}
}
// no daemon in the list now, stop the timer.
if (_daemons.length <= 0){
_timer.stop();
}
}

When other classes need to reference the daemon instance, they can access it by providing the daemon name in string. We need to get the instance from string instead of directly referencing the instance because sometimes this daemon name is stored in string format in the database.

public function getDaemonByName(daemonName:String):Daemon {
for each(var d:Daemon in _daemons){
if (d.name == daemonName){
return d;
}
}
return null;
}

During the initialization process, the daemon manger will load all in-progress quests and register the daemons to restore the working quests' status.

private function onExtensionResponse(e:SFSEvent):void {
var cmd:String = e.params.dataObj.cmd;
var data:Object = e.params.dataObj;
if (cmd=='getAllInProgressQuests') {
for each(var daemon:Daemon in data.daemons){
registerDaemon(daemon);
}
for each(var quested:Number in data.inProgressList) {
_inProgressList.push(questId);
}
} else if (cmd=='loadCompletedQuestList') {
for each(var quest:Object in data.completedList) {
_completedList.push(quest.quest_id);
}
}
}

Creating our first quest — Tom's Question

We are now going to create an example to demonstrate what we just implemented. The quest is called Tom's Question. There is an NPC called Tom who blocked the player's road. When the player asked Tom to walk away, Tom said he didn't know which foot to move first. If the player chooses to help Tom find the answer, a quest panel will prompt to let the player accept the quest.

The player then needs to ask a wise man, another NPC, the same question and seek the answer from him. When player gets the answer and gets back to Tom, an option appears in the conversation to let the player answer Tom's question.

Finally, Tom gets the solution and the quest daemon will track the completion and update the status in the database.

Creating our first quest — Tom's Question

The quest daemon that is in charge of the Tom's Question quest will keep the latest progress and status of the quest. The conversation menu can use this information to check if some options in the conversation tree are displayed or not.

_conversation[6].preRequest = function(){
// check if the quest "Tom's Question" is completed
// if the quest is complete, we hide this option
var completedList:Array = DaemonManager.instance.completedList;
for each(var questId:Number in completedList){
if (questId == _questId){
return false;
}
}
// get the daemon of the quest "Tom's Question"
var daemon:Daemon = DaemonManager.instance.getDaemonByName (_daemonName);
if (daemon == null) return false;
/* get the progress of the Tom's Question quest, if the game is in progress,
display this conversation, otherwise, hide it. */
var progress:Number = DaemonManager.instance.getDaemonByName (_daemonName).progress;
var status:Number = DaemonManager.instance.getDaemonByName (_daemonName).status;
if (status < 2 && progress > 0){
return true;
}
return false;
}

When players reach the conversation node where the wise man gave the answer, an event is dispatched to inform the daemon to update the progress.

_conversation[5] = new ConversationNode();
_conversation[5].message = "Ahha, what a good question. Why not take a wheelchair? Then you don't need to move either one.";
_conversation[5].choices = [0];
_conversation[5].choiceMessages[0] = "Thank you!";
_conversation[5].action = 'found_answer_of_tom_question';

Here is the flow of the whole quest.

Creating our first quest — Tom's Question

The TomQuestionD.as quest daemon listens to the found_answer_of_tom_question and complete_tom_question event to update the progress. The found_answer_of_tom_question event will be dispatched when the player talks to the wise man NPC and gets the answer. The complete_tom_question is dispatched when the player talks to Tom again and tells him the correct answer.

public function TomQuestionD(stageRef) {
// execute the superclass's constructor code.
super(stageRef);
_name = "TomQuestionD";
_stage.addEventListener( 'found_answer_of_tom_question',onFoundAnswer);
_stage.addEventListener('complete_tom_question',onComplete);
SmartFox.sfs.addEventListener(SFSEvent.onExtensionResponse, onExtensionResponse);
var params:Object = {};
params.questId = _questId;
SmartFox.sfs.sendXtMessage("virtualWorld", "getQuestStatus", params, "json");
}

When the wise man tells the player the answer, the daemon will catch that event and update the quest progress.

private function onFoundAnswer(e:Event):void {
_progress++;
var params:Object = {};
params.progress = _progress;
params.questId = _questId;
SmartFox.sfs.sendXtMessage("virtualWorld", "updateQuestProgress", params, "json");
}

When the player tells Tom the answer, the daemon marks the quest as completed in the server.

private function onComplete(e:Event):void {
var params:Object = {};
params.questId = _questId;
SmartFox.sfs.sendXtMessage("virtualWorld", "completeQuest", params, "json");
DaemonManager.instance.unregisterDaemon(_name);
DaemonManager.instance.completedList.push(_questId);
}

In the example, we see that the daemon is useful for checking and holding the status of the quest so that others' logic can determine which state the player is in with the quest.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.36.213