code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "common.h"

#include "keys.h"
#include "names2.h"
#include "game.h"
#include "tags.h"
#include "sector.h"
#include "sprite.h"

// temp - should be moved
#include "ninja.h"
#include "ai.h"

#include "network.h"

SWBOOL PlayerTakeDamage(PLAYERp, short);
ANIMATOR InitActorRunToward;
SWBOOL FAF_Sector(short);

short FindTrackToPlayer(USERp u);
ANIMATORp ChooseAction(DECISION decision[]);

//static short ZigZagDeltaAng[] = {-200, 200};

// Choose between two things
short AttackOrRun = 200;

#define CHOOSE2(value) (RANDOM_P2(1024) < (value))

void DebugMoveHit(short /*SpriteNum*/)
{
#if 0
    SPRITEp sp;
    USERp u = User[SpriteNum];

#if 0
    if (u->ret == -1)
        printf("Hit a ledge\n");
    else
#endif
    switch (TEST(u->ret, HIT_MASK))
        {
        case HIT_SPRITE:
            sp = &sprite[NORM_SPRITE(u->ret)];
            //DSPRINTF(ds, "Hit a Sprite %d, stat %d ", sp-sprite, (short)sp->statnum);
            if (sp->statnum == STAT_MISSILE)
                {
                //DSPRINTF(ds, "Monster hit bullet %d, stat %d ", sp-sprite, (short)sp->statnum);
                }
            else
                {
                //DSPRINTF(ds, "Hit a Sprite %d, stat %d ", sp-sprite, (short)sp->statnum);
                }
            break;
        case HIT_WALL:
            //DSPRINTF(ds, "Hit a Wall %d    ", NORM_WALL(u->ret));
            break;
        case HIT_SECTOR:
            //DSPRINTF(ds, "Hit a Sector %d  ", NORM_SECTOR(u->ret));
            break;
        }

    MONO_PRINT(ds);
#endif
}

SWBOOL ActorMoveHitReact(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // Should only return TRUE if there is a reaction to what was hit that
    // would cause the calling function to abort

    switch (TEST(u->ret, HIT_MASK))
    {
    case HIT_SPRITE:
    {
        short HitSprite = NORM_SPRITE(u->ret);
        USERp hu;
        ANIMATORp action;

        hu = User[HitSprite];

        // if you ran into a player - call close range functions
        if (hu && hu->PlayerP)
        {
            DoActorPickClosePlayer(SpriteNum);
            action = ChooseAction(u->Personality->TouchTarget);
            if (action)
            {
                (*action)(SpriteNum);
                return TRUE;
            }
        }
        break;
    }

    case HIT_WALL:
    {
        break;
    }

    case HIT_SECTOR:
    {
        break;
    }
    }

    return FALSE;
}

SWBOOL ActorFlaming(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    if (u->flame >= 0)
    {
        int size;
        SPRITEp fp = &sprite[u->flame];

        size = SPRITEp_SIZE_Z(sp) - DIV4(SPRITEp_SIZE_Z(sp));

        //DSPRINTF(ds,"enemy size %d, flame size %d",size>>8,SPRITEp_SIZE_Z(fp)>>8);
        MONO_PRINT(ds);

        if (SPRITEp_SIZE_Z(fp) > size)
            return TRUE;
    }

    return FALSE;
}

void
DoActorSetSpeed(short SpriteNum, uint8_t speed)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    if (TEST(sp->cstat, CSTAT_SPRITE_RESTORE))
        return;

    ASSERT(u->Attrib);

    u->speed = speed;

    if (ActorFlaming(SpriteNum))
        sp->xvel = u->Attrib->Speed[speed] + DIV2(u->Attrib->Speed[speed]);
    else
        sp->xvel = u->Attrib->Speed[speed];
}

/*
  !AIC - Does a table lookup based on a random value from 0 to 1023.
  These tables are defined at the top of all actor files such as ninja.c,
  goro.c etc.
*/

ANIMATORp
ChooseAction(DECISION decision[])
{
    short random_value;
    short i;

    // !JIM! Here is an opportunity for some AI, instead of randomness!
    random_value = RANDOM_P2(1024<<5)>>5;

    for (i = 0; TRUE; i++)
    {
        ASSERT(i < 10);

        if (random_value <= decision[i].range)
        {
            return decision[i].action;
        }
    }
}

/*
  !AIC - Sometimes just want the offset of the action
*/

short
ChooseActionNumber(short decision[])
{
    short random_value;
    short i;

    random_value = RANDOM_P2(1024<<5)>>5;

    for (i = 0; TRUE; i++)
    {
        if (random_value <= decision[i])
        {
            return i;
        }
    }
}

int
DoActorNoise(ANIMATORp Action, short SpriteNum)
{
    USERp u = User[SpriteNum];

    if (Action == InitActorAmbientNoise)
    {
        PlaySpriteSound(SpriteNum, attr_ambient, v3df_follow);
//      MONO_PRINT("Ambient Sound");
    }
    else if (Action == InitActorAlertNoise)
    {
        if (u && !u->DidAlert) // This only allowed once
            PlaySpriteSound(SpriteNum, attr_alert, v3df_follow);
//      MONO_PRINT("Alert Sound");
    }
    else if (Action == InitActorAttackNoise)
    {
        PlaySpriteSound(SpriteNum, attr_attack, v3df_follow);
//      MONO_PRINT("Attack Sound");
    }
    if (Action == InitActorPainNoise)
    {
        PlaySpriteSound(SpriteNum, attr_pain, v3df_follow);
//      MONO_PRINT("Pain Sound");
    }
    else if (Action == InitActorDieNoise)
    {
        PlaySpriteSound(SpriteNum, attr_die, v3df_none);
//      MONO_PRINT("Die Sound");
    }
    else if (Action == InitActorExtra1Noise)
    {
        PlaySpriteSound(SpriteNum, attr_extra1, v3df_follow);
    }
    else if (Action == InitActorExtra2Noise)
    {
        PlaySpriteSound(SpriteNum, attr_extra2, v3df_follow);
    }
    else if (Action == InitActorExtra3Noise)
    {
        PlaySpriteSound(SpriteNum, attr_extra3, v3df_follow);
    }
    if (Action == InitActorExtra4Noise)
    {
        PlaySpriteSound(SpriteNum, attr_extra4, v3df_follow);
    }
    if (Action == InitActorExtra5Noise)
    {
        PlaySpriteSound(SpriteNum, attr_extra5, v3df_follow);
    }
    if (Action == InitActorExtra6Noise)
    {
        PlaySpriteSound(SpriteNum, attr_extra6, v3df_follow);
    }

    return 0;
}

int
CanSeePlayer(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    // if actor can still see the player
    int look_height = SPRITEp_TOS(sp);

    ASSERT(u->tgt_sp);

    //if (FAF_Sector(sp->sectnum))
    //    return(TRUE);

    if (FAFcansee(sp->x, sp->y, look_height, sp->sectnum, u->tgt_sp->x, u->tgt_sp->y, SPRITEp_UPPER(u->tgt_sp), u->tgt_sp->sectnum))
        return TRUE;
    else
        return FALSE;
}

int
CanHitPlayer(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP, hp;
    hitdata_t hitinfo;
    int xvect,yvect,zvect;
    short ang;
    // if actor can still see the player
    int zhs, zhh;

    //if (FAF_Sector(sp->sectnum))
    //    return(TRUE);

    zhs = sp->z - DIV2(SPRITEp_SIZE_Z(sp));

    hp = u->tgt_sp;

    // get angle to target
    ang = getangle(hp->x - sp->x, hp->y - sp->y);

    // get x,yvect
    xvect = sintable[NORM_ANGLE(ang + 512)];
    yvect = sintable[NORM_ANGLE(ang)];

    // get zvect
    zhh = hp->z - DIV2(SPRITEp_SIZE_Z(hp));
    if (hp->x - sp->x != 0)
        zvect = xvect * ((zhh - zhs)/(hp->x - sp->x));
    else if (hp->y - sp->y != 0)
        zvect = yvect * ((zhh - zhs)/(hp->y - sp->y));
    else
        return FALSE;

    // so actors won't shoot straight up at you
    // need to be a bit of a distance away
    // before they have a valid shot
//    if (labs(zvect / FindDistance2D(hp->x - sp->x, hp->y - sp->y)) > 200)
//       return(FALSE);

    FAFhitscan(sp->x, sp->y, zhs, sp->sectnum,
               xvect,
               yvect,
               zvect,
               &hitinfo, CLIPMASK_MISSILE);

    if (hitinfo.sect < 0)
        return FALSE;

    if (hitinfo.sprite == u->tgt_sp - sprite)
        return TRUE;

    ////DSPRINTF(ds,"CanHit %s",ret ? "TRUE" : "FALSE");
    //MONO_PRINT(ds);

    return FALSE;
}

/*
  !AIC - Pick a nearby player to be the actors target
*/

int
DoActorPickClosePlayer(short SpriteNum)
{
    //extern short Zombies;
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int dist, near_dist = MAX_ACTIVE_RANGE, a,b,c;
    short pnum;
    PLAYERp pp;
    // if actor can still see the player
    int look_height = SPRITEp_TOS(sp);
    SWBOOL found = FALSE;
    int i,nexti;

    if (u->ID == ZOMBIE_RUN_R0 && gNet.MultiGameType == MULTI_GAME_COOPERATIVE)
        goto TARGETACTOR;

    // Set initial target to Player 0
    u->tgt_sp = Player->SpriteP;

    if (TEST(u->Flags2, SPR2_DONT_TARGET_OWNER))
    {
        TRAVERSE_CONNECT(pnum)
        {
            pp = &Player[pnum];

            if (sp->owner == pp->PlayerSprite)
                continue;

            u->tgt_sp = pp->SpriteP;
            break;
        }
    }

    // Set initial target to the closest player
    near_dist = MAX_ACTIVE_RANGE;
    TRAVERSE_CONNECT(pnum)
    {
        pp = &Player[pnum];

        // Zombies don't target their masters!
        if (TEST(u->Flags2, SPR2_DONT_TARGET_OWNER))
        {
            if (sp->owner == pp->PlayerSprite)
                continue;

            if (!PlayerTakeDamage(pp, SpriteNum))
                continue;

            // if co-op don't hurt teammate
            // if (gNet.MultiGameType == MULTI_GAME_COOPERATIVE && !gNet.HurtTeammate && u->spal == User[pp->PlayerSprite]->spal)
            //    continue;
        }

        DISTANCE(sp->x, sp->y, pp->posx, pp->posy, dist, a, b, c);

        if (dist < near_dist)
        {
            near_dist = dist;
            u->tgt_sp = pp->SpriteP;
        }
    }

    // see if you can find someone close that you can SEE
    near_dist = MAX_ACTIVE_RANGE;
    found = FALSE;
    TRAVERSE_CONNECT(pnum)
    {
        pp = &Player[pnum];

        // Zombies don't target their masters!
        if (TEST(u->Flags2, SPR2_DONT_TARGET_OWNER))
        {
            if (sp->owner == pp->PlayerSprite)
                continue;

            if (!PlayerTakeDamage(pp, SpriteNum))
                continue;

            // if co-op don't hurt teammate
            //if (gNet.MultiGameType == MULTI_GAME_COOPERATIVE && !gNet.HurtTeammate && u->spal == User[pp->PlayerSprite]->spal)
            //    continue;
        }

        DISTANCE(sp->x, sp->y, pp->posx, pp->posy, dist, a, b, c);

        //SWBOOL ICanSee = FAFcansee(sp->x, sp->y, look_height, sp->sectnum, pp->SpriteP->x, pp->SpriteP->y, SPRITEp_UPPER(pp->SpriteP), pp->SpriteP->sectnum);
        if (dist < near_dist && FAFcansee(sp->x, sp->y, look_height, sp->sectnum, pp->SpriteP->x, pp->SpriteP->y, SPRITEp_UPPER(pp->SpriteP), pp->SpriteP->sectnum))
        {
            near_dist = dist;
            u->tgt_sp = pp->SpriteP;
            found = TRUE;
        }
    }

TARGETACTOR:
    // this is only for Zombies right now
    // zombie target other actors
    if (!found && TEST(u->Flags2, SPR2_DONT_TARGET_OWNER))
    {
        near_dist = MAX_ACTIVE_RANGE;
        TRAVERSE_SPRITE_STAT(headspritestat[STAT_ENEMY], i, nexti)
        {
            if (i == SpriteNum)
                continue;

            if (TEST(User[i]->Flags, SPR_SUICIDE|SPR_DEAD))
                continue;

            DISTANCE(sp->x, sp->y, sprite[i].x, sprite[i].y, dist, a, b, c);

            if (dist < near_dist && FAFcansee(sp->x, sp->y, look_height, sp->sectnum, sprite[i].x, sprite[i].y, SPRITEp_UPPER(&sprite[i]), sprite[i].sectnum))
            {
                near_dist = dist;
                u->tgt_sp = &sprite[i];
            }
        }
    }

    return 0;
}

int
GetPlayerSpriteNum(short SpriteNum)
{
    USERp u = User[SpriteNum];
    short pnum;
    PLAYERp pp;

    TRAVERSE_CONNECT(pnum)
    {
        pp = &Player[pnum];

        if (pp->SpriteP == u->tgt_sp)
        {
            return pp->PlayerSprite;
        }
    }
    return 0;
}

int
CloseRangeDist(SPRITEp sp1, SPRITEp sp2)
{
    int clip1 = sp1->clipdist;
    int clip2 = sp2->clipdist;

    // add clip boxes and a fudge factor
#define DIST_CLOSE_RANGE 400

    return (clip1<<2) + (clip2<<2) + DIST_CLOSE_RANGE;
}

int DoActorOperate(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    short nearsector, nearwall, nearsprite;
    int nearhitdist;
    int z[2];
    unsigned int i;

    if (u->ID == HORNET_RUN_R0 || u->ID == EEL_RUN_R0 || u->ID == BUNNY_RUN_R0)
        return FALSE;

    if (u->Rot == u->ActorActionSet->Sit || u->Rot == u->ActorActionSet->Stand)
        return FALSE;

    if ((u->WaitTics -= ACTORMOVETICS) > 0)
        return FALSE;

    //DSPRINTF(ds,"sp->x = %ld, sp->y = %ld, sp->sector = %d, tp->x = %ld, tp->y = %ld, tp->ang = %d\n",sp->x,sp->y,sp->sectnum,tpoint->x,tpoint->y,tpoint->ang);
    //MONO_PRINT(ds);

    z[0] = sp->z - SPRITEp_SIZE_Z(sp) + Z(5);
    z[1] = sp->z - DIV2(SPRITEp_SIZE_Z(sp));

    for (i = 0; i < SIZ(z); i++)
    {
        neartag(sp->x, sp->y, z[i], sp->sectnum, sp->ang,
                &nearsector, &nearwall, &nearsprite,
                &nearhitdist, 1024L, NTAG_SEARCH_LO_HI, NULL);

        //DSPRINTF(ds,"nearsector = %d, nearwall = %d, nearsprite = %d hitdist == %ld\n",nearsector,nearwall,nearsprite,nearhitdist);
        //MONO_PRINT(ds);

#if 0  // Actors don't hit switches on a whim
        if (nearsprite >= 0 && nearhitdist < 1024)
        {
            if (OperateSprite(nearsprite, FALSE))
            {
                u->WaitTics = 2 * 120;

                NewStateGroup(SpriteNum, u->ActorActionSet->Stand);
            }
        }
#endif

    }

    if (nearsector >= 0 && nearhitdist < 1024)
    {
        if (OperateSector(nearsector, FALSE))
        {
            u->WaitTics = 2 * 120;

            NewStateGroup(SpriteNum, u->ActorActionSet->Sit);
        }
    }

    if (nearwall >= 0 && nearhitdist < 1024)
    {
        if (OperateWall(nearwall, FALSE))
        {
            u->WaitTics = 2 * 120;

            NewStateGroup(SpriteNum, u->ActorActionSet->Stand);
        }
    }
    return TRUE;

}

DECISION GenericFlaming[] =
{
    {30, InitActorAttack},
    {512, InitActorRunToward},
    {1024, InitActorRunAway},
};

/*
 !AIC KEY - This routine decides what the actor will do next.  It is not called
 every time through the loop.  This would be too slow.  It is only called when
 the actor needs to know what to do next such as running into something or being
 targeted.  It makes decisions based on the distance and viewablity of its target
 (u->tgt_sp).  When it figures out the situatation with its target it calls
 ChooseAction which does a random table lookup to decide what action to initialize.
 Once this action is initialized it will be called until it can't figure out what to
 do anymore and then this routine is called again.
*/

ANIMATORp
DoActorActionDecide(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int dist;
    ANIMATORp action;
    USERp pu=NULL;
    SWBOOL ICanSee=FALSE;

    // REMINDER: This function is not even called if SpriteControl doesn't let
    // it get called

    ASSERT(u->Personality);

    u->Dist = 0;
    action = InitActorDecide;

    if (TEST(u->Flags, SPR_JUMPING | SPR_FALLING))
    {
        //CON_Message("Jumping or falling");
        return action;
    }

    // everybody on fire acts like this
    if (ActorFlaming(SpriteNum))
    {
        action = ChooseAction(&GenericFlaming[0]);
        //CON_Message("On Fire");
        return action;
    }

    ICanSee = CanSeePlayer(SpriteNum);  // Only need to call once
    // But need the result multiple times

    // !AIC KEY - If aware of player - var is changed in SpriteControl
    if (TEST(u->Flags, SPR_ACTIVE))
    {
        // CODE BELOW = CRAP, DON'T USE IT OR IT MAKES ACTORS NOT ANIMATE SOMETIMES!!!!!
        // If target was actor, retarget to player it actor died
        // or just randomly give the target actor a break
        //if ((User[u->tgt_sp-sprite] &&
        //    User[u->tgt_sp-sprite]->Health <= 0) || RANDOM_RANGE(1000) > 950)
        //    {
        //    DoActorPickClosePlayer(SpriteNum);
        //    InitActorReposition(SpriteNum);
        //    return(action);
        //    }

        // Try to operate stuff
        DoActorOperate(SpriteNum);

        // if far enough away and cannot see the player
        dist = Distance(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y);

        if (dist > 30000 && !ICanSee)
        {
            // Enemy goes inactive - he is still allowed to roam about for about
            // 5 seconds trying to find another player before his active_range is
            // bumped down
            SetEnemyInactive(SpriteNum);

            // You've lost the player - now decide what to do
            action = ChooseAction(u->Personality->LostTarget);
            //CON_Message("LostTarget");
            return action;
        }

        pu = User[GetPlayerSpriteNum(SpriteNum)];
        // check for short range attack possibility
        if ((dist < CloseRangeDist(sp, u->tgt_sp) && ICanSee) ||
            (pu && pu->WeaponNum == WPN_FIST && u->ID != RIPPER2_RUN_R0 && u->ID != RIPPER_RUN_R0))
        {
            if ((u->ID == COOLG_RUN_R0 && TEST(sp->cstat, CSTAT_SPRITE_TRANSLUCENT)) || TEST(sp->cstat, CSTAT_SPRITE_INVISIBLE))
                action = ChooseAction(u->Personality->Evasive);
            else
                action = ChooseAction(u->Personality->CloseRange);
            //CON_Message("CloseRange");
            return action;
        }

        // if player is facing me and I'm being attacked
        if (FACING(sp, u->tgt_sp) && TEST(u->Flags, SPR_ATTACKED) && ICanSee)
        {
            // if I'm a target - at least one missile comming at me
            if (TEST(u->Flags, SPR_TARGETED))
            {
                // not going to evade, reset the target bit
                RESET(u->Flags, SPR_TARGETED);        // as far as actor
                // knows, its not a
                // target any more
                if (u->ActorActionSet->Duck && RANDOM_P2(1024<<8)>>8 < 100)
                    action = InitActorDuck;
                else
                {
                    if ((u->ID == COOLG_RUN_R0 && TEST(sp->cstat, CSTAT_SPRITE_TRANSLUCENT)) || TEST(sp->cstat, CSTAT_SPRITE_INVISIBLE))
                        action = ChooseAction(u->Personality->Evasive);
                    else
                        action = ChooseAction(u->Personality->Battle);
                }
                //CON_Message("Battle 1");
                return action;
            }
            // if NOT a target - don't bother with evasive action and start
            // fighting
            else
            {
                if ((u->ID == COOLG_RUN_R0 && TEST(sp->cstat, CSTAT_SPRITE_TRANSLUCENT)) || TEST(sp->cstat, CSTAT_SPRITE_INVISIBLE))
                    action = ChooseAction(u->Personality->Evasive);
                else
                    action = ChooseAction(u->Personality->Battle);
                //CON_Message("Battle 2");
                return action;
            }

        }
        // if player is NOT facing me he is running or unaware of actor
        else if (ICanSee)
        {
            if ((u->ID == COOLG_RUN_R0 && TEST(sp->cstat, CSTAT_SPRITE_TRANSLUCENT)) || TEST(sp->cstat, CSTAT_SPRITE_INVISIBLE))
                action = ChooseAction(u->Personality->Evasive);
            else
                action = ChooseAction(u->Personality->Offense);
            //CON_Message("Offense");
            return action;
        }
        else
        {
            // You've lost the player - now decide what to do
            action = ChooseAction(u->Personality->LostTarget);
            //CON_Message("Close but cant see, LostTarget");
            return action;
        }
    }
    // Not active - not aware of player and cannot see him
    else
    {
        // try and find another player
        // pick a closeby player as the (new) target
        if (sp->hitag != TAG_SWARMSPOT)
            DoActorPickClosePlayer(SpriteNum);

        // if close by
        dist = Distance(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y);
        if (dist < 15000 || ICanSee)
        {
            if ((FACING(sp, u->tgt_sp) && dist < 10000) || ICanSee)
            {
                DoActorOperate(SpriteNum);

                // Don't let player completely sneek up behind you
                action = ChooseAction(u->Personality->Surprised);
                //CON_Message("Surprised");
                if (!u->DidAlert && ICanSee)
                {
                    DoActorNoise(InitActorAlertNoise, SpriteNum);
                    u->DidAlert = TRUE;
                }
                return action;

            }
            else
            {
                // Player has not seen actor, to be fair let him know actor
                // are there
                DoActorNoise(ChooseAction(u->Personality->Broadcast),SpriteNum);
                //CON_Message("Actor Noise");
                return action;
            }
        }
    }

    //CON_Message("Couldn't resolve decide, InitActorDecide");
    return action;
}

/*
  !AIC - Setup to do the decision
*/

int
InitActorDecide(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // NOTE: It is possible to overflow the stack with too many calls to this
    // routine
    // Should use:
    // u->ActorActionFunc = DoActorDecide;
    // Instead of calling this function direcly

    // MONO_PRINT(strcpy(ds,"Init Actor Stay Put"));

    u->ActorActionFunc = DoActorDecide;

    DoActorDecide(SpriteNum);

    return 0;
}

int
DoActorDecide(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    ANIMATORp actor_action;

    // DoActorTest(SpriteNum);

    // See what to do next
    actor_action = DoActorActionDecide(SpriteNum);

    // Fix for the GenericFlaming bug for actors that don't have attack states
    if (actor_action == InitActorAttack && u->WeaponNum == 0)
        return 0;   // Just let the actor do as it was doing before in this case

    // zombie is attacking a player
    if (actor_action == InitActorAttack && u->ID == ZOMBIE_RUN_R0 && User[u->tgt_sp-sprite]->PlayerP)
    {
        // Don't let zombies shoot at master
        if (sp->owner == (u->tgt_sp - sprite))
            return 0;

        // if this player cannot take damage from this zombie(weapon) return out
        if (!PlayerTakeDamage(User[u->tgt_sp-sprite]->PlayerP, SpriteNum))
            return 0;
    }

    ASSERT(actor_action != NULL);

    if (actor_action != InitActorDecide)
    {
        // NOT staying put
        (*actor_action)(SpriteNum);
        //CON_Message("DoActorDecide: NOT Staying put");
    }
    else
    {
        // Actually staying put
        NewStateGroup(SpriteNum, u->ActorActionSet->Stand);
        //CON_Message("DoActorDecide: Staying put");
    }

    return 0;
}

int
InitActorAlertNoise(short SpriteNum)
{
    USERp u = User[SpriteNum];
    // MONO_PRINT(strcpy(ds,"Init Actor Threat Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorAmbientNoise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorAttackNoise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorPainNoise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorDieNoise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorExtra1Noise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorExtra2Noise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorExtra3Noise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorExtra4Noise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorExtra5Noise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorExtra6Noise(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // MONO_PRINT(strcpy(ds,"Init Actor Move Noise"));

    // make some sort of noise here

    // InitActorDecide(SpriteNum);
    u->ActorActionFunc = DoActorDecide;

    return 0;
}

/*
  !AIC KEY - Routines handle moving toward the player.
*/

int
InitActorMoveCloser(short SpriteNum)
{
    USERp u = User[SpriteNum];

    //MONO_PRINT("Init Actor Move Closer\n");

    u->ActorActionFunc = DoActorMoveCloser;

    if (u->Rot != u->ActorActionSet->Run)
        NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorCantMoveCloser(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    //MONO_PRINT("Can't move closer\n");

    u->track = FindTrackToPlayer(u);

    if (u->track >= 0)
    {
        sp->ang = getangle((Track[u->track].TrackPoint + u->point)->x - sp->x, (Track[u->track].TrackPoint + u->point)->y - sp->y);

        DoActorSetSpeed(SpriteNum, MID_SPEED);
        SET(u->Flags, SPR_FIND_PLAYER);

        u->ActorActionFunc = DoActorDecide;
        NewStateGroup(SpriteNum, u->ActorActionSet->Run);
        //MONO_PRINT("Trying to get to the track point\n");
    }
    else
    {
        int InitActorReposition(short SpriteNum);

        // Try to move closer
        //MONO_PRINT("Move Closer - Trying to move around\n");

        InitActorReposition(SpriteNum);
    }
    return 0;
}

int
DoActorMoveCloser(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int nx, ny;

    nx = sp->xvel * (int) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = sp->xvel * (int) sintable[sp->ang] >> 14;

    // if cannot move the sprite
    if (!move_actor(SpriteNum, nx, ny, 0L))
    {
        DebugMoveHit(SpriteNum);

        if (ActorMoveHitReact(SpriteNum))
            return 0;

        DoActorCantMoveCloser(SpriteNum);
        return 0;
    }

    // Do a noise if ok
    DoActorNoise(ChooseAction(u->Personality->Broadcast),SpriteNum);

#if 0
    // evasion if targeted
    if (TEST(u->Flags, SPR_TARGETED))
    {
        ANIMATORp action;

        action = ChooseAction(u->Personality->Evasive);
        if (action)
        {
            (*action)(SpriteNum);
            return 0;
        }
    }
#endif

    // after moving a ways check to see if player is still in sight
    if (u->DistCheck > 550)
    {
        u->DistCheck = 0;

        // If player moved out of sight
        if (!CanSeePlayer(SpriteNum))
        {
            // stay put and choose another option
            InitActorDecide(SpriteNum);
            return 0;
        }
        else
        {
            // turn to face player
            sp->ang = getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y);
        }
    }

    // Should be a random value test
    if (u->Dist > 512 * 3)
    {
        InitActorDecide(SpriteNum);
    }

    return 0;

}

/*
  !AIC - Find tracks of different types.  Toward target, away from target, etc.
*/

short
FindTrackToPlayer(USERp u)
{
    SPRITEp sp = u->SpriteP;

    short point, track_dir, track;
    short i, *type, size;
    int zdiff;

    static short PlayerAbove[] =
    {
        BIT(TT_LADDER),
        BIT(TT_STAIRS),
        BIT(TT_JUMP_UP),
        BIT(TT_TRAVERSE),
        BIT(TT_OPERATE),
        BIT(TT_SCAN)
    };

    static short PlayerBelow[] =
    {
        BIT(TT_JUMP_DOWN),
        BIT(TT_STAIRS),
        BIT(TT_TRAVERSE),
        BIT(TT_OPERATE),
        BIT(TT_SCAN)
    };

    static short PlayerOnLevel[] =
    {
        BIT(TT_DUCK_N_SHOOT),
        BIT(TT_HIDE_N_SHOOT),
        BIT(TT_TRAVERSE),
        BIT(TT_EXIT),
        BIT(TT_OPERATE),
        BIT(TT_SCAN)
    };

    //MONO_PRINT("FindTrackToPlayer\n");

    zdiff = SPRITEp_UPPER(u->tgt_sp) - (sp->z - SPRITEp_SIZE_Z(sp) + Z(8));

    if (labs(zdiff) <= Z(20))
    {
        type = PlayerOnLevel;
        size = SIZ(PlayerOnLevel);
    }
    else
    {
        if (zdiff < 0)
        {
            type = PlayerAbove;
            size = SIZ(PlayerAbove);
        }
        else
        {
            type = PlayerBelow;
            size = SIZ(PlayerBelow);
        }
    }

    for (i = 0; i < size; i++)
    {
        track = ActorFindTrack(u->SpriteNum, 1, type[i], &point, &track_dir);

        if (track >= 0)
        {
            u->point = point;
            u->track_dir = track_dir;
            SET(Track[track].flags, TF_TRACK_OCCUPIED);

            ////DSPRINTF(ds, "Found Track To Player\n");
            //MONO_PRINT(ds);

            return track;
        }
    }

    return -1;

}

short
FindTrackAwayFromPlayer(USERp u)
{
    short point, track_dir, track;
    unsigned int i;

    static short RunAwayTracks[] =
    {
        BIT(TT_EXIT),
        BIT(TT_LADDER),
        BIT(TT_TRAVERSE),
        BIT(TT_STAIRS),
        BIT(TT_JUMP_UP),
        BIT(TT_JUMP_DOWN),
        BIT(TT_DUCK_N_SHOOT),
        BIT(TT_HIDE_N_SHOOT),
        BIT(TT_OPERATE),
        BIT(TT_SCAN)
    };

    //MONO_PRINT("FindTrackAwayFromPlayer\n");

    for (i = 0; i < SIZ(RunAwayTracks); i++)
    {
        track = ActorFindTrack(u->SpriteNum, -1, RunAwayTracks[i], &point, &track_dir);

        if (track >= 0)
        {
            u->point = point;
            u->track_dir = track_dir;
            SET(Track[track].flags, TF_TRACK_OCCUPIED);

            ////DSPRINTF(ds, "Found Run Away Track\n");
            //MONO_PRINT(ds);

            return track;
        }
        //MONO_PRINT("Did not find a run away track!\n");
    }

    return -1;

}

short
FindWanderTrack(USERp u)
{
    short point, track_dir, track;
    unsigned int i;

    static short WanderTracks[] =
    {
        BIT(TT_DUCK_N_SHOOT),
        BIT(TT_HIDE_N_SHOOT),
        BIT(TT_WANDER),
        BIT(TT_JUMP_DOWN),
        BIT(TT_JUMP_UP),
        BIT(TT_TRAVERSE),
        BIT(TT_STAIRS),
        BIT(TT_LADDER),
        BIT(TT_EXIT),
        BIT(TT_OPERATE)
    };

    //MONO_PRINT("FindWanderTrack\n");

    for (i = 0; i < SIZ(WanderTracks); i++)
    {
        track = ActorFindTrack(u->SpriteNum, -1, WanderTracks[i], &point, &track_dir);

        if (track >= 0)
        {
            u->point = point;
            u->track_dir = track_dir;
            SET(Track[track].flags, TF_TRACK_OCCUPIED);

            return track;
        }
    }

    return -1;
}

int
InitActorRunAway(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    //MONO_PRINT("Init Actor RunAway\n");

    u->ActorActionFunc = DoActorDecide;
    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    u->track = FindTrackAwayFromPlayer(u);

    if (u->track >= 0)
    {
        sp->ang = NORM_ANGLE(getangle((Track[u->track].TrackPoint + u->point)->x - sp->x, (Track[u->track].TrackPoint + u->point)->y - sp->y));
        DoActorSetSpeed(SpriteNum, FAST_SPEED);
        SET(u->Flags, SPR_RUN_AWAY);
        //MONO_PRINT("Actor running away on track\n");
    }
    else
    {
        SET(u->Flags, SPR_RUN_AWAY);
        InitActorReposition(SpriteNum);
        ////DSPRINTF(ds, "Actor RunAway\n");
        //MONO_PRINT(ds);
    }

    return 0;
}

int
InitActorRunToward(short SpriteNum)
{
    USERp u = User[SpriteNum];

    //MONO_PRINT("InitActorRunToward\n");

    u->ActorActionFunc = DoActorDecide;
    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    InitActorReposition(SpriteNum);
    DoActorSetSpeed(SpriteNum, FAST_SPEED);

    return 0;
}

/*
  !AIC - Where actors do their attacks.  There is some special case code throughout
  these.  Both close and long range attacks are handled here by transitioning to
  the correct attack state.
*/

int
InitActorAttack(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    // zombie is attacking a player
    if (u->ID == ZOMBIE_RUN_R0 && User[u->tgt_sp-sprite]->PlayerP)
    {
        // Don't let zombies shoot at master
        if (sp->owner == (u->tgt_sp - sprite))
            return 0;

        // if this player cannot take damage from this zombie(weapon) return out
        if (!PlayerTakeDamage(User[u->tgt_sp-sprite]->PlayerP, SpriteNum))
            return 0;
    }

    if (TEST(sprite[u->tgt_sp-sprite].cstat, CSTAT_SPRITE_TRANSLUCENT))
    {
        InitActorRunAway(SpriteNum);
        return 0;
    }

    if (User[u->tgt_sp-sprite] &&
        User[u->tgt_sp-sprite]->Health <= 0)
    {
        DoActorPickClosePlayer(SpriteNum);
        InitActorReposition(SpriteNum);
        return 0;
    }

    if (!CanHitPlayer(SpriteNum))
    {
        InitActorReposition(SpriteNum);
        return 0;
    }

    // if the guy you are after is dead, look for another and
    // reposition
    if (User[u->tgt_sp-sprite] &&
        User[u->tgt_sp-sprite]->PlayerP &&
        TEST(User[u->tgt_sp-sprite]->PlayerP->Flags, PF_DEAD))
    {
        DoActorPickClosePlayer(SpriteNum);
        InitActorReposition(SpriteNum);
        return 0;
    }

    u->ActorActionFunc = DoActorAttack;

    // move into standing frame
    //NewStateGroup(SpriteNum, u->ActorActionSet->Stand);

    // face player when attacking
    sp->ang = NORM_ANGLE(getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y));

    // If it's your own kind, lay off!
    if (u->ID == User[u->tgt_sp - sprite]->ID && !User[u->tgt_sp - sprite]->PlayerP)
    {
        InitActorRunAway(SpriteNum);
        return 0;
    }

#if 0
    // if low on health determine if needs to run away
    if (u->Health < 26)
    {
        if (CHOOSE2(AttackOrRun))
        {
            InitActorRunAway(SpriteNum);

            // could do a FindHealth here

            return 0;
        }

    }
#endif

    // Hari Kari for Ninja's
    if (u->ActorActionSet->Death2)
    {
        //#define SUICIDE_HEALTH_VALUE 26
#define SUICIDE_HEALTH_VALUE 38
        //#define SUICIDE_HEALTH_VALUE 50

        if (u->Health < SUICIDE_HEALTH_VALUE)
        {
            if (CHOOSE2(100))
            {
                u->ActorActionFunc = DoActorDecide;
                NewStateGroup(SpriteNum, u->ActorActionSet->Death2);
                return 0;
            }
        }
    }

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorAttack(short SpriteNum)
{
    USERp u = User[SpriteNum],pu;
    SPRITEp sp = User[SpriteNum]->SpriteP;
    short rand_num;
    int dist,a,b,c;

    DoActorNoise(ChooseAction(u->Personality->Broadcast),SpriteNum);

    DISTANCE(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y, dist, a, b, c);

    pu = User[GetPlayerSpriteNum(SpriteNum)];
    if ((u->ActorActionSet->CloseAttack[0] && dist < CloseRangeDist(sp, u->tgt_sp)) ||
        (pu && pu->WeaponNum == WPN_FIST))      // JBF: added null check
    {
        rand_num = ChooseActionNumber(u->ActorActionSet->CloseAttackPercent);

        NewStateGroup(SpriteNum, u->ActorActionSet->CloseAttack[rand_num]);
    }
    else
    {
        ASSERT(u->WeaponNum != 0);

        rand_num = ChooseActionNumber(u->ActorActionSet->AttackPercent);

        ASSERT(rand_num < u->WeaponNum);

        NewStateGroup(SpriteNum, u->ActorActionSet->Attack[rand_num]);
        u->ActorActionFunc = DoActorDecide;
    }

    //u->ActorActionFunc = DoActorDecide;

    return 0;
}

int
InitActorEvade(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    //MONO_PRINT("Init Actor Evade\n");

    // Evade is same thing as run away except when you get to the end of the
    // track
    // you stop and take up the fight again.

    u->ActorActionFunc = DoActorDecide;
    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    u->track = FindTrackAwayFromPlayer(u);

    if (u->track >= 0)
    {
        sp->ang = NORM_ANGLE(getangle((Track[u->track].TrackPoint + u->point)->x - sp->x, (Track[u->track].TrackPoint + u->point)->y - sp->y));
        DoActorSetSpeed(SpriteNum, FAST_SPEED);
        // NOT doing a RUN_AWAY
        RESET(u->Flags, SPR_RUN_AWAY);
    }

    return 0;
}

int
InitActorWanderAround(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    //DSPRINTF(ds, "InitActorWanderAround\n");
    //MONO_PRINT(ds);

    u->ActorActionFunc = DoActorDecide;
    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    DoActorPickClosePlayer(SpriteNum);

    u->track = FindWanderTrack(u);

    if (u->track >= 0)
    {
        sp->ang = getangle((Track[u->track].TrackPoint + u->point)->x - sp->x, (Track[u->track].TrackPoint + u->point)->y - sp->y);
        DoActorSetSpeed(SpriteNum, NORM_SPEED);
    }

    return 0;
}

int
InitActorFindPlayer(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int DoActorFindPlayer(short SpriteNum);

    //DSPRINTF(ds, "InitActorFindPlayer\n");
    //MONO_PRINT(ds);

    u->ActorActionFunc = DoActorDecide;
    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    u->track = FindTrackToPlayer(u);

    if (u->track >= 0)
    {
        sp->ang = getangle((Track[u->track].TrackPoint + u->point)->x - sp->x, (Track[u->track].TrackPoint + u->point)->y - sp->y);
        DoActorSetSpeed(SpriteNum, MID_SPEED);
        SET(u->Flags, SPR_FIND_PLAYER);

        u->ActorActionFunc = DoActorDecide;
        NewStateGroup(SpriteNum, u->ActorActionSet->Run);
    }
    else
    {
        InitActorReposition(SpriteNum);
    }

    return 0;
}

int
InitActorDuck(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    short dist;

//    MONO_PRINT(strcpy(ds, "Init Actor Duck"));

    if (!u->ActorActionSet->Duck)
    {
        u->ActorActionFunc = DoActorDecide;
        return 0;
    }

    u->ActorActionFunc = DoActorDuck;
    NewStateGroup(SpriteNum, u->ActorActionSet->Duck);

    dist = Distance(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y);

    if (dist > 8000)
    {
        u->WaitTics = 190;
    }
    else
    {
        //u->WaitTics = 120;
        u->WaitTics = 60;
    }

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorDuck(short SpriteNum)
{
    USERp u = User[SpriteNum];

    if ((u->WaitTics -= ACTORMOVETICS) < 0)
    {
        NewStateGroup(SpriteNum, u->ActorActionSet->Rise);
        u->ActorActionFunc = DoActorDecide;
        // InitActorDecide(SpriteNum);
        RESET(u->Flags, SPR_TARGETED);
    }

    return 0;
}

int
DoActorMoveJump(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int nx, ny;

    // Move while jumping

    nx = sp->xvel * (int) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = sp->xvel * (int) sintable[sp->ang] >> 14;

    move_actor(SpriteNum, nx, ny, 0L);

    if (!TEST(u->Flags, SPR_JUMPING|SPR_FALLING))
    {
        InitActorDecide(SpriteNum);
    }

    return 0;
}

int move_scan(short SpriteNum, short ang, int dist, int *stopx, int *stopy, int *stopz, short *stopsect)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    int nx,ny;
    uint32_t cliptype = CLIPMASK_ACTOR;
    int ret;

    short sang,ss;
    int x, y, z, loz, hiz;
    SPRITEp lo_sp, hi_sp;
    SECTORp lo_sectp, hi_sectp;

    // moves out a bit but keeps the sprites original postion/sector.

    // save off position info
    x = sp->x;
    y = sp->y;
    z = sp->z;
    sang = sp->ang;
    loz = u->loz;
    hiz = u->hiz;
    lo_sp = u->lo_sp;
    hi_sp = u->hi_sp;
    lo_sectp = u->lo_sectp;
    hi_sectp = u->hi_sectp;
    ss = sp->sectnum;

    // do the move
    sp->ang = ang;
    nx = (dist) * (int) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = (dist) * (int) sintable[sp->ang] >> 14;

    ret = move_sprite(SpriteNum, nx, ny, 0, u->ceiling_dist, u->floor_dist, cliptype, 1);
    // move_sprite DOES do a getzrange point?

    // should I look down with a FAFgetzrange to see where I am?

    // remember where it stopped
    *stopx = sp->x;
    *stopy = sp->y;
    *stopz = sp->z;
    *stopsect = sp->sectnum;

    // reset position information
    sp->x = x;
    sp->y = y;
    sp->z = z;
    sp->ang = sang;
    u->loz = loz;
    u->hiz = hiz;
    u->lo_sp = lo_sp;
    u->hi_sp = hi_sp;
    u->lo_sectp = lo_sectp;
    u->hi_sectp = hi_sectp;
    changespritesect(SpriteNum, ss);

    return ret;
}

#define TOWARD 1
#define AWAY -1

int
FindNewAngle(short SpriteNum, signed char dir, int DistToMove)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    SWBOOL DropAhead(short SpriteNum, short min_height);

    static short toward_angle_delta[4][9] =
    {
        { -160, -384, 160, 384, -256, 256, -512, 512, -99},
        { -384, -160, 384, 160, -256, 256, -512, 512, -99},
        { 160, 384, -160, -384, 256, -256, 512, -512, -99},
        { 384, 160, -384, -160, 256, -256, 512, -512, -99}
    };

    static short away_angle_delta[4][8] =
    {
        { -768, 768, -640, 640, -896, 896, 1024, -99},
        { 768, -768, 640, -640, -896, 896, 1024, -99},
        { 896, -896, -768, 768, -640, 640, 1024, -99},
        { 896, -896, 768, -768, 640, -640, 1024, -99}
    };

    int16_t* adp = NULL;

    short new_ang, oang;
    short save_ang = -1;
    unsigned short ret;
    int set;

    int dist, stopx, stopy, stopz;
    short stopsect;
    // start out with mininum distance that will be accepted as a move
    int save_dist = 500;

    // if on fire, run shorter distances
    if (ActorFlaming(SpriteNum))
        DistToMove = DIV4(DistToMove) + DIV8(DistToMove);

    // Find angle to from the player
    oang = NORM_ANGLE(getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y));

    // choose a random angle array
    switch (dir)
    {
    case TOWARD:
        set = RANDOM_P2(4<<8)>>8;
        adp = &toward_angle_delta[set][0];
        break;
    case AWAY:
        set = RANDOM_P2(4<<8)>>8;
        if (CanHitPlayer(SpriteNum))
        {
            adp = &toward_angle_delta[set][0];
        }
        else
        {
            adp = &away_angle_delta[set][0];
        }
        break;
    default:
        printf("FindNewAngle called with dir=%d!\n",dir);
        return 0;
    }

    for (; *adp != -99; adp++)
    {
        new_ang = NORM_ANGLE(oang + *adp);

#if 1
        // look directly ahead for a ledge
        if (!TEST(u->Flags, SPR_NO_SCAREDZ | SPR_JUMPING | SPR_FALLING | SPR_SWIMMING | SPR_DEAD))
        {
            sp->ang = new_ang;
            if (DropAhead(SpriteNum, u->lo_step))
            {
                sp->ang = oang;
                continue;
            }
            sp->ang = oang;
        }
#endif

        // check to see how far we can move
        ret = move_scan(SpriteNum, new_ang, DistToMove, &stopx, &stopy, &stopz, &stopsect);

        if (ret == 0)
        {
            // cleanly moved in new direction without hitting something
            u->TargetDist = Distance(sp->x, sp->y, stopx, stopy);
            return new_ang;
        }
        else
        {
            // hit something
            dist = Distance(sp->x, sp->y, stopx, stopy);

            if (dist > save_dist)
            {
                save_ang = new_ang;
                save_dist = dist;
            }
        }
    }

    if (save_ang != -1)
    {
        u->TargetDist = save_dist;

        // If actor moved to the TargetDist it would look like he was running
        // into things.

        // To keep this from happening make the TargetDist is less than the
        // point you would hit something

        if (u->TargetDist > 4000)
            u->TargetDist -= 3500;

        sp->ang = save_ang;
        return save_ang;
    }

    return -1;
}

/*

  !AIC KEY - Reposition code is called throughout this file.  What this does is
  pick a new direction close to the target direction (or away from the target
  direction if running away) and a distance to move in and tries to move there
  with move_scan(). If it hits something it will try again.  No movement is
  actually acomplished here. This is just testing for clear paths to move in.
  Location variables that are changed are saved and reset.  FindNewAngle() and
  move_scan() are two routines (above) that go with this.  This is definately
  not called every time through the loop.  It would be majorly slow.

*/

#define REPOSITION 2

#if REPOSITION == 2
int
InitActorReposition(short SpriteNum)
{
    int DoActorReposition(short SpriteNum);
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    short ang;
    int rnum;
    int dist;

    static int AwayDist[8] =
    {
        17000,
        20000,
        26000,
        26000,
        26000,
        32000,
        32000,
        42000
    };

    static int TowardDist[8] =
    {
        10000,
        15000,
        20000,
        20000,
        25000,
        30000,
        35000,
        40000
    };

    static int PlayerDist[8] =
    {
        2000,
        3000,
        3000,
        5000,
        5000,
        5000,
        9000,
        9000
    };

    //MONO_PRINT("InitActorReposition\n");

    u->Dist = 0;

    rnum = RANDOM_P2(8<<8)>>8;
    dist = Distance(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y);

    if (dist < PlayerDist[rnum] || TEST(u->Flags, SPR_RUN_AWAY))
    {
        rnum = RANDOM_P2(8<<8)>>8;
        ang = FindNewAngle(SpriteNum, AWAY, AwayDist[rnum]);
        if (ang == -1)
        {
            u->Vis = 8;
            InitActorPause(SpriteNum);
            return 0;
        }

        sp->ang = ang;
        DoActorSetSpeed(SpriteNum, FAST_SPEED);
        RESET(u->Flags, SPR_RUN_AWAY);
    }
    else
    {
        // try to move toward player
        rnum = RANDOM_P2(8<<8)>>8;
        ang = FindNewAngle(SpriteNum, TOWARD, TowardDist[rnum]);
        if (ang == -1)
        {
            // try to move away from player
            rnum = RANDOM_P2(8<<8)>>8;
            ang = FindNewAngle(SpriteNum, AWAY, AwayDist[rnum]);
            if (ang == -1)
            {
                u->Vis = 8;
                InitActorPause(SpriteNum);
                return 0;
            }
        }
        else
        {
            // pick random speed to move toward the player
            if (RANDOM_P2(1024) < 512)
                DoActorSetSpeed(SpriteNum, NORM_SPEED);
            else
                DoActorSetSpeed(SpriteNum, MID_SPEED);
        }

        sp->ang = ang;
    }

    u->ActorActionFunc = DoActorReposition;
    if (!TEST(u->Flags, SPR_SWIMMING))
        NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorReposition(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int nx, ny;

    nx = sp->xvel * (int) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = sp->xvel * (int) sintable[sp->ang] >> 14;

    // still might hit something and have to handle it.
    if (!move_actor(SpriteNum, nx, ny, 0L))
    {
        if (ActorMoveHitReact(SpriteNum))
            return 0;

        u->Vis = 6;
        InitActorPause(SpriteNum);
        return 0;
    }

    // if close to target distance do a Decision again
    if (u->TargetDist < 50)
    {
        InitActorDecide(SpriteNum);
    }

    return 0;
}

int
InitActorPause(short SpriteNum)
{
    USERp u = User[SpriteNum];

    u->ActorActionFunc = DoActorPause;

    // !JIM! This makes actors not animate
    //if (!TEST(u->Flags, SPR_SWIMMING))
    //NewStateGroup(SpriteNum, u->ActorActionSet->Stand);

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorPause(short SpriteNum)
{
    USERp u = User[SpriteNum];

    // Using Vis instead of WaitTics, var name sucks, but it's the same type
    // WaitTics is used by too much other actor code and causes problems here
    if ((u->Vis -= ACTORMOVETICS) < 0)
    {
        u->ActorActionFunc = DoActorDecide;
        RESET(u->Flags, SPR_TARGETED);
    }

    return 0;
}
#endif

// not used - simplified version that is purely random that is easier to follow
// just leaving it here to look at
#if REPOSITION == 1
int
InitActorReposition(short SpriteNum)
{
    int DoActorReposition(short SpriteNum);
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    //MONO_PRINT("InitActorReposition\n");

    u->WaitTics = 8;
    sp->ang = RANDOM_P2(2048);

    u->ActorActionFunc = DoActorReposition;
    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorReposition(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int nx, ny;

    nx = sp->xvel * (int) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = sp->xvel * (int) sintable[sp->ang] >> 14;

    if (!move_actor(SpriteNum, nx, ny, 0L))
    {
        if (ActorMoveHitReact(SpriteNum))
            return 0;

        InitActorPause(SpriteNum);
        return 0;
    }

    return 0;
}

int
InitActorPause(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    //MONO_PRINT("InitActorPause\n");

    u->ActorActionFunc = DoActorPause;

    //NewStateGroup(SpriteNum, u->ActorActionSet->Stand);

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int
DoActorPause(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    if ((u->WaitTics -= ACTORMOVETICS) < 0)
    {
        u->ActorActionFunc = DoActorDecide;
        RESET(u->Flags, SPR_TARGETED);
    }

    return 0;
}
#endif

#include "saveable.h"

static saveable_code saveable_ai_code[] =
{
    SAVE_CODE(DebugMoveHit),
    SAVE_CODE(ActorMoveHitReact),
    SAVE_CODE(ActorFlaming),
    SAVE_CODE(DoActorSetSpeed),
    SAVE_CODE(ChooseAction),
    SAVE_CODE(ChooseActionNumber),
    SAVE_CODE(DoActorNoise),
    SAVE_CODE(CanSeePlayer),
    SAVE_CODE(CanHitPlayer),
    SAVE_CODE(DoActorPickClosePlayer),
    SAVE_CODE(GetPlayerSpriteNum),
    SAVE_CODE(CloseRangeDist),
    SAVE_CODE(DoActorOperate),
    SAVE_CODE(DoActorActionDecide),
    SAVE_CODE(InitActorDecide),
    SAVE_CODE(DoActorDecide),
    SAVE_CODE(InitActorAlertNoise),
    SAVE_CODE(InitActorAmbientNoise),
    SAVE_CODE(InitActorAttackNoise),
    SAVE_CODE(InitActorPainNoise),
    SAVE_CODE(InitActorDieNoise),
    SAVE_CODE(InitActorExtra1Noise),
    SAVE_CODE(InitActorExtra2Noise),
    SAVE_CODE(InitActorExtra3Noise),
    SAVE_CODE(InitActorExtra4Noise),
    SAVE_CODE(InitActorExtra5Noise),
    SAVE_CODE(InitActorExtra6Noise),
    SAVE_CODE(InitActorMoveCloser),
    SAVE_CODE(DoActorCantMoveCloser),
    SAVE_CODE(DoActorMoveCloser),
    SAVE_CODE(FindTrackToPlayer),
    SAVE_CODE(FindTrackAwayFromPlayer),
    SAVE_CODE(FindWanderTrack),
    SAVE_CODE(InitActorRunAway),
    SAVE_CODE(InitActorRunToward),
    SAVE_CODE(InitActorAttack),
    SAVE_CODE(DoActorAttack),
    SAVE_CODE(InitActorEvade),
    SAVE_CODE(InitActorWanderAround),
    SAVE_CODE(InitActorFindPlayer),
    SAVE_CODE(InitActorDuck),
    SAVE_CODE(DoActorDuck),
    SAVE_CODE(DoActorMoveJump),
    SAVE_CODE(FindNewAngle),
    SAVE_CODE(InitActorReposition),
    SAVE_CODE(DoActorReposition),
    SAVE_CODE(InitActorPause),
    SAVE_CODE(DoActorPause)
};

static saveable_data saveable_ai_data[] =
{
    SAVE_DATA(GenericFlaming)
};

saveable_module saveable_ai =
{
    // code
    saveable_ai_code,
    SIZ(saveable_ai_code),

    // data
    saveable_ai_data,
    SIZ(saveable_ai_data)
};


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#ifndef AI_H

#define AI_H

// Call functions based on a random range value
typedef struct
{
    short range;
    ANIMATORp action;
} DECISION, *DECISIONp;

// Personality structure
struct PERSONALITYstruct
{
    DECISIONp Battle;
    DECISIONp Offense;
    DECISIONp Broadcast;
    DECISIONp Surprised;
    DECISIONp Evasive;
    DECISIONp LostTarget;
    DECISIONp CloseRange;
    DECISIONp TouchTarget;
};

enum ActorStates { SLOW_SPEED, NORM_SPEED, MID_SPEED, FAST_SPEED, MAX_SPEED};

#define MAXATTRIBSNDS   11
typedef enum
{
    attr_ambient, attr_alert, attr_attack, attr_pain, attr_die,
    attr_extra1, attr_extra2, attr_extra3,attr_extra4,attr_extra5,
    attr_extra6
} ATTRIB_SNDS;

struct ATTRIBUTEstruct
{
    short Speed[MAX_SPEED];
    int8_t TicAdjust[MAX_SPEED];
    uint8_t MaxWeapons;
    /*ATTRIB_SNDS*/ int Sounds[MAXATTRIBSNDS];  // JBF: ATTRIB_SNDS? Somehow I don't think this is what was intended...
};

extern ATTRIBUTE DefaultAttrib;

// AI.C functions
void DebugMoveHit(short SpriteNum);
SWBOOL ActorMoveHitReact(short SpriteNum);
SWBOOL ActorFlaming(short SpriteNum);
void DoActorSetSpeed(short SpriteNum,uint8_t speed);
short ChooseActionNumber(short decision[]);
int DoActorNoise(ANIMATORp Action,short SpriteNum);
int CanSeePlayer(short SpriteNum);
int CanHitPlayer(short SpriteNum);
int DoActorPickClosePlayer(short SpriteNum);
int CloseRangeDist(SPRITEp sp1,SPRITEp sp2);
int InitActorDecide(short SpriteNum);
int DoActorDecide(short SpriteNum);
int InitActorAlertNoise(short SpriteNum);
int InitActorAmbientNoise(short SpriteNum);
int InitActorAttackNoise(short SpriteNum);
int InitActorPainNoise(short SpriteNum);
int InitActorDieNoise(short SpriteNum);
int InitActorExtra1Noise(short SpriteNum);
int InitActorExtra2Noise(short SpriteNum);
int InitActorExtra3Noise(short SpriteNum);
int InitActorExtra4Noise(short SpriteNum);
int InitActorExtra5Noise(short SpriteNum);
int InitActorExtra6Noise(short SpriteNum);
int InitActorMoveCloser(short SpriteNum);
int DoActorCantMoveCloser(short SpriteNum);
int DoActorMoveCloser(short SpriteNum);
short FindTrackToPlayer(USERp u);
short FindTrackAwayFromPlayer(USERp u);
short FindWanderTrack(USERp u);
int InitActorRunAway(short SpriteNum);
int InitActorRunToward(short SpriteNum);
int InitActorAttack(short SpriteNum);
int DoActorAttack(short SpriteNum);
int InitActorEvade(short SpriteNum);
int InitActorWanderAround(short SpriteNum);
int InitActorFindPlayer(short SpriteNum);
int InitActorDuck(short SpriteNum);
int DoActorDuck(short SpriteNum);
int DoActorMoveJump(short SpriteNum);
int move_scan(short SpriteNum,short ang,int dist,int *stopx,int *stopy,int *stopz,short *stopsect);
int FindNewAngle(short SpriteNum,signed char dir,int DistToMove);
int InitActorReposition(short SpriteNum);
int DoActorReposition(short SpriteNum);
int InitActorPause(short SpriteNum);
int DoActorPause(short SpriteNum);

/*
ANIMATOR
InitActorDecide,
InitActorMoveCloser,
InitActorAttack,
InitActorRunAway,
InitActorEvade,
InitActorWanderAround,
InitActorFindPlayer,
InitActorReposition,
InitActorPause,
InitActorDuck,
InitActorAmbientNoise,
InitActorAlertNoise,
InitActorAttackNoise,
InitActorPainNoise,
InitActorDieNoise,
InitActorExtra1Noise,
InitActorExtra2Noise,
InitActorExtra3Noise,
InitActorExtra4Noise,
InitActorExtra5Noise,
InitActorExtra6Noise;

ANIMATOR
DoActorDecide,
DoActorMoveCloser,
DoActorAttack,
DoActorRunAway,
DoActorWanderAround,
DoActorReposition,
DoActorPause,
DoActorDuck,
DoActorAmbientNoise,
DoActorAlertNoise,
DoActorAttackNoise,
DoActorPainNoise,
DoActorDieNoise,
DoActorExtra1Noise,
DoActorExtra2Noise,
DoActorExtra3Noise,
DoActorExtra4Noise,
DoActorExtra5Noise,
DoActorExtra6Noise;
*/

void DoActorSetSpeed(short SpriteNum, uint8_t speed);

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#ifdef AMBIENT_TABLE
#define AMB_ENTRY(name, diginame, ambient_flags, maxtics) {name, diginame, ambient_flags, maxtics},
#endif

#ifdef AMBIENT_ENUM
#define AMB_ENTRY(name, diginame, ambient_flags, maxtics) name,
#endif

// Ambient Flags, flags can be added whenever needed, up to 16 bits
#define AMB_NONE        0
#define AMB_FOLLOW      1       // 1 = Do coordinate updates on sound
// Use this only if the sprite won't be deleted soon
#define AMB_DOPPLER     4       // 1 = Don't use doppler pitch variance

#define AMB_PAN         8       // 1 = Don't do panning of sound
#define AMB_INTERMIT    32      // 1 = This is a non-looping intermittant sound

// Tic counts used for intermittent sounds
#define AMB_TICRATE  12     // 120/10 since it's only called 10x per second
#define AMB_NOTICS   0
#define AMB_5        5 *AMB_TICRATE
#define AMB_10       10*AMB_TICRATE      // AMB_TICRATE is the game's tic rate
#define AMB_15       15*AMB_TICRATE
#define AMB_20       20*AMB_TICRATE
#define AMB_30       30*AMB_TICRATE
#define AMB_45       45*AMB_TICRATE
#define AMB_60       60*AMB_TICRATE
#define AMB_120      120*AMB_TICRATE

// BUBBLES
AMB_ENTRY(0,     DIGI_BUBBLES,           AMB_PAN,       AMB_NOTICS)

// CRICKETS
AMB_ENTRY(1,     DIGI_CRICKETS,          AMB_PAN,       AMB_NOTICS)

// AMBIENT WATER DRIPPING IN CAVE
AMB_ENTRY(2,     DIGI_CAVEDRIP1,         AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(3,     DIGI_CAVEDRIP2,         AMB_INTERMIT,  AMB_10)
AMB_ENTRY(4,     DIGI_DRIP,              AMB_PAN|AMB_INTERMIT,  AMB_20)

// WATER FALL
AMB_ENTRY(5,     DIGI_WATERFALL1,        AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(6,     DIGI_WATERFALL2,        AMB_NONE,      AMB_NOTICS)

// WATER FLOWING
AMB_ENTRY(7,     DIGI_WATERFLOW1,        AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(8,     DIGI_WATERFLOW2,        AMB_NONE,      AMB_NOTICS)

// CRACKLING FIRE FOR CONTINUOUS BURN
AMB_ENTRY(9,     DIGI_FIRE1,             AMB_NONE,      AMB_NOTICS)

// POWERFULL HIGH HEAT CONTINUOUS BURN
AMB_ENTRY(10,    DIGI_FIRE2,             AMB_NONE,      AMB_NOTICS)

// AMBIENT GONG FOR USE IN TEMPLE/PALACE LEVELS
AMB_ENTRY(11,    DIGI_GONG,              AMB_INTERMIT,  AMB_120)

// AMBIENT LAVA FLOW
AMB_ENTRY(12,    DIGI_LAVAFLOW1,         AMB_NONE,      AMB_NOTICS)

// AMBIENT MUD BUBBLES
AMB_ENTRY(13,    DIGI_MUBBUBBLES1,       AMB_NONE,      AMB_NOTICS)

// AMBIENT EARTH QUAKE
AMB_ENTRY(14,    DIGI_EARTHQUAKE,        AMB_NONE,      AMB_NOTICS)

// YUCKY SEWER FLOW
AMB_ENTRY(15,    DIGI_SEWERFLOW1,        AMB_NONE,      AMB_NOTICS)

// STEAM FLOW
AMB_ENTRY(16,    DIGI_STEAM1,            AMB_NONE,      AMB_NOTICS)

// VOLCANIC STEAM VENT
AMB_ENTRY(17,    DIGI_VOLCANOSTEAM1,     AMB_NONE,      AMB_NOTICS)

// SCARY AMBIENT SWAMP SOUNDS
AMB_ENTRY(18,    DIGI_SWAMP,             AMB_NONE,      AMB_NOTICS)

// AMBIENT ROLLING THUNDER
AMB_ENTRY(19,    DIGI_THUNDER,           AMB_PAN|AMB_INTERMIT,  AMB_60)

// UNDERWATER AMBIENCE
AMB_ENTRY(20,    DIGI_UNDERWATER,        AMB_PAN,       AMB_NOTICS)

// SPOOKY ETHERAL void AMBIENCE (NETHERWORLDLY SOUNDS)
AMB_ENTRY(21,    DIGI_VOID1,             AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(22,    DIGI_VOID2,             AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(23,    DIGI_VOID3,             AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(24,    DIGI_VOID4,             AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(25,    DIGI_VOID5,             AMB_NONE,      AMB_NOTICS)

// VOLCANIC ERUPTION
AMB_ENTRY(26,    DIGI_ERUPTION,          AMB_NONE,      AMB_NOTICS)

// VOLCANIC SIZZLING PROJECTILES FLYING THROUGH AIR
AMB_ENTRY(27,    DIGI_VOLCANOPROJECTILE, AMB_NONE,      AMB_NOTICS)

// LIGHT WIND AMBIENCE
AMB_ENTRY(28,    DIGI_LIGHTWIND,         AMB_NONE,      AMB_NOTICS)

// STRONG BLOWING WIND AMBIENCE
AMB_ENTRY(29,    DIGI_STRONGWIND,        AMB_PAN|AMB_INTERMIT,  AMB_20)

// BREAKING WOOD AMBIENCE
AMB_ENTRY(30,    DIGI_BREAKINGWOOD,      AMB_INTERMIT,  AMB_120)

// BREAKING, TUMBLING STONES FALLING AMBIENCE
AMB_ENTRY(31,    DIGI_BREAKSTONES,       AMB_NONE,      AMB_NOTICS)

// MOTOR BOAT
AMB_ENTRY(32,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(33,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(34,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)

// WWII JAP ARMY TANK
AMB_ENTRY(35,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(36,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(37,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(38,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(39,    DIGI_NULL,              AMB_NONE,      AMB_NOTICS)

// WWII JAP BOMBER PLANE
AMB_ENTRY(40,    DIGI_BOMBRFLYING,       AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(41,    DIGI_BOMBRDROPBOMB,     AMB_NONE,      AMB_NOTICS)

// GIANT DRILL MACHINE
AMB_ENTRY(42,   DIGI_DRILL,              AMB_NONE,      AMB_NOTICS)

// SECTOR GEAR COG TURNING
AMB_ENTRY(43,   DIGI_GEAR1,              AMB_NONE,      AMB_NOTICS)

// GENERIC SECTOR OBJECT MACHINE RUNNING
AMB_ENTRY(44,   DIGI_MACHINE1,           AMB_NONE,      AMB_NOTICS)

// ENGINE ROOM
AMB_ENTRY(45,   DIGI_ENGROOM1,           AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(46,   DIGI_ENGROOM2,           AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(47,   DIGI_ENGROOM3,           AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(48,   DIGI_ENGROOM4,           AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(49,   DIGI_ENGROOM5,           AMB_NONE,      AMB_NOTICS)

// HELICOPTER, SPINNING BLADE SOUND
AMB_ENTRY(50,   DIGI_HELI,               AMB_NONE,      AMB_NOTICS)

// ECHOING HEART
AMB_ENTRY(51,   DIGI_BIGHART,            AMB_NONE,      AMB_NOTICS)

// ETHERAL WIND
AMB_ENTRY(52,   DIGI_WIND4,              AMB_NONE,      AMB_NOTICS)

// SPOOKY SINE WAVE
AMB_ENTRY(53,   DIGI_SPOOKY1,            AMB_NONE,      AMB_NOTICS)

// JET ENGINE
AMB_ENTRY(54,   DIGI_JET,                AMB_NONE,      AMB_NOTICS)

// CEREMONIAL DRUM CHANT
AMB_ENTRY(55,   DIGI_DRUMCHANT,          AMB_NONE,      AMB_NOTICS)

AMB_ENTRY(56,   DIGI_ASIREN1,           AMB_INTERMIT,  AMB_45)
AMB_ENTRY(57,   DIGI_FIRETRK1,          AMB_INTERMIT,  AMB_60)
AMB_ENTRY(58,   DIGI_TRAFFIC1,          AMB_INTERMIT,  AMB_60)
AMB_ENTRY(59,   DIGI_TRAFFIC2,          AMB_INTERMIT,  AMB_60)
AMB_ENTRY(60,   DIGI_TRAFFIC3,          AMB_INTERMIT,  AMB_60)
AMB_ENTRY(61,   DIGI_TRAFFIC4,          AMB_INTERMIT,  AMB_60)
AMB_ENTRY(62,   DIGI_TRAFFIC5,          AMB_INTERMIT,  AMB_30)
AMB_ENTRY(63,   DIGI_TRAFFIC6,          AMB_INTERMIT,  AMB_60)
AMB_ENTRY(64,   DIGI_HELI1,             AMB_INTERMIT,  AMB_120)
AMB_ENTRY(65,   DIGI_JET1,              AMB_INTERMIT,  AMB_120)
AMB_ENTRY(66,   DIGI_MOTO1,             AMB_INTERMIT,  AMB_45)
AMB_ENTRY(67,   DIGI_MOTO2,             AMB_INTERMIT,  AMB_60)
AMB_ENTRY(68,   DIGI_NEON1,             AMB_INTERMIT,  AMB_5)
AMB_ENTRY(69,   DIGI_SUBWAY,            AMB_INTERMIT,  AMB_30)
AMB_ENTRY(70,   DIGI_TRAIN1,            AMB_INTERMIT,  AMB_120)
AMB_ENTRY(71,   DIGI_BIRDS1,            AMB_INTERMIT,  AMB_10)
AMB_ENTRY(72,   DIGI_BIRDS2,            AMB_INTERMIT,  AMB_10)
AMB_ENTRY(73,   DIGI_AMOEBA,            AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(74,   DIGI_TRAIN3,            AMB_INTERMIT,  AMB_120)
AMB_ENTRY(75,   DIGI_TRAIN8,            AMB_INTERMIT,  AMB_120)
AMB_ENTRY(76,   DIGI_WHIPME,            AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(77,   DIGI_FLAGWAVE,          AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(78,   DIGI_ANIMECRY,          AMB_NONE,      AMB_NOTICS)
AMB_ENTRY(79,   DIGI_WINDCHIMES,        AMB_INTERMIT,  AMB_10)
AMB_ENTRY(80,   DIGI_BOATCREAK,         AMB_INTERMIT,  AMB_10)
AMB_ENTRY(81,   DIGI_SHIPBELL,          AMB_INTERMIT,  AMB_30)
AMB_ENTRY(82,   DIGI_FOGHORN,           AMB_INTERMIT,  AMB_120)

#undef AMB_ENTRY


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "cache1d.h"

#include "keys.h"
#include "mytypes.h"
#include "fx_man.h"
#include "music.h"
#include "scriplib.h"
#include "gamedefs.h"
#include "keyboard.h"

#include "control.h"
#include "config.h"
#include "sounds.h"
#include "function.h"

#include "game.h"
#include "colormap.h"
#include "network.h"

#include "animlib.h"
#include "anim.h"

#include "common_game.h"

#define MAX_ANMS 10
anim_t *anm_ptr[MAX_ANMS];

int ANIMnumframes;
unsigned char ANIMpal[3*256];
unsigned char ANIMnum = 0;
short SoundState;

const char *ANIMname[] =
{
    "sw.anm",
    "swend.anm",
    "sumocinm.anm",
    "zfcin.anm",
};

#define ANIM_TILE(num) (MAXTILES-11 + (num))

void AnimShareIntro(int frame, int numframes)
{
    int zero=0;

    if (frame == numframes-1)
        ototalclock += 120;
    else if (frame == 1)
    {
        PlaySound(DIGI_NOMESSWITHWANG,&zero,&zero,&zero,v3df_none);
        ototalclock += 120*3;
    }
    else
        ototalclock += 8;

    if (frame == 5)
    {
        PlaySound(DIGI_INTRO_SLASH,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 15)
    {
        PlaySound(DIGI_INTRO_WHIRL,&zero,&zero,&zero,v3df_none);
    }
}

void AnimSerp(int frame, int numframes)
{
    int zero=0;
    ototalclock += 16;

    if (frame == numframes-1)
        ototalclock += 1*120;

    if (frame == 1)
    {
        PlaySound(DIGI_SERPTAUNTWANG,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 16)
    {
        PlaySound(DIGI_SHAREND_TELEPORT,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 35)
    {
        SoundState++;
        PlaySound(DIGI_WANGTAUNTSERP1,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 51)
    {
        SoundState++;
        PlaySound(DIGI_SHAREND_UGLY1,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 64)
    {
        SoundState++;
        PlaySound(DIGI_SHAREND_UGLY2,&zero,&zero,&zero,v3df_none);
    }
}

void AnimSumo(int frame, int numframes)
{
    int zero=0;
    ototalclock += 10;

    if (frame == numframes-1)
        ototalclock += 1*120;

    if (frame == 1)
        ototalclock += 30;

    if (frame == 2)
    {
        // hungry
        PlaySound(DIGI_JG41012,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 30)
    {
        PlaySound(DIGI_HOTHEADSWITCH,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 42)
    {
        PlaySound(DIGI_HOTHEADSWITCH,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 59)
    {
        PlaySound(DIGI_JG41028,&zero,&zero,&zero,v3df_none);
    }
}

void AnimZilla(int frame, int numframes)
{
    int zero=0;
    ototalclock += 16;

    if (frame == numframes-1)
        ototalclock += 1*120;

    if (frame == 1)
    {
        PlaySound(DIGI_ZC1,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 5)
    {
        PlaySound(DIGI_JG94024,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 14)
    {
        PlaySound(DIGI_ZC2,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 30)
    {
        PlaySound(DIGI_ZC3,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 32)
    {
        PlaySound(DIGI_ZC4,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 37)
    {
        PlaySound(DIGI_ZC5,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 63)
    {
        PlaySound(DIGI_Z16043,&zero,&zero,&zero,v3df_none);
        PlaySound(DIGI_ZC6,&zero,&zero,&zero,v3df_none);
        PlaySound(DIGI_ZC7,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 72)
    {
        PlaySound(DIGI_ZC7,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 73)
    {
        PlaySound(DIGI_ZC4,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 77)
    {
        PlaySound(DIGI_ZC5,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 87)
    {
        PlaySound(DIGI_ZC8,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 103)
    {
        PlaySound(DIGI_ZC7,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 108)
    {
        PlaySound(DIGI_ZC9,&zero,&zero,&zero,v3df_none);
    }
    else if (frame == 120)
    {
        PlaySound(DIGI_JG94039,&zero,&zero,&zero,v3df_none);
    }
}

unsigned char *LoadAnm(short anim_num)
{
    int handle;
    int length;
    unsigned char *animbuf;

    DSPRINTF(ds,"LoadAnm");
    MONO_PRINT(ds);

    // this seperate allows the anim to be precached easily

    ANIMnum = anim_num;

    // lock it
    walock[ANIM_TILE(ANIMnum)] = 219;

    if (anm_ptr[anim_num] == 0)
    {
        handle = kopen4load(ANIMname[ANIMnum], 0);
        if (handle == -1)
            return NULL;
        length = kfilelength(handle);

        g_cache.allocateBlock((intptr_t *) &anm_ptr[anim_num], length + sizeof(anim_t), &walock[ANIM_TILE(ANIMnum)]);
        animbuf = (unsigned char *)((intptr_t)anm_ptr[anim_num] + sizeof(anim_t));

        kread(handle, animbuf, length);
        kclose(handle);
    }
    else
    {
        animbuf = (unsigned char *)((intptr_t)anm_ptr[anim_num] + sizeof(anim_t));
    }

    return animbuf;
}

void
playanm(short anim_num)
{
    unsigned char *animbuf;
    int i, length = 0, numframes = 0;
    int32_t handle = -1;
    UserInput uinfo = { FALSE, FALSE, FALSE, dir_None };

    ANIMnum = anim_num;

    KB_FlushKeyboardQueue();
    KB_ClearKeysDown();

    DSPRINTF(ds,"PlayAnm");
    MONO_PRINT(ds);

    DSPRINTF(ds,"PlayAnm");
    MONO_PRINT(ds);

    animbuf = LoadAnm(anim_num);
    if (!animbuf)
        return;

    // [JM] Temporary, needed to get the file's length for ANIM_LoadAnim. !CHECKME!
    handle = kopen4load(ANIMname[ANIMnum], 0);
    if (handle == -1) return;
    length = kfilelength(handle);
    kclose(handle);

    DSPRINTF(ds,"PlayAnm - Palette Stuff");
    MONO_PRINT(ds);

    ANIM_LoadAnim(animbuf, length);
    ANIMnumframes = ANIM_NumFrames();
    numframes = ANIMnumframes;

    tilesiz[ANIM_TILE(ANIMnum)].x = 200;
    tilesiz[ANIM_TILE(ANIMnum)].y = 320;

    videoClearViewableArea(0L);

    paletteSetColorTable(ANIMPAL, ANIM_GetPalette());
    videoSetPalette(gs.Brightness, ANIMPAL, 2);
    if (ANIMnum == 1)
    {
        // draw the first frame
        waloff[ANIM_TILE(ANIMnum)] = (intptr_t)ANIM_DrawFrame(1);
        tileInvalidate(ANIM_TILE(ANIMnum), 0, 1<<4);
        rotatesprite(0 << 16, 0 << 16, 65536L, 512, ANIM_TILE(ANIMnum), 0, 0, 2 + 4 + 8 + 16 + 64, 0, 0, xdim - 1, ydim - 1);
    }

    SoundState = 0;
    //ototalclock = totalclock + 120*2;
    ototalclock = (int32_t) totalclock;

    for (i = 1; i < numframes; i++)
    {
        while (totalclock < ototalclock)
        {
            handleevents();
            CONTROL_GetUserInput(&uinfo);
            CONTROL_ClearUserInput(&uinfo);
            switch (ANIMnum)
            {
            case ANIM_INTRO:
                if (KB_KeyWaiting() || uinfo.button0 || uinfo.button1 || quitevent)
                    goto ENDOFANIMLOOP;
                break;
            case ANIM_SERP:
                if (KEY_PRESSED(KEYSC_ESC) || uinfo.button1 || quitevent)
                    goto ENDOFANIMLOOP;
                break;
            }

            getpackets();
        }

        switch (ANIMnum)
        {
        case ANIM_INTRO:
            AnimShareIntro(i,numframes);
            break;
        case ANIM_SERP:
            AnimSerp(i,numframes);
            break;
        case ANIM_SUMO:
            AnimSumo(i,numframes);
            break;
        case ANIM_ZILLA:
            AnimZilla(i,numframes);
            break;
        }

        videoClearViewableArea(0L);

        waloff[ANIM_TILE(ANIMnum)] = (intptr_t)ANIM_DrawFrame(i);
        tileInvalidate(ANIM_TILE(ANIMnum), 0, 1<<4);

        rotatesprite(0 << 16, 0 << 16, 65536L, 512, ANIM_TILE(ANIMnum), 0, 0, 2 + 4 + 8 + 16 + 64, 0, 0, xdim - 1, ydim - 1);
        videoNextPage();
    }

    // pause on final frame
    while (totalclock < ototalclock)
    {
        handleevents();
        getpackets();
    }

ENDOFANIMLOOP:

    videoClearViewableArea(0L);
    videoNextPage();

    videoSetPalette(gs.Brightness, BASEPAL, 2);

    KB_FlushKeyboardQueue();
    KB_ClearKeysDown();
    ANIM_FreeAnim();
    walock[ANIM_TILE(ANIMnum)] = 1;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#define ANIM_INTRO 0
#define ANIM_SERP 1
#define ANIM_SUMO 2
#define ANIM_ZILLA 3

unsigned char *LoadAnm(short anim_num);
void playanm(short anim_num);


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1996, 2005 - 3D Realms Entertainment

This file is NOT part of Shadow Warrior version 1.2
However, it is either an older version of a file that is, or is
some test code written during the development of Shadow Warrior.
This file is provided purely for educational interest.

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

// scriplib.c
#include "build.h"
#include "editor.h"
#include "cache1d.h"

#include "names2.h"
#include "game.h"

#include "parse.h"

#define PATHSEPERATOR   '\\'

//#define COMPUTE_TOTALS	1

/*
=============================================================================

                        ABNORMAL TERMINATION

=============================================================================
*/
void Error(const char *error, ...)
{
    va_list argptr;

    va_start(argptr,error);
    vprintf(error,argptr);
    va_end(argptr);
    printf("\n");
    exit(1);
}

/*
=============================================================================

                        PARSING STUFF

=============================================================================
*/

char    token[MAXTOKEN];
char    *scriptbuffer,*script_p,*scriptend_p;
int     grabbed;
int     scriptline;
SWBOOL    endofscript;
SWBOOL    tokenready;                     // only TRUE if UnGetToken was just called

/*
==============
=
= LoadScriptFile
=
==============
*/

SWBOOL LoadScriptFile(const char *filename)
{
    int size, readsize;
    int fp;

    if ((fp=kopen4load(filename,0)) == -1)
    {
        // If there's no script file, forget it.
        return FALSE;
    }

    size = kfilelength(fp);

    scriptbuffer = (char *)Xmalloc(size);

    ASSERT(scriptbuffer != NULL);

    readsize = kread(fp, scriptbuffer, size);

    kclose(fp);

    PRODUCTION_ASSERT(readsize == size);

    // Convert filebuffer to all upper case
    //Bstrupr(scriptbuffer);

    script_p = scriptbuffer;
    scriptend_p = script_p + size;
    scriptline = 1;
    endofscript = FALSE;
    tokenready = FALSE;
    return TRUE;
}

/*
==============
=
= UnGetToken
=
= Signals that the current token was not used, and should be reported
= for the next GetToken.  Note that

GetToken (TRUE);
UnGetToken ();
GetToken (FALSE);

= could cross a line boundary.
=
==============
*/

void UnGetToken(void)
{
    tokenready = TRUE;
}

/*
==============
=
= GetToken
=
==============
*/

void GetToken(SWBOOL crossline)
{
    char    *token_p;

    if (tokenready)                         // is a token already waiting?
    {
        tokenready = FALSE;
        return;
    }

    if (script_p >= scriptend_p)
    {
        if (!crossline)
            Error("Line %i is incomplete\n",scriptline);
        endofscript = TRUE;
        return;
    }

//
// skip space
//
skipspace:
    while (*script_p <= 32)
    {
        if (script_p >= scriptend_p)
        {
            if (!crossline)
                Error("Line %i is incomplete\n",scriptline);
            endofscript = TRUE;
            return;
        }
        if (*script_p++ == '\n')
        {
            if (!crossline)
                Error("Line %i is incomplete\n",scriptline);
            scriptline++;
        }
    }

    if (script_p >= scriptend_p)
    {
        if (!crossline)
            Error("Line %i is incomplete\n",scriptline);
        endofscript = TRUE;
        return;
    }

    if (*script_p == '#')   // # is comment field
    {
        if (!crossline)
            Error("Line %i is incomplete\n",scriptline);
        while (*script_p++ != '\n')
            if (script_p >= scriptend_p)
            {
                endofscript = TRUE;
                return;
            }
        goto skipspace;
    }

//
// copy token
//
    token_p = token;

    while (*script_p > 32 && *script_p != '#')
    {
        *token_p++ = *script_p++;
        if (script_p == scriptend_p)
            break;
        ASSERT(token_p != &token[MAXTOKEN]);
//			Error ("Token too large on line %i\n",scriptline);
    }

    *token_p = 0;
}

/*
==============
=
= TokenAvailable
=
= Returns true if there is another token on the line
=
==============
*/

SWBOOL TokenAvailable(void)
{
    char    *search_p;

    search_p = script_p;

    if (search_p >= scriptend_p)
        return FALSE;

    while (*search_p <= 32)
    {
        if (*search_p == '\n')
            return FALSE;
        search_p++;
        if (search_p == scriptend_p)
            return FALSE;

    }

    if (*search_p == '#')
        return FALSE;

    return TRUE;
}

void DefaultExtension(char *path, char *extension)
{
    char    *src;
//
// if path doesn't have a .EXT, append extension
// (extension should include the .)
//
    src = path + strlen(path) - 1;

    while (*src != '\\' && src != path)
    {
        if (*src == '.')
            return;                 // it has an extension
        src--;
    }

    strcat(path, extension);
}

void DefaultPath(char *path, char *basepath)
{
    char    temp[128];

    if (path[0] == '\\')
        return;                                                 // absolute path location
    strcpy(temp,path);
    strcpy(path,basepath);
    strcat(path,temp);
}

void    StripFilename(char *path)
{
    int             length;

    length = strlen(path)-1;
    while (length > 0 && path[length] != PATHSEPERATOR)
        length--;
    path[length] = 0;
}

void ExtractFileBase(char *path, char *dest)
{
    char    *src;
    int             length;

    src = path + strlen(path) - 1;

//
// back up until a \ or the start
//
    while (src != path && *(src-1) != '\\')
        src--;

//
// copy up to eight characters
//
    memset(dest,0,8);
    length = 0;
    while (*src && *src != '.')
    {
        if (++length == 9)
            Error("Filename base of %s >8 chars",path);
        *dest++ = toupper(*src++);
    }
}

/*
==============
=
= ParseNum / ParseHex
=
==============
*/

int ParseHex(char *hex)
{
    char    *str;
    int    num;

    num = 0;
    str = hex;

    while (*str)
    {
        num <<= 4;
        if (*str >= '0' && *str <= '9')
            num += *str-'0';
        else if (*str >= 'a' && *str <= 'f')
            num += 10 + *str-'a';
        else if (*str >= 'A' && *str <= 'F')
            num += 10 + *str-'A';
        else
            Error("Bad hex number: %s",hex);
        str++;
    }

    return num;
}

int ParseNum(char *str)
{
    if (str[0] == '$')
        return ParseHex(str+1);
    if (str[0] == '0' && str[1] == 'x')
        return ParseHex(str+2);
    return atol(str);
}

// voxelarray format is:
//      spritenumber, voxelnumber
int aVoxelArray[MAXTILES];

// Load all the voxel files using swvoxfil.txt script file
// Script file format:

//			# - Comment
//			spritenumber (in artfile), voxel number, filename
//			Ex. 1803 0 medkit2.kvx
//			    1804 1 shotgun.kvx
//				etc....

void LoadKVXFromScript(const char *filename)
{
    int lNumber=0,lTile=0;  // lNumber is the voxel no. and lTile is the editart tile being
    // replaced.
    char *sName;            // KVS file being loaded in.

    int grabbed=0;          // Number of lines parsed

    sName = (char *)Xmalloc(256);    // Up to 256 bytes for path
    ASSERT(sName != NULL);

    // zero out the array memory with -1's for pics not being voxelized
    memset(aVoxelArray,-1,sizeof(aVoxelArray));

    // Load the file
    if (!LoadScriptFile(filename)) return;

    do
    {
        GetToken(TRUE); // Crossing a line boundary on the end of line to first token
        // of a new line is permitted (and expected)
        if (endofscript)
            break;

        lTile = atol(token);

        GetToken(FALSE);
        lNumber = atol(token);

        GetToken(FALSE);
        strcpy(sName,token);            // Copy the whole token as a file name and path

        // Load the voxel file into memory
        if (!qloadkvx(lNumber,sName))
        {
            // Store the sprite and voxel numbers for later use
            aVoxelArray[lTile] = lNumber;   // Voxel num
        }

        if (lNumber >= nextvoxid)   // JBF: so voxels in the def file append to the list
            nextvoxid = lNumber + 1;

        grabbed++;
        ASSERT(grabbed < MAXSPRITES);

    }
    while (script_p < scriptend_p);

    Xfree(scriptbuffer);
    script_p = NULL;
}

/// MISC ////////////////////////////////////////////////////////////////////

/*
extern int idleclock,slackerclock;

// Watch dog function.  Tracks user's work times.
void LogUserTime( SWBOOL bIsLoggingIn )
{
    int size, readsize;
    time_t time_of_day;
    char serialid[20],filename[100],fbase[20],buf[26],filetemp[100];
    FILE *fp;
    int tothours, totmins, totsecs, gtotalclock=0,gidleclock=0;
    ldiv_t mins_secs;
    ldiv_t hrs_mins;
    int i;

    char path[] = "o:\\user\\jimn\\logs\\";
//	char path[] = "c:\\jim\\sw\\";

    memset(filename,0,sizeof(filename));
    memset(fbase,0,sizeof(fbase));
    memset(serialid,0,sizeof(serialid));
    memset(buf,0,sizeof(buf));
    memset(filetemp,0,sizeof(filetemp));

    // Get the time of day user logged in to build
    time_of_day = time( NULL );

    // Get the serial number from the user's disk drive "it's unique!"
    system("dir > serid.bld");
    LoadScriptFile("serid.bld");

    // Go to the serial number
    for (i=0; i<11; i++)
    {
        GetToken (TRUE);
        if (endofscript)
            return;
    }

    // Copy the token to serialid
    strcpy(serialid,token);

    // Free the script memory when done
    Xfree(scriptbuffer);
    script_p = NULL;

    // Build a file name using serial id.
    strcpy(filename,path);
    strncpy(fbase,serialid,8);
    strcat(fbase,".bld");
    strcat(filename,fbase);

    // Delete the temp file
    system("erase serid.bld");

    // Unhide the file so it can be opened
    _dos_setfileattr(filename,_A_NORMAL);

    // Open the file
    fp = fopen( filename, "a+" );

    // Opening on the network failed, try putting it on the current disk drive
    if(fp == NULL)
    {
        // Unhide the file so it can be opened/this works if file was created before!
        _dos_setfileattr(fbase,_A_NORMAL);
        fp = fopen( fbase, "a+" );
        strcpy(filetemp,fbase);
    } else
        strcpy(filetemp,filename);

    if( fp == NULL)
        return;
    else
    {
        if(bIsLoggingIn)
        {
            fprintf(fp, "//////////////////////////////\n");
            fprintf(fp, "User logged into build at: %s", _ctime( &time_of_day, buf ) );
        }else
        {
            totsecs = totalclock/120;	// Convert totalclock to seconds.

            mins_secs = ldiv( totsecs, 60L );
            totmins = mins_secs.quot;
            totsecs = mins_secs.rem;

            hrs_mins = ldiv( totmins, 60L);
            tothours = hrs_mins.quot;
            totmins = hrs_mins.rem;

            fprintf(fp, "TotalClock: %ld\n",totalclock);
#ifdef COMPUTE_TOTALS
            fprintf(fp, "IdleClock: %ld\n",slackerclock);
#endif
            fprintf(fp, "Time this session: %ld Hours %ld Mins %ld Secs\n",tothours,totmins,totsecs);
#ifdef COMPUTE_TOTALS
            totsecs = (totalclock-slackerclock)/120;	// Convert totalclock to seconds.
            if(totsecs<=0) totsecs = 0;

            mins_secs = ldiv( totsecs, 60L );
            totmins = mins_secs.quot;
            totsecs = mins_secs.rem;

            hrs_mins = ldiv( totmins, 60L);
            tothours = hrs_mins.quot;
            totmins = hrs_mins.rem;
            fprintf(fp, "Time - idleclock : %ld Hours %ld Mins %ld Secs\n",tothours,totmins,totsecs);
#endif
        }

        fclose( fp );
    }

#if 1
    if(!bIsLoggingIn)
    {
        // Compute total time for file
        LoadScriptFile(filetemp);

        do {
            GetToken (TRUE);

            if (endofscript)
                break;

            if(!strcmpi(token,"totalclock:"))
            {
                GetToken(TRUE);
                gtotalclock += atol(token);
            }
#if 0
            if(!strcmpi(token,"idleclock:"))
            {
                GetToken(TRUE);
                gidleclock += atol(token);
            }
#endif

        } while (script_p < scriptend_p);

        // Free the script memory when done
        Xfree(scriptbuffer);
        script_p = NULL;

        // Open the file
        fp = fopen( filetemp, "a+" );

        // Now compute the grand total
        if(fp != NULL)
        {
            totsecs = gtotalclock/120;	// Convert totalclock to seconds.

            mins_secs = ldiv( totsecs, 60L );
            totmins = mins_secs.quot;
            totsecs = mins_secs.rem;

            hrs_mins = ldiv( totmins, 60L);
            tothours = hrs_mins.quot;
            totmins = hrs_mins.rem;

            fprintf(fp, "\nTotal time so far  : %ld Hours %ld Mins %ld Secs\n",tothours,totmins,totsecs);

#if 0
            totsecs = (gtotalclock-gidleclock)/120;	// Convert totalclock to seconds.
            if(totsecs<=0) totsecs = 0;

            mins_secs = ldiv( totsecs, 60L );
            totmins = mins_secs.quot;
            totsecs = mins_secs.rem;

            hrs_mins = ldiv( totmins, 60L);
            tothours = hrs_mins.quot;
            totmins = hrs_mins.rem;

            fprintf(fp, "\nTotal actual time  : %ld Hours %ld Mins %ld Secs\n",tothours,totmins,totsecs);
#endif

            fclose(fp);
        }
    }
#endif

    _dos_setfileattr(filename,_A_HIDDEN);

}
*/