// WL_STATE.C

#include "wl_def.h"
#pragma hdrstop

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

                            LOCAL CONSTANTS

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


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

                            GLOBAL VARIABLES

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


static const dirtype opposite[9] =
    {west,southwest,south,southeast,east,northeast,north,northwest,nodir};

static const dirtype diagonal[9][9] =
{
    /* east */  {nodir,nodir,northeast,nodir,nodir,nodir,southeast,nodir,nodir},
                {nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir},
    /* north */ {northeast,nodir,nodir,nodir,northwest,nodir,nodir,nodir,nodir},
                {nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir},
    /* west */  {nodir,nodir,northwest,nodir,nodir,nodir,southwest,nodir,nodir},
                {nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir},
    /* south */ {southeast,nodir,nodir,nodir,southwest,nodir,nodir,nodir,nodir},
                {nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir},
                {nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir,nodir}
};



void    SpawnNewObj (unsigned tilex, unsigned tiley, statetype *state);
void    NewState (objtype *ob, statetype *state);

boolean TryWalk (objtype *ob);
void    MoveObj (objtype *ob, int32_t move);

void    KillActor (objtype *ob);
void    DamageActor (objtype *ob, unsigned damage);

boolean CheckLine (objtype *ob);
void    FirstSighting (objtype *ob);
boolean CheckSight (objtype *ob);

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

                                LOCAL VARIABLES

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



//===========================================================================


/*
===================
=
= SpawnNewObj
=
= Spaws a new actor at the given TILE coordinates, with the given state, and
= the given size in GLOBAL units.
=
= newobj = a pointer to an initialized new actor
=
===================
*/

void SpawnNewObj (unsigned tilex, unsigned tiley, statetype *state)
{
    GetNewActor ();
    newobj->state = state;
    if (state->tictime)
        newobj->ticcount = DEMOCHOOSE_ORIG_SDL(
                US_RndT () % state->tictime,
                US_RndT () % state->tictime + 1);     // Chris' moonwalk bugfix ;D
    else
        newobj->ticcount = 0;

    newobj->tilex = (short) tilex;
    newobj->tiley = (short) tiley;
    newobj->x = ((int32_t)tilex<<TILESHIFT)+TILEGLOBAL/2;
    newobj->y = ((int32_t)tiley<<TILESHIFT)+TILEGLOBAL/2;
    newobj->dir = nodir;

    actorat[tilex][tiley] = newobj;
    newobj->areanumber =
        *(mapsegs[0] + (newobj->tiley<<mapshift)+newobj->tilex) - AREATILE;
}



/*
===================
=
= NewState
=
= Changes ob to a new state, setting ticcount to the max for that state
=
===================
*/

void NewState (objtype *ob, statetype *state)
{
    ob->state = state;
    ob->ticcount = state->tictime;
}



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

                        ENEMY TILE WORLD MOVEMENT CODE

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


/*
==================================
=
= TryWalk
=
= Attempts to move ob in its current (ob->dir) direction.
=
= If blocked by either a wall or an actor returns FALSE
=
= If move is either clear or blocked only by a door, returns TRUE and sets
=
= ob->tilex         = new destination
= ob->tiley
= ob->areanumber    = the floor tile number (0-(NUMAREAS-1)) of destination
= ob->distance      = TILEGLOBAl, or -doornumber if a door is blocking the way
=
= If a door is in the way, an OpenDoor call is made to start it opening.
= The actor code should wait until
=       doorobjlist[-ob->distance].action = dr_open, meaning the door has been
=       fully opened
=
==================================
*/

#define CHECKDIAG(x,y)                              \
{                                                   \
    temp=(uintptr_t)actorat[x][y];                  \
    if (temp)                                       \
    {                                               \
        if (temp<256)                               \
            return false;                           \
        if (((objtype *)temp)->flags&FL_SHOOTABLE)  \
            return false;                           \
    }                                               \
}

#ifdef PLAYDEMOLIKEORIGINAL
    #define DOORCHECK                                   \
            if(DEMOCOND_ORIG)                           \
                doornum = temp&63;                      \
            else                                        \
            {                                           \
                doornum = (int) temp & 127;             \
                OpenDoor(doornum);                      \
                ob->distance = -doornum - 1;            \
                return true;                            \
            }
#else
    #define DOORCHECK                                   \
            doornum = (int) temp & 127;                 \
            OpenDoor(doornum);                          \
            ob->distance = -doornum - 1;                \
            return true;
#endif

#define CHECKSIDE(x,y)                                  \
{                                                       \
    temp=(uintptr_t)actorat[x][y];                      \
    if (temp)                                           \
    {                                                   \
        if (temp<128)                                   \
            return false;                               \
        if (temp<256)                                   \
        {                                               \
            DOORCHECK                                   \
        }                                               \
        else if (((objtype *)temp)->flags&FL_SHOOTABLE) \
            return false;                               \
    }                                                   \
}


boolean TryWalk (objtype *ob)
{
    int       doornum = -1;
    uintptr_t temp;

    if (ob->obclass == inertobj)
    {
        switch (ob->dir)
        {
            case north:
                ob->tiley--;
                break;

            case northeast:
                ob->tilex++;
                ob->tiley--;
                break;

            case east:
                ob->tilex++;
                break;

            case southeast:
                ob->tilex++;
                ob->tiley++;
                break;

            case south:
                ob->tiley++;
                break;

            case southwest:
                ob->tilex--;
                ob->tiley++;
                break;

            case west:
                ob->tilex--;
                break;

            case northwest:
                ob->tilex--;
                ob->tiley--;
                break;
        }
    }
    else
    {
        switch (ob->dir)
        {
            case north:
                if (ob->obclass == dogobj || ob->obclass == fakeobj
                    || ob->obclass == ghostobj || ob->obclass == spectreobj)
                {
                    CHECKDIAG(ob->tilex,ob->tiley-1);
                }
                else
                {
                    CHECKSIDE(ob->tilex,ob->tiley-1);
                }
                ob->tiley--;
                break;

            case northeast:
                CHECKDIAG(ob->tilex+1,ob->tiley-1);
                CHECKDIAG(ob->tilex+1,ob->tiley);
                CHECKDIAG(ob->tilex,ob->tiley-1);
                ob->tilex++;
                ob->tiley--;
                break;

            case east:
                if (ob->obclass == dogobj || ob->obclass == fakeobj
                    || ob->obclass == ghostobj || ob->obclass == spectreobj)
                {
                    CHECKDIAG(ob->tilex+1,ob->tiley);
                }
                else
                {
                    CHECKSIDE(ob->tilex+1,ob->tiley);
                }
                ob->tilex++;
                break;

            case southeast:
                CHECKDIAG(ob->tilex+1,ob->tiley+1);
                CHECKDIAG(ob->tilex+1,ob->tiley);
                CHECKDIAG(ob->tilex,ob->tiley+1);
                ob->tilex++;
                ob->tiley++;
                break;

            case south:
                if (ob->obclass == dogobj || ob->obclass == fakeobj
                    || ob->obclass == ghostobj || ob->obclass == spectreobj)
                {
                    CHECKDIAG(ob->tilex,ob->tiley+1);
                }
                else
                {
                    CHECKSIDE(ob->tilex,ob->tiley+1);
                }
                ob->tiley++;
                break;

            case southwest:
                CHECKDIAG(ob->tilex-1,ob->tiley+1);
                CHECKDIAG(ob->tilex-1,ob->tiley);
                CHECKDIAG(ob->tilex,ob->tiley+1);
                ob->tilex--;
                ob->tiley++;
                break;

            case west:
                if (ob->obclass == dogobj || ob->obclass == fakeobj
                    || ob->obclass == ghostobj || ob->obclass == spectreobj)
                {
                    CHECKDIAG(ob->tilex-1,ob->tiley);
                }
                else
                {
                    CHECKSIDE(ob->tilex-1,ob->tiley);
                }
                ob->tilex--;
                break;

            case northwest:
                CHECKDIAG(ob->tilex-1,ob->tiley-1);
                CHECKDIAG(ob->tilex-1,ob->tiley);
                CHECKDIAG(ob->tilex,ob->tiley-1);
                ob->tilex--;
                ob->tiley--;
                break;

            case nodir:
                return false;

            default:
                Quit ("Walk: Bad dir");
        }
    }

#ifdef PLAYDEMOLIKEORIGINAL
    if (DEMOCOND_ORIG && doornum != -1)
    {
        OpenDoor(doornum);
        ob->distance = -doornum-1;
        return true;
    }
#endif

    ob->areanumber =
        *(mapsegs[0] + (ob->tiley<<mapshift)+ob->tilex) - AREATILE;

    ob->distance = TILEGLOBAL;
    return true;
}


/*
==================================
=
= SelectDodgeDir
=
= Attempts to choose and initiate a movement for ob that sends it towards
= the player while dodging
=
= If there is no possible move (ob is totally surrounded)
=
= ob->dir           =       nodir
=
= Otherwise
=
= ob->dir           = new direction to follow
= ob->distance      = TILEGLOBAL or -doornumber
= ob->tilex         = new destination
= ob->tiley
= ob->areanumber    = the floor tile number (0-(NUMAREAS-1)) of destination
=
==================================
*/

void SelectDodgeDir (objtype *ob)
{
    int         deltax,deltay,i;
    unsigned    absdx,absdy;
    dirtype     dirtry[5];
    dirtype     turnaround,tdir;

    if (ob->flags & FL_FIRSTATTACK)
    {
        //
        // turning around is only ok the very first time after noticing the
        // player
        //
        turnaround = nodir;
        ob->flags &= ~FL_FIRSTATTACK;
    }
    else
        turnaround=opposite[ob->dir];

    deltax = player->tilex - ob->tilex;
    deltay = player->tiley - ob->tiley;

    //
    // arange 5 direction choices in order of preference
    // the four cardinal directions plus the diagonal straight towards
    // the player
    //

    if (deltax>0)
    {
        dirtry[1]= east;
        dirtry[3]= west;
    }
    else
    {
        dirtry[1]= west;
        dirtry[3]= east;
    }

    if (deltay>0)
    {
        dirtry[2]= south;
        dirtry[4]= north;
    }
    else
    {
        dirtry[2]= north;
        dirtry[4]= south;
    }

    //
    // randomize a bit for dodging
    //
    absdx = abs(deltax);
    absdy = abs(deltay);

    if (absdx > absdy)
    {
        tdir = dirtry[1];
        dirtry[1] = dirtry[2];
        dirtry[2] = tdir;
        tdir = dirtry[3];
        dirtry[3] = dirtry[4];
        dirtry[4] = tdir;
    }

    if (US_RndT() < 128)
    {
        tdir = dirtry[1];
        dirtry[1] = dirtry[2];
        dirtry[2] = tdir;
        tdir = dirtry[3];
        dirtry[3] = dirtry[4];
        dirtry[4] = tdir;
    }

    dirtry[0] = diagonal [ dirtry[1] ] [ dirtry[2] ];

    //
    // try the directions util one works
    //
    for (i=0;i<5;i++)
    {
        if ( dirtry[i] == nodir || dirtry[i] == turnaround)
            continue;

        ob->dir = dirtry[i];
        if (TryWalk(ob))
            return;
    }

    //
    // turn around only as a last resort
    //
    if (turnaround != nodir)
    {
        ob->dir = turnaround;

        if (TryWalk(ob))
            return;
    }

    ob->dir = nodir;
}


/*
============================
=
= SelectChaseDir
=
= As SelectDodgeDir, but doesn't try to dodge
=
============================
*/

void SelectChaseDir (objtype *ob)
{
    int     deltax,deltay;
    dirtype d[3];
    dirtype tdir, olddir, turnaround;


    olddir=ob->dir;
    turnaround=opposite[olddir];

    deltax=player->tilex - ob->tilex;
    deltay=player->tiley - ob->tiley;

    d[1]=nodir;
    d[2]=nodir;

    if (deltax>0)
        d[1]= east;
    else if (deltax<0)
        d[1]= west;
    if (deltay>0)
        d[2]=south;
    else if (deltay<0)
        d[2]=north;

    if (abs(deltay)>abs(deltax))
    {
        tdir=d[1];
        d[1]=d[2];
        d[2]=tdir;
    }

    if (d[1]==turnaround)
        d[1]=nodir;
    if (d[2]==turnaround)
        d[2]=nodir;


    if (d[1]!=nodir)
    {
        ob->dir=d[1];
        if (TryWalk(ob))
            return;     /*either moved forward or attacked*/
    }

    if (d[2]!=nodir)
    {
        ob->dir=d[2];
        if (TryWalk(ob))
            return;
    }

    /* there is no direct path to the player, so pick another direction */

    if (olddir!=nodir)
    {
        ob->dir=olddir;
        if (TryWalk(ob))
            return;
    }

    if (US_RndT()>128)      /*randomly determine direction of search*/
    {
        for (tdir=north; tdir<=west; tdir=(dirtype)(tdir+1))
        {
            if (tdir!=turnaround)
            {
                ob->dir=tdir;
                if ( TryWalk(ob) )
                    return;
            }
        }
    }
    else
    {
        for (tdir=west; tdir>=north; tdir=(dirtype)(tdir-1))
        {
            if (tdir!=turnaround)
            {
                ob->dir=tdir;
                if ( TryWalk(ob) )
                    return;
            }
        }
    }

    if (turnaround !=  nodir)
    {
        ob->dir=turnaround;
        if (ob->dir != nodir)
        {
            if ( TryWalk(ob) )
                return;
        }
    }

    ob->dir = nodir;                // can't move
}


/*
============================
=
= SelectRunDir
=
= Run Away from player
=
============================
*/

void SelectRunDir (objtype *ob)
{
    int deltax,deltay;
    dirtype d[3];
    dirtype tdir;


    deltax=player->tilex - ob->tilex;
    deltay=player->tiley - ob->tiley;

    if (deltax<0)
        d[1]= east;
    else
        d[1]= west;
    if (deltay<0)
        d[2]=south;
    else
        d[2]=north;

    if (abs(deltay)>abs(deltax))
    {
        tdir=d[1];
        d[1]=d[2];
        d[2]=tdir;
    }

    ob->dir=d[1];
    if (TryWalk(ob))
        return;     /*either moved forward or attacked*/

    ob->dir=d[2];
    if (TryWalk(ob))
        return;

    /* there is no direct path to the player, so pick another direction */

    if (US_RndT()>128)      /*randomly determine direction of search*/
    {
        for (tdir=north; tdir<=west; tdir=(dirtype)(tdir+1))
        {
            ob->dir=tdir;
            if ( TryWalk(ob) )
                return;
        }
    }
    else
    {
        for (tdir=west; tdir>=north; tdir=(dirtype)(tdir-1))
        {
            ob->dir=tdir;
            if ( TryWalk(ob) )
                return;
        }
    }

    ob->dir = nodir;                // can't move
}


/*
=================
=
= MoveObj
=
= Moves ob be move global units in ob->dir direction
= Actors are not allowed to move inside the player
= Does NOT check to see if the move is tile map valid
=
= ob->x                 = adjusted for new position
= ob->y
=
=================
*/

void MoveObj (objtype *ob, int32_t move)
{
    int32_t    deltax,deltay;

    switch (ob->dir)
    {
        case north:
            ob->y -= move;
            break;
        case northeast:
            ob->x += move;
            ob->y -= move;
            break;
        case east:
            ob->x += move;
            break;
        case southeast:
            ob->x += move;
            ob->y += move;
            break;
        case south:
            ob->y += move;
            break;
        case southwest:
            ob->x -= move;
            ob->y += move;
            break;
        case west:
            ob->x -= move;
            break;
        case northwest:
            ob->x -= move;
            ob->y -= move;
            break;

        case nodir:
            return;

        default:
            Quit ("MoveObj: bad dir!");
    }

    //
    // check to make sure it's not on top of player
    //
    if (areabyplayer[ob->areanumber])
    {
        deltax = ob->x - player->x;
        if (deltax < -MINACTORDIST || deltax > MINACTORDIST)
            goto moveok;
        deltay = ob->y - player->y;
        if (deltay < -MINACTORDIST || deltay > MINACTORDIST)
            goto moveok;

        if (ob->hidden)          // move closer until he meets CheckLine
            goto moveok;

        if (ob->obclass == ghostobj || ob->obclass == spectreobj)
            TakeDamage (tics*2,ob);

        //
        // back up
        //
        switch (ob->dir)
        {
            case north:
                ob->y += move;
                break;
            case northeast:
                ob->x -= move;
                ob->y += move;
                break;
            case east:
                ob->x -= move;
                break;
            case southeast:
                ob->x -= move;
                ob->y -= move;
                break;
            case south:
                ob->y -= move;
                break;
            case southwest:
                ob->x += move;
                ob->y -= move;
                break;
            case west:
                ob->x += move;
                break;
            case northwest:
                ob->x += move;
                ob->y += move;
                break;

            case nodir:
                return;
        }
        return;
    }
moveok:
    ob->distance -=move;
}

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

                                STUFF

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

/*
===============
=
= DropItem
=
= Tries to drop a bonus item somewhere in the tiles surrounding the
= given tilex/tiley
=
===============
*/

void DropItem (wl_stat_t itemtype, int tilex, int tiley)
{
    int     x,y,xl,xh,yl,yh;

    //
    // find a free spot to put it in
    //
    if (!actorat[tilex][tiley])
    {
        PlaceItemType (itemtype, tilex,tiley);
        return;
    }

    xl = tilex-1;
    xh = tilex+1;
    yl = tiley-1;
    yh = tiley+1;

    for (x=xl ; x<= xh ; x++)
    {
        for (y=yl ; y<= yh ; y++)
        {
            if (!actorat[x][y])
            {
                PlaceItemType (itemtype, x,y);
                return;
            }
        }
    }
}



/*
===============
=
= KillActor
=
===============
*/

void KillActor (objtype *ob)
{
    int     tilex,tiley;

    tilex = ob->tilex = (word)(ob->x >> TILESHIFT);         // drop item on center
    tiley = ob->tiley = (word)(ob->y >> TILESHIFT);

    switch (ob->obclass)
    {
        case guardobj:
            GivePoints (100);
            NewState (ob,&s_grddie1);
            PlaceItemType (bo_clip2,tilex,tiley);
            break;

        case officerobj:
            GivePoints (400);
            NewState (ob,&s_ofcdie1);
            PlaceItemType (bo_clip2,tilex,tiley);
            break;

        case mutantobj:
            GivePoints (700);
            NewState (ob,&s_mutdie1);
            PlaceItemType (bo_clip2,tilex,tiley);
            break;

        case ssobj:
            GivePoints (500);
            NewState (ob,&s_ssdie1);
            if (gamestate.bestweapon < wp_machinegun)
                PlaceItemType (bo_machinegun,tilex,tiley);
            else
                PlaceItemType (bo_clip2,tilex,tiley);
            break;

        case dogobj:
            GivePoints (200);
            NewState (ob,&s_dogdie1);
            break;

#ifndef SPEAR
        case bossobj:
            GivePoints (5000);
            NewState (ob,&s_bossdie1);
            PlaceItemType (bo_key1,tilex,tiley);
            break;

        case gretelobj:
            GivePoints (5000);
            NewState (ob,&s_greteldie1);
            PlaceItemType (bo_key1,tilex,tiley);
            break;

        case giftobj:
            GivePoints (5000);
            gamestate.killx = player->x;
            gamestate.killy = player->y;
            NewState (ob,&s_giftdie1);
            break;

        case fatobj:
            GivePoints (5000);
            gamestate.killx = player->x;
            gamestate.killy = player->y;
            NewState (ob,&s_fatdie1);
            break;

        case schabbobj:
            GivePoints (5000);
            gamestate.killx = player->x;
            gamestate.killy = player->y;
            NewState (ob,&s_schabbdie1);
            break;
        case fakeobj:
            GivePoints (2000);
            NewState (ob,&s_fakedie1);
            break;

        case mechahitlerobj:
            GivePoints (5000);
            NewState (ob,&s_mechadie1);
            break;
        case realhitlerobj:
            GivePoints (5000);
            gamestate.killx = player->x;
            gamestate.killy = player->y;
            NewState (ob,&s_hitlerdie1);
            break;
#else
        case spectreobj:
            if (ob->flags&FL_BONUS)
            {
                GivePoints (200);       // Get points once for each
                ob->flags &= ~FL_BONUS;
            }
            NewState (ob,&s_spectredie1);
            break;

        case angelobj:
            GivePoints (5000);
            NewState (ob,&s_angeldie1);
            break;

        case transobj:
            GivePoints (5000);
            NewState (ob,&s_transdie0);
            PlaceItemType (bo_key1,tilex,tiley);
            break;

        case uberobj:
            GivePoints (5000);
            NewState (ob,&s_uberdie0);
            PlaceItemType (bo_key1,tilex,tiley);
            break;

        case willobj:
            GivePoints (5000);
            NewState (ob,&s_willdie1);
            PlaceItemType (bo_key1,tilex,tiley);
            break;

        case deathobj:
            GivePoints (5000);
            NewState (ob,&s_deathdie1);
            PlaceItemType (bo_key1,tilex,tiley);
            break;
#endif
    }

    gamestate.killcount++;
    ob->flags &= ~FL_SHOOTABLE;
    actorat[ob->tilex][ob->tiley] = NULL;
    ob->flags |= FL_NONMARK;
}



/*
===================
=
= DamageActor
=
= Called when the player succesfully hits an enemy.
=
= Does damage points to enemy ob, either putting it into a stun frame or
= killing it.
=
===================
*/

void DamageActor (objtype *ob, unsigned damage)
{
    madenoise = true;

    //
    // do double damage if shooting a non attack mode actor
    //
    if ( !(ob->flags & FL_ATTACKMODE) )
        damage <<= 1;

    ob->hitpoints -= (short)damage;

    if (ob->hitpoints<=0)
        KillActor (ob);
    else
    {
        if (! (ob->flags & FL_ATTACKMODE) )
            FirstSighting (ob);             // put into combat mode

        switch (ob->obclass)                // dogs only have one hit point
        {
            case guardobj:
                if (ob->hitpoints&1)
                    NewState (ob,&s_grdpain);
                else
                    NewState (ob,&s_grdpain1);
                break;

            case officerobj:
                if (ob->hitpoints&1)
                    NewState (ob,&s_ofcpain);
                else
                    NewState (ob,&s_ofcpain1);
                break;

            case mutantobj:
                if (ob->hitpoints&1)
                    NewState (ob,&s_mutpain);
                else
                    NewState (ob,&s_mutpain1);
                break;

            case ssobj:
                if (ob->hitpoints&1)
                    NewState (ob,&s_sspain);
                else
                    NewState (ob,&s_sspain1);

                break;
        }
    }
}

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

                                CHECKSIGHT

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


/*
=====================
=
= CheckLine
=
= Returns true if a straight line between the player and ob is unobstructed
=
=====================
*/

boolean CheckLine (objtype *ob)
{
    int         x1,y1,xt1,yt1,x2,y2,xt2,yt2;
    int         x,y;
    int         xdist,ydist,xstep,ystep;
    int         partial,delta;
    int32_t     ltemp;
    int         xfrac,yfrac,deltafrac;
    unsigned    value,intercept;

    x1 = ob->x >> UNSIGNEDSHIFT;            // 1/256 tile precision
    y1 = ob->y >> UNSIGNEDSHIFT;
    xt1 = x1 >> 8;
    yt1 = y1 >> 8;

    x2 = plux;
    y2 = pluy;
    xt2 = player->tilex;
    yt2 = player->tiley;

    xdist = abs(xt2-xt1);

    if (xdist > 0)
    {
        if (xt2 > xt1)
        {
            partial = 256-(x1&0xff);
            xstep = 1;
        }
        else
        {
            partial = x1&0xff;
            xstep = -1;
        }

        deltafrac = abs(x2-x1);
        delta = y2-y1;
        ltemp = ((int32_t)delta<<8)/deltafrac;
        if (ltemp > 0x7fffl)
            ystep = 0x7fff;
        else if (ltemp < -0x7fffl)
            ystep = -0x7fff;
        else
            ystep = ltemp;
        yfrac = y1 + (((int32_t)ystep*partial) >>8);

        x = xt1+xstep;
        xt2 += xstep;
        do
        {
            y = yfrac>>8;
            yfrac += ystep;

            value = (unsigned)tilemap[x][y];
            x += xstep;

            if (!value)
                continue;

            if (value<128 || value>256)
                return false;

            //
            // see if the door is open enough
            //
            value &= ~0x80;
            intercept = yfrac-ystep/2;

            if (intercept>doorposition[value])
                return false;

        } while (x != xt2);
    }

    ydist = abs(yt2-yt1);

    if (ydist > 0)
    {
        if (yt2 > yt1)
        {
            partial = 256-(y1&0xff);
            ystep = 1;
        }
        else
        {
            partial = y1&0xff;
            ystep = -1;
        }

        deltafrac = abs(y2-y1);
        delta = x2-x1;
        ltemp = ((int32_t)delta<<8)/deltafrac;
        if (ltemp > 0x7fffl)
            xstep = 0x7fff;
        else if (ltemp < -0x7fffl)
            xstep = -0x7fff;
        else
            xstep = ltemp;
        xfrac = x1 + (((int32_t)xstep*partial) >>8);

        y = yt1 + ystep;
        yt2 += ystep;
        do
        {
            x = xfrac>>8;
            xfrac += xstep;

            value = (unsigned)tilemap[x][y];
            y += ystep;

            if (!value)
                continue;

            if (value<128 || value>256)
                return false;

            //
            // see if the door is open enough
            //
            value &= ~0x80;
            intercept = xfrac-xstep/2;

            if (intercept>doorposition[value])
                return false;
        } while (y != yt2);
    }

    return true;
}


/*
================
=
= CheckSight
=
= Checks a straight line between player and current object
=
= If the sight is ok, check alertness and angle to see if they notice
=
= returns true if the player has been spoted
=
================
*/

#define MINSIGHT        0x18000l

boolean CheckSight (objtype *ob)
{
    int32_t deltax,deltay;

    //
    // don't bother tracing a line if the area isn't connected to the player's
    //
    if (!areabyplayer[ob->areanumber])
        return false;

    //
    // if the player is real close, sight is automatic
    //
    deltax = player->x - ob->x;
    deltay = player->y - ob->y;

    if (deltax > -MINSIGHT && deltax < MINSIGHT
        && deltay > -MINSIGHT && deltay < MINSIGHT)
        return true;

    //
    // see if they are looking in the right direction
    //
    switch (ob->dir)
    {
        case north:
            if (deltay > 0)
                return false;
            break;

        case east:
            if (deltax < 0)
                return false;
            break;

        case south:
            if (deltay < 0)
                return false;
            break;

        case west:
            if (deltax > 0)
                return false;
            break;

        // check diagonal moving guards fix

        case northwest:
            if (DEMOCOND_SDL && deltay > -deltax)
                return false;
            break;

        case northeast:
            if (DEMOCOND_SDL && deltay > deltax)
                return false;
            break;

        case southwest:
            if (DEMOCOND_SDL && deltax > deltay)
                return false;
            break;

        case southeast:
            if (DEMOCOND_SDL && -deltax > deltay)
                return false;
            break;
    }

    //
    // trace a line to check for blocking tiles (corners)
    //
    return CheckLine (ob);
}


/*
===============
=
= FirstSighting
=
= Puts an actor into attack mode and possibly reverses the direction
= if the player is behind it
=
===============
*/

void FirstSighting (objtype *ob)
{
    //
    // react to the player
    //
    switch (ob->obclass)
    {
        case guardobj:
            PlaySoundLocActor(HALTSND,ob);
            NewState (ob,&s_grdchase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case officerobj:
            PlaySoundLocActor(SPIONSND,ob);
            NewState (ob,&s_ofcchase1);
            ob->speed *= 5;                 // go faster when chasing player
            break;

        case mutantobj:
            NewState (ob,&s_mutchase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case ssobj:
            PlaySoundLocActor(SCHUTZADSND,ob);
            NewState (ob,&s_sschase1);
            ob->speed *= 4;                 // go faster when chasing player
            break;

        case dogobj:
            PlaySoundLocActor(DOGBARKSND,ob);
            NewState (ob,&s_dogchase1);
            ob->speed *= 2;                 // go faster when chasing player
            break;

#ifndef SPEAR
        case bossobj:
            SD_PlaySound(GUTENTAGSND);
            NewState (ob,&s_bosschase1);
            ob->speed = SPDPATROL*3;        // go faster when chasing player
            break;

#ifndef APOGEE_1_0
        case gretelobj:
            SD_PlaySound(KEINSND);
            NewState (ob,&s_gretelchase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case giftobj:
            SD_PlaySound(EINESND);
            NewState (ob,&s_giftchase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case fatobj:
            SD_PlaySound(ERLAUBENSND);
            NewState (ob,&s_fatchase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;
#endif

        case schabbobj:
            SD_PlaySound(SCHABBSHASND);
            NewState (ob,&s_schabbchase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case fakeobj:
            SD_PlaySound(TOT_HUNDSND);
            NewState (ob,&s_fakechase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case mechahitlerobj:
            SD_PlaySound(DIESND);
            NewState (ob,&s_mechachase1);
            ob->speed *= 3;                 // go faster when chasing player
            break;

        case realhitlerobj:
            SD_PlaySound(DIESND);
            NewState (ob,&s_hitlerchase1);
            ob->speed *= 5;                 // go faster when chasing player
            break;

        case ghostobj:
            NewState (ob,&s_blinkychase1);
            ob->speed *= 2;                 // go faster when chasing player
            break;
#else
        case spectreobj:
            SD_PlaySound(GHOSTSIGHTSND);
            NewState (ob,&s_spectrechase1);
            ob->speed = 800;                        // go faster when chasing player
            break;

        case angelobj:
            SD_PlaySound(ANGELSIGHTSND);
            NewState (ob,&s_angelchase1);
            ob->speed = 1536;                       // go faster when chasing player
            break;

        case transobj:
            SD_PlaySound(TRANSSIGHTSND);
            NewState (ob,&s_transchase1);
            ob->speed = 1536;                       // go faster when chasing player
            break;

        case uberobj:
            NewState (ob,&s_uberchase1);
            ob->speed = 3000;                       // go faster when chasing player
            break;

        case willobj:
            SD_PlaySound(WILHELMSIGHTSND);
            NewState (ob,&s_willchase1);
            ob->speed = 2048;                       // go faster when chasing player
            break;

        case deathobj:
            SD_PlaySound(KNIGHTSIGHTSND);
            NewState (ob,&s_deathchase1);
            ob->speed = 2048;                       // go faster when chasing player
            break;
#endif
    }

    if (ob->distance < 0)
        ob->distance = 0;       // ignore the door opening command

    ob->flags |= FL_ATTACKMODE|FL_FIRSTATTACK;
}



/*
===============
=
= SightPlayer
=
= Called by actors that ARE NOT chasing the player.  If the player
= is detected (by sight, noise, or proximity), the actor is put into
= it's combat frame and true is returned.
=
= Incorporates a random reaction delay
=
===============
*/

boolean SightPlayer (objtype *ob)
{
    if (ob->flags & FL_ATTACKMODE)
        Quit ("An actor in ATTACKMODE called SightPlayer!");

    if (ob->temp2)
    {
        //
        // count down reaction time
        //
        ob->temp2 -= (short) tics;
        if (ob->temp2 > 0)
            return false;
        ob->temp2 = 0;                                  // time to react
    }
    else
    {
        if (!areabyplayer[ob->areanumber])
            return false;

        if (ob->flags & FL_AMBUSH)
        {
            if (!CheckSight (ob))
                return false;
            ob->flags &= ~FL_AMBUSH;
        }
        else
        {
            if (!madenoise && !CheckSight (ob))
                return false;
        }


        switch (ob->obclass)
        {
            case guardobj:
                ob->temp2 = 1+US_RndT()/4;
                break;
            case officerobj:
                ob->temp2 = 2;
                break;
            case mutantobj:
                ob->temp2 = 1+US_RndT()/6;
                break;
            case ssobj:
                ob->temp2 = 1+US_RndT()/6;
                break;
            case dogobj:
                ob->temp2 = 1+US_RndT()/8;
                break;

            case bossobj:
            case schabbobj:
            case fakeobj:
            case mechahitlerobj:
            case realhitlerobj:
            case gretelobj:
            case giftobj:
            case fatobj:
            case spectreobj:
            case angelobj:
            case transobj:
            case uberobj:
            case willobj:
            case deathobj:
                ob->temp2 = 1;
                break;
        }
        return false;
    }

    FirstSighting (ob);

    return true;
}