View Single Post
  #6  
Old 09-28-2014, 06:29 PM
jdoran
Hill Giant
 
Join Date: Jul 2012
Posts: 212
Default Code Changes

I was on IRC today, and the view was that without seeing code it would be difficult to form an opinion.

My concern right now is to figure out the best way of merging my changes with the current base. It would be great if I could have some hooks added to the main code-base so that I wouldn't need to do a lot of work each time there was a code change.

Rather than post full files, let me illustrate some of the major concerns.

First, I have been using PERL to hold quest scripts. This was all that was available when I started my work. I may switch to Lua. Keep this in mind when considering the following.

The routine PerlembParser::EventCommon figures out the package name to use based on objid. This will create a name such as qst1234 (for npcid 1234) or qstdefault for other object types. This package name will not work for my runtime, as I need each quest to live in its own package/namespace.

I maintain a notion of 'quest-id' which means that generated quests are assigned a locally unique integer when they are loaded into the server. So we might have quest 400012, and it will have its own namespace. All of the scripts needed for this quest will be placed in this namespace. This keeps name clashes from occuring. If the quest is unloaded and later reloaded, it will be assigned a different quest-id, and this is OK.

My package names are based on this quest-id, and are therefore unique on the machine. It is very important that when an event is dispatched to a script, that this package name be determined correctly. Currently I am passing the quest-id and the subroutine name into the call to EventNPC (and its ilk).

Official code:
Code:
parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0);
Modified code:
Code:
parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0,
                                0, "");
This is an example of an existing EventNPC call, modified to pass in a 0 quest-id and an empty subroutine name. The EventCommon code would continue to work as before. But if my runtime passed in an event, it would have a non-zero quest id. In this case, the package id would be calculated differently. This was done in loadQuest (which is no more) based on the quest type.

Code:
        case questLARC:
                packagename = QTriggers::Instance().packageName(questID);
                filename = QTriggers::Instance().questScript(questID);
                path << "quests/larc/" << filename;

                if (QTriggers::Instance().isLoaded(questID))
                {
                        LogFile->write(EQEMuLog::Quest, "quest %d already loaded",
                                questID);
                        break;
                }

                LogFile->write(EQEMuLog::Quest, "Parser: load quest %d: "
                        "package = %s, filename = %s", questID, packagename.c_str(),
                        filename.c_str());

                QTriggers::Instance().markLoaded(questID);
                perl -> eval_file(packagename.c_str(), path.str().c_str());
                break;
Adding two parameters to each EventNPC may be unpopular. I am open to alternatives. And I'm reluctant to make these changes without some positive feedback.

The next modification was the addition of a different event mechanism. I created a structure to hold information on each event, and then passed that into my quest manager. This quest manager object would decide if the event was of interest:
  • Does the client have an active quest which will use this event?
  • Is the player at the proper point in this quest for the event to be considered?

My goal is to not send each event into the script unless it is needed. Yes, there are ways to do this with globals. My technique isolates one quest from another. I can add/remove quests without worrying about what global flags are being used. The quest manager needs to keep track of the progress through the quest anyways, so there is no additional work.

I should add at this point: The quest manager maintains each quest as a graph of nodes which are connected via events. The quest is in effect a state machine. The events may cause a state change (the quest to advance). The quest manager therefore keeps track of the current state of each quest in the database. Quests are infrequently updated, so there is little overhead in updating the database when a quest advances. And records for active quests are loaded per-character when the character zones in, and only those events which can occur in the current zone are tracked.

Code:
        QEventDeath death_event;
        death_event.id = EVENT_DEATH;
        death_event.entityID = GetID();
        death_event.npcID = GetNPCTypeID();
        death_event.init = give_exp_client;
        death_event.killer = killerMob->CastToNPC();
        if (give_exp_client)
        {
                death_event.character_id = give_exp_client -> CharacterID();
        }
        else
        {
                death_event.character_id = 0;           // gm kill perhaps
        }

        death_event.spawngroup = GetSp2();

.... and later

                                        QTriggers::Instance().processEvent(&death_event);
I think there is plenty of info in my structure to generate an old-fashioned EventNPC call. But there is not enough info in the EventNPC call to replicate my processEvent. For example, some quests might advance when N mobs are killed out of a spawn group. (or out of a set of spawn groups).

These two issues are my major concern right now. The rest of the changes are isolated. Well I think there is another call made when the client zones in.
Reply With Quote