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

// 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.x = sprite[j].x;
            hitinfo.y = sprite[j].y;
            hitinfo.z = sprite[j].z;
            for (l=0; l<=8; l++)
            {
                if (tmulscale11(hitinfo.x-x1,hitinfo.x-x1,hitinfo.y-y1,hitinfo.y-y1,(hitinfo.z-z1)>>4,(hitinfo.z-z1)>>4) < 3072)
                {
                    dx = sintable[(sprite[j].ang+512)&2047];
                    dy = sintable[sprite[j].ang&2047];
                    if ((x1-hitinfo.x)*dy > (y1-hitinfo.y)*dx) i = -k*512; else i = k*512;
                    syn->vel -= mulscale17(dy,i);
                    syn->svel += mulscale17(dx,i);
                }

                if (l < 7)
                {
                    hitinfo.x += (mulscale14(sprite[j].xvel,sintable[(sprite[j].ang+512)&2047])<<2);
                    hitinfo.y += (mulscale14(sprite[j].xvel,sintable[sprite[j].ang&2047])<<2);
                    hitinfo.z += (sprite[j].zvel<<2);
                }
                else
                {
                    hitscan(&sprite[j].xyz,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.x-x1)*(hitinfo.x-x1)+(hitinfo.y-y1)*(hitinfo.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.x = x2+((x1-x2)*fightdist/dist);
        hitinfo.y = y2+((y1-y2)*fightdist/dist);
        syn->vel += (hitinfo.x-x1)*2047/dist;
        syn->svel += (hitinfo.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.x = hitinfo.y = 0;
                        for (i=startwall; i<endwall; i++)
                        {
                            hitinfo.x += wall[i].x;
                            hitinfo.y += wall[i].y;
                        }
                        hitinfo.x /= (endwall-startwall);
                        hitinfo.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.x-x1)*(wall[i].y-y1) <= (hitinfo.y-y1)*(wall[i].x-x1))
                                if ((hitinfo.x-x1)*(wall[wall[i].point2].y-y1) >= (hitinfo.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 = (wrand()%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.x = p->posx; hitinfo.y = p->posy; hitinfo.z = p->posz; hitinfo.sect = p->cursectnum;
    i = clipmove(&hitinfo.xyz,&hitinfo.sect,p->xvect,p->yvect,164L,4L<<8,4L<<8,CLIPMASK0);
    if (!i)
    {
        hitinfo.x = p->posx; hitinfo.y = p->posy; hitinfo.z = p->posz+(24<<8); hitinfo.sect = p->cursectnum;
        i = clipmove(&hitinfo.xyz,&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));
        }
    */
}

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

// JSECTOR.C
// This is all Jim's programming having to do with sectors.

#include "build.h"

#include "keys.h"
#include "names2.h"
#include "jnames.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "sector.h"
#include "player.h"
#include "sprite.h"
#include "reserve.h"
#include "jsector.h"
#include "jtags.h"
#include "lists.h"
#include "pal.h"
#include "parent.h"

// V A R I A B L E   D E C L A R A T I O N S //////////////////////////////////////////////////////

MIRRORTYPE mirror[MAXMIRRORS];

short mirrorcnt; //, floormirrorcnt;
//short floormirrorsector[MAXMIRRORS];
SWBOOL mirrorinview;
uint32_t oscilationclock;

// Voxel stuff
//SWBOOL bVoxelsOn = TRUE;                  // Turn voxels on by default
SWBOOL bSpinBobVoxels = FALSE;            // Do twizzly stuff to voxels, but
// not by default
SWBOOL bAutoSize = TRUE;                  // Autosizing on/off

//extern int chainnumpages;
extern AMB_INFO ambarray[];
extern short NormalVisibility;

extern ParentalStruct aVoxelArray[MAXTILES];

// F U N C T I O N S //////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////
//  SpawnWallSound
/////////////////////////////////////////////////////
void
SpawnWallSound(short sndnum, short i)
{
    short SpriteNum;
    vec3_t mid;
    SPRITEp sp;
    int handle;

    SpriteNum = COVERinsertsprite(0, STAT_DEFAULT);
    if (SpriteNum < 0)
        return;

    sp = &sprite[SpriteNum];
    sp->cstat = 0;
    sp->extra = 0;
    // Get wall midpoint for offset in mirror view
    mid.x = (wall[i].x + wall[wall[i].point2].x) / 2;
    mid.y = (wall[i].y + wall[wall[i].point2].y) / 2;
    mid.z = (sector[wall[i].nextsector].ceilingz + sector[wall[i].nextsector].floorz) / 2;
    setspritez(SpriteNum, &mid);
    sp = &sprite[SpriteNum];

    handle = PlaySound(sndnum, &sp->x, &sp->y, &sp->z, v3df_dontpan | v3df_doppler);
    if (handle != -1)
        Set3DSoundOwner(SpriteNum);
}

short
CheckTileSound(short picnum)
{
    short sndnum = -1;

    switch (picnum)
    {
    case 163:                           // Sizzly Lava
    case 167:
        sndnum = DIGI_VOLCANOSTEAM1;
        break;
    case 175:                           // Flowing Lava
        sndnum = DIGI_ERUPTION;
        break;
    case 179:                           // Bubbly lava
        sndnum = DIGI_LAVAFLOW1;
        break;
    case 300:                           // Water fall tile
        sndnum = DIGI_WATERFALL1;
        break;
    case 334:                           // Teleporter Pad
        sndnum = DIGI_ENGROOM1;
        break;
    case 2690:                          // Jet engine fan
        sndnum = DIGI_JET;
        break;
    case 2672:                          // X-Ray Machine engine
        sndnum = DIGI_ENGROOM5;
        break;
    case 768:                           // Electricity
//          sndnum = DIGI_;
        break;
    case 2714:                          // Pachinko Machine
//          sndnum = DIGI_;
        break;
    case 2782:                          // Telepad
        sndnum = DIGI_ENGROOM4;
        break;
    case 3382:                          // Gears
        sndnum = DIGI_ENGROOM5;
        break;
    case 2801:                          // Computers
    case 2804:
    case 2807:
    case 3352:
    case 3385:
    case 3389:
    case 3393:
    case 3397:
    case 3401:
    case 3405:
//          sndnum = DIGI_;
        break;
    case 3478:                          // Radar screen
//          sndnum = DIGI_;
        break;
    default:
        sndnum = -1;
        break;
    }
    return sndnum;
}

/////////////////////////////////////////////////////
//  Initialize any of my special use sprites
/////////////////////////////////////////////////////
void
JS_SpriteSetup(void)
{
    SPRITEp sp;
    short SpriteNum = 0, NextSprite;
    USERp u;
    short i;
    int handle;

    TRAVERSE_SPRITE_STAT(headspritestat[0], SpriteNum, NextSprite)
    {
        short tag;

        sp = &sprite[SpriteNum];
        tag = sp->hitag;

        // Non static camera. Camera sprite will be drawn!
        if (tag == MIRROR_CAM && sprite[SpriteNum].picnum != ST1)
        {
            // Just change it to static, sprite has all the info I need
//          u = SpawnUser(SpriteNum, sp->picnum, NULL);
//          RESET(sp->cstat, CSTAT_SPRITE_BLOCK);
//          SET(sp->cstat, CSTAT_SPRITE_BLOCK_HITSCAN);
            change_sprite_stat(SpriteNum, STAT_SPAWN_SPOT);
        }

        switch (sprite[SpriteNum].picnum)
        {
        case ST1:
            if (tag == MIRROR_CAM)
            {
                // Just change it to static, sprite has all the info I need
                // ST1 cameras won't move with SOBJ's!
                change_sprite_stat(SpriteNum, STAT_ST1);
            }
            else if (tag == MIRROR_SPAWNSPOT)
            {
                // Just change it to static, sprite has all the info I need
                change_sprite_stat(SpriteNum, STAT_ST1);
            }
            else if (tag == AMBIENT_SOUND)
            {
                change_sprite_stat(SpriteNum, STAT_AMBIENT);
                // PlaySound(sp->lotag, &sp->x, &sp->y, &sp->z, v3df_ambient
                // | v3df_init | v3df_doppler);
            }
            else if (tag == TAG_ECHO_SOUND)
            {
                change_sprite_stat(SpriteNum, STAT_ECHO);
            }
            else if (tag == TAG_DRIPGEN)
            {
                ANIMATOR GenerateDrips;

                u = SpawnUser(SpriteNum, 0, NULL);

                ASSERT(u != NULL);
                u->RotNum = 0;
                u->WaitTics = sp->lotag * 120;

                u->ActorActionFunc = GenerateDrips;

                change_sprite_stat(SpriteNum, STAT_NO_STATE);
                SET(sp->cstat, CSTAT_SPRITE_INVISIBLE);
            }
            break;
        // Sprites in editart that should play ambient sounds
        // automatically
        case 380:
        case 396:
        case 430:
        case 443:
        case 512:
        case 521:
        case 541:
        case 2720:
        case 3143:
        case 3157:
            handle = PlaySound(DIGI_FIRE1, &sp->x, &sp->y, &sp->z, v3df_follow|v3df_dontpan|v3df_doppler);
            if (handle != -1)
                Set3DSoundOwner(SpriteNum);
            break;
        case 795:
        case 880:
            handle = PlaySound(DIGI_WATERFLOW1, &sp->x, &sp->y, &sp->z, v3df_follow|v3df_dontpan|v3df_doppler);
            if (handle != -1)
                Set3DSoundOwner(SpriteNum);
            break;
        case 460:  // Wind Chimes
            handle = PlaySound(79, &sp->x, &sp->y, &sp->z, v3df_ambient | v3df_init
                               | v3df_doppler | v3df_follow);
            if (handle != -1)
                Set3DSoundOwner(SpriteNum);
            break;

        }
    }
    // Check for certain walls to make sounds
    for (i = 0; i < numwalls; i++)
    {
        short picnum;

        picnum = wall[i].picnum;

        // Set the don't stick bit for liquid tiles
        switch (picnum)
        {
        case 175:
        case 179:
        case 300:
        case 320:
        case 330:
        case 352:
        case 780:
        case 890:
        case 2608:
        case 2616:
            //case 3834:
            SET(wall[i].extra, WALLFX_DONT_STICK);
            break;
        }

#if 0
        short sndnum;
        if ((sndnum = CheckTileSound(picnum)) != -1)
        {
            SpawnWallSound(sndnum, i);
        }
        picnum = wall[i].overpicnum;
        if ((sndnum = CheckTileSound(picnum)) != -1)
        {
            SpawnWallSound(sndnum, i);
        }
#endif
    }
}

/////////////////////////////////////////////////////
//  Initialize the mirrors
/////////////////////////////////////////////////////
void
JS_InitMirrors(void)
{
    short startwall, endwall;
    int i, j, s;
    short SpriteNum = 0, NextSprite;
    SWBOOL Found_Cam = FALSE;

    // Set all the mirror struct values to -1
    memset(mirror, 0xFF, sizeof(mirror));

    mirrorinview = FALSE;               // Initially set global mirror flag
    // to no mirrors seen

    // Scan wall tags for mirrors
    mirrorcnt = 0;
    tilesiz[MIRROR].x = 0;
    tilesiz[MIRROR].y = 0;
    oscilationclock = ototalclock;

    for (i = 0; i < MAXMIRRORS; i++)
    {
        tilesiz[i + MIRRORLABEL].x = 0;
        tilesiz[i + MIRRORLABEL].y = 0;
        mirror[i].campic = -1;
        mirror[i].camsprite = -1;
        mirror[i].camera = -1;
        mirror[i].ismagic = FALSE;
    }

    for (i = 0; i < numwalls; i++)
    {
        s = wall[i].nextsector;
        if ((s >= 0) && (wall[i].overpicnum == MIRROR) && (wall[i].cstat & 32))
        {
            if ((sector[s].floorstat & 1) == 0)
            {
                if (mirrorcnt >= MAXMIRRORS)
                {
                    buildprintf("MAXMIRRORS reached! Skipping mirror wall[%d]\n", i);
                    wall[i].overpicnum = sector[s].ceilingpicnum;
                    continue;
                }

                wall[i].overpicnum = MIRRORLABEL + mirrorcnt;
                wall[i].picnum = MIRRORLABEL + mirrorcnt;
                sector[s].ceilingpicnum = MIRRORLABEL + mirrorcnt;
                sector[s].floorpicnum = MIRRORLABEL + mirrorcnt;
                sector[s].floorstat |= 1;
                mirror[mirrorcnt].mirrorwall = i;
                mirror[mirrorcnt].mirrorsector = s;
                mirror[mirrorcnt].numspawnspots = 0;
                mirror[mirrorcnt].ismagic = FALSE;
                do if (wall[i].lotag == TAG_WALL_MAGIC_MIRROR)
                {
                    short ii, nextii;
                    SPRITEp sp;

                    Found_Cam = FALSE;
                    TRAVERSE_SPRITE_STAT(headspritestat[STAT_ST1], ii, nextii)
                    {
                        sp = &sprite[ii];
                        // if correct type and matches
                        if (sp->hitag == MIRROR_CAM && sp->lotag == wall[i].hitag)
                        {
                            mirror[mirrorcnt].camera = ii;
                            // Set up camera varialbes
                            SP_TAG5(sp) = sp->ang;      // Set current angle to
                            // sprite angle
                            Found_Cam = TRUE;
                        }
                    }

                    ii = nextii = 0;
                    TRAVERSE_SPRITE_STAT(headspritestat[STAT_SPAWN_SPOT], ii, nextii)
                    {

                        sp = &sprite[ii];

                        // if correct type and matches
                        if (sp->hitag == MIRROR_CAM && sp->lotag == wall[i].hitag)
                        {
                            mirror[mirrorcnt].camera = ii;
                            // Set up camera varialbes
                            SP_TAG5(sp) = sp->ang;      // Set current angle to
                            // sprite angle
                            Found_Cam = TRUE;
                        }
                    }

                    if (!Found_Cam)
                    {
                        buildprintf("Could not find the camera view sprite for match %d\n",TrackerCast(wall[i].hitag));
                        buildprintf("Map Coordinates: x = %d, y = %d\n",TrackerCast(wall[i].x),TrackerCast(wall[i].y));
                        break;
                    }

                    mirror[mirrorcnt].ismagic = TRUE;

                    Found_Cam = FALSE;
                    if (TEST_BOOL1(&sprite[mirror[mirrorcnt].camera]))
                    {
                        TRAVERSE_SPRITE_STAT(headspritestat[0], SpriteNum, NextSprite)
                        {
                            sp = &sprite[SpriteNum];
                            if (sp->picnum >= CAMSPRITE && sp->picnum < CAMSPRITE + 8 &&
                                sp->hitag == wall[i].hitag)
                            {
                                mirror[mirrorcnt].campic = sp->picnum;
                                mirror[mirrorcnt].camsprite = SpriteNum;

                                // JBF: commenting out this line results in the screen in $BULLET being visible
                                tilesiz[mirror[mirrorcnt].campic].x = tilesiz[mirror[mirrorcnt].campic].y = 0;

                                Found_Cam = TRUE;
                            }
                        }

                        if (!Found_Cam)
                        {
                            buildprintf("Did not find drawtotile for camera number %d\n",mirrorcnt);
                            buildprintf("wall[%d].hitag == %d\n",i,TrackerCast(wall[i].hitag));
                            buildprintf("Map Coordinates: x = %d, y = %d\n", TrackerCast(wall[i].x), TrackerCast(wall[i].y));
                            RESET_BOOL1(&sprite[mirror[mirrorcnt].camera]);
                        }
                    }

                    // For magic mirrors, set allowable viewing time to 30
                    // secs
                    // Base rate is supposed to be 120, but time is double
                    // what I expect
                    mirror[mirrorcnt].maxtics = 60 * 30;

                }
                while (0);

                mirror[mirrorcnt].mstate = m_normal;

                // Set tics used to none
                mirror[mirrorcnt].tics = 0;

                if (mirror[mirrorcnt].ismagic)
                {
                    //DSPRINTF(ds, "mirror.mirrorwall %d", mirror[mirrorcnt].mirrorwall);
                    MONO_PRINT(ds);
                    //DSPRINTF(ds, "mirror.mirrorsector %d", mirror[mirrorcnt].mirrorsector);
                    MONO_PRINT(ds);
                    //DSPRINTF(ds, "mirror.camera %d", mirror[mirrorcnt].camera);
                    MONO_PRINT(ds);
                }

                mirrorcnt++;
            }
            else
                wall[i].overpicnum = sector[s].ceilingpicnum;
        }
    }

    // Invalidate textures in sector behind mirror
    for (i = 0; i < mirrorcnt; i++)
    {
        startwall = sector[mirror[i].mirrorsector].wallptr;
        endwall = startwall + sector[mirror[i].mirrorsector].wallnum;
        for (j = startwall; j < endwall; j++)
        {
            wall[j].picnum = MIRROR;
            wall[j].overpicnum = MIRROR;
        }
    }

}                                   // InitMirrors

/////////////////////////////////////////////////////
//  Draw a 3d screen to a specific tile
/////////////////////////////////////////////////////
#if 1
void drawroomstotile(int daposx, int daposy, int daposz,
                     fix16_t daq16ang, fix16_t daq16horiz, short dacursectnum, short tilenume)
{
    if (waloff[tilenume] == 0)
        tileLoad(tilenume);

    PRODUCTION_ASSERT(waloff[tilenume]);

    renderSetTarget(tilenume, tilesiz[tilenume].x, tilesiz[tilenume].y);

    renderDrawRoomsQ16(daposx, daposy, daposz, daq16ang, daq16horiz, dacursectnum);
    analyzesprites(daposx, daposy, daposz, FALSE);
    renderDrawMasks();

    renderRestoreTarget();

    squarerotatetile(tilenume);

    tileInvalidate(tilenume, -1, -1);
}
#else
void
drawroomstotile(int daposx, int daposy, int daposz,
                fix16_t daq16ang, fix16_t daq16horiz, short dacursectnum, short tilenume)
{

    int i, j, k, bakchainnumpages, bakvidoption;
    intptr_t bakframeplace;
    vec2_t bakwindowxy1, bakwindowxy2;
    int xsiz, ysiz;
    char *ptr1, *ptr2;

    // DRAWROOMS TO TILE BACKUP&SET CODE
    xsiz = tilesiz[tilenume].x;
    ysiz = tilesiz[tilenume].y;
    // bakchainnumpages = chainnumpages;
    bakchainnumpages = numpages;
    // chainnumpages = 0;
    numpages = 0;
    bakvidoption = vidoption;
    vidoption = 1;
    if (waloff[tilenume] == 0)
        loadtile(tilenume);
    bakframeplace = frameplace;
    frameplace = waloff[tilenume];
    bakwindowxy1 = windowxy1;
    bakwindowxy2 = windowxy2;
    setview(0, 0, xsiz - 1, ysiz - 1);
    setvlinebpl(xsiz);
    j = 0;
    for (i = 0; i <= ysiz; i++)
    {
        ylookup[i] = j, j += xsiz;
    }

    // DRAWS TO TILE HERE
    renderDrawRoomsQ16(daposx, daposy, daposz, daq16ang, daq16horiz, dacursectnum + MAXSETORS);
    analyzesprites(daposx, daposy, daposz, FALSE);
    renderDrawMasks();

    setviewback();

    // ROTATE TILE (supports square tiles only for rotation part)
    if (xsiz == ysiz)
    {
        k = (xsiz << 1);
        for (i = xsiz - 1; i >= 0; i--)
        {
            ptr1 = (char *)(waloff[tilenume] + i * (xsiz + 1));
            ptr2 = ptr1;
            if ((i & 1) != 0)
            {
                ptr1--;
                ptr2 -= xsiz;
                swapchar(ptr1, ptr2);
            }
            for (j = (i >> 1) - 1; j >= 0; j--)
            {
                ptr1 -= 2;
                ptr2 -= k;
                swapchar2(ptr1, ptr2, xsiz);
                // swapchar(ptr1,ptr2);
                // swapchar(ptr1+1,ptr2+xsiz);
            }
        }
    }

    // DRAWROOMS TO TILE RESTORE CODE
    setview(bakwindowxy1.x, bakwindowxy1.y, bakwindowxy2.x, bakwindowxy2.y);
    // chainnumpages = bakchainnumpages;
    numpages = bakchainnumpages;
    vidoption = bakvidoption;
    frameplace = bakframeplace;
    j = 0;
    // if (chainnumpages >= 2)
    if (numpages >= 2)
    {
        for (i = 0; i <= ysiz; i++)
            ylookup[i] = j, j += (xdim >> 2);
    }
    else
    {
        for (i = 0; i <= ysiz; i++)
            ylookup[i] = j, j += xdim;
    }
    setvlinebpl(ylookup[1]);
}
#endif

void
JS_ProcessEchoSpot()
{
    short i,nexti;
    SPRITEp tp;
    int j,dist;
    PLAYERp pp = Player+screenpeek;
    int16_t reverb;
    SWBOOL reverb_set = FALSE;

    // Process echo sprites
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_ECHO], i, nexti)
    {
        dist = 0x7fffffff;

        tp = &sprite[i];

        j = klabs(tp->x - pp->posx);
        j += klabs(tp->y - pp->posy);
        if (j < dist)
            dist = j;

        if (dist <= SP_TAG4(tp)) // tag4 = ang
        {
            reverb = SP_TAG2(tp);
            if (reverb > 200) reverb = 200;
            if (reverb < 100) reverb = 100;

            COVER_SetReverb(reverb);
            reverb_set = TRUE;
        }
    }
    if (!TEST(pp->Flags, PF_DIVING) && !reverb_set && pp->Reverb <= 0)
        COVER_SetReverb(0);
}

/////////////////////////////////////////////////////
//  Draw one mirror, the one closest to player
//  Cams and see to teleporters do NOT support room above room!
/////////////////////////////////////////////////////
#define MAXCAMDIST 8000

int camloopcnt = 0;                    // Timer to cycle through player
// views
short camplayerview = 1;                // Don't show yourself!

void
JS_DrawMirrors(PLAYERp pp, int tx, int ty, int tz, fix16_t tpq16ang, fix16_t tpq16horiz)
{
    int j, cnt;
    int dist;
    int tposx, tposy; // Camera
    int *longptr;
    fix16_t tang;

//    long tx, ty, tz, tpang;             // Interpolate so mirror doesn't
    // drift!
    SWBOOL bIsWallMirror = FALSE;

    camloopcnt += (int32_t) (totalclock - ototalclock);
    if (camloopcnt > (60 * 5))          // 5 seconds per player view
    {
        camloopcnt = 0;
        camplayerview++;
        if (camplayerview >= numplayers)
            camplayerview = 1;
    }

    // WARNING!  Assuming (MIRRORLABEL&31) = 0 and MAXMIRRORS = 64 <-- JBF: wrong
    longptr = (int *)&gotpic[MIRRORLABEL >> 3];
    if (longptr && (longptr[0] || longptr[1]))
    {
        uint32_t oscilation_delta = ototalclock - oscilationclock;
        oscilation_delta -= oscilation_delta % 4;
        oscilationclock += oscilation_delta;
        oscilation_delta *= 2;
        for (cnt = MAXMIRRORS - 1; cnt >= 0; cnt--)
            //if (TEST_GOTPIC(cnt + MIRRORLABEL) || TEST_GOTPIC(cnt + CAMSPRITE))
            if (TEST_GOTPIC(cnt + MIRRORLABEL) || ((unsigned)mirror[cnt].campic < MAXTILES && TEST_GOTPIC(mirror[cnt].campic)))
            {
                bIsWallMirror = FALSE;
                if (TEST_GOTPIC(cnt + MIRRORLABEL))
                {
                    bIsWallMirror = TRUE;
                    RESET_GOTPIC(cnt + MIRRORLABEL);
                }
                //else if (TEST_GOTPIC(cnt + CAMSPRITE))
                else if ((unsigned)mirror[cnt].campic < MAXTILES && TEST_GOTPIC(mirror[cnt].campic))
                {
                    //RESET_GOTPIC(cnt + CAMSPRITE);
                    RESET_GOTPIC(mirror[cnt].campic);
                }

                mirrorinview = TRUE;

//                tx = pp->oposx + mulscale16(pp->posx - pp->oposx, smoothratio);
//                ty = pp->oposy + mulscale16(pp->posy - pp->oposy, smoothratio);
//                tz = pp->oposz + mulscale16(pp->posz - pp->oposz, smoothratio);
//                tpq16ang = pp->oq16ang + mulscale16(((pp->q16ang + fix16_from_int(1024) - pp->oq16ang) & 0x7FFFFFF) - fix16_from_int(1024), smoothratio);

                dist = 0x7fffffff;

                if (bIsWallMirror)
                {
                    j = klabs(wall[mirror[cnt].mirrorwall].x - tx);
                    j += klabs(wall[mirror[cnt].mirrorwall].y - ty);
                    if (j < dist)
                        dist = j;
                }
                else
                {
                    SPRITEp tp;

                    tp = &sprite[mirror[cnt].camsprite];

                    j = klabs(tp->x - tx);
                    j += klabs(tp->y - ty);
                    if (j < dist)
                        dist = j;
                }

//              //DSPRINTF(ds,"mirror.tics == %ul", mirror[i].tics);
//              MONO_PRINT(ds);

                if (mirror[cnt].ismagic)
                {
                    SPRITEp sp=NULL;
                    int camhoriz;
                    short w;
                    int dx, dy, dz, tdx, tdy, tdz, midx, midy;

                    ASSERT(mirror[cnt].camera != -1);

                    sp = &sprite[mirror[cnt].camera];

                    ASSERT(sp);

                    // char tvisibility;
                    // tvisibility = g_visibility;
//                  g_visibility <<= 1;       // Make mirror darker

                    // Make TV cam style mirror seem to shimmer
//                  if (mirror[cnt].ismagic && STD_RANDOM_P2(256) > 128)
//                      g_visibility -= STD_RANDOM_P2(128);

                    // Calculate the angle of the mirror wall
                    w = mirror[cnt].mirrorwall;

                    // Get wall midpoint for offset in mirror view
                    midx = (wall[w].x + wall[wall[w].point2].x) / 2;
                    midy = (wall[w].y + wall[wall[w].point2].y) / 2;

                    // Finish finding offsets
                    tdx = klabs(midx - tx);
                    tdy = klabs(midy - ty);

                    if (midx >= tx)
                        dx = sp->x - tdx;
                    else
                        dx = sp->x + tdx;

                    if (midy >= ty)
                        dy = sp->y - tdy;
                    else
                        dy = sp->y + tdy;

                    tdz = klabs(tz - sp->z);
                    if (tz >= sp->z)
                        dz = sp->z + tdz;
                    else
                        dz = sp->z - tdz;

                    // Is it a TV cam or a teleporter that shows destination?
                    // TRUE = It's a TV cam
                    mirror[cnt].mstate = m_normal;
                    if (TEST_BOOL1(sp))
                        mirror[cnt].mstate = m_viewon;

                    // Show teleport destination
                    // NOTE: Adding MAXSECTORS lets you draw a room, even if
                    // you are outside of it!
                    if (mirror[cnt].mstate != m_viewon)
                    {
                        tilesiz[MIRROR].x = tilesiz[MIRROR].y = 0;
                        // Set TV camera sprite size to 0 to show mirror
                        // behind in this case!

                        if (mirror[cnt].campic != -1)
                            tilesiz[mirror[cnt].campic].x = tilesiz[mirror[cnt].campic].y = 0;
                        renderDrawRoomsQ16(dx, dy, dz, tpq16ang, tpq16horiz, sp->sectnum + MAXSECTORS);
                        analyzesprites(dx, dy, dz, FALSE);
                        renderDrawMasks();
                    }
                    else
                    {
                        if (mirror[cnt].campic == -1)
                        {
                            TerminateGame();
                            buildprintf("Missing campic for mirror %d\n",cnt);
                            buildprintf("Map Coordinates: x = %d, y = %d\n",midx,midy);
                            exit(0);
                        }

                        // BOOL2 = Oscilate camera
                        if (TEST_BOOL2(sp))
                        {
                            if (TEST_BOOL3(sp)) // If true add increment to
                            // angle else subtract
                            {
                                // Store current angle in TAG5
                                SP_TAG5(sp) = NORM_ANGLE((SP_TAG5(sp) + oscilation_delta));

                                // TAG6 = Turn radius
                                if (klabs(GetDeltaAngle(SP_TAG5(sp), sp->ang)) >= SP_TAG6(sp))
                                {
                                    SP_TAG5(sp) = NORM_ANGLE((SP_TAG5(sp) - oscilation_delta));
                                    RESET_BOOL3(sp);    // Reverse turn
                                    // direction.
                                }
                            }
                            else
                            {
                                // Store current angle in TAG5
                                SP_TAG5(sp) = NORM_ANGLE((SP_TAG5(sp) - oscilation_delta));

                                // TAG6 = Turn radius
                                if (klabs(GetDeltaAngle(SP_TAG5(sp), sp->ang)) >= SP_TAG6(sp))
                                {
                                    SP_TAG5(sp) = NORM_ANGLE((SP_TAG5(sp) + oscilation_delta));
                                    SET_BOOL3(sp);      // Reverse turn
                                    // direction.
                                }
                            }
                        }
                        else if (!TEST_BOOL2(sp))
                        {
                            SP_TAG5(sp) = sp->ang;      // Copy sprite angle to
                            // tag5
                        }

                        // See if there is a horizon value.  0 defaults to
                        // 100!
                        if (SP_TAG7(sp) != 0)
                        {
                            camhoriz = SP_TAG7(sp);
                            if (camhoriz > PLAYER_HORIZ_MAX)
                                camhoriz = PLAYER_HORIZ_MAX;
                            else if (camhoriz < PLAYER_HORIZ_MIN)
                                camhoriz = PLAYER_HORIZ_MIN;
                        }
                        else
                            camhoriz = 100;     // Default

                        // Set up the tile for drawing
                        tilesiz[mirror[cnt].campic].x = tilesiz[mirror[cnt].campic].y = 128;

                        if (dist < MAXCAMDIST)
                        {
                            PLAYERp cp = Player + camplayerview;

                            if (TEST_BOOL11(sp) && numplayers > 1)
                            {
                                drawroomstotile(cp->posx, cp->posy, cp->posz, cp->q16ang, cp->q16horiz, cp->cursectnum, mirror[cnt].campic);
                            }
                            else
                            {
                                drawroomstotile(sp->x, sp->y, sp->z, fix16_from_int(SP_TAG5(sp)), fix16_from_int(camhoriz), sp->sectnum, mirror[cnt].campic);
                            }
                        }
                    }
                }
                else
                {
                    // It's just a mirror
                    // Prepare drawrooms for drawing mirror and calculate
                    // reflected
                    // position into tposx, tposy, and tang (tposz == cposz)
                    // Must call preparemirror before drawrooms and
                    // completemirror after drawrooms

                    renderPrepareMirror(tx, ty, tz, tpq16ang, tpq16horiz,
                                  mirror[cnt].mirrorwall, /*mirror[cnt].mirrorsector,*/ &tposx, &tposy, &tang);

                    renderDrawRoomsQ16(tposx, tposy, tz, (tang), tpq16horiz, mirror[cnt].mirrorsector + MAXSECTORS);

                    analyzesprites(tposx, tposy, tz, TRUE);
                    renderDrawMasks();

                    renderCompleteMirror();   // Reverse screen x-wise in this
                    // function
                }

                // g_visibility = tvisibility;
                // g_visibility = NormalVisibility;

                // renderDrawRoomsQ16(tx, ty, tz, tpq16ang, tpq16horiz, pp->cursectnum);
                // Clean up anything that the camera view might have done
                SetFragBar(pp);
                tilesiz[MIRROR].x = tilesiz[MIRROR].y = 0;
                wall[mirror[cnt].mirrorwall].overpicnum = MIRRORLABEL + cnt;
            }
            else
                mirrorinview = FALSE;
    }
}

void
DoAutoSize(tspriteptr_t tspr)
{
    if (!bAutoSize)
        return;

    switch (tspr->picnum)
    {
    case ICON_STAR:                     // 1793
        break;
    case ICON_UZI:                      // 1797
        tspr->xrepeat = 43;
        tspr->yrepeat = 40;
        break;
    case ICON_UZIFLOOR:         // 1807
        tspr->xrepeat = 43;
        tspr->yrepeat = 40;
        break;
    case ICON_LG_UZI_AMMO:              // 1799
        break;
    case ICON_HEART:                    // 1824
        break;
    case ICON_HEART_LG_AMMO:            // 1820
        break;
    case ICON_GUARD_HEAD:               // 1814
        break;
    case ICON_FIREBALL_LG_AMMO: // 3035
        break;
    case ICON_ROCKET:                   // 1843
        break;
    case ICON_SHOTGUN:                  // 1794
        tspr->xrepeat = 57;
        tspr->yrepeat = 58;
        break;
    case ICON_LG_ROCKET:                // 1796
        break;
    case ICON_LG_SHOTSHELL:             // 1823
        break;
    case ICON_MICRO_GUN:                // 1818
        break;
    case ICON_MICRO_BATTERY:            // 1800
        break;
    case ICON_GRENADE_LAUNCHER: // 1817
        tspr->xrepeat = 54;
        tspr->yrepeat = 52;
        break;
    case ICON_LG_GRENADE:               // 1831
        break;
    case ICON_LG_MINE:                  // 1842
        break;
    case ICON_RAIL_GUN:         // 1811
        tspr->xrepeat = 50;
        tspr->yrepeat = 54;
        break;
    case ICON_RAIL_AMMO:                // 1812
        break;
    case ICON_SM_MEDKIT:                // 1802
        break;
    case ICON_MEDKIT:                   // 1803
        break;
    case ICON_CHEMBOMB:
        tspr->xrepeat = 64;
        tspr->yrepeat = 47;
        break;
    case ICON_FLASHBOMB:
        tspr->xrepeat = 32;
        tspr->yrepeat = 34;
        break;
    case ICON_NUKE:
        break;
    case ICON_CALTROPS:
        tspr->xrepeat = 37;
        tspr->yrepeat = 30;
        break;
    case ICON_BOOSTER:                  // 1810
        tspr->xrepeat = 30;
        tspr->yrepeat = 38;
        break;
    case ICON_HEAT_CARD:                // 1819
        tspr->xrepeat = 46;
        tspr->yrepeat = 47;
        break;
    case ICON_REPAIR_KIT:               // 1813
        break;
    case ICON_EXPLOSIVE_BOX:            // 1801
        break;
    case ICON_ENVIRON_SUIT:             // 1837
        break;
    case ICON_FLY:                      // 1782
        break;
    case ICON_CLOAK:                    // 1826
        break;
    case ICON_NIGHT_VISION:             // 3031
        tspr->xrepeat = 59;
        tspr->yrepeat = 71;
        break;
    case ICON_NAPALM:                   // 3046
        break;
    case ICON_RING:                     // 3050
        break;
    case ICON_RINGAMMO:         // 3054
        break;
    case ICON_NAPALMAMMO:               // 3058
        break;
    case ICON_GRENADE:                  // 3059
        break;
    case ICON_ARMOR:                    // 3030
        tspr->xrepeat = 82;
        tspr->yrepeat = 84;
        break;
    case BLUE_KEY:                      // 1766
        break;
    case RED_KEY:                       // 1770
        break;
    case GREEN_KEY:                     // 1774
        break;
    case YELLOW_KEY:                    // 1778
        break;
    case BLUE_CARD:
    case RED_CARD:
    case GREEN_CARD:
    case YELLOW_CARD:
        tspr->xrepeat = 36;
        tspr->yrepeat = 33;
        break;
    case GOLD_SKELKEY:
    case SILVER_SKELKEY:
    case BRONZE_SKELKEY:
    case RED_SKELKEY:
        tspr->xrepeat = 39;
        tspr->yrepeat = 45;
        break;
    case SKEL_LOCKED:
    case SKEL_UNLOCKED:
        tspr->xrepeat = 47;
        tspr->yrepeat = 40;
        break;
    case RAMCARD_LOCKED:
    case RAMCARD_UNLOCKED:
    case CARD_LOCKED:
    case CARD_UNLOCKED:
        break;
    default:
        break;
    }
}

// Rotation angles for sprites
short rotang = 0;

void
JAnalyzeSprites(tspriteptr_t tspr)
{
    rotang += 4;
    if (rotang > 2047)
        rotang = 0;

    // Take care of autosizing
    DoAutoSize(tspr);

#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST && md_tilehasmodel(tspr->picnum, 0) >= 0 && usemodels) return;
#endif

    Bassert((unsigned)tspr->owner < MAXSPRITES);

    // Check for voxels
    //if (bVoxelsOn)
    if (gs.Voxels && usevoxels && videoGetRenderMode() != REND_POLYMER)
    {
        if (aVoxelArray[tspr->picnum].Voxel >= 0 && !(spriteext[tspr->owner].flags&SPREXT_NOTMD))
        {
            // Turn on voxels
            tspr->picnum = aVoxelArray[tspr->picnum].Voxel;     // Get the voxel number
            tspr->cstat |= 48;          // Set stat to voxelize sprite
        }
    }
    else
    {
        switch (tspr->picnum)
        {
        case 764: // Gun barrel
            if (!usevoxels || videoGetRenderMode() == REND_POLYMER || (spriteext[tspr->owner].flags&SPREXT_NOTMD))
            {
                tspr->cstat |= 16;
                break;
            }

            if (aVoxelArray[tspr->picnum].Voxel >= 0)
            {
                // Turn on voxels
                tspr->picnum = aVoxelArray[tspr->picnum].Voxel;     // Get the voxel number
                tspr->cstat |= 48;          // Set stat to voxelize sprite
            }
            break;
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Parental Lockout Stuff
//////////////////////////////////////////////////////////////////////////////////////////////
OrgTileList orgwalllist;                // The list containing orginal wall
// pics
OrgTileList orgwalloverlist;            // The list containing orginal wall
// over pics
OrgTileList orgsectorceilinglist;       // The list containing orginal sector
// ceiling pics
OrgTileList orgsectorfloorlist;         // The list containing orginal sector
// floor pics

void
InsertOrgTile(OrgTileP tp, OrgTileListP thelist)
{
    ASSERT(tp);
    ASSERT(ValidPtr(tp));

    // if list is empty, insert at front
    if (EMPTY(thelist))
    {
        INSERT(thelist, tp);
        return;
    }

    // Otherwise insert it at end
    INSERT_TAIL(thelist, tp);
    return;
}

OrgTileP
InitOrgTile(OrgTileListP thelist)
{
    OrgTileP tp;

    tp = (OrgTileP)CallocMem(sizeof(OrgTile), 1);

    ASSERT(tp);

    InsertOrgTile(tp, thelist);

    return tp;
}

void
KillOrgTile(OrgTileP tp)
{
    ASSERT(tp);
    ASSERT(ValidPtr(tp));

    REMOVE(tp);

    FreeMem(tp);
}

OrgTileP
FindOrgTile(short index, OrgTileListP thelist)
{
    OrgTileP tp, next_tp;

    if (EMPTY(thelist))
        return NULL;

    TRAVERSE(thelist, tp, next_tp)
    {
        if (tp->index == index)
            return tp;
    }

    return NULL;
}

// Call this at terminate game time
void
JS_UnInitLockouts(void)
{
    OrgTileP tp=NULL, next_tp=NULL;

    TRAVERSE(&orgwalllist, tp, next_tp)
    {
        KillOrgTile(tp);
    }
    TRAVERSE(&orgwalloverlist, tp, next_tp)
    {
        KillOrgTile(tp);
    }
    TRAVERSE(&orgsectorceilinglist, tp, next_tp)
    {
        KillOrgTile(tp);
    }
    TRAVERSE(&orgsectorfloorlist, tp, next_tp)
    {
        KillOrgTile(tp);
    }
}

/////////////////////////////////////////////////////
//  Initialize the original tiles list
//  Creates a list of all orginal tiles and their
//  replacements.  Several tiles can use the same
//  replacement tilenum, so the list is built
//  using the original tilenums as a basis for
//  memory allocation
//  t == 1 - wall
//  t == 2 - overpicnum
//  t == 3 - ceiling
//  t == 4 - floor
/////////////////////////////////////////////////////
void
JS_PlockError(short wall_num, short t)
{
    TerminateGame();
    printf("ERROR: JS_InitLockouts(), out of range tile number\n");
    switch (t)
    {
    case 1:
        printf("wall %d, x %d, y %d, pic %d\n", wall_num, TrackerCast(wall[wall_num].x), TrackerCast(wall[wall_num].y), TrackerCast(wall[wall_num].picnum));
        break;
    case 2:
        printf("wall %d, x %d, y %d, OVERpic %d\n", wall_num, TrackerCast(wall[wall_num].x), TrackerCast(wall[wall_num].y), TrackerCast(wall[wall_num].overpicnum));
        break;
    case 3:
        printf("sector %d, ceiling %d\n", wall_num, TrackerCast(sector[wall_num].ceilingpicnum));
        break;
    case 4:
        printf("sector %d, floor %d\n", wall_num, TrackerCast(sector[wall_num].floorpicnum));
        break;
    }
    exit(0);
}

void
JS_InitLockouts(void)
{
    short i;
    OrgTileP tp;

    INITLIST(&orgwalllist);             // The list containing orginal wall
    // pics
    INITLIST(&orgwalloverlist);         // The list containing orginal wall
    // over pics
    INITLIST(&orgsectorceilinglist);    // The list containing orginal sector
    // ceiling pics
    INITLIST(&orgsectorfloorlist);      // The list containing orginal sector
    // floor pics

    // Check all walls
    for (i = 0; i < numwalls; i++)
    {
        short picnum;

        picnum = wall[i].picnum;
        if (aVoxelArray[picnum].Parental >= INVISTILE)
            JS_PlockError(i,1);

        if (aVoxelArray[picnum].Parental >= 0)
        {
            if ((tp = FindOrgTile(i, &orgwalllist)) == NULL)
                tp = InitOrgTile(&orgwalllist);
            tp->index = i;
            tp->orgpicnum = wall[i].picnum;
        }

        picnum = wall[i].overpicnum;
        if (aVoxelArray[picnum].Parental >= INVISTILE)
            JS_PlockError(i,2);

        if (aVoxelArray[picnum].Parental >= 0)
        {
            if ((tp = FindOrgTile(i, &orgwalloverlist)) == NULL)
                tp = InitOrgTile(&orgwalloverlist);
            tp->index = i;
            tp->orgpicnum = wall[i].overpicnum;
        }
    }
    // Check all ceilings and floors
    for (i = 0; i < numsectors; i++)
    {
        short picnum;

        picnum = sector[i].ceilingpicnum;
        if (aVoxelArray[picnum].Parental >= INVISTILE)
            JS_PlockError(i,3);

        if (aVoxelArray[picnum].Parental >= 0)
        {
            if ((tp = FindOrgTile(i, &orgsectorceilinglist)) == NULL)
                tp = InitOrgTile(&orgsectorceilinglist);
            tp->index = i;
            tp->orgpicnum = sector[i].ceilingpicnum;
        }

        picnum = sector[i].floorpicnum;
        if (aVoxelArray[picnum].Parental >= INVISTILE)
            JS_PlockError(i,2);

        if (aVoxelArray[picnum].Parental >= 0)
        {
            if ((tp = FindOrgTile(i, &orgsectorfloorlist)) == NULL)
                tp = InitOrgTile(&orgsectorfloorlist);
            tp->index = i;
            tp->orgpicnum = sector[i].floorpicnum;
        }
    }
}

/////////////////////////////////////////////////////
//  Switch back and forth between locked out stuff
/////////////////////////////////////////////////////
void
JS_ToggleLockouts(void)
{
    short i;
    OrgTileP tp;

    // Check all walls
    for (i = 0; i < numwalls; i++)
    {
        short picnum;

        if (gs.ParentalLock)
        {
            picnum = wall[i].picnum;
            ASSERT(aVoxelArray[picnum].Parental < INVISTILE);   // Invalid, walls can't
            // be invisible
            if (aVoxelArray[picnum].Parental >= 0)
            {
                wall[i].picnum = aVoxelArray[picnum].Parental;
            }
        }
        else if ((tp = FindOrgTile(i, &orgwalllist)) != NULL)
            wall[i].picnum = tp->orgpicnum;     // Restore them

        if (gs.ParentalLock)
        {
            picnum = wall[i].overpicnum;
            ASSERT(aVoxelArray[picnum].Parental < INVISTILE);   // Invalid, walls can't
            // be invisible
            if (aVoxelArray[picnum].Parental >= 0)
            {
                wall[i].overpicnum = aVoxelArray[picnum].Parental;
            }
        }
        else if ((tp = FindOrgTile(i, &orgwalloverlist)) != NULL)
            wall[i].overpicnum = tp->orgpicnum; // Restore them
    }

    // Check all sectors
    for (i = 0; i < numsectors; i++)
    {
        short picnum;

        if (gs.ParentalLock)
        {
            picnum = sector[i].ceilingpicnum;
            ASSERT(aVoxelArray[picnum].Parental < INVISTILE);   // Invalid, walls can't
            // be invisible
            if (aVoxelArray[picnum].Parental >= 0)
            {
                sector[i].ceilingpicnum = aVoxelArray[picnum].Parental;
            }
        }
        else if ((tp = FindOrgTile(i, &orgsectorceilinglist)) != NULL)
            sector[i].ceilingpicnum = tp->orgpicnum;    // Restore them

        if (gs.ParentalLock)
        {
            picnum = sector[i].floorpicnum;
            ASSERT(aVoxelArray[picnum].Parental < INVISTILE);   // Invalid, walls can't
            // be invisible
            if (aVoxelArray[picnum].Parental >= 0)
            {
                sector[i].floorpicnum = aVoxelArray[picnum].Parental;
            }
        }
        else if ((tp = FindOrgTile(i, &orgsectorfloorlist)) != NULL)
            sector[i].floorpicnum = tp->orgpicnum;      // Restore them
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////

void
UnlockKeyLock(short key_num, short hit_sprite)
{
    SPRITEp sp;
    int SpriteNum = 0, NextSprite = 0, color = 0;

    // Get palette by looking at key number
    switch (key_num - 1)
    {
    case 0:                             // RED_KEY
        color = PALETTE_PLAYER9;
        break;
    case 1:                             // BLUE_KEY
        color = PALETTE_PLAYER7;
        break;
    case 2:                             // GREEN_KEY
        color = PALETTE_PLAYER6;
        break;
    case 3:                             // YELLOW_KEY
        color = PALETTE_PLAYER4;
        break;
    case 4:                             // SILVER_SKELKEY
        color = PALETTE_PLAYER4;
        break;
    case 5:                             // GOLD_SKELKEY
        color = PALETTE_PLAYER1;
        break;
    case 6:                             // BRONZE_SKELKEY
        color = PALETTE_PLAYER8;
        break;
    case 7:                             // RED_SKELKEY
        color = PALETTE_PLAYER9;
        break;
    }

    TRAVERSE_SPRITE_STAT(headspritestat[0], SpriteNum, NextSprite)
    {
        sp = &sprite[SpriteNum];

        switch (sp->picnum)
        {
        case SKEL_LOCKED:
            if (sp->pal == color)
            {
                PlaySound(DIGI_UNLOCK, &sp->x, &sp->y, &sp->z, v3df_doppler | v3df_dontpan);
                if (SpriteNum == hit_sprite)
                    sp->picnum = SKEL_UNLOCKED;
            }
            break;
        case RAMCARD_LOCKED:
            if (sp->pal == color)
            {
                PlaySound(DIGI_CARDUNLOCK, &sp->x, &sp->y, &sp->z, v3df_doppler | v3df_dontpan);
                sp->picnum = RAMCARD_UNLOCKED;
            }
            break;
        case CARD_LOCKED:
            if (sp->pal == color)
            {
                PlaySound(DIGI_RAMUNLOCK, &sp->x, &sp->y, &sp->z, v3df_doppler | v3df_dontpan);
                if (SpriteNum == hit_sprite)
                    sp->picnum = CARD_UNLOCKED;
                else
                    sp->picnum = CARD_UNLOCKED+1;
            }
            break;
        }

    }
}


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 JSECTOR_H
#define JSECTOR_H

#include "reserve.h"

#define MAXMIRRORDIST       3300    // At this distance, or less, the magic mirrors activate.
#define MAXMIRRORMONSTERS   4       // Max monsters any one magic mirror can spawn

typedef enum
{
    m_normal, m_viewon, m_pissed
} MIRRORSTATE;

typedef struct
{
    short mirrorwall;                   // Wall number containing the mirror
    // tile
    short mirrorsector;                 // nextsector used internally to draw
    // mirror rooms
    short camera;                       // Contains number of ST1 sprite used
    // as a camera
    short camsprite;                    // sprite pointing to campic
    short campic;                       // Editart tile number to draw a
    // screen to
    short numspawnspots;                // Number of spawnspots used
    short spawnspots[MAXMIRRORMONSTERS]; // One spot for each possible skill
    // level for a
    // max of up to 4 coolie ghosts to spawn.
    SWBOOL ismagic;                       // Is this a magic mirror?
    MIRRORSTATE mstate;                 // What state the mirror is currently
    // in
    int maxtics;                       // Tic count used to time mirror
    // events
    int tics;                          // How much viewing time has been
    // used on mirror?
} MIRRORTYPE, *MIRRORTYPEp;

extern MIRRORTYPE mirror[MAXMIRRORS];

extern short mirrorcnt, floormirrorcnt;
extern short floormirrorsector[MAXMIRRORS];
extern SWBOOL mirrorinview;
extern short NormalVisibility;

void JAnalyzeSprites(tspriteptr_t tspr);
void JS_DrawMirrors(PLAYERp pp,int tx,int ty,int tz,fix16_t tpq16ang,fix16_t tpq16horiz);
void JS_InitMirrors(void);
void JS_InitLockouts(void);
void JS_ToggleLockouts(void);
void JS_UnInitLockouts(void);
void JS_ProcessEchoSpot(void);
void JS_SpriteSetup(void);

#endif


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 JTAGS_H
#define JTAGS_H

//////////////////////////////////////////////////////////////////////////////////////////
//
// SPRITE TAGS (TAG THE SPRITES'S HITAG)  ST1 tags
//
//////////////////////////////////////////////////////////////////////////////////////////
#define AI_NORMAL  0
#define AI_EVASIVE 1
#define AI_SNIPER  2
#define AI_GUNGHO  3

#define SWITCH_LOCKED 29

// My sprite sprite tags start at 1000 to be separate from Frank's

//* Magic mirror cameras
//* LOTAG is the unique camera number
#define MIRROR_CAM       1000
//* These are spots at which a pissed off mirror will spawn a coolie ghost
//* Make sure to set the skill levels on these sprites too.
#define MIRROR_SPAWNSPOT 1001

//* Ambient Sounds
//* LOTAG is the enumerated sound num to play
#define AMBIENT_SOUND      1002
#define TAG_NORESPAWN_FLAG 1003
#define TAG_GET_STAR       1004
#define TAG_ECHO_SOUND     1005
#define TAG_DRIPGEN        1006
#define TAG_BUBBLEGEN      1007
#define TAG_SWARMSPOT      1008

#define TAG_PACHINKOLIGHT 9997
#define TAG_INVISONINJA 9998
#define LUMINOUS 9999

//////////////////////////////////////////////////////////////////////////////////////////
//
// WALL TAGS (TAG THE WALL'S LOTAG)
//
//////////////////////////////////////////////////////////////////////////////////////////

//* Turns a regular mirror into a magic mirror that shows a room containing ST1 sprite at
//* sprite's angle and z height.
//* HITAG is unique camera sprite number matching the ST1 camera sprite
#define TAG_WALL_MAGIC_MIRROR   306

//////////////////////////////////////////////////////////////////////////////////////////
//
// LIGHTING TAGS (TAG THE SECTOR'S LOTAG)
//
//////////////////////////////////////////////////////////////////////////////////////////

//* Fade effect.  Fades in and out smoothly.
//* Ceiling is minimum darkness.
//* Floor is maximum darkness.
//* High byte is speed of flicker.
//* The lower the number the faster.  Default is 3.  I recommend 8.
//* Use TAG_LIGHT_FADE_DIFFUSE tags around the initial torch sector just like light fade.
//* A good value to use for torches, is a 2 in high tag of TAG_LIGHT_FADE_DIFFUSE

#define TAG_LIGHT_TORCH_FADE    305

#endif