Kisuka's Blog
Kisuka's Blog

[Hercules] Quest Bubbles

While working on the new Novice Academy script files I yet again ran across a rather large use of the Quest Bubbles in Ragnarok. These bubbles are basically a small icon that appears above an NPC's head and marks their location on the mini-map. It's primarily used to show the location of a quest.

It looks something like this in-game:

These bubbles have been in the Athena since around 2010, but it has never really be implemented the way it should be... or at least the way it works in AEGIS. After 3 years of dealing with it, I've finally gotten fed up of it not working and revamped it.

How it works in AEGIS

In AEGIS, the quest bubble is attached to an NPC by calling the AddQuestInfo script command during initialization of an NPC. This attaches a Quest ID, a Bubble Effect and a Bubble Type to the NPC. It looks something like this:

npc "izlude" "Instructor Argos#izlude" 4_M_LIEMAN 140 260 3 0 0
OnInit:
        AddQuestInfo 15001 1 0
        SetQuestJob 15001 NOVICE
return
OnClick:
...

What exactly is happening here is the quest 15001 is being attached to the Instructor Agros NPC along with a bubble effect of 0x01 and a type of 0. The strange thing is that type is almost never changed from 0. So far, I've only seen the effect changed. Anyways! What this does is, when the players shows up on a map it checks if there are any quests present on this map. If there are, it goes through every quest added on that map to NPCs by the result of AddQuestInfo. If it is found that there is a quest, and the player has not started the quest id which was specified it will show the bubble of the specified effect and color.

Optionally, you can also specify a job restriction by using the command, SetQuestJob as shown in the above example. This basically adds an additional check to that loop of quests, checking if the user not only hasn't started the quest id but is also the job it has assigned to it now. If the user is not the right job class, the icon/bubble is not shown to the user.

When an npc has given the user the quest via the setquest command, it runs through the list of quests again for that map, looking for any changes to the checks. Because the npc has just set the quest it was checking for, the icon then disappears and is no longer shown to the user.

How it worked in eAthena

For the longest time, these bubbles just simply didn't work the way they should. The only real way to make them work was utilizing the OnPCLoadMapEvent label, which looked something like this:

OnPCLoadMapEvent:
        if(strcharinfo(3)=="izlude" && checkquest(15001) != 2) 
                showevent 1,0;  
        end;

Basically, what this would do is every time a player would change, warp, jump, etc to a map that has the loadevent mapflag enabled. Every OnPCLoadMapEvent would run. Do make sure the bubble doesn't appear on another map that has loadevent enabled as well, an if statement checking if the user is on the same map as the NPC is added.

If the user is on the same map and they pass the checkquest command, the bubble is then shown using the showevent script command. This whole process becomes extremely resource intensive on larger servers, and is also very hard to keep management as quest bubbles are spread out among various maps. Keeping a huge list of loadevent mapflags would be extremely ugly and not very practical.

How it has been changed

First off, I find the concept of the showevent script command to make no sense at all. So in my implementation, it's not used in the script at all. Instead, a new command has been added called questinfo. This command works similar to it's AEGIS counterpart, allowing the scripter to specify a quest id and bubble effect. It also pulls in the functions of the SetQuestJob command from AEGIS. Let's see what this looks like:

izlude,140,260,3    script  Instructor Argos#izlude 887,{
        ...

        OnInit:
                questinfo 15001, QTYPE_QUEST, Job_Novice;
                end;
}

Now, immediately you will notice a difference, instead of specifying the bubble effect's id directly we've used QTYPE_QUEST. This is a new constant I've added, along with the following others:

  • No Icon : QTYPE_NONE
  • ! Quest Icon : QTYPE_QUEST
  • ? Quest Icon : QTYPE_QUEST2
  • ! Job Icon : QTYPE_JOB
  • ? Job Icon : QTYPE_JOB2
  • ! Event Icon : QTYPE_EVENT
  • ? Event Icon : QTYPE_EVENT2
  • Warg : QTYPE_WARG
  • Warg Face : QTYPE_WARG2 (Only for packetver >= 20120410)

You could alternatively use the corresponding IDs, but constants just make things easier in my opinion. You may also notice we are no longer specifying the bubble type. I removed that part and made it always pass 0 since that's what it's always passing on AEGIS anyways. You also have the option of specifying the job restriction, this parameter is optional.

So what is it doing under the hood you ask? Well... the NPC has a new struct called quest...

struct {
    int quest_id;
    int icon;
    bool hasJob;
    int job;
} quest;

When the NPC is initialized, and the questinfo command is run, it adds the Quest ID, and Bubble Icon (effect) to the struct. If the scripter has also specified a job restriction it will set hasJob to True and add the Job Class id to job.

Now here's where the magic happens... when a user logins, warps, teleports, etc clif_parse_LoadEndAck in clif.c is called. Think of this as all the pre-checks a user has to go through before they can begin walking in-game (stuff like, double logins, are you dead? and now are there quests presents?). In clif_parse_LoadEndAck I've added a new check, which will run through all the NPCs on the current map that the user is on. It will check if that NPC has a quest attached to it.

If a quest is present, it will use the quest log's functions to check if the quest has been started, if it hasn't, the packet for showing the quest bubble for that npc is sent. If a job restriction is present, it will run through that check right before the quest check, if the user is not the specified job it will not send the packet.

And that's how it works :)

After Thoughts

Now, while writing this random blog entry I've thought of some things that perhaps should be implemented, the major one being if the quest checks should be in it's own function outside of clif_parse_LoadEndAck. This way it can be called from LoadEndAck, SetQuest, and ReloadScript. This would be more close to how AEGIS does it I believe.