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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "lists.h"
#include "game.h"
#include "common_game.h"
#include "network.h"
#include "text.h"

#define BAR_HEIGHT 48
#define XDIM 320
#define YDIM 200

short DebugBorderShade = 0;

short RegBorderTest[] =
{
    51, 53, 127, 128, 140, 145, 152, 197, 198, 201, 205, 206, 213, 218, 242, 243,
    245, 246, 247, 257, 2560, 2561, 2562, 2570, 2571, 2572, 2573, 2576, 2578,
    2579, 2580, 2581, 2582, 2583, 2584, 2585, 2593, 2594
};
short SWBorderTest[] =
{
    51, 53, 127, 128, 140, 145, 201, 205, 206, 213, 218,
    245, 2560, 2573, 2576, 2580, 2581, 2582, 2583, 2584, 2593
};

#undef BORDER_TILE
#define BORDER_TILE \
    (SW_SHAREWARE ? \
     SWBorderTest[gs.BorderTile % SIZ(SWBorderTest)] : \
     RegBorderTest[gs.BorderTile % SIZ(RegBorderTest)] \
    )

#define f_320 FIXED(320,0)
#define f_200 FIXED(200,0)

#define X_TO_FIXED(val) (x_aspect_mul*(val))
#define Y_TO_FIXED(val) (y_aspect_mul*(val))

SWBOOL RedrawScreen = FALSE;

int f_xdim, f_ydim, x_pix_size, y_pix_size, x_aspect_mul, y_aspect_mul;
int CrosshairX, CrosshairY;

extern SWBOOL BorderAdjust;
SWBOOL GlobSpriteBoxUpdateEveryFrame = FALSE;

PANEL_SPRITEp
pSpawnFullScreenSpriteBox(PLAYERp pp, short id, short pic, short pri, int x, int y, short x1, short y1, short x2, short y2)
{
    PANEL_SPRITEp psp;

    psp = pSpawnSprite(pp, NULL, pri, x, y);

    psp->ID = id;
    psp->numpages = numpages;
    if (GlobSpriteBoxUpdateEveryFrame)
    {
        psp->numpages = 1;
    }
    psp->picndx = -1;
    psp->picnum = pic;
    psp->x1 = x1;
    psp->y1 = y1;
    psp->x2 = x2;
    psp->y2 = y2;
    psp->shade = DebugBorderShade;

    //SET(psp->flags, PANF_STATUS_AREA | PANF_KILL_AFTER_SHOW | PANF_IGNORE_START_MOST  | PANF_DRAW_BEFORE_VIEW | PANF_NOT_ALL_PAGES);
    SET(psp->flags, PANF_STATUS_AREA | PANF_KILL_AFTER_SHOW | PANF_IGNORE_START_MOST | PANF_DRAW_BEFORE_VIEW);
    //extern SWBOOL DrawBeforeView;
    //DrawBeforeView = TRUE;

    //SET(psp->flags, PANF_SCREEN_CLIP | PANF_KILL_AFTER_SHOW | PANF_IGNORE_START_MOST);

    return psp;
}

void SetCrosshair(void)
{
    int wdx,wdy,x,y;

    wdx = ((windowxy2.x-windowxy1.x)/2);
    wdy = ((windowxy2.y-windowxy1.y)/2);
    x = windowxy1.x + wdx;
    y = windowxy1.y + wdy;

    CrosshairX = x / (xdim/320.0);
    CrosshairY = y / (ydim/200.0);

    // rotatesprite takes FIXED point number
    CrosshairX <<= 16;
    CrosshairY <<= 16;
}

void
SetupAspectRatio(void)
{
    f_xdim = FIXED(xdim, 0);
    f_ydim = FIXED(ydim, 0);

    x_pix_size = (f_320 / xdim);
    y_pix_size = (f_200 / ydim);

    x_aspect_mul = (f_xdim / 320);
    y_aspect_mul = (f_ydim / 200);
}

void
SetConsoleDmost(void)
{
    int ystart;

    int i;
    int adj=0;

    // dont setup the startumost/dmost arrays if border is 0
    if (gs.BorderNum == BORDER_NONE || gs.BorderNum == BORDER_MINI_BAR)
        return;

    //
    // Set the whole thing to the size of the bar
    //

    ystart = f_ydim - Y_TO_FIXED(BAR_HEIGHT);

    if (ydim == 480 && gs.BorderNum == 2)
        adj = 1;

    //for (i = FIXED(0, 0); i < f_320; i += x_pix_size)
    for (i = 0; i < xdim; i++)
    // define picture
    // boundaries
    {
        startdmost[i] = MSW(ystart) + adj;
    }
}

void ClearStartMost(void)
{
    int i;

    for (i = 0; i < xdim; i++)
        startdmost[i] = ydim;

    memset(startumost, 0, xdim * sizeof(int16_t));
}

void
SetFragBar(PLAYERp pp)
{
    short i, num_frag_bars;
    int y;
    extern int16_t OrigCommPlayers;

    if (numplayers <= 1)
        return;

    if (gNet.MultiGameType == MULTI_GAME_COOPERATIVE)
        return;

    // if player sprite has not been initialized we have no business
    // sticking a frag bar up.  Prevents processing from MenuLevel etc.
    if (!pp->SpriteP)
        return;

    //num_frag_bars = ((numplayers-1)/4)+1;
    num_frag_bars = ((OrigCommPlayers-1)/4)+1;

    for (i = windowxy1.x; i <= windowxy2.x; i++)
    {
        y = (tilesiz[FRAG_BAR].y * num_frag_bars) - (2 * (num_frag_bars-1));
        y = y * (ydim/200.0);

        if (windowxy1.y < y)
            startumost[i] = y;
    }

    for (i = 0, y = 0; i < num_frag_bars; i++)
    {
        pSpawnFullScreenSprite(pp, FRAG_BAR, PRI_MID, 0, y);
        y += tilesiz[FRAG_BAR].y - 2;
    }

    // write each persons kill info to everybody
    // for (i = 0; i < numplayers; i++)
    TRAVERSE_CONNECT(i)
    {
        PlayerUpdateKills(Player + i, 0);
        DisplayFragNames(Player + i);
    }
}

SWBOOL RectOverlap(short tx1, short ty1, short bx1, short by1, short tx2, short ty2, short bx2, short by2)
{
    if (bx1 >= tx2)
        if (tx1 <= bx2)
            if (ty1 <= by2)
                if (by1 >= ty2)
                    return TRUE;

    return FALSE;
}

void DrawBorderShade(PLAYERp pp, short shade_num, short wx1, short wy1, short wx2, short wy2)
{
    short i,j,k,l;
    PANEL_SPRITEp psp;
    int dark_shade = 27 - (shade_num * 6);
    int light_shade = 20 - (shade_num * 6);

    for (i = 0; i < xdim; i += tilesiz[BORDER_TILE].x)
    {
        for (j = 0; j < ydim; j += tilesiz[BORDER_TILE].y)
        {
            k = i + tilesiz[BORDER_TILE].x;
            l = j + tilesiz[BORDER_TILE].y;

            if (RectOverlap(i, j, k, l, wx1 - 1, wy1 - 1, wx2 + 1, wy1))
            {
                // draw top box of the border
                psp = pSpawnFullScreenSpriteBox(pp, ID_BORDER_TOP, BORDER_TILE, PRI_BACK + 1, i, j, wx1 - 1, wy1 - 1, wx2 + 1, wy1);
                psp->shade = dark_shade;
                psp->ID = ID_BORDER_SHADE;
            }

            if (RectOverlap(i, j, k, l, wx1 - 1, wy2, wx2 + 1, wy2 + 1))
            {
                // draw bottom box of the border
                psp = pSpawnFullScreenSpriteBox(pp, ID_BORDER_BOTTOM, BORDER_TILE, PRI_BACK + 1, i, j, wx1 - 1, wy2, wx2 + 1, wy2 + 1);
                psp->shade = light_shade;
                psp->ID = ID_BORDER_SHADE;
            }
            if (RectOverlap(i, j, k, l, wx1 - 1, wy1 - 1, wx1, wy2 + 1))
            {
                // draw left box of the border
                psp = pSpawnFullScreenSpriteBox(pp, ID_BORDER_LEFT, BORDER_TILE, PRI_BACK + 1, i, j, wx1 - 1, wy1 - 1, wx1, wy2 + 1);
                psp->shade = dark_shade;
                psp->ID = ID_BORDER_SHADE;
            }
            if (RectOverlap(i, j, k, l, wx2, wy1 - 1, wx2 + 1, wy2 + 1))
            {
                // draw right box of the border
                psp = pSpawnFullScreenSpriteBox(pp, ID_BORDER_RIGHT, BORDER_TILE, PRI_BACK + 1, i, j, wx2, wy1 - 1, wx2 + 1, wy2 + 1);
                psp->shade = light_shade;
                psp->ID = ID_BORDER_SHADE;
            }
        }
    }
}

void
BorderShade(PLAYERp pp, SWBOOL refresh)
{
    int wx1, wx2, wy1, wy2;
    uint8_t lines;

    wx1 = windowxy1.x - 1;
    wy1 = windowxy1.y - 1;
    wx2 = windowxy2.x + 1;
    wy2 = windowxy2.y + 1;

    for (lines = 0; lines < 4; lines++)
    {

        // make sure that these values dont go out of bound - which they do
        wx1 = max(wx1, 0);
        wx2 = min(wx2, xdim - 1);
        wy1 = max(wy1, 0);

        if (refresh)
        {
            // silly thing seems off by 1
            wy2 = min(wy2, ydim - (Y_TO_FIXED(BAR_HEIGHT) >> 16) - 2);
        }
        else
        {
            if (gs.BorderNum >= BORDER_BAR+1 && gs.BorderNum <= BORDER_BAR+2)
                wy2 = min(wy2, ydim - 1);
            else
                wy2 = min(wy2, ydim - (Y_TO_FIXED(BAR_HEIGHT) >> 16) - 1);
        }

        DrawBorderShade(pp, lines, wx1, wy1, wx2, wy2);
        // increase view size by one - dont do a set view though
        wx1--;
        wy1--;
        wx2++;
        wy2++;
    }
}

BORDER_INFO BorderInfoValues[] =
{
    // x,y,screensize
    {0, 0, 0},
    {0, 0, 0},
    {0, BAR_HEIGHT, 0},

    {0, BAR_HEIGHT, (1 * 16)},
    {0, BAR_HEIGHT, (2 * 16)},
    {0, BAR_HEIGHT, (3 * 16)},
    {0, BAR_HEIGHT, (4 * 16)},
    {0, BAR_HEIGHT, (5 * 16)},
    {0, BAR_HEIGHT, (6 * 16)},
    {0, BAR_HEIGHT, (7 * 16)},
    {0, BAR_HEIGHT, (8 * 16)},
    {0, BAR_HEIGHT, (9 * 16)},
    {0, BAR_HEIGHT, (10 * 16)},
    {0, BAR_HEIGHT, (11 * 16)},
    {0, BAR_HEIGHT, (12 * 16)}
};

void DrawBorder(PLAYERp pp, short x, short y, short x2, short y2)
{
    short i,j,k,l;
    short count = 0;

    for (i = 0; i < xdim; i += tilesiz[BORDER_TILE].x)
    {
        for (j = 0; j < ydim; j += tilesiz[BORDER_TILE].y)
        {
            k = i + tilesiz[BORDER_TILE].x;
            l = j + tilesiz[BORDER_TILE].y;

            if (RectOverlap(i, j, k, l, x, y, windowxy1.x-1, y2))
            {
                // draw top box of the border
                pSpawnFullScreenSpriteBox(pp, ID_BORDER_TOP, BORDER_TILE, PRI_BACK, i, j, x, y, windowxy1.x-1, y2);
                count++;
            }

            if (RectOverlap(i, j, k, l, windowxy2.x+1, y, x2, y2))
            {
                // draw bottom box of the border
                pSpawnFullScreenSpriteBox(pp, ID_BORDER_BOTTOM, BORDER_TILE, PRI_BACK, i, j, windowxy2.x+1, y, x2, y2);
                count++;
            }

            if (RectOverlap(i, j, k, l, windowxy1.x, y, windowxy2.x, windowxy1.y-1))
            {
                // draw left box of the border
                pSpawnFullScreenSpriteBox(pp, ID_BORDER_LEFT, BORDER_TILE, PRI_BACK, i, j, windowxy1.x, y, windowxy2.x, windowxy1.y-1);
                count++;
            }

            if (RectOverlap(i, j, k, l, windowxy1.x, windowxy2.y+1, windowxy2.x, y2))
            {
                // draw right box of the border
                pSpawnFullScreenSpriteBox(pp, ID_BORDER_RIGHT, BORDER_TILE, PRI_BACK, i, j, windowxy1.x, windowxy2.y+1, windowxy2.x, y2);
                count++;
            }
        }
    }
}

void DrawPanelBorderSides(PLAYERp pp, short x, short y, short x2, short y2, short panl, short panr)
{
    short i,j,k,l;
    short count = 0;

    for (i = 0; i < xdim; i += tilesiz[BORDER_TILE].x)
    {
        for (j = 0; j < ydim; j += tilesiz[BORDER_TILE].y)
        {
            k = i + tilesiz[BORDER_TILE].x;
            l = j + tilesiz[BORDER_TILE].y;

            if (RectOverlap(i, j, k, l, x, y, panl, y2))
            {
                pSpawnFullScreenSpriteBox(pp, ID_PANEL_BORDER_LEFT, BORDER_TILE, PRI_BACK, i, j, x, y, panl, y2);
                count++;
            }

            if (RectOverlap(i, j, k, l, panr, y, x2, y2))
            {
                pSpawnFullScreenSpriteBox(pp, ID_PANEL_BORDER_RIGHT, BORDER_TILE, PRI_BACK, i, j, panr, y, x2, y2);
                count++;
            }
        }
    }
}

static
void BorderSetView(PLAYERp UNUSED(pp), int *Xdim, int *Ydim, int *ScreenSize)
{
    int x, x2, y, y2;
    BORDER_INFO *b;

    BorderInfo = BorderInfoValues[gs.BorderNum];

    b = &BorderInfo;

    // figure out the viewing window x and y dimensions
    *Xdim = MSW(f_xdim - X_TO_FIXED(b->Xdim));
    *Ydim = MSW(f_ydim - Y_TO_FIXED(b->Ydim));
    *ScreenSize = MSW(f_xdim - X_TO_FIXED(b->ScreenSize));

    // figure out the viewing window x and y coordinates
    x = DIV2(*Xdim) - DIV2(*ScreenSize);
    x2 = x + *ScreenSize - 1;
    y = DIV2(*Ydim) - DIV2((*ScreenSize **Ydim) / *Xdim);
    y2 = y + ((*ScreenSize **Ydim) / *Xdim) - 1;

    // avoid a one-pixel tall HOM
    if (gs.BorderNum == BORDER_BAR)
        ++y2;

    // global windowxy1, windowxy2 coords set here
    videoSetViewableArea(x, y, x2, y2);
    SetCrosshair();
}

//
// Redraw the border without changing the view
//

static void
BorderRefresh(PLAYERp pp)
{
    int x, x2, y, y2;
    BORDER_INFO *b;

    if (pp != Player + myconnectindex)
        return;

    if (!BorderAdjust)
        return;

    if (gs.BorderNum < BORDER_BAR)
        return;

    // Redraw the BORDER_TILE only if getting smaller
    BorderInfo = BorderInfoValues[gs.BorderNum];

    b = &BorderInfo;

    // A refresh does not change the view size so we dont need to do a
    // setview
    // We don't need the calculations for the border drawing boxes - its
    // the whole screen
    // minus the border if necessary

    // fill in the sides of the panel when the screen is wide
    if (gs.BorderNum >= BORDER_BAR && r_usenewaspect)
    {
        const int sidew = (xdim - scale(4, ydim, 3)) / 2;

        x = 0;
        x2 = xdim - 1;

        y = ydim - (Y_TO_FIXED(b->Ydim) >> 16);
        y2 = ydim - 1;

        DrawPanelBorderSides(pp, x, y, x2, y2, sidew, xdim-sidew);
    }

    // only need a border if border is > BORDER_BAR
    if (gs.BorderNum > BORDER_BAR)
    {
        // make sure that these values dont go out of bound - which they do
        x = 0;
        x2 = xdim - 1;

        y = 0;
        y2 = ydim - (Y_TO_FIXED(b->Ydim) >> 16) - 1;

        DrawBorder(pp, x, y, x2, y2);

        // kill ALL outstanding (not yet drawn) border shade sprites before
        // doing more shading
        pKillScreenSpiteIDs(pp, ID_BORDER_SHADE);

        BorderShade(pp, TRUE);
    }
}

//
// Redraw the whole screen
//

void SetBorder(PLAYERp pp, int value)
{
    int Xdim, Ydim, ScreenSize;

    if (pp != Player + myconnectindex)
        return;

    if (!BorderAdjust)
        return;

    if (value >= 0) // just refresh
        gs.BorderNum = value;

    if (gs.BorderNum < BORDER_NONE)
    {
        gs.BorderNum = BORDER_NONE;
        //return;
    }

    if (gs.BorderNum > (int)SIZ(BorderInfoValues) - 1)
    {
        gs.BorderNum = SIZ(BorderInfoValues) - 1;
        return;
    }

    BorderSetView(pp, &Xdim, &Ydim, &ScreenSize);

    if (gs.BorderNum >= BORDER_BAR)
    {
        BorderRefresh(pp);

        if (gs.BorderNum == BORDER_BAR)
            SetConsoleDmost();

        pSpawnFullScreenSprite(pp, STATUS_BAR, PRI_FRONT, 0, 200 - tilesiz[STATUS_BAR].y);
        PlayerUpdatePanelInfo(Player + screenpeek);
    }

    SetFragBar(pp);
}

void
SetRedrawScreen(PLAYERp pp)
{
    //int x, x2, y, y2;

    if (pp != Player + myconnectindex)
        return;

    if (!BorderAdjust)
        return;

    if (gs.BorderNum < BORDER_NONE)
        gs.BorderNum = BORDER_NONE;

    // Redraw the BORDER_TILE only if getting smaller
    BorderInfo = BorderInfoValues[gs.BorderNum];

    // test at redrawing the whole screen
    RedrawScreen = TRUE;
}


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

// BOTS.H
// Contains useful header information for bot creation

#ifndef BOTS_H
#define BOTS_H

// BOT DEFINITIONS AND STRUCTURES

typedef enum
{
    BOTstand, BOThide, BOTrun, BOTduck, BOTjump, BOTstrafe, BOTshoot, BOTuseinv,
    BOTopen, BOTswimup, BOTswimdown, BOTturn, BOTuserts
} BOT_Actions;

// Linked lists containing node trees that are chosen based on desired actions
struct NODEstruct;
typedef struct NODEstruct NODE, *NODEp;

struct NODEstruct
{
    NODEp p, l, r;              // Pointers to tree nodes
    int goalx, goaly, goalz;   // x,y,z point bot wants to get to
    BOT_Actions action;         // Action to take if this node is reached
    int tics;                  // Optionally stay in this node for x tics.
};

struct NODETREEstruct;
typedef struct NODETREEstruct NODETREE, *NODETREEp;

struct NODETREEstruct
{
    short SpriteNum;        // Sprite number in sprite array of goal item
    NODEp tree;             // This is the node tree used to navigate to goal
    SWBOOL Locked;            // If list is locked, a bot is using/modifying it and
    // other bots cannot modify it while it's locked
};

// Bots main action variables
typedef struct BOT_BRAIN
{
    short tgt_inv;      // Inventory item it wants to use
    short tgt_weapon;   // Weapon in wants to activate and use
    short tgt_enemy;    // Enemy it wants to kill
    short tgt_sprite;   // Sprite it wants to pickup or operate
    short tgt_sector;   // Sector it wants to get to
    short tgt_wall;     // Wall it wants to touch
    BOT_Actions action; // Bot's current action
} BotBrain, *BotBrain_p;

// NOTE:
// The following arrays should be saved off with save games!

// 0  = Item not accessible, no item of type was found
// 1  = Shuriken
// 3  = Caltrops
// 4  = Gas Bomb
// 5  = Flash Bomb
// 6  = Uzi Ammo
// 7  = Shotgun Ammo
// 8  = Rocket Ammo
// 9  = 40mm Ammo
// 10 = Sticky Bombs
// 11 = Rail Ammo
// 12 = Head Ammo
// 13 = Heart Ammo
// 14 = Uzi
// 15 = Shotgun
// 16 = Rocket Launcher
// 17 = 40mm Launcher
// 18 = Rail Gun
// 19 = Head
// 20 = Heart
// 21 = MedKit
// 22 = Armor
// 23 = Big Armor
// 24 = Portable MedKit
// 25 = Fortune Cookie
////////////////////////
extern NODETREE BOT_TREELIST[25][50]; // There can be up to 50 of each item
// with a cooresponding search tree for each

#endif

repo_name: orochi/nblood

license: mit


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "sprite.h"
#include "sector.h"
#include "light.h"
#include "weapon.h"

#include "break.h"

BREAK_INFOp GlobBreakInfo;

static int SectorOfWall(short theline);
static void DoWallBreakSpriteMatch(short match);

BREAK_INFO WallBreakInfo[] =
{
    { 60,  -1, SHRAP_METAL, BF_KILL, 0 },
    { 82,  -1, SHRAP_METAL, BF_KILL, 0 },
    { 1, 3593, SHRAP_METAL, 0, 0 },
//{ 13, -1, SHRAP_PAPER },
//{ 14, -1, SHRAP_PAPER },
//{ 49, -1, SHRAP_PAPER },
//{ 52, -1, SHRAP_PAPER },
//{ 99, -1, SHRAP_PAPER },
//{ 102, -1, SHRAP_PAPER },
//{ 207, -1, SHRAP_PAPER },
    { 253, 255, SHRAP_GLASS, 0, 0 },
    { 254, 255, SHRAP_GLASS, 0, 0 },
    { 282, -1, SHRAP_GLASS, 0, 0 },
    { 283, 4974, SHRAP_METAL, 0, 0 },
    { 318, 599, SHRAP_GLASS, 0, 0},
    { 486, -1, SHRAP_METAL, 0, 0 },
    { 487, 3676, SHRAP_METAL, 0, 0 },
    { 628, 3585, SHRAP_METAL, 0, 0},
    { 630, 3586, SHRAP_METAL, 0, 0},
    { 633, 608, SHRAP_GLASS, 0, 0},
    { 634, 608, SHRAP_GLASS, 0, 0 },
    { 637, 3587, SHRAP_METAL, 0, 0},
    { 640, 3588, SHRAP_METAL, 0, 0},
    { 641, 3588, SHRAP_METAL, 0, 0},
    { 665, 3588, SHRAP_METAL, 0, 0},
    { 742, 3589, SHRAP_COIN, 0, 0},
    { 743, 3590, SHRAP_COIN, 0, 0},
    { 750, 608, SHRAP_GLASS, 0, 0},
    { 2667, 608, SHRAP_GLASS, 0, 0},
    { 2769, 3681, SHRAP_GLASS, 0, 0},
    { 2676, 3591, SHRAP_GLASS, 0, 0},
    { 2677, 3592, SHRAP_GLASS, 0, 0},
    { 2687, 2727, SHRAP_GLASS, 0, 0},
    { 2688, 2728, SHRAP_GLASS, 0, 0},
//{ 2714, 3593, SHRAP_GLASS},
    { 2732, 3594, SHRAP_GLASS, 0, 0},
    { 2777, 3683, SHRAP_METAL, 0, 0},
    { 2778, 2757, SHRAP_GLASS, 0, 0},
    { 2801, 3591, SHRAP_GLASS, 0, 0},
    { 2804, 3595, SHRAP_GLASS, 0, 0},
    { 2807, 3596, SHRAP_GLASS, 0, 0},
    { 2810, 4989, SHRAP_METAL, 0, 0},
    { 4890, 4910, SHRAP_METAL, 0, 0},
    { 4891, 4911, SHRAP_METAL, 0, 0},
    { 4892, 4912, SHRAP_METAL, 0, 0},
    { 4893, 4913, SHRAP_METAL, 0, 0},
    { 4894, 4914, SHRAP_METAL, 0, 0},
    { 4895, 4915, SHRAP_METAL, 0, 0},
    { 3336, 4940, SHRAP_COIN, 0, 0},
    { 3337, 4941, SHRAP_COIN, 0, 0},
    { 4885, 4888, SHRAP_METAL, 0, 0},
    { 4887, 4889, SHRAP_COIN, 0, 0},
    { 3350, 4942, SHRAP_GLASS, 0, 0},
    { 3351, 4943, SHRAP_METAL, 0, 0},
    { 3352, 4944, SHRAP_METAL, 0, 0},
    { 3353, 4945, SHRAP_METAL, 0, 0},
    { 4896, 4898, SHRAP_METAL, 0, 0},
    { 4897, 4899, SHRAP_METAL, 0, 0},
    { 3385, 4981, SHRAP_METALMIX, 0, 0},
    { 3389, 4982, SHRAP_METALMIX, 0, 0},
    { 3393, 4984, SHRAP_METALMIX, 0, 0},
    { 3397, 4983, SHRAP_METALMIX, 0, 0},
    { 3401, 4985, SHRAP_METALMIX, 0, 0},
    { 3405, 4986, SHRAP_METALMIX, 0, 0},
    { 3409, 4988, SHRAP_METALMIX, 0, 0},
    { 3413, 4987, SHRAP_METALMIX, 0, 0},
    { 253, 255,   SHRAP_METALMIX, 0, 0},
    { 283, 4974,  SHRAP_METALMIX, 0, 0},
    { 299, 4975,  SHRAP_METALMIX, 0, 0},
    {5078, 5079,  SHRAP_METALMIX, 0, 0},
    {5080, 5092,  SHRAP_MARBELS, 0, 0},
    {5083, 5093,  SHRAP_MARBELS, 0, 0},
    {5086, 5094,  SHRAP_MARBELS, 0, 0},
    {5089, 5095,  SHRAP_MARBELS, 0, 0},
    {4970, 4973,  SHRAP_METAL, 0, 0},
    {297,  4980,  SHRAP_METAL, 0, 0},
    {1,    4976,  SHRAP_METAL, 0, 0},
    {4917, 4918,  SHRAP_METAL, 0, 0},
    {4902, 4903,  SHRAP_METAL, 0, 0},
};

BREAK_INFO SpriteBreakInfo[] =
{
    { 60,  -1, SHRAP_METAL, BF_KILL, 0},
    { 82,  -1, SHRAP_METAL, BF_KILL, 0},
    { 138, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 253, 255, SHRAP_GLASS, 0, 0},
    { 254, 255, SHRAP_GLASS, 0, 0},
    { 270, -1, SHRAP_PAPER, BF_BURN, 0},
    { 271, -1, SHRAP_PAPER, BF_BURN, 0},
    { 272, -1, SHRAP_WOOD, 0, 0},
    { 274, -1, SHRAP_PAPER, BF_BURN, 0},
//{ 276, -1, SHRAP_WOOD },
//{ 277, -1, SHRAP_WOOD },
//{ 278, -1, SHRAP_WOOD },
    { 282, -1, SHRAP_GLASS, 0, 0},
    { 283, -1, SHRAP_METAL, 0, 0},
    { 297, -1, SHRAP_METAL, 0, 0},
    { 299, -1, SHRAP_METAL, 0, 0},
    { 363, -1, SHRAP_METAL, BF_KILL, 0},
    { 365, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 366, -1, SHRAP_METAL, BF_KILL,5},
    { 367, -1, SHRAP_WOOD, BF_KILL, 0},
    { 368, -1, SHRAP_GIBS, BF_KILL, 0},
    { 369, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 371, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 372, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 374, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 375, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 376, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 377, -1, SHRAP_STONE, BF_KILL, 0},
    { 379, -1, SHRAP_WOOD, BF_KILL, 0},
    { 380, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 385, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 386, -1, SHRAP_GIBS, BF_KILL, 0},
    { 387, -1, SHRAP_GIBS, BF_KILL, 0},
    { 388, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 391, -1, SHRAP_GIBS, BF_KILL, 0},
    { 392, -1, SHRAP_GIBS, BF_KILL, 0},
    { 393, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 394, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 395, -1, SHRAP_GIBS, BF_KILL, 0},
    { 396, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 397, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 398, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 399, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 400, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 401, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 402, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 407, -1, SHRAP_METAL, BF_KILL, 0},
    { 408, -1, SHRAP_PAPER, BF_BURN, 0},
    { 409, -1, SHRAP_PAPER, BF_BURN, 0},
    { 415, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL,5},
    { 418, -1, SHRAP_GENERIC, BF_KILL|BF_FIRE_FALL,5},
    { 422, -1, SHRAP_METAL, BF_KILL, 0},
    { 423, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 424, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 425, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 428, -1, SHRAP_METAL, BF_BURN, 0},
    { 430, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 431, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 432, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 433, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 434, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 435, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 436, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 437, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 438, -1, SHRAP_GIBS, BF_KILL, 0},
    { 441, -1, SHRAP_WOOD, BF_KILL, 0},
    { 442, -1, SHRAP_STONE, BF_KILL, 0},
    { 443, -1, SHRAP_STONE, BF_KILL|BF_FIRE_FALL, 0},
    { 453, -1, SHRAP_WOOD, BF_KILL, 0},
    { 458, -1, SHRAP_STONE, BF_KILL, 0},
    { 459, -1, SHRAP_STONE, BF_KILL, 0},
    { 460, -1, SHRAP_METAL, BF_KILL, 0},
    { 461, -1, SHRAP_METAL, BF_KILL, 0},
    { 462, -1, SHRAP_METAL, BF_KILL, 0},
    { 463, -1, SHRAP_STONE, BF_KILL, 0},
    { 467, -1, SHRAP_STONE, BF_KILL, 0},
    { 468, -1, SHRAP_WOOD, BF_KILL, 0},
    { 475, -1, SHRAP_GLASS, BF_KILL, 0},
    { 481, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 482, -1, SHRAP_WOOD, BF_KILL, 0},
    { 483, -1, SHRAP_WOOD, BF_KILL|BF_TOUGH, 0},
    { 491, -1, SHRAP_WOOD, BF_KILL, 0},
    { 492, -1, SHRAP_METAL, BF_KILL, 0},
    { 493, -1, SHRAP_METAL, BF_KILL, 0},
    { 498, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 500, -1, SHRAP_METAL, BF_KILL, 0},
    { 501, -1, SHRAP_METAL, BF_KILL, 0},
    { 504, -1, SHRAP_METAL, BF_KILL,5},
    { 505, -1, SHRAP_BLOOD, BF_KILL,5},
    { 506, -1, SHRAP_GENERIC, BF_KILL,5},
    { 507, -1, SHRAP_GLASS, BF_KILL, 0},
    { 508, -1, SHRAP_GLASS, BF_KILL, 0},
    { 509, -1, SHRAP_GLASS, BF_KILL, 0},
    { 510, -1, SHRAP_GLASS, BF_KILL, 0},
    { 511, -1, SHRAP_METAL, BF_KILL, 0},
    { 512, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL,5},
    { 516, -1, SHRAP_WOOD, BF_BURN, 0},
    { 517, -1, SHRAP_WOOD, BF_BURN, 0},
    { 518, -1, SHRAP_WOOD, BF_BURN, 0},
    { 519, -1, SHRAP_WOOD, BF_FIRE_FALL|BF_KILL,5},
    { 520, -1, SHRAP_WOOD, BF_KILL, 0},
    { 521, -1, SHRAP_WOOD, BF_KILL|BF_FIRE_FALL, 0},
    { 537, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 541, -1, SHRAP_WOOD, BF_KILL|BF_FIRE_FALL, 0},
    { 586, -1, SHRAP_METAL, BF_KILL, 0},
    { 590, -1, SHRAP_METAL, BF_KILL, 0},
    { 591, -1, SHRAP_METAL, BF_KILL, 0},
    { 593, 608, SHRAP_GLASS,BF_TOUGH, 0},
    { 604, -1, SHRAP_METAL, BF_KILL, 0},
    { 613, -1, SHRAP_LARGE_EXPLOSION, BF_KILL, 0},
    { 614, -1, SHRAP_METAL, BF_KILL, 0},
    { 615, -1, SHRAP_METAL, BF_KILL, 0},
    { 618, -1, SHRAP_GLASS, BF_KILL, 0},
    { 646, -1, SHRAP_METAL, BF_KILL, 0},
    { 647, -1, SHRAP_LARGE_EXPLOSION, BF_KILL, 0},
    { 648, -1, SHRAP_LARGE_EXPLOSION, BF_KILL, 0},
    { 649, -1, SHRAP_METAL, BF_KILL, 0},
    { 656, -1, SHRAP_METAL, BF_KILL, 0},
    { 657, -1, SHRAP_METAL, BF_KILL, 0},
    { 658, -1, SHRAP_LARGE_EXPLOSION, BF_KILL, 0},
    { 659, -1, SHRAP_METAL, BF_KILL,5},
//{ 660, -1, SHRAP_STONE, BF_TOUGH|BF_KILL},
//{ 661, -1, SHRAP_STONE, BF_TOUGH|BF_KILL},
//{ 662, -1, SHRAP_STONE, BF_TOUGH|BF_KILL},
    { 663, -1, SHRAP_METAL, BF_KILL,10},
    { 664, -1, SHRAP_METAL, BF_KILL,5},
    { 666, -1, SHRAP_PLANT, BF_KILL, 0},
    { 670, -1, SHRAP_METAL, BF_KILL|BF_FIRE_FALL, 0},
    { 671, -1, SHRAP_GLASS, BF_KILL|BF_FIRE_FALL, 0},
    { 673, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 674, -1, SHRAP_GIBS, BF_KILL, 0},
    { 675, -1, SHRAP_GIBS, BF_KILL, 0},
    { 676, -1, SHRAP_GIBS, BF_KILL, 0},
    { 678, -1, SHRAP_GLASS, BF_KILL,5},
    { 679, -1, SHRAP_GLASS, BF_KILL,5},
    { 683, -1, SHRAP_GLASS, BF_KILL,5},
    { 684, -1, SHRAP_GLASS, BF_KILL,5},
    { 685, -1, SHRAP_GLASS, BF_KILL,5},
    { 686, -1, SHRAP_PAPER, BF_KILL,5},
    { 687, -1, SHRAP_STONE, BF_KILL|BF_TOUGH, 0},
    { 688, -1, SHRAP_STONE, BF_KILL|BF_TOUGH, 0},
    { 690, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 692, -1, SHRAP_WOOD, BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 695, -1, SHRAP_STONE, BF_KILL, 0},
    { 696, -1, SHRAP_STONE, BF_KILL, 0},
    { 697, -1, SHRAP_STONE, BF_KILL, 0},
    { 698, -1, SHRAP_STONE, BF_KILL, 0},
    { 699, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 702, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 703, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 704, -1, SHRAP_STONE, BF_TOUGH|BF_KILL, 0},
    { 706, -1, SHRAP_PLANT, BF_KILL, 0},
    { 707, -1, SHRAP_PLANT, BF_KILL, 0},
    { 710, -1, SHRAP_PLANT, BF_KILL, 0},
    { 711, -1, SHRAP_PLANT, BF_KILL, 0},
    { 714, -1, SHRAP_STONE, BF_KILL,5},
    { 721, -1, SHRAP_GIBS, BF_KILL, 0},
    { 722, -1, SHRAP_GIBS, BF_KILL, 0},
    { 723, -1, SHRAP_GIBS, BF_KILL, 0},
    { 724, -1, SHRAP_GIBS, BF_KILL, 0},
    { 725, -1, SHRAP_PLANT, BF_KILL, 0},
    { 730, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 744, -1, SHRAP_GLASS, BF_KILL,5},
    { 2563, -1, SHRAP_PAPER, BF_BURN, 0},
    { 2564, -1, SHRAP_PAPER, BF_BURN, 0},
    { 3570, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN, 0},
    { 3571, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 3572, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 3573, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 3574, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 3575, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 3576, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 3577, -1, SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 3578, -1, SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 3579, -1, SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 3580, -1, SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 3581, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN, 0},
    { 3582, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN, 0},
    { 2640, -1, SHRAP_STONE, BF_KILL,5},
    { 2641, -1, SHRAP_STONE, BF_KILL, 0},
    { 2642, -1, SHRAP_STONE, BF_KILL, 0},
    { 2680, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 2681, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 2682, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 2683, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 2684, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 2685, -1, SHRAP_GENERIC, BF_KILL, 0},
    { 2687, 2727, SHRAP_GLASS, 0, 0},
    { 2688, 2728, SHRAP_GLASS, 0, 0},
    { 2699, -1, SHRAP_WOOD, BF_KILL, 0},
    { 2709, -1, SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 2720, -1, SHRAP_GIBS, BF_KILL, 0},
    { 2721, -1, SHRAP_GIBS, BF_KILL, 0},
    { 2722, -1, SHRAP_GIBS, BF_KILL, 0},
    { 2723, -1, SHRAP_GIBS, BF_KILL, 0},
    { 2724, -1, SHRAP_GIBS, BF_KILL, 0},
    { 2725, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 2726, -1, SHRAP_BLOOD, BF_KILL, 0},
    { 2719, -1, SHRAP_GLASS, BF_KILL, 0},
    { 2750, -1, SHRAP_WOOD, BF_KILL, 0},
    { 2676, 3591, SHRAP_GLASS, 0, 0},
    { 2769, 3681, SHRAP_GLASS, 0, 0},
    { 2777, 3683, SHRAP_METAL, BF_TOUGH, 0},
    { 2778, 2757, SHRAP_GLASS, 0, 0},
    { 3448, 3451, SHRAP_METAL, BF_TOUGH|BF_KILL, 0},
    { 3449, -1, SHRAP_PAPER, BF_KILL, 0},
    { 3497, -1, SHRAP_GENERIC, BF_KILL|BF_TOUGH, 0},
    { 3551, -1, SHRAP_METAL, BF_KILL, 0},
    { 3552, -1, SHRAP_METAL, BF_KILL, 0},
    { 3553, -1, SHRAP_METAL, BF_KILL, 0},
    { 3554, -1, SHRAP_METAL, BF_KILL, 0},
    { 3555, -1, SHRAP_METAL, BF_KILL, 0},
    { 3556, -1, SHRAP_METAL, BF_KILL, 0},
    { 3557, -1, SHRAP_METAL, BF_KILL, 0},
    { 3558, -1, SHRAP_WOOD, BF_KILL, 0},
    { 3568, -1, SHRAP_WOOD, BF_BURN, 0},
    { 4994, -1, SHRAP_METAL, BF_KILL, 0},
    { 4995, -1, SHRAP_METAL, BF_KILL, 0},
    { 5010, -1, SHRAP_WOOD, BF_TOUGH|BF_BURN|BF_OVERRIDE_BLOCK, 0},
    { 5017, -1, SHRAP_PAPER, BF_KILL, 0},
    { 5018, -1, SHRAP_PAPER, BF_KILL, 0},
    { 5019, -1, SHRAP_PAPER, BF_KILL, 0},
    { 5060, -1, SHRAP_METAL, BF_KILL, 0},
    { 5061, -1, SHRAP_METAL, BF_KILL, 0},
    { 5073, -1, SHRAP_GIBS, BF_KILL, 0},

// Evil ninja Hari-Kari - can gib
    { 4218, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},

// old ninja dead frames
    { 1133, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 1134, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},

// dead actors
    { 811, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 1440, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 1512, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 1643, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 1680, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},

    { 4219+7, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 4236, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},  // Evil Ninja cut in half
    { 4421, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},  // Dead Big Ripper
    { 4312, -1, SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},  // Dead Coolie Ghost
    { 766, -1, SHRAP_COIN,BF_KILL, 0},
    { 767, -1, SHRAP_COIN,BF_KILL, 0},
    { 2700, -1, SHRAP_PAPER,BF_KILL, 0},
    { 2701, -1, SHRAP_PAPER,BF_KILL, 0},
    { 2702, -1, SHRAP_PAPER,BF_KILL, 0},
    { 2703, -1, SHRAP_PAPER,BF_KILL, 0},
    { 2704, -1, SHRAP_PAPER,BF_KILL, 0},
    { 2705, -1, SHRAP_PAPER,BF_KILL, 0},
    { 2218, -1, SHRAP_METAL,BF_KILL, 0},   // Caltrops are breakable
    { 689,  -1, SHRAP_METAL,BF_TOUGH|BF_KILL, 0},
//{ 2540, -1, SHRAP_METAL,BF_TOUGH|BF_KILL},
    { 3354, -1, SHRAP_METAL,BF_KILL, 0},
    { 3357, -1, SHRAP_PAPER,BF_KILL, 0},
    { 4886, -1, SHRAP_GLASS,BF_KILL, 0},
    { 646, 708, SHRAP_METAL,BF_TOUGH|BF_KILL, 0},
    { 708, -1,  SHRAP_METAL,BF_TOUGH|BF_KILL, 0},
    { 656, -1,  SHRAP_METAL,BF_KILL, 0},
    { 663, -1,  SHRAP_METAL,BF_KILL, 0},
    { 664, -1,  SHRAP_METAL,BF_KILL, 0},
    { 691, -1,  SHRAP_METAL,BF_KILL, 0},
    { 5021,-1,  SHRAP_GIBS, BF_KILL, 0},
    { 712, -1,  SHRAP_LARGE_EXPLOSION, BF_KILL, 0},
    { 713, -1,  SHRAP_LARGE_EXPLOSION, BF_KILL, 0},
    { 693, -1,  SHRAP_WOODMIX, BF_KILL|BF_TOUGH, 0},
    { 5041,-1,  SHRAP_GIBS, BF_KILL, 0},
    { 5042,5077,SHRAP_GIBS, BF_TOUGH|BF_KILL, 0},
    { 5077,-1,  SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 3356,3358,SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 3358,-1,  SHRAP_WOOD, BF_TOUGH|BF_KILL, 0},
    { 900, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 901, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 902, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 915, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 916, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 917, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 930, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 931, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 932, -1,  SHRAP_GIBS, BF_KILL, 0},
    { 1670,-1,  SHRAP_GIBS, BF_KILL, 0},
    { 2219,-1,  SHRAP_METAL,BF_KILL|BF_TOUGH, 0},
    { 4768,-1,  SHRAP_GLASS,BF_KILL, 0},
    { 4792,-1,  SHRAP_GLASS,BF_KILL, 0},
    { 4816,-1,  SHRAP_GLASS,BF_KILL, 0},
    { 4840,-1,  SHRAP_GLASS,BF_KILL, 0},
    { 4584,-1,  SHRAP_GIBS, BF_KILL, 0},
    { 5062,-1,  SHRAP_WOOD, BF_KILL|BF_TOUGH, 0},
    { 5063,4947,SHRAP_PAPERMIX, BF_KILL|BF_TOUGH|BF_LEAVE_BREAK, 0},
    { 4947,-1,  SHRAP_PAPERMIX, BF_KILL|BF_TOUGH, 0},
    { 1160,-1,  SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
    { 5104,-1,  SHRAP_GIBS, BF_KILL, 0},
    { 3795,-1,  SHRAP_GIBS, BF_KILL, 0},
    { 470,-1,   SHRAP_GIBS, BF_KILL, 0},
    { 5205,-1,  SHRAP_GIBS, BF_KILL|BF_TOUGH, 0},
//{ 969,-1,   SHRAP_GIBS, BF_KILL|BF_TOUGH},
//{ 1277,-1,  SHRAP_GIBS, BF_KILL|BF_TOUGH},
};

//////////////////////////////////////////////
// SORT & SEARCH SUPPORT
//////////////////////////////////////////////

static int CompareBreakInfo(void const * a, void const * b)
{
    auto break_info1 = (BREAK_INFO const *)a;
    auto break_info2 = (BREAK_INFO const *)b;

    // will return a number less than 0 if break_info1 < break_info2
    return break_info1->picnum - break_info2->picnum;
}

BREAK_INFOp FindWallBreakInfo(short picnum)
{
    return (BREAK_INFOp)bsearch(&picnum, &WallBreakInfo, SIZ(WallBreakInfo), sizeof(BREAK_INFO), CompareBreakInfo);
}

BREAK_INFOp FindSpriteBreakInfo(short picnum)
{
    return (BREAK_INFOp)bsearch(&picnum, &SpriteBreakInfo, SIZ(SpriteBreakInfo), sizeof(BREAK_INFO), CompareBreakInfo);
}

//////////////////////////////////////////////
// SETUP
//////////////////////////////////////////////

void SortBreakInfo(void)
{
    qsort(&SpriteBreakInfo, SIZ(SpriteBreakInfo), sizeof(BREAK_INFO), CompareBreakInfo);
    qsort(&WallBreakInfo, SIZ(WallBreakInfo), sizeof(BREAK_INFO), CompareBreakInfo);
}

BREAK_INFOp SetupWallForBreak(WALLp wallp)
{
    BREAK_INFOp break_info;

    break_info = FindWallBreakInfo(wallp->picnum);
    if (break_info)
    {
        wallp->lotag = TAG_WALL_BREAK;
        SET(wallp->extra, WALLFX_DONT_STICK);

        // set for cacheing
        if (break_info->breaknum >= 0)
            SET_GOTPIC(break_info->breaknum);
    }

    if (wallp->overpicnum > 0 && TEST(wallp->cstat, CSTAT_WALL_MASKED))
    {
        break_info = FindWallBreakInfo(wallp->overpicnum);
        if (break_info)
        {
            wallp->lotag = TAG_WALL_BREAK;
            SET(wallp->extra, WALLFX_DONT_STICK);
        }
    }

    return break_info;
}

BREAK_INFOp SetupSpriteForBreak(SPRITEp sp)
{
    short picnum = sp->picnum;
    BREAK_INFOp break_info;

    // ignore as a breakable if true
    if (sp->lotag == TAG_SPRITE_HIT_MATCH)
        return NULL;

    break_info = FindSpriteBreakInfo(picnum);
    if (break_info)
    {

        // use certain sprites own blocking for determination
        if (TEST(break_info->flags, BF_OVERRIDE_BLOCK))
        {
            // if not blocking then skip this code
            if (!TEST(sp->cstat, CSTAT_SPRITE_BLOCK))
            {
                return (BREAK_INFOp)(-1);
            }
        }

        if (TEST(break_info->flags, BF_BURN))
            SET(sp->extra, SPRX_BURNABLE);
        else
            SET(sp->extra, SPRX_BREAKABLE);

        sp->clipdist = SPRITEp_SIZE_X(sp);

        SET(sp->cstat, CSTAT_SPRITE_BREAKABLE);

        // set for cacheing
        if (break_info->breaknum >= 0)
            SET_GOTPIC(break_info->breaknum);
    }

    return break_info;
}

//////////////////////////////////////////////
// ACTIVATE
//////////////////////////////////////////////

short FindBreakSpriteMatch(short match)
{
    short i,nexti;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_BREAKABLE], i, nexti)
    {
        if (SPRITE_TAG2(i) == match && sprite[i].picnum == ST1)
        {
            return i;
        }
    }

    return -1;
}

//
// WALL
//

int AutoBreakWall(WALLp wallp, int hit_x, int hit_y, int hit_z, short ang, short type)
{
    BREAK_INFOp break_info;
    short BreakSprite;
    WALLp nwp;
    SPRITEp bsp;

    //DSPRINTF(ds,"wallnum %d, pic %d, lo %d, hi %d",wallp-wall, wallp->picnum, wallp->lotag, wallp->hitag);
    MONO_PRINT(ds);

    wallp->lotag = 0;
    if (wallp->nextwall >= 0)
    {
        nwp = &wall[wallp->nextwall];

        // get rid of both sides
        // only break ONE of the walls

        if (nwp->lotag == TAG_WALL_BREAK &&
            nwp->overpicnum > 0 &&
            TEST(nwp->cstat, CSTAT_WALL_MASKED))
        {
            nwp->lotag = 0;
        }
    }

    if (wallp->overpicnum > 0 && TEST(wallp->cstat, CSTAT_WALL_MASKED))
        break_info = FindWallBreakInfo(wallp->overpicnum);
    else
        break_info = FindWallBreakInfo(wallp->picnum);

    if (!break_info)
    {
        //DSPRINTF(ds,"Break Info not found - wall %d", wallp - wall);
        MONO_PRINT(ds);

        return FALSE;
    }

    // Check to see if it should break with current weapon type
    if (!CheckBreakToughness(break_info, type)) return FALSE;

    if (hit_x != INT32_MAX)
    {
        vec3_t hit_pos = { hit_x, hit_y, hit_z };
        // need correct location for spawning shrap
        BreakSprite = COVERinsertsprite(0, STAT_DEFAULT);
        ASSERT(BreakSprite >= 0);
        bsp = &sprite[BreakSprite];
        bsp->cstat = 0;
        bsp->extra = 0;
        bsp->ang = ang;
        bsp->picnum = ST1;
        bsp->xrepeat = bsp->yrepeat = 64;
        setspritez(BreakSprite, &hit_pos);

        // pass Break Info Globally
        GlobBreakInfo = break_info;
        SpawnShrap(BreakSprite, -1);
        GlobBreakInfo = NULL;

        KillSprite(BreakSprite);
    }

    // change the wall
    if (wallp->overpicnum > 0 && TEST(wallp->cstat, CSTAT_WALL_MASKED))
    {
        if (break_info->breaknum == -1)
        {
            RESET(wallp->cstat, CSTAT_WALL_MASKED|CSTAT_WALL_1WAY|CSTAT_WALL_BLOCK_HITSCAN|CSTAT_WALL_BLOCK);
            wallp->overpicnum = 0;
            if (wallp->nextwall >= 0)
            {
                nwp = &wall[wallp->nextwall];
                RESET(nwp->cstat, CSTAT_WALL_MASKED|CSTAT_WALL_1WAY|CSTAT_WALL_BLOCK_HITSCAN|CSTAT_WALL_BLOCK);
                nwp->overpicnum = 0;
            }
        }
        else
        {
            RESET(wallp->cstat, CSTAT_WALL_BLOCK_HITSCAN|CSTAT_WALL_BLOCK);
            wallp->overpicnum = break_info->breaknum;
            if (wallp->nextwall >= 0)
            {
                nwp = &wall[wallp->nextwall];
                RESET(nwp->cstat, CSTAT_WALL_BLOCK_HITSCAN|CSTAT_WALL_BLOCK);
                nwp->overpicnum = break_info->breaknum;
            }
        }
    }
    else
    {
        if (break_info->breaknum == -1)
            wallp->picnum = 594; // temporary break pic
        else
        {
            wallp->picnum = break_info->breaknum;
            if ((int16_t)wallp->hitag < 0)
                DoWallBreakSpriteMatch(wallp->hitag);
        }
    }

    return TRUE;
}

SWBOOL UserBreakWall(WALLp wp, short UNUSED(ang))
{
    short SpriteNum;
    SPRITEp sp;
    short match = wp->hitag;
    int block_flags = CSTAT_WALL_BLOCK|CSTAT_WALL_BLOCK_HITSCAN;
    int type_flags = CSTAT_WALL_TRANSLUCENT|CSTAT_WALL_MASKED|CSTAT_WALL_1WAY;
    int flags = block_flags|type_flags;
    short ret = FALSE;

    SpriteNum = FindBreakSpriteMatch(match);

    if (SpriteNum < 0)
    {
        // do it the old way and get rid of wall - assumed to be masked
        DoSpawnSpotsForKill(match);
        RESET(wp->cstat, flags);
        if (wp->nextwall >= 0)
            RESET(wall[wp->nextwall].cstat, flags);

        // clear tags
        wp->hitag = wp->lotag = 0;
        if (wp->nextwall >= 0)
            wall[wp->nextwall].hitag = wall[wp->nextwall].lotag = 0;
        return TRUE;
    }

    sp = &sprite[SpriteNum];

    if (wp->picnum == SP_TAG5(sp))
        return TRUE;

    // make it BROKEN
    if (SP_TAG7(sp) <= 1)
    {
        DoSpawnSpotsForKill(match);
        DoLightingMatch(match, -1);

        if (SP_TAG8(sp) == 0)
        {
            wp->picnum = SP_TAG5(sp);
            // clear tags
            wp->hitag = wp->lotag = 0;
            if (wp->nextwall >= 0)
                wall[wp->nextwall].hitag = wall[wp->nextwall].lotag = 0;
            ret = FALSE;
        }
        else if (SP_TAG8(sp) == 1)
        {
            // clear flags
            RESET(wp->cstat, flags);
            if (wp->nextwall >= 0)
                RESET(wall[wp->nextwall].cstat, flags);
            // clear tags
            wp->hitag = wp->lotag = 0;
            if (wp->nextwall >= 0)
                wall[wp->nextwall].hitag = wall[wp->nextwall].lotag = 0;

            ret = TRUE;
        }
        else if (SP_TAG8(sp) == 2)
        {
            // set to broken pic
            wp->picnum = SP_TAG5(sp);

            // clear flags
            RESET(wp->cstat, block_flags);
            if (wp->nextwall >= 0)
                RESET(wall[wp->nextwall].cstat, block_flags);

            // clear tags
            wp->hitag = wp->lotag = 0;
            if (wp->nextwall >= 0)
                wall[wp->nextwall].hitag = wall[wp->nextwall].lotag = 0;

            ret = FALSE;
        }

        return ret;
    }
    else
    {
        // increment picnum
        wp->picnum++;

        DoSpawnSpotsForDamage(match);
    }

    return FALSE;
}

int WallBreakPosition(short hit_wall, short *sectnum, int *x, int *y, int *z, short *ang)
{
    short w,nw;
    WALLp wp;
    int nx,ny;
    short wall_ang;

    w = hit_wall;
    wp = &wall[w];

    nw = wall[w].point2;
    wall_ang = NORM_ANGLE(getangle(wall[nw].x - wall[w].x, wall[nw].y - wall[w].y)+512);

    *sectnum = SectorOfWall(w);
    ASSERT(*sectnum >= 0);

    // midpoint of wall
    *x = DIV2(wall[w].x + wall[w].x);
    *y = DIV2(wall[w].y + wall[w].y);

    //getzsofsector(*sectnum, *x, *y, cz, fz);

    if (wp->nextwall < 0)
    {
        // white wall
        *z = DIV2(sector[*sectnum].floorz + sector[*sectnum].ceilingz);
    }
    else
    {
        short next_sectnum = wp->nextsector;

        // red wall
        ASSERT(wp->nextsector >= 0);

        // floor and ceiling meet
        if (sector[next_sectnum].floorz == sector[next_sectnum].ceilingz)
            *z = DIV2(sector[*sectnum].floorz + sector[*sectnum].ceilingz);
        else
        // floor is above other sector
        if (sector[next_sectnum].floorz < sector[*sectnum].floorz)
            *z = DIV2(sector[next_sectnum].floorz + sector[*sectnum].floorz);
        else
        // ceiling is below other sector
        if (sector[next_sectnum].ceilingz > sector[*sectnum].ceilingz)
            *z = DIV2(sector[next_sectnum].ceilingz + sector[*sectnum].ceilingz);
    }

    *ang = wall_ang;

    nx = MOVEx(128, wall_ang);
    ny = MOVEy(128, wall_ang);

    *x += nx;
    *y += ny;

    updatesectorz(*x,*y,*z,sectnum);
    if (*sectnum < 0)
    {
        *x = INT32_MAX;  // don't spawn shrap, just change wall
        return FALSE;
    }

    return TRUE;
}

// If the tough parameter is not set, then it can't break tough walls and sprites
SWBOOL HitBreakWall(WALLp wp, int hit_x, int hit_y, int hit_z, short ang, short type)
{
    short match = wp->hitag;

    if (match > 0)
    {
        UserBreakWall(wp, ang);
        return TRUE;
    }

    //if (hit_x == INT32_MAX)
    {
        short sectnum;
        WallBreakPosition(wp - wall, &sectnum, &hit_x, &hit_y, &hit_z, &ang);
    }

    AutoBreakWall(wp, hit_x, hit_y, hit_z, ang, type);
    return TRUE;
}

//
// SPRITE
//

int KillBreakSprite(short BreakSprite)
{
    SPRITEp bp = &sprite[BreakSprite];
    USERp bu = User[BreakSprite];

    // Does not actually kill the sprite so it will be valid for the rest
    // of the loop traversal.

    // IMPORTANT: Do not change the statnum if possible so that NEXTI in
    // SpriteControl loop traversals will maintain integrity.

    SpriteQueueDelete(BreakSprite);

    if (bu)
    {
        if (bp->statnum == STAT_DEFAULT)
            // special case allow kill of sprites on STAT_DEFAULT list
            // a few things have users and are not StateControlled
            KillSprite(BreakSprite);
        else
            SetSuicide(BreakSprite);
    }
    else
    {
        change_sprite_stat(BreakSprite, STAT_SUICIDE);
    }

    return 0;
}

int UserBreakSprite(short BreakSprite)
{
    SPRITEp sp;
    SPRITEp bp = &sprite[BreakSprite];
    short match = bp->lotag;
    short match_extra;
    short SpriteNum;

    SpriteNum = FindBreakSpriteMatch(match);

    if (SpriteNum < 0)
    {
        // even if you didn't find a matching ST1 go ahead and kill it and match everything
        // its better than forcing everyone to have a ST1
        DoMatchEverything(NULL, match, -1);
        // Kill sound if one is attached
        DeleteNoSoundOwner(BreakSprite);
        //change_sprite_stat(BreakSprite, STAT_SUICIDE);
        KillBreakSprite(BreakSprite);
        return TRUE;
    }

    sp = &sprite[SpriteNum];
    match_extra = SP_TAG6(bp);

    if (bp->picnum == SP_TAG5(sp))
        return TRUE;

    // make it BROKEN
    if (SP_TAG7(sp) <= 1)
    {
        DoMatchEverything(NULL, match_extra, -1);
        //DoSpawnSpotsForKill(match_extra);
        DoLightingMatch(match_extra, OFF);

        if (SP_TAG8(sp) == 0)
        {
            bp->picnum = SP_TAG5(sp);
            RESET(bp->extra, SPRX_BREAKABLE);
        }
        else
        // kill sprite
        if (SP_TAG8(sp) == 1)
        {
            // Kill sound if one is attached
            DeleteNoSoundOwner(BreakSprite);
            KillBreakSprite(BreakSprite);
            //KillSprite(BreakSprite);
            //change_sprite_stat(BreakSprite, STAT_SUICIDE);
            return TRUE;
        }
        else if (SP_TAG8(sp) == 2)
        // leave it
        {
            // set to broken pic
            bp->picnum = SP_TAG5(sp);

            // reset
            if (SP_TAG8(sp) == 2)
            {
                RESET(bp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
            }

            RESET(bp->extra, SPRX_BREAKABLE);
        }
    }
    else
    {
        // increment picnum
        bp->picnum++;

        DoSpawnSpotsForDamage(match_extra);
    }

    return FALSE;
}

int AutoBreakSprite(short BreakSprite, short type)
{
    SPRITEp bp = &sprite[BreakSprite];
    BREAK_INFOp break_info;
    extern void DoWallBreakMatch(short match);

    break_info = FindSpriteBreakInfo(bp->picnum);

    if ((int16_t)bp->hitag < 0)
        DoWallBreakMatch(bp->hitag);

    if (!break_info)
    {
        //DSPRINTF(ds,"Break Info not found - sprite %d", bp - sprite);
        MONO_PRINT(ds);
        return FALSE;
    }

    // Check to see if it should break with current weapon type
    if (!CheckBreakToughness(break_info, type))
    {
        if (break_info->breaknum != -1)
        {
            if (!TEST(break_info->flags, BF_LEAVE_BREAK))
            {
                RESET(bp->extra, SPRX_BREAKABLE);
                RESET(bp->cstat, CSTAT_SPRITE_BREAKABLE);
            }

            bp->picnum = break_info->breaknum;
            // pass Break Info Globally
            GlobBreakInfo = break_info;
            SpawnShrap(BreakSprite, -1);
            GlobBreakInfo = NULL;
            if (bp->picnum == 3683)
                RESET(bp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
        }

        return FALSE;
    }

    RESET(bp->extra, SPRX_BREAKABLE);
    RESET(bp->cstat, CSTAT_SPRITE_BREAKABLE);

    // pass Break Info Globally
    GlobBreakInfo = break_info;
    SpawnShrap(BreakSprite, -1);
    GlobBreakInfo = NULL;

    // kill it or change the pic
    if (TEST(break_info->flags, BF_KILL) || break_info->breaknum == -1)
    {
        if (TEST(break_info->flags, BF_FIRE_FALL))
            SpawnBreakFlames(BreakSprite);

        RESET(bp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
        SET(bp->cstat, CSTAT_SPRITE_INVISIBLE);
        // Kill sound if one is attached
        DeleteNoSoundOwner(BreakSprite);
        KillBreakSprite(BreakSprite);
        //change_sprite_stat(BreakSprite, STAT_SUICIDE);
        //KillSprite(BreakSprite);
        return TRUE;
    }
    else
    {
        bp->picnum = break_info->breaknum;
        if (bp->picnum == 3683)
            RESET(bp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
    }

    return FALSE;
}

SWBOOL NullActor(USERp u)
{
    // a Null Actor is defined as an actor that has no real controlling programming attached

    // check to see if attached to SO
    if (TEST(u->Flags, SPR_SO_ATTACHED))
        return TRUE;

    // does not have a STATE or FUNC to control it
    if (!u->State)
        return TRUE;

    // does not have a STATE or FUNC to control it
    if (!u->ActorActionFunc)
        return TRUE;

    return FALSE;
}

int HitBreakSprite(short BreakSprite, short type)
{
    SPRITEp bp = &sprite[BreakSprite];
    USERp bu = User[BreakSprite];

    //SPRITEp sp;
    // ignore as a breakable if true
    //if (sp->lotag == TAG_SPRITE_HIT_MATCH)
    //    return(FALSE);

    if (TEST_BOOL1(bp))
    {
        if (TEST_BOOL2(bp))
            return FALSE;

        return UserBreakSprite(BreakSprite);
    }

    if (bu && !NullActor(bu))
    {
        // programmed animating type - without BOOL1 set
        if (bp->lotag)
            DoLightingMatch(bp->lotag, -1);

        SpawnShrap(BreakSprite, -1);
        RESET(bp->extra, SPRX_BREAKABLE);
        return FALSE;
    }

    return AutoBreakSprite(BreakSprite,type);
}

static int SectorOfWall(short theline)
{
    short i, startwall, endwall, sectnum;

    sectnum = -1;

    for (i=0; i<numsectors; i++)
    {
        startwall = sector[i].wallptr;
        endwall = startwall + sector[i].wallnum - 1;
        if ((theline >= startwall) && (theline <= endwall))
        {
            sectnum = i;
            break;
        }
    }

    return sectnum;
}

void DoWallBreakMatch(short match)
{
    short i,sectnum;
    int x,y,z;
    WALLp wp;
    short wall_ang;

    for (i=0; i<=numwalls; i++)
    {
        if (wall[i].hitag == match)
        {
            WallBreakPosition(i, &sectnum, &x, &y, &z, &wall_ang);
            //short nw = wall[i].point2;
            //wall_ang = NORM_ANGLE(getangle(wall[nw].x - wall[i].x, wall[nw].y - wall[i].y)+512);
            wp = &wall[i];
            wp->hitag = 0; // Reset the hitag
            AutoBreakWall(wp, x, y, z, wall_ang, 0);
        }
    }
}

static void DoWallBreakSpriteMatch(short match)
{
    short i,nexti;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_ENEMY], i, nexti)
    {
        SPRITEp sp = &sprite[i];

        if (sp->hitag == match)
        {
            KillSprite(i);
        }
    }
}


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

#define BF_TOUGH (BIT(0))
#define BF_KILL  (BIT(1))
#define BF_BURN  (BIT(2))
#define BF_OVERRIDE_BLOCK  (BIT(3))
#define BF_FIRE_FALL (BIT(4))
#define BF_LEAVE_BREAK (BIT(5))

typedef struct
{
    short picnum, breaknum, shrap_type;
    short flags, shrap_amt;
} BREAK_INFO, *BREAK_INFOp;

BREAK_INFOp FindWallBreakInfo(short picnum);
BREAK_INFOp FindSpriteBreakInfo(short picnum);
void SortBreakInfo(void);
BREAK_INFOp SetupWallForBreak(WALLp wallp);
BREAK_INFOp SetupSpriteForBreak(SPRITEp sp);
short FindBreakSpriteMatch(short match);
SWBOOL HitBreakWall(WALLp wp, int, int, int, short ang, short type);
int HitBreakSprite(short BreakSprite, short type);
SWBOOL CheckBreakToughness(BREAK_INFOp break_info, short ID);
int WallBreakPosition(short hit_wall, short *sectnum, int *x, int *y, int *z, short *ang);
void SortBreakInfo(void);

#endif


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

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

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

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

See the GNU General Public License for more details.

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

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

#include "build.h"
#include "editor.h"

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

extern int qsetmode;

SWBOOL FindCeilingView(short match, int32_t* x, int32_t* y, int32_t z, int16_t* sectnum);
SWBOOL FindFloorView(short match, int32_t* x, int32_t* y, int32_t z, int16_t* sectnum);
short ViewSectorInScene(short cursectnum, short level);
void Message(const char *string, char color);

void
_Assert(const char *expr, const char *strFile, unsigned uLine)
{
    printf(ds, "Assertion failed: %s %s, line %u\n", expr, strFile, uLine);
    //DSPRINTF(ds, "Assertion failed: %s %s, line %u", expr, strFile, uLine);
    MONO_PRINT(ds);
    exit(0);
}

////////////////////////////////////////////////////////////////////
//
// FLOOR ABOVE FLOOR
//
////////////////////////////////////////////////////////////////////

#define ZMAX 400
typedef struct
{
    int32_t zval[ZMAX];
    int16_t sectnum[ZMAX];
    int16_t pic[ZMAX];
    int16_t slope[ZMAX];
    int16_t zcount;
} SAVE, *SAVEp;

SAVE save;

SWBOOL FAFon = 0;
SWBOOL FAF_DontMoveSectors = FALSE;

short bak_searchsector, bak_searchwall, bak_searchstat;
extern short searchsector, searchwall, searchstat, searchit;

void SetupBuildFAF(void);
void ResetBuildFAF(void);

void ToggleFAF(void)
{
    if (keystatus[KEYSC_3])
    {
        keystatus[KEYSC_3] = FALSE;

        FLIP(FAFon, 1);
        if (FAFon)
        {
            SetupBuildFAF();
        }
        else
        {
            ResetBuildFAF();
        }
    }

    if (FAFon && qsetmode == 200)
    {
        DrawOverlapRoom(pos.x, pos.y, pos.z, ang, horiz, cursectnum);

        // make it so that you can edit both areas in 3D
        // back up vars after the first drawrooms
        bak_searchsector = searchsector;
        bak_searchwall = searchwall;
        bak_searchstat = searchstat;
        searchit = 2;
    }

    if (FAFon && qsetmode == 200 && keystatus[KEYSC_4])
    {
        short match;
        int tx,ty,tz;
        short tsectnum;
        keystatus[KEYSC_4] = FALSE;

        tx = pos.x;
        ty = pos.y;
        tz = pos.z;
        tsectnum = cursectnum;

        save.zcount = 0;

        if (sector[cursectnum].ceilingpicnum == FAF_MIRROR_PIC)
        {
            match = ViewSectorInScene(tsectnum, VIEW_LEVEL1);

            FAF_DontMoveSectors = TRUE;
            FindCeilingView(match, &tx, &ty, tz, &tsectnum);
            FAF_DontMoveSectors = FALSE;

            pos.x = tx;
            pos.y = ty;
            cursectnum = tsectnum;
            pos.z = sector[cursectnum].floorz - Z(20);
        }
        else if (sector[cursectnum].floorpicnum == FAF_MIRROR_PIC)
        {
            match = ViewSectorInScene(tsectnum, VIEW_LEVEL2);

            FAF_DontMoveSectors = TRUE;
            FindFloorView(match, &tx, &ty, tz, &tsectnum);
            FAF_DontMoveSectors = FALSE;

            pos.x = tx;
            pos.y = ty;
            cursectnum = tsectnum;
            pos.z = sector[cursectnum].ceilingz + Z(20);
        }
    }

}

void FAF_AfterDrawRooms(void)
{
    // make it so that you can edit both areas in 3D
    // if your cursor is in the FAF_MIRROR_PIC area use the vars from the first
    // drawrooms instead
    if ((searchstat == 1 && sector[searchsector].ceilingpicnum == FAF_MIRROR_PIC) ||
        (searchstat == 2 && sector[searchsector].floorpicnum == FAF_MIRROR_PIC))
    {
        searchsector = bak_searchsector;
        searchwall = bak_searchwall;
        searchstat = bak_searchstat;
    }
}

void
SetupBuildFAF(void)
{
    short i, nexti;
    SPRITEp sp;
    short SpriteNum, NextSprite;
    // move every sprite to the correct list
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_DEFAULT], SpriteNum, NextSprite)
    {
        sp = &sprite[SpriteNum];

        if (sp->picnum != ST1)
            continue;

        switch (sp->hitag)
        {
        case VIEW_THRU_CEILING:
        case VIEW_THRU_FLOOR:
        {
            int i,nexti;
            // make sure there is only one set per level of these
            TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
            {
                if (sprite[i].hitag == sp->hitag && sprite[i].lotag == sp->lotag)
                {
                    sprintf(ds,"Two VIEW_THRU_ tags with same match found on level\n1: x %d, y %d \n2: x %d, y %d", TrackerCast(sp->x), TrackerCast(sp->y), TrackerCast(sprite[i].x), TrackerCast(sprite[i].y));
                    Message(ds,0);
                }
            }

            changespritestat(SpriteNum, STAT_FAF);
            break;
        }

        case VIEW_LEVEL1:
        case VIEW_LEVEL2:
        case VIEW_LEVEL3:
        case VIEW_LEVEL4:
        case VIEW_LEVEL5:
        case VIEW_LEVEL6:
        {
            changespritestat(SpriteNum, STAT_FAF);
            break;
        }
        }
    }

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sector[sp->sectnum].ceilingpicnum == FAF_PLACE_MIRROR_PIC)
        {
            sector[sp->sectnum].ceilingpicnum = FAF_MIRROR_PIC;
            if (sector[sp->sectnum].floorz == sector[sp->sectnum].ceilingz)
            {
                sprintf(ds, "Mirror used for non-connect area. Use tile 342. Sect %d, x %d, y %d\n", TrackerCast(sp->sectnum), TrackerCast(wall[sector[sp->sectnum].wallptr].x), TrackerCast(wall[sector[sp->sectnum].wallptr].y));
                Message(ds,0);
            }
        }

        if (sector[sp->sectnum].floorpicnum == FAF_PLACE_MIRROR_PIC)
        {
            sector[sp->sectnum].floorpicnum = FAF_MIRROR_PIC;
            if (sector[sp->sectnum].floorz == sector[sp->sectnum].ceilingz)
            {
                sprintf(ds, "Mirror used for non-connect area. Use tile 342. Sect %d, x %d, y %d\n", TrackerCast(sp->sectnum), TrackerCast(wall[sector[sp->sectnum].wallptr].x), TrackerCast(wall[sector[sp->sectnum].wallptr].y));
                Message(ds,0);
            }
        }

        if (sector[sp->sectnum].ceilingpicnum == FAF_PLACE_MIRROR_PIC+1)
            sector[sp->sectnum].ceilingpicnum = FAF_MIRROR_PIC+1;

        if (sector[sp->sectnum].floorpicnum == FAF_PLACE_MIRROR_PIC+1)
            sector[sp->sectnum].floorpicnum = FAF_MIRROR_PIC+1;
    }

    for (i = 0; i < numwalls; i++)
    {
        if (wall[i].picnum == FAF_PLACE_MIRROR_PIC)
            wall[i].picnum = FAF_MIRROR_PIC;

        if (wall[i].picnum == FAF_PLACE_MIRROR_PIC+1)
            wall[i].picnum = FAF_MIRROR_PIC+1;
    }

#if 0
    // check ceiling and floor heights
    SPRITEp vc_sp,vf_sp,vl_sp;
    short vc,nextvc,vf,nextvf,l,nextl;
    int zdiff;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], vc, nextvc)
    {
        vc_sp = &sprite[vc];

        if (vc_sp->hitag == VIEW_THRU_CEILING)
        {
            TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], vf, nextvf)
            {
                vf_sp = &sprite[vf];

                if (vf_sp->hitag == VIEW_THRU_FLOOR && vf_sp->lotag == vc_sp->lotag)
                {
                    zdiff = labs(sector[vc_sp->sectnum].ceilingz - sector[vf_sp->sectnum].floorz);

                    //DSPRINTF(ds,"zdiff %d",zdiff);
                    MONO_PRINT(ds);

                    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], l, nextl)
                    {
                        vl_sp = &sprite[l];

                        if (vl_sp->hitag == VIEW_LEVEL1)
                        {
                            if (sector[vl_sp->sectnum].ceilingz < sector[vc_sp->sectnum].ceilingz + zdiff)
                            {
                                sprintf(ds,"Sector %d (x %d, y %d) ceiling z to close to VIEW_THRU_CEILING z",
                                        vl_sp->sectnum, wall[sector[vl_sp->sectnum].wallptr].x, wall[sector[vl_sp->sectnum].wallptr].y);
                                Message(ds,0);
                            }
                        }
                        else if (vl_sp->hitag == VIEW_LEVEL2)
                        {
                            if (sector[vl_sp->sectnum].floorz > sector[vf_sp->sectnum].floorz + zdiff)
                            {
                                sprintf(ds,"Sector %d (x %d, y %d)floor z to close to VIEW_THRU_FLOOR z",
                                        vl_sp->sectnum, wall[sector[vl_sp->sectnum].wallptr].x, wall[sector[vl_sp->sectnum].wallptr].y);
                                Message(ds,0);
                            }
                        }
                    }
                }
            }
        }
    }
#endif

}

void
ResetBuildFAF(void)
{
    short i, nexti;
    SPRITEp sp;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sector[sp->sectnum].ceilingpicnum == FAF_MIRROR_PIC)
            sector[sp->sectnum].ceilingpicnum = FAF_PLACE_MIRROR_PIC;

        if (sector[sp->sectnum].floorpicnum == FAF_MIRROR_PIC)
            sector[sp->sectnum].floorpicnum = FAF_PLACE_MIRROR_PIC;

        if (sector[sp->sectnum].ceilingpicnum == FAF_MIRROR_PIC+1)
            sector[sp->sectnum].ceilingpicnum = FAF_PLACE_MIRROR_PIC+1;

        if (sector[sp->sectnum].floorpicnum == FAF_MIRROR_PIC+1)
            sector[sp->sectnum].floorpicnum = FAF_PLACE_MIRROR_PIC+1;
    }

    for (i = 0; i < numwalls; i++)
    {
        if (wall[i].picnum == FAF_MIRROR_PIC)
            wall[i].picnum = FAF_PLACE_MIRROR_PIC;

        if (wall[i].picnum == FAF_MIRROR_PIC+1)
            wall[i].picnum = FAF_PLACE_MIRROR_PIC+1;
    }

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        changespritestat(i, STAT_DEFAULT);
    }
}

SWBOOL
PicInView(short tile_num, SWBOOL reset)
{
    if (TEST(gotpic[tile_num >> 3], 1 << (tile_num & 7)))
    {
        if (reset)
            RESET(gotpic[tile_num >> 3], 1 << (tile_num & 7));

        return TRUE;
    }

    return FALSE;
}

void
GetUpperLowerSector(short match, int x, int y, short *upper, short *lower)
{
    int i;
    short sectorlist[16];
    short sln = 0;
    short SpriteNum, Next;
    SPRITEp sp;

    // didn't find it yet so test ALL sectors
    if (sln < 2)
    {
        sln = 0;
        for (i = numsectors - 1; i >= 0; i--)
        {
            if (inside(x, y, (short) i) == 1)
            {
                SWBOOL found = FALSE;

                TRAVERSE_SPRITE_SECT(headspritesect[i], SpriteNum, Next)
                {
                    sp = &sprite[SpriteNum];

                    if (sp->statnum == STAT_FAF &&
                        (sp->hitag >= VIEW_LEVEL1 && sp->hitag <= VIEW_LEVEL6)
                        && sp->lotag == match)
                    {
                        found = TRUE;
                    }
                }

                if (!found)
                    continue;

                if (sln < (int)SIZ(sectorlist))
                    sectorlist[sln] = i;
                sln++;
            }
        }
    }

    if (sln == 0)
    {
        *upper = -1;
        *lower = -1;
        return;
    }
    // Map rooms have NOT been dragged on top of each other
    else if (sln == 1)
    {
        *lower = sectorlist[0];
        *upper = sectorlist[0];
        return;
    }
    // Map rooms HAVE been dragged on top of each other
    else if (sln > 2)
    {
        // try again moving the x,y pos around until you only get two sectors
        GetUpperLowerSector(match, x - 1, y, upper, lower);
    }
    else if (sln == 2)
    {
        if (sector[sectorlist[0]].floorz < sector[sectorlist[1]].floorz)
        {
            // swap
            // make sectorlist[0] the LOW sector
            short hold;

            hold = sectorlist[0];
            sectorlist[0] = sectorlist[1];
            sectorlist[1] = hold;
        }

        *lower = sectorlist[0];
        *upper = sectorlist[1];
    }
}

SWBOOL
FindCeilingView(short match, int32_t* x, int32_t* y, int32_t z, int16_t* sectnum)
{
    int xoff = 0;
    int yoff = 0;
    short i, nexti;
    SPRITEp sp = NULL;
    int pix_diff;
    int newz;

    save.zcount = 0;

    // Search Stat List For closest ceiling view sprite
    // Get the match, xoff, yoff from this point
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->hitag == VIEW_THRU_CEILING && sp->lotag == match)
        {
            xoff = *x - sp->x;
            yoff = *y - sp->y;

            break;
        }
    }

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->lotag == match)
        {
            // determine x,y position
            if (sp->hitag == VIEW_THRU_FLOOR)
            {
                short upper, lower;

                *x = sp->x + xoff;
                *y = sp->y + yoff;

                // get new sector
                GetUpperLowerSector(match, *x, *y, &upper, &lower);
                *sectnum = upper;
                break;
            }
        }
    }

    if (*sectnum < 0)
        return FALSE;

    ASSERT(sp);
    ASSERT(sp->hitag == VIEW_THRU_FLOOR);

    if (FAF_DontMoveSectors)
        return TRUE;

    pix_diff = labs(z - sector[sp->sectnum].floorz) >> 8;
    newz = sector[sp->sectnum].floorz + ((pix_diff / 128) + 1) * Z(128);

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->lotag == match)
        {
            // move lower levels ceilings up for the correct view
            if (sp->hitag == VIEW_LEVEL2)
            {
                // save it off
                save.sectnum[save.zcount] = sp->sectnum;
                save.zval[save.zcount] = sector[sp->sectnum].floorz;
                save.pic[save.zcount] = sector[sp->sectnum].floorpicnum;
                save.slope[save.zcount] = sector[sp->sectnum].floorheinum;

                sector[sp->sectnum].floorz = newz;
                sector[sp->sectnum].floorpicnum = FAF_MIRROR_PIC+1;
                sector[sp->sectnum].floorheinum = 0;

                save.zcount++;
                ASSERT(save.zcount < ZMAX);
            }
        }
    }

    return TRUE;
}

SWBOOL
FindFloorView(short match, int32_t* x, int32_t* y, int32_t z, int16_t* sectnum)
{
    int xoff = 0;
    int yoff = 0;
    short i, nexti;
    SPRITEp sp = NULL;
    int newz;
    int pix_diff;

    save.zcount = 0;

    // Search Stat List For closest ceiling view sprite
    // Get the match, xoff, yoff from this point
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->hitag == VIEW_THRU_FLOOR && sp->lotag == match)
        {
            xoff = *x - sp->x;
            yoff = *y - sp->y;

            break;
        }
    }

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->lotag == match)
        {
            // determine x,y position
            if (sp->hitag == VIEW_THRU_CEILING)
            {
                short upper, lower;

                *x = sp->x + xoff;
                *y = sp->y + yoff;

                // get new sector
                GetUpperLowerSector(match, *x, *y, &upper, &lower);
                *sectnum = lower;
                break;
            }
        }
    }

    if (*sectnum < 0)
        return FALSE;

    ASSERT(sp);
    ASSERT(sp->hitag == VIEW_THRU_CEILING);

    if (FAF_DontMoveSectors)
        return TRUE;

    // move ceiling multiple of 128 so that the wall tile will line up
    pix_diff = labs(z - sector[sp->sectnum].ceilingz) >> 8;
    newz = sector[sp->sectnum].ceilingz - ((pix_diff / 128) + 1) * Z(128);

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->lotag == match)
        {
            // move upper levels floors down for the correct view
            if (sp->hitag == VIEW_LEVEL1)
            {
                // save it off
                save.sectnum[save.zcount] = sp->sectnum;
                save.zval[save.zcount] = sector[sp->sectnum].ceilingz;
                save.pic[save.zcount] = sector[sp->sectnum].ceilingpicnum;
                save.slope[save.zcount] = sector[sp->sectnum].ceilingheinum;

                sector[sp->sectnum].ceilingz = newz;

                sector[sp->sectnum].ceilingpicnum = FAF_MIRROR_PIC+1;
                sector[sp->sectnum].ceilingheinum = 0;

                save.zcount++;
                ASSERT(save.zcount < ZMAX);
            }
        }
    }

    return TRUE;
}

SWBOOL
SectorInScene(short tile_num)
{
    if (TEST(gotsector[tile_num >> 3], 1 << (tile_num & 7)))
    {
        RESET(gotsector[tile_num >> 3], 1 << (tile_num & 7));
        return TRUE;
    }

    return FALSE;
}

short
ViewSectorInScene(short cursectnum, short level)
{
    int i, nexti;
    SPRITEp sp;
    short match;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_FAF], i, nexti)
    {
        sp = &sprite[i];

        if (sp->hitag == level)
        {
            if (cursectnum == sp->sectnum)
            {
                // ignore case if sprite is pointing up
                if (sp->ang == 1536)
                    continue;

                // only gets to here is sprite is pointing down
                // found a potential match
                match = sp->lotag;

                return match;
            }
        }
    }

    return -1;
}

void
DrawOverlapRoom(int tx, int ty, int tz, fix16_t tq16ang, fix16_t tq16horiz, short tsectnum)
{
    short i;
    short match;

    save.zcount = 0;

    match = ViewSectorInScene(tsectnum, VIEW_LEVEL1);
    if (match != -1)
    {
        FindCeilingView(match, &tx, &ty, tz, &tsectnum);

        if (tsectnum < 0)
        {
            sprintf(ds,"COULD NOT FIND TAGGED LEVEL2 SECTOR FROM X %d, Y %d, SECTNUM %d.",pos.x,pos.y,cursectnum);
            Message(ds, 0);
            return;
        }

        renderDrawRoomsQ16(tx, ty, tz, tq16ang, tq16horiz, tsectnum);
        renderDrawMasks();

        // reset Z's
        for (i = 0; i < save.zcount; i++)
        {
            sector[save.sectnum[i]].floorz = save.zval[i];
            sector[save.sectnum[i]].floorpicnum = save.pic[i];
            sector[save.sectnum[i]].floorheinum = save.slope[i];
        }
    }
    else
    {
        match = ViewSectorInScene(tsectnum, VIEW_LEVEL2);
        if (match != -1)
        {
            FindFloorView(match, &tx, &ty, tz, &tsectnum);

            if (tsectnum < 0)
            {
                sprintf(ds,"COULD NOT FIND TAGGED LEVEL1 SECTOR FROM X %d, Y %d, SECTNUM %d.",pos.x,pos.y,cursectnum);
                Message(ds, 0);
                return;
            }

            renderDrawRoomsQ16(tx, ty, tz, tq16ang, tq16horiz, tsectnum);
            renderDrawMasks();

            // reset Z's
            for (i = 0; i < save.zcount; i++)
            {
                sector[save.sectnum[i]].ceilingz = save.zval[i];
                sector[save.sectnum[i]].ceilingpicnum = save.pic[i];
                sector[save.sectnum[i]].ceilingheinum = save.slope[i];
            }
        }
    }
}