Download

D2X-XL Worklog

Notes on features and problems from the development of D2X-XL, newest first.

Code Cleanup: AI

After the 'high profile' enhanced Pyro coding had been going on during the last few weeks, I have found some time now to do some direly needed 'behind the scenes' clean up and reworking of the existing code base, which still in parts is a big, ugly mess, stuffing dozens and dozens of functions with thousands of code lines into one or two completely unstructured source files. I tried to avoid this task, but having to deal with bugs and flaws in exactly that code forced me into bringing it into a shape I could work with.

What I had tackled first was the AI code. I ended up in splitting two huge files into ten smaller ones that actually contained what the file names suggested. The main AI dispatcher function was a moloch with over 1000 lines of code, performing several tasks all in one. Well ... it worked somehow, so the guy who wrote it shall be forgiven - at least he is one of the guys who brought us Descent.

I moved each AI function's code block into a separate function that gets called via a function table using the AI state as table index. Instead of juggling around with a bunch of separate data areas, the communication between the various functions is performed by exchanging a neat and clean data block. Here is an excerpt of the code as it looks now to give you an impression (before, each function call was a big block of code):

void DoAIFrame (tObject *objP)
{
   tAIStateInfo si;

// initialize si here
si.nObject = OBJ_IDX (objP);
si.nObjRef = si.nObject ^ gameData.app.nFrameCount;
...
if (AIFireHandler (objP, &si)) return;
if (AIApproachHandler (objP, &si)) return;
if (AIRetryHandler (objP, &si)) return;
if (AIMatCenHandler (objP, &si)) return;
if (AIAwarenessHandler (objP, &si)) return;
if (AIPlayerDeadHandler (objP, &si)) return;
if (AIBumpHandler (objP, &si)) return;
...
}

Here is an example of using a function table instead of a big list of separate state queries (before, there was a huge case statement instead of the function call via the table):

// type of the handler function
typedef int (* pAIHandler) (tObject *objP, tAIStateInfo *);

//the function table containing the handler functions for each state
static pAIHandler aisHandler1 [] = {
   AISNoneHandler1, AISRestHandler1, AISSearchHandler1,
   AISLockHandler1, AINothingHandler, AISFireHandler1,
   AISRecoverHandler1, AINothingHandler};

int AIForceFireHandler (tObject *objP, tAIStateInfo *siP)
{
tAIStatic *aiP = siP->aiP;
...
//only call function if the state points into the table
if ((ushort) aiP->CURRENT_STATE < AIS_ERROR)
   return aisHandler1 [aiP->CURRENT_STATE] (objP, siP);
return AISDefaultHandler1 (objP, siP);
}