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

// CONSOLE.C
// Handles all argument storing and user console variable modifications.
// 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 "weapon.h"
#include "text.h"
#include "jsector.h"

// DEFINES ///////////////////////////////////////////////////////////////////////////////////
#define MAX_USER_ARGS           100
#define MAX_CONSOLE_COMMANDS    100
#define MAX_HISTORY             20

SWBOOL SpriteInfo = FALSE;
extern SWBOOL QuitFlag;

// FUNCTION PROTOTYPES ///////////////////////////////////////////////////////////////////////
void CON_ProcessOptions(void);
void CON_ClearConsole(void);
uint8_t CON_CommandCmp(const char *str1, const char *str2, int len);
void CheatInput(void);

// Modify actor routines
void CON_ModXrepeat(void);
void CON_ModYrepeat(void);
void CON_ModTranslucent(void);
void CON_GetHelp(void);
void CON_Sound(void);
void CON_Reverb(void);
void CON_Heap(void);
void CON_Cache(void);
void CON_SoundTest(void);
void CON_SpriteInfo(void);
void CON_KillSprite(void);
void CON_SpriteDetail(void);
void CON_UserDetail(void);
void CON_Quit(void);
void CON_LoadSetup(void);
void CON_DamageData(void);
void CON_WinPachinko(void);
void CON_Tweak(void);
void CON_Bunny(void);
void CON_CheckHeap(void);
void CON_DumpHeap(void);
void CON_ShowMirror(void);
void CON_MultiNameChange(void);
void CON_DumpSoundList(void);

// STRUCTURES ////////////////////////////////////////////////////////////////////////////////

typedef struct
{
    const char *command;              // Text string representing the command that calls this function
    void (*function)(void);     // Function assigned to the command, take no parameters

} CON_COMMAND, *CON_COMMANDp;

// Contains any commands that don't get added by particular setup functions
CON_COMMAND pre_commands[] =
{
#if DEBUG
    {"bobbing",     CON_ProcessOptions},
    {"swnext",      CheatInput},
    {"swprev",      CheatInput},
    {"swsecret",    CheatInput},
    {"swstart",     CheatInput},
    {"swres",       CheatInput},
    {"swloc",       CheatInput},
    {"swroom",      CheatInput},
    {"swmap",       CheatInput},
    {"swvox",       CheatInput},
    {"swsave",      CheatInput},
#endif
#if DEBUG
    {"george",      CheatInput},
    {"blackburn",   CheatInput},
    {"reverb",      CON_Reverb},
    {"mem",         CON_Heap},
    {"cache",       CON_Cache},
    {"xrepeat",     CON_ModXrepeat},
    {"yrepeat",     CON_ModYrepeat},
    {"translucent", CON_ModTranslucent},
    {"spriteinfo",  CON_SpriteInfo},
    {"kill",        CON_KillSprite},
    {"showsprite",  CON_SpriteDetail},
    {"showuser",    CON_UserDetail},
    {"damage",      CON_DamageData},
    {"tweak",       CON_Tweak},
    {"checkheap",   CON_CheckHeap},
    {"dumpheap",    CON_DumpHeap},
    {"showmirror",  CON_ShowMirror},
    {"clear",       CON_ClearConsole},
    {"dumpsounds",  CON_DumpSoundList},
    {"help",        CON_GetHelp},
//{"quit",        CON_Quit},
#endif
    {"swchan",      CheatInput},
    {"swgimme",     CheatInput},
    {"swtrek##",    CheatInput},
    {"swgreed",     CheatInput},
    {"swghost",     CheatInput},
    {"swstart",     CheatInput},
    {"swres",       CheatInput},
    {"swloc",       CheatInput},
    {"swmap",       CheatInput},
    {"swsave",      CheatInput},
    {"sound",       CON_SoundTest},
    {"winpachinko", CON_WinPachinko},
    {"config",      CON_LoadSetup},
    {"swtrix",      CON_Bunny},
    {"swname",      CON_MultiNameChange},
    {NULL, NULL}
};

// GLOBALS ///////////////////////////////////////////////////////////////////////////////////

CON_COMMAND commandlist[MAX_CONSOLE_COMMANDS];  // Console command array
CON_COMMANDp commandptr;    // Pointer to a command

int16_t numcommands=0;    // Total number of commands in the command list

char command_history[MAX_HISTORY][256]; // History of what has been typed in lately
int16_t curr_history=0; // Line currently being pointed to in the history array
int16_t add_history_pos=0; // Where next history line will be added in history array
int16_t numhistory=0;

// Array which stores all the user arguments passed into the game.
static char user_args[MAX_USER_ARGS][256];
static uint8_t con_argnum=0;   // Total number of arguments that were passed into the game

char con_message[80]; // Holds the current console message to send to adduserquote

// FUNCTIONS /////////////////////////////////////////////////////////////////////////////////

//
// Frank's neato input string checker, useful for my stuff too.
//
uint8_t CON_CommandCmp(const char *str1, const char *str2, int len)
{
    const char *cp1 = str1;
    const char *cp2 = str2;

    do
    {
        if (*cp1 != *cp2)
        {
            if (*cp1 != '#' && *cp2 != '#')
                return FALSE;
            else if ((*cp1 == '#' && !isdigit(*cp2)) || (*cp2 == '#' && !isdigit(*cp1)))
                return FALSE;
        }

        cp1++;
        cp2++;
    }
    while (--len);

    return TRUE;
}

SWBOOL IsCommand(char *str)
{
    int i;
    char first[512];

    sscanf(str,"%s",first);

    for (i = 0; i < numcommands; i++)
    {
        // Don't even try if they aren't the same length
        if (strlen(first) != strlen(commandlist[i].command))
            continue;

        // See if it's in there
        if (CON_CommandCmp(first, commandlist[i].command, strlen(first)))
        {
            return TRUE;
        }
    }

    return FALSE;
}

//
// Sends a message to the user quote array
//

void CON_Message(const char *message, ...)
{
    va_list argptr;

    va_start(argptr,message);
    vsprintf(&con_message[0],message,argptr);
    va_end(argptr);

    // Send message to user quote array for immediate display
    adduserquote(&con_message[0]);
}

//
// Sends a message to the console quote array
//

void CON_ConMessage(const char *message, ...)
{
    va_list argptr;

    va_start(argptr,message);
    vsprintf(&con_message[0],message,argptr);
    va_end(argptr);

    // Send message to user quote array for immediate display
    addconquote(&con_message[0]);
}

//
// Stores user arguments passed in on the command line for later inspection
//
void CON_StoreArg(const char *userarg)
{
    if (con_argnum < MAX_USER_ARGS)
    {
        strcpy(&user_args[con_argnum][0],userarg);
        Bstrlwr(&user_args[con_argnum][0]);
        con_argnum++;
    }
}

//
// Checkes the user command array to see if user did in fact pass in a particular argument
//
SWBOOL CON_CheckParm(const char *userarg)
{
    int16_t i;

    for (i=0; i<con_argnum; i++)
    {
        if (!strcmp(&user_args[i][0],userarg))
            return TRUE;   // Yep, it's in there
    }

    return FALSE;   // Not a parameter that was passed in
}

//
// Scrolls up and down through previous user commands like DosKey
// Copies the history text string into the MessageInputCommand
//
void CON_CommandHistory(signed char dir)
{
    if (curr_history + dir < numhistory)
        curr_history += dir;
    if (curr_history < 0) curr_history = 0;
    if (curr_history >= MAX_HISTORY) curr_history = MAX_HISTORY - 1;

    strcpy(MessageInputString, command_history[curr_history]);
}

void CON_AddHistory(const char *commandstr)
{
    Bstrncpy(command_history[add_history_pos], commandstr, sizeof(command_history[add_history_pos]));
    if ((++add_history_pos) >= MAX_HISTORY) add_history_pos = 0; // loop back around

    if ((++numhistory) > MAX_HISTORY) numhistory = MAX_HISTORY;
}

//
// Adds a command name to the command list and assigns the appropriate function pointer
//
SWBOOL CON_AddCommand(const char *command, void (*function)(void))
{
    if (command != NULL && function != NULL && numcommands < MAX_CONSOLE_COMMANDS)
    {
//      strcpy(commandlist[numcommands].command, command);
        commandlist[numcommands].command = command;
        commandlist[numcommands].function = function;

        // Increment counter to set up for next command insertion
        numcommands++;

        ASSERT(numcommands <= MAX_CONSOLE_COMMANDS);

        return TRUE;
    }

    return FALSE;
}

//
// Process commands
// Returns TRUE upon success
//
void CON_ProcessUserCommand(void)
{
    int16_t i=0;
    char temp_message[256],command_str[256];

    strcpy(temp_message,MessageInputString);
    sscanf(Bstrlwr(temp_message),"%s", command_str); // Get the base command type

    for (i = 0; i < numcommands; i++)
    {
        // Don't even try if they aren't the same length
        if (strlen(command_str) != strlen(commandlist[i].command)) continue;
        // See if it's in there
        if (CON_CommandCmp(command_str, commandlist[i].command, strlen(command_str)))
        {
            if (commandlist[i].function)
            {
                (*commandlist[i].function)();
                CON_AddHistory(MessageInputString); // Keep history only of valid input
                return;
            }
        }
    }

    if (ConPanel)
        CON_ConMessage("Syntax Error or Command not enabled!");
}

//
// Initialize the console command list with the pre_command startup array
//
void CON_InitConsole(void)
{
    CON_COMMANDp i;

    for (i = &pre_commands[0]; i->command != NULL; i++)
    {
        if (!CON_AddCommand(i->command, i->function))
        {
            printf("CON_InitConsole: Failed to add command contained in pre_commands list.\n");
            TerminateGame();
            exit(0);
        }
    }

    //printf("CON_InitConsole: Command list initialized.\n");
}

//
// Process as a command, anything that could be set in the options menu as well
//
void CON_ProcessOptions(void)
{

}

// Clear the console screen
void CON_ClearConsole(void)
{
    short i;

    for (i=0; i<MAXCONQUOTES; i++)
        strcpy(con_quote[i],"\0");
}

/////////////////////////////////////////////////////////////////////////////////////////////
// The user console programming function library ////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////

SWBOOL CheckValidSprite(short SpriteNum)
{
    if (SpriteNum < 0 || SpriteNum > 6144)
    {
        CON_ConMessage("ERROR: Sprite %d is out of range.",SpriteNum);
        return FALSE;
    }
    return TRUE;
}

// Get help on a console command
void CON_GetHelp(void)
{
    char base[80], command[80];

    if (sscanf(MessageInputString,"%s %s",base,command) < 2)
    {
        CON_ConMessage("Usage: help [keyword]");
        return;
    }

    Bstrlwr(command);    // Make sure operator is all lower case

    if (!strcmp(command, "xrepeat"))
    {
        CON_ConMessage("Usage: xrepeat [repeat value 0-255],");
        CON_ConMessage("   [User ID (-1 for all ID's)], [SpriteNum (-1 for all of type ID)]");
        return;
    }
    else if (!strcmp(command, "yrepeat"))
    {
        CON_ConMessage("Usage: yrepeat [repeat value 0-255],");
        CON_ConMessage("   [User ID (-1 for all ID's)], [SpriteNum (-1 for all of type ID)]");
        return;
    }
    else if (!strcmp(command, "translucent"))
    {
        CON_ConMessage("Usage: translucent [OFF/ON 0-1],");
        CON_ConMessage("   [User ID (-1 for all ID's)], [SpriteNum (-1 for all of type ID)]");
        return;
    }
    else
    {
        CON_ConMessage("No help was located on that subject.");
    }
}

// Modify sprites xrepeat value
void CON_ModXrepeat(void)
{
    char base[80];
    int16_t op1=64,op2=-1,op3=-1;
    short i;

    if (sscanf(MessageInputString,"%s %hd %hd %hd",base,&op1,&op2,&op3) < 4)
    {
        strcpy(MessageInputString,"help xrepeat");
        CON_GetHelp();
        return;
    }

    if (op3 == -1)
    {
        for (i=0; i<MAXSPRITES; i++)
        {
            SPRITEp sp = &sprite[i];
            USERp u = User[i];

            if (op2 == -1)
                sp->xrepeat = op1;
            else
            {
                if (u->ID == op2)
                    sp->xrepeat = op1;
            }
        }
        if (op2 == -1)
            CON_ConMessage("Xrepeat set to %d for all u->ID's for all sprites.",op1);
        else
            CON_ConMessage("Xrepeat set to %d for u->ID = %d for all sprites.",op1,op2);
    }
    else
    {
        // Do it only for one sprite
        SPRITEp sp = &sprite[op3];

        if (!CheckValidSprite(op3)) return;

        sp->xrepeat = op1;
        CON_ConMessage("Xrepeat set to %d for sprite %d.",op1,op3);
    }
}

// Modify sprites yrepeat value
void CON_ModYrepeat(void)
{
    char base[80];
    int16_t op1=64,op2=-1,op3=-1;
    short i;

    if (sscanf(MessageInputString,"%s %hd %hd %hd",base,&op1,&op2,&op3) < 4)
    {
        strcpy(MessageInputString,"help yrepeat");
        CON_GetHelp();
        return;
    }

    if (op3 == -1)
    {
        for (i=0; i<MAXSPRITES; i++)
        {
            SPRITEp sp = &sprite[i];
            USERp u = User[i];

            if (op2 == -1)
                sp->yrepeat = op1;
            else
            {
                if (u->ID == op2)
                    sp->yrepeat = op1;
            }
        }
        if (op2 == -1)
            CON_ConMessage("Yrepeat set to %d for all u->ID's for all sprites.",op1);
        else
            CON_ConMessage("Yrepeat set to %d for u->ID = %d for all sprites.",op1,op2);
    }
    else
    {
        // Do it only for one sprite
        SPRITEp sp = &sprite[op3];

        if (!CheckValidSprite(op3)) return;

        sp->yrepeat = op1;
        CON_ConMessage("Yrepeat set to %d for sprite %d.",op1,op3);
    }
}

void CON_ModTranslucent(void)
{
    char base[80];
    int16_t op1=0;
    SPRITEp sp;

    // Format: translucent [SpriteNum]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help translucent");
        CON_GetHelp();
        return;
    }

    if (!CheckValidSprite(op1)) return;

    sp = &sprite[op1];

    if (TEST(sp->cstat,CSTAT_SPRITE_TRANSLUCENT))
    {
        RESET(sp->cstat,CSTAT_SPRITE_TRANSLUCENT);
        CON_ConMessage("Translucence RESET for sprite %d.",op1);
    }
    else
    {
        SET(sp->cstat,CSTAT_SPRITE_TRANSLUCENT);
        CON_ConMessage("Translucence SET for sprite %d.",op1);
    }
}

void CON_SoundTest(void)
{
    int zero=0;
    char base[80];
    int16_t op1=0;

    // Format: sound [number]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help sound");
        CON_GetHelp();
        return;
    }

    if (op1 < 0 || op1 >= DIGI_MAX)
    {
        CON_ConMessage("Sound number out of range.");
        return;
    }

    PlaySound(op1,&zero,&zero,&zero,v3df_none);
}

void CON_Reverb(void)
{
    char base[80];
    int16_t op1=0;
    PLAYERp pp = Player + screenpeek;

    // Format: reverb [number]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help reverb");
        CON_GetHelp();
        return;
    }

    CON_ConMessage("Reverb is now set to %d.",op1);
    COVER_SetReverb(op1);
    pp->Reverb = op1;
}

void CON_Heap(void)
{
    /*
    int totalmemory=0;
    extern int TotalMemory, ActualHeap;
    int i;
    void *testheap;

    totalmemory = Z_AvailHeap();
    CON_ConMessage("Total heap at game startup = %d", TotalMemory);
    CON_ConMessage("ActualHeap reserved for non-cache use = %d", ActualHeap);
    CON_ConMessage("Total unallocated blocks in bytes minus reserved heap = %d", totalmemory);
    CON_ConMessage("NOTE: Allocation exceeding ActualHeap will result in out of memory");
    // Find remaining heap space unused
    i = ActualHeap;
    while(i>0)
    {
    testheap = AllocMem(i);
    if(!testheap)
        i-=1024L; // Decrease in 1k increments
    else
        {
        CON_ConMessage("Heap test result (+ or - 1k):");
        CON_ConMessage("=============================");
        CON_ConMessage("Unallocated heap space remaining  = %d",i);
        CON_ConMessage("Unallocated heap space used  = %d",ActualHeap - i);
        FreeMem(testheap);
        i=0; // Beam us out of here Scotty!
        }
    }

    if(ActualHeap < 50000L)
    {
    CON_ConMessage("ALERT: Memory is critically low!");
    }
    */
}

int TileRangeMem(int start)
{
    int i;
    int total=0;

    switch (start)
    {
    case 4096: // Evil Ninja
        for (i=4096; i<=4239; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 800:  // Hornet
        for (i=800; i<=811; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 817:
        for (i=817; i<=819; i++) // Bouncing Betty
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 820: // Skull
        for (i=820; i<=854; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 960:
        for (i=960; i<=1016; i++) // Serpent God
            total += tilesiz[i].x*tilesiz[i].y;
        for (i=1300; i<=1314; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 1024:
        for (i=1024; i<=1175; i++) // LoWang
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 1320:
        for (i=1320; i<=1396; i++) // Skeletor Priest
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 1400:
        for (i=1400; i<=1440; i++) // Coolie
            total += tilesiz[i].x*tilesiz[i].y;
        for (i=4260; i<=4266; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 1441:
        for (i=1441; i<=1450; i++) // Coolie Ghost
            total += tilesiz[i].x*tilesiz[i].y;
        for (i=4267; i<=4312; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 1469:
        for (i=1469; i<=1497; i++) // Guardian
            total += tilesiz[i].x*tilesiz[i].y;
        for (i=1504; i<=1518; i++)
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 1580:
        for (i=1580; i<=1644; i++) // Little Ripper
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 4320:
        for (i=4320; i<=4427; i++) // Big Ripper
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2540:
        for (i=2540; i<=2546; i++) // Trashcan
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 4430:
        for (i=4430; i<=4479; i++) // Fish
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 4490:
        for (i=4490; i<=4544; i++) // Sumo
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 5023:
        for (i=5023; i<=5026; i++) // Toilet Girl
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 5032:
        for (i=5032; i<=5035; i++) // Wash Girl
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2000:
        for (i=2000; i<=2002; i++) // Chop Stick Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2004:
        for (i=2004; i<=2009; i++) // Uzi Panel
            total += tilesiz[i].x*tilesiz[i].y;
        for (i=2040; i<=2043; i++) // Uzi Overlays
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2010:
        for (i=2010; i<=2019; i++) // Rail Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2130:
        for (i=2130; i<=2137; i++) // Shuriken Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2050:
        for (i=2050; i<=2053; i++) // Heart Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2054:
        for (i=2054; i<=2057; i++) // HotHead Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2070:
        for (i=2070; i<=2077; i++) // Rocket Launcher Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2080:
        for (i=2080; i<=2083; i++) // Sword Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 4090:
        for (i=4090; i<=4093; i++) // Bloody Sword Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2121:
        for (i=2121; i<=2126; i++) // 40MM Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2211:
        for (i=2211; i<=2216; i++) // Shotgun Panel
            total += tilesiz[i].x*tilesiz[i].y;
        for (i=2225; i<=2227; i++) // Shotgun Quad-Mode Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    case 2220:
        for (i=2220; i<=2224; i++) // Sticky Bomb Panel
            total += tilesiz[i].x*tilesiz[i].y;
        break;
    }

    return total;
}

void CON_Cache(void)
{
    char incache[MAXTILES]{};
    int i,tottiles,totsprites,totactors;

    // Calculate all level tiles, non-actor stuff
    for (i=0; i<numsectors; i++)
    {
        incache[sector[i].ceilingpicnum] = 1;
        incache[sector[i].floorpicnum] = 1;
    }

    for (i=0; i<numwalls; i++)
    {
        incache[wall[i].picnum] = 1;
        if (wall[i].overpicnum >= 0)
            incache[wall[i].overpicnum] = 1;
    }

    tottiles = 0;
    for (i=0; i<MAXTILES; i++)
        if (incache[i] > 0)
            tottiles += tilesiz[i].x*tilesiz[i].y;

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

    memset(incache, 0, sizeof(incache));

    // Sprites on the stat list get counted as cached, others don't
    for (i=0; i<MAXSPRITES; i++)
        if (sprite[i].statnum < MAXSTATUS)
            incache[sprite[i].picnum] = 1;

    totsprites = 0;
    totactors = 0;

    for (i=0; i<MAXTILES; i++)
    {
        if (incache[i] > 0)
        {
            switch (i)
            {
            case 4096:
                totactors+=TileRangeMem(4096);
                incache[4096]=0;
                break;
            case 800:
                totactors+=TileRangeMem(800);
                incache[800]=0;
                break;
            case 817:
                totactors+=TileRangeMem(817);
                incache[817]=0;
                break;
            case 820:
                totactors+=TileRangeMem(820);
                incache[820]=0;
                break;
            case 960:
                totactors+=TileRangeMem(960);
                incache[960]=0;
                break;
            //case 1024:   // Lo Wang is calculated later
            //     totactors+=TileRangeMem(1024);
            //     incache[1024]=0;
            //break;
            case 1320:
                totactors+=TileRangeMem(1320);
                incache[1320]=0;
                break;
            case 1400:
                totactors+=TileRangeMem(1400);
                incache[1400]=0;
                break;
            case 1441:
                totactors+=TileRangeMem(1441);
                incache[1441]=0;
                break;
            case 1469:
                totactors+=TileRangeMem(1469);
                incache[1469]=0;
                break;
            case 1580:
                totactors+=TileRangeMem(1580);
                incache[1580]=0;
                break;
            case 4320:
                totactors+=TileRangeMem(4320);
                incache[4320]=0;
                break;
            case 2540:
                totactors+=TileRangeMem(2540);
                incache[2540]=0;
                break;
            case 4430:
                totactors+=TileRangeMem(4430);
                incache[4430]=0;
                break;
            case 4490:
                totactors+=TileRangeMem(4490);
                incache[4490]=0;
                break;
            case 5023:
                totactors+=TileRangeMem(5023);
                incache[5023]=0;
                break;
            case 5032:
                totactors+=TileRangeMem(5032);
                incache[5032]=0;
                break;
            case 2000:
                totactors+=TileRangeMem(2000);
                incache[2000]=0;
                break;
            case 2004:
                totactors+=TileRangeMem(2004);
                incache[2004]=0;
                break;
            case 2010:
                totactors+=TileRangeMem(2010);
                incache[2010]=0;
                break;
            case 2130:
                totactors+=TileRangeMem(2130);
                incache[2130]=0;
                break;
            case 2050:
                totactors+=TileRangeMem(2050);
                incache[2050]=0;
                break;
            case 2054:
                totactors+=TileRangeMem(2054);
                incache[2054]=0;
                break;
            case 2070:
                totactors+=TileRangeMem(2070);
                incache[2070]=0;
                break;
            case 2080:
                totactors+=TileRangeMem(2080);
                incache[2080]=0;
                break;
            case 4090:
                totactors+=TileRangeMem(4090);
                incache[4090]=0;
                break;
            case 2121:
                totactors+=TileRangeMem(2121);
                incache[2121]=0;
                break;
            case 2211:
                totactors+=TileRangeMem(2211);
                incache[2211]=0;
                break;
            case 2220:
                totactors+=TileRangeMem(2220);
                incache[2220]=0;
                break;

            default: totsprites += tilesiz[i].x*tilesiz[i].y;
            }
        }
    }

    CON_ConMessage("/////////////////////////////////////////////");
    CON_ConMessage("Current Memory Consumption:");
    CON_ConMessage("Total Tiles        = %d",tottiles);
    CON_ConMessage("Total Sprites      = %d",totsprites);
    CON_ConMessage("Total Actors       = %d",totactors);
    CON_ConMessage("Total Memory       = %d",(tottiles+totsprites+totactors));
    CON_ConMessage("Total with LoWang  = %d",(tottiles+totsprites+totactors+TileRangeMem(1024)));
    CON_ConMessage("/////////////////////////////////////////////");

}

void CON_SpriteInfo(void)
{
    SpriteInfo++;
    if (SpriteInfo > 2) SpriteInfo = 0;

    if (SpriteInfo == 0)
        CON_ConMessage("Sprite information is OFF.");
    else if (SpriteInfo == 1)
        CON_ConMessage("Sprite information is ON (Brief Mode).");
    else
        CON_ConMessage("Sprite information is ON (Verbose Mode).");
}

void CON_KillSprite(void)
{
    char base[80];
    int16_t op1=0;
    short i;
    USERp u;

    // Format: kill [SpriteNum]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help kill");
        CON_GetHelp();
        return;
    }

    if (op1 == -1)
    {
        for (i=0; i<MAXSPRITES; i++)
        {
            u = User[i];
            if (!u->PlayerP)
                SetSuicide(i);
        }
        CON_ConMessage("Killed all sprites except Players.");
    }
    else
    {
        if (!CheckValidSprite(op1)) return;

        SetSuicide(op1);
        CON_ConMessage("Killed sprite %d.",op1);
    }

}

void CON_SpriteDetail(void)
{
    char base[80];
    int16_t op1=0;

    // Format: showsprite [SpriteNum]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help showsprite");
        CON_GetHelp();
        return;
    }

    if (!CheckValidSprite(op1)) return;
    auto const sp = (uspritetype const *)&sprite[op1];

    CON_ConMessage("x = %d, y = %d, z = %d",sp->x,sp->y,sp->z);
    CON_ConMessage("cstat = %d, picnum = %d",sp->cstat,sp->picnum);
    CON_ConMessage("shade = %d, pal = %d, clipdist = %d",sp->shade,sp->pal,sp->clipdist);
    CON_ConMessage("xrepeat = %d, yrepeat = %d",sp->xrepeat, sp->yrepeat);
    CON_ConMessage("xoffset = %d, yoffset = %d",sp->xoffset, sp->yoffset);
    CON_ConMessage("sectnum = %d, statnum = %d",sp->sectnum, sp->statnum);
    CON_ConMessage("ang = %d, owner = %d",sp->ang,sp->owner);
    CON_ConMessage("xvel = %d, yvel = %d, zvel = %d",sp->xvel,sp->yvel,sp->zvel);
    CON_ConMessage("lotag = %d, hitag = %d, extra = %d",sp->lotag,sp->hitag,sp->extra);
}

void CON_UserDetail(void)
{
    char base[80];
    int16_t op1=0;
    USERp u;

    // Format: showuser [SpriteNum]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help showsprite");
        CON_GetHelp();
        return;
    }

    if (!CheckValidSprite(op1)) return;
    u = User[op1];

    if (!u) return;

    CON_ConMessage("State = %p, Rot = %p",u->State,u->Rot);
    CON_ConMessage("StateStart = %p, StateEnd = %p",u->StateStart,u->StateEnd);
    CON_ConMessage("ActorActionFunc = %p",u->ActorActionFunc);
    CON_ConMessage("ActorActionSet = %p",u->ActorActionSet);
    CON_ConMessage("Personality = %p",u->Personality);
    CON_ConMessage("Attrib = %p",u->Attrib);
    CON_ConMessage("Flags = %d, Flags2 = %d, Tics = %d",u->Flags,u->Flags2,u->Tics);
    CON_ConMessage("RotNum = %d, ID = %d",u->RotNum,u->ID);
    CON_ConMessage("Health = %d, MaxHealth = %d",u->Health,u->MaxHealth);
    CON_ConMessage("LastDamage = %d, PainThreshold = %d",u->LastDamage,u->PainThreshold);
    CON_ConMessage("jump_speed = %d, jump_grav = %d",u->jump_speed,u->jump_grav);
    CON_ConMessage("xchange = %d, ychange = %d, zchange = %d",u->xchange,u->ychange,u->zchange);
    CON_ConMessage("ret = %d, WaitTics = %d, spal = %d",u->ret,u->WaitTics,u->spal);
}

void CON_Quit(void)
{
    if (CommPlayers >= 2)
        MultiPlayQuitFlag = TRUE;
    else
        QuitFlag = TRUE;
    OSD_ShowDisplay(0);
}

void CON_MultiNameChange(void)
{
    char base[16],command[16];

    // Format: swname [name]
    if (sscanf(MessageInputString,"%6s %12s",base,command) < 2)
        return;

    SendMulitNameChange(command);
}

void CON_LoadSetup(void)
{
    /*
    char base[80],command[80];

    // Format: showuser [SpriteNum]
    if (sscanf(MessageInputString,"%s %s",base,command) < 2)
    {
    strcpy(MessageInputString,"help config");
    CON_GetHelp();
    return;
    }

    if (!SafeFileExists(command))
    {
    CON_ConMessage("CON_LoadSetup: %s does not exist.",command);
    return;
    } else
    {
    strcpy(setupfilename,command);
    }
    initkeys();
    CONFIG_ReadSetup();
    CONTROL_Startup( ControllerType, &GetTime, 120 );
    SetupGameButtons();

    if (CONTROL_JoystickEnabled)
    {
    CONTROL_CenterJoystick(CenterCenter, UpperLeft, LowerRight, CenterThrottle,
      CenterRudder);
    }
    CON_ConMessage("Loaded new config file.");
    */
    CON_ConMessage("JonoF: Maybe later");
}

const char *damagename[] =
{
    "WPN_STAR","WPN_UZI",
    "WPN_SHOTGUN","WPN_MICRO",
    "WPN_GRENADE","WPN_MINE",
    "WPN_RAIL","WPN_HEART",
    "WPN_HOTHEAD","WPN_NAPALM"
    "WPN_RING","WPN_ROCKET",
    "WPN_SWORD","WPN_FIST",
    "DMG_NAPALM","DMG_MIRV_METEOR",
    "DMG_SERP_METEOR","DMG_ELECTRO_SHARD",
    "DMG_SECTOR_EXP","DMG_BOLT_EXP",
    "DMG_TANK_SHELL_EXP","DMG_FIREBALL_EXP",
    "DMG_NAPALM_EXP","DMG_SKULL_EXP",
    "DMG_BASIC_EXP","DMG_GRENADE_EXP",
    "DMG_MINE_EXP","DMG_MINE_SHRAP",
    "DMG_MICRO_EXP","DMG_NUCLEAR_EXP",
    "DMG_RADIATION_CLOUD","DMG_FLASHBOMB",
    "DMG_FIREBALL_FLAMES","DMG_RIPPER_SLASH",
    "DMG_SKEL_SLASH","DMG_COOLG_BASH",
    "DMG_COOLG_FIRE","DMG_GORO_CHOP",
    "DMG_GORO_FIREBALL","DMG_SERP_SLASH",
    "DMG_LAVA_BOULDER","DMG_LAVA_SHARD",
    "DMG_HORNET_STING","DMG_EEL_ELECTRO",
    "DMG_SPEAR_TRAP","DMG_VOMIT",
    "DMG_BLADE"
};

void CON_DamageData(void)
{

    char base[80],field[80];
    int16_t op1=0;
    unsigned int op2, i;

    // Format: damage [field] [item] [value]
    if (sscanf(MessageInputString,"%s %s %hd %u",base,field,&op1,&op2) < 3)
    {
        strcpy(MessageInputString,"help damage");
        CON_GetHelp();
        return;
    }

    if (op1 < -1 || op1 > 46)
    {
        CON_ConMessage("Damage Data index is out of range.");
        return;
    }

    if (!strcmp(field,"damage_lo"))
    {
        DamageData[op1].damage_lo = op2;
        CON_ConMessage("DamageData[%s].damage_lo = %d",damagename[op1],op2);
    }
    else if (!strcmp(field,"damage_hi"))
    {
        DamageData[op1].damage_hi = op2;
        CON_ConMessage("DamageData[%s].damage_hi = %d",damagename[op1],op2);
    }
    else if (!strcmp(field,"radius"))
    {
        DamageData[op1].radius = op2;
        CON_ConMessage("DamageData[%s].radius = %d",damagename[op1],op2);
    }
    else if (!strcmp(field,"max_ammo"))
    {
        DamageData[op1].max_ammo = op2;
        CON_ConMessage("DamageData[%s].max_ammo = %d",damagename[op1],op2);
    }
    else if (!strcmp(field,"min_ammo"))
    {
        DamageData[op1].min_ammo = op2;
        CON_ConMessage("DamageData[%s].min_ammo = %d",damagename[op1],op2);
    }
    if (!strcmp(field,"show"))
    {
        if (op1 == -1)
        {
            for (i=op2; i<=op2+10; i+=2)
            {
                if (i<47)
                    CON_ConMessage("[%d] = %s  [%d] = %s",i,damagename[i],i+1,damagename[i+1]);
            }
        }
        else
        {
            CON_ConMessage(" ");
            CON_ConMessage("Item = %s:",damagename[op1]);
            CON_ConMessage("damage_lo = %d, damag_hi = %d",DamageData[op1].damage_lo,DamageData[op1].damage_hi);
            CON_ConMessage("radius = %u",DamageData[op1].radius);
            CON_ConMessage("min_ammo = %d, max_ammo = %d",DamageData[op1].min_ammo,DamageData[op1].max_ammo);
            CON_ConMessage(" ");
        }
    }
}

void CON_WinPachinko(void)
{
    extern SWBOOL Pachinko_Win_Cheat;
    PLAYERp pp = Player + myconnectindex;
    extern void CheckSndData(char *file, int line);

    if (CommEnabled)
        return;

    Pachinko_Win_Cheat = !Pachinko_Win_Cheat;

    //CheckSndData( __FILE__, __LINE__ );

    if (Pachinko_Win_Cheat)
        PutStringInfo(pp,"Pachinko Win Cheat Enabled");
    else
        PutStringInfo(pp,"Pachinko Win Cheat Disabled");
}

void CON_Tweak(void)
{
    char base[80], command[80];
    int op1=0;

    // Format: tweak [weapon] [number]
    if (sscanf(MessageInputString,"%s %s %d",base,command,&op1) < 3)
    {
        strcpy(MessageInputString,"help tweak");
        CON_GetHelp();
        return;
    }

    Bstrlwr(command);    // Make sure operator is all lower case
    if (!strcmp(command,"adjust"))
    {
        extern short ADJUST;
        ADJUST = op1;
        CON_ConMessage("Zvelocity ADJUST set to %d.",op1);
    }
    else if (!strcmp(command,"adjustv"))
    {
        extern int ADJUSTV;
        ADJUSTV = op1;
        CON_ConMessage("Zvelocity ADJUSTV set to %d.",op1);
    }
}

void CON_Bunny(void)
{
    PLAYERp pp = Player + myconnectindex;

    if (CommEnabled)
        return;

    pp->BunnyMode = !pp->BunnyMode;
    if (pp->BunnyMode)
        PutStringInfo(pp,"Bunny rockets enabled!");
    else
        PutStringInfo(pp,"Bunny rockets disabled!");
}

void CON_CheckHeap(void)
{
    /*
    switch( _heapchk() )
    {
    case _HEAPOK:
      CON_ConMessage( "OK - heap is good\n" );
      break;
    case _HEAPEMPTY:
      CON_ConMessage( "OK - heap is empty\n" );
      break;
    case _HEAPBADBEGIN:
      CON_ConMessage( "ERROR - heap is damaged\n" );
      break;
    case _HEAPBADNODE:
      CON_ConMessage( "ERROR - bad node in heap\n" );
      break;
    }
    */
    CON_ConMessage("JonoF: Not now");
}

/*
void heap_dump( void )
  {
    struct _heapinfo h_info;
    int heap_status;

    h_info._pentry = NULL;
    for(;;) {
      heap_status = _heapwalk( &h_info );
      if( heap_status != _HEAPOK ) break;
      printf( "  %s block at %Fp of size %4.4X\n",
        (h_info._useflag == _USEDENTRY ? "USED" : "FREE"),
        h_info._pentry, h_info._size );

    }

    switch( heap_status ) {
    case _HEAPEND:
      printf( "OK - end of heap\n" );
      break;
    case _HEAPEMPTY:
      printf( "OK - heap is empty\n" );
      break;
    case _HEAPBADBEGIN:
      printf( "ERROR - heap is damaged\n" );
      break;
    case _HEAPBADPTR:
      printf( "ERROR - bad pointer to heap\n" );
      break;
    case _HEAPBADNODE:

      printf( "ERROR - bad node in heap\n" );
    }
  }
  */

void CON_DumpHeap(void)
{
    //heap_dump(); // Dump it.
    CON_ConMessage("JonoF: Not now");
}

void CON_ShowMirror(void)
{
    char base[80];
    int16_t op1=0;

    // Format: showmirror [SpriteNum]
    if (sscanf(MessageInputString,"%s %hd",base,&op1) < 2)
    {
        strcpy(MessageInputString,"help showmirror");
        CON_GetHelp();
        return;
    }

    if (op1 < 0 || op1 > 9)
    {
        CON_ConMessage("Mirror number is out of range!");
        return;
    }

    CON_ConMessage("camera is the ST1 sprite used as the view spot");
    CON_ConMessage("camspite is the SpriteNum of the drawtotile tile in editart");
    CON_ConMessage("camspic is the tile number of the drawtotile in editart");
    CON_ConMessage("iscamera is whether or not this mirror is a camera type");
    CON_ConMessage(" ");
    CON_ConMessage("mirror[%d].mirrorwall = %d",op1,mirror[op1].mirrorwall);
    CON_ConMessage("mirror[%d].mirrorsector = %d",op1,mirror[op1].mirrorsector);
    CON_ConMessage("mirror[%d].camera = %d",op1,mirror[op1].camera);
    CON_ConMessage("mirror[%d].camsprite = %d",op1,mirror[op1].camsprite);
    CON_ConMessage("mirror[%d].campic = %d",op1,mirror[op1].campic);
    CON_ConMessage("mirror[%d].iscamera = %d",op1,mirror[op1].ismagic);
}

void CON_DumpSoundList(void)
{
    extern void DumpSounds(void);

    DumpSounds();
    CON_Message("Sounds dumped to dbg.foo");

}


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 "game.h"
#include "tags.h"
#include "ai.h"
#include "sprite.h"
#include "actor.h"
#include "weapon.h"
#include "track.h"

ANIMATOR DoCoolgCircle,InitCoolgCircle;

DECISION CoolgBattle[] =
{
    {50,    InitCoolgCircle             },
    {450,   InitActorMoveCloser         },
    //{456,   InitActorAmbientNoise        },
    //{760,   InitActorRunAway            },
    {1024,  InitActorAttack             }
};

DECISION CoolgOffense[] =
{
    {449,   InitActorMoveCloser         },
    //{554,   InitActorAmbientNoise       },
    {1024,  InitActorAttack             }
};

DECISION CoolgBroadcast[] =
{
    //{1,    InitActorAlertNoise         },
    {1,    InitActorAmbientNoise       },
    {1024, InitActorDecide             }
};

DECISION CoolgSurprised[] =
{
    {100,   InitCoolgCircle            },
    {701,   InitActorMoveCloser        },
    {1024,  InitActorDecide            }
};

DECISION CoolgEvasive[] =
{
    {20,     InitCoolgCircle           },
    {1024,   InitActorRunAway          },
};

DECISION CoolgLostTarget[] =
{
    {900,   InitActorFindPlayer         },
    {1024,  InitActorWanderAround       }
};

DECISION CoolgCloseRange[] =
{
    {800,   InitActorAttack             },
    {1024,  InitActorReposition         }
};

DECISION CoolgTouchTarget[] =
{
    //{50,   InitCoolgCircle            },
    {1024,  InitActorAttack            },
};

PERSONALITY CoolgPersonality =
{
    CoolgBattle,
    CoolgOffense,
    CoolgBroadcast,
    CoolgSurprised,
    CoolgEvasive,
    CoolgLostTarget,
    CoolgCloseRange,
    CoolgTouchTarget
};

ATTRIBUTE CoolgAttrib =
{
    {60, 80, 150, 190},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                 // MaxWeapons;
    {
        DIGI_CGAMBIENT, DIGI_CGALERT, 0,
        DIGI_CGPAIN, DIGI_CGSCREAM, DIGI_CGMATERIALIZE,
        DIGI_CGTHIGHBONE,DIGI_CGMAGIC,DIGI_CGMAGICHIT,0
    }
};

//////////////////////
//
// COOLG RUN
//////////////////////

#define COOLG_RUN_RATE 40

ANIMATOR DoCoolgMove,NullAnimator,DoStayOnFloor, DoActorDebris, NullCoolg, DoCoolgBirth;

STATE s_CoolgRun[5][4] =
{
    {
        {COOLG_RUN_R0 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[0][1]},
        {COOLG_RUN_R0 + 1, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[0][2]},
        {COOLG_RUN_R0 + 2, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[0][3]},
        {COOLG_RUN_R0 + 3, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[0][0]},
    },
    {
        {COOLG_RUN_R1 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[1][1]},
        {COOLG_RUN_R1 + 1, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[1][2]},
        {COOLG_RUN_R1 + 2, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[1][3]},
        {COOLG_RUN_R1 + 3, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[1][0]},
    },
    {
        {COOLG_RUN_R2 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[2][1]},
        {COOLG_RUN_R2 + 1, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[2][2]},
        {COOLG_RUN_R2 + 2, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[2][3]},
        {COOLG_RUN_R2 + 3, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[2][0]},
    },
    {
        {COOLG_RUN_R3 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[3][1]},
        {COOLG_RUN_R3 + 1, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[3][2]},
        {COOLG_RUN_R3 + 2, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[3][3]},
        {COOLG_RUN_R3 + 3, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[3][0]},
    },
    {
        {COOLG_RUN_R4 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[4][1]},
        {COOLG_RUN_R4 + 1, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[4][2]},
        {COOLG_RUN_R4 + 2, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[4][3]},
        {COOLG_RUN_R4 + 3, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgRun[4][0]},
    }
};

STATEp sg_CoolgRun[] =
{
    &s_CoolgRun[0][0],
    &s_CoolgRun[1][0],
    &s_CoolgRun[2][0],
    &s_CoolgRun[3][0],
    &s_CoolgRun[4][0]
};

//////////////////////
//
// COOLG STAND
//
//////////////////////

STATE s_CoolgStand[5][1] =
{
    {
        {COOLG_RUN_R0 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgStand[0][0]},
    },
    {
        {COOLG_RUN_R1 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgStand[1][0]},
    },
    {
        {COOLG_RUN_R2 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgStand[2][0]},
    },
    {
        {COOLG_RUN_R3 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgStand[3][0]},
    },
    {
        {COOLG_RUN_R4 + 0, COOLG_RUN_RATE, DoCoolgMove, &s_CoolgStand[4][0]},
    }
};

STATEp sg_CoolgStand[] =
{
    &s_CoolgStand[0][0],
    &s_CoolgStand[1][0],
    &s_CoolgStand[2][0],
    &s_CoolgStand[3][0],
    &s_CoolgStand[4][0]
};

//////////////////////
//
// COOLG CLUB
//
//////////////////////

#define COOLG_RATE 16
ANIMATOR InitCoolgBash;

STATE s_CoolgClub[5][6] =
{
    {
        {COOLG_CLUB_R0 + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[0][1]},
        {COOLG_RUN_R0  + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[0][2]},
        {COOLG_CLUB_R0 + 1, 0|SF_QUICK_CALL, InitCoolgBash, &s_CoolgClub[0][3]},
        {COOLG_CLUB_R0 + 1, COOLG_RATE, NullCoolg, &s_CoolgClub[0][4]},
        {COOLG_CLUB_R0 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_CoolgClub[0][5]},
        {COOLG_CLUB_R0 + 1, COOLG_RATE, DoCoolgMove, &s_CoolgClub[0][5]}
    },
    {
        {COOLG_CLUB_R1 + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[1][1]},
        {COOLG_RUN_R1  + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[1][2]},
        {COOLG_CLUB_R1 + 1, 0|SF_QUICK_CALL, InitCoolgBash, &s_CoolgClub[1][3]},
        {COOLG_CLUB_R1 + 1, COOLG_RATE, NullCoolg, &s_CoolgClub[1][4]},
        {COOLG_CLUB_R1 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_CoolgClub[1][5]},
        {COOLG_CLUB_R1 + 1, COOLG_RATE, DoCoolgMove, &s_CoolgClub[1][5]}
    },
    {
        {COOLG_CLUB_R2 + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[2][1]},
        {COOLG_RUN_R2  + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[2][2]},
        {COOLG_CLUB_R2 + 1, 0|SF_QUICK_CALL, InitCoolgBash, &s_CoolgClub[2][3]},
        {COOLG_CLUB_R2 + 1, COOLG_RATE, NullCoolg, &s_CoolgClub[2][4]},
        {COOLG_CLUB_R2 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_CoolgClub[2][5]},
        {COOLG_CLUB_R2 + 1, COOLG_RATE, DoCoolgMove, &s_CoolgClub[2][5]}
    },
    {
        {COOLG_CLUB_R3 + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[3][1]},
        {COOLG_RUN_R3  + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[3][2]},
        {COOLG_CLUB_R3 + 1, 0|SF_QUICK_CALL, InitCoolgBash, &s_CoolgClub[3][3]},
        {COOLG_CLUB_R3 + 1, COOLG_RATE, NullCoolg, &s_CoolgClub[3][4]},
        {COOLG_CLUB_R3 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_CoolgClub[3][5]},
        {COOLG_CLUB_R3 + 1, COOLG_RATE, DoCoolgMove, &s_CoolgClub[3][5]}
    },
    {
        {COOLG_CLUB_R4 + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[4][1]},
        {COOLG_RUN_R4  + 0, COOLG_RATE, NullCoolg, &s_CoolgClub[4][2]},
        {COOLG_CLUB_R4 + 1, 0|SF_QUICK_CALL, InitCoolgBash, &s_CoolgClub[4][3]},
        {COOLG_CLUB_R4 + 1, COOLG_RATE, NullCoolg, &s_CoolgClub[4][4]},
        {COOLG_CLUB_R4 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_CoolgClub[4][5]},
        {COOLG_CLUB_R4 + 1, COOLG_RATE, DoCoolgMove, &s_CoolgClub[4][5]}
    }
};

STATEp sg_CoolgClub[] =
{
    &s_CoolgClub[0][0],
    &s_CoolgClub[1][0],
    &s_CoolgClub[2][0],
    &s_CoolgClub[3][0],
    &s_CoolgClub[4][0]
};

//////////////////////
//
// COOLG FIRE
//
//////////////////////

ANIMATOR InitCoolgFire;
#define COOLG_FIRE_RATE 12

STATE s_CoolgAttack[5][7] =
{
    {
        {COOLG_FIRE_R0 + 0, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[0][1]},
        {COOLG_FIRE_R0 + 1, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[0][2]},
        {COOLG_FIRE_R0 + 2, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[0][3]},
        {COOLG_FIRE_R0 + 2, 0|SF_QUICK_CALL,    InitCoolgFire,          &s_CoolgAttack[0][4]},
        {COOLG_FIRE_R0 + 2, COOLG_FIRE_RATE,    NullCoolg,              &s_CoolgAttack[0][5]},
        {COOLG_FIRE_R0 + 2, 0|SF_QUICK_CALL,    InitActorDecide,        &s_CoolgAttack[0][6]},
        {COOLG_RUN_R0  + 2, COOLG_FIRE_RATE,    DoCoolgMove,            &s_CoolgAttack[0][6]}
    },
    {
        {COOLG_FIRE_R1 + 0, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[1][1]},
        {COOLG_FIRE_R1 + 1, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[1][2]},
        {COOLG_FIRE_R1 + 2, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[1][3]},
        {COOLG_FIRE_R1 + 2, 0|SF_QUICK_CALL,    InitCoolgFire,          &s_CoolgAttack[1][4]},
        {COOLG_FIRE_R1 + 2, COOLG_FIRE_RATE,    NullCoolg,              &s_CoolgAttack[1][5]},
        {COOLG_FIRE_R1 + 2, 0|SF_QUICK_CALL,    InitActorDecide,        &s_CoolgAttack[1][6]},
        {COOLG_RUN_R0  + 2, COOLG_FIRE_RATE,    DoCoolgMove,            &s_CoolgAttack[1][6]}
    },
    {
        {COOLG_FIRE_R2 + 0, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[2][1]},
        {COOLG_FIRE_R2 + 1, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[2][2]},
        {COOLG_FIRE_R2 + 2, COOLG_FIRE_RATE*2,  NullCoolg,              &s_CoolgAttack[2][3]},
        {COOLG_FIRE_R2 + 2, 0|SF_QUICK_CALL,    InitCoolgFire,          &s_CoolgAttack[2][4]},
        {COOLG_FIRE_R2 + 2, COOLG_FIRE_RATE,    NullCoolg,              &s_CoolgAttack[2][5]},
        {COOLG_FIRE_R2 + 2, 0|SF_QUICK_CALL,    InitActorDecide,        &s_CoolgAttack[2][6]},
        {COOLG_RUN_R0  + 2, COOLG_FIRE_RATE,    DoCoolgMove,            &s_CoolgAttack[2][6]}
    },
    {
        {COOLG_RUN_R3 + 0, COOLG_FIRE_RATE*2,  NullCoolg,               &s_CoolgAttack[3][1]},
        {COOLG_RUN_R3 + 1, COOLG_FIRE_RATE*2,  NullCoolg,               &s_CoolgAttack[3][2]},
        {COOLG_RUN_R3 + 2, COOLG_FIRE_RATE*2,  NullCoolg,               &s_CoolgAttack[3][3]},
        {COOLG_RUN_R3 + 2, 0|SF_QUICK_CALL,    InitCoolgFire,           &s_CoolgAttack[3][4]},
        {COOLG_RUN_R3 + 2, COOLG_FIRE_RATE,    NullCoolg,               &s_CoolgAttack[3][5]},
        {COOLG_RUN_R3 + 2, 0|SF_QUICK_CALL,    InitActorDecide,         &s_CoolgAttack[3][6]},
        {COOLG_RUN_R0 + 2, COOLG_FIRE_RATE,    DoCoolgMove,            &s_CoolgAttack[3][6]}
    },
    {
        {COOLG_RUN_R4 + 0, COOLG_FIRE_RATE*2,  NullCoolg,               &s_CoolgAttack[4][1]},
        {COOLG_RUN_R4 + 1, COOLG_FIRE_RATE*2,  NullCoolg,               &s_CoolgAttack[4][2]},
        {COOLG_RUN_R4 + 2, COOLG_FIRE_RATE*2,  NullCoolg,               &s_CoolgAttack[4][3]},
        {COOLG_RUN_R4 + 2, 0|SF_QUICK_CALL,    InitCoolgFire,           &s_CoolgAttack[4][4]},
        {COOLG_RUN_R4 + 2, COOLG_FIRE_RATE,    NullCoolg,               &s_CoolgAttack[4][5]},
        {COOLG_RUN_R4 + 2, 0|SF_QUICK_CALL,    InitActorDecide,         &s_CoolgAttack[4][6]},
        {COOLG_RUN_R0 + 2, COOLG_FIRE_RATE,    DoCoolgMove,            &s_CoolgAttack[4][6]}
    }
};

STATEp sg_CoolgAttack[] =
{
    &s_CoolgAttack[0][0],
    &s_CoolgAttack[1][0],
    &s_CoolgAttack[2][0],
    &s_CoolgAttack[3][0],
    &s_CoolgAttack[4][0]
};

//////////////////////
//
// COOLG PAIN
//
//////////////////////

#define COOLG_PAIN_RATE 15
ANIMATOR DoCoolgPain;

STATE s_CoolgPain[5][2] =
{
    {
        {COOLG_PAIN_R0 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[0][1]},
        {COOLG_PAIN_R0 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[0][1]},
    },
    {
        {COOLG_RUN_R1 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[1][1]},
        {COOLG_RUN_R1 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[1][1]},
    },
    {
        {COOLG_RUN_R2 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[2][1]},
        {COOLG_RUN_R2 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[2][1]},
    },
    {
        {COOLG_RUN_R3 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[3][1]},
        {COOLG_RUN_R3 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[3][1]},
    },
    {
        {COOLG_RUN_R4 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[4][1]},
        {COOLG_RUN_R4 + 0, COOLG_PAIN_RATE, DoCoolgPain, &s_CoolgPain[4][1]},
    },
};

STATEp sg_CoolgPain[] =
{
    s_CoolgPain[0],
    s_CoolgPain[1],
    s_CoolgPain[2],
    s_CoolgPain[3],
    s_CoolgPain[4]
};

//////////////////////
//
// COOLG DIE
//
//////////////////////

#define COOLG_DIE_RATE 20

#define COOLG_DIE 4307
#define COOLG_DEAD 4307+5
ANIMATOR DoCoolgDeath;
STATE s_CoolgDie[] =
{
    {COOLG_DIE +    0, COOLG_DIE_RATE, DoCoolgDeath, &s_CoolgDie[1]},
    {COOLG_DIE +    1, COOLG_DIE_RATE, DoCoolgDeath, &s_CoolgDie[2]},
    {COOLG_DIE +    2, COOLG_DIE_RATE, DoCoolgDeath, &s_CoolgDie[3]},
    {COOLG_DIE +    3, COOLG_DIE_RATE, DoCoolgDeath, &s_CoolgDie[4]},
    {COOLG_DIE +    4, COOLG_DIE_RATE, DoCoolgDeath, &s_CoolgDie[5]},
    {COOLG_DIE +    5, COOLG_DIE_RATE, DoCoolgDeath, &s_CoolgDie[5]},
};

STATEp sg_CoolgDie[] =
{
    s_CoolgDie
};

STATE s_CoolgDead[] =
{
    {COOLG_DEAD, SF_QUICK_CALL, QueueFloorBlood, &s_CoolgDead[1]},
    {COOLG_DEAD, COOLG_DIE_RATE, DoActorDebris, &s_CoolgDead[1]},
};

STATEp sg_CoolgDead[] =
{
    s_CoolgDead
};

//////////////////////
//
// COOLG BIRTH
//
//////////////////////

#define COOLG_BIRTH_RATE 20
#define COOLG_BIRTH 4268

STATE s_CoolgBirth[] =
{
    {COOLG_BIRTH + 0, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[1]},
    {COOLG_BIRTH + 1, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[2]},
    {COOLG_BIRTH + 2, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[3]},
    {COOLG_BIRTH + 3, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[4]},
    {COOLG_BIRTH + 4, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[5]},
    {COOLG_BIRTH + 5, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[6]},
    {COOLG_BIRTH + 6, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[7]},
    {COOLG_BIRTH + 7, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[8]},
    {COOLG_BIRTH + 8, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[9]},
    {COOLG_BIRTH + 8, COOLG_BIRTH_RATE, NullAnimator, &s_CoolgBirth[10]},
    {COOLG_BIRTH + 8, 0|SF_QUICK_CALL, DoCoolgBirth, &s_CoolgBirth[10]}
};

STATEp sg_CoolgBirth[] =
{
    s_CoolgBirth
};

/*
STATEp *Stand[MAX_WEAPONS];
STATEp *Run;
STATEp *Jump;
STATEp *Fall;
STATEp *Crawl;
STATEp *Swim;
STATEp *Fly;
STATEp *Rise;
STATEp *Sit;
STATEp *Look;
STATEp *Climb;
STATEp *Pain;
STATEp *Death1;
STATEp *Death2;
STATEp *Dead;
STATEp *DeathJump;
STATEp *DeathFall;
STATEp *CloseAttack[2];
STATEp *Attack[6];
STATEp *Special[2];
*/

ACTOR_ACTION_SET CoolgActionSet =
{
    sg_CoolgStand,
    sg_CoolgRun,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL, //climb
    sg_CoolgPain, //pain
    sg_CoolgDie,
    NULL,
    sg_CoolgDead,
    NULL,
    NULL,
//  {sg_CoolgClub},
    {sg_CoolgAttack},
    {1024},
    {sg_CoolgAttack},
    {1024},
    {NULL,NULL},
    NULL,
    NULL
};

int DoCoolgMatchPlayerZ(short SpriteNum);

void
CoolgCommon(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];

    sp->clipdist = (200) >> 2;
    //u->floor_dist = Z(5);
    u->floor_dist = Z(16);
    u->ceiling_dist = Z(20);

    u->sz = sp->z;

    sp->xrepeat = 42;
    sp->yrepeat = 42;
    SET(sp->extra, SPRX_PLAYER_OR_ENEMY);
}

int
SetupCoolg(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u;
    ANIMATOR DoActorDecide;

    if (TEST(sp->cstat, CSTAT_SPRITE_RESTORE))
    {
        u = User[SpriteNum];
        ASSERT(u);
    }
    else
    {
        User[SpriteNum] = u = SpawnUser(SpriteNum,COOLG_RUN_R0,s_CoolgRun[0]);
        u->Health = HEALTH_COOLIE_GHOST;
    }

    ChangeState(SpriteNum, s_CoolgRun[0]);
    u->Attrib = &CoolgAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_CoolgDie;
    u->Rot = sg_CoolgRun;

    EnemyDefaults(SpriteNum, &CoolgActionSet, &CoolgPersonality);

    SET(u->Flags, SPR_NO_SCAREDZ|SPR_XFLIP_TOGGLE);

    CoolgCommon(SpriteNum);

    return 0;
}

extern short TotalKillable;

int
NewCoolg(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    USERp nu;
    SPRITEp np;
    ANIMATOR DoActorDecide;
    short New;

    New = SpawnSprite(STAT_ENEMY, COOLG_RUN_R0, &s_CoolgBirth[0], sp->sectnum, sp->x, sp->y, sp->z, sp->ang, 50);

    nu = User[New];
    np = &sprite[New];

    ChangeState(New, &s_CoolgBirth[0]);
    nu->StateEnd = s_CoolgDie;
    nu->Rot = sg_CoolgRun;
    np->pal = nu->spal = u->spal;

    nu->ActorActionSet = &CoolgActionSet;

    np->shade = sp->shade;
    nu->Personality = &CoolgPersonality;
    nu->Attrib = &CoolgAttrib;

    // special case
    TotalKillable++;
    CoolgCommon(New);

    return 0;
}

int
DoCoolgBirth(short New)
{
    USERp u;
    ANIMATOR DoActorDecide;

    u = User[New];

    u->Health = HEALTH_COOLIE_GHOST;
    u->Attrib = &CoolgAttrib;
    DoActorSetSpeed(New, NORM_SPEED);

    ChangeState(New, s_CoolgRun[0]);
    u->StateEnd = s_CoolgDie;
    u->Rot = sg_CoolgRun;

    EnemyDefaults(New, &CoolgActionSet, &CoolgPersonality);
    // special case
    TotalKillable--;

    SET(u->Flags, SPR_NO_SCAREDZ|SPR_XFLIP_TOGGLE);
    CoolgCommon(New);

    return 0;
}

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

    u->ShellNum -= ACTORMOVETICS;

    if (TEST(u->Flags,SPR_SLIDING))
        DoActorSlide(SpriteNum);

    DoCoolgMatchPlayerZ(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

int DoCoolgMatchPlayerZ(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    SPRITEp tsp = User[SpriteNum]->tgt_sp;
    int zdiff,zdist;
    int loz,hiz;

    int bound;

    // If blocking bits get unset, just die
    if (!TEST(sp->cstat,CSTAT_SPRITE_BLOCK) || !TEST(sp->cstat,CSTAT_SPRITE_BLOCK_HITSCAN))
    {
        InitBloodSpray(SpriteNum, TRUE, 105);
        InitBloodSpray(SpriteNum, TRUE, 105);
        UpdateSinglePlayKills(SpriteNum);
        SetSuicide(SpriteNum);
    }

    // actor does a sine wave about u->sz - this is the z mid point

    zdiff = (SPRITEp_MID(tsp)) - u->sz;

    // check z diff of the player and the sprite
    zdist = Z(20 + RANDOM_RANGE(100)); // put a random amount
    //zdist = Z(20);
    if (labs(zdiff) > zdist)
    {
        if (zdiff > 0)
            u->sz += 170 * ACTORMOVETICS;
        else
            u->sz -= 170 * ACTORMOVETICS;
    }

#define COOLG_BOB_AMT (Z(8))

    // save off lo and hi z
    loz = u->loz;
    hiz = u->hiz;

    // adjust loz/hiz for water depth
    if (u->lo_sectp && SectUser[u->lo_sectp - sector] && SectUser[u->lo_sectp - sector]->depth)
        loz -= Z(SectUser[u->lo_sectp - sector]->depth) - Z(8);

    // lower bound
    if (u->lo_sp)
        bound = loz - u->floor_dist;
    else
        bound = loz - u->floor_dist - COOLG_BOB_AMT;

    if (u->sz > bound)
    {
        u->sz = bound;
    }

    // upper bound
    if (u->hi_sp)
        bound = hiz + u->ceiling_dist;
    else
        bound = hiz + u->ceiling_dist + COOLG_BOB_AMT;

    if (u->sz < bound)
    {
        u->sz = bound;
    }

    u->sz = min(u->sz, loz - u->floor_dist);
    u->sz = max(u->sz, hiz + u->ceiling_dist);

    u->Counter = (u->Counter + (ACTORMOVETICS<<3)) & 2047;
    sp->z = u->sz + ((COOLG_BOB_AMT * (int)sintable[u->Counter]) >> 14);

    bound = u->hiz + u->ceiling_dist + COOLG_BOB_AMT;
    if (sp->z < bound)
    {
        // bumped something
        sp->z = u->sz = bound + COOLG_BOB_AMT;
    }

    return 0;
}

int InitCoolgCircle(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];

    u->ActorActionFunc = DoCoolgCircle;

    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    // set it close
    DoActorSetSpeed(SpriteNum, FAST_SPEED);

    // set to really fast
    sp->xvel = 400;
    // angle adjuster
    u->Counter2 = sp->xvel/3;
    // random angle direction
    if (RANDOM_P2(1024) < 512)
        u->Counter2 = -u->Counter2;

    // z velocity
    u->jump_speed = 400 + RANDOM_P2(256);
    if (labs(u->sz - u->hiz) < labs(u->sz - u->loz))
        u->jump_speed = -u->jump_speed;

    u->WaitTics = (RANDOM_RANGE(3)+1) * 120;

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

int DoCoolgCircle(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int nx,ny,bound;

    sp->ang = NORM_ANGLE(sp->ang + u->Counter2);

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

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

    // move in the z direction
    u->sz -= u->jump_speed * ACTORMOVETICS;

    bound = u->hiz + u->ceiling_dist + COOLG_BOB_AMT;
    if (u->sz < bound)
    {
        // bumped something
        u->sz = bound;
        InitActorReposition(SpriteNum);
        return 0;
    }

    // time out
    if ((u->WaitTics -= ACTORMOVETICS) < 0)
    {
        InitActorReposition(SpriteNum);
        u->WaitTics = 0;
        return 0;
    }

    return 0;
}

int
DoCoolgDeath(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int nx, ny;

    RESET(sp->cstat, CSTAT_SPRITE_TRANSLUCENT);
    RESET(sp->cstat, CSTAT_SPRITE_INVISIBLE);
    sp->xrepeat = 42;
    sp->shade = -10;

    if (TEST(u->Flags, SPR_FALLING))
    {
        DoFall(SpriteNum);
    }
    else
    {
        DoFindGroundPoint(SpriteNum);
        u->floor_dist = 0;
        DoBeginFall(SpriteNum);
    }

    if (TEST(u->Flags, SPR_SLIDING))
        DoActorSlide(SpriteNum);

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

    u->ret = move_sprite(SpriteNum, nx, ny, 0L, u->ceiling_dist, u->floor_dist, CLIPMASK_MISSILE, ACTORMOVETICS);
    DoFindGroundPoint(SpriteNum);

    // on the ground
    if (sp->z >= u->loz)
    {
        RESET(u->Flags, SPR_FALLING|SPR_SLIDING);
        RESET(sp->cstat, CSTAT_SPRITE_YFLIP); // If upside down, reset it
        NewStateGroup(SpriteNum, u->ActorActionSet->Dead);
        return 0;
    }

    return 0;
}

int DoCoolgMove(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];

    if ((u->ShellNum -= ACTORMOVETICS) <= 0)
    {
        switch (u->FlagOwner)
        {
        case 0:
            SET(sp->cstat, CSTAT_SPRITE_TRANSLUCENT);
            u->ShellNum = SEC(2);
            break;
        case 1:
            PlaySound(DIGI_VOID3, &sp->x, &sp->y, &sp->z, v3df_follow);
            RESET(sp->cstat, CSTAT_SPRITE_TRANSLUCENT);
            SET(sp->cstat, CSTAT_SPRITE_INVISIBLE);
            u->ShellNum = SEC(1) + SEC(RANDOM_RANGE(2));
            break;
        case 2:
            SET(sp->cstat, CSTAT_SPRITE_TRANSLUCENT);
            RESET(sp->cstat, CSTAT_SPRITE_INVISIBLE);
            u->ShellNum = SEC(2);
            break;
        case 3:
            PlaySound(DIGI_VOID3, &sp->x, &sp->y, &sp->z, v3df_follow);
            RESET(sp->cstat, CSTAT_SPRITE_TRANSLUCENT);
            RESET(sp->cstat, CSTAT_SPRITE_INVISIBLE);
            u->ShellNum = SEC(2) + SEC(RANDOM_RANGE(3));
            break;
        default:
            u->FlagOwner = 0;
            break;
        }
        u->FlagOwner++;
        if (u->FlagOwner > 3) u->FlagOwner = 0;
    }

    if (u->FlagOwner-1 == 0)
    {
        sp->xrepeat--;
        sp->shade++;
        if (sp->xrepeat < 4) sp->xrepeat = 4;
        if (sp->shade > 126)
        {
            sp->shade = 127;
            sp->hitag = 9998;
        }
    }
    else if (u->FlagOwner-1 == 2)
    {
        sp->hitag = 0;
        sp->xrepeat++;
        sp->shade--;
        if (sp->xrepeat > 42) sp->xrepeat = 42;
        if (sp->shade < -10) sp->shade = -10;
    }
    else if (u->FlagOwner == 0)
    {
        sp->xrepeat = 42;
        sp->shade = -10;
        sp->hitag = 0;
    }

    if (TEST(u->Flags,SPR_SLIDING))
        DoActorSlide(SpriteNum);

    if (u->track >= 0)
        ActorFollowTrack(SpriteNum, ACTORMOVETICS);
    else
    {
        (*u->ActorActionFunc)(SpriteNum);
    }

    if (RANDOM_P2(1024) < 32 && !TEST(sp->cstat, CSTAT_SPRITE_INVISIBLE))
        InitCoolgDrip(SpriteNum);

    DoCoolgMatchPlayerZ(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;

}

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

    NullCoolg(SpriteNum);

    if ((u->WaitTics -= ACTORMOVETICS) <= 0)
        InitActorDecide(SpriteNum);

    return 0;
}

#include "saveable.h"

static saveable_code saveable_coolg_code[] =
{
    SAVE_CODE(CoolgCommon),
    SAVE_CODE(SetupCoolg),
    SAVE_CODE(NewCoolg),
    SAVE_CODE(DoCoolgBirth),
    SAVE_CODE(NullCoolg),
    SAVE_CODE(DoCoolgMatchPlayerZ),
    SAVE_CODE(InitCoolgCircle),
    SAVE_CODE(DoCoolgCircle),
    SAVE_CODE(DoCoolgDeath),
    SAVE_CODE(DoCoolgMove),
    SAVE_CODE(DoCoolgPain),
};

static saveable_data saveable_coolg_data[] =
{
    SAVE_DATA(CoolgBattle),
    SAVE_DATA(CoolgOffense),
    SAVE_DATA(CoolgBroadcast),
    SAVE_DATA(CoolgSurprised),
    SAVE_DATA(CoolgEvasive),
    SAVE_DATA(CoolgLostTarget),
    SAVE_DATA(CoolgCloseRange),
    SAVE_DATA(CoolgTouchTarget),

    SAVE_DATA(CoolgPersonality),

    SAVE_DATA(CoolgAttrib),

    SAVE_DATA(s_CoolgRun),
    SAVE_DATA(sg_CoolgRun),
    SAVE_DATA(s_CoolgStand),
    SAVE_DATA(sg_CoolgStand),
    SAVE_DATA(s_CoolgClub),
    SAVE_DATA(sg_CoolgClub),
    SAVE_DATA(s_CoolgAttack),
    SAVE_DATA(sg_CoolgAttack),
    SAVE_DATA(s_CoolgPain),
    SAVE_DATA(sg_CoolgPain),
    SAVE_DATA(s_CoolgDie),
    SAVE_DATA(sg_CoolgDie),
    SAVE_DATA(s_CoolgDead),
    SAVE_DATA(sg_CoolgDead),
    SAVE_DATA(s_CoolgBirth),
    SAVE_DATA(sg_CoolgBirth),

    SAVE_DATA(CoolgActionSet),
};

saveable_module saveable_coolg =
{
    // code
    saveable_coolg_code,
    SIZ(saveable_coolg_code),

    // data
    saveable_coolg_data,
    SIZ(saveable_coolg_data)
};

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 "ai.h"
#include "sprite.h"
#include "actor.h"
#include "track.h"
#include "weapon.h"

ANIMATOR InitCoolieCharge;

DECISION CoolieBattle[] =
{
    {700,   InitCoolieCharge         },
    {990,   InitActorMoveCloser         },
    {1000,  InitActorAttackNoise        },
    {1024,  InitActorRunAway           }
};

DECISION CoolieOffense[] =
{
    {700,   InitCoolieCharge         },
    {1015,  InitActorMoveCloser         },
    {1024,  InitActorAttackNoise        }
};

DECISION CoolieBroadcast[] =
{
    //{1,     InitActorAlertNoise        },
    {16,    InitActorAmbientNoise          },
    {1024,  InitActorDecide            }
};

DECISION CoolieSurprised[] =
{
    {700,   InitActorMoveCloser         },
    {703,   InitActorAmbientNoise        },
    {1024,  InitActorDecide            }
};

DECISION CoolieEvasive[] =
{
    {10,   InitActorEvade  },
    {1024, NULL            }
};

DECISION CoolieLostTarget[] =
{
    {900,   InitActorFindPlayer         },
    {1024,  InitActorWanderAround       }
};

DECISION CoolieCloseRange[] =
{
    {400,   InitCoolieCharge         },
    {1024,  InitActorReposition            }
};

PERSONALITY CooliePersonality =
{
    CoolieBattle,
    CoolieOffense,
    CoolieBroadcast,
    CoolieSurprised,
    CoolieEvasive,
    CoolieLostTarget,
    CoolieCloseRange,
    CoolieCloseRange
};

ATTRIBUTE CoolieAttrib =
{
    {60, 80, 100, 200},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                 // MaxWeapons;
    {
        DIGI_COOLIEAMBIENT, DIGI_COOLIEALERT, DIGI_COOLIEALERT,
        DIGI_COOLIEPAIN, 0, DIGI_CGMATERIALIZE,
        DIGI_COOLIEEXPLODE,0,0,0
    }
};

//////////////////////
//
// COOLIE RUN
//
//////////////////////

#define COOLIE_RATE 12

ANIMATOR DoCoolieMove,NullCoolie,DoStayOnFloor,
         DoActorDebris, SpawnCoolieExp,
         SpawnCoolg;

STATE s_CoolieRun[5][4] =
{
    {
        {COOLIE_RUN_R0 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[0][1]},
        {COOLIE_RUN_R0 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[0][2]},
        {COOLIE_RUN_R0 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[0][3]},
        {COOLIE_RUN_R0 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[0][0]}
    },
    {
        {COOLIE_RUN_R1 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[1][1]},
        {COOLIE_RUN_R1 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[1][2]},
        {COOLIE_RUN_R1 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[1][3]},
        {COOLIE_RUN_R1 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[1][0]}
    },
    {
        {COOLIE_RUN_R2 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[2][1]},
        {COOLIE_RUN_R2 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[2][2]},
        {COOLIE_RUN_R2 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[2][3]},
        {COOLIE_RUN_R2 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[2][0]}
    },
    {
        {COOLIE_RUN_R3 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[3][1]},
        {COOLIE_RUN_R3 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[3][2]},
        {COOLIE_RUN_R3 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[3][3]},
        {COOLIE_RUN_R3 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[3][0]}
    },
    {
        {COOLIE_RUN_R4 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[4][1]},
        {COOLIE_RUN_R4 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[4][2]},
        {COOLIE_RUN_R4 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[4][3]},
        {COOLIE_RUN_R4 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieRun[4][0]},
    }
};

STATEp sg_CoolieRun[] =
{
    &s_CoolieRun[0][0],
    &s_CoolieRun[1][0],
    &s_CoolieRun[2][0],
    &s_CoolieRun[3][0],
    &s_CoolieRun[4][0]
};

//////////////////////
//
// COOLIE CHARGE
//
//////////////////////

#define COOLIE_RATE 12

ANIMATOR DoCoolieMove,NullCoolie,DoStayOnFloor,
         DoActorDebris, SpawnCoolieExp,
         SpawnCoolg;

STATE s_CoolieCharge[5][4] =
{
    {
        {COOLIE_CHARGE_R0 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[0][1]},
        {COOLIE_CHARGE_R0 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[0][2]},
        {COOLIE_CHARGE_R0 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[0][3]},
        {COOLIE_CHARGE_R0 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[0][0]}
    },
    {
        {COOLIE_CHARGE_R1 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[1][1]},
        {COOLIE_CHARGE_R1 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[1][2]},
        {COOLIE_CHARGE_R1 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[1][3]},
        {COOLIE_CHARGE_R1 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[1][0]}
    },
    {
        {COOLIE_CHARGE_R2 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[2][1]},
        {COOLIE_CHARGE_R2 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[2][2]},
        {COOLIE_CHARGE_R2 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[2][3]},
        {COOLIE_CHARGE_R2 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[2][0]}
    },
    {
        {COOLIE_CHARGE_R3 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[3][1]},
        {COOLIE_CHARGE_R3 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[3][2]},
        {COOLIE_CHARGE_R3 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[3][3]},
        {COOLIE_CHARGE_R3 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[3][0]}
    },
    {
        {COOLIE_CHARGE_R4 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[4][1]},
        {COOLIE_CHARGE_R4 + 1, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[4][2]},
        {COOLIE_CHARGE_R4 + 2, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[4][3]},
        {COOLIE_CHARGE_R4 + 3, COOLIE_RATE, DoCoolieMove, &s_CoolieCharge[4][0]},
    }
};

STATEp sg_CoolieCharge[] =
{
    &s_CoolieCharge[0][0],
    &s_CoolieCharge[1][0],
    &s_CoolieCharge[2][0],
    &s_CoolieCharge[3][0],
    &s_CoolieCharge[4][0]
};

//////////////////////
//
// COOLIE STAND
//
//////////////////////

STATE s_CoolieStand[5][6] =
{
    {
        {COOLIE_RUN_R0 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieStand[0][0]}
    },
    {
        {COOLIE_RUN_R1 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieStand[1][0]}
    },
    {
        {COOLIE_RUN_R2 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieStand[2][0]}
    },
    {
        {COOLIE_RUN_R3 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieStand[3][0]}
    },
    {
        {COOLIE_RUN_R4 + 0, COOLIE_RATE, DoCoolieMove, &s_CoolieStand[4][0]}
    }
};

STATEp sg_CoolieStand[] =
{
    &s_CoolieStand[0][0],
    &s_CoolieStand[1][0],
    &s_CoolieStand[2][0],
    &s_CoolieStand[3][0],
    &s_CoolieStand[4][0]
};

//////////////////////
//
// COOLIE PAIN
//
//////////////////////

#define COOLIE_PAIN_RATE 60
ANIMATOR CooliePain;

STATE s_CooliePain[5][1] =
{
    {
        {COOLIE_PAIN_R0 + 0, COOLIE_PAIN_RATE, CooliePain, &s_CooliePain[0][0]},
        //{COOLIE_PAIN_R0 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_CooliePain[0][0]}
    },
    {
        {COOLIE_PAIN_R1 + 0, COOLIE_PAIN_RATE, CooliePain, &s_CooliePain[1][0]},
        //{COOLIE_PAIN_R1 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_CooliePain[1][0]}
    },
    {
        {COOLIE_PAIN_R2 + 0, COOLIE_PAIN_RATE, CooliePain, &s_CooliePain[2][0]},
        //{COOLIE_PAIN_R2 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_CooliePain[2][0]}
    },
    {
        {COOLIE_PAIN_R3 + 0, COOLIE_PAIN_RATE, CooliePain, &s_CooliePain[3][0]},
        //{COOLIE_PAIN_R3 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_CooliePain[3][0]}
    },
    {
        {COOLIE_PAIN_R4 + 0, COOLIE_PAIN_RATE, CooliePain, &s_CooliePain[4][0]},
        //{COOLIE_PAIN_R4 + 0, 0|SF_QUICK_CALL, InitActorDecide, &s_CooliePain[4][0]}
    }
};

STATEp sg_CooliePain[] =
{
    &s_CooliePain[0][0],
    &s_CooliePain[1][0],
    &s_CooliePain[2][0],
    &s_CooliePain[3][0],
    &s_CooliePain[4][0]
};

//////////////////////
//
// COOLIE DIE
//
//////////////////////

#define COOLIE_DIE_RATE 30
ANIMATOR DoCoolieWaitBirth;

STATE s_CoolieDie[] =
{
    {COOLIE_DIE + 0, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[1]},

    {COOLIE_DIE + 0, 0|SF_QUICK_CALL, SpawnCoolieExp, &s_CoolieDie[2]},

    {COOLIE_DIE + 1, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[3]},
    {COOLIE_DIE + 2, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[4]},
    {COOLIE_DIE + 3, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[5]},
    {COOLIE_DIE + 4, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[6]},
    {COOLIE_DIE + 5, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[7]},
    {COOLIE_DIE + 6, COOLIE_DIE_RATE, NullCoolie, &s_CoolieDie[8]},
    {COOLIE_DIE + 7, COOLIE_DIE_RATE, DoCoolieWaitBirth, &s_CoolieDie[8]},

    {COOLIE_DIE + 7, COOLIE_DIE_RATE*5, DoActorDebris, &s_CoolieDie[10]},
    {COOLIE_DIE + 7, 0|SF_QUICK_CALL, SpawnCoolg, &s_CoolieDie[11]},
    {COOLIE_DEAD_NOHEAD, SF_QUICK_CALL, QueueFloorBlood, &s_CoolieDie[12]},
    {COOLIE_DEAD_NOHEAD, COOLIE_DIE_RATE, DoActorDebris, &s_CoolieDie[12]}
};

STATEp sg_CoolieDie[] =
{
    s_CoolieDie
};

STATE s_CoolieDead[] =
{
    {COOLIE_DEAD, COOLIE_DIE_RATE, DoActorDebris, &s_CoolieDead[0]},
};

STATEp sg_CoolieDead[] =
{
    s_CoolieDead
};

/*
typedef struct
{
#define MAX_ACTOR_CLOSE_ATTACK 2
#define MAX_ACTOR_ATTACK 6
STATEp *Stand;
STATEp *Run;
STATEp *Jump;
STATEp *Fall;
STATEp *Crawl;
STATEp *Swim;
STATEp *Fly;
STATEp *Rise;
STATEp *Sit;
STATEp *Look;
STATEp *Climb;
STATEp *Pain;
STATEp *Death1;
STATEp *Death2;
STATEp *Dead;
STATEp *DeathJump;
STATEp *DeathFall;

STATEp *CloseAttack[MAX_ACTOR_CLOSE_ATTACK];
short  CloseAttackPercent[MAX_ACTOR_CLOSE_ATTACK];

STATEp *Attack[MAX_ACTOR_ATTACK];
short  AttackPercent[MAX_ACTOR_ATTACK];

STATEp *Special[2];
STATEp *Duck;
STATEp *Dive;
}ACTOR_ACTION_SET,*ACTOR_ACTION_SETp;
*/

ACTOR_ACTION_SET CoolieActionSet =
{
    sg_CoolieStand,
    sg_CoolieRun,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL, //climb
    sg_CooliePain, //pain
    sg_CoolieDie,
    NULL,
    sg_CoolieDead,
    NULL,
    NULL,
    {sg_CoolieCharge},
    {1024},
    {sg_CoolieCharge},
    {1024},
    {NULL},
    NULL,
    NULL
};

void EnemyDefaults(short SpriteNum, ACTOR_ACTION_SETp action, PERSONALITYp person)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = &sprite[SpriteNum];
    unsigned int wpn;
    short wpn_cnt;
    short depth = 0;
    extern short TotalKillable;

    switch (u->ID)
    {
    case PACHINKO1:
    case PACHINKO2:
    case PACHINKO3:
    case PACHINKO4:
    case 623:
    case TOILETGIRL_R0:
    case WASHGIRL_R0:
    case CARGIRL_R0:
    case MECHANICGIRL_R0:
    case SAILORGIRL_R0:
    case PRUNEGIRL_R0:
    case TRASHCAN:
    case BUNNY_RUN_R0:
        break;
    default:
    {
        TotalKillable++;
#if DEBUG
        extern SWBOOL DebugSecret;
        if (DebugSecret)
        {
            sprintf(ds,"COUNTED: spnum %d, pic %d, x %d, y %d",SpriteNum,sp->picnum,sp->x,sp->y);
            DebugWriteString(ds);
        }
#endif
    }

    break;
    }

    RESET(sp->cstat, CSTAT_SPRITE_RESTORE);

    u->spal = sp->pal;

    u->RotNum = 5;
    sp->clipdist = (256) >> 2;

    u->zclip = Z(48);
    u->lo_step = Z(32);

    u->floor_dist = u->zclip - u->lo_step;
    u->ceiling_dist = SPRITEp_SIZE_Z(sp) - u->zclip;

    u->Radius = 400;

    u->MaxHealth = u->Health;

    u->PainThreshold = DIV16(u->Health) - 1;
    //u->PainThreshold = DIV4(u->Health) - 1;

    SET(sp->cstat,CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
    SET(sp->extra,SPRX_PLAYER_OR_ENEMY);

    sprite[SpriteNum].picnum = u->State->Pic;
    change_sprite_stat(SpriteNum, STAT_ENEMY);

    u->Personality = person;
    u->ActorActionSet = action;

    DoActorZrange(SpriteNum);

    //KeepActorOnFloor(SpriteNum); // for swimming actors

    // make sure we start in the water if thats where we are
    if (u->lo_sectp) // && SectUser[u->lo_sectp - sector])
    {
        short i,nexti;
        short sectnum = u->lo_sectp - sector;

        if (SectUser[sectnum] && TEST(u->lo_sectp->extra, SECTFX_SINK))
        {
            depth = SectUser[sectnum]->depth;
        }
        else
        {
            TRAVERSE_SPRITE_SECT(headspritesect[sectnum],i,nexti)
            {
                SPRITEp np = &sprite[i];
                if (np->picnum == ST1 && np->hitag == SECT_SINK)
                {
                    depth = np->lotag;
                }
            }
        }
    }

    if (depth && labs(sp->z - u->loz) < Z(8))
    {
        sp->z += Z(depth);
        u->loz = sp->z;
        u->oz = sp->z;
    }

    if (!action)
        return;

    NewStateGroup(SpriteNum, u->ActorActionSet->Run);

    u->ActorActionFunc = DoActorDecide;

    // find the number of long range attacks
    for (wpn = wpn_cnt = 0; wpn < SIZ(u->ActorActionSet->Attack); wpn++)
    {
        if (u->ActorActionSet->Attack[wpn])
            wpn_cnt++;
        else
            break;
    }

    // for actors this tells the number of weapons available
    // for player it tells the current weapon
    u->WeaponNum = wpn_cnt;
}

int
SetupCoolie(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u;
    ANIMATOR DoActorDecide;

    if (TEST(sp->cstat, CSTAT_SPRITE_RESTORE))
    {
        u = User[SpriteNum];
        ASSERT(u);
    }
    else
    {
        User[SpriteNum] = u = SpawnUser(SpriteNum,COOLIE_RUN_R0,s_CoolieRun[0]);
        u->Health = HEALTH_COOLIE;
    }

    ChangeState(SpriteNum,s_CoolieRun[0]);
    u->Attrib = &CoolieAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_CoolieDie;
    u->Rot = sg_CoolieRun;

    EnemyDefaults(SpriteNum, &CoolieActionSet, &CooliePersonality);

    sp->xrepeat = 42;
    sp->yrepeat = 42;

    SET(u->Flags, SPR_XFLIP_TOGGLE);

    return 0;
}

int SpawnCoolg(short SpriteNum)
{
    int NewCoolg(short);

    // Don't do a ghost every time
    if (RANDOM_RANGE(1000) > 700) return 0;

    NewCoolg(SpriteNum);

    PlaySpriteSound(SpriteNum,attr_extra1,v3df_follow);

    return 0;
}

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

    if (TEST(u->Flags,SPR_SLIDING))
        DoActorSlide(SpriteNum);

    if (!TEST(u->Flags,SPR_CLIMBING))
        KeepActorOnFloor(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    if ((u->WaitTics -= ACTORMOVETICS) <= 0)
        InitActorDecide(SpriteNum);

    return 0;
}

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

    if (TEST(u->Flags,SPR_SLIDING))
        DoActorSlide(SpriteNum);

    if (!TEST(u->Flags,SPR_CLIMBING))
        KeepActorOnFloor(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

int DoCoolieMove(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];

    if (TEST(u->Flags,SPR_SLIDING))
        DoActorSlide(SpriteNum);

    if (u->track >= 0)
        ActorFollowTrack(SpriteNum, ACTORMOVETICS);
    else
        (*u->ActorActionFunc)(SpriteNum);

    KeepActorOnFloor(SpriteNum);

    if (DoActorSectorDamage(SpriteNum))
    {
        return 0;
    }

    if (Distance(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y) < 1200)
    {
        //DoActorDie(SpriteNum, -3);
        UpdateSinglePlayKills(SpriteNum);
        DoActorDie(SpriteNum, SpriteNum);
        return 0;
    }

    return 0;
}

int InitCoolieCharge(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];

    if (RANDOM_P2(1024) > 950)
        PlaySound(DIGI_COOLIESCREAM, &sp->x, &sp->y, &sp->z, v3df_follow);

    DoActorSetSpeed(SpriteNum, FAST_SPEED);

    InitActorMoveCloser(SpriteNum);

    NewStateGroup(SpriteNum, sg_CoolieCharge);

    return 0;
}

int
DoCoolieWaitBirth(short SpriteNum)
{
    USERp u;

    u = User[SpriteNum];

    if ((u->Counter -= ACTORMOVETICS) <= 0)
    {
        ChangeState(SpriteNum,&s_CoolieDie[9]);
    }

    return 0;
}

#include "saveable.h"

static saveable_code saveable_coolie_code[] =
{
    SAVE_CODE(EnemyDefaults),
    SAVE_CODE(SetupCoolie),
    SAVE_CODE(SpawnCoolg),
    SAVE_CODE(CooliePain),
    SAVE_CODE(NullCoolie),
    SAVE_CODE(DoCoolieMove),
    SAVE_CODE(InitCoolieCharge),
    SAVE_CODE(DoCoolieWaitBirth),
};

static saveable_data saveable_coolie_data[] =
{
    SAVE_DATA(CoolieBattle),
    SAVE_DATA(CoolieOffense),
    SAVE_DATA(CoolieBroadcast),
    SAVE_DATA(CoolieSurprised),
    SAVE_DATA(CoolieEvasive),
    SAVE_DATA(CoolieLostTarget),
    SAVE_DATA(CoolieCloseRange),

    SAVE_DATA(CooliePersonality),

    SAVE_DATA(CoolieAttrib),

    SAVE_DATA(s_CoolieRun),
    SAVE_DATA(sg_CoolieRun),
    SAVE_DATA(s_CoolieCharge),
    SAVE_DATA(sg_CoolieCharge),
    SAVE_DATA(s_CoolieStand),
    SAVE_DATA(sg_CoolieStand),
    SAVE_DATA(s_CooliePain),
    SAVE_DATA(sg_CooliePain),
    SAVE_DATA(s_CoolieDie),
    SAVE_DATA(sg_CoolieDie),
    SAVE_DATA(s_CoolieDead),
    SAVE_DATA(sg_CoolieDead),

    SAVE_DATA(CoolieActionSet),
};

saveable_module saveable_coolie =
{
    // code
    saveable_coolie_code,
    SIZ(saveable_coolie_code),

    // data
    saveable_coolie_data,
    SIZ(saveable_coolie_data)
};


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#include "names2.h"
#include "game.h"
#include "tags.h"
#include "weapon.h"
#include "sprite.h"
#include "track.h"
#include "interpso.h"

void CopySectorWalls(short dest_sectnum, short src_sectnum)
{
    short dest_wall_num, src_wall_num, start_wall;
    SECTOR_OBJECTp sop;
    SECTORp *sectp;

    dest_wall_num = sector[dest_sectnum].wallptr;
    src_wall_num = sector[src_sectnum].wallptr;

    start_wall = dest_wall_num;

    do
    {
        wall[dest_wall_num].picnum = wall[src_wall_num].picnum;

        wall[dest_wall_num].xrepeat =       wall[src_wall_num].xrepeat;
        wall[dest_wall_num].yrepeat =       wall[src_wall_num].yrepeat;
        wall[dest_wall_num].overpicnum =    wall[src_wall_num].overpicnum;
        wall[dest_wall_num].pal =           wall[src_wall_num].pal;
        wall[dest_wall_num].cstat =         wall[src_wall_num].cstat;
        wall[dest_wall_num].shade =         wall[src_wall_num].shade;
        wall[dest_wall_num].xpanning =      wall[src_wall_num].xpanning;
        wall[dest_wall_num].ypanning =      wall[src_wall_num].ypanning;
        wall[dest_wall_num].hitag =         wall[src_wall_num].hitag;
        wall[dest_wall_num].lotag =         wall[src_wall_num].lotag;
        wall[dest_wall_num].extra =         wall[src_wall_num].extra;

        uint16_t const dest_nextwall = wall[dest_wall_num].nextwall;
        uint16_t const src_nextwall = wall[src_wall_num].nextwall;

        if (dest_nextwall < MAXWALLS && src_nextwall < MAXWALLS)
        {
            wall[dest_nextwall].picnum = wall[src_nextwall].picnum;
            wall[dest_nextwall].xrepeat = wall[src_nextwall].xrepeat;
            wall[dest_nextwall].yrepeat = wall[src_nextwall].yrepeat;
            wall[dest_nextwall].overpicnum = wall[src_nextwall].overpicnum;
            wall[dest_nextwall].pal = wall[src_nextwall].pal;
            wall[dest_nextwall].cstat = wall[src_nextwall].cstat;
            wall[dest_nextwall].shade = wall[src_nextwall].shade;
            wall[dest_nextwall].xpanning = wall[src_nextwall].xpanning;
            wall[dest_nextwall].ypanning = wall[src_nextwall].ypanning;
            wall[dest_nextwall].hitag = wall[src_nextwall].hitag;
            wall[dest_nextwall].lotag = wall[src_nextwall].lotag;
            wall[dest_nextwall].extra = wall[src_nextwall].extra;
        }

        dest_wall_num = wall[dest_wall_num].point2;
        src_wall_num = wall[src_wall_num].point2;
    }
    while (dest_wall_num != start_wall);

    // TODO: Mapping a sector to the sector object to which it belongs is better
    for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++)
    {
        if (SO_EMPTY(sop))
            continue;

        for (sectp = sop->sectp; *sectp; sectp++)
            if (*sectp - sector == dest_sectnum)
            {
                so_setinterpolationtics(sop, 0);
                break;
            }
    }
}

void CopySectorMatch(short match)
{
    short ed,nexted,ss,nextss;
    SPRITEp dest_sp, src_sp;
    SECTORp dsectp,ssectp;
    short kill, nextkill;
    SPRITEp k;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_COPY_DEST], ed, nexted)
    {
        dest_sp = &sprite[ed];
        dsectp = &sector[dest_sp->sectnum];

        if (match != sprite[ed].lotag)
            continue;

        TRAVERSE_SPRITE_STAT(headspritestat[STAT_COPY_SOURCE], ss, nextss)
        {
            src_sp = &sprite[ss];

            if (SP_TAG2(src_sp) == SPRITE_TAG2(ed) &&
                SP_TAG3(src_sp) == SPRITE_TAG3(ed))
            {
                short src_move, nextsrc_move;
                ssectp = &sector[src_sp->sectnum];

                // !!!!!AAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHHHHH
                // Don't kill anything you don't have to
                // this wall killing things on a Queue causing
                // invalid situations

#if 1
                // kill all sprites in the dest sector that need to be
                TRAVERSE_SPRITE_SECT(headspritesect[dest_sp->sectnum], kill, nextkill)
                {
                    k = &sprite[kill];

                    // kill anything not invisible
                    if (!TEST(k->cstat, CSTAT_SPRITE_INVISIBLE))
                    {
                        if (User[kill])
                        {
                            // be safe with the killing
                            //SetSuicide(kill);
                        }
                        else
                        {
                            SpriteQueueDelete(kill); // new function to allow killing - hopefully
                            KillSprite(kill);
                        }
                    }
                }
#endif

                CopySectorWalls(dest_sp->sectnum, src_sp->sectnum);

                TRAVERSE_SPRITE_SECT(headspritesect[src_sp->sectnum], src_move, nextsrc_move)
                {
                    // don't move ST1 Copy Tags
                    if (SPRITE_TAG1(src_move) != SECT_COPY_SOURCE)
                    {
                        int sx,sy,dx,dy,src_xoff,src_yoff,trash;

                        // move sprites from source to dest - use center offset

                        // get center of src and dest sect
                        SectorMidPoint(src_sp->sectnum, &sx, &sy, &trash);
                        SectorMidPoint(dest_sp->sectnum, &dx, &dy, &trash);

                        // get offset
                        src_xoff = sx - sprite[src_move].x;
                        src_yoff = sy - sprite[src_move].y;

                        // move sprite to dest sector
                        sprite[src_move].x = dx - src_xoff;
                        sprite[src_move].y = dy - src_yoff;

                        // change sector
                        changespritesect(src_move, dest_sp->sectnum);

                        // check to see if it moved on to a sector object
                        if (TEST(sector[dest_sp->sectnum].extra, SECTFX_SECTOR_OBJECT))
                        {
                            SECTOR_OBJECTp sop;
                            extern int GlobSpeedSO;

                            // find and add sprite to SO
                            sop = DetectSectorObject(&sector[sprite[src_move].sectnum]);
                            AddSpriteToSectorObject(src_move, sop);

                            // update sprites postions so they aren't in the
                            // wrong place for one frame
                            GlobSpeedSO = 0;
                            RefreshPoints(sop, 0, 0, TRUE);
                        }
                    }
                }

                // copy sector user if there is one
                if (SectUser[src_sp->sectnum] || SectUser[dest_sp->sectnum])
                {
                    SECT_USERp ssectu = GetSectUser(src_sp->sectnum);
                    SECT_USERp dsectu = GetSectUser(dest_sp->sectnum);

                    memcpy(dsectu, ssectu, sizeof(SECT_USER));
                }

                dsectp->hitag = ssectp->hitag;
                dsectp->lotag = ssectp->lotag;

                dsectp->floorz = ssectp->floorz;
                dsectp->ceilingz = ssectp->ceilingz;

                dsectp->floorshade = ssectp->floorshade;
                dsectp->ceilingshade = ssectp->ceilingshade;

                dsectp->floorpicnum = ssectp->floorpicnum;
                dsectp->ceilingpicnum = ssectp->ceilingpicnum;

                dsectp->floorheinum = ssectp->floorheinum;
                dsectp->ceilingheinum = ssectp->ceilingheinum;

                dsectp->floorpal = ssectp->floorpal;
                dsectp->ceilingpal = ssectp->ceilingpal;

                dsectp->floorxpanning = ssectp->floorxpanning;
                dsectp->ceilingxpanning = ssectp->ceilingxpanning;

                dsectp->floorypanning = ssectp->floorypanning;
                dsectp->ceilingypanning = ssectp->ceilingypanning;

                dsectp->floorstat = ssectp->floorstat;
                dsectp->ceilingstat = ssectp->ceilingstat;

                dsectp->extra = ssectp->extra;
                dsectp->visibility = ssectp->visibility;
            }
        }
    }

    // do this outside of processing loop for safety

    // kill all matching dest
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_COPY_DEST], ed, nexted)
    {
        if (match == sprite[ed].lotag)
            KillSprite(ed);
    }

    // kill all matching sources
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_COPY_SOURCE], ss, nextss)
    {
        if (match == sprite[ss].lotag)
            KillSprite(ss);
    }

}


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#ifdef DAMAGE_TABLE
#define DAMAGE_ENTRY(id, init_func, damage_lo, damage_hi, radius, max_ammo, min_ammo, with_weapon) \
    { init_func, damage_lo, damage_hi, radius, max_ammo, min_ammo, with_weapon, NULL, NULL, -1, -1 },
#define DAMAGE_ENTRY_WPN(id, init_func, damage_lo, damage_hi, radius, max_ammo, min_ammo, with_weapon, weapon_name, ammo_name, weapon_pickup, ammo_pickup ) \
    { init_func, damage_lo, damage_hi, radius, max_ammo, min_ammo, with_weapon, weapon_name, ammo_name, weapon_pickup, ammo_pickup },
#endif

#ifdef DAMAGE_ENUM
#define DAMAGE_ENTRY(id, init_func, damage_lo, damage_hi, radius, max_ammo, min_ammo, with_weapon) \
    id,
#define DAMAGE_ENTRY_WPN(id, init_func, damage_lo, damage_hi, radius, max_ammo, min_ammo, with_weapon, weapon_name, ammo_name, weapon_pickup, ammo_pickup ) \
    id,
#endif

// DAMAGES ////////////////////////////////////////////////////////////////////

// weapon
DAMAGE_ENTRY(WPN_FIST,            InitWeaponFist,       10,    40,   0,  -1, -1, -1)
DAMAGE_ENTRY_WPN(WPN_STAR,        InitWeaponStar,        5,    10,   0,  99,  3, -1, "Shurikens", NULL, 9, -1)
DAMAGE_ENTRY_WPN(WPN_SHOTGUN,     InitWeaponShotgun,     4,     4,   0,  52,  1, -1, "Riot Gun", "Shotshells", 8, 24)
DAMAGE_ENTRY_WPN(WPN_UZI,         InitWeaponUzi,         5,     7,   0, 200,  1, -1, "UZI Submachine Gun", "UZI Clip", 50, 50)
DAMAGE_ENTRY_WPN(WPN_MICRO,       InitWeaponMicro,      15,    30,   0,  50,  1, -1, "Missile Launcher", "Missiles", 5, 5)
DAMAGE_ENTRY_WPN(WPN_GRENADE,     InitWeaponGrenade,    15,    30,   0,  50,  1, -1, "Grenade Launcher", "Grenade Shells", 6, 8)
DAMAGE_ENTRY_WPN(WPN_MINE,        InitWeaponMine,        5,    10,   0,  20,  1, -1, "Sticky Bombs", NULL, 5, -1)
DAMAGE_ENTRY_WPN(WPN_RAIL,        InitWeaponRail,       40,    60,   0,  20,  1, -1, "Rail Gun", "Rail Gun Rods", 10, 10)
DAMAGE_ENTRY_WPN(WPN_HOTHEAD,     InitWeaponHothead,    10,    25,   0,  80,  1, -1, "Guardian Head", "Firebursts", 30, 60)
DAMAGE_ENTRY_WPN(WPN_HEART,       InitWeaponHeart,      75,   100,   0,   5,  1, -1, "Ripper Heart", "Deathcoils", 1, 6)

DAMAGE_ENTRY(WPN_NAPALM,          InitWeaponHothead,    50,   100,   0, 100,  40, WPN_HOTHEAD)
DAMAGE_ENTRY(WPN_RING,            InitWeaponHothead,    15,    50,   0, 100,  20, WPN_HOTHEAD)
DAMAGE_ENTRY(WPN_ROCKET,          InitWeaponMicro,      30,    60,   0, 100,   1, WPN_MICRO)
DAMAGE_ENTRY(WPN_SWORD,           InitWeaponSword,      50,    80,   0,  -1,  -1, -1)

// extra weapons connected to other

// spell
DAMAGE_ENTRY(DMG_NAPALM,          NULL,                90,   150, 0, -1, -1, -1)
DAMAGE_ENTRY(DMG_MIRV_METEOR,     NULL,                35,    65, 0, -1, -1, -1)
DAMAGE_ENTRY(DMG_SERP_METEOR,     NULL,                 7,    15, 0, -1, -1, -1)

// radius damage
DAMAGE_ENTRY(DMG_ELECTRO_SHARD,   NULL,                 2,     6,      0, -1, -1, -1)
DAMAGE_ENTRY(DMG_SECTOR_EXP,      NULL,                50,   100,   3200, -1, -1, -1)
DAMAGE_ENTRY(DMG_BOLT_EXP,        NULL,                80,   160,   3200, -1, -1, -1)
DAMAGE_ENTRY(DMG_TANK_SHELL_EXP,  NULL,                80,   200,   4500, -1, -1, -1)
DAMAGE_ENTRY(DMG_FIREBALL_EXP,    NULL,                -1,    -1,   1000, -1, -1, -1)
DAMAGE_ENTRY(DMG_NAPALM_EXP,      NULL,                60,    90,   3200, -1, -1, -1)
DAMAGE_ENTRY(DMG_SKULL_EXP,       NULL,                40,    75,   4500, -1, -1, -1)
DAMAGE_ENTRY(DMG_BASIC_EXP,       NULL,                10,    25,   1000, -1, -1, -1)
DAMAGE_ENTRY(DMG_GRENADE_EXP,     NULL,                70,   140,   6500, -1, -1, -1)
DAMAGE_ENTRY(DMG_MINE_EXP,        NULL,                85,   115,   6500, -1, -1, -1)
DAMAGE_ENTRY(DMG_MINE_SHRAP,      NULL,                15,    30,      0, -1, -1, -1)
DAMAGE_ENTRY(DMG_MICRO_EXP,       NULL,                50,   100,   4500, -1, -1, -1)
DAMAGE_ENTRY_WPN(DMG_NUCLEAR_EXP, NULL,                 0,   800,  30000, -1, -1, -1, "Nuclear Warhead", "Heat Seeker Card", 1, 5)
DAMAGE_ENTRY(DMG_RADIATION_CLOUD, NULL,                 2,     6,   5000, -1, -1, -1)
DAMAGE_ENTRY(DMG_FLASHBOMB,       NULL,               100,   150,  16384, -1, -1, -1)

DAMAGE_ENTRY(DMG_FIREBALL_FLAMES, NULL,                 2,     6,    300, -1, -1, -1)

// actor
DAMAGE_ENTRY(DMG_RIPPER_SLASH,    NULL,                10,    30,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_SKEL_SLASH,      NULL,                10,    20,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_COOLG_BASH,      NULL,                10,    20,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_COOLG_FIRE,      NULL,                15,    30,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_GORO_CHOP,       NULL,                20,    40,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_GORO_FIREBALL,   NULL,                5,     20,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_SERP_SLASH,      NULL,                75,    75,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_LAVA_BOULDER,    NULL,                100,  100,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_LAVA_SHARD,      NULL,                25,    25,   0, -1, -1, -1)
DAMAGE_ENTRY(DMG_HORNET_STING,    NULL,                5,     10,    0, -1, -1, -1)
DAMAGE_ENTRY(DMG_EEL_ELECTRO,     NULL,                10,    40, 3400, -1, -1, -1)

// misc
DAMAGE_ENTRY(DMG_SPEAR_TRAP,      NULL,                15,   20,    0, -1, -1, -1)
DAMAGE_ENTRY(DMG_VOMIT,           NULL,                 5,    15,   0, -1, -1, -1)

// inanimate objects
DAMAGE_ENTRY(DMG_BLADE,           NULL,                10,    20,   0, -1, -1, -1)
DAMAGE_ENTRY(MAX_WEAPONS,         NULL,                10,    20,   0, -1, -1, -1)

#undef DAMAGE_ENTRY
#undef DAMAGE_ENTRY_WPN