// char.c

// Egoboo, Copyright (C) 2000 Aaron Bishop

#include "egoboo.h"

//--------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------
void flash_character_height(int character, unsigned char valuelow, signed short low,
    unsigned char valuehigh, signed short high)
{
    // ZZ> This function sets a character's lighting depending on vertex height...
    //     Can make feet dark and head light...
    int cnt;
    unsigned short frame;
    signed short z;


    frame = chrframe[character];
    cnt = 0;
    while(cnt < madtransvertices[chrmodel[character]])
    {
        z = madvrtz[frame][cnt];
        if(z < low)
        {
            chrvrta[character][cnt] = valuelow;
        }
        else
        {
            if(z > high)
            {
                chrvrta[character][cnt] = valuehigh;
            }
            else
            {
                chrvrta[character][cnt] = (valuehigh * (z - low) / (high - low)) +
                                          (valuelow * (high - z) / (high - low));
            }
        }
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void flash_character(int character, unsigned char value)
{
    // ZZ> This function sets a character's lighting
    int cnt;

    cnt = 0;
    while(cnt < madtransvertices[chrmodel[character]])
    {
        chrvrta[character][cnt] = value;
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void flash_select()
{
    // ZZ> This function makes the selected characters blink
    int cnt;
    unsigned char value;

    if((wldframe&31)==0 && allselect==FALSE)
    {
        value = ((wldframe&32)<<3) - ((wldframe&32)>>5);
        cnt = 0;
        while(cnt < numrtsselect)
        {
            flash_character(rtsselect[cnt], value);
            cnt++;
        }
    }
}

//--------------------------------------------------------------------------------------------
void add_to_dolist(int cnt)
{
    // This function puts a character in the list
    int fan;


    if(!chrindolist[cnt])
    {
        fan = chronwhichfan[cnt];
        if(meshinrenderlist[fan])
        {
            chrlightlevel[cnt] = meshvrtl[meshvrtstart[fan]];
            dolist[numdolist] = cnt;
            chrindolist[cnt] = TRUE;
            numdolist++;


            // Do flashing
            if((allframe&chrflashand[cnt]) == 0 && chrflashand[cnt]!=DONTFLASH)
            {
                flash_character(cnt, 255);
            }
            // Do blacking
            if((allframe&SEEKURSEAND) == 0 && localseekurse && chriskursed[cnt])
            {
                flash_character(cnt, 0);
            }
        }
        else
        {
            // Double check for large/special objects
            if(capalwaysdraw[chrmodel[cnt]])
            {
                dolist[numdolist] = cnt;
                chrindolist[cnt] = TRUE;
                numdolist++;
            }
        }
        // Add its weapons too
        if(chrholdingwhich[cnt][0] != MAXCHR)
            add_to_dolist(chrholdingwhich[cnt][0]);
        if(chrholdingwhich[cnt][1] != MAXCHR)
            add_to_dolist(chrholdingwhich[cnt][1]);
    }
}

//--------------------------------------------------------------------------------------------
void order_dolist(void)
{
    // ZZ> This function orders the dolist based on distance from camera,
    //     which is needed for reflections to properly clip themselves.
    //     Order from closest to farthest
    int cnt, tnc, character, order;
    int dist[MAXCHR];
    unsigned short olddolist[MAXCHR];


    // Figure the distance of each
    cnt = 0;
    while(cnt < numdolist)
    {
        character = dolist[cnt];  olddolist[cnt] = character;
        if(chrlight[character] != 255 || chralpha[character] != 255)
        {
            // This makes stuff inside an invisible character visible...
            // A key inside a Jellcube, for example
            dist[cnt] = 0x7fffffff;
        }
        else
        {
            dist[cnt] = ABS(chrxpos[character]-camx)+ABS(chrypos[character]-camy);
        }
        cnt++;
    }


    // Put em in the right order
    cnt = 0;
    while(cnt < numdolist)
    {
        character = olddolist[cnt];
        order = 0;  // Assume this character is closest
        tnc = 0;
        while(tnc < numdolist)
        {
            // For each one closer, increment the order
            order += (dist[cnt] > dist[tnc]);
            order += (dist[cnt] == dist[tnc]) && (cnt < tnc);
            tnc++;
        }
        dolist[order] = character;
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void make_dolist(void)
{
    // ZZ> This function finds the characters that need to be drawn and puts them in the list
    int cnt, character;


    // Remove everyone from the dolist
    cnt = 0;
    while(cnt < numdolist)
    {
        character = dolist[cnt];
        chrindolist[character] = FALSE;
        cnt++;
    }
    numdolist = 0;


    // Now fill it up again
    cnt = 0;
    while(cnt < MAXCHR)
    {
        if(chron[cnt]&&(!chrinpack[cnt]))
        {
            // Add the character
            add_to_dolist(cnt);
        }
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void keep_weapons_with_holders()
{
    // ZZ> This function keeps weapons near their holders
    int cnt, character;


    // !!!BAD!!!  May need to do 3 levels of attachment...
    cnt = 0;
    while(cnt < MAXCHR)
    {
        if(chron[cnt])
        {
            character = chrattachedto[cnt];
            if(character == MAXCHR)
            {
                // Keep inventory with character
                if(chrinpack[cnt] == FALSE)
                {
                    character = chrnextinpack[cnt];
                    while(character != MAXCHR)
                    {
                        chrxpos[character] = chrxpos[cnt];
                        chrypos[character] = chrypos[cnt];
                        chrzpos[character] = chrzpos[cnt];
                        // Copy olds to make SendMessageNear work
                        chroldx[character] = chrxpos[cnt];
                        chroldy[character] = chrypos[cnt];
                        chroldz[character] = chrzpos[cnt];
                        character = chrnextinpack[character];
                    }
                }
            }
            else
            {
                // Keep in hand weapons with character
                if(chrmatrixvalid[character] && chrmatrixvalid[cnt])
                {
                    chrxpos[cnt] = chrmatrix[cnt]_CNV(3,0);
                    chrypos[cnt] = chrmatrix[cnt]_CNV(3,1);
                    chrzpos[cnt] = chrmatrix[cnt]_CNV(3,2);
                }
                else
                {
                    chrxpos[cnt] = chrxpos[character];
                    chrypos[cnt] = chrypos[character];
                    chrzpos[cnt] = chrzpos[character];
                }
                chrturnleftright[cnt] = chrturnleftright[character];
                // Copy this stuff ONLY if it's a weapon, not for mounts
                if(chrtransferblend[character]&&chrisitem[cnt])
                {
                    if(chralpha[character]!=255)
                    {
                        chralpha[cnt] = chralpha[character];
                    }
                    if(chrlight[character]!=255)
                    {
                        chrlight[cnt] = chrlight[character];
                    }
                }
            }
        }
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void make_turntosin(void)
{
    // ZZ> This function makes the lookup table for chrturn...
    int cnt;

    cnt = 0;
    while(cnt < 16384)
    {
        turntosin[cnt] = sin((float) (2*PI*cnt/16384.0));
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void make_one_character_matrix(unsigned short cnt)
{
   // ZZ> This function sets one character's matrix
   unsigned short tnc;
   chrmatrixvalid[cnt] = TRUE;
   if(chroverlay[cnt])
   {
       // Overlays are kept with their target...
       tnc = chraitarget[cnt];
       chrxpos[cnt] = chrxpos[tnc];
       chrypos[cnt] = chrypos[tnc];
       chrzpos[cnt] = chrzpos[tnc];
       chrmatrix[cnt]_CNV(0,0) = chrmatrix[tnc]_CNV(0,0);
       chrmatrix[cnt]_CNV(0,1) = chrmatrix[tnc]_CNV(0,1);
       chrmatrix[cnt]_CNV(0,2) = chrmatrix[tnc]_CNV(0,2);
       chrmatrix[cnt]_CNV(0,3) = chrmatrix[tnc]_CNV(0,3);
       chrmatrix[cnt]_CNV(1,0) = chrmatrix[tnc]_CNV(1,0);
       chrmatrix[cnt]_CNV(1,1) = chrmatrix[tnc]_CNV(1,1);
       chrmatrix[cnt]_CNV(1,2) = chrmatrix[tnc]_CNV(1,2);
       chrmatrix[cnt]_CNV(1,3) = chrmatrix[tnc]_CNV(1,3);
       chrmatrix[cnt]_CNV(2,0) = chrmatrix[tnc]_CNV(2,0);
       chrmatrix[cnt]_CNV(2,1) = chrmatrix[tnc]_CNV(2,1);
       chrmatrix[cnt]_CNV(2,2) = chrmatrix[tnc]_CNV(2,2);
       chrmatrix[cnt]_CNV(2,3) = chrmatrix[tnc]_CNV(2,3);
       chrmatrix[cnt]_CNV(3,0) = chrmatrix[tnc]_CNV(3,0);
       chrmatrix[cnt]_CNV(3,1) = chrmatrix[tnc]_CNV(3,1);
       chrmatrix[cnt]_CNV(3,2) = chrmatrix[tnc]_CNV(3,2);
       chrmatrix[cnt]_CNV(3,3) = chrmatrix[tnc]_CNV(3,3);
   }
   else
   {
       chrmatrix[cnt] = ScaleXYZRotateXYZTranslate(chrscale[cnt], chrscale[cnt], chrscale[cnt],
                                    chrturnleftright[cnt]>>2,
                                    ((unsigned short) (chrturnmapud[cnt]+32768))>>2,
                                    ((unsigned short) (chrturnmaplr[cnt]+32768))>>2,
                                    chrxpos[cnt], chrypos[cnt], chrzpos[cnt]);
   }
}

//--------------------------------------------------------------------------------------------
void free_one_character(int character)
{
    // ZZ> This function sticks a character back on the free character stack
    int cnt;

    freechrlist[numfreechr]=character;
    numfreechr++;
    // Remove from stat list
    if(chrstaton[character])
    {
        chrstaton[character] = FALSE;
        cnt = 0;
        while(cnt < numstat)
        {
            if(statlist[cnt] == character)
            {
                cnt++;
                while(cnt < numstat)
                {
                    statlist[cnt-1] = statlist[cnt];
                    cnt++;
                }
                numstat--;
            }
            cnt++;
        }
    }
    // Make sure everyone knows it died
    if(chralive[character] && capinvictus[chrmodel[character]]==FALSE)
    {
        teammorale[chrbaseteam[character]]--;
    }
    cnt = 0;
    while(cnt < MAXCHR)
    {
        if(chron[cnt])
        {
            if(chraitarget[cnt] == character)
            {
                chralert[cnt]|=ALERTIFTARGETKILLED;
                chraitarget[cnt] = cnt;
            }
            if(teamleader[chrteam[cnt]] == character)
            {
                chralert[cnt]|=ALERTIFLEADERKILLED;
            }
        }
        cnt++;
    }
    if(teamleader[chrteam[character]]==character)
    {
        teamleader[chrteam[character]] = NOLEADER;
    }
    chron[character]=FALSE;
    chralive[character]=FALSE;
    chrinpack[character]=FALSE;
}

//--------------------------------------------------------------------------------------------
void free_inventory(int character)
{
    // ZZ> This function frees every item in the character's inventory
    int cnt, next;

    cnt = chrnextinpack[character];
    while(cnt < MAXCHR)
    {
        next = chrnextinpack[cnt];
        free_one_character(cnt);
        cnt = next;
    }
}

//--------------------------------------------------------------------------------------------
void attach_particle_to_character(int particle, int character, int grip)
{
    // ZZ> This function sets one particle's position to be attached to a character.
    //     It will kill the particle if the character is no longer around
    unsigned short vertex, model, frame, lastframe;
    unsigned char lip;
    float pointx;
    float pointy;
    float pointz;
    int temp;


    // Check validity of attachment
    if(chron[character]==FALSE || chrinpack[character])
    {
        prttime[particle] = 1;
        return;
    }


    // Do we have a matrix???
    if(chrmatrixvalid[character])//meshinrenderlist[chronwhichfan[character]])
    {
        // Transform the weapon grip from model to world space
        model = chrmodel[character];
        frame = chrframe[character];
        lastframe = chrlastframe[character];
        lip = chrlip[character]>>6;
        if(grip == SPAWNORIGIN)
        {
            prtxpos[particle] = chrmatrix[character]_CNV(3,0);
            prtypos[particle] = chrmatrix[character]_CNV(3,1);
            prtzpos[particle] = chrmatrix[character]_CNV(3,2);
            return;
        }
        vertex = madvertices[model] - grip;


        // Calculate grip point locations with linear interpolation and other silly things
        switch(lip)
        {
            case 0:  // 25% this frame
                temp = madvrtx[lastframe][vertex];
                temp = temp+temp+temp+madvrtx[frame][vertex]>>2;
                pointx = temp;///chrscale[cnt];
                temp = madvrty[lastframe][vertex];
                temp = temp+temp+temp+madvrty[frame][vertex]>>2;
                pointy = temp;///chrscale[cnt];
                temp = madvrtz[lastframe][vertex];
                temp = temp+temp+temp+madvrtz[frame][vertex]>>2;
                pointz = temp;///chrscale[cnt];
                break;
            case 1:  // 50% this frame
                pointx = (madvrtx[frame][vertex]+madvrtx[lastframe][vertex]>>1);///chrscale[cnt];
                pointy = (madvrty[frame][vertex]+madvrty[lastframe][vertex]>>1);///chrscale[cnt];
                pointz = (madvrtz[frame][vertex]+madvrtz[lastframe][vertex]>>1);///chrscale[cnt];
                break;
            case 2:  // 75% this frame
                temp = madvrtx[frame][vertex];
                temp = temp+temp+temp+madvrtx[lastframe][vertex]>>2;
                pointx = temp;///chrscale[cnt];
                temp = madvrty[frame][vertex];
                temp = temp+temp+temp+madvrty[lastframe][vertex]>>2;
                pointy = temp;///chrscale[cnt];
                temp = madvrtz[frame][vertex];
                temp = temp+temp+temp+madvrtz[lastframe][vertex]>>2;
                pointz = temp;///chrscale[cnt];
                break;
            case 3:  // 100% this frame...  This is the legible one
                pointx = madvrtx[frame][vertex];///chrscale[cnt];
                pointy = madvrty[frame][vertex];///chrscale[cnt];
                pointz = madvrtz[frame][vertex];///chrscale[cnt];
                break;
        }





        // Do the transform
        prtxpos[particle] = (pointx*chrmatrix[character]_CNV(0,0) +
                             pointy*chrmatrix[character]_CNV(1,0) +
                             pointz*chrmatrix[character]_CNV(2,0));
        prtypos[particle] = (pointx*chrmatrix[character]_CNV(0,1) +
                             pointy*chrmatrix[character]_CNV(1,1) +
                             pointz*chrmatrix[character]_CNV(2,1));
        prtzpos[particle] = (pointx*chrmatrix[character]_CNV(0,2) +
                             pointy*chrmatrix[character]_CNV(1,2) +
                             pointz*chrmatrix[character]_CNV(2,2));
        prtxpos[particle] += chrmatrix[character]_CNV(3,0);
        prtypos[particle] += chrmatrix[character]_CNV(3,1);
        prtzpos[particle] += chrmatrix[character]_CNV(3,2);
    }
    else
    {
        // No matrix, so just wing it...
        prtxpos[particle] = chrxpos[character];
        prtypos[particle] = chrypos[character];
        prtzpos[particle] = chrzpos[character];
    }
}

//--------------------------------------------------------------------------------------------
void make_one_weapon_matrix(unsigned short cnt)
{
    // ZZ> This function sets one weapon's matrix, based on who it's attached to
    #define POINTS 4
    int tnc;
    unsigned short character, vertex, model, frame, lastframe;
    unsigned char lip;
    float pointx[POINTS];
    float pointy[POINTS];
    float pointz[POINTS];
    float nupointx[POINTS];
    float nupointy[POINTS];
    float nupointz[POINTS];
    int temp;


    // Transform the weapon grip from model to world space
    character = chrattachedto[cnt];
    model = chrmodel[character];
    frame = chrframe[character];
    lastframe = chrlastframe[character];
    lip = chrlip[character]>>6;
    chrmatrixvalid[cnt] = TRUE;


    // Calculate grip point locations with linear interpolation and other silly things
    switch(lip)
    {
        case 0:  // 25% this frame
            vertex = chrweapongrip[cnt][0];
            temp = madvrtx[lastframe][vertex];
            temp = temp+temp+temp+madvrtx[frame][vertex]>>2;
            pointx[0] = temp;
            temp = madvrty[lastframe][vertex];
            temp = temp+temp+temp+madvrty[frame][vertex]>>2;
            pointy[0] = temp;
            temp = madvrtz[lastframe][vertex];
            temp = temp+temp+temp+madvrtz[frame][vertex]>>2;
            pointz[0] = temp;

            vertex = chrweapongrip[cnt][1];
            temp = madvrtx[lastframe][vertex];
            temp = temp+temp+temp+madvrtx[frame][vertex]>>2;
            pointx[1] = temp;
            temp = madvrty[lastframe][vertex];
            temp = temp+temp+temp+madvrty[frame][vertex]>>2;
            pointy[1] = temp;
            temp = madvrtz[lastframe][vertex];
            temp = temp+temp+temp+madvrtz[frame][vertex]>>2;
            pointz[1] = temp;

            vertex = chrweapongrip[cnt][2];
            temp = madvrtx[lastframe][vertex];
            temp = temp+temp+temp+madvrtx[frame][vertex]>>2;
            pointx[2] = temp;
            temp = madvrty[lastframe][vertex];
            temp = temp+temp+temp+madvrty[frame][vertex]>>2;
            pointy[2] = temp;
            temp = madvrtz[lastframe][vertex];
            temp = temp+temp+temp+madvrtz[frame][vertex]>>2;
            pointz[2] = temp;

            vertex = chrweapongrip[cnt][3];
            temp = madvrtx[lastframe][vertex];
            temp = temp+temp+temp+madvrtx[frame][vertex]>>2;
            pointx[3] = temp;
            temp = madvrty[lastframe][vertex];
            temp = temp+temp+temp+madvrty[frame][vertex]>>2;
            pointy[3] = temp;
            temp = madvrtz[lastframe][vertex];
            temp = temp+temp+temp+madvrtz[frame][vertex]>>2;
            pointz[3] = temp;
            break;
        case 1:  // 50% this frame
            vertex = chrweapongrip[cnt][0];
            pointx[0] = (madvrtx[frame][vertex]+madvrtx[lastframe][vertex]>>1);
            pointy[0] = (madvrty[frame][vertex]+madvrty[lastframe][vertex]>>1);
            pointz[0] = (madvrtz[frame][vertex]+madvrtz[lastframe][vertex]>>1);
            vertex = chrweapongrip[cnt][1];
            pointx[1] = (madvrtx[frame][vertex]+madvrtx[lastframe][vertex]>>1);
            pointy[1] = (madvrty[frame][vertex]+madvrty[lastframe][vertex]>>1);
            pointz[1] = (madvrtz[frame][vertex]+madvrtz[lastframe][vertex]>>1);
            vertex = chrweapongrip[cnt][2];
            pointx[2] = (madvrtx[frame][vertex]+madvrtx[lastframe][vertex]>>1);
            pointy[2] = (madvrty[frame][vertex]+madvrty[lastframe][vertex]>>1);
            pointz[2] = (madvrtz[frame][vertex]+madvrtz[lastframe][vertex]>>1);
            vertex = chrweapongrip[cnt][3];
            pointx[3] = (madvrtx[frame][vertex]+madvrtx[lastframe][vertex]>>1);
            pointy[3] = (madvrty[frame][vertex]+madvrty[lastframe][vertex]>>1);
            pointz[3] = (madvrtz[frame][vertex]+madvrtz[lastframe][vertex]>>1);
            break;
        case 2:  // 75% this frame
            vertex = chrweapongrip[cnt][0];
            temp = madvrtx[frame][vertex];
            temp = temp+temp+temp+madvrtx[lastframe][vertex]>>2;
            pointx[0] = temp;
            temp = madvrty[frame][vertex];
            temp = temp+temp+temp+madvrty[lastframe][vertex]>>2;
            pointy[0] = temp;
            temp = madvrtz[frame][vertex];
            temp = temp+temp+temp+madvrtz[lastframe][vertex]>>2;
            pointz[0] = temp;


            vertex = chrweapongrip[cnt][1];
            temp = madvrtx[frame][vertex];
            temp = temp+temp+temp+madvrtx[lastframe][vertex]>>2;
            pointx[1] = temp;
            temp = madvrty[frame][vertex];
            temp = temp+temp+temp+madvrty[lastframe][vertex]>>2;
            pointy[1] = temp;
            temp = madvrtz[frame][vertex];
            temp = temp+temp+temp+madvrtz[lastframe][vertex]>>2;
            pointz[1] = temp;


            vertex = chrweapongrip[cnt][2];
            temp = madvrtx[frame][vertex];
            temp = temp+temp+temp+madvrtx[lastframe][vertex]>>2;
            pointx[2] = temp;
            temp = madvrty[frame][vertex];
            temp = temp+temp+temp+madvrty[lastframe][vertex]>>2;
            pointy[2] = temp;
            temp = madvrtz[frame][vertex];
            temp = temp+temp+temp+madvrtz[lastframe][vertex]>>2;
            pointz[2] = temp;


            vertex = chrweapongrip[cnt][3];
            temp = madvrtx[frame][vertex];
            temp = temp+temp+temp+madvrtx[lastframe][vertex]>>2;
            pointx[3] = temp;
            temp = madvrty[frame][vertex];
            temp = temp+temp+temp+madvrty[lastframe][vertex]>>2;
            pointy[3] = temp;
            temp = madvrtz[frame][vertex];
            temp = temp+temp+temp+madvrtz[lastframe][vertex]>>2;
            pointz[3] = temp;

            break;
        case 3:  // 100% this frame...  This is the legible one
            vertex = chrweapongrip[cnt][0];
            pointx[0] = madvrtx[frame][vertex];
            pointy[0] = madvrty[frame][vertex];
            pointz[0] = madvrtz[frame][vertex];
            vertex = chrweapongrip[cnt][1];
            pointx[1] = madvrtx[frame][vertex];
            pointy[1] = madvrty[frame][vertex];
            pointz[1] = madvrtz[frame][vertex];
            vertex = chrweapongrip[cnt][2];
            pointx[2] = madvrtx[frame][vertex];
            pointy[2] = madvrty[frame][vertex];
            pointz[2] = madvrtz[frame][vertex];
            vertex = chrweapongrip[cnt][3];
            pointx[3] = madvrtx[frame][vertex];
            pointy[3] = madvrty[frame][vertex];
            pointz[3] = madvrtz[frame][vertex];
            break;
    }





    tnc = 0;
    while(tnc < POINTS)
    {
        // Do the transform
        nupointx[tnc] = (pointx[tnc]*chrmatrix[character]_CNV(0,0) +
                       pointy[tnc]*chrmatrix[character]_CNV(1,0) +
                       pointz[tnc]*chrmatrix[character]_CNV(2,0));
        nupointy[tnc] = (pointx[tnc]*chrmatrix[character]_CNV(0,1) +
                       pointy[tnc]*chrmatrix[character]_CNV(1,1) +
                       pointz[tnc]*chrmatrix[character]_CNV(2,1));
        nupointz[tnc] = (pointx[tnc]*chrmatrix[character]_CNV(0,2) +
                       pointy[tnc]*chrmatrix[character]_CNV(1,2) +
                       pointz[tnc]*chrmatrix[character]_CNV(2,2));

        nupointx[tnc] += chrmatrix[character]_CNV(3,0);
        nupointy[tnc] += chrmatrix[character]_CNV(3,1);
        nupointz[tnc] += chrmatrix[character]_CNV(3,2);

        tnc++;
    }




    // Calculate weapon's matrix based on positions of grip points
    // chrscale is recomputed at time of attachment
    chrmatrix[cnt] = FourPoints(nupointx[0], nupointy[0], nupointz[0],
                                nupointx[1], nupointy[1], nupointz[1],
                                nupointx[2], nupointy[2], nupointz[2],
                                nupointx[3], nupointy[3], nupointz[3],
                                chrscale[cnt]);
}

//--------------------------------------------------------------------------------------------
void make_character_matrices()
{
    // ZZ> This function makes all of the character's matrices
    int cnt, tnc;

    // Forget about old matrices
    cnt = 0;
    while(cnt < MAXCHR)
    {
        chrmatrixvalid[cnt] = FALSE;
        cnt++;
    }


    // Do base characters
    tnc = 0;
    while(tnc < MAXCHR)
    {
        if(chrattachedto[tnc] == MAXCHR && chron[tnc])  // Skip weapons for now
        {
            make_one_character_matrix(tnc);
        }
        tnc++;
    }



    // Do first level of attachments
    tnc = 0;
    while(tnc < MAXCHR)
    {
        if(chrattachedto[tnc] != MAXCHR && chron[tnc])
        {
            if(chrattachedto[chrattachedto[tnc]] == MAXCHR)
            {
                make_one_weapon_matrix(tnc);
            }
        }
        tnc++;
    }


    // Do second level of attachments
    tnc = 0;
    while(tnc < MAXCHR)
    {
        if(chrattachedto[tnc] != MAXCHR && chron[tnc])
        {
            if(chrattachedto[chrattachedto[tnc]] != MAXCHR)
            {
                make_one_weapon_matrix(tnc);
            }
        }
        tnc++;
    }
}

//--------------------------------------------------------------------------------------------
int get_free_character()
{
    // ZZ> This function gets an unused character and returns its index
    int character;


    if(numfreechr==0)
    {
        // Return MAXCHR if we can't find one
        return MAXCHR;
    }
    else
    {
        // Just grab the next one
        numfreechr--;
        character=freechrlist[numfreechr];
    }
    return character;
}

//--------------------------------------------------------------------------------------------
unsigned char find_target_in_block(int x, int y, float chrx, float chry, unsigned short facing, 
  unsigned char onlyfriends, unsigned char anyone, unsigned char team,
  unsigned short donttarget, unsigned short oldtarget)
{
    // ZZ> This function helps find a target, returning TRUE if it found a decent target
    int cnt;
    unsigned short angle;
    unsigned short charb;
    unsigned char enemies, returncode;
    unsigned int fanblock;
    int distance;



    returncode = FALSE;


    // Current fanblock
    if(x >= 0 && x < (meshsizex>>2) && y >= 0 && y < (meshsizey>>2))
    {
        fanblock = x+meshblockstart[y];


        enemies = FALSE;
        if(onlyfriends==FALSE) enemies = TRUE;


        charb = meshbumplistchr[fanblock];
        cnt = 0;
        while(cnt < meshbumplistchrnum[fanblock])
        {
            if(chralive[charb] && chrinvictus[charb]==FALSE && charb != donttarget && charb != oldtarget)
            {
                if(anyone || (chrteam[charb]==team && onlyfriends) || (teamhatesteam[team][chrteam[charb]] && enemies))
                {
                    distance = ABS(chrxpos[charb]-chrx) + ABS(chrypos[charb]-chry);
                    if(distance < globestdistance)
                    {
                        angle = (atan2(chrypos[charb]-chry, chrxpos[charb]-chrx)+PI)*65535/(2*PI);
                        angle = facing-angle;
                        if(angle < globestangle || angle > (65535-globestangle))
                        {
                            returncode = TRUE;
                            globesttarget = charb;
                            globestdistance = distance;
                            glouseangle = angle;
                            if(angle  > 32767)
                                globestangle = -angle;
                            else
                                globestangle = angle;
                        }
                    }
                }
            }
            charb = chrbumpnext[charb];
            cnt++;
        }
    }
    return returncode;
}

//--------------------------------------------------------------------------------------------
unsigned short find_target(float chrx, float chry, unsigned short facing, 
  unsigned short targetangle, unsigned char onlyfriends, unsigned char anyone,
  unsigned char team, unsigned short donttarget, unsigned short oldtarget)
{
    // This function finds the best target for the given parameters
    unsigned char done;
    int x, y;

    x = chrx;
    y = chry;
    x = x>>9;
    y = y>>9;
    globestdistance = 9999;
    globestangle = targetangle;
    done = find_target_in_block(x, y, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x+1, y, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x-1, y, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x, y+1, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x, y-1, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    if(done) return globesttarget;


    done = find_target_in_block(x+1, y+1, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x+1, y-1, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x-1, y+1, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    done |= find_target_in_block(x-1, y-1, chrx, chry, facing, onlyfriends, anyone, team, donttarget, oldtarget);
    if(done) return globesttarget;


    return MAXCHR;
}

//--------------------------------------------------------------------------------------------
void free_all_characters()
{
    // ZZ> This function resets the character allocation list
    nolocalplayers = TRUE;
    numfreechr = 0;
    while(numfreechr<MAXCHR)
    {
        chron[numfreechr] = FALSE;
        chralive[numfreechr] = FALSE;
        chrinpack[numfreechr] = FALSE;
        chrnuminpack[numfreechr] = 0;
        chrnextinpack[numfreechr] = MAXCHR;
        chrstaton[numfreechr] = FALSE;
        chrmatrixvalid[numfreechr] = FALSE;
        freechrlist[numfreechr] = numfreechr;
        numfreechr++;
    }
    numpla = 0;
    numlocalpla = 0;
    numstat = 0;
}

//--------------------------------------------------------------------------------------------
unsigned char __chrhitawall(int character)
{
    // ZZ> This function returns nonzero if the character hit a wall that the
    //     character is not allowed to cross
    unsigned char passtl, passtr, passbr, passbl;
    int x, y, bs;


    y = chrypos[character];  x = chrxpos[character];  bs = chrbumpsize[character]>>1;
    // !!!BAD!!! Should really do bound checking...
    passtl = meshfx[meshfanstart[y-bs>>7]+(x-bs>>7)];
    passtr = meshfx[meshfanstart[y-bs>>7]+(x+bs>>7)];
    passbr = meshfx[meshfanstart[y+bs>>7]+(x+bs>>7)];
    passbl = meshfx[meshfanstart[y+bs>>7]+(x-bs>>7)];
    passtl = (passtl|passtr|passbr|passbl)&chrstoppedby[character];
    return passtl;
}

//--------------------------------------------------------------------------------------------
void reset_character_accel(unsigned short character)
{
    // ZZ> This function fixes a character's max acceleration
    unsigned short enchant;

    if(character != MAXCHR)
    {
        if(chron[character])
        {
            // Okay, remove all acceleration enchants
            enchant = chrfirstenchant[character];
            while(enchant < MAXENCHANT)
            {
                remove_enchant_value(enchant, ADDACCEL);
                enchant = encnextenchant[enchant];
            }
            // Set the starting value
            chrmaxaccel[character] = capmaxaccel[chrmodel[character]][chrtexture[character]-madskinstart[chrmodel[character]]];
            // Put the acceleration enchants back on
            enchant = chrfirstenchant[character];
            while(enchant < MAXENCHANT)
            {
                add_enchant_value(enchant, ADDACCEL, enceve[enchant]);
                enchant = encnextenchant[enchant];
            }
        }
    }
}

//--------------------------------------------------------------------------------------------
void detach_character_from_mount(unsigned short character, unsigned char ignorekurse,
  unsigned char doshop)
{
    // ZZ> This function drops an item
    unsigned short mount, hand, enchant, cnt, passage, owner, price;
    unsigned char inshop;
    int loc;


    // Make sure the character is valid
    if(character==MAXCHR)
        return;


    // Make sure the character is mounted
    mount = chrattachedto[character];
    if(mount>=MAXCHR)
        return;


    // Make sure both are still around
    if(!chron[character] || !chron[mount])
        return;


    // Don't allow living characters to drop kursed weapons
    if(ignorekurse==FALSE && chriskursed[character] && chralive[mount] && chrisitem[character])
    {
        chralert[character] = chralert[character]|ALERTIFNOTDROPPED;
        return;
    }


    // Figure out which hand it's in
    hand = 0;
    if(chrinwhichhand[character] == GRIPRIGHT)
    {
        hand = 1;
    }


    // Rip 'em apart
    chrattachedto[character] = MAXCHR;
    if(chrholdingwhich[mount][0] == character)
        chrholdingwhich[mount][0] = MAXCHR;
    if(chrholdingwhich[mount][1] == character)
        chrholdingwhich[mount][1] = MAXCHR;
    chrscale[character] = chrfat[character]*madscale[chrmodel[character]]*4;


    // Run the falling animation...
    play_action(character, ACTIONJB+hand, FALSE);



    // Set the positions
    if(chrmatrixvalid[character])
    {
      chrxpos[character] = chrmatrix[character]_CNV(3,0);
      chrypos[character] = chrmatrix[character]_CNV(3,1);
      chrzpos[character] = chrmatrix[character]_CNV(3,2);
    }
    else
    {
      chrxpos[character] = chrxpos[mount];
      chrypos[character] = chrypos[mount];
      chrzpos[character] = chrzpos[mount];
    }



    // Make sure it's not dropped in a wall...
    if(__chrhitawall(character))
    {
        chrxpos[character] = chrxpos[mount];
        chrypos[character] = chrypos[mount];
    }


    // Check for shop passages
    inshop = FALSE;
    if(chrisitem[character] && numshoppassage != 0 && doshop)
    {
        cnt = 0;
        while(cnt < numshoppassage)
        {
            passage = shoppassage[cnt];
            loc = chrxpos[character];
            loc = loc >> 7;
            if(loc >= passtlx[passage] && loc <= passbrx[passage])
            {
                loc = chrypos[character];
                loc = loc >> 7;
                if(loc >= passtly[passage] && loc <= passbry[passage])
                {
                    inshop = TRUE;
                    owner = shopowner[passage];
                    cnt = numshoppassage;  // Finish loop
                    if(owner == NOOWNER)
                    {
                        // The owner has died!!!
                        inshop = FALSE;
                    }
                }
            }
            cnt++;
        }
        if(inshop)
        {
            // Give the mount its money back, alert the shop owner
            price = capskincost[chrmodel[character]][0];
            if(capisstackable[chrmodel[character]])
            {
                price = price * chrammo[character];
            }
            chrmoney[mount] += price;
            chrmoney[owner] -= price;
            if(chrmoney[owner] < 0)  chrmoney[owner] = 0;
            if(chrmoney[mount] > MAXMONEY)  chrmoney[mount] = MAXMONEY;
            chralert[owner] |= ALERTIFORDERED;
            chrorder[owner] = price;  // Tell owner how much...
            chrcounter[owner] = 0;  // 0 for buying an item
        }
    }



    // Make sure it works right
    chrhitready[character]=TRUE;
    if(inshop)
    {
      // Drop straight down to avoid theft
      chrxvel[character]=0;
      chryvel[character]=0;
    }
    else
    {
      chrxvel[character]=chrxvel[mount];
      chryvel[character]=chryvel[mount];
    }
    chrzvel[character]=DROPZVEL;


    // Turn looping off
    chrloopaction[character]=FALSE;


    // Reset the team if it is a mount
    if(chrismount[mount])
    {
        chrteam[mount] = chrbaseteam[mount];
        chralert[mount] |= ALERTIFDROPPED;
    }
    chrteam[character] = chrbaseteam[character];
    chralert[character] |= ALERTIFDROPPED;


    // Reset transparency
    if(chrisitem[character] && chrtransferblend[mount])
    {
        // Okay, reset transparency
        enchant = chrfirstenchant[character];
        while(enchant < MAXENCHANT)
        {
            unset_enchant_value(enchant, SETALPHABLEND);
            unset_enchant_value(enchant, SETLIGHTBLEND);
            enchant = encnextenchant[enchant];
        }
        chralpha[character] = capalpha[chrmodel[character]];
        chrlight[character] = caplight[chrmodel[character]];
        enchant = chrfirstenchant[character];
        while(enchant < MAXENCHANT)
        {
            set_enchant_value(enchant, SETALPHABLEND, enceve[enchant]);
            set_enchant_value(enchant, SETLIGHTBLEND, enceve[enchant]);
            enchant = encnextenchant[enchant];
        }
    }


    // Set twist
    chrturnmaplr[character] = 32768;
    chrturnmapud[character] = 32768;
}

//--------------------------------------------------------------------------------------------
void attach_character_to_mount(unsigned short character, unsigned short mount,
    unsigned short grip)
{
    // ZZ> This function attaches one character to another ( the mount )
    //     at either the left or right grip
    int tnc, hand;


    // Make sure both are still around
    if(!chron[character] || !chron[mount] || chrinpack[character] || chrinpack[mount])
        return;

    // Figure out which hand this grip relates to
    hand = 1;
    if(grip == GRIPLEFT)
        hand = 0;


    // Make sure the the hand is valid
    if(capgripvalid[chrmodel[mount]][hand]==FALSE)
        return;


    // Put 'em together
    chrinwhichhand[character] = grip;
    chrattachedto[character] = mount;
    chrholdingwhich[mount][hand] = character;
    tnc = madvertices[chrmodel[mount]] - grip;
    chrweapongrip[character][0] = tnc;
    chrweapongrip[character][1] = tnc+1;
    chrweapongrip[character][2] = tnc+2;
    chrweapongrip[character][3] = tnc+3;
    chrscale[character] = chrfat[character]/(chrfat[mount]*1280);
    chrinwater[character] = FALSE;
    chrjumptime[character]=JUMPDELAY*4;


    // Run the held animation
    if(chrismount[mount] && grip == GRIPONLY)
    {
        // Riding mount
        play_action(character, ACTIONMI, TRUE);
        chrloopaction[character]=TRUE;
    }
    else
    {
        play_action(character, ACTIONMM+hand, FALSE);
        if(chrisitem[character])
        {
            // Item grab
            chrkeepaction[character]=TRUE;
        }
    }




    // Set the team
    if(chrisitem[character])
    {
        chrteam[character]=chrteam[mount];
        // Set the alert
        chralert[character] = chralert[character]|ALERTIFGRABBED;
    }
    if(chrismount[mount])
    {
        chrteam[mount]=chrteam[character];
        // Set the alert
        if(chrisitem[mount]==FALSE)
        {
            chralert[mount] = chralert[mount]|ALERTIFGRABBED;
        }
    }


    // It's not gonna hit the floor
    chrhitready[character]=FALSE;
}

//--------------------------------------------------------------------------------------------
unsigned short stack_in_pack(unsigned short item, unsigned short character)
{
    // ZZ> This function looks in the character's pack for an item similar
    //     to the one given.  If it finds one, it returns the similar item's
    //     index number, otherwise it returns MAXCHR.
    unsigned short inpack, id;
    unsigned char allok;


    if(capisstackable[chrmodel[item]]==TRUE)
    {
        inpack = chrnextinpack[character];
        allok = FALSE;
        while(inpack != MAXCHR && allok == FALSE)
        {
            allok = TRUE;
            if(chrmodel[inpack]!=chrmodel[item])
            {
                if(capisstackable[chrmodel[inpack]]==FALSE)
                {
                    allok = FALSE;
                }

                if(chrammomax[inpack]!=chrammomax[item])
                {
                    allok = FALSE;
                }

                id = 0;
                while(id < MAXIDSZ && allok == TRUE)
                {
                    if(capidsz[chrmodel[inpack]][id]!=capidsz[chrmodel[item]][id])
                    {
                        allok = FALSE;
                    }
                    id++;
                }
            }
            if(allok == FALSE)
            {
                inpack = chrnextinpack[inpack];
            }
        }
        if(allok == TRUE)
        {
            return inpack;
        }
    }
    return MAXCHR;
}

//--------------------------------------------------------------------------------------------
void add_item_to_character_pack(unsigned short item, unsigned short character)
{
    // ZZ> This function puts one character inside the other's pack
    unsigned short oldfirstitem, newammo, stack;


    // Make sure everything is hunkydori
    if((!chron[item]) || (!chron[character]) || chrinpack[item] || chrinpack[character] ||
       chrisitem[character])
        return;


    stack = stack_in_pack(item, character);
    if(stack != MAXCHR)
    {
        // We found a similar, stackable item in the pack
        if(chrnameknown[item] || chrnameknown[stack])
        {
            chrnameknown[item] = TRUE;
            chrnameknown[stack] = TRUE;
        }
        if(capusageknown[chrmodel[item]] || capusageknown[chrmodel[stack]])
        {
            capusageknown[chrmodel[item]] = TRUE;
            capusageknown[chrmodel[stack]] = TRUE;
        }
        newammo = chrammo[item] + chrammo[stack];
        if(newammo <= chrammomax[stack])
        {
            // All transfered, so kill the in hand item
            chrammo[stack] = newammo;
            if(chrattachedto[item] != MAXCHR)
            {
                detach_character_from_mount(item, TRUE, FALSE);
            }
            free_one_character(item);
        }
        else
        {
            // Only some were transfered, 
            chrammo[item] = chrammo[item]+chrammo[stack]-chrammomax[stack];
            chrammo[stack] = chrammomax[stack];
            chralert[character]|=ALERTIFTOOMUCHBAGGAGE;
        }
    }
    else
    {
        // Make sure we have room for another item
        if(chrnuminpack[character] >= MAXNUMINPACK)
        {
            chralert[character]|=ALERTIFTOOMUCHBAGGAGE;
            return;
        }


        // Take the item out of hand
        if(chrattachedto[item] != MAXCHR)
        {
            detach_character_from_mount(item, TRUE, FALSE);
            chralert[item] &= (~ALERTIFDROPPED);
        }


        // Remove the item from play
        chrhitready[item] = FALSE;
        chrinpack[item] = TRUE;


        // Insert the item into the pack as the first one
        oldfirstitem = chrnextinpack[character];
        chrnextinpack[character] = item;
        chrnextinpack[item] = oldfirstitem;
        chrnuminpack[character]++;
        if(capisequipment[chrmodel[item]])
        {
            // AtLastWaypoint doubles as PutAway
            chralert[item]|=ALERTIFATLASTWAYPOINT;
        }
    }
    return;
}

//--------------------------------------------------------------------------------------------
unsigned short get_item_from_character_pack(unsigned short character, unsigned short grip, unsigned char ignorekurse)
{
    // ZZ> This function takes the last item in the character's pack and puts
    //     it into the designated hand.  It returns the item number or MAXCHR.
    unsigned short item, nexttolastitem;


    // Make sure everything is hunkydori
    if((!chron[character]) || chrinpack[character] || chrisitem[character] || chrnextinpack[character]==MAXCHR)
        return MAXCHR;
    if(chrnuminpack[character] == 0)
        return MAXCHR;


    // Find the last item in the pack
    nexttolastitem = character;
    item = chrnextinpack[character];
    while(chrnextinpack[item] != MAXCHR)
    {
        nexttolastitem = item;
        item = chrnextinpack[item];
    }


    // Figure out what to do with it
    if(chriskursed[item] && chrisequipped[item] && ignorekurse == FALSE)
    {
        // Flag the last item as not removed
        chralert[item] |= ALERTIFNOTPUTAWAY;  // Doubles as IfNotTakenOut
        // Cycle it to the front
        chrnextinpack[item] = chrnextinpack[character];
        chrnextinpack[nexttolastitem] = MAXCHR;
        chrnextinpack[character] = item;
        if(character == nexttolastitem)
        {
            chrnextinpack[item] = MAXCHR;
        }
        return MAXCHR;
    }
    else
    {
        // Remove the last item from the pack
        chrinpack[item] = FALSE;
        chrisequipped[item] = FALSE;
        chrnextinpack[nexttolastitem] = MAXCHR;
        chrnuminpack[character]--;
        chrteam[item]=chrteam[character];


        // Attach the item to the character's hand
        attach_character_to_mount(item, character, grip);
        chralert[item]&=(~ALERTIFGRABBED);
        chralert[item]|=(ALERTIFTAKENOUT);
    }
    return item;
}

//--------------------------------------------------------------------------------------------
void drop_keys(unsigned short character)
{
    // ZZ> This function drops all keys ( [KEYA] to [KEYZ] ) that are in a character's
    //     inventory ( Not hands ).
    unsigned short item, lastitem, nextitem, direction, cosdir;
    int testa, testz;


    if(character < MAXCHR)
    {
        if(chron[character])
        {
            if(chrzpos[character] > -2) // Don't lose keys in pits...
            {
                // The IDSZs to find
                testa = ('K'-'A'<<15)|('E'-'A'<<10)|('Y'-'A'<<5)|('A'-'A');  // [KEYA]
                testz = ('K'-'A'<<15)|('E'-'A'<<10)|('Y'-'A'<<5)|('Z'-'A');  // [KEYZ]


                lastitem = character;
                item = chrnextinpack[character];
                while(item != MAXCHR)
                {
                    nextitem = chrnextinpack[item];
                    if(item != character)  // Should never happen...
                    {
                        if((capidsz[chrmodel[item]][IDSZPARENT]>=(unsigned int) testa &&
                            capidsz[chrmodel[item]][IDSZPARENT]<=(unsigned int) testz) ||
                           (capidsz[chrmodel[item]][IDSZTYPE]>=(unsigned int) testa &&
                            capidsz[chrmodel[item]][IDSZTYPE]<=(unsigned int) testz))
                        {
                            // We found a key...
                            chrinpack[item] = FALSE;
                            chrisequipped[item] = FALSE;
                            chrnextinpack[lastitem] = nextitem;
                            chrnextinpack[item] = MAXCHR;
                            chrnuminpack[character]--;
                            chrattachedto[item] = MAXCHR;
                            chralert[item] |= ALERTIFDROPPED;
                            chrhitready[item]=TRUE;


                            direction = RANDIE;
                            chrturnleftright[item] = direction+32768;
                            cosdir = direction + 16384;
                            chrlevel[item] = chrlevel[character];
                            chrxpos[item] = chrxpos[character];
                            chrypos[item] = chrypos[character];
                            chrzpos[item] = chrzpos[character];
                            chrxvel[item] = turntosin[cosdir>>2]*DROPXYVEL;
                            chryvel[item] = turntosin[direction>>2]*DROPXYVEL;
                            chrzvel[item] = DROPZVEL;
                            chrteam[item] = chrbaseteam[item];
                        }
                        else
                        {
                            lastitem = item;
                        }
                    }
                    item = nextitem;
                }
            }
        }
    }
}

//--------------------------------------------------------------------------------------------
void drop_all_items(unsigned short character)
{
    // ZZ> This function drops all of a character's items
    unsigned short item, direction, cosdir, diradd;


    if(character < MAXCHR)
    {
        if(chron[character])
        {
            detach_character_from_mount(chrholdingwhich[character][0], TRUE, FALSE);
            detach_character_from_mount(chrholdingwhich[character][1], TRUE, FALSE);
            if(chrnuminpack[character] > 0)
            {
                direction = chrturnleftright[character]+32768;
                diradd = 65535 / chrnuminpack[character];
                while(chrnuminpack[character] > 0)
                {
                    item = get_item_from_character_pack(character, GRIPLEFT, FALSE);
                    if(item < MAXCHR)
                    {
                        detach_character_from_mount(item, TRUE, TRUE);
                        chrhitready[item]=TRUE;
                        chralert[item]|=ALERTIFDROPPED;
                        chrxpos[item] = chrxpos[character];
                        chrypos[item] = chrypos[character];
                        chrzpos[item] = chrzpos[character];
                        chrlevel[item] = chrlevel[character];
                        chrturnleftright[item] = direction+32768;
                        cosdir = direction + 16384;
                        chrxvel[item] = turntosin[cosdir>>2]*DROPXYVEL;
                        chryvel[item] = turntosin[direction>>2]*DROPXYVEL;
                        chrzvel[item] = DROPZVEL;
                        chrteam[item] = chrbaseteam[item];
                    } 
                    direction += diradd;
                }
            }
        }
    }
}

//--------------------------------------------------------------------------------------------
void character_grab_stuff(int chara, int grip, unsigned char people)
{
    // ZZ> This function makes the character pick up an item if there's one around
    float xa, ya, za, xb, yb, zb, dist;
    int charb, hand;
    unsigned short vertex, model, frame, owner, passage, cnt, price;
    float pointx, pointy, pointz;
    unsigned char inshop;
    int loc;


    // Make life easier
    model = chrmodel[chara];
    hand = (grip-4)>>2;  // 0 is left, 1 is right


    // Make sure the character doesn't have something already, and that it has hands
    if(chrholdingwhich[chara][hand]!=MAXCHR || capgripvalid[model][hand] == FALSE)
        return;


    // Do we have a matrix???
    if(chrmatrixvalid[chara])//meshinrenderlist[chronwhichfan[chara]])
    {
        // Transform the weapon grip from model to world space
        frame = chrframe[chara];
        vertex = madvertices[model] - grip;


        // Calculate grip point locations
        pointx = madvrtx[frame][vertex];///chrscale[cnt];
        pointy = madvrty[frame][vertex];///chrscale[cnt];
        pointz = madvrtz[frame][vertex];///chrscale[cnt];


        // Do the transform
        xa = (pointx*chrmatrix[chara]_CNV(0,0) +
              pointy*chrmatrix[chara]_CNV(1,0) +
              pointz*chrmatrix[chara]_CNV(2,0));
        ya = (pointx*chrmatrix[chara]_CNV(0,1) +
              pointy*chrmatrix[chara]_CNV(1,1) +
              pointz*chrmatrix[chara]_CNV(2,1));
        za = (pointx*chrmatrix[chara]_CNV(0,2) +
              pointy*chrmatrix[chara]_CNV(1,2) +
              pointz*chrmatrix[chara]_CNV(2,2));
        xa += chrmatrix[chara]_CNV(3,0);
        ya += chrmatrix[chara]_CNV(3,1);
        za += chrmatrix[chara]_CNV(3,2);
    }
    else
    {
        // Just wing it
        xa = chrxpos[chara];
        ya = chrypos[chara];
        za = chrzpos[chara];
    }



    // Go through all characters to find the best match
    charb = 0;
    while(charb < MAXCHR)
    {
        if(chron[charb] && (!chrinpack[charb]) && chrweight[charb]<chrweight[chara] && chralive[charb] && chrattachedto[charb]==MAXCHR && ((people==FALSE&&chrisitem[charb]) || (people==TRUE&&chrisitem[charb]==FALSE)))
        {
            xb = chrxpos[charb];
            yb = chrypos[charb];
            zb = chrzpos[charb];
            // First check absolute value diamond
            xb = ABS(xa-xb);
            yb = ABS(ya-yb);
            zb = ABS(za-zb);
            dist = xb+yb;
            if(dist < GRABSIZE && zb < GRABSIZE)
            {
                // Don't grab your mount
                if(chrholdingwhich[charb][0]!=chara && chrholdingwhich[charb][1]!=chara)
                {
                    // Check for shop
                    inshop = FALSE;
                    if(chrisitem[charb] && numshoppassage != 0)
                    {
                        cnt = 0;
                        while(cnt < numshoppassage)
                        {
                            passage = shoppassage[cnt];
                            loc = chrxpos[charb];
                            loc = loc >> 7;
                            if(loc >= passtlx[passage] && loc <= passbrx[passage])
                            {
                                loc = chrypos[charb];
                                loc = loc >> 7;
                                if(loc >= passtly[passage] && loc <= passbry[passage])
                                {
                                    inshop = TRUE;
                                    owner = shopowner[passage];
                                    cnt = numshoppassage;  // Finish loop
                                    if(owner == NOOWNER)
                                    {
                                        // The owner has died!!!
                                        inshop = FALSE;
                                    }
                                }
                            }
                            cnt++;
                        }
                        if(inshop)
                        {
                            // Pay the shop owner, or don't allow grab...
                            if(chrisitem[chara])
                            {
                                // Pets can shop for free =]
                                inshop = FALSE;
                            }
                            else
                            {
                                chralert[owner] |= ALERTIFORDERED;
                                price = capskincost[chrmodel[charb]][0];
                                if(capisstackable[chrmodel[charb]])
                                {
                                    price = price * chrammo[charb];
                                }
                                chrorder[owner] = price;  // Tell owner how much...
                                if(chrmoney[chara] >= price)
                                {
                                    // Okay to buy
                                    chrcounter[owner] = 1;  // 1 for selling an item
                                    chrmoney[chara] -= price;  // Skin 0 cost is price
                                    chrmoney[owner] += price;
                                    if(chrmoney[owner] > MAXMONEY)  chrmoney[owner] = MAXMONEY;
                                    inshop = FALSE;
                                }
                                else
                                {
                                    // Don't allow purchase
                                    chrcounter[owner] = 2;  // 2 for "you can't afford that"
                                    inshop = TRUE;
                                }
                            }
                        }
                    }


                    if(inshop == FALSE)
                    {
                        // Stick 'em together and quit
                        attach_character_to_mount(charb, chara, grip);
                        charb=MAXCHR;
                        if(people==TRUE)
                        {
                            // Do a slam animation...  ( Be sure to drop!!! )
                            play_action(chara, ACTIONMC+hand, FALSE);
                        }
                    }
                    else
                    {
                        // Lift the item a little and quit...
                        chrzvel[charb] = DROPZVEL;
                        chrhitready[charb]=TRUE;
                        chralert[charb] |= ALERTIFDROPPED;
                        charb=MAXCHR;
                    }
                }
            }
        }
        charb++;
    }
}

//--------------------------------------------------------------------------------------------
void character_swipe(unsigned short cnt, unsigned char grip)
{
    // ZZ> This function spawns an attack particle
    int weapon, particle, spawngrip, thrown;
    unsigned char action;
    unsigned short tTmp;
    float dampen;
    float x, y, z, velocity;


    weapon = chrholdingwhich[cnt][grip];
    spawngrip = SPAWNLAST;
    action = chraction[cnt];
    // See if it's an unarmed attack...
    if(weapon==MAXCHR)
    {
        weapon = cnt;
        spawngrip = 4+(grip<<2);  // 0 = GRIPLEFT, 1 = GRIPRIGHT
    }


    if(weapon!=cnt && ((capisstackable[chrmodel[weapon]] && chrammo[weapon]>1) || (action >= ACTIONFA && action <= ACTIONFD)))
    {
        // Throw the weapon if it's stacked or a hurl animation
        x = chrxpos[cnt];
        y = chrypos[cnt];
        z = chrzpos[cnt];
        thrown = spawn_one_character(x, y, z, chrmodel[weapon], chrteam[cnt], 0, chrturnleftright[cnt], chrname[weapon], MAXCHR);
        if(thrown < MAXCHR)
        {
            chriskursed[thrown] = FALSE;
            chrammo[thrown] = 1;
            chralert[thrown]|=ALERTIFTHROWN;
            velocity = chrstrength[cnt]/(chrweight[thrown]*THROWFIX);
            velocity += MINTHROWVELOCITY;
            if(velocity > MAXTHROWVELOCITY)
            {
                velocity = MAXTHROWVELOCITY;
            }
            tTmp = chrturnleftright[cnt]>>2;
            chrxvel[thrown]+=turntosin[(tTmp+12288)&16383]*velocity;
            chryvel[thrown]+=turntosin[(tTmp+8192)&16383]*velocity;
            chrzvel[thrown] = DROPZVEL;
            if(chrammo[weapon]<=1)
            {
                // Poof the item
                detach_character_from_mount(weapon, TRUE, FALSE);
                free_one_character(weapon);
            }
            else
            {
                chrammo[weapon]--;
            }
        }
    }
    else
    {
        // Spawn an attack particle
        if(chrammomax[weapon]==0||chrammo[weapon]!=0)
        {
            if(chrammo[weapon]>0 && capisstackable[chrmodel[weapon]]==FALSE)
            {
                chrammo[weapon]--;  // Ammo usage
            }
            if(capattackprttype[chrmodel[weapon]]!=-1)
            {
                particle = spawn_one_particle(chrxpos[weapon], chrypos[weapon], chrzpos[weapon], chrturnleftright[cnt], chrmodel[weapon], capattackprttype[chrmodel[weapon]], weapon, spawngrip, chrteam[cnt], cnt, 0, MAXCHR);
                if(particle!=MAXPRT)
                {
                    if(capattackattached[chrmodel[weapon]]==FALSE)
                    {
                        // Detach the particle
                        if(pipstartontarget[prtpip[particle]]==FALSE || prttarget[particle]==MAXCHR)
                        {
                            attach_particle_to_character(particle, weapon, spawngrip);
                            // Correct Z spacing base, but nothing else...
                            prtzpos[particle]+=pipzspacingbase[prtpip[particle]];
                        }
                        prtattachedtocharacter[particle]=MAXCHR;
                        // Don't spawn in walls
                        if(__prthitawall(particle))
                        {
                            prtxpos[particle] = chrxpos[weapon];
                            prtypos[particle] = chrypos[weapon];
                            if(__prthitawall(particle))
                            {
                                prtxpos[particle] = chrxpos[cnt];
                                prtypos[particle] = chrypos[cnt];
                            }
                        }
                    }
                    else
                    {
                        // Attached particles get a strength bonus for reeling...
                        dampen = REELBASE+(chrstrength[cnt]/REEL);
                        prtxvel[particle] = prtxvel[particle]*dampen;
                        prtyvel[particle] = prtyvel[particle]*dampen;
                        prtzvel[particle] = prtzvel[particle]*dampen;
                    }
                    // Initial particles get a strength bonus, which may be 0.00
                    prtdamagebase[particle]+=(chrstrength[cnt]*capstrengthdampen[chrmodel[weapon]]);
                    // Initial particles get an enchantment bonus
                    prtdamagebase[particle]+=chrdamageboost[weapon];
                    // Initial particles inherit damage type of weapon
                    prtdamagetype[particle] = chrdamagetargettype[weapon];
                }
            }
        }
        else
        {
            chrammoknown[weapon] = TRUE;
        }
    }
}

//--------------------------------------------------------------------------------------------
void move_characters(void)
{
    // ZZ> This function handles character physics
    int cnt;
    unsigned int mapud, maplr;
    unsigned char twist, actionready;
    unsigned char speed, framelip, allowedtoattack;
    float level, friction;
    float dvx, dvy, dvmax;
    unsigned short action, weapon, mount, item;
    int distance, volume;
    unsigned char watchtarget, grounded;


    // Move every character
    cnt = 0;
    while(cnt < MAXCHR)
    {
        if(chron[cnt] && (!chrinpack[cnt]))
        {
            grounded = FALSE;
            valuegopoof = FALSE;
            // Down that ol' damage timer
            chrdamagetime[cnt]-=(chrdamagetime[cnt] != 0);


            // Character's old location
            chroldx[cnt] = chrxpos[cnt];
            chroldy[cnt] = chrypos[cnt];
            chroldz[cnt] = chrzpos[cnt];
            chroldturn[cnt] = chrturnleftright[cnt];
//            if(chrattachedto[cnt]!=MAXCHR)
//            {
//                chrturnleftright[cnt] = chrturnleftright[chrattachedto[cnt]];
//                if(chrindolist[cnt]==FALSE)
//                {
//                    chrxpos[cnt] = chrxpos[chrattachedto[cnt]];
//                    chrypos[cnt] = chrypos[chrattachedto[cnt]];
//                    chrzpos[cnt] = chrzpos[chrattachedto[cnt]];
//                }
//            }


            // Texture movement
            chruoffset[cnt]+=chruoffvel[cnt];
            chrvoffset[cnt]+=chrvoffvel[cnt];


            if(chralive[cnt])
            {
                if(chrattachedto[cnt]==MAXCHR)
                {
                    // Character latches for generalized movement
                    dvx = chrlatchx[cnt];
                    dvy = chrlatchy[cnt];


                    // Reverse movements for daze
                    if(chrdazetime[cnt] > 0)
                    {
                        dvx=-dvx;
                        dvy=-dvy;
                    }
                    // Switch x and y for daze
                    if(chrgrogtime[cnt] > 0)
                    {
                        dvmax = dvx;
                        dvx=dvy;
                        dvy=dvmax;
                    }



                    // Get direction from the DESIRED change in velocity
                    if(chrturnmode[cnt]==TURNMODEWATCH)
                    {
                        if((ABS(dvx) > WATCHMIN || ABS(dvy) > WATCHMIN))
                        {
                            chrturnleftright[cnt] = terp_dir(chrturnleftright[cnt],(atan2(dvy, dvx)+PI)*65535/(2*PI));
                        }
                    }
                    // Face the target
                    watchtarget = (chrturnmode[cnt]==TURNMODEWATCHTARGET);
                    if(watchtarget)
                    {
                        if(cnt != chraitarget[cnt])
                            chrturnleftright[cnt] = terp_dir(chrturnleftright[cnt],(atan2(chrypos[chraitarget[cnt]]-chrypos[cnt], chrxpos[chraitarget[cnt]]-chrxpos[cnt])+PI)*65535/(2*PI));
                    }



                    if(madframefx[chrframe[cnt]]&MADFXSTOP)
                    {
                        dvx = 0;
                        dvy = 0;
                    }
                    else
                    {
                        // Limit to max acceleration
                        dvmax = chrmaxaccel[cnt];
                        if(dvx < -dvmax) dvx=-dvmax;
                        if(dvx > dvmax) dvx=dvmax;
                        if(dvy < -dvmax) dvy=-dvmax;
                        if(dvy > dvmax) dvy=dvmax;
                        chrxvel[cnt]+=dvx;
                        chryvel[cnt]+=dvy;
                    }


                    // Get direction from ACTUAL change in velocity
                    if(chrturnmode[cnt]==TURNMODEVELOCITY)
                    {
                        if(dvx<-TURNSPD||dvx>TURNSPD||dvy<-TURNSPD||dvy>TURNSPD)
                        {
                            if(chrisplayer[cnt])
                            {
                                // Players turn quickly
                                chrturnleftright[cnt] = terp_dir_fast(chrturnleftright[cnt],(atan2(dvy, dvx)+PI)*65535/(2*PI));
                            }
                            else
                            {
                                // AI turn slowly
                                chrturnleftright[cnt] = terp_dir(chrturnleftright[cnt],(atan2(dvy, dvx)+PI)*65535/(2*PI));
                            }
                        }
                    }
                    // Otherwise make it spin
                    else if(chrturnmode[cnt]==TURNMODESPIN)
                    {
                        chrturnleftright[cnt]+=SPINRATE;
                    }
                }


                // Character latches for generalized buttons
                if(chrlatchbutton[cnt]!=0)
                {
                    if(chrlatchbutton[cnt]&LATCHBUTTONJUMP)
                    {
                        if(chrattachedto[cnt]!=MAXCHR && chrjumptime[cnt]==0)
                        {
                            detach_character_from_mount(cnt, TRUE, TRUE);
                            chrjumptime[cnt]=JUMPDELAY;
                            chrzvel[cnt]=DISMOUNTZVEL;
                            if(chrflyheight[cnt] != 0)
                                chrzvel[cnt]=DISMOUNTZVELFLY;
                            chrzpos[cnt]+=chrzvel[cnt];
                            if(chrjumpnumberreset[cnt]!=JUMPINFINITE && chrjumpnumber[cnt] != 0)
                              chrjumpnumber[cnt]--;
                            // Play the sound
                            distance = ABS(camtrackx-chrxpos[cnt])+ABS(camtracky-chrypos[cnt]);
                            volume = -distance;

                            volume = volume<<VOLSHIFT;
                            if(volume > VOLMIN)
                            {
                                if(capwavejump[chrmodel[cnt]] != -1)
                                    play_sound_pvf(capwaveindex[chrmodel[cnt]][capwavejump[chrmodel[cnt]]], PANMID, volume, FRQRANDOM);
                            }

                        }
                        if(chrjumptime[cnt]==0 && chrjumpnumber[cnt]!=0 && chrflyheight[cnt] == 0)
                        {
                            if(chrjumpnumberreset[cnt]!=1 || chrjumpready[cnt])
                            {
                                // Make the character jump
                                chrhitready[cnt]=TRUE;
                                if(chrinwater[cnt])
                                {
                                    chrzvel[cnt]=WATERJUMP;
                                }
                                else
                                {
                                    chrzvel[cnt]=chrjump[cnt];
                                }
                                chrjumptime[cnt]=JUMPDELAY;
                                chrjumpready[cnt]=FALSE;
                                if(chrjumpnumberreset[cnt]!=JUMPINFINITE) chrjumpnumber[cnt]--;
                                // Set to jump animation if not doing anything better
                                if(chractionready[cnt])    play_action(cnt, ACTIONJA, TRUE);
                                // Play the sound
                                distance = ABS(camtrackx-chrxpos[cnt])+ABS(camtracky-chrypos[cnt]);
                                volume = -distance;

                                volume = volume<<VOLSHIFT;
                                if(volume > VOLMIN)
                                {
                                    if(capwavejump[chrmodel[cnt]] != -1)
                                        play_sound_pvf(capwaveindex[chrmodel[cnt]][capwavejump[chrmodel[cnt]]], PANMID, volume, FRQRANDOM);
                                }

                            }
                        }
                    }
                    if((chrlatchbutton[cnt]&LATCHBUTTONALTLEFT)&&chractionready[cnt]&&chrreloadtime[cnt]==0)
                    {
                        chrreloadtime[cnt] = GRABDELAY;
                        if(chrholdingwhich[cnt][0]==MAXCHR)
                        {
                            // Grab left
                            play_action(cnt, ACTIONME, FALSE);
                        }
                        else
                        {
                            // Drop left
                            play_action(cnt, ACTIONMA, FALSE);
                        }
                    }
                    if((chrlatchbutton[cnt]&LATCHBUTTONALTRIGHT)&&chractionready[cnt]&&chrreloadtime[cnt]==0)
                    {
                        chrreloadtime[cnt] = GRABDELAY;
                        if(chrholdingwhich[cnt][1]==MAXCHR)
                        {
                            // Grab right
                            play_action(cnt, ACTIONMF, FALSE);
                        }
                        else
                        {
                            // Drop right
                            play_action(cnt, ACTIONMB, FALSE);
                        }
                    }
                    if((chrlatchbutton[cnt]&LATCHBUTTONPACKLEFT)&&chractionready[cnt]&&chrreloadtime[cnt]==0)
                    {
                        chrreloadtime[cnt] = PACKDELAY;
                        item = chrholdingwhich[cnt][0];
                        if(item!=MAXCHR)
                        {
                            if((chriskursed[item] || capistoobig[chrmodel[item]]) && capisequipment[chrmodel[item]]==FALSE)
                            {
                                // The item couldn't be put away
                                chralert[item]|=ALERTIFNOTPUTAWAY;
                            }
                            else
                            {
                                // Put the item into the pack
                                add_item_to_character_pack(item, cnt);
                            }
                        }
                        else
                        {
                            // Get a new one out and put it in hand
                            get_item_from_character_pack(cnt, GRIPLEFT, FALSE);
                        }
                        // Make it take a little time
                        play_action(cnt, ACTIONMG, FALSE);
                    }
                    if((chrlatchbutton[cnt]&LATCHBUTTONPACKRIGHT)&&chractionready[cnt]&&chrreloadtime[cnt]==0)
                    {
                        chrreloadtime[cnt] = PACKDELAY;
                        item = chrholdingwhich[cnt][1];
                        if(item!=MAXCHR)
                        {
                            if((chriskursed[item] || capistoobig[chrmodel[item]]) && capisequipment[chrmodel[item]]==FALSE)
                            {
                                // The item couldn't be put away
                                chralert[item]|=ALERTIFNOTPUTAWAY;
                            }
                            else
                            {
                                // Put the item into the pack
                                add_item_to_character_pack(item, cnt);
                            }
                        }
                        else
                        {
                            // Get a new one out and put it in hand
                            get_item_from_character_pack(cnt, GRIPRIGHT, FALSE);
                        }
                        // Make it take a little time
                        play_action(cnt, ACTIONMG, FALSE);
                    }
                    if(chrlatchbutton[cnt]&LATCHBUTTONLEFT&&chrreloadtime[cnt]==0)
                    {
                        // Which weapon?
                        weapon = chrholdingwhich[cnt][0];
                        if(weapon == MAXCHR)
                        {
                            // Unarmed means character itself is the weapon
                            weapon = cnt;
                        }
                        action = capweaponaction[chrmodel[weapon]];


                        // Can it do it?
                        allowedtoattack = TRUE;
                        if(madactionvalid[chrmodel[cnt]][action]==FALSE || chrreloadtime[weapon] > 0 ||
                           (capneedskillidtouse[chrmodel[weapon]] && capidsz[chrmodel[cnt]][IDSZSKILL] != capidsz[chrmodel[weapon]][IDSZSKILL]))
                        {
                            allowedtoattack = FALSE;
                            if(chrreloadtime[weapon] == 0)
                            {
                                // This character can't use this weapon
                                chrreloadtime[weapon] = 50;
                                if(chrstaton[cnt])
                                {
                                    // Tell the player that they can't use this weapon
                                    sprintf(generictext, "%s can't use this item...", chrname[cnt]);
                                    debug_message(generictext);
                                }
                            }
                        }
                        if(action==ACTIONDA)
                        {
                            allowedtoattack = FALSE;
                            if(chrreloadtime[weapon]==0)
                            {
                                chralert[weapon]=chralert[weapon]|ALERTIFUSED;
                            }
                        }


                        if(allowedtoattack)
                        {
                            // Rearing mount
                            mount = chrattachedto[cnt];
                            if(mount!=MAXCHR)
                            {
                                allowedtoattack = capridercanattack[chrmodel[mount]];
                                if(chrismount[mount]&&chralive[mount]&&chrisplayer[mount]==FALSE&&chractionready[mount])
                                {
                                    if((action != ACTIONPA || allowedtoattack == FALSE) && chractionready[cnt])
                                    {
                                        play_action(chrattachedto[cnt], ACTIONUA+(rand()&1), FALSE);
                                        chralert[chrattachedto[cnt]]|=ALERTIFUSED;
                                    }
                                    else
                                    {
                                        allowedtoattack = FALSE;
                                    }
                                }
                            }


                            // Attack button
                            if(allowedtoattack)
                            {
                                if(chractionready[cnt] && madactionvalid[chrmodel[cnt]][action])
                                {
                                    // Check mana cost
                                    if(chrmana[cnt] >= chrmanacost[weapon] || chrcanchannel[cnt])
                                    {
                                        cost_mana(cnt, chrmanacost[weapon], weapon);
                                        // Check life healing
                                        chrlife[cnt]+=chrlifeheal[weapon];
                                        if(chrlife[cnt] > chrlifemax[cnt])  chrlife[cnt] = chrlifemax[cnt];
                                        actionready = FALSE;
                                        if(action == ACTIONPA)
                                            actionready = TRUE;
                                        action+=rand()&1;
                                        play_action(cnt, action, actionready);
                                        if(weapon!=cnt)
                                        {
                                            // Make the weapon attack too
                                            play_action(weapon, ACTIONMJ, FALSE);
                                            chralert[weapon]=chralert[weapon]|ALERTIFUSED;
                                        }
                                        else
                                        {
                                            // Flag for unarmed attack
                                            chralert[cnt]|=ALERTIFUSED;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else if(chrlatchbutton[cnt]&LATCHBUTTONRIGHT&&chrreloadtime[cnt]==0)
                    {
                        // Which weapon?
                        weapon = chrholdingwhich[cnt][1];
                        if(weapon == MAXCHR)
                        {
                            // Unarmed means character itself is the weapon
                            weapon = cnt;
                        }
                        action = capweaponaction[chrmodel[weapon]]+2;


                        // Can it do it?
                        allowedtoattack = TRUE;
                        if(madactionvalid[chrmodel[cnt]][action]==FALSE || chrreloadtime[weapon] > 0 ||
                           (capneedskillidtouse[chrmodel[weapon]] && capidsz[chrmodel[cnt]][IDSZSKILL] != capidsz[chrmodel[weapon]][IDSZSKILL]))
                        {
                            allowedtoattack = FALSE;
                            if(chrreloadtime[weapon] == 0)
                            {
                                // This character can't use this weapon
                                chrreloadtime[weapon] = 50;
                                if(chrstaton[cnt])
                                {
                                    // Tell the player that they can't use this weapon
                                    sprintf(generictext, "%s can't use this item...", chrname[cnt]);
                                    debug_message(generictext);
                                }
                            }
                        }
                        if(action==ACTIONDC)
                        {
                            allowedtoattack = FALSE;
                            if(chrreloadtime[weapon]==0)
                            {
                                chralert[weapon]=chralert[weapon]|ALERTIFUSED;
                            }
                        }


                        if(allowedtoattack)
                        {
                            // Rearing mount
                            mount = chrattachedto[cnt];
                            if(mount!=MAXCHR)
                            {
                                allowedtoattack = capridercanattack[chrmodel[mount]];
                                if(chrismount[mount]&&chralive[mount]&&chrisplayer[mount]==FALSE&&chractionready[mount])
                                {
                                    if((action != ACTIONPC || allowedtoattack == FALSE) && chractionready[cnt])
                                    {
                                        play_action(chrattachedto[cnt], ACTIONUC+(rand()&1), FALSE);
                                        chralert[chrattachedto[cnt]]|=ALERTIFUSED;
                                    }
                                    else
                                    {
                                        allowedtoattack = FALSE;
                                    }
                                }
                            }


                            // Attack button
                            if(allowedtoattack)
                            {
                                if(chractionready[cnt] && madactionvalid[chrmodel[cnt]][action])
                                {
                                    // Check mana cost
                                    if(chrmana[cnt] >= chrmanacost[weapon] || chrcanchannel[cnt])
                                    {
                                        cost_mana(cnt, chrmanacost[weapon], weapon);
                                        // Check life healing
                                        chrlife[cnt]+=chrlifeheal[weapon];
                                        if(chrlife[cnt] > chrlifemax[cnt])  chrlife[cnt] = chrlifemax[cnt];
                                        actionready = FALSE;
                                        if(action == ACTIONPC)
                                            actionready = TRUE;
                                        action+=rand()&1;
                                        play_action(cnt, action, actionready);
                                        if(weapon!=cnt)
                                        {
                                            // Make the weapon attack too
                                            play_action(weapon, ACTIONMJ, FALSE);
                                            chralert[weapon]=chralert[weapon]|ALERTIFUSED;
                                        }
                                        else
                                        {
                                            // Flag for unarmed attack
                                            chralert[cnt]|=ALERTIFUSED;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }




            // Is the character in the air?
            level = chrlevel[cnt];
            if(chrflyheight[cnt]==0)
            {
                chrzpos[cnt]+=chrzvel[cnt];
                if(chrzpos[cnt] > level || (chrzvel[cnt] > STOPBOUNCING && chrzpos[cnt] > level-STOPBOUNCING))
                {
                    // Character is in the air
                    chrjumpready[cnt]=FALSE;
                    chrzvel[cnt]+=gravity;


                    // Down jump timers for flapping characters
                    if(chrjumptime[cnt]!=0) chrjumptime[cnt]--;


                    // Airborne characters still get friction to make control easier
                    friction = airfriction;
                }
                else
                {
                    // Character is on the ground
                    chrzpos[cnt] = level;
                    grounded = TRUE;
                    if(chrhitready[cnt])
                    {
                        chralert[cnt]|=ALERTIFHITGROUND;
                        chrhitready[cnt] = FALSE;
                    }


                    // Make the characters slide
                    twist = meshtwist[chronwhichfan[cnt]];
                    friction = noslipfriction;
                    if(meshfx[chronwhichfan[cnt]]&MESHFXSLIPPY)
                    {
                        if(wateriswater && (meshfx[chronwhichfan[cnt]]&MESHFXWATER) && chrlevel[cnt] < watersurfacelevel+RAISE+1)
                        {
                            // It says it's slippy, but the water is covering it...
                            // Treat exactly as normal
                            chrjumpready[cnt]=TRUE;
                            chrjumpnumber[cnt] = chrjumpnumberreset[cnt];
                            if(chrjumptime[cnt] > 0) chrjumptime[cnt]--;
                            chrzvel[cnt] = -chrzvel[cnt]*chrdampen[cnt];
                            chrzvel[cnt]+=gravity;
                        }
                        else
                        {
                            // It's slippy all right...
                            friction = slippyfriction;
                            chrjumpready[cnt]=TRUE;
                            if(chrjumptime[cnt] > 0) chrjumptime[cnt]--;
                            if(chrweight[cnt]!=65535)
                            {
                                // Slippy hills make characters slide
                                chrxvel[cnt]+=vellrtwist[twist];
                                chryvel[cnt]+=veludtwist[twist];
                                chrzvel[cnt]=-SLIDETOLERANCE;
                            }
                            if(flattwist[twist])
                            {
                                // Reset jumping on flat areas of slippiness
                                chrjumpnumber[cnt] = chrjumpnumberreset[cnt];
                            }
                        }
                    }
                    else
                    {
                        // Reset jumping
                        chrjumpready[cnt]=TRUE;
                        chrjumpnumber[cnt] = chrjumpnumberreset[cnt];
                        if(chrjumptime[cnt] > 0) chrjumptime[cnt]--;
                        chrzvel[cnt] = -chrzvel[cnt]*chrdampen[cnt];
                        chrzvel[cnt]+=gravity;
                    }




                    // Characters with sticky butts lie on the surface of the mesh
                    if(chrstickybutt[cnt]||chralive[cnt]==FALSE)
                    {
                        maplr = chrturnmaplr[cnt];
                        maplr = (maplr<<6)-maplr+maplrtwist[twist];
                        mapud = chrturnmapud[cnt];
                        mapud = (mapud<<6)-mapud+mapudtwist[twist];
                        chrturnmaplr[cnt] = maplr>>6;
                        chrturnmapud[cnt] = mapud>>6;
                    }
                }
            }
            else
            {
                //  Flying
                chrjumpready[cnt]=FALSE;
                chrzpos[cnt]+=chrzvel[cnt];
                if(level < 0) level = 0;  // Don't fall in pits...
                chrzvel[cnt]+=(level+chrflyheight[cnt]-chrzpos[cnt])*FLYDAMPEN;
                if(chrzpos[cnt] < level)
                {
                    chrzpos[cnt] = level;
                    chrzvel[cnt] = 0;
                }
                // Airborne characters still get friction to make control easier
                friction = airfriction;
            }
            // Move the character
            chrxpos[cnt]+=chrxvel[cnt];
            if(__chrhitawall(cnt)) { chrxpos[cnt]=chroldx[cnt]; chrxvel[cnt]=-chrxvel[cnt]; }
            chrypos[cnt]+=chryvel[cnt];
            if(__chrhitawall(cnt)) { chrypos[cnt]=chroldy[cnt]; chryvel[cnt]=-chryvel[cnt]; }
            // Apply friction for next time
            chrxvel[cnt]=chrxvel[cnt]*friction;
            chryvel[cnt]=chryvel[cnt]*friction;


            // Animate the character
            chrlip[cnt] = (chrlip[cnt] + 64);
            if(chrlip[cnt] == 192)
            {
                // Check frame effects
                if(madframefx[chrframe[cnt]]&MADFXACTLEFT)
                    character_swipe(cnt, 0);
                if(madframefx[chrframe[cnt]]&MADFXACTRIGHT)
                    character_swipe(cnt, 1);
                if(madframefx[chrframe[cnt]]&MADFXGRABLEFT)
                    character_grab_stuff(cnt, GRIPLEFT, FALSE);
                if(madframefx[chrframe[cnt]]&MADFXGRABRIGHT)
                    character_grab_stuff(cnt, GRIPRIGHT, FALSE);
                if(madframefx[chrframe[cnt]]&MADFXCHARLEFT)
                    character_grab_stuff(cnt, GRIPLEFT, TRUE);
                if(madframefx[chrframe[cnt]]&MADFXCHARRIGHT)
                    character_grab_stuff(cnt, GRIPRIGHT, TRUE);
                if(madframefx[chrframe[cnt]]&MADFXDROPLEFT)
                    detach_character_from_mount(chrholdingwhich[cnt][0], FALSE, TRUE);
                if(madframefx[chrframe[cnt]]&MADFXDROPRIGHT)
                    detach_character_from_mount(chrholdingwhich[cnt][1], FALSE, TRUE);
                if(madframefx[chrframe[cnt]]&MADFXPOOF && chrisplayer[cnt]==FALSE)
                    valuegopoof = TRUE;
                if(madframefx[chrframe[cnt]]&MADFXFOOTFALL)
                {
                    if(capwavefootfall[chrmodel[cnt]] != -1)
                    {
                        distance = ABS(camtrackx-chrxpos[cnt])+ABS(camtracky-chrypos[cnt]);
                        volume = -distance;

                        volume = volume<<VOLSHIFT;
                        if(volume > VOLMIN)
                        {
                            play_sound_pvf(capwaveindex[chrmodel[cnt]][capwavefootfall[chrmodel[cnt]]], PANMID, volume, FRQRANDOM);
                        }

                    }
                }
            }
            if(chrlip[cnt] == 0)
            {
                // Change frames
                chrlastframe[cnt] = chrframe[cnt];
                chrframe[cnt]++;
                if(chrframe[cnt] == madactionend[chrmodel[cnt]][chraction[cnt]])
                {
                    // Action finished
                    if(chrkeepaction[cnt])
                    {
                        // Keep the last frame going
                        chrframe[cnt] = chrlastframe[cnt];
                    }
                    else
                    {
                        if(chrloopaction[cnt]==FALSE)
                        {
                            // Go on to the next action
                            chraction[cnt] = chrnextaction[cnt];
                            chrnextaction[cnt] = ACTIONDA;
                        }
                        else
                        {
                            // See if the character is mounted...
                            if(chrattachedto[cnt]!=MAXCHR)
                            {
                                chraction[cnt] = ACTIONMI;
                            }
                        }
                        chrframe[cnt] = madactionstart[chrmodel[cnt]][chraction[cnt]];
                    }
                    chractionready[cnt] = TRUE;
                }
            }



            // Do "Be careful!" delay
            if(chrcarefultime[cnt] != 0)
            {
                chrcarefultime[cnt]--;
            }


            // Get running, walking, sneaking, or dancing, from speed
            if((chrkeepaction[cnt]|chrloopaction[cnt])==FALSE)
            {
                framelip = madframelip[chrframe[cnt]];  // 0 - 15...  Way through animation
                if(chractionready[cnt]&&chrlip[cnt]==0&&grounded&&chrflyheight[cnt]==0&&(framelip&7)<2)
                {
                    // Do the motion stuff
                    speed = ABS((int) chrxvel[cnt])+ABS((int) chryvel[cnt]);
                    if(speed < chrsneakspd[cnt])
                    {
//                        chrnextaction[cnt] = ACTIONDA;
                        // Do boredom
                        chrboretime[cnt]--;
                        if(chrboretime[cnt] < 0)
                        {
                            chralert[cnt]|=ALERTIFBORED;
                            chrboretime[cnt] = BORETIME;
                        }
                        else
                        {
                            // Do standstill
                            if(chraction[cnt]>ACTIONDD)
                            {
                                chraction[cnt] = ACTIONDA;
                                chrframe[cnt] = madactionstart[chrmodel[cnt]][chraction[cnt]];
                            }
                        }
                    }
                    else
                    {
                        chrboretime[cnt] = BORETIME;
                        if(speed < chrwalkspd[cnt])
                        {
                            chrnextaction[cnt] = ACTIONWA;
                            if(chraction[cnt]!=ACTIONWA)
                            {
                                chrframe[cnt] = madframeliptowalkframe[chrmodel[cnt]][LIPWA][framelip];
                                chraction[cnt] = ACTIONWA;
                            }
                        }
                        else
                        {
                            if(speed < chrrunspd[cnt])
                            {
                                chrnextaction[cnt] = ACTIONWB;
                                if(chraction[cnt]!=ACTIONWB)
                                {
                                    chrframe[cnt] = madframeliptowalkframe[chrmodel[cnt]][LIPWB][framelip];
                                    chraction[cnt] = ACTIONWB;
                                }
                            }
                            else
                            {
                                chrnextaction[cnt] = ACTIONWC;
                                if(chraction[cnt]!=ACTIONWC)
                                {
                                    chrframe[cnt] = madframeliptowalkframe[chrmodel[cnt]][LIPWC][framelip];
                                    chraction[cnt] = ACTIONWC;
                                }
                            }
                        }
                    }
                }
            }
            // Do poofing
            if(valuegopoof)
            {
                if(chrattachedto[cnt] != MAXCHR)
                    detach_character_from_mount(cnt, TRUE, FALSE);
                if(chrholdingwhich[cnt][0] != MAXCHR)
                    detach_character_from_mount(chrholdingwhich[cnt][0], TRUE, FALSE);
                if(chrholdingwhich[cnt][1] != MAXCHR)
                    detach_character_from_mount(chrholdingwhich[cnt][1], TRUE, FALSE);
                free_inventory(cnt);
                free_one_character(cnt);
            }
        }
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void setup_characters(char *modname)
{
    // ZZ> This function sets up character data, loaded from "SPAWN.TXT"
    int currentcharacter=0, lastcharacter, passage, content, money, level, skin, cnt, tnc, localnumber;
    unsigned char ghost, team, stat, cTmp;
    char *name;
    char itislocal;
    char myname[256], newloadname[256];
    unsigned short facing, slot, grip, attach;
    float x, y, z;
    FILE *fileread;


    // Turn all characters off
    free_all_characters();


    // Turn some back on
    make_newloadname(modname, "gamedat/spawn.txt", newloadname);
    fileread = fopen( FILENAME( newloadname ), "r");
    currentcharacter = MAXCHR;
    if(fileread)
    {
        while(goto_colon_yesno(fileread))
        {
            fscanf(fileread, "%s", myname);
            name = myname;
            if(myname[0] == 'N' && myname[1] == 'O' && myname[2] == 'N' &&
                myname[3] == 'E' && myname[4] == 0)
            {
                // Random name
                name = NULL;
            }
            cnt = 0;
            while(cnt < 256)
            {
                if(myname[cnt]=='_')  myname[cnt] = ' ';
                cnt++;
            }
            fscanf(fileread, "%d", &slot);
            fscanf(fileread, "%f%f%f", &x, &y, &z); x = x*128;  y = y*128;  z = z*128;
            cTmp = get_first_letter(fileread);
                attach = MAXCHR;
                facing = NORTH;
                if(cTmp == 'S' || cTmp == 's')  facing = SOUTH;
                if(cTmp == 'E' || cTmp == 'e')  facing = EAST;
                if(cTmp == 'W' || cTmp == 'w')  facing = WEST;
                if(cTmp == 'L' || cTmp == 'l')  { attach = currentcharacter; grip = GRIPLEFT;   }
                if(cTmp == 'R' || cTmp == 'r')  { attach = currentcharacter; grip = GRIPRIGHT;  }
                if(cTmp == 'I' || cTmp == 'i')  { attach = currentcharacter; grip = INVENTORY;  }
            fscanf(fileread, "%d%d%d%d%d", &money, &skin, &passage, &content, &level);
            cTmp = get_first_letter(fileread);
                stat = FALSE;
                if(cTmp == 'T' || cTmp == 't') stat = TRUE;
            cTmp = get_first_letter(fileread);
                ghost = FALSE;
                if(cTmp == 'T' || cTmp == 't') ghost = TRUE;
            team = (get_first_letter(fileread)-'A')%MAXTEAM;


            // Spawn the character
            if(team < numplayer || rtscontrol==FALSE || team >= MAXPLAYER)
            {
                lastcharacter = spawn_one_character(x, y, z, slot, team, skin, facing, name, MAXCHR);
                if(lastcharacter < MAXCHR)
                {
                    if(allselect && team == rtslocalteam)
                        add_select(lastcharacter);
                    chrmoney[lastcharacter]+=money;
                    if(chrmoney[lastcharacter] > MAXMONEY)  chrmoney[lastcharacter] = MAXMONEY;
                    if(chrmoney[lastcharacter] < 0)  chrmoney[lastcharacter] = 0;
                    chraicontent[lastcharacter] = content;
                    chrpassage[lastcharacter] = passage;
                    if(attach==MAXCHR)
                    {
                        // Free character
                        currentcharacter = lastcharacter;
                    }
                    else
                    {
                        // Attached character
                        if(grip != INVENTORY)
                        {
                            // Wielded character
                            attach_character_to_mount(lastcharacter, currentcharacter, grip);
                            let_character_think(lastcharacter);  // Empty the grabbed messages
                        }
                        else
                        {
                            // Inventory character
                            add_item_to_character_pack(lastcharacter, currentcharacter);
                            chralert[lastcharacter] |= ALERTIFGRABBED;  // Make spellbooks change
                            chrattachedto[lastcharacter] = currentcharacter;  // Make grab work
                            let_character_think(lastcharacter);  // Empty the grabbed messages
                            chrattachedto[lastcharacter] = MAXCHR;  // Fix grab
                        }
                    }
                    // Turn on player input devices
                    if(stat)
                    {
                        if(importamount == 0)
                        {
                            if(playeramount < 2)
                            {
                                if(numstat == 0)
                                {
                                    // Single player module
                                    add_player(lastcharacter, numstat, INPUTMOUSE|INPUTKEY|INPUTJOYA|INPUTJOYB);
                                }
                            }
                            else
                            {
                                if(networkon == FALSE)
                                {
                                    if(playeramount == 2)
                                    {
                                        // Two player hack
                                        if(numstat == 0)
                                        {
                                            // First player
                                            add_player(lastcharacter, numstat, INPUTMOUSE|INPUTKEY|INPUTJOYB);
                                        }
                                        if(numstat == 1)
                                        {
                                            // Second player
                                            add_player(lastcharacter, numstat, INPUTJOYA);
                                        }
                                    }
                                    else
                                    {
                                        // Three player hack
                                        if(numstat == 0)
                                        {
                                            // First player
                                            add_player(lastcharacter, numstat, INPUTKEY);
                                        }
                                        if(numstat == 1)
                                        {
                                            // Second player
                                            add_player(lastcharacter, numstat, INPUTJOYA);
                                        }
                                        if(numstat == 2)
                                        {
                                            // Third player
                                            add_player(lastcharacter, numstat, INPUTJOYB|INPUTMOUSE);
                                        }
                                    }
                                }
                                else
                                {
                                    // One player per machine hack
                                    if(localmachine == numstat)
                                    {
                                        add_player(lastcharacter, numstat, INPUTMOUSE|INPUTKEY|INPUTJOYA|INPUTJOYB);
                                    }
                                }
                            }
                        }
                        if(numstat < importamount)
                        {
                            // Multiplayer import module
                            itislocal = FALSE;
                            tnc = 0;
                            while(tnc < numimport)
                            {
                                if(capimportslot[chrmodel[lastcharacter]] == localslot[tnc])
                                {
                                    itislocal = TRUE;
                                    localnumber = tnc;
                                    tnc = numimport;
                                }
                                tnc++;
                            }


                            if(itislocal)
                            {
                                // It's a local player
                                add_player(lastcharacter, numstat, localcontrol[localnumber]);
                            }
                            else
                            {
                                // It's a remote player
                                add_player(lastcharacter, numstat, INPUTNONE);
                            }
                        }
                        // Turn on the stat display
                        add_stat(lastcharacter);
                    }

                    // Set the starting level
                    if(chrisplayer[lastcharacter]==FALSE)
                    {
                        // Let the character gain levels
                        level = level - 1;
                        while(chrexperiencelevel[lastcharacter] < level && chrexperience[lastcharacter] < MAXXP)
                        {
                            give_experience(lastcharacter, MAXXP, XPDIRECT);
                        }
                    }
                    if(ghost)
                    {
                        // Make the character a ghost !!!BAD!!!  Can do with enchants
                        chralpha[lastcharacter] = 128;
                        chrlight[lastcharacter] = 255;
                    }
                }
            }
        }
        fclose(fileread);
    }
    else
    {
        general_error(0, 0, newloadname);
    }
    clear_messages();


    // Make sure local players are displayed first
    sort_stat();


    // Fix tilting trees problem
    tilt_characters_to_terrain();

    // Assume RTS mode always has players...  So it doesn't quit
    if(rtscontrol)  nolocalplayers = FALSE;
}

//--------------------------------------------------------------------------------------------
void set_one_player_latch(unsigned short player)
{
    // ZZ> This function converts input readings to latch settings, so players can
    //     move around
    float newx, newy;
    unsigned short turnsin, turncos, character;
    unsigned char device;
    float dist, scale;
    float inputx, inputy;


    // Check to see if we need to bother
    if(plavalid[player] && pladevice[player] != INPUTNONE)
    {
        // Make life easier
        character = plaindex[player];
        device = pladevice[player];

        // Clear the player's latch buffers
        plalatchbutton[player] = 0;
        plalatchx[player] = 0;
        plalatchy[player] = 0;

        // Mouse routines
        if((device & INPUTMOUSE) && mouseon)
        {
            // Movement
            newx = 0;
            newy = 0;
            if((autoturncamera==255 && numlocalpla == 1) ||
               !control_mouse_is_pressed(MOS_CAMERA))  // Don't allow movement in camera control mode
            {
                dist = sqrt(mousex*mousex+mousey*mousey);
                if(dist > 0)
                {
                    scale = mousesense/dist;
                    if(dist < mousesense)
                    {
                        scale = dist/mousesense;
                    }
                    scale = scale/mousesense;
                    if(chrattachedto[character]!=MAXCHR)
                    {
                        // Mounted
                        inputx = mousex*chrmaxaccel[chrattachedto[character]]*scale;
                        inputy = mousey*chrmaxaccel[chrattachedto[character]]*scale;
                    }
                    else
                    {
                        // Unmounted
                        inputx = mousex*chrmaxaccel[character]*scale;
                        inputy = mousey*chrmaxaccel[character]*scale;
                    }
                    turnsin = (camturnleftrightone*16383);
                    turnsin = turnsin&16383;
                    turncos = (turnsin+4096)&16383;
                    if(autoturncamera==255 &&
                       numlocalpla == 1 &&
                       control_mouse_is_pressed(MOS_CAMERA)==0)  inputx = 0;
                    newx = (inputx*turntosin[turncos]+inputy*turntosin[turnsin]);
                    newy = (-inputx*turntosin[turnsin]+inputy*turntosin[turncos]);
//                    plalatchx[player]+=newx;
//                    plalatchy[player]+=newy;
                }
            }
            plalatchx[player]+=newx*mousecover+mouselatcholdx*mousesustain;
            plalatchy[player]+=newy*mousecover+mouselatcholdy*mousesustain;
            mouselatcholdx = plalatchx[player];
            mouselatcholdy = plalatchy[player];
            // Sustain old movements to ease mouse play
//            plalatchx[player]+=mouselatcholdx*mousesustain;
//            plalatchy[player]+=mouselatcholdy*mousesustain;
//            mouselatcholdx = plalatchx[player];
//            mouselatcholdy = plalatchy[player];
            // Read buttons
            if(control_mouse_is_pressed(MOS_JUMP))
                plalatchbutton[player] |= LATCHBUTTONJUMP;
            if(control_mouse_is_pressed(MOS_LEFT_USE))
                plalatchbutton[player] |= LATCHBUTTONLEFT;
            if(control_mouse_is_pressed(MOS_LEFT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTLEFT;
            if(control_mouse_is_pressed(MOS_LEFT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKLEFT;
            if(control_mouse_is_pressed(MOS_RIGHT_USE))
                plalatchbutton[player] |= LATCHBUTTONRIGHT;
            if(control_mouse_is_pressed(MOS_RIGHT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTRIGHT;
            if(control_mouse_is_pressed(MOS_RIGHT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKRIGHT;
        }


        // Joystick A routines
        if((device & INPUTJOYA) && joyaon)
        {
            // Movement
            if((autoturncamera==255 && numlocalpla == 1) ||
               !control_joya_is_pressed(JOA_CAMERA))
            {
                newx = 0;
                newy = 0;
                inputx = 0;
                inputy = 0;
                dist = sqrt(joyax*joyax+joyay*joyay);
                if(dist > 0)
                {
                    scale = 1.0/dist;
                    if(chrattachedto[character]!=MAXCHR)
                    {
                        // Mounted
                        inputx = joyax*chrmaxaccel[chrattachedto[character]]*scale;
                        inputy = joyay*chrmaxaccel[chrattachedto[character]]*scale;
                    }
                    else
                    {
                        // Unmounted
                        inputx = joyax*chrmaxaccel[character]*scale;
                        inputy = joyay*chrmaxaccel[character]*scale;
                    }
                }
                turnsin = (camturnleftrightone*16383);
                turnsin = turnsin&16383;
                turncos = (turnsin+4096)&16383;
                if(autoturncamera==255 &&
                   numlocalpla == 1 &&
                   !control_joya_is_pressed(JOA_CAMERA))  inputx = 0;
                newx = (inputx*turntosin[turncos]+inputy*turntosin[turnsin]);
                newy = (-inputx*turntosin[turnsin]+inputy*turntosin[turncos]);
                plalatchx[player]+=newx;
                plalatchy[player]+=newy;
            }
            // Read buttons
            if(control_joya_is_pressed(JOA_JUMP))
                plalatchbutton[player] |= LATCHBUTTONJUMP;
            if(control_joya_is_pressed(JOA_LEFT_USE))
                plalatchbutton[player] |= LATCHBUTTONLEFT;
            if(control_joya_is_pressed(JOA_LEFT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTLEFT;
            if(control_joya_is_pressed(JOA_LEFT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKLEFT;
            if(control_joya_is_pressed(JOA_RIGHT_USE))
                plalatchbutton[player] |= LATCHBUTTONRIGHT;
            if(control_joya_is_pressed(JOA_RIGHT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTRIGHT;
            if(control_joya_is_pressed(JOA_RIGHT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKRIGHT;
        }


        // Joystick B routines
        if((device & INPUTJOYB) && joybon)
        {
            // Movement
            if((autoturncamera==255 && numlocalpla == 1) ||
               !control_joyb_is_pressed(JOB_CAMERA))
            {
                newx = 0;
                newy = 0;
                inputx = 0;
                inputy = 0;
                dist = sqrt(joybx*joybx+joyby*joyby);
                if(dist > 0)
                {
                    scale = 1.0/dist;
                    if(chrattachedto[character]!=MAXCHR)
                    {
                        // Mounted
                        inputx = joybx*chrmaxaccel[chrattachedto[character]]*scale;
                        inputy = joyby*chrmaxaccel[chrattachedto[character]]*scale;
                    }
                    else
                    {
                        // Unmounted
                        inputx = joybx*chrmaxaccel[character]*scale;
                        inputy = joyby*chrmaxaccel[character]*scale;
                    }
                }
                turnsin = (camturnleftrightone*16383);
                turnsin = turnsin&16383;
                turncos = (turnsin+4096)&16383;
                if(autoturncamera==255 &&
                   numlocalpla == 1 &&
                   !control_joyb_is_pressed(JOB_CAMERA))  inputx = 0;
                newx = (inputx*turntosin[turncos]+inputy*turntosin[turnsin]);
                newy = (-inputx*turntosin[turnsin]+inputy*turntosin[turncos]);
                plalatchx[player]+=newx;
                plalatchy[player]+=newy;
            }
            // Read buttons
            if(control_joyb_is_pressed(JOB_JUMP))
                plalatchbutton[player] |= LATCHBUTTONJUMP;
            if(control_joyb_is_pressed(JOB_LEFT_USE))
                plalatchbutton[player] |= LATCHBUTTONLEFT;
            if(control_joyb_is_pressed(JOB_LEFT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTLEFT;
            if(control_joyb_is_pressed(JOB_LEFT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKLEFT;
            if(control_joyb_is_pressed(JOB_RIGHT_USE))
                plalatchbutton[player] |= LATCHBUTTONRIGHT;
            if(control_joyb_is_pressed(JOB_RIGHT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTRIGHT;
            if(control_joyb_is_pressed(JOB_RIGHT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKRIGHT;
        }

        // Keyboard routines
        if((device & INPUTKEY) && keyon)
        {
            // Movement
            if(chrattachedto[character]!=MAXCHR)
            {
                // Mounted
                inputx = (control_key_is_pressed(KEY_RIGHT)-control_key_is_pressed(KEY_LEFT))*chrmaxaccel[chrattachedto[character]];
                inputy = (control_key_is_pressed(KEY_DOWN)-control_key_is_pressed(KEY_UP))*chrmaxaccel[chrattachedto[character]];
            }
            else
            {
                // Unmounted
                inputx = (control_key_is_pressed(KEY_RIGHT)-control_key_is_pressed(KEY_LEFT))*chrmaxaccel[character];
                inputy = (control_key_is_pressed(KEY_DOWN)-control_key_is_pressed(KEY_UP))*chrmaxaccel[character];
            }
            turnsin = (camturnleftrightone*16383);
            turnsin = turnsin&16383;
            turncos = (turnsin+4096)&16383;
            if(autoturncamera==255 && numlocalpla == 1)  inputx = 0;
            newx = (inputx*turntosin[turncos]+inputy*turntosin[turnsin]);
            newy = (-inputx*turntosin[turnsin]+inputy*turntosin[turncos]);
            plalatchx[player]+=newx;
            plalatchy[player]+=newy;
            // Read buttons
            if(control_key_is_pressed(KEY_JUMP))
                plalatchbutton[player] |= LATCHBUTTONJUMP;
            if(control_key_is_pressed(KEY_LEFT_USE))
                plalatchbutton[player] |= LATCHBUTTONLEFT;
            if(control_key_is_pressed(KEY_LEFT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTLEFT;
            if(control_key_is_pressed(KEY_LEFT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKLEFT;
            if(control_key_is_pressed(KEY_RIGHT_USE))
                plalatchbutton[player] |= LATCHBUTTONRIGHT;
            if(control_key_is_pressed(KEY_RIGHT_GET))
                plalatchbutton[player] |= LATCHBUTTONALTRIGHT;
            if(control_key_is_pressed(KEY_RIGHT_PACK))
                plalatchbutton[player] |= LATCHBUTTONPACKRIGHT;
        }
    }
}

//--------------------------------------------------------------------------------------------
void set_local_latches(void)
{
    // ZZ> This function emulates AI thinkin' by setting latches from input devices
    int cnt;

    cnt = 0;
    while(cnt < MAXPLAYER)
    {
        set_one_player_latch(cnt);
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void make_onwhichfan(void)
{
    // ZZ> This function figures out which fan characters are on and sets their level
    unsigned short character;
    int x, y, ripand, distance;
	//int volume;
    float level;




    // First figure out which fan each character is in
    character = 0;
    while(character < MAXCHR)
    {
        if(chron[character] && (!chrinpack[character]))
        {
            x = chrxpos[character];
            y = chrypos[character];
            x = x>>7;
            y = y>>7;
            chronwhichfan[character] = meshfanstart[y]+x;
        }
        character++;
    }

    // Get levels every update
    character = 0;
    while(character < MAXCHR)
    {
        if(chron[character] && (!chrinpack[character]))
        {
            level = get_level(chrxpos[character], chrypos[character], chronwhichfan[character], chrwaterwalk[character])+RAISE;
            if(chralive[character])
            {
                if(meshfx[chronwhichfan[character]]&MESHFXDAMAGE && chrzpos[character]<=chrlevel[character]+DAMAGERAISE && chrattachedto[character]==MAXCHR)
                {
                    if((chrdamagemodifier[character][damagetiletype]&DAMAGESHIFT) != 3 && !chrinvictus[character]) // 3 means they're pretty well immune
                    {
                        distance = ABS(camtrackx-chrxpos[character])+ABS(camtracky-chrypos[character]);
                        if(distance < damagetilemindistance)
                        {
                           damagetilemindistance = distance;
                        }
                        if(distance < damagetilemindistance + 256)
                        {
                           damagetilesoundtime = 0;
                        }
                        if(chrdamagetime[character] == 0)
                        {
                            damage_character(character, 32768, damagetileamount, 1, damagetiletype, DAMAGETEAM, chrbumplast[character], DAMFXBLOC|DAMFXARMO);
                            chrdamagetime[character] = DAMAGETILETIME;
                        }
                        if(damagetileparttype != -1 && (wldframe&damagetilepartand)==0)
                        {
                            spawn_one_particle(chrxpos[character], chrypos[character], chrzpos[character],
                                               0, MAXMODEL, damagetileparttype, MAXCHR, SPAWNLAST, NULLTEAM, MAXCHR, 0, MAXCHR);
                        }
                    }
                    if(chrreaffirmdamagetype[character]==damagetiletype)
                    {
                        if((wldframe&TILEREAFFIRMAND)==0)
                            reaffirm_attached_particles(character);
                    }
                }
            }
            if(chrzpos[character] < watersurfacelevel && (meshfx[chronwhichfan[character]]&MESHFXWATER))
            {
                if(chrinwater[character]==FALSE)
                {
                    // Splash
                    if(chrattachedto[character]==MAXCHR)
                    {
                        spawn_one_particle(chrxpos[character], chrypos[character], watersurfacelevel+RAISE,
                                           0, MAXMODEL, SPLASH, MAXCHR, SPAWNLAST, NULLTEAM, MAXCHR, 0, MAXCHR);
                    }
                    chrinwater[character] = TRUE;
                    if(wateriswater)
                    {
                        chralert[character]|=ALERTIFINWATER;
                    }
                }
                else
                {
                    if(chrzpos[character]>watersurfacelevel-RIPPLETOLERANCE && capripple[chrmodel[character]])
                    {
                        // Ripples
                        ripand = ((int)chrxvel[character]!=0)|((int)chryvel[character]!=0);
                        ripand = RIPPLEAND>>ripand;
                        if((wldframe&ripand)==0&&chrzpos[character]<watersurfacelevel&&chralive[character])
                        {
                            spawn_one_particle(chrxpos[character], chrypos[character], watersurfacelevel,
                                               0, MAXMODEL, RIPPLE, MAXCHR, SPAWNLAST, NULLTEAM, MAXCHR, 0, MAXCHR);
                        }
                    }
                    if(wateriswater && (wldframe&7) == 0)
                    {
                        chrjumpready[character] = TRUE;
                        chrjumpnumber[character]=1; //chrjumpnumberreset[character];
                    }
                }
                chrxvel[character]=chrxvel[character]*waterfriction;
                chryvel[character]=chryvel[character]*waterfriction;
                chrzvel[character]=chrzvel[character]*waterfriction;
            }
            else
            {
                chrinwater[character] = FALSE;
            }
            chrlevel[character] = level;
        }
        character++;
    }


    // Play the damage tile sound
    if(damagetilesound >= 0)
    {
        if((wldframe & 3)==0)
        {
            // Change the volume...
/*PORT
            volume = -(damagetilemindistance + (damagetilesoundtime<<8));
            volume = volume<<VOLSHIFT;
            if(volume > VOLMIN)
            {
                lpDSBuffer[damagetilesound]->SetVolume(volume);
            }
            if(damagetilesoundtime < TILESOUNDTIME)  damagetilesoundtime++;
            else damagetilemindistance = 9999;
*/
        }
    }
}

//--------------------------------------------------------------------------------------------
void bump_characters(void)
{
    // ZZ> This function sets handles characters hitting other characters or particles
    unsigned short character, particle, entry, pip, direction;
    unsigned int chara, charb, fanblock, prtidparent, prtidtype, chridvulnerability, eveidremove;
    signed char hide;
    int cnt, tnc, dist, chrinblock, prtinblock, enchant, temp;
    float xa, ya, za, xb, yb, zb;
    float ax, ay, nx, ny, scale;  // For deflection
    unsigned short facing;


    // Clear the lists
    fanblock = 0;
    while(fanblock < numfanblock)
    {
        meshbumplistchrnum[fanblock] = 0;
        meshbumplistprtnum[fanblock] = 0;
        fanblock++;
    }



    // Fill 'em back up
    character = 0;
    while(character < MAXCHR)
    {
        if(chron[character]&&(!chrinpack[character])&&(chrattachedto[character]==MAXCHR||chrreaffirmdamagetype[character]!=DAMAGENULL))
        {
            hide = caphidestate[chrmodel[character]];
            if(hide == NOHIDE || hide != chraistate[character])
            {
                chrholdingweight[character] = 0;
                fanblock = (((int)chrxpos[character])>>9)+meshblockstart[((int)chrypos[character])>>9];
                // Insert before any other characters on the block
                entry = meshbumplistchr[fanblock];
                chrbumpnext[character] = entry;
                meshbumplistchr[fanblock] = character;
                meshbumplistchrnum[fanblock]++;
            }
        }
        character++;
    }
    particle = 0;
    while(particle < MAXPRT)
    {
        if(prton[particle]&&prtbumpsize[particle])
        {
            fanblock = (((int)prtxpos[particle])>>9)+meshblockstart[((int)prtypos[particle])>>9];
            // Insert before any other particles on the block
            entry = meshbumplistprt[fanblock];
            prtbumpnext[particle] = entry;
            meshbumplistprt[fanblock] = particle;
            meshbumplistprtnum[fanblock]++;
        }
        particle++;
    }



    // Check collisions with other characters and bump particles
    // Only check each pair once
    fanblock = 0;
    while(fanblock < numfanblock)
    {
        chara = meshbumplistchr[fanblock];
        chrinblock = meshbumplistchrnum[fanblock];
        prtinblock = meshbumplistprtnum[fanblock];
        cnt = 0;
        while(cnt < chrinblock)
        {
            xa = chrxpos[chara];
            ya = chrypos[chara];
            za = chrzpos[chara];
            chridvulnerability = capidsz[chrmodel[chara]][IDSZVULNERABILITY];
            // Don't let items bump
            if(chrbumpheight[chara])//chrisitem[chara]==FALSE)
            {
                charb = chrbumpnext[chara];  // Don't collide with self
                tnc = cnt+1;
                while(tnc < chrinblock)
                {
                    if(chrbumpheight[charb])//chrisitem[charb]==FALSE)
                    {
                        xb = chrxpos[charb];
                        yb = chrypos[charb];
                        zb = chrzpos[charb];
                        // First check absolute value diamond
                        xb = ABS(xa-xb);
                        yb = ABS(ya-yb);
                        dist = xb+yb;
                        if(dist < chrbumpsizebig[chara] || dist < chrbumpsizebig[charb])
                        {
                            // Then check bounding box square...  Square+Diamond=Octagon
                            if((xb<chrbumpsize[chara] || xb<chrbumpsize[charb])&&
                            (yb<chrbumpsize[chara] || yb<chrbumpsize[charb]))
                            {
                               // Pretend that they collided
                                chrbumplast[chara]=charb;
                                chrbumplast[charb]=chara;
                                // Now see if either is on top the other like a platform
                                if(za>zb+chrbumpheight[charb]-PLATTOLERANCE+chrzvel[chara]-chrzvel[charb]&&(capcanuseplatforms[chrmodel[chara]]||za>zb+chrbumpheight[charb]))
                                {
                                    // Is A falling on B?
                                    if(za<zb+chrbumpheight[charb]&&chrplatform[charb])//&&chrflyheight[chara]==0)
                                    {
                                        // A is inside, coming from above
                                        chrzpos[chara]=(chrzpos[chara])*PLATKEEP+(chrzpos[charb]+chrbumpheight[charb]+PLATADD)*PLATASCEND;
                                        chrxvel[chara]+=(chrxvel[charb])*platstick;
                                        chryvel[chara]+=(chryvel[charb])*platstick;
                                        if(chrzvel[chara]<chrzvel[charb])
                                            chrzvel[chara]=chrzvel[charb];
                                        chrturnleftright[chara]+=(chrturnleftright[charb]-chroldturn[charb]);
                                        chrjumpready[chara]=TRUE;
                                        chrjumpnumber[chara]=chrjumpnumberreset[chara];
                                        chrholdingweight[charb]=chrweight[chara];
                                    }
                                }
                                else
                                {
                                    if(zb>za+chrbumpheight[chara]-PLATTOLERANCE+chrzvel[charb]-chrzvel[chara]&&(capcanuseplatforms[chrmodel[charb]]||zb>za+chrbumpheight[chara]))
                                    {
                                        // Is B falling on A?
                                        if(zb<za+chrbumpheight[chara]&&chrplatform[chara])//&&chrflyheight[charb]==0)
                                        {
                                            // B is inside, coming from above
                                            chrzpos[charb]=(chrzpos[charb])*PLATKEEP+(chrzpos[chara]+chrbumpheight[chara]+PLATADD)*PLATASCEND;
                                            chrxvel[charb]+=(chrxvel[chara])*platstick;
                                            chryvel[charb]+=(chryvel[chara])*platstick;
                                            if(chrzvel[charb]<chrzvel[chara])
                                                chrzvel[charb]=chrzvel[chara];
                                            chrturnleftright[charb]+=(chrturnleftright[chara]-chroldturn[chara]);
                                            chrjumpready[charb]=TRUE;
                                            chrjumpnumber[charb]=chrjumpnumberreset[charb];
                                            chrholdingweight[chara]=chrweight[charb];
                                        }
                                    }
                                    else
                                    {
                                        // They are inside each other, which ain't good
                                        // Only collide if moving toward the other
                                        if(chrxvel[chara]>0)
                                        {
                                            if(chrxpos[chara]<chrxpos[charb]) { chrxvel[charb]+=chrxvel[chara]*chrbumpdampen[charb];  chrxvel[chara]=-chrxvel[chara]*chrbumpdampen[chara];  chrxpos[chara]=chroldx[chara]; }
                                        }
                                        else
                                        {
                                            if(chrxpos[chara]>chrxpos[charb]) { chrxvel[charb]+=chrxvel[chara]*chrbumpdampen[charb];  chrxvel[chara]=-chrxvel[chara]*chrbumpdampen[chara];  chrxpos[chara]=chroldx[chara]; }
                                        }
                                        if(chryvel[chara]>0)
                                        {
                                            if(chrypos[chara]<chrypos[charb]) { chryvel[charb]+=chryvel[chara]*chrbumpdampen[charb];  chryvel[chara]=-chryvel[chara]*chrbumpdampen[chara];  chrypos[chara]=chroldy[chara]; }
                                        }
                                        else
                                        {
                                            if(chrypos[chara]>chrypos[charb]) { chryvel[charb]+=chryvel[chara]*chrbumpdampen[charb];  chryvel[chara]=-chryvel[chara]*chrbumpdampen[chara];  chrypos[chara]=chroldy[chara]; }
                                        }
                                        if(chrxvel[charb]>0)
                                        {
                                            if(chrxpos[charb]<chrxpos[chara]) { chrxvel[chara]+=chrxvel[charb]*chrbumpdampen[chara];  chrxvel[charb]=-chrxvel[charb]*chrbumpdampen[charb];  chrxpos[charb]=chroldx[charb]; }
                                        }
                                        else
                                        {
                                            if(chrxpos[charb]>chrxpos[chara]) { chrxvel[chara]+=chrxvel[charb]*chrbumpdampen[chara];  chrxvel[charb]=-chrxvel[charb]*chrbumpdampen[charb];  chrxpos[charb]=chroldx[charb]; }
                                        }
                                        if(chryvel[charb]>0)
                                        {
                                            if(chrypos[charb]<chrypos[chara]) { chryvel[chara]+=chryvel[charb]*chrbumpdampen[chara];  chryvel[charb]=-chryvel[charb]*chrbumpdampen[charb];  chrypos[charb]=chroldy[charb]; }
                                        }
                                        else
                                        {
                                            if(chrypos[charb]>chrypos[chara]) { chryvel[chara]+=chryvel[charb]*chrbumpdampen[chara];  chryvel[charb]=-chryvel[charb]*chrbumpdampen[charb];  chrypos[charb]=chroldy[charb]; }
                                        }
                                        xa=chrxpos[chara];
                                        ya=chrypos[chara];
                                        chralert[chara]=chralert[chara]|ALERTIFBUMPED;
                                        chralert[charb]=chralert[charb]|ALERTIFBUMPED;
                                    }
                                }
                            }
                        }
                    }
                    charb = chrbumpnext[charb];
                    tnc++;
                }
                // Now double check the last character we bumped into, in case it's a platform
                charb = chrbumplast[chara];
                if(chron[charb]&&(!chrinpack[charb])&&charb!=chara&&chrattachedto[charb]==MAXCHR&&chrbumpheight[chara]&&chrbumpheight[charb])
                {
                    xb = chrxpos[charb];
                    yb = chrypos[charb];
                    zb = chrzpos[charb];
                    // First check absolute value diamond
                    xb = ABS(xa-xb);
                    yb = ABS(ya-yb);
                    dist = xb+yb;
                    if(dist < chrbumpsizebig[chara] || dist < chrbumpsizebig[charb])
                    {
                        // Then check bounding box square...  Square+Diamond=Octagon
                        if((xb<chrbumpsize[chara] || xb<chrbumpsize[charb])&&
                           (yb<chrbumpsize[chara] || yb<chrbumpsize[charb]))
                        {
                            // Now see if either is on top the other like a platform
                            if(za>zb+chrbumpheight[charb]-PLATTOLERANCE+chrzvel[chara]-chrzvel[charb]&&(capcanuseplatforms[chrmodel[chara]]||za>zb+chrbumpheight[charb]))
                            {
                                // Is A falling on B?
                                if(za<zb+chrbumpheight[charb]&&chrplatform[charb]&&chralive[chara])//&&chrflyheight[chara]==0)
                                {
                                    // A is inside, coming from above
                                    chrzpos[chara]=(chrzpos[chara])*PLATKEEP+(chrzpos[charb]+chrbumpheight[charb]+PLATADD)*PLATASCEND;
                                    chrxvel[chara]+=(chrxvel[charb])*platstick;
                                    chryvel[chara]+=(chryvel[charb])*platstick;
                                    if(chrzvel[chara]<chrzvel[charb])
                                        chrzvel[chara]=chrzvel[charb];
                                    chrturnleftright[chara]+=(chrturnleftright[charb]-chroldturn[charb]);
                                    chrjumpready[chara]=TRUE;
                                    chrjumpnumber[chara]=chrjumpnumberreset[chara];
                                    if(madactionvalid[chrmodel[chara]][ACTIONMI]&&chralive[chara]&&chralive[charb]&&chrismount[charb]&&chrisitem[chara]==FALSE&&chrholdingwhich[charb][0]==MAXCHR&&chrattachedto[chara]==MAXCHR&&chrjumptime[chara]==0&&chrflyheight[chara]==0)
                                    {
                                        attach_character_to_mount(chara, charb, GRIPONLY);
                                        chrbumplast[chara]=chara;
                                        chrbumplast[charb]=charb;
                                    }
                                    chrholdingweight[charb]=chrweight[chara];
                                }
                            }
                            else
                            {
                                if(zb>za+chrbumpheight[chara]-PLATTOLERANCE+chrzvel[charb]-chrzvel[chara]&&(capcanuseplatforms[chrmodel[charb]]||zb>za+chrbumpheight[chara]))
                                {
                                    // Is B falling on A?
                                    if(zb<za+chrbumpheight[chara]&&chrplatform[chara]&&chralive[charb])//&&chrflyheight[charb]==0)
                                    {
                                        // B is inside, coming from above
                                        chrzpos[charb]=(chrzpos[charb])*PLATKEEP+(chrzpos[chara]+chrbumpheight[chara]+PLATADD)*PLATASCEND;
                                        chrxvel[charb]+=(chrxvel[chara])*platstick;
                                        chryvel[charb]+=(chryvel[chara])*platstick;
                                        if(chrzvel[charb]<chrzvel[chara])
                                            chrzvel[charb]=chrzvel[chara];
                                        chrturnleftright[charb]+=(chrturnleftright[chara]-chroldturn[chara]);
                                        chrjumpready[charb]=TRUE;
                                        chrjumpnumber[charb]=chrjumpnumberreset[charb];
                                        if(madactionvalid[chrmodel[charb]][ACTIONMI]&&chralive[chara]&&chralive[charb]&&chrismount[chara]&&chrisitem[charb]==FALSE&&chrholdingwhich[chara][0]==MAXCHR&&chrattachedto[charb]==MAXCHR&&chrjumptime[charb]==0&&chrflyheight[charb]==0)
                                        {
                                            attach_character_to_mount(charb, chara, GRIPONLY);
                                            chrbumplast[chara]=chara;
                                            chrbumplast[charb]=charb;
                                        }
                                        chrholdingweight[chara]=chrweight[charb];
                                    }
                                }
                                else
                                {
                                    // They are inside each other, which ain't good
                                    // Only collide if moving toward the other
                                    if(chrxvel[chara]>0)
                                    {
                                        if(chrxpos[chara]<chrxpos[charb]) { chrxvel[charb]+=chrxvel[chara]*chrbumpdampen[charb];  chrxvel[chara]=-chrxvel[chara]*chrbumpdampen[chara];  chrxpos[chara]=chroldx[chara]; }
                                    }
                                    else
                                    {
                                        if(chrxpos[chara]>chrxpos[charb]) { chrxvel[charb]+=chrxvel[chara]*chrbumpdampen[charb];  chrxvel[chara]=-chrxvel[chara]*chrbumpdampen[chara];  chrxpos[chara]=chroldx[chara]; }
                                    }
                                    if(chryvel[chara]>0)
                                    {
                                        if(chrypos[chara]<chrypos[charb]) { chryvel[charb]+=chryvel[chara]*chrbumpdampen[charb];  chryvel[chara]=-chryvel[chara]*chrbumpdampen[chara];  chrypos[chara]=chroldy[chara]; }
                                    }
                                    else
                                    {
                                        if(chrypos[chara]>chrypos[charb]) { chryvel[charb]+=chryvel[chara]*chrbumpdampen[charb];  chryvel[chara]=-chryvel[chara]*chrbumpdampen[chara];  chrypos[chara]=chroldy[chara]; }
                                    }
                                    if(chrxvel[charb]>0)
                                    {
                                        if(chrxpos[charb]<chrxpos[chara]) { chrxvel[chara]+=chrxvel[charb]*chrbumpdampen[chara];  chrxvel[charb]=-chrxvel[charb]*chrbumpdampen[charb];  chrxpos[charb]=chroldx[charb]; }
                                    }
                                    else
                                    {
                                        if(chrxpos[charb]>chrxpos[chara]) { chrxvel[chara]+=chrxvel[charb]*chrbumpdampen[chara];  chrxvel[charb]=-chrxvel[charb]*chrbumpdampen[charb];  chrxpos[charb]=chroldx[charb]; }
                                    }
                                    if(chryvel[charb]>0)
                                    {
                                        if(chrypos[charb]<chrypos[chara]) { chryvel[chara]+=chryvel[charb]*chrbumpdampen[chara];  chryvel[charb]=-chryvel[charb]*chrbumpdampen[charb];  chrypos[charb]=chroldy[charb]; }
                                    }
                                    else
                                    {
                                        if(chrypos[charb]>chrypos[chara]) { chryvel[chara]+=chryvel[charb]*chrbumpdampen[chara];  chryvel[charb]=-chryvel[charb]*chrbumpdampen[charb];  chrypos[charb]=chroldy[charb]; }
                                    }
                                    xa=chrxpos[chara];
                                    ya=chrypos[chara];
                                    chralert[chara]=chralert[chara]|ALERTIFBUMPED;
                                    chralert[charb]=chralert[charb]|ALERTIFBUMPED;
                                }
                            }
                        }
                    }
                }
            }
            // Now check collisions with every bump particle in same area
            if(chralive[chara])
            {
                particle = meshbumplistprt[fanblock];
                tnc = 0;
                while(tnc < prtinblock)
                {
                    xb = prtxpos[particle];
                    yb = prtypos[particle];
                    zb = prtzpos[particle];
                    // First check absolute value diamond
                    xb = ABS(xa-xb);
                    yb = ABS(ya-yb);
                    dist = xb+yb;
                    if(dist < chrbumpsizebig[chara] || dist < prtbumpsizebig[particle])
                    {
                        // Then check bounding box square...  Square+Diamond=Octagon
                        if((xb<chrbumpsize[chara]  ||xb<prtbumpsize[particle])&&
                           (yb<chrbumpsize[chara]  ||yb<prtbumpsize[particle])&&
                           (zb>za-prtbumpheight[particle]&&zb<za+chrbumpheight[chara]+prtbumpheight[particle]))
                        {
                            pip = prtpip[particle];
                            if(zb>za+chrbumpheight[chara]+prtzvel[particle]&&prtzvel[particle]<0&&chrplatform[chara]&&prtattachedtocharacter[particle]==MAXCHR)
                            {
                                // Particle is falling on A
                                prtzpos[particle]=za+chrbumpheight[chara];
                                prtzvel[particle]=-prtzvel[particle]*pipdampen[pip];
                                prtxvel[particle]+=(chrxvel[chara])*platstick;
                                prtyvel[particle]+=(chryvel[chara])*platstick;
                            }
                            // Check reaffirmation of particles
                            if(prtattachedtocharacter[particle]!=chara)
                            {
                                if(chrreloadtime[chara]==0)
                                {
                                    if(chrreaffirmdamagetype[chara]==prtdamagetype[particle]&&chrdamagetime[chara]==0)
                                    {
                                        reaffirm_attached_particles(chara);
                                    }
                                }
                            }
                            // Check for missile treatment
                            if((chrdamagemodifier[chara][prtdamagetype[particle]]&3) < 2 ||
                               chrmissiletreatment[chara] == MISNORMAL ||
                               prtattachedtocharacter[particle] != MAXCHR ||
                               (prtchr[particle] == chara && pipfriendlyfire[pip]==FALSE) ||
                               (chrmana[chrmissilehandler[chara]] < (chrmissilecost[chara]<<4) && chrcanchannel[chrmissilehandler[chara]] == FALSE))
                            {
                                if((teamhatesteam[prtteam[particle]][chrteam[chara]]||(pipfriendlyfire[pip]&&((chara!=prtchr[particle]&&chara!=chrattachedto[prtchr[particle]])||piponlydamagefriendly[pip])))&&chrinvictus[chara]==FALSE)
                                {
                                    spawn_bump_particles(chara, particle); // Catch on fire
                                    if((prtdamagebase[particle]|prtdamagerand[particle])>1)
                                    {
                                        prtidparent = capidsz[prtmodel[particle]][IDSZPARENT];
                                        prtidtype = capidsz[prtmodel[particle]][IDSZTYPE];
                                        if(chrdamagetime[chara]==0 && prtattachedtocharacter[particle]!=chara && (pipdamfx[pip]&DAMFXARRO) == 0)
                                        {
                                            // Normal particle damage
                                            if(pipallowpush[pip])
                                            {
                                                chrxvel[chara]=prtxvel[particle]*chrbumpdampen[chara];
                                                chryvel[chara]=prtyvel[particle]*chrbumpdampen[chara];
                                                chrzvel[chara]=prtzvel[particle]*chrbumpdampen[chara];
                                            }
                                            direction = (atan2(prtyvel[particle], prtxvel[particle])+PI)*65535/(2*PI);
                                            direction = chrturnleftright[chara]-direction+32768;
                                            // Check all enchants to see if they are removed
                                            enchant = chrfirstenchant[chara];
                                            while(enchant != MAXENCHANT)
                                            {
                                                eveidremove = everemovedbyidsz[enceve[enchant]];
                                                temp = encnextenchant[enchant];
                                                if(eveidremove != IDSZNONE && (eveidremove == prtidtype || eveidremove == prtidparent))
                                                {
                                                    remove_enchant(enchant);
                                                }
                                                enchant = temp;
                                            }
                                            // Damage the character
                                            if(chridvulnerability != IDSZNONE && (chridvulnerability == prtidtype || chridvulnerability == prtidparent))
                                            {
                                                damage_character(chara, direction, prtdamagebase[particle]<<1, prtdamagerand[particle]<<1, prtdamagetype[particle], prtteam[particle], prtchr[particle], pipdamfx[pip]);
                                                chralert[chara]|=ALERTIFHITVULNERABLE;
                                            }
                                            else
                                            {
                                                damage_character(chara, direction, prtdamagebase[particle], prtdamagerand[particle], prtdamagetype[particle], prtteam[particle], prtchr[particle], pipdamfx[pip]);
                                            }
                                            // Do confuse effects
                                            if((madframefx[chrframe[chara]]&MADFXINVICTUS)==FALSE || pipdamfx[pip]&DAMFXBLOC)
                                            {
                                                if(pipgrogtime[pip] != 0 && capcanbegrogged[chrmodel[chara]])
                                                {
                                                    chrgrogtime[chara]+=pipgrogtime[pip];
                                                    if(chrgrogtime[chara]<0)  chrgrogtime[chara] = 32767;
                                                    chralert[chara] = chralert[chara]|ALERTIFGROGGED;
                                                }
                                                if(pipdazetime[pip] != 0 && capcanbedazed[chrmodel[chara]])
                                                {
                                                    chrdazetime[chara]+=pipdazetime[pip];
                                                    if(chrdazetime[chara]<0)  chrdazetime[chara] = 32767;
                                                    chralert[chara] = chralert[chara]|ALERTIFDAZED;
                                                }
                                            }
                                            // Notify the attacker of a scored hit
                                            if(prtchr[particle]!=MAXCHR)
                                            {
                                                chralert[prtchr[particle]]=chralert[prtchr[particle]]|ALERTIFSCOREDAHIT;
                                                chrhitlast[prtchr[particle]]=chara;
                                            }
                                        }
                                        if((wldframe&31)==0 && prtattachedtocharacter[particle]==chara)
                                        {
                                            // Attached particle damage ( Burning )
                                            if(pipxyvelbase[pip]==0)
                                            {
                                                // Make character limp
                                                chrxvel[chara]=0;
                                                chryvel[chara]=0;
                                            }
                                            damage_character(chara, 32768, prtdamagebase[particle], prtdamagerand[particle], prtdamagetype[particle], prtteam[particle], prtchr[particle], pipdamfx[pip]);
                                        }
                                    }
                                    if(pipendbump[pip])
                                    {
                                        if(pipbumpmoney[pip])
                                        {
                                            if(chrcangrabmoney[chara]&&chralive[chara]&&chrdamagetime[chara]==0)
                                            {
                                                if(chrismount[chara])
                                                {
                                                    // Let mounts collect money for their riders
                                                    if(chrholdingwhich[chara][0] != MAXCHR)
                                                    {
                                                        chrmoney[chrholdingwhich[chara][0]]+=pipbumpmoney[pip];
                                                        if(chrmoney[chrholdingwhich[chara][0]] > MAXMONEY) chrmoney[chrholdingwhich[chara][0]] = 0;
                                                        if(chrmoney[chrholdingwhich[chara][0]] < 0) chrmoney[chrholdingwhich[chara][0]] = 0;
                                                        prttime[particle]=1;
                                                    }
                                                }
                                                else
                                                {
                                                    // Normal money collection
                                                    chrmoney[chara]+=pipbumpmoney[pip];
                                                    if(chrmoney[chara] > MAXMONEY) chrmoney[chara] = 0;
                                                    if(chrmoney[chara] < 0) chrmoney[chara] = 0;
                                                    prttime[particle]=1;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            prttime[particle]=1;
                                            // Only hit one character, not several
                                            prtdamagebase[particle] = 0;
                                            prtdamagerand[particle] = 1;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                if(prtchr[particle] != chara)
                                {
                                    cost_mana(chrmissilehandler[chara], (chrmissilecost[chara]<<4), prtchr[particle]);
                                    // Treat the missile
                                    if(chrmissiletreatment[chara] == MISDEFLECT)
                                    {
                                        // Use old position to find normal
                                        ax = prtxpos[particle] - prtxvel[particle];
                                        ay = prtypos[particle] - prtyvel[particle];
                                        ax = chrxpos[chara] - ax;
                                        ay = chrypos[chara] - ay;
                                        // Find size of normal
                                        scale = ax*ax + ay*ay;
                                        if(scale > 0)
                                        {
                                            // Make the normal a unit normal
                                            scale = sqrt(scale);
                                            nx = ax/scale;
                                            ny = ay/scale;
                                            // Deflect the incoming ray off the normal
                                            scale = (prtxvel[particle]*nx + prtyvel[particle]*ny)*2;
                                            ax = scale*nx;
                                            ay = scale*ny;
                                            prtxvel[particle] = prtxvel[particle] - ax;
                                            prtyvel[particle] = prtyvel[particle] - ay;
                                        }
                                    }
                                    else
                                    {
                                        // Reflect it back in the direction it came
                                        prtxvel[particle] = -prtxvel[particle];
                                        prtyvel[particle] = -prtyvel[particle];
                                    }
                                    // Change the owner of the missile
                                    if(piphoming[pip] == FALSE)
                                    {
                                        prtteam[particle] = chrteam[chara];
                                        prtchr[particle] = chara;
                                    }
                                    // Change the direction of the particle
                                    if(piprotatetoface[pip])
                                    {
                                        // Turn to face new direction
                                        facing = atan2(prtyvel[particle], prtxvel[particle])*65535/(2*PI);
                                        facing+=32768;
                                        prtfacing[particle] = facing;
                                    }
                                }
                            }
                        }
                    }
                    particle = prtbumpnext[particle];
                    tnc++;
                }
            }
            chara = chrbumpnext[chara];
            cnt++;
        }
        fanblock++;
    }
}


//--------------------------------------------------------------------------------------------
void stat_return()
{
    // ZZ> This function brings mana and life back
    int cnt, owner, target, eve;


    // Do reload time
    cnt = 0;
    while(cnt < MAXCHR)
    {
        if(chrreloadtime[cnt] > 0)
        {
            chrreloadtime[cnt]--;
        }
        cnt++;
    }



    // Do stats
    if(statclock==ONESECOND)
    {
        // Reset the clock
        statclock = 0;


        // Do all the characters
        cnt = 0;
        while(cnt < MAXCHR)
        {
            if(chron[cnt]&&(!chrinpack[cnt])&&chralive[cnt])
            {
                chrmana[cnt]+=chrmanareturn[cnt];
                if(chrmana[cnt] < 0)
                    chrmana[cnt] = 0;
                if(chrmana[cnt] > chrmanamax[cnt])
                    chrmana[cnt] = chrmanamax[cnt];
                chrlife[cnt]+=chrlifereturn[cnt];
                if(chrlife[cnt] < 1)
                    chrlife[cnt] = 1;
                if(chrlife[cnt] > chrlifemax[cnt])
                    chrlife[cnt] = chrlifemax[cnt];
                if(chrgrogtime[cnt] > 0)
                {
                    chrgrogtime[cnt]--;
                    if(chrgrogtime[cnt] < 0)
                        chrgrogtime[cnt] = 0;
                }
                if(chrdazetime[cnt] > 0)
                {
                    chrdazetime[cnt]--;
                    if(chrdazetime[cnt] < 0)
                        chrdazetime[cnt] = 0;
                }
            }
            cnt++;
        }


        // Run through all the enchants as well
        cnt = 0;
        while(cnt < MAXENCHANT)
        {
            if(encon[cnt])
            {
                if(enctime[cnt] != 0)
                {
                    if(enctime[cnt] > 0)
                    {
                        enctime[cnt]--;
                    }
                    owner = encowner[cnt];
                    target = enctarget[cnt];
                    eve = enceve[cnt];


                    // Do drains
                    if(chralive[owner])
                    {
                        // Change life
                        chrlife[owner]+=encownerlife[cnt];
                        if(chrlife[owner] < 1)
                        {
                            chrlife[owner] = 1;
                            kill_character(owner, target);
                        }
                        if(chrlife[owner] > chrlifemax[owner])
                        {
                            chrlife[owner] = chrlifemax[owner];
                        }
                        // Change mana
                        if(cost_mana(owner, -encownermana[cnt], target)==FALSE && eveendifcantpay[eve])
                        {
                            remove_enchant(cnt);
                        }
                    }
                    else
                    {
                        if(evestayifnoowner[eve]==FALSE)
                        {
                            remove_enchant(cnt);
                        }
                    }
                    if(encon[cnt])
                    {
                        if(chralive[target])
                        {
                            // Change life
                            chrlife[target]+=enctargetlife[cnt];
                            if(chrlife[target] < 1)
                            {
                                chrlife[target] = 1;
                                kill_character(target, owner);
                            }
                            if(chrlife[target] > chrlifemax[target])
                            {
                                chrlife[target] = chrlifemax[target];
                            }
                            // Change mana
                            if(cost_mana(target, -enctargetmana[cnt], owner)==FALSE && eveendifcantpay[eve])
                            {
                                remove_enchant(cnt);
                            }
                        }
                        else
                        {
                            remove_enchant(cnt);
                        }
                    }
                }
                else
                {
                    remove_enchant(cnt);
                }
            }
            cnt++;
        }
    }
}

//--------------------------------------------------------------------------------------------
void pit_kill()
{
    // ZZ> This function kills any character in a deep pit...
    int cnt;

    if(pitskill)
    {
        if(pitclock > 19)
        {
            pitclock = 0;


            // Kill any particles that fell in a pit, if they die in water...
            cnt = 0;
            while(cnt < MAXPRT)
            {
                if(prton[cnt])
                {
                    if(prtzpos[cnt] < PITDEPTH && pipendwater[prtpip[cnt]])
                    {
                        prttime[cnt] = 1;
                    }
                }
                cnt++;
            }



            // Kill any characters that fell in a pit...
            cnt = 0;
            while(cnt < MAXCHR)
            {
                if(chron[cnt] && chralive[cnt] && chrinpack[cnt] == FALSE)
                {
                    if(chrinvictus[cnt] == FALSE && chrzpos[cnt] < PITDEPTH && chrattachedto[cnt]==MAXCHR)
                    {
                        // Got one!
                        kill_character(cnt, MAXCHR);
                        chrxvel[cnt] = 0;
                        chryvel[cnt] = 0;
                    }
                }
                cnt++;
            }
        }
        else
        {
            pitclock++;
        }
    }
}

//--------------------------------------------------------------------------------------------
void reset_players()
{
    // ZZ> This function clears the player list data
    int cnt, tnc;


    // Reset the local data stuff
    localseekurse = FALSE;
    localseeinvisible = FALSE;
    alllocalpladead = FALSE;


    // Reset the initial player data and latches
    cnt = 0;
    while(cnt < MAXPLAYER)
    {
        plavalid[cnt] = FALSE;
        plaindex[cnt] = 0;
        plalatchx[cnt] = 0;
        plalatchy[cnt] = 0;
        plalatchbutton[cnt] = 0;
        tnc = 0;
        while(tnc < MAXLAG)
        {
            platimelatchx[cnt][tnc] = 0;
            platimelatchy[cnt][tnc] = 0;
            platimelatchbutton[cnt][tnc] = 0;
            tnc++;
        }
        pladevice[cnt] = INPUTNONE;
        cnt++;
    }
    numpla = 0;
    nexttimestamp = -1;
    numplatimes = STARTTALK+1;
    if(hostactive) numplatimes++;
}

//--------------------------------------------------------------------------------------------
void resize_characters()
{
    // ZZ> This function makes the characters get bigger or smaller, depending
    //     on their sizegoto and sizegototime
    int cnt, item, mount;
    unsigned char willgetcaught;
    float newsize;


    cnt = 0;
    while(cnt < MAXCHR)
    {
        if(chron[cnt] && chrsizegototime[cnt])
        {
            // Make sure it won't get caught in a wall
            willgetcaught = FALSE;
            if(chrsizegoto[cnt] > chrfat[cnt])
            {
                chrbumpsize[cnt] += 10;
                if(__chrhitawall(cnt))
                {
                    willgetcaught = TRUE;
                }
                chrbumpsize[cnt] -= 10;
            }


            // If it is getting caught, simply halt growth until later
            if(willgetcaught==FALSE)
            {
                // Figure out how big it is
                chrsizegototime[cnt]--;
                newsize = chrsizegoto[cnt];
                if(chrsizegototime[cnt] != 0)
                {
                    newsize = (chrfat[cnt] * .90) + (newsize * .10);
                }


                // Make it that big...
                chrfat[cnt] = newsize;
                chrshadowsize[cnt] = chrshadowsizesave[cnt]*newsize;
                chrbumpsize[cnt] = chrbumpsizesave[cnt]*newsize;
                chrbumpsizebig[cnt] = chrbumpsizebigsave[cnt]*newsize;
                chrbumpheight[cnt] = chrbumpheightsave[cnt]*newsize;
                chrweight[cnt] = capweight[chrmodel[cnt]]*newsize;
                if(capweight[chrmodel[cnt]]==255) chrweight[cnt] = 65535;


                // Now come up with the magic number
                mount = chrattachedto[cnt];
                if(mount == MAXCHR)
                {
                    chrscale[cnt] = newsize*madscale[chrmodel[cnt]]*4;
                }
                else
                {
                    chrscale[cnt] = newsize/(chrfat[mount]*1280);
                }


                // Make in hand items stay the same size...
                newsize = newsize*1280;
                item = chrholdingwhich[cnt][0];
                if(item != MAXCHR)
                    chrscale[item] = chrfat[item]/newsize;
                item = chrholdingwhich[cnt][1];
                if(item != MAXCHR)
                    chrscale[item] = chrfat[item]/newsize;
            }
        }
        cnt++;
    }
}

//--------------------------------------------------------------------------------------------
void export_one_character_name(char *szSaveName, unsigned short character)
{
    // ZZ> This function makes the naming.txt file for the character
    FILE* filewrite;
    int profile;
    char cTmp;
    int cnt, tnc;


    // Can it export?
    profile = chrmodel[character];
    filewrite = fopen(szSaveName, "w");
    if(filewrite)
    {
        cnt = 0;
        cTmp = chrname[character][0];
        cnt++;
        while(cnt < MAXCAPNAMESIZE && cTmp != 0)
        {
            fprintf(filewrite, ":");
            tnc = 0;
            while(tnc < 8 && cTmp != 0)
            {
                if(cTmp == ' ')
                {
                    fprintf(filewrite, "_");
                }
                else
                {
                    fprintf(filewrite, "%c", cTmp);
                }
                cTmp = chrname[character][cnt];
                tnc++;
                cnt++;
            }
            fprintf(filewrite, "\n");
            fprintf(filewrite, ":STOP\n\n");
        }
        fclose(filewrite);
    }
}

//--------------------------------------------------------------------------------------------
void export_one_character_profile(char *szSaveName, unsigned short character)
{
    // ZZ> This function creates a data.txt file for the given character.
    //     it is assumed that all enchantments have been done away with
    FILE* filewrite;
    int profile;
    int damagetype, skin;
    char types[10] = "SCPHEFIZ";
    char codes[4];


    // General stuff
    profile = chrmodel[character];


    // Open the file
    filewrite = fopen(szSaveName, "w");
    if(filewrite)
    {
        // Real general data
        fprintf(filewrite, "Slot number    : -1\n");  // -1 signals a flexible load thing
        funderf(filewrite, "Class name     : ", capclassname[profile]);
        ftruthf(filewrite, "Uniform light  : ", capuniformlit[profile]);
        fprintf(filewrite, "Maximum ammo   : %d\n", capammomax[profile]);
        fprintf(filewrite, "Current ammo   : %d\n", chrammo[character]);
        fgendef(filewrite, "Gender         : ", chrgender[character]);
        fprintf(filewrite, "\n");



        // Object stats
        fprintf(filewrite, "Life color     : %d\n", chrlifecolor[character]);
        fprintf(filewrite, "Mana color     : %d\n", chrmanacolor[character]);
        fprintf(filewrite, "Life           : %4.2f\n", chrlifemax[character]/256.0);
        fpairof(filewrite, "Life up        : ", caplifeperlevelbase[profile], caplifeperlevelrand[profile]);
        fprintf(filewrite, "Mana           : %4.2f\n", chrmanamax[character]/256.0);
        fpairof(filewrite, "Mana up        : ", capmanaperlevelbase[profile], capmanaperlevelrand[profile]);
        fprintf(filewrite, "Mana return    : %4.2f\n", chrmanareturn[character]/16.0);
        fpairof(filewrite, "Mana return up : ", capmanareturnperlevelbase[profile], capmanareturnperlevelrand[profile]);
        fprintf(filewrite, "Mana flow      : %4.2f\n", chrmanaflow[character]/256.0);
        fpairof(filewrite, "Mana flow up   : ", capmanaflowperlevelbase[profile], capmanaflowperlevelrand[profile]);
        fprintf(filewrite, "STR            : %4.2f\n", chrstrength[character]/256.0);
        fpairof(filewrite, "STR up         : ", capstrengthperlevelbase[profile], capstrengthperlevelrand[profile]);
        fprintf(filewrite, "WIS            : %4.2f\n", chrwisdom[character]/256.0);
        fpairof(filewrite, "WIS up         : ", capwisdomperlevelbase[profile], capwisdomperlevelrand[profile]);
        fprintf(filewrite, "INT            : %4.2f\n", chrintelligence[character]/256.0);
        fpairof(filewrite, "INT up         : ", capintelligenceperlevelbase[profile], capintelligenceperlevelrand[profile]);
        fprintf(filewrite, "DEX            : %4.2f\n", chrdexterity[character]/256.0);
        fpairof(filewrite, "DEX up         : ", capdexterityperlevelbase[profile], capdexterityperlevelrand[profile]);
        fprintf(filewrite, "\n");



        // More physical attributes
        fprintf(filewrite, "Size           : %4.2f\n", chrsizegoto[character]);
        fprintf(filewrite, "Size up        : %4.2f\n", capsizeperlevel[profile]);
        fprintf(filewrite, "Shadow size    : %d\n", capshadowsize[profile]);
        fprintf(filewrite, "Bump size      : %d\n", capbumpsize[profile]);
        fprintf(filewrite, "Bump height    : %d\n", capbumpheight[profile]);
        fprintf(filewrite, "Bump dampen    : %4.2f\n", capbumpdampen[profile]);
        fprintf(filewrite, "Weight         : %d\n", capweight[profile]);
        fprintf(filewrite, "Jump power     : %4.2f\n", capjump[profile]);
        fprintf(filewrite, "Jump number    : %d\n", capjumpnumber[profile]);
        fprintf(filewrite, "Sneak speed    : %d\n", capsneakspd[profile]);
        fprintf(filewrite, "Walk speed     : %d\n", capwalkspd[profile]);
        fprintf(filewrite, "Run speed      : %d\n", caprunspd[profile]);
        fprintf(filewrite, "Fly to height  : %d\n", capflyheight[profile]);
        fprintf(filewrite, "Flashing AND   : %d\n", capflashand[profile]);
        fprintf(filewrite, "Alpha blending : %d\n", capalpha[profile]);
        fprintf(filewrite, "Light blending : %d\n", caplight[profile]);
        ftruthf(filewrite, "Transfer blend : ", captransferblend[profile]);
        fprintf(filewrite, "Sheen          : %d\n", capsheen[profile]);
        ftruthf(filewrite, "Phong mapping  : ", capenviro[profile]);
        fprintf(filewrite, "Texture X add  : %4.2f\n", capuoffvel[profile]/65535.0);
        fprintf(filewrite, "Texture Y add  : %4.2f\n", capvoffvel[profile]/65535.0);
        ftruthf(filewrite, "Sticky butt    : ", capstickybutt[profile]);
        fprintf(filewrite, "\n");



        // Invulnerability data
        ftruthf(filewrite, "Invictus       : ", capinvictus[profile]);
        fprintf(filewrite, "NonI facing    : %d\n", capnframefacing[profile]);
        fprintf(filewrite, "NonI angle     : %d\n", capnframeangle[profile]);
        fprintf(filewrite, "I facing       : %d\n", capiframefacing[profile]);
        fprintf(filewrite, "I angle        : %d\n", capiframeangle[profile]);
        fprintf(filewrite, "\n");



        // Skin defenses
        fprintf(filewrite, "Base defense   : %3d %3d %3d %3d\n", 255-capdefense[profile][0], 255-capdefense[profile][1],
                                                                 255-capdefense[profile][2], 255-capdefense[profile][3]);
        damagetype = 0;
        while(damagetype < MAXDAMAGETYPE)
        {
            fprintf(filewrite, "%c damage shift : %3d %3d %3d %3d\n", types[damagetype],
                                                                      capdamagemodifier[profile][damagetype][0]&DAMAGESHIFT,
                                                                      capdamagemodifier[profile][damagetype][1]&DAMAGESHIFT,
                                                                      capdamagemodifier[profile][damagetype][2]&DAMAGESHIFT,
                                                                      capdamagemodifier[profile][damagetype][3]&DAMAGESHIFT);
            damagetype++;
        }
        damagetype = 0;
        while(damagetype < MAXDAMAGETYPE)
        {
            skin = 0;
            while(skin < 4)
            {
                codes[skin] = 'F';
                if(capdamagemodifier[profile][damagetype][skin]&DAMAGEINVERT)
                    codes[skin] = 'T';
                if(capdamagemodifier[profile][damagetype][skin]&DAMAGECHARGE)
                    codes[skin] = 'C';
                skin++;
            }
            fprintf(filewrite, "%c damage code  : %3c %3c %3c %3c\n", types[damagetype], codes[0], codes[1], codes[2], codes[3]);
            damagetype++;
        }
        fprintf(filewrite, "Acceleration   : %3.0f %3.0f %3.0f %3.0f\n", capmaxaccel[profile][0]*80,
                                                                         capmaxaccel[profile][1]*80, 
                                                                         capmaxaccel[profile][2]*80,
                                                                         capmaxaccel[profile][3]*80);
        fprintf(filewrite, "\n");



        // Experience and level data
        fprintf(filewrite, "EXP for 2nd    : %d\n", capexperienceforlevel[profile][1]);
        fprintf(filewrite, "EXP for 3rd    : %d\n", capexperienceforlevel[profile][2]);
        fprintf(filewrite, "EXP for 4th    : %d\n", capexperienceforlevel[profile][3]);
        fprintf(filewrite, "EXP for 5th    : %d\n", capexperienceforlevel[profile][4]);
        fprintf(filewrite, "EXP for 6th    : %d\n", capexperienceforlevel[profile][5]);
        fprintf(filewrite, "Starting EXP   : %d\n", chrexperience[character]);
        fprintf(filewrite, "EXP worth      : %d\n", capexperienceworth[profile]);
        fprintf(filewrite, "EXP exchange   : %5.3f\n", capexperienceexchange[profile]);
        fprintf(filewrite, "EXPSECRET      : %4.2f\n", capexperiencerate[profile][0]);
        fprintf(filewrite, "EXPQUEST       : %4.2f\n", capexperiencerate[profile][1]);
        fprintf(filewrite, "EXPDARE        : %4.2f\n", capexperiencerate[profile][2]);
        fprintf(filewrite, "EXPKILL        : %4.2f\n", capexperiencerate[profile][3]);
        fprintf(filewrite, "EXPMURDER      : %4.2f\n", capexperiencerate[profile][4]);
        fprintf(filewrite, "EXPREVENGE     : %4.2f\n", capexperiencerate[profile][5]);
        fprintf(filewrite, "EXPTEAMWORK    : %4.2f\n", capexperiencerate[profile][6]);
        fprintf(filewrite, "EXPROLEPLAY    : %4.2f\n", capexperiencerate[profile][7]);
        fprintf(filewrite, "\n");



        // IDSZ identification tags
        undo_idsz(capidsz[profile][0]);
        fprintf(filewrite, "IDSZ Parent    : [%s]\n", valueidsz);
        undo_idsz(capidsz[profile][1]);
        fprintf(filewrite, "IDSZ Type      : [%s]\n", valueidsz);
        undo_idsz(capidsz[profile][2]);
        fprintf(filewrite, "IDSZ Skill     : [%s]\n", valueidsz);
        undo_idsz(capidsz[profile][3]);
        fprintf(filewrite, "IDSZ Special   : [%s]\n", valueidsz);
        undo_idsz(capidsz[profile][4]);
        fprintf(filewrite, "IDSZ Hate      : [%s]\n", valueidsz);
        undo_idsz(capidsz[profile][5]);
        fprintf(filewrite, "IDSZ Vulnie    : [%s]\n", valueidsz);
        fprintf(filewrite, "\n");



        // Item and damage flags
        ftruthf(filewrite, "Is an item     : ", capisitem[profile]);
        ftruthf(filewrite, "Is a mount     : ", capismount[profile]);
        ftruthf(filewrite, "Is stackable   : ", capisstackable[profile]);
        ftruthf(filewrite, "Name known     : ", chrnameknown[character]);
        ftruthf(filewrite, "Usage known    : ", capusageknown[profile]);
        ftruthf(filewrite, "Is exportable  : ", capcancarrytonextmodule[profile]);
        ftruthf(filewrite, "Requires skill : ", capneedskillidtouse[profile]);
        ftruthf(filewrite, "Is platform    : ", capplatform[profile]);
        ftruthf(filewrite, "Collects money : ", capcangrabmoney[profile]);
        ftruthf(filewrite, "Can open stuff : ", capcanopenstuff[profile]);
        fprintf(filewrite, "\n");



        // Other item and damage stuff
        fdamagf(filewrite, "Damage type    : ", capdamagetargettype[profile]);
        factiof(filewrite, "Attack type    : ", capweaponaction[profile]);
        fprintf(filewrite, "\n");



        // Particle attachments
        fprintf(filewrite, "Attached parts : %d\n", capattachedprtamount[profile]);
        fdamagf(filewrite, "Reaffirm type  : ", capattachedprtreaffirmdamagetype[profile]);
        fprintf(filewrite, "Particle type  : %d\n", capattachedprttype[profile]);
        fprintf(filewrite, "\n");



        // Character hands
        ftruthf(filewrite, "Left valid     : ", capgripvalid[profile][0]);
        ftruthf(filewrite, "Right valid    : ", capgripvalid[profile][1]);
        fprintf(filewrite, "\n");



        // Particle spawning on attack
        ftruthf(filewrite, "Part on weapon : ", capattackattached[profile]);
        fprintf(filewrite, "Part type      : %d\n", capattackprttype[profile]);
        fprintf(filewrite, "\n");



        // Particle spawning for GoPoof
        fprintf(filewrite, "Poof amount    : %d\n", capgopoofprtamount[profile]);
        fprintf(filewrite, "Facing add     : %d\n", capgopoofprtfacingadd[profile]);
        fprintf(filewrite, "Part type      : %d\n", capgopoofprttype[profile]);
        fprintf(filewrite, "\n");



        // Particle spawning for blood
        ftruthf(filewrite, "Blood valid    : ", capbloodvalid[profile]);
        fprintf(filewrite, "Part type      : %d\n", capbloodprttype[profile]);
        fprintf(filewrite, "\n");



        // Extra stuff
        ftruthf(filewrite, "Waterwalking   : ", capwaterwalk[profile]);
        fprintf(filewrite, "Bounce dampen  : %5.3f\n", capdampen[profile]);
        fprintf(filewrite, "\n");



        // More stuff
        fprintf(filewrite, "Life healing   : %5.3f\n", caplifeheal[profile]/256.0);
        fprintf(filewrite, "Mana cost      : %5.3f\n", capmanacost[profile]/256.0);
        fprintf(filewrite, "Life return    : %d\n", caplifereturn[profile]);
        fprintf(filewrite, "Stopped by     : %d\n", capstoppedby[profile]);
        funderf(filewrite, "Skin 0 name    : ", capskinname[profile][0]);
        funderf(filewrite, "Skin 1 name    : ", capskinname[profile][1]);
        funderf(filewrite, "Skin 2 name    : ", capskinname[profile][2]);
        funderf(filewrite, "Skin 3 name    : ", capskinname[profile][3]);
        fprintf(filewrite, "Skin 0 cost    : %d\n", capskincost[profile][0]);
        fprintf(filewrite, "Skin 1 cost    : %d\n", capskincost[profile][1]);
        fprintf(filewrite, "Skin 2 cost    : %d\n", capskincost[profile][2]);
        fprintf(filewrite, "Skin 3 cost    : %d\n", capskincost[profile][3]);
        fprintf(filewrite, "STR dampen     : %5.3f\n", capstrengthdampen[profile]);
        fprintf(filewrite, "\n");



        // Another memory lapse
        ftruthf(filewrite, "No rider attak : ", TRUE-capridercanattack[profile]);
        ftruthf(filewrite, "Can be dazed   : ", capcanbedazed[profile]);
        ftruthf(filewrite, "Can be grogged : ", capcanbegrogged[profile]);
        fprintf(filewrite, "NOT USED       : 0\n");
        fprintf(filewrite, "NOT USED       : 0\n");
        ftruthf(filewrite, "Can see invisi : ", capcanseeinvisible[profile]);
        fprintf(filewrite, "Kursed chance  : %d\n", chriskursed[character]*100);
        fprintf(filewrite, "Footfall sound : %d\n", capwavefootfall[profile]);
        fprintf(filewrite, "Jump sound     : %d\n", capwavejump[profile]);
        fprintf(filewrite, "\n");


        // Expansions
        fprintf(filewrite, ":[GOLD] %d\n", chrmoney[character]);
        if(capskindressy[profile]&1)
            fprintf(filewrite, ":[DRES] 0\n");
        if(capskindressy[profile]&2)
            fprintf(filewrite, ":[DRES] 1\n");
        if(capskindressy[profile]&4)
            fprintf(filewrite, ":[DRES] 2\n");
        if(capskindressy[profile]&8)
            fprintf(filewrite, ":[DRES] 3\n");
        if(capresistbumpspawn[profile])
            fprintf(filewrite, ":[STUK] 0\n");
        if(capistoobig[profile])
            fprintf(filewrite, ":[PACK] 0\n");
        if(capreflect[profile]==FALSE)
            fprintf(filewrite, ":[VAMP] 1\n");
        if(capalwaysdraw[profile])
            fprintf(filewrite, ":[DRAW] 1\n");
        if(capisranged[profile])
            fprintf(filewrite, ":[RANG] 1\n");
        if(caphidestate[profile]!=NOHIDE)
            fprintf(filewrite, ":[HIDE] %d\n", caphidestate[profile]);
        if(capisequipment[profile])
            fprintf(filewrite, ":[EQUI] 1\n");
        if(capbumpsizebig[profile]==(capbumpsize[profile]<<1))
            fprintf(filewrite, ":[SQUA] 1\n");
        if(capicon[profile] != capusageknown[profile])
            fprintf(filewrite, ":[ICON] %d\n", capicon[profile]);
        if(capforceshadow[profile])
            fprintf(filewrite, ":[SHAD] 1\n");
        if(capcanseekurse[profile])
            fprintf(filewrite, ":[CKUR] 1\n");
        if(capripple[profile] == capisitem[profile])
            fprintf(filewrite, ":[RIPP] %d\n", capripple[profile]);
        fprintf(filewrite, ":[PLAT] %d\n", capcanuseplatforms[profile]);
        fprintf(filewrite, ":[SKIN] %d\n", chrtexture[character]-madskinstart[profile]);
        fprintf(filewrite, ":[CONT] %d\n", chraicontent[character]);
        fprintf(filewrite, ":[STAT] %d\n", chraistate[character]);
        fprintf(filewrite, ":[LEVL] %d\n", chrexperiencelevel[character]);
        fclose(filewrite);
    }
}

//--------------------------------------------------------------------------------------------
void export_one_character_skin(char *szSaveName, unsigned short character)
{
    // ZZ> This function creates a skin.txt file for the given character.
    FILE* filewrite;
    int profile;


    // General stuff
    profile = chrmodel[character];


    // Open the file
    filewrite = fopen(szSaveName, "w");
    if(filewrite)
    {
        fprintf(filewrite, "This file is used only by the import menu\n");
        fprintf(filewrite, ": %d\n", (chrtexture[character]-madskinstart[profile])&3);
        fclose(filewrite);
    }
}

//--------------------------------------------------------------------------------------------
int load_one_character_profile(char *szLoadName)
{
    // ZZ> This function fills a character profile with data from data.txt, returning
    // the object slot that the profile was stuck into.  It may cause the program
    // to abort if bad things happen.
    FILE* fileread;
    int object;
    char szTmp[256];
    int iTmp;
    float fTmp;
    char cTmp;
    int damagetype, level, xptype, idsz;
    int test;

    // Open the file
    fileread = fopen(FILENAME(szLoadName), "r");
    //printf(" DIAG: trying to read %s\n",szLoadName);
    if(fileread!=NULL)
    {
        globalname = szLoadName;
        // Read in the object slot
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp); object=iTmp;
        if(object < 0)
        {
            if(importobject < 0)
            {
                sprintf(szTmp, "-1 OBJECT SLOT (%s)", szLoadName);
                general_error(object, object, szTmp);
            }
            else
            {
                object = importobject;
            }
        }



        // Read in the real general data
        goto_colon(fileread);  get_name(fileread, capclassname[object]);


        // Make sure we don't load over an existing model
        if(madused[object])
        {
            sprintf(szTmp, "<--MODEL SLOT USED TWICE (%s)", capclassname[object]);
            general_error(object, object, szTmp);
        }
        madused[object] = TRUE;


        // Light cheat
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capuniformlit[object] = FALSE;
/*PORT
            if(cTmp=='T' || cTmp=='t' || shading == D3DSHADE_FLAT)  capuniformlit[object] = TRUE;
*/
        // Ammo
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capammomax[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capammo[object] = iTmp;
        // Gender
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capgender[object] = GENOTHER;
            if(cTmp=='F' || cTmp=='f')  capgender[object] = GENFEMALE;
            if(cTmp=='M' || cTmp=='m')  capgender[object] = GENMALE;
            if(cTmp=='R' || cTmp=='r')  capgender[object] = GENRANDOM;
        // Read in the object stats
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  caplifecolor[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capmanacolor[object] = iTmp;
        goto_colon(fileread);  read_pair(fileread);
            caplifebase[object] = pairbase;  capliferand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            caplifeperlevelbase[object] = pairbase;  caplifeperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capmanabase[object] = pairbase;  capmanarand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capmanaperlevelbase[object] = pairbase;  capmanaperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capmanareturnbase[object] = pairbase;  capmanareturnrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capmanareturnperlevelbase[object] = pairbase;  capmanareturnperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capmanaflowbase[object] = pairbase;  capmanaflowrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capmanaflowperlevelbase[object] = pairbase;  capmanaflowperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capstrengthbase[object] = pairbase;  capstrengthrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capstrengthperlevelbase[object] = pairbase;  capstrengthperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capwisdombase[object] = pairbase;  capwisdomrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capwisdomperlevelbase[object] = pairbase;  capwisdomperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capintelligencebase[object] = pairbase;  capintelligencerand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capintelligenceperlevelbase[object] = pairbase;  capintelligenceperlevelrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capdexteritybase[object] = pairbase;  capdexterityrand[object] = pairrand;
        goto_colon(fileread);  read_pair(fileread);
            capdexterityperlevelbase[object] = pairbase;  capdexterityperlevelrand[object] = pairrand;

        // More physical attributes
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capsize[object] = fTmp;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capsizeperlevel[object] = fTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capshadowsize[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capbumpsize[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capbumpheight[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capbumpdampen[object] = fTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capweight[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capjump[object] = fTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capjumpnumber[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capsneakspd[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capwalkspd[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  caprunspd[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capflyheight[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capflashand[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capalpha[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  caplight[object] = iTmp;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            captransferblend[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  captransferblend[object] = TRUE;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capsheen[object] = iTmp;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capenviro[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capenviro[object] = TRUE;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capuoffvel[object] = fTmp*65535;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capvoffvel[object] = fTmp*65535;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capstickybutt[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capstickybutt[object] = TRUE;


        // Invulnerability data
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capinvictus[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capinvictus[object] = TRUE;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capnframefacing[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capnframeangle[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capiframefacing[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capiframeangle[object] = iTmp;
        // Resist burning and stuck arrows with nframe angle of 1 or more
        if(capnframeangle[object] > 0)
        {
            if(capnframeangle[object] == 1)
            {
                capnframeangle[object] = 0;
            }
        }


        // Skin defenses ( 4 skins )
        goto_colon(fileread);
            fscanf(fileread, "%d", &iTmp);  capdefense[object][0] = 255-iTmp;
            fscanf(fileread, "%d", &iTmp);  capdefense[object][1] = 255-iTmp;
            fscanf(fileread, "%d", &iTmp);  capdefense[object][2] = 255-iTmp;
            fscanf(fileread, "%d", &iTmp);  capdefense[object][3] = 255-iTmp;
        damagetype = 0;
        while(damagetype < MAXDAMAGETYPE)
        {
            goto_colon(fileread);
                fscanf(fileread, "%d", &iTmp);  capdamagemodifier[object][damagetype][0] = iTmp;
                fscanf(fileread, "%d", &iTmp);  capdamagemodifier[object][damagetype][1] = iTmp;
                fscanf(fileread, "%d", &iTmp);  capdamagemodifier[object][damagetype][2] = iTmp;
                fscanf(fileread, "%d", &iTmp);  capdamagemodifier[object][damagetype][3] = iTmp;
            damagetype++;
        }
        damagetype = 0;
        while(damagetype < MAXDAMAGETYPE)
        {
            goto_colon(fileread);
                cTmp = get_first_letter(fileread);  if(cTmp=='T' || cTmp=='t')  capdamagemodifier[object][damagetype][0]|=DAMAGEINVERT;
                                                    if(cTmp=='C' || cTmp=='c')  capdamagemodifier[object][damagetype][0]|=DAMAGECHARGE;
                cTmp = get_first_letter(fileread);  if(cTmp=='T' || cTmp=='t')  capdamagemodifier[object][damagetype][1]|=DAMAGEINVERT;
                                                    if(cTmp=='C' || cTmp=='c')  capdamagemodifier[object][damagetype][1]|=DAMAGECHARGE;
                cTmp = get_first_letter(fileread);  if(cTmp=='T' || cTmp=='t')  capdamagemodifier[object][damagetype][2]|=DAMAGEINVERT;
                                                    if(cTmp=='C' || cTmp=='c')  capdamagemodifier[object][damagetype][2]|=DAMAGECHARGE;
                cTmp = get_first_letter(fileread);  if(cTmp=='T' || cTmp=='t')  capdamagemodifier[object][damagetype][3]|=DAMAGEINVERT;
                                                    if(cTmp=='C' || cTmp=='c')  capdamagemodifier[object][damagetype][3]|=DAMAGECHARGE;
            damagetype++;
        }
        goto_colon(fileread);
            fscanf(fileread, "%f", &fTmp);  capmaxaccel[object][0] = fTmp/80.0;
            fscanf(fileread, "%f", &fTmp);  capmaxaccel[object][1] = fTmp/80.0;
            fscanf(fileread, "%f", &fTmp);  capmaxaccel[object][2] = fTmp/80.0;
            fscanf(fileread, "%f", &fTmp);  capmaxaccel[object][3] = fTmp/80.0;


        // Experience and level data
        capexperienceforlevel[object][0] = 0;
        level = 1;
        while(level < MAXLEVEL)
        {
            goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capexperienceforlevel[object][level] = iTmp;
            level++;
        }
        goto_colon(fileread);  read_pair(fileread);
            pairbase = pairbase>>8;
            pairrand = pairrand>>8;
            if(pairrand < 1)  pairrand = 1;
            capexperiencebase[object] = pairbase;
            capexperiencerand[object] = pairrand;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capexperienceworth[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capexperienceexchange[object] = fTmp;
        xptype = 0;
        while(xptype < MAXEXPERIENCETYPE)
        {
            goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capexperiencerate[object][xptype] = fTmp+0.001;
            xptype++;
        }


        // IDSZ tags
        idsz = 0;
        while(idsz < MAXIDSZ)
        {
            goto_colon(fileread);  iTmp = get_idsz(fileread);  capidsz[object][idsz] = iTmp;
            idsz++;
        }


        // Item and damage flags
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capisitem[object] = FALSE;  capripple[object] = TRUE;
            if(cTmp=='T' || cTmp=='t')  { capisitem[object] = TRUE; capripple[object] = FALSE; }
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capismount[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capismount[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capisstackable[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capisstackable[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capnameknown[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capnameknown[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capusageknown[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capusageknown[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capcancarrytonextmodule[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capcancarrytonextmodule[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capneedskillidtouse[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capneedskillidtouse[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capplatform[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capplatform[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capcangrabmoney[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capcangrabmoney[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capcanopenstuff[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capcanopenstuff[object] = TRUE;



        // More item and damage stuff
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            if(cTmp=='S' || cTmp=='s')  capdamagetargettype[object] = DAMAGESLASH;
            if(cTmp=='C' || cTmp=='c')  capdamagetargettype[object] = DAMAGECRUSH;
            if(cTmp=='P' || cTmp=='p')  capdamagetargettype[object] = DAMAGEPOKE;
            if(cTmp=='H' || cTmp=='h')  capdamagetargettype[object] = DAMAGEHOLY;
            if(cTmp=='E' || cTmp=='e')  capdamagetargettype[object] = DAMAGEEVIL;
            if(cTmp=='F' || cTmp=='f')  capdamagetargettype[object] = DAMAGEFIRE;
            if(cTmp=='I' || cTmp=='i')  capdamagetargettype[object] = DAMAGEICE;
            if(cTmp=='Z' || cTmp=='z')  capdamagetargettype[object] = DAMAGEZAP;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capweaponaction[object] = what_action(cTmp);


        // Particle attachments
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capattachedprtamount[object] = iTmp;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            if(cTmp=='N' || cTmp=='n')  capattachedprtreaffirmdamagetype[object] = DAMAGENULL;
            if(cTmp=='S' || cTmp=='s')  capattachedprtreaffirmdamagetype[object] = DAMAGESLASH;
            if(cTmp=='C' || cTmp=='c')  capattachedprtreaffirmdamagetype[object] = DAMAGECRUSH;
            if(cTmp=='P' || cTmp=='p')  capattachedprtreaffirmdamagetype[object] = DAMAGEPOKE;
            if(cTmp=='H' || cTmp=='h')  capattachedprtreaffirmdamagetype[object] = DAMAGEHOLY;
            if(cTmp=='E' || cTmp=='e')  capattachedprtreaffirmdamagetype[object] = DAMAGEEVIL;
            if(cTmp=='F' || cTmp=='f')  capattachedprtreaffirmdamagetype[object] = DAMAGEFIRE;
            if(cTmp=='I' || cTmp=='i')  capattachedprtreaffirmdamagetype[object] = DAMAGEICE;
            if(cTmp=='Z' || cTmp=='z')  capattachedprtreaffirmdamagetype[object] = DAMAGEZAP;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capattachedprttype[object] = iTmp;


        // Character hands
        capgripvalid[object][0] = FALSE;
        capgripvalid[object][1] = FALSE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            if(cTmp=='T' || cTmp=='t')  capgripvalid[object][0] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            if(cTmp=='T' || cTmp=='t')  capgripvalid[object][1] = TRUE;




        // Attack order ( weapon )
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capattackattached[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capattackattached[object] = TRUE;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capattackprttype[object] = iTmp;


        // GoPoof
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capgopoofprtamount[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capgopoofprtfacingadd[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capgopoofprttype[object] = iTmp;


        // Blood
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capbloodvalid[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capbloodvalid[object] = TRUE;
            if(cTmp=='U' || cTmp=='u')  capbloodvalid[object] = ULTRABLOODY;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capbloodprttype[object] = iTmp;


        // Stuff I forgot
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capwaterwalk[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capwaterwalk[object] = TRUE;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capdampen[object] = fTmp;


        // More stuff I forgot
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  caplifeheal[object] = fTmp*256;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capmanacost[object] = fTmp*256;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  caplifereturn[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capstoppedby[object] = iTmp|MESHFXIMPASS;
        goto_colon(fileread);  get_name(fileread, capskinname[object][0]);
        goto_colon(fileread);  get_name(fileread, capskinname[object][1]);
        goto_colon(fileread);  get_name(fileread, capskinname[object][2]);
        goto_colon(fileread);  get_name(fileread, capskinname[object][3]);
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capskincost[object][0] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capskincost[object][1] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capskincost[object][2] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);  capskincost[object][3] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%f", &fTmp);  capstrengthdampen[object] = fTmp;



        // Another memory lapse
        goto_colon(fileread);  cTmp = get_first_letter(fileread);
            capridercanattack[object] = TRUE;
            if(cTmp=='T' || cTmp=='t')  capridercanattack[object] = FALSE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);  // Can be dazed
            capcanbedazed[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capcanbedazed[object] = TRUE;
        goto_colon(fileread);  cTmp = get_first_letter(fileread);  // Can be grogged
            capcanbegrogged[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capcanbegrogged[object] = TRUE;
        goto_colon(fileread);  // !!!BAD!!! Life add
        goto_colon(fileread);  // !!!BAD!!! Mana add
        goto_colon(fileread);  cTmp = get_first_letter(fileread);  // Can see invisible
            capcanseeinvisible[object] = FALSE;
            if(cTmp=='T' || cTmp=='t')  capcanseeinvisible[object] = TRUE;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);      // Chance of kursed
            capkursechance[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);      // Footfall sound
            if(iTmp < -1) iTmp = -1;
            if(iTmp > MAXWAVE-1) iTmp = MAXWAVE-1;
            capwavefootfall[object] = iTmp;
        goto_colon(fileread);  fscanf(fileread, "%d", &iTmp);      // Jump sound
            if(iTmp < -1) iTmp = -1;
            if(iTmp > MAXWAVE-1) iTmp = MAXWAVE-1;
            capwavejump[object] = iTmp;


        // Clear expansions...
        capskindressy[object] = FALSE;
        capresistbumpspawn[object] = FALSE;
        capistoobig[object] = FALSE;
        capreflect[object] = TRUE;
        capalwaysdraw[object] = FALSE;
        capisranged[object] = FALSE;
        caphidestate[object] = NOHIDE;
        capisequipment[object] = FALSE;
        capbumpsizebig[object] = capbumpsize[object]+(capbumpsize[object]>>1);
        capcanseekurse[object] = FALSE;
        capmoney[object] = 0;
        capicon[object] = capusageknown[object];
        capforceshadow[object] = FALSE;
        capskinoverride[object] = NOSKINOVERRIDE;
        capcontentoverride[object] = 0;
        capstateoverride[object] = 0;
        capleveloverride[object] = 0;
        capcanuseplatforms[object] = !capplatform[object];
        // Read expansions
        while(goto_colon_yesno(fileread))
        {
            idsz = get_idsz(fileread);
            fscanf(fileread, "%c%d", &cTmp, &iTmp);
            test = ('D'-'A'<<15)|('R'-'A'<<10)|('E'-'A'<<5)|('S'-'A');  // [DRES]
            if(idsz == test)  capskindressy[object]|= 1<<iTmp;
            test = ('G'-'A'<<15)|('O'-'A'<<10)|('L'-'A'<<5)|('D'-'A');  // [GOLD]
            if(idsz == test)  capmoney[object] = iTmp;
            test = ('S'-'A'<<15)|('T'-'A'<<10)|('U'-'A'<<5)|('K'-'A');  // [STUK]
            if(idsz == test)  capresistbumpspawn[object] = 1-iTmp;
            test = ('P'-'A'<<15)|('A'-'A'<<10)|('C'-'A'<<5)|('K'-'A');  // [PACK]
            if(idsz == test)  capistoobig[object] = 1-iTmp;
            test = ('V'-'A'<<15)|('A'-'A'<<10)|('M'-'A'<<5)|('P'-'A');  // [VAMP]
            if(idsz == test)  capreflect[object] = 1-iTmp;
            test = ('D'-'A'<<15)|('R'-'A'<<10)|('A'-'A'<<5)|('W'-'A');  // [DRAW]
            if(idsz == test)  capalwaysdraw[object] = iTmp;
            test = ('R'-'A'<<15)|('A'-'A'<<10)|('N'-'A'<<5)|('G'-'A');  // [RANG]
            if(idsz == test)  capisranged[object] = iTmp;
            test = ('H'-'A'<<15)|('I'-'A'<<10)|('D'-'A'<<5)|('E'-'A');  // [HIDE]
            if(idsz == test)  caphidestate[object] = iTmp;
            test = ('E'-'A'<<15)|('Q'-'A'<<10)|('U'-'A'<<5)|('I'-'A');  // [EQUI]
            if(idsz == test)  capisequipment[object] = iTmp;
            test = ('S'-'A'<<15)|('Q'-'A'<<10)|('U'-'A'<<5)|('A'-'A');  // [SQUA]
            if(idsz == test)  capbumpsizebig[object] = capbumpsize[object]<<1;
            test = ('I'-'A'<<15)|('C'-'A'<<10)|('O'-'A'<<5)|('N'-'A');  // [ICON]
            if(idsz == test)  capicon[object] = iTmp;
            test = ('S'-'A'<<15)|('H'-'A'<<10)|('A'-'A'<<5)|('D'-'A');  // [SHAD]
            if(idsz == test)  capforceshadow[object] = iTmp;
            test = ('C'-'A'<<15)|('K'-'A'<<10)|('U'-'A'<<5)|('R'-'A');  // [CKUR]
            if(idsz == test)  capcanseekurse[object] = iTmp;
            test = ('S'-'A'<<15)|('K'-'A'<<10)|('I'-'A'<<5)|('N'-'A');  // [SKIN]
            if(idsz == test)  capskinoverride[object] = iTmp&3;
            test = ('C'-'A'<<15)|('O'-'A'<<10)|('N'-'A'<<5)|('T'-'A');  // [CONT]
            if(idsz == test)  capcontentoverride[object] = iTmp;
            test = ('S'-'A'<<15)|('T'-'A'<<10)|('A'-'A'<<5)|('T'-'A');  // [STAT]
            if(idsz == test)  capstateoverride[object] = iTmp;
            test = ('L'-'A'<<15)|('E'-'A'<<10)|('V'-'A'<<5)|('L'-'A');  // [LEVL]
            if(idsz == test)  capleveloverride[object] = iTmp;
            test = ('P'-'A'<<15)|('L'-'A'<<10)|('A'-'A'<<5)|('T'-'A');  // [PLAT]
            if(idsz == test)  capcanuseplatforms[object] = iTmp;
            test = ('R'-'A'<<15)|('I'-'A'<<10)|('P'-'A'<<5)|('P'-'A');  // [RIPP]
            if(idsz == test)  capripple[object] = iTmp;
        }
        fclose(fileread);
    }
    else
    {
        // The data file wasn't found
        general_error(0, 0, "DATA.TXT NOT FOUND");
    }

    return object;
}

//--------------------------------------------------------------------------------------------
int get_skin(char *filename)
{
    // ZZ> This function reads the skin.txt file...
    FILE*   fileread;
    int skin;


    skin = 0;
    fileread = fopen(FILENAME(filename), "r");
    if(fileread)
    {
        goto_colon_yesno(fileread);
        fscanf(fileread, "%d", &skin);
        skin = skin & 3;
        fclose(fileread);
    }
    return skin;
}

//--------------------------------------------------------------------------------------------
void check_player_import(char *dirname)
{
    // ZZ> This function figures out which players may be imported, and loads basic
    //     data for each
    char searchname[128];
    char filename[128];
    int skin;
    BOOL keeplooking;
    char *foundfile;


    // Set up...
    numloadplayer = 0;

    // Search for all objects
    sprintf(searchname, FILENAME("%s/*.obj"), dirname);
    foundfile = DirGetFirst(searchname);
    keeplooking = 1;
    if(foundfile != NULL)
    {
        while(keeplooking && numloadplayer < MAXLOADPLAYER)
        {
            //fprintf(stderr,"foundfile=%s keeplooking=%d numload=%d/%d\n",foundfile,keeplooking,numloadplayer,MAXLOADPLAYER);
            prime_names();
            sprintf(loadplayerdir[numloadplayer], "%s", foundfile);
            sprintf(filename, FILENAME("%s/%s/skin.txt"), dirname, foundfile);
            skin = get_skin(filename);
            sprintf(filename, FILENAME("%s/%s/icon%d.bmp"), dirname, foundfile, skin);
            load_one_icon(filename);
            sprintf(filename, FILENAME("%s/%s/naming.txt"), dirname, foundfile);
            read_naming(0, filename);
            naming_names(0);
            sprintf(loadplayername[numloadplayer], "%s", namingnames);
            numloadplayer++;
            foundfile=DirGetNext();
            if(foundfile==NULL) keeplooking=0; else keeplooking=1;
        }
    }
    DirClose();
    nullicon = globalnumicon;
    load_one_icon("basicdat/nullicon.bmp");
    keybicon = globalnumicon;
    load_one_icon("basicdat/keybicon.bmp");
    mousicon = globalnumicon;
    load_one_icon("basicdat/mousicon.bmp");
    joyaicon = globalnumicon;
    load_one_icon("basicdat/joyaicon.bmp");
    joybicon = globalnumicon;
    load_one_icon("basicdat/joybicon.bmp");
    keybplayer = 0;
    mousplayer = 0;
    joyaplayer = 0;
    joybplayer = 0;
}

