//-------------------------------------------------------------------------
/*
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
*/
//-------------------------------------------------------------------------

// JPLAYER.C
// Copyright (c) 1996 by Jim Norwood

#include "build.h"

#include "mytypes.h"
#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "player.h"
#include "lists.h"
#include "warp.h"
#include "quake.h"

#include "common_game.h"
#include "function.h"
#include "control.h"
#include "trigger.h"

#include "savedef.h"
#include "menus.h"
#include "network.h"
#include "pal.h"

#include "bots.h"

SWBOOL WeaponOK(PLAYERp pp);

#define MAXANGVEL 80

// From build.h
#define CLIPMASK0 (((1L)<<16)+1L)
#define CLIPMASK1 (((256L)<<16)+64L)


// PLAYER QUOTES TO OTHER PLAYERS ////////////////////////////////////////////////////////////

#define STARTALPHANUM 4608  // New SW font for typing in stuff, It's in ASCII order.
#define ENDALPHANUM   4701
#define MINIFONT      2930  // Start of small font, it's blue for good palette swapping

#define NUMPAGES 1
#define NUMOFFIRSTTIMEACTIVE 100  // You can save up to 100 strings in the message history queue

char pus, pub;  // Global text vars
char fta_quotes[NUMOFFIRSTTIMEACTIVE][64];


int gametext(int x,int y,char *t,char s,short dabits)
{
    short ac,newx;
    char centre, *oldt;

    centre = (x == (320>>1));
    newx = 0;
    oldt = t;

    if (centre)
    {
        while (*t)
        {
            if (*t == 32) {newx+=5; t++; continue; }
            else ac = *t - '!' + STARTALPHANUM;

            if (ac < STARTALPHANUM || ac > ENDALPHANUM) break;

            if (*t >= '0' && *t <= '9')
                newx += 8;
            else newx += tilesiz[ac].x;
            t++;
        }

        t = oldt;
        x = (320>>1)-(newx>>1);
    }

    while (*t)
    {
        if (*t == 32) {x+=5; t++; continue; }
        else ac = *t - '!' + STARTALPHANUM;

        if (ac < STARTALPHANUM || ac > ENDALPHANUM)
            break;

        rotatesprite(x<<16,y<<16,65536L,0,ac,s,16,dabits,0,0,xdim-1,ydim-1);

        if (*t >= '0' && *t <= '9')
            x += 8;
        else x += tilesiz[ac].x;

        t++;
    }

    return x;
}

int minigametext(int x,int y,const char *t,short dabits)
{
    short ac,newx;
    char centre;
    const char *oldt;

    centre = (x == (320>>1));
    newx = 0;
    oldt = t;

    if (centre)
    {
        while (*t)
        {
            if (*t == 32) {newx+=4; t++; continue; }
            else ac = *t - '!' + 2930;

            if ((ac < 2930 || ac > 3023) && *t != asc_Space) break;

            if (*t > asc_Space && *t < 127)
            {
                newx += tilesiz[ac].x;
            }
            else
                x += 4;

            t++;
        }

        t = oldt;
        x = (320>>1)-(newx>>1);
    }

    while (*t)
    {
        if (*t == 32) {x+=4; t++; continue; }
        else ac = *t - '!' + 2930;

        if ((ac < 2930 || ac > 3023) && *t != asc_Space) break;

        if (*t > asc_Space && *t < 127)
        {
            rotatesprite(x<<16,y<<16,65536L,0,ac,-128,17,dabits,0,0,xdim-1,ydim-1);
            x += tilesiz[ac].x;
        }
        else
            x += 4;

        t++;
    }

    return x;
}

int minitext(int x,int y,char *t,char p,char sb)
{
    short ac;

    while (*t)
    {
        *t = toupper(*t);
        if (*t == 32) {x+=5; t++; continue; }
        else ac = *t - '!' + MINIFONT;

        rotatesprite(x<<16,y<<16,65536L,0,ac,0,p,sb,0,0,xdim-1,ydim-1);
        x += 4; // tilesiz[ac].x+1;

        t++;
    }
    return x;
}

int minitextshade(int x,int y,char *t,char s,char p,char sb)
{
    short ac;

    while (*t)
    {
        *t = toupper(*t);
        if (*t == 32) {x+=5; t++; continue; }
        else ac = *t - '!' + MINIFONT;

        rotatesprite(x<<16,y<<16,65536L,0,ac,s,p,sb,0,0,xdim-1,ydim-1);
        x += 4; // tilesiz[ac].x+1;

        t++;
    }
    return x;
}

int quotebot, quotebotgoal;
short user_quote_time[MAXUSERQUOTES];
char user_quote[MAXUSERQUOTES][256];

void adduserquote(const char *daquote)
{
    int i;

    SetRedrawScreen(Player+myconnectindex);

    for (i=MAXUSERQUOTES-1; i>0; i--)
    {
        strcpy(user_quote[i],user_quote[i-1]);
        user_quote_time[i] = user_quote_time[i-1];
    }
    strcpy(user_quote[0],daquote);
    user_quote_time[0] = 180;
}

void operatefta(void)
{
    int i, j, k;

    j=MESSAGE_LINE; // Base line position on screen
    quotebot = min(quotebot,j);
    quotebotgoal = min(quotebotgoal,j);
    if (MessageInputMode)
        j -= 6; // Bump all lines up one to make room for new line
    quotebotgoal = j;
    j = quotebot;

    for (i=0; i<MAXUSERQUOTES; i++)
    {
        k = user_quote_time[i];
        if (k <= 0)
            break;

        if (gs.BorderNum <= BORDER_BAR+1)
        {
            // dont fade out
            if (k > 4)
                minigametext(320>>1,j,user_quote[i],2+8);
            else if (k > 2)
                minigametext(320>>1,j,user_quote[i],2+8+1);
            else
                minigametext(320>>1,j,user_quote[i],2+8+1+32);
        }
        else
        {
            // dont fade out
            minigametext(320>>1,j,user_quote[i],2+8);
        }

        j -= 6;
    }
}

//////////// Console Message Queue ////////////////////////////////////
int conbot, conbotgoal;
char con_quote[MAXCONQUOTES][256];

void addconquote(char *daquote)
{
    int i;

    for (i=MAXCONQUOTES-1; i>0; i--)
    {
        strcpy(con_quote[i],con_quote[i-1]);
    }
    strcpy(con_quote[0],daquote);
}

#define CON_ROT_FLAGS (ROTATE_SPRITE_CORNER|ROTATE_SPRITE_SCREEN_CLIP|ROTATE_SPRITE_NON_MASK)
void operateconfta(void)
{
    int i, j;

    if (!ConPanel) return; // If panel isn't up, don't draw anything

    // Draw the background console pic
    rotatesprite((0)<<16,(0)<<16,65536L,0,5119,0,0,CON_ROT_FLAGS,0,0,xdim-1,ydim-1);

    j=99; // Base line position on screen
    conbot = min(conbot,j);
    conbotgoal = min(conbotgoal,j);
    if (ConInputMode) j -= 6; // Bump all lines up one to make room for new line
    conbotgoal = j; j = conbot;

    for (i=0; i<MAXCONQUOTES; i++)
    {
        MNU_DrawSmallString(27, j, con_quote[i], 0, 17); // 17 = white
        j -= 6;
    }
}

// BOT STUFF ////////////////////////////////////////////////////////////////////////////////

void BOT_UseInventory(PLAYERp p, short targetitem, SW_PACKET *syn)
{
    // Try to get to item
    if (p->InventoryNum == targetitem)
        syn->bits |= (1<<SK_INV_USE);
    else
    {
        syn->bits |= (1<<SK_INV_LEFT);  // Scroll to it
        syn->bits |= (1<<SK_INV_USE); // Use whatever you're on too
    }
}

void BOT_ChooseWeapon(PLAYERp p, USERp u, SW_PACKET *syn)
{
    short weap;

    // If you have a nuke, fire it
    if (u->WeaponNum == WPN_MICRO && p->WpnRocketNuke && p->WpnRocketType != 2)
    {
        syn->bits ^= 15;
        syn->bits |= 4;
    }
    else
        for (weap=9; weap>=0; weap--)
        {
            if (weap <= u->WeaponNum) break;
            if (TEST(p->WpnFlags, BIT(weap)) && p->WpnAmmo[weap] > DamageData[weap].min_ammo)
            {
                syn->bits ^= 15;
                syn->bits |= weap;
                break;
            }
        }
}

int getspritescore(/*int snum, */int dapicnum)
{

    switch (dapicnum)
    {
    case ICON_STAR: return 5;
    case ICON_UZI: return 20;
    case ICON_UZIFLOOR: return 20;
    case ICON_LG_UZI_AMMO: return 15;
    case ICON_HEART: return 160;
    case ICON_HEART_LG_AMMO: return 60;
    case ICON_GUARD_HEAD: return 170;
    case ICON_FIREBALL_LG_AMMO: return 70;
    case ICON_ROCKET: return 100;
    case ICON_SHOTGUN: return 130;
    case ICON_LG_ROCKET: return 100;
    case ICON_LG_SHOTSHELL: return 30;
    case ICON_MICRO_GUN: return 200;
    case ICON_MICRO_BATTERY: return 100;
    case ICON_GRENADE_LAUNCHER: return 150;
    case ICON_LG_GRENADE: return 50;
    case ICON_LG_MINE: return 150;
    case ICON_RAIL_GUN: return 180;
    case ICON_RAIL_AMMO: return 80;

    case ST_QUICK_EXIT:
    case ST_QUICK_SCAN:
    case ICON_MEDKIT:
    case ICON_CHEMBOMB:
    case ICON_FLASHBOMB:
    case ICON_NUKE:
    case ICON_CALTROPS:
    case TRACK_SPRITE:
    case ST1:
    case ST2:
    case ST_QUICK_JUMP:
    case ST_QUICK_JUMP_DOWN:
    case ST_QUICK_SUPER_JUMP: return 120;  break;

        // Commented out for now, example.
//        case FREEZEAMMO: if (ps[snum].ammo_amount[FREEZE_WEAPON] < max_ammo_amount[FREEZE_WEAPON]) return(10); else return(0);
    }


    return 0;
}

static int fdmatrix[13][13] =
{
//SWRD  SHUR  UZI SHOT RPG  40MM MINE RAIL HEAD HEAD2HEAD3HEART
    { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},   //SWRD
    {1024, 512, 128, 128,2560, 128,2560, 128,2560,2560,2560, 128, 128},   //SHUR
    {2560,1024, 512, 512,2560, 128,2560,2560,1024,2560,2560,2560,2560},   //UZI
    { 512, 512, 512, 512,2560, 128,2560, 512, 512, 512, 512, 512, 512},   //SHOT
    {2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560},   //RPG
    { 512, 512, 512, 512,2048, 512,2560,2560, 512,2560,2560,2560,2560},   //40MM
    { 128, 128, 128, 128, 512, 128, 128, 128, 128, 128, 128, 128, 128},   //MINE
    {1536,1536,1536,1536,2560,1536,1536,1536,1536,1536,1536,1536,1536},   //RAIL
    {2560,1024, 512,1024,1024,1024,2560, 512,1024,2560,2560, 512, 512},   //HEAD1
    { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},   //HEAD2
    { 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512},   //HEAD3
    {1024, 512, 128, 128,2560, 512,2560,1024, 128,2560,1024,1024,1024},   //HEART
};

static int goalx[MAX_SW_PLAYERS_REG], goaly[MAX_SW_PLAYERS_REG], goalz[MAX_SW_PLAYERS_REG];
static int goalsect[MAX_SW_PLAYERS_REG], goalwall[MAX_SW_PLAYERS_REG], goalsprite[MAX_SW_PLAYERS_REG];
static int goalplayer[MAX_SW_PLAYERS_REG], clipmovecount[MAX_SW_PLAYERS_REG];
short searchsect[MAXSECTORS], searchparent[MAXSECTORS];
char dashow2dsector[(MAXSECTORS+7)>>3];

void computergetinput(int snum, SW_PACKET *syn)
{
    int i, j, k, l, x1, y1, z1, x2, y2, z2, dx, dy, nextj;
    int dist, daang, zang, fightdist, damyang, damysect;
    int startsect, endsect, splc, send, startwall, endwall;
    hitdata_t hitinfo;
    PLAYERp p;
    walltype *wal;
    int myx, myy, myz, myang, mycursectnum;
    USERp u;
    //extern SWBOOL Pachinko_Win_Cheat;

    if (!MoveSkip4) return; // Make it so the bots don't slow the game down so bad!

    p = &Player[snum];
    u = User[p->PlayerSprite];  // Set user struct

    // Copy current weapon number to player struct
    p->WpnNum = u->WeaponNum;
    if (p->WpnNum >= MAX_WEAPONS) p->WpnNum = MAX_WEAPONS-1;

    // Init local position variables
    myx = p->posx;
    myy = p->posy;
    myz = p->posz;
    myang = fix16_to_int(p->q16ang);
    mycursectnum = p->cursectnum;

    // Reset input bits
    syn->vel = 0;
    syn->svel = 0;
    syn->q16angvel = 0;
    syn->q16aimvel = 0;
    syn->bits = 0;

    x1 = p->posx;
    y1 = p->posy;
    z1 = p->posz;

    damyang = fix16_to_int(p->q16ang);
    damysect = sprite[p->PlayerSprite].sectnum;
    if ((numplayers >= 2) && (snum == myconnectindex))
    { x1 = myx; y1 = myy; z1 = myz+PLAYER_HEIGHT; damyang = myang; damysect = mycursectnum; }

    // Always operate everything
    syn->bits |= (1<<SK_OPERATE);

    // If bot can't see the goal enemy, set target to himself so that he
    // will pick a new target
    if (TEST(Player[goalplayer[snum]].Flags, PF_DEAD) || STD_RANDOM_RANGE(1000) > 800)
        goalplayer[snum] = snum;
    else
    {
        x2 = Player[goalplayer[snum]].posx;
        y2 = Player[goalplayer[snum]].posy;
        z2 = Player[goalplayer[snum]].posz;
        if (!FAFcansee(x1,y1,z1-(48<<8),damysect,x2,y2,z2-(48<<8),sprite[Player[goalplayer[snum]].PlayerSprite].sectnum))
            goalplayer[snum] = snum;
    }

    // Pick a new target player if goal is dead or target is itself
    if (goalplayer[snum] == snum)
    {
        j = 0x7fffffff;
        for (i=connecthead; i>=0; i=connectpoint2[i])
            if (i != snum)
            {
                if (TEST(Player[i].Flags, PF_DEAD))
                    continue;

                x2 = Player[i].posx;
                y2 = Player[i].posy;
                z2 = Player[i].posz;

                if (!FAFcansee(x1,y1,z1-(48<<8),damysect,x2,y2,z2-(48<<8),sprite[Player[i].PlayerSprite].sectnum))
                    continue;

                dist = ksqrt((sprite[Player[i].PlayerSprite].x-x1)*(sprite[Player[i].PlayerSprite].x-x1)+(sprite[Player[i].PlayerSprite].y-y1)*(sprite[Player[i].PlayerSprite].y-y1));

                if (dist < j) { j = dist; goalplayer[snum] = i; }
            }
    }

    // Pick a weapon
    BOT_ChooseWeapon(p, u, syn);

    // x2,y2,z2 is the coordinates of the target sprite
    x2 = Player[goalplayer[snum]].posx;
    y2 = Player[goalplayer[snum]].posy;
    z2 = Player[goalplayer[snum]].posz;

    // If bot is dead, either barf or respawn
    if (TEST(p->Flags, PF_DEAD))
    {
        if (STD_RANDOM_RANGE(1000) > 990)
        {
            syn->bits |= (1<<SK_SPACE_BAR);  // Respawn
        }
        else
            syn->bits |= (1<<SK_SHOOT);    // Try to barf
    }

    // Need Health?
    if (u->Health < p->MaxHealth)
        BOT_UseInventory(p, INVENTORY_MEDKIT, syn);

    // Check the missile stat lists to see what's being fired and
    // take the appropriate action
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_MISSILE], j, nextj)
    {
        switch (sprite[j].picnum)
        {
        case FIREBALL: k = 0; break;
        case BOLT_THINMAN_R0:
            k = 0;
            syn->bits |= (1<<SK_JUMP); // Always jump when rockets being fired!
            break;
        default: k = 0; break;
        }
        if (k)
        {
            hitinfo.pos.x = sprite[j].x;
            hitinfo.pos.y = sprite[j].y;
            hitinfo.pos.z = sprite[j].z;
            for (l=0; l<=8; l++)
            {
                if (tmulscale11(hitinfo.pos.x-x1,hitinfo.pos.x-x1,hitinfo.pos.y-y1,hitinfo.pos.y-y1,(hitinfo.pos.z-z1)>>4,(hitinfo.pos.z-z1)>>4) < 3072)
                {
                    dx = sintable[(sprite[j].ang+512)&2047];
                    dy = sintable[sprite[j].ang&2047];
                    if ((x1-hitinfo.pos.x)*dy > (y1-hitinfo.pos.y)*dx) i = -k*512; else i = k*512;
                    syn->vel -= mulscale17(dy,i);
                    syn->svel += mulscale17(dx,i);
                }

                if (l < 7)
                {
                    hitinfo.pos.x += (mulscale14(sprite[j].xvel,sintable[(sprite[j].ang+512)&2047])<<2);
                    hitinfo.pos.y += (mulscale14(sprite[j].xvel,sintable[sprite[j].ang&2047])<<2);
                    hitinfo.pos.z += (sprite[j].zvel<<2);
                }
                else
                {
                    hitscan((vec3_t *)&sprite[j],sprite[j].sectnum,
                            mulscale14(sprite[j].xvel,sintable[(sprite[j].ang+512)&2047]),
                            mulscale14(sprite[j].xvel,sintable[sprite[j].ang&2047]),
                            (int)sprite[j].zvel,
                            &hitinfo,CLIPMASK1);
                }
            }
        }
    }


    if (!TEST(Player[goalplayer[snum]].Flags, PF_DEAD) && snum != goalplayer[snum] &&
        ((FAFcansee(x1,y1,z1-(24<<8),damysect,x2,y2,z2-(24<<8),sprite[Player[goalplayer[snum]].PlayerSprite].sectnum)) ||
         (FAFcansee(x1,y1,z1-(48<<8),damysect,x2,y2,z2-(48<<8),sprite[Player[goalplayer[snum]].PlayerSprite].sectnum))))
    {
        // Shoot how often by skill level
        short shootrnd=0;

        shootrnd = STD_RANDOM_RANGE(1000);

        if ((Skill == 0 && shootrnd > 990) ||
            (Skill == 1 && shootrnd > 550) ||
            (Skill == 2 && shootrnd > 350) ||
            (Skill == 3))
            syn->bits |= (1<<SK_SHOOT);
        else
            syn->bits &= ~(1<<SK_SHOOT);

        // Jump sometimes, to try to be evasive
        if (STD_RANDOM_RANGE(256) > 252)
            syn->bits |= (1<<SK_JUMP);

        // Make sure selected weapon is in range
        //ASSERT(p->WpnNum < MAX_WEAPONS);
        //ASSERT(Player[goalplayer[snum]].WpnNum < MAX_WEAPONS);

        // Only fire explosive type weaps if you are not too close to the target!
        if (u->WeaponNum == WPN_MICRO || u->WeaponNum == WPN_GRENADE || u->WeaponNum == WPN_RAIL)
        {
            vec3_t hit_pos = { x1, y1, z1-PLAYER_HEIGHT };
            hitscan(&hit_pos,damysect,sintable[(damyang+512)&2047],sintable[damyang&2047],
                    (100-fix16_to_int(p->q16horiz)-fix16_to_int(p->q16horizoff))*32,&hitinfo,CLIPMASK1);
            if ((hitinfo.pos.x-x1)*(hitinfo.pos.x-x1)+(hitinfo.pos.y-y1)*(hitinfo.pos.y-y1) < 2560*2560) syn->bits &= ~(1<<SK_SHOOT);
        }

        // Get fighting distance based on you and your opponents current weapons
        fightdist = fdmatrix[p->WpnNum][Player[goalplayer[snum]].WpnNum];
        if (fightdist < 128) fightdist = 128;

        // Figure out your distance from the enemy target sprite
        dist = ksqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); if (dist == 0) dist = 1;
        daang = NORM_ANGLE(getangle(x2+(Player[goalplayer[snum]].xvect>>14)-x1,y2+(Player[goalplayer[snum]].yvect>>14)-y1));

        zang = 100-((z2-z1)*8)/dist;
        fightdist = max(fightdist,(klabs(z2-z1)>>4));

        hitinfo.pos.x = x2+((x1-x2)*fightdist/dist);
        hitinfo.pos.y = y2+((y1-y2)*fightdist/dist);
        syn->vel += (hitinfo.pos.x-x1)*2047/dist;
        syn->svel += (hitinfo.pos.y-y1)*2047/dist;

        //Strafe attack
        if (fightdist)
        {
            j = (int32_t) totalclock+snum*13468;
            i = sintable[(j<<6)&2047];
            i += sintable[((j+4245)<<5)&2047];
            i += sintable[((j+6745)<<4)&2047];
            i += sintable[((j+15685)<<3)&2047];
            dx = sintable[(sprite[Player[goalplayer[snum]].PlayerSprite].ang+512)&2047];
            dy = sintable[sprite[Player[goalplayer[snum]].PlayerSprite].ang&2047];
            if ((x1-x2)*dy > (y1-y2)*dx) i += 8192; else i -= 8192;
            syn->vel += ((sintable[(daang+1024)&2047]*i)>>17);
            syn->svel += ((sintable[(daang+512)&2047]*i)>>17);
        }

        // Make aiming and running speed suck by skill level
        if (Skill == 0)
        {
            daang = NORM_ANGLE((daang-256) + STD_RANDOM_RANGE(512));
            syn->vel -= syn->vel/2;
            syn->svel -= syn->svel/2;
        }
        else if (Skill == 1)
        {
            daang = NORM_ANGLE((daang-128) + STD_RANDOM_RANGE(256));
            syn->vel -= syn->vel/8;
            syn->svel -= syn->svel/8;
        }
        else if (Skill == 2)
            daang = NORM_ANGLE((daang-64) + STD_RANDOM_RANGE(128));

        // Below formula fails in certain cases
        //syn->q16angvel = fix16_from_int(min(max((((daang+1024-damyang)&2047)-1024)>>1,-MAXANGVEL),MAXANGVEL)); //was 127
        p->q16ang = fix16_from_int(daang);
        syn->q16aimvel = fix16_from_int(min(max((zang-fix16_to_int(p->q16horiz))>>1,-PLAYER_HORIZ_MAX),PLAYER_HORIZ_MAX));
        // Sets type of aiming, auto aim for bots
        syn->bits |= (1<<SK_AUTO_AIM);
        return;
    }

    goalsect[snum] = -1;

#if 1
    if (goalsect[snum] < 0)
    {
        goalwall[snum] = -1;
        startsect = sprite[p->PlayerSprite].sectnum;
        endsect = sprite[Player[goalplayer[snum]].PlayerSprite].sectnum;

        clearbufbyte(dashow2dsector,(MAXSECTORS+7)>>3,0L);
        searchsect[0] = startsect;
        searchparent[0] = -1;
        dashow2dsector[startsect>>3] |= (1<<(startsect&7));
        for (splc=0,send=1; splc<send; splc++)
        {
            startwall = sector[searchsect[splc]].wallptr;
            endwall = startwall + sector[searchsect[splc]].wallnum;
            for (i=startwall,wal=&wall[startwall]; i<endwall; i++,wal++)
            {
                j = wal->nextsector; if (j < 0) continue;

                dx = ((wall[wal->point2].x+wal->x)>>1);
                dy = ((wall[wal->point2].y+wal->y)>>1);
                if ((getceilzofslope(j,dx,dy) > getflorzofslope(j,dx,dy)-(28<<8)) && ((sector[j].lotag < 15) || (sector[j].lotag > 22)))
                    continue;
                if (getflorzofslope(j,dx,dy) < getflorzofslope(searchsect[splc],dx,dy)-(72<<8))
                    continue;
                if ((dashow2dsector[j>>3]&(1<<(j&7))) == 0)
                {
                    dashow2dsector[j>>3] |= (1<<(j&7));
                    searchsect[send] = (short)j;
                    searchparent[send] = (short)splc;
                    send++;
                    if (j == endsect)
                    {
                        clearbufbyte(dashow2dsector,(MAXSECTORS+7)>>3,0L);
                        for (k=send-1; k>=0; k=searchparent[k])
                            dashow2dsector[searchsect[k]>>3] |= (1<<(searchsect[k]&7));

                        for (k=send-1; k>=0; k=searchparent[k])
                            if (!searchparent[k]) break;

                        goalsect[snum] = searchsect[k];
                        startwall = sector[goalsect[snum]].wallptr;
                        endwall = startwall+sector[goalsect[snum]].wallnum;
                        hitinfo.pos.x = hitinfo.pos.y = 0;
                        for (i=startwall; i<endwall; i++)
                        {
                            hitinfo.pos.x += wall[i].x;
                            hitinfo.pos.y += wall[i].y;
                        }
                        hitinfo.pos.x /= (endwall-startwall);
                        hitinfo.pos.y /= (endwall-startwall);

                        startwall = sector[startsect].wallptr;
                        endwall = startwall+sector[startsect].wallnum;
                        l = 0; k = startwall;
                        for (i=startwall; i<endwall; i++)
                        {
                            if (wall[i].nextsector != goalsect[snum]) continue;
                            dx = wall[wall[i].point2].x-wall[i].x;
                            dy = wall[wall[i].point2].y-wall[i].y;

                            //if (dx*(y1-wall[i].y) <= dy*(x1-wall[i].x))
                            //   if (dx*(y2-wall[i].y) >= dy*(x2-wall[i].x))
                            if ((hitinfo.pos.x-x1)*(wall[i].y-y1) <= (hitinfo.pos.y-y1)*(wall[i].x-x1))
                                if ((hitinfo.pos.x-x1)*(wall[wall[i].point2].y-y1) >= (hitinfo.pos.y-y1)*(wall[wall[i].point2].x-x1))
                                { k = i; break; }

                            dist = ksqrt(dx*dx+dy*dy);
                            if (dist > l) { l = dist; k = i; }
                        }
                        goalwall[snum] = k;
                        daang = ((getangle(wall[wall[k].point2].x-wall[k].x,wall[wall[k].point2].y-wall[k].y)+1536)&2047);
                        goalx[snum] = ((wall[k].x+wall[wall[k].point2].x)>>1)+(sintable[(daang+512)&2047]>>8);
                        goaly[snum] = ((wall[k].y+wall[wall[k].point2].y)>>1)+(sintable[daang&2047]>>8);
                        goalz[snum] = sector[goalsect[snum]].floorz-(32<<8);
                        break;
                    }
                }
            }

#if 0
            for (i=headspritesect[searchsect[splc]]; i>=0; i=nextspritesect[i])
                if (sprite[i].lotag == 7)
                {
                    j = sprite[sprite[i].owner].sectnum;
                    if ((dashow2dsector[j>>3]&(1<<(j&7))) == 0)
                    {
                        dashow2dsector[j>>3] |= (1<<(j&7));
                        searchsect[send] = (short)j;
                        searchparent[send] = (short)splc;
                        send++;
                        if (j == endsect)
                        {
                            clearbufbyte(dashow2dsector,(MAXSECTORS+7)>>3,0L);
                            for (k=send-1; k>=0; k=searchparent[k])
                                dashow2dsector[searchsect[k]>>3] |= (1<<(searchsect[k]&7));

                            for (k=send-1; k>=0; k=searchparent[k])
                                if (!searchparent[k]) break;

                            goalsect[snum] = searchsect[k];
                            startwall = sector[startsect].wallptr;
                            endwall = startwall+sector[startsect].wallnum;
                            l = 0; k = startwall;
                            for (i=startwall; i<endwall; i++)
                            {
                                dx = wall[wall[i].point2].x-wall[i].x;
                                dy = wall[wall[i].point2].y-wall[i].y;
                                dist = ksqrt(dx*dx+dy*dy);
                                if ((wall[i].nextsector == goalsect[snum]) && (dist > l))
                                { l = dist; k = i; }
                            }
                            goalwall[snum] = k;
                            daang = ((getangle(wall[wall[k].point2].x-wall[k].x,wall[wall[k].point2].y-wall[k].y)+1536)&2047);
                            goalx[snum] = ((wall[k].x+wall[wall[k].point2].x)>>1)+(sintable[(daang+512)&2047]>>8);
                            goaly[snum] = ((wall[k].y+wall[wall[k].point2].y)>>1)+(sintable[daang&2047]>>8);
                            goalz[snum] = sector[goalsect[snum]].floorz-(32<<8);
                            break;
                        }
                    }
                }
            if (goalwall[snum] >= 0) break;
#endif
        }
    }

    if ((goalsect[snum] < 0) || (goalwall[snum] < 0))
    {
        if (goalsprite[snum] < 0)
        {
            for (k=0; k<4; k++)
            {
                i = (rand()%numsectors);
                for (j=headspritesect[i]; j>=0; j=nextspritesect[j])
                {
                    if ((sprite[j].xrepeat <= 0) || (sprite[j].yrepeat <= 0)) continue;
                    if (getspritescore(/*snum,*/sprite[j].picnum) <= 0) continue;
                    if (FAFcansee(x1,y1,z1-(32<<8),damysect,sprite[j].x,sprite[j].y,sprite[j].z-(4<<8),i))
                    { goalx[snum] = sprite[j].x; goaly[snum] = sprite[j].y; goalz[snum] = sprite[j].z; goalsprite[snum] = j; break; }
                }
            }
        }
        x2 = goalx[snum];
        y2 = goaly[snum];
        dist = ksqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); if (!dist) return;
        daang = getangle(x2-x1,y2-y1);
        syn->vel += (x2-x1)*2047/dist;
        syn->svel += (y2-y1)*2047/dist;
        syn->q16angvel = fix16_from_int(min(max((((daang+1024-damyang)&2047)-1024)>>3,-MAXANGVEL),MAXANGVEL));
    }
    else
        goalsprite[snum] = -1;
#endif

    hitinfo.pos.x = p->posx; hitinfo.pos.y = p->posy; hitinfo.pos.z = p->posz; hitinfo.sect = p->cursectnum;
    i = clipmove(&hitinfo.pos,&hitinfo.sect,p->xvect,p->yvect,164L,4L<<8,4L<<8,CLIPMASK0);
    if (!i)
    {
        hitinfo.pos.x = p->posx; hitinfo.pos.y = p->posy; hitinfo.pos.z = p->posz+(24<<8); hitinfo.sect = p->cursectnum;
        i = clipmove(&hitinfo.pos,&hitinfo.sect,p->xvect,p->yvect,164L,4L<<8,4L<<8,CLIPMASK0);
    }
    if (i)
    {
        clipmovecount[snum]++;

        j = 0;
        if ((i&0xc000) == 32768)  //Hit a wall (49152 for sprite)
            if (wall[i&(MAXWALLS-1)].nextsector >= 0)
            {
                if (getflorzofslope(wall[i&(MAXWALLS-1)].nextsector,p->posx,p->posy) <= p->posz+(24<<8)) j |= 1;
                if (getceilzofslope(wall[i&(MAXWALLS-1)].nextsector,p->posx,p->posy) >= p->posz-(24<<8)) j |= 2;
            }
        if ((i&0xc000) == 49152) j = 1;
        // Jump
        if (j&1) if (clipmovecount[snum] == 4) syn->bits |= (1<<SK_JUMP);
        // Crawl
        if (j&2) syn->bits |= (1<<SK_CRAWL);

        //Strafe attack
        daang = getangle(x2-x1,y2-y1);
        if ((i&0xc000) == 32768)
            daang = getangle(wall[wall[i&(MAXWALLS-1)].point2].x-wall[i&(MAXWALLS-1)].x,wall[wall[i&(MAXWALLS-1)].point2].y-wall[i&(MAXWALLS-1)].y);
        j = (int32_t) totalclock+snum*13468;
        i = sintable[(j<<6)&2047];
        i += sintable[((j+4245)<<5)&2047];
        i += sintable[((j+6745)<<4)&2047];
        i += sintable[((j+15685)<<3)&2047];
        syn->vel += ((sintable[(daang+1024)&2047]*i)>>17);
        syn->svel += ((sintable[(daang+512)&2047]*i)>>17);

        // Try to Open
        if ((clipmovecount[snum]&31) == 2) syn->bits |= (1<<SK_OPERATE);
        // *TODO: In Duke, this is Kick, but I need to select sword then fire in SW
//        if ((clipmovecount[snum]&31) == 17) syn->bits |= (1<<22);
        if (clipmovecount[snum] > 32) { goalsect[snum] = -1; goalwall[snum] = -1; clipmovecount[snum] = 0; }

        goalsprite[snum] = -1;
    }
    else
        clipmovecount[snum] = 0;

    if ((goalsect[snum] >= 0) && (goalwall[snum] >= 0))
    {
        x2 = goalx[snum];
        y2 = goaly[snum];
        dist = ksqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); if (!dist) return;
        daang = getangle(x2-x1,y2-y1);
        if ((goalwall[snum] >= 0) && (dist < 4096))
            daang = ((getangle(wall[wall[goalwall[snum]].point2].x-wall[goalwall[snum]].x,wall[wall[goalwall[snum]].point2].y-wall[goalwall[snum]].y)+1536)&2047);
        syn->vel += (x2-x1)*2047/dist;
        syn->svel += (y2-y1)*2047/dist;
        syn->q16angvel = fix16_from_int(min(max((((daang+1024-damyang)&2047)-1024)>>3,-MAXANGVEL),MAXANGVEL));
    }


    /*
        // Use extended bot logic for navigation through level
        goalsect[snum] = -1;
        goalwall[snum] = -1;
        goalsprite[snum] = -1;

        // Go to a goal place
        if ((goalsect[snum] >= 0) && (goalwall[snum] >= 0))
        {
            x2 = goalx[snum];
            y2 = goaly[snum];
            dist = ksqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); if (!dist) return;
            daang = getangle(x2-x1,y2-y1);
            if ((goalwall[snum] >= 0) && (dist < 4096))
                daang = ((getangle(wall[wall[goalwall[snum]].point2].x-wall[goalwall[snum]].x,wall[wall[goalwall[snum]].point2].y-wall[goalwall[snum]].y)+1536)&2047);
            syn->vel += (x2-x1)*2047/dist;
            syn->svel += (y2-y1)*2047/dist;
            syn->q16angvel = fix16_from_int(min(max((((daang+1024-damyang)&2047)-1024)>>3,-MAXANGVEL),MAXANGVEL));
        }
    */
}

