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 "actor.h"
#include "track.h"
#include "sector.h"

extern uint8_t RedBookSong[40];
extern short BossSpriteNum[3];

DECISION SerpBattle[] =
{
    {670,   InitActorMoveCloser         },
    {700,   InitActorAmbientNoise         },
    {710,   InitActorRunAway            },
    {1024,  InitActorAttack             }
};

DECISION SerpOffense[] =
{
    {775,   InitActorMoveCloser         },
    {800,   InitActorAmbientNoise         },
    {1024,  InitActorAttack             }
};

DECISION SerpBroadcast[] =
{
    //{21,    InitActorAlertNoise         },
    {10,    InitActorAmbientNoise       },
    {1024,  InitActorDecide             }
};

DECISION SerpSurprised[] =
{
    {701,   InitActorMoveCloser        },
    {1024,  InitActorDecide            }
};

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

DECISION SerpLostTarget[] =
{
    {900,   InitActorFindPlayer         },
    {921,   InitActorAmbientNoise       },
    {1024,  InitActorWanderAround       }
};

DECISION SerpCloseRange[] =
{
    {700,   InitActorAttack             },
    {1024,  InitActorReposition         }
};

PERSONALITY SerpPersonality =
{
    SerpBattle,
    SerpOffense,
    SerpBroadcast,
    SerpSurprised,
    SerpEvasive,
    SerpLostTarget,
    SerpCloseRange,
    SerpCloseRange
};

ATTRIBUTE SerpAttrib =
{
    {200, 220, 240, 270},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                  // MaxWeapons;
    {
        DIGI_SERPAMBIENT, DIGI_SERPALERT, DIGI_SERPSWORDATTACK,
        DIGI_SERPPAIN, DIGI_SERPSCREAM, DIGI_SERPMAGICLAUNCH,
        DIGI_SERPSUMMONHEADS,DIGI_SERPTAUNTYOU,DIGI_SERPDEATHEXPLODE,0
    }
};

ATTRIBUTE SerpPissedAttrib =
{
    {200, 220, 240, 270},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                  // MaxWeapons;
    {
        DIGI_SERPAMBIENT, DIGI_SERPALERT, DIGI_SERPSWORDATTACK,
        DIGI_SERPPAIN, DIGI_SERPSCREAM, DIGI_SERPMAGICLAUNCH,
        DIGI_SERPSUMMONHEADS,DIGI_SERPTAUNTYOU,DIGI_SERPDEATHEXPLODE,0
    }
};

//////////////////////
//
// SERP RUN
//
//////////////////////

#define SERP_RUN_RATE 24

ANIMATOR DoSerpMove,NullSerp,DoActorDebris,NullSerp;

STATE s_SerpRun[5][4] =
{
    {
        {SERP_RUN_R0 + 0, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[0][1]},
        {SERP_RUN_R0 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[0][2]},
        {SERP_RUN_R0 + 2, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[0][3]},
        {SERP_RUN_R0 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[0][0]},
    },
    {
        {SERP_RUN_R1 + 0, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[1][1]},
        {SERP_RUN_R1 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[1][2]},
        {SERP_RUN_R1 + 2, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[1][3]},
        {SERP_RUN_R1 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[1][0]},
    },
    {
        {SERP_RUN_R2 + 0, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[2][1]},
        {SERP_RUN_R2 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[2][2]},
        {SERP_RUN_R2 + 2, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[2][3]},
        {SERP_RUN_R2 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[2][0]},
    },
    {
        {SERP_RUN_R3 + 0, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[3][1]},
        {SERP_RUN_R3 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[3][2]},
        {SERP_RUN_R3 + 2, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[3][3]},
        {SERP_RUN_R3 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[3][0]},
    },
    {
        {SERP_RUN_R4 + 0, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[4][1]},
        {SERP_RUN_R4 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[4][2]},
        {SERP_RUN_R4 + 2, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[4][3]},
        {SERP_RUN_R4 + 1, SERP_RUN_RATE, DoSerpMove, &s_SerpRun[4][0]},
    }
};

STATEp sg_SerpRun[] =
{
    &s_SerpRun[0][0],
    &s_SerpRun[1][0],
    &s_SerpRun[2][0],
    &s_SerpRun[3][0],
    &s_SerpRun[4][0]
};

//////////////////////
//
// SERP SLASH
//
//////////////////////

#define SERP_SLASH_RATE 9
ANIMATOR InitActorDecide;
ANIMATOR InitSerpSlash;

STATE s_SerpSlash[5][10] =
{
    {
        {SERP_SLASH_R0 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[0][1]},
        {SERP_SLASH_R0 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[0][2]},
        {SERP_SLASH_R0 + 0, SERP_SLASH_RATE*2, NullSerp, &s_SerpSlash[0][3]},
        {SERP_SLASH_R0 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[0][4]},
        {SERP_SLASH_R0 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[0][5]},
        {SERP_SLASH_R0 + 3, SF_QUICK_CALL, InitSerpSlash, &s_SerpSlash[0][6]},
        {SERP_SLASH_R0 + 3, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[0][7]},
        {SERP_SLASH_R0 + 4, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[0][8]},
        {SERP_SLASH_R0 + 4, 0|SF_QUICK_CALL, InitActorDecide, &s_SerpSlash[0][9]},
        {SERP_SLASH_R0 + 4, SERP_SLASH_RATE, DoSerpMove, &s_SerpSlash[0][9]},
    },
    {
        {SERP_SLASH_R1 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[1][1]},
        {SERP_SLASH_R1 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[1][2]},
        {SERP_SLASH_R1 + 0, SERP_SLASH_RATE*2, NullSerp, &s_SerpSlash[1][3]},
        {SERP_SLASH_R1 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[1][4]},
        {SERP_SLASH_R1 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[1][5]},
        {SERP_SLASH_R1 + 3, SF_QUICK_CALL, InitSerpSlash, &s_SerpSlash[1][6]},
        {SERP_SLASH_R1 + 3, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[1][7]},
        {SERP_SLASH_R1 + 4, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[1][8]},
        {SERP_SLASH_R1 + 4, 0|SF_QUICK_CALL, InitActorDecide, &s_SerpSlash[1][9]},
        {SERP_SLASH_R1 + 4, SERP_SLASH_RATE, DoSerpMove, &s_SerpSlash[1][9]},
    },
    {
        {SERP_SLASH_R2 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[2][1]},
        {SERP_SLASH_R2 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[2][2]},
        {SERP_SLASH_R2 + 0, SERP_SLASH_RATE*2, NullSerp, &s_SerpSlash[2][3]},
        {SERP_SLASH_R2 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[2][4]},
        {SERP_SLASH_R2 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[2][5]},
        {SERP_SLASH_R2 + 3, SF_QUICK_CALL, InitSerpSlash, &s_SerpSlash[2][6]},
        {SERP_SLASH_R2 + 3, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[2][7]},
        {SERP_SLASH_R2 + 4, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[2][8]},
        {SERP_SLASH_R2 + 4, 0|SF_QUICK_CALL, InitActorDecide, &s_SerpSlash[2][9]},
        {SERP_SLASH_R2 + 4, SERP_SLASH_RATE, DoSerpMove, &s_SerpSlash[2][9]},
    },
    {
        {SERP_SLASH_R3 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[3][1]},
        {SERP_SLASH_R3 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[3][2]},
        {SERP_SLASH_R3 + 0, SERP_SLASH_RATE*2, NullSerp, &s_SerpSlash[3][3]},
        {SERP_SLASH_R3 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[3][4]},
        {SERP_SLASH_R3 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[3][5]},
        {SERP_SLASH_R3 + 3, SF_QUICK_CALL, InitSerpSlash, &s_SerpSlash[3][6]},
        {SERP_SLASH_R3 + 3, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[3][7]},
        {SERP_SLASH_R3 + 4, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[3][8]},
        {SERP_SLASH_R3 + 4, 0|SF_QUICK_CALL, InitActorDecide, &s_SerpSlash[3][9]},
        {SERP_SLASH_R3 + 4, SERP_SLASH_RATE, DoSerpMove, &s_SerpSlash[3][9]},
    },
    {
        {SERP_SLASH_R4 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[4][1]},
        {SERP_SLASH_R4 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[4][2]},
        {SERP_SLASH_R4 + 0, SERP_SLASH_RATE*2, NullSerp, &s_SerpSlash[4][3]},
        {SERP_SLASH_R4 + 1, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[4][4]},
        {SERP_SLASH_R4 + 2, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[4][5]},
        {SERP_SLASH_R4 + 3, SF_QUICK_CALL, InitSerpSlash, &s_SerpSlash[4][6]},
        {SERP_SLASH_R4 + 3, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[4][7]},
        {SERP_SLASH_R4 + 4, SERP_SLASH_RATE, NullSerp, &s_SerpSlash[4][8]},
        {SERP_SLASH_R4 + 4, 0|SF_QUICK_CALL, InitActorDecide, &s_SerpSlash[4][9]},
        {SERP_SLASH_R4 + 4, SERP_SLASH_RATE, DoSerpMove, &s_SerpSlash[4][9]},
    }
};

STATEp sg_SerpSlash[] =
{
    &s_SerpSlash[0][0],
    &s_SerpSlash[1][0],
    &s_SerpSlash[2][0],
    &s_SerpSlash[3][0],
    &s_SerpSlash[4][0]
};

//////////////////////
//
// SERP SKULL SPELL
//
//////////////////////

#define SERP_SKULL_SPELL_RATE 18
ANIMATOR InitSerpSkullSpell;
ANIMATOR InitSerpRing;

STATE s_SerpSkullSpell[5][8] =
{
    {
        {SERP_SPELL_R0 + 2, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[0][1]},
        {SERP_SPELL_R0 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[0][2]},
        {SERP_SPELL_R0 + 0, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[0][3]},
        {SERP_SPELL_R0 + 0, SF_QUICK_CALL, InitSerpRing, &s_SerpSkullSpell[0][4]},
        {SERP_SPELL_R0 + 0, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[0][5]},
        {SERP_SPELL_R0 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[0][6]},
        {SERP_SPELL_R0 + 1, SF_QUICK_CALL,   InitActorDecide, &s_SerpSkullSpell[0][7]},
        {SERP_SPELL_R0 + 1, SERP_SKULL_SPELL_RATE, DoSerpMove, &s_SerpSkullSpell[0][7]},
    },
    {
        {SERP_SPELL_R1 + 2, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[1][1]},
        {SERP_SPELL_R1 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[1][2]},
        {SERP_SPELL_R1 + 0, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[1][3]},
        {SERP_SPELL_R1 + 0, SF_QUICK_CALL, InitSerpRing, &s_SerpSkullSpell[1][4]},
        {SERP_SPELL_R1 + 0, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[1][5]},
        {SERP_SPELL_R1 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[1][6]},
        {SERP_SPELL_R1 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSkullSpell[1][7]},
        {SERP_SPELL_R1 + 1, SERP_SKULL_SPELL_RATE, DoSerpMove, &s_SerpSkullSpell[1][7]},
    },
    {
        {SERP_SPELL_R2 + 2, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[2][1]},
        {SERP_SPELL_R2 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[2][2]},
        {SERP_SPELL_R2 + 0, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[2][3]},
        {SERP_SPELL_R2 + 0, SF_QUICK_CALL, InitSerpRing, &s_SerpSkullSpell[2][4]},
        {SERP_SPELL_R2 + 0, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[2][5]},
        {SERP_SPELL_R2 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[2][6]},
        {SERP_SPELL_R2 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSkullSpell[2][7]},
        {SERP_SPELL_R2 + 1, SERP_SKULL_SPELL_RATE, DoSerpMove, &s_SerpSkullSpell[2][7]},
    },
    {
        {SERP_SPELL_R3 + 2, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[3][1]},
        {SERP_SPELL_R3 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[3][2]},
        {SERP_SPELL_R3 + 0, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[3][3]},
        {SERP_SPELL_R3 + 0, SF_QUICK_CALL, InitSerpRing, &s_SerpSkullSpell[3][4]},
        {SERP_SPELL_R3 + 0, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[3][5]},
        {SERP_SPELL_R3 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[3][6]},
        {SERP_SPELL_R3 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSkullSpell[3][7]},
        {SERP_SPELL_R3 + 1, SERP_SKULL_SPELL_RATE, DoSerpMove, &s_SerpSkullSpell[3][7]},
    },
    {
        {SERP_SPELL_R4 + 2, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[4][1]},
        {SERP_SPELL_R4 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[4][2]},
        {SERP_SPELL_R4 + 0, SERP_SKULL_SPELL_RATE*2, NullSerp, &s_SerpSkullSpell[4][3]},
        {SERP_SPELL_R4 + 0, SF_QUICK_CALL, InitSerpRing, &s_SerpSkullSpell[4][4]},
        {SERP_SPELL_R4 + 0, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[4][5]},
        {SERP_SPELL_R4 + 1, SERP_SKULL_SPELL_RATE, NullSerp, &s_SerpSkullSpell[4][6]},
        {SERP_SPELL_R4 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSkullSpell[4][7]},
        {SERP_SPELL_R4 + 1, SERP_SKULL_SPELL_RATE, DoSerpMove, &s_SerpSkullSpell[4][7]},
    }
};

STATEp sg_SerpSkullSpell[] =
{
    &s_SerpSkullSpell[0][0],
    &s_SerpSkullSpell[1][0],
    &s_SerpSkullSpell[2][0],
    &s_SerpSkullSpell[3][0],
    &s_SerpSkullSpell[4][0]
};

//////////////////////
//
// SERP SPELL
//
//////////////////////

#define SERP_SPELL_RATE 18
ANIMATOR InitSerpSpell;

STATE s_SerpSpell[5][8] =
{
    {
        {SERP_SPELL_R0 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[0][1]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[0][2]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[0][3]},
        {SERP_SPELL_R0 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpSpell[0][4]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[0][5]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[0][6]},
        {SERP_SPELL_R0 + 1, SF_QUICK_CALL,   InitActorDecide, &s_SerpSpell[0][7]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpSpell[0][7]},
    },
    {
        {SERP_SPELL_R1 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[1][1]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[1][2]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[1][3]},
        {SERP_SPELL_R1 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpSpell[1][4]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[1][5]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[1][6]},
        {SERP_SPELL_R1 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSpell[1][7]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpSpell[1][7]},
    },
    {
        {SERP_SPELL_R2 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[2][1]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[2][2]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[2][3]},
        {SERP_SPELL_R2 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpSpell[2][4]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[2][5]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[2][6]},
        {SERP_SPELL_R2 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSpell[2][7]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpSpell[2][7]},
    },
    {
        {SERP_SPELL_R3 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[3][1]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[3][2]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[3][3]},
        {SERP_SPELL_R3 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpSpell[3][4]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[3][5]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[3][6]},
        {SERP_SPELL_R3 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSpell[3][7]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpSpell[3][7]},
    },
    {
        {SERP_SPELL_R4 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[4][1]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[4][2]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpSpell[4][3]},
        {SERP_SPELL_R4 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpSpell[4][4]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[4][5]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpSpell[4][6]},
        {SERP_SPELL_R4 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpSpell[4][7]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpSpell[4][7]},
    }
};

STATEp sg_SerpSpell[] =
{
    &s_SerpSpell[0][0],
    &s_SerpSpell[1][0],
    &s_SerpSpell[2][0],
    &s_SerpSpell[3][0],
    &s_SerpSpell[4][0]
};

//////////////////////
//
// SERP SPELL MONSTER
//
//////////////////////

ANIMATOR InitSerpMonstSpell;

STATE s_SerpMonstSpell[5][8] =
{
    {
        {SERP_SPELL_R0 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[0][1]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[0][2]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[0][3]},
        {SERP_SPELL_R0 + 0, SF_QUICK_CALL, InitSerpMonstSpell, &s_SerpMonstSpell[0][4]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[0][5]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[0][6]},
        {SERP_SPELL_R0 + 1, SF_QUICK_CALL,   InitActorDecide, &s_SerpMonstSpell[0][7]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpMonstSpell[0][7]},
    },
    {
        {SERP_SPELL_R1 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[1][1]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[1][2]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[1][3]},
        {SERP_SPELL_R1 + 0, SF_QUICK_CALL, InitSerpMonstSpell, &s_SerpMonstSpell[1][4]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[1][5]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[1][6]},
        {SERP_SPELL_R1 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpMonstSpell[1][7]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpMonstSpell[1][7]},
    },
    {
        {SERP_SPELL_R2 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[2][1]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[2][2]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[2][3]},
        {SERP_SPELL_R2 + 0, SF_QUICK_CALL, InitSerpMonstSpell, &s_SerpMonstSpell[2][4]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[2][5]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[2][6]},
        {SERP_SPELL_R2 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpMonstSpell[2][7]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpMonstSpell[2][7]},
    },
    {
        {SERP_SPELL_R3 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[3][1]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[3][2]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[3][3]},
        {SERP_SPELL_R3 + 0, SF_QUICK_CALL, InitSerpMonstSpell, &s_SerpMonstSpell[3][4]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[3][5]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[3][6]},
        {SERP_SPELL_R3 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpMonstSpell[3][7]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpMonstSpell[3][7]},
    },
    {
        {SERP_SPELL_R4 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[4][1]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[4][2]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpMonstSpell[4][3]},
        {SERP_SPELL_R4 + 0, SF_QUICK_CALL, InitSerpMonstSpell, &s_SerpMonstSpell[4][4]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[4][5]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpMonstSpell[4][6]},
        {SERP_SPELL_R4 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpMonstSpell[4][7]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpMonstSpell[4][7]},
    }
};

STATEp sg_SerpMonstSpell[] =
{
    &s_SerpMonstSpell[0][0],
    &s_SerpMonstSpell[1][0],
    &s_SerpMonstSpell[2][0],
    &s_SerpMonstSpell[3][0],
    &s_SerpMonstSpell[4][0]
};

//////////////////////
//
// SERP RAPID SPELL
//
//////////////////////

#define SERP_SPELL_RATE 18
ANIMATOR InitSerpRapidSpell;

STATE s_SerpRapidSpell[5][10] =
{
    {
        {SERP_SPELL_R0 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[0][1]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[0][2]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[0][3]},
        {SERP_SPELL_R0 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[0][4]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[0][5]},
        {SERP_SPELL_R0 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[0][6]},
        {SERP_SPELL_R0 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[0][7]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[0][8]},
        {SERP_SPELL_R0 + 1, SF_QUICK_CALL,   InitActorDecide, &s_SerpRapidSpell[0][9]},
        {SERP_SPELL_R0 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpRapidSpell[0][9]},
    },
    {
        {SERP_SPELL_R1 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[1][1]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[1][2]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[1][3]},
        {SERP_SPELL_R1 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[1][4]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[1][5]},
        {SERP_SPELL_R1 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[1][6]},
        {SERP_SPELL_R1 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[1][7]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[1][8]},
        {SERP_SPELL_R1 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpRapidSpell[1][9]},
        {SERP_SPELL_R1 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpRapidSpell[1][9]},
    },
    {
        {SERP_SPELL_R2 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[2][1]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[2][2]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[2][3]},
        {SERP_SPELL_R2 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[2][4]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[2][5]},
        {SERP_SPELL_R2 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[2][6]},
        {SERP_SPELL_R2 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[2][7]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[2][8]},
        {SERP_SPELL_R2 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpRapidSpell[2][9]},
        {SERP_SPELL_R2 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpRapidSpell[2][9]},
    },
    {
        {SERP_SPELL_R3 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[3][1]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[3][2]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[3][3]},
        {SERP_SPELL_R3 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[3][4]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[3][5]},
        {SERP_SPELL_R3 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[3][6]},
        {SERP_SPELL_R3 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[3][7]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[3][8]},
        {SERP_SPELL_R3 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpRapidSpell[3][9]},
        {SERP_SPELL_R3 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpRapidSpell[3][9]},
    },
    {
        {SERP_SPELL_R4 + 2, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[4][1]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[4][2]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[4][3]},
        {SERP_SPELL_R4 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[4][4]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE*2, NullSerp, &s_SerpRapidSpell[4][5]},
        {SERP_SPELL_R4 + 0, SF_QUICK_CALL, InitSerpSpell, &s_SerpRapidSpell[4][6]},
        {SERP_SPELL_R4 + 0, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[4][7]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, NullSerp, &s_SerpRapidSpell[4][8]},
        {SERP_SPELL_R4 + 1, SF_QUICK_CALL, InitActorDecide, &s_SerpRapidSpell[4][9]},
        {SERP_SPELL_R4 + 1, SERP_SPELL_RATE, DoSerpMove, &s_SerpRapidSpell[4][9]},
    }
};

STATEp sg_SerpRapidSpell[] =
{
    &s_SerpRapidSpell[0][0],
    &s_SerpRapidSpell[1][0],
    &s_SerpRapidSpell[2][0],
    &s_SerpRapidSpell[3][0],
    &s_SerpRapidSpell[4][0]
};

//////////////////////
//
// SERP STAND
//
//////////////////////

#define SERP_STAND_RATE 12

STATE s_SerpStand[5][1] =
{
    {
        {SERP_RUN_R0 + 0, SERP_STAND_RATE, DoSerpMove, &s_SerpStand[0][0]},
    },
    {
        {SERP_RUN_R1 + 0, SERP_STAND_RATE, DoSerpMove, &s_SerpStand[1][0]},
    },
    {
        {SERP_RUN_R2 + 0, SERP_STAND_RATE, DoSerpMove, &s_SerpStand[2][0]},
    },
    {
        {SERP_RUN_R3 + 0, SERP_STAND_RATE, DoSerpMove, &s_SerpStand[3][0]},
    },
    {
        {SERP_RUN_R4 + 0, SERP_STAND_RATE, DoSerpMove, &s_SerpStand[4][0]},
    },
};

STATEp sg_SerpStand[] =
{
    s_SerpStand[0],
    s_SerpStand[1],
    s_SerpStand[2],
    s_SerpStand[3],
    s_SerpStand[4]
};

//////////////////////
//
// SERP DIE
//
//////////////////////

#define SERP_DIE_RATE 20
ANIMATOR DoDeathSpecial;

STATE s_SerpDie[] =
{
    {SERP_DIE + 0, SERP_DIE_RATE, NullSerp, &s_SerpDie[1]},
    {SERP_DIE + 1, SERP_DIE_RATE, NullSerp, &s_SerpDie[2]},
    {SERP_DIE + 2, SERP_DIE_RATE, NullSerp, &s_SerpDie[3]},
    {SERP_DIE + 3, SERP_DIE_RATE, NullSerp, &s_SerpDie[4]},
    {SERP_DIE + 4, SERP_DIE_RATE, NullSerp, &s_SerpDie[5]},
    {SERP_DIE + 5, SERP_DIE_RATE, NullSerp, &s_SerpDie[6]},
    {SERP_DIE + 6, SERP_DIE_RATE, NullSerp, &s_SerpDie[7]},
    {SERP_DIE + 7, SERP_DIE_RATE, NullSerp, &s_SerpDie[8]},
    {SERP_DIE + 8, SERP_DIE_RATE, NullSerp, &s_SerpDie[9]},
    {SERP_DIE + 8, SF_QUICK_CALL, DoDeathSpecial, &s_SerpDie[10]},
    {SERP_DEAD,    SERP_DIE_RATE, DoActorDebris, &s_SerpDie[10]}
};

STATE s_SerpDead[] =
{
    {SERP_DEAD, SERP_DIE_RATE, DoActorDebris, &s_SerpDead[0]},
};

STATEp sg_SerpDie[] =
{
    s_SerpDie
};

STATEp sg_SerpDead[] =
{
    s_SerpDead
};

/*
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 SerpActionSet =
{
    sg_SerpStand,
    sg_SerpRun,
    NULL, //sg_SerpJump,
    NULL, //sg_SerpFall,
    NULL, //sg_SerpCrawl,
    NULL, //sg_SerpSwim,
    NULL, //sg_SerpFly,
    NULL, //sg_SerpRise,
    NULL, //sg_SerpSit,
    NULL, //sg_SerpLook,
    NULL, //climb
    NULL, //pain
    sg_SerpDie,
    NULL, //sg_SerpHariKari,
    sg_SerpDead,
    NULL, //sg_SerpDeathJump,
    NULL, //sg_SerpDeathFall,
    {sg_SerpSlash},
    {1024},
    {sg_SerpSlash, sg_SerpSpell, sg_SerpRapidSpell, sg_SerpRapidSpell},
    {256, 724, 900, 1024},
    {NULL},
    NULL,
    NULL
};

int
SetupSerp(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,SERP_RUN_R0,s_SerpRun[0]);
        u->Health = HEALTH_SERP_GOD;
    }

    if (Skill == 0) u->Health = 1100;
    if (Skill == 1) u->Health = 2200;

    ChangeState(SpriteNum, s_SerpRun[0]);
    u->Attrib = &SerpAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_SerpDie;
    u->Rot = sg_SerpRun;

    EnemyDefaults(SpriteNum, &SerpActionSet, &SerpPersonality);

    // Mini-Boss Serp
    if (sp->pal == 16)
    {
        u->Health = 1000;
        sp->yrepeat = 74;
        sp->xrepeat = 74;
    }
    else
    {
        sp->yrepeat = 100;
        sp->xrepeat = 128;
    }

    sp->clipdist = (512) >> 2;
    SET(u->Flags, SPR_XFLIP_TOGGLE|SPR_ELECTRO_TOLERANT);

    u->loz = sp->z;

    // amount to move up for clipmove
    u->zclip = Z(80);
    // size of step can walk off of
    u->lo_step = Z(40);

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

    return 0;
}

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

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

    KeepActorOnFloor(SpriteNum);

    //DoActorSectorDamage(SpriteNum);
    return 0;
}

int DoSerpMove(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);

    // serp ring
    if (sp->pal != 16)
    {
        switch (u->Counter2)
        {
        case 0:
            if (u->Health != u->MaxHealth)
            {
                NewStateGroup(SpriteNum, sg_SerpSkullSpell);
                u->Counter2++;
            }
            break;

        case 1:
            //if (u->Health <= DIV2(u->MaxHealth))
        {
            if (u->Counter <= 0)
                NewStateGroup(SpriteNum, sg_SerpSkullSpell);
        }
        break;
        }
    }

    KeepActorOnFloor(SpriteNum);

    //DoActorSectorDamage(SpriteNum);
    return 0;
}

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

    DoMatchEverything(NULL, sp->lotag, ON);

    if (!SW_SHAREWARE && gs.MusicOn)
        PlaySong(0, RedBookSong[Level], TRUE, FALSE);

    BossSpriteNum[0] = -2;
    return 0;
}

#include "saveable.h"

static saveable_code saveable_serp_code[] =
{
    SAVE_CODE(SetupSerp),
    SAVE_CODE(NullSerp),
    SAVE_CODE(DoSerpMove),
    SAVE_CODE(DoDeathSpecial),
};

static saveable_data saveable_serp_data[] =
{
    SAVE_DATA(SerpBattle),
    SAVE_DATA(SerpOffense),
    SAVE_DATA(SerpBroadcast),
    SAVE_DATA(SerpSurprised),
    SAVE_DATA(SerpEvasive),
    SAVE_DATA(SerpLostTarget),
    SAVE_DATA(SerpCloseRange),

    SAVE_DATA(SerpPersonality),

    SAVE_DATA(SerpAttrib),
    SAVE_DATA(SerpPissedAttrib),

    SAVE_DATA(s_SerpRun),
    SAVE_DATA(sg_SerpRun),
    SAVE_DATA(s_SerpSlash),
    SAVE_DATA(sg_SerpSlash),
    SAVE_DATA(s_SerpSkullSpell),
    SAVE_DATA(sg_SerpSkullSpell),
    SAVE_DATA(s_SerpSpell),
    SAVE_DATA(sg_SerpSpell),
    SAVE_DATA(s_SerpMonstSpell),
    SAVE_DATA(sg_SerpMonstSpell),
    SAVE_DATA(s_SerpRapidSpell),
    SAVE_DATA(sg_SerpRapidSpell),
    SAVE_DATA(s_SerpStand),
    SAVE_DATA(sg_SerpStand),
    SAVE_DATA(s_SerpDie),
    SAVE_DATA(s_SerpDead),
    SAVE_DATA(sg_SerpDie),
    SAVE_DATA(sg_SerpDead),

    SAVE_DATA(SerpActionSet),
};

saveable_module saveable_serp =
{
    // code
    saveable_serp_code,
    SIZ(saveable_serp_code),

    // data
    saveable_serp_data,
    SIZ(saveable_serp_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 "mytypes.h"
#include "gamedefs.h"
// Only ad to the end. These currently have to be in this order because of the
// way they are initilized.

typedef struct
{
    int MouseSpeed;
    int MusicVolume;
    int SoundVolume;
    int8_t BorderNum;
    int8_t Brightness;
    int8_t BorderTile;
    SWBOOL MouseAimingType;
    SWBOOL MouseLook;
    SWBOOL MouseInvert;
    SWBOOL Bobbing;
    SWBOOL Tilting;
    SWBOOL Shadows;
    SWBOOL AutoRun;
    SWBOOL Crosshair;
    SWBOOL AutoAim;
    SWBOOL InterpolateSO;
    SWBOOL Messages;
    SWBOOL FxOn;
    SWBOOL MusicOn;
    SWBOOL Talking;
    SWBOOL Ambient;
    SWBOOL FlipStereo;
// Net Options from Menus
    uint8_t NetGameType;   // 0=DeathMatch [spawn], 1=Cooperative 2=DeathMatch [no spawn]
    uint8_t NetLevel;      // 1-28
    uint8_t NetMonsters;   // Cycle skill levels
    SWBOOL NetHurtTeammate;  // Allow friendly kills
    SWBOOL NetSpawnMarkers;    // Respawn markers on/off
    SWBOOL NetTeamPlay;   // Team play
    uint8_t NetKillLimit;  // Number of frags at which game ends
    uint8_t NetTimeLimit;  // Limit time of game
    uint8_t NetColor;      // Chosen color for player
    uint8_t ParentalLock;  // Parental Lock on/off
    char Password[20];  // Parental Lock password
    SWBOOL NetNuke;
    SWBOOL Voxels;
    SWBOOL Stats;
    SWBOOL MouseAimingOn; // whether it was on or off - NOT the type of mouse aiming
    SWBOOL PlayCD;
    char WaveformTrackName[MAXWAVEFORMTRACKLENGTH];
    SWBOOL Darts;
    SWBOOL WeaponAutoSwitch;
    int FOV;
} GAME_SET, *GAME_SETp;

extern const GAME_SET gs_defaults;
extern GAME_SET gs;

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 "baselayer.h"
#include "osd.h"

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

#include "mytypes.h"
#include "fx_man.h"
#include "music.h"
#include "scriplib.h"
#include "gamedefs.h"
#include "keyboard.h"

#include "control.h"
#include "config.h"
#include "sounds.h"
#include "function.h"

#include "rts.h"

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

=
= SetupGameButtons
=
===================
*/

void SetupGameButtons(void)
{
    // processes array from function.h - char * gamefunctions[];

    short gamefunc;

    for (gamefunc = 0; gamefunc < NUMGAMEFUNCTIONS; gamefunc++)
    {
        CONTROL_DefineFlag(gamefunc,FALSE);
    }
}

void CenterCenter(void)
{
    printf("\nCenter the joystick and press a button\n");
}

void UpperLeft(void)
{
    printf("Move joystick to upper-left corner and press a button\n");
}

void LowerRight(void)
{
    printf("Move joystick to lower-right corner and press a button\n");
}

void CenterThrottle(void)
{
    printf("Center the throttle control and press a button\n");
}

void CenterRudder(void)
{
    printf("Center the rudder control and press a button\n");
}

/*
===================
=
= GetTime
=
===================
*/

//static int32_t timert;

int32_t GetTime(void)
{
    return (int32_t) totalclock;
    //return timert++;
}

void InitSetup(void)
{
    //RegisterShutdownFunction( ShutDown );

    //StartWindows();
    //initkeys();
    //CONFIG_GetSetupFilename();
    //InitializeKeyDefList();
    //CONFIG_ReadSetup();
    if (CONTROL_Startup(controltype_keyboardandmouse, &GetTime, /*120*/ 140)) exit(1);
    SetupGameButtons();
    CONFIG_SetupMouse();
    CONFIG_SetupJoystick();

    CONTROL_JoystickEnabled = (ud_setup.UseJoystick && CONTROL_JoyPresent);
    CONTROL_MouseEnabled = (ud_setup.UseMouse && CONTROL_MousePresent);

    /*{
    int i;
    CONTROL_PrintKeyMap();
    for(i=0;i<NUMGAMEFUNCTIONS;i++) CONTROL_PrintControlFlag(i);
    CONTROL_PrintAxes();
    }*/

    RTS_Init(RTSName);

    OSD_Exec("voidsw_cvars.cfg");
    OSD_Exec("voidsw_autoexec.cfg");

    system_getcvars();
    FX_InitCvars();
}

#if 0
void TermSetup(void)
{
    //FreeKeyDefList();
}
#endif

// BELOW IS FROM A TEST SETUP BY MARK DOC
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

#if 0
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include "types.h"
#include "sndcards.h"
#include "fx_man.h"
#include "music.h"
#include "scriplib.h"
#include "gamedefs.h"
#include "keyboard.h"

#include "control.h"
#include "config.h"
#include "sounds.h"
#include "function.h"
#include "rts.h"
#include "timer.h"

int32_t timerhandle=0;
volatile int32_t timer;
/*
===================
=
= Shutdown
=
===================
*/

void ShutDown(void)
{
    KB_Shutdown();
    TIME_RemoveTimer(timerhandle);
    SoundShutdown();
    MusicShutdown();
    CONFIG_WriteSetup();
}

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

=
= SetupGameButtons
=
===================
*/

void SetupGameButtons(void)
{
    CONTROL_DefineFlag(gamefunc_Move_Forward,      FALSE);
    CONTROL_DefineFlag(gamefunc_Move_Backward,     FALSE);
    CONTROL_DefineFlag(gamefunc_Turn_Left,         FALSE);
    CONTROL_DefineFlag(gamefunc_Turn_Right,        FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe,            FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe_Left,       FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe_Right,      FALSE);
    CONTROL_DefineFlag(gamefunc_TurnAround,        FALSE);
    CONTROL_DefineFlag(gamefunc_Jump,              FALSE);
    CONTROL_DefineFlag(gamefunc_Crouch,            FALSE);
    CONTROL_DefineFlag(gamefunc_Fire,              FALSE);
    CONTROL_DefineFlag(gamefunc_Open,              FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Up,           FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Down,         FALSE);
    CONTROL_DefineFlag(gamefunc_Aim_Up,            FALSE);
    CONTROL_DefineFlag(gamefunc_Aim_Down,          FALSE);
    CONTROL_DefineFlag(gamefunc_Run,               FALSE);
    CONTROL_DefineFlag(gamefunc_SendMessage,       FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_1,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_2,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_3,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_4,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_5,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_6,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_7,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_8,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_9,          FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_10,         FALSE);
    CONTROL_DefineFlag(gamefunc_Map,               FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Left,         FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Right,        FALSE);
    CONTROL_DefineFlag(gamefunc_Shrink_Screen,     FALSE);
    CONTROL_DefineFlag(gamefunc_Enlarge_Screen,    FALSE);
    CONTROL_DefineFlag(gamefunc_AutoRun,           FALSE);
    CONTROL_DefineFlag(gamefunc_Center_View,       FALSE);
    CONTROL_DefineFlag(gamefunc_Holster_Weapon,    FALSE);
    CONTROL_DefineFlag(gamefunc_Inventory_Left,    FALSE);
    CONTROL_DefineFlag(gamefunc_Inventory_Right,   FALSE);
    CONTROL_DefineFlag(gamefunc_Inventory,         FALSE);

}

/*
===================
=
= GetTime
=
===================
*/

int32_t GetTime(void)
{
    return timer;
}

/*
===================
=
= CenterCenter
=
===================
*/

void CenterCenter(void)
{
    printf("Center the joystick and press a button\n");
}

/*
===================
=
= UpperLeft
=
===================
*/

void UpperLeft(void)
{
    printf("Move joystick to upper-left corner and press a button\n");
}

/*
===================
=
= LowerRight
=
===================
*/

void LowerRight(void)
{
    printf("Move joystick to lower-right corner and press a button\n");
}

/*
===================
=
= CenterThrottle
=
===================
*/

void CenterThrottle(void)
{
    printf("Center the throttle control and press a button\n");
}

/*
===================
=
= CenterRudder
=
===================
*/

void CenterRudder(void)
{
    printf("Center the rudder control and press a button\n");
}

void main()
{
    char *song;
    char *voc;
    volatile int32_t lasttime;

    RegisterShutdownFunction(ShutDown);
    KB_Startup();
    timerhandle = TIME_AddTimer(40, &timer);
    //CONFIG_GetSetupFilename();
    CONFIG_ReadSetup();
    CONTROL_Startup(1, &GetTime, 1500);
    SetupGameButtons();

    SoundStartup();
    MusicStartup();
    RTS_Init(RTSName);

    // load in some test data

    LoadFile("test.mid",&song);
    LoadFile("test.voc",&voc);

    // start playing a song

    MUSIC_PlaySong(song, MUSIC_LoopSong);

    lasttime = timer;
    while (1)
    {
        int32_t i;
        ControlInfo info;

        while (lasttime==timer)
        {
            ServiceEvents();
        }
        lasttime = timer;
//      printf("timer=%ld\n",timer);
        CONTROL_GetInput(&info);

        if (
            info.dx!=0 ||
            info.dy!=0 ||
            info.dz!=0 ||
            info.dpitch!=0 ||
            info.dyaw!=0 ||
            info.droll!=0
            )
            printf("x=%6ld y=%6ld z=%6ld yaw=%6ld pitch=%6ld roll=%6ld\n",
                   info.dx,info.dy,info.dz,info.dyaw,info.dpitch,info.droll);
        // Get Keyboard input and set appropiate game function states
        for (i=0; i<NUMGAMEFUNCTIONS; i++)
        {
            if (BUTTON(i) && !BUTTONHELD(i))
            {
                printf("%s\n",gamefunctions[i]);
            }
        }
        for (i=0; i<10; i++)
        {
            if (KB_KeyPressed(sc_F1+i))
            {
                uint8_t *ptr;
                KB_ClearKeyDown(sc_F1+i);
                ptr = (uint8_t *)RTS_GetSound(i);
                FX_PlayVOC(ptr, 0, 255, 255, 255, 255, 0);
            }
        }
        // Check to see if fire is being pressed so we can play a sound
        if (BUTTON(gamefunc_Fire) && !BUTTONHELD(gamefunc_Fire))
        {
            FX_PlayVOC(voc, 0, 255, 255, 255, 255, 0);
        }

        // Check to see if we want to exit
        if (KB_KeyPressed(sc_Escape))
        {
            break;
        }

    }

    ShutDown();
}
#endif


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#include "keys.h"
#include "names2.h"
#include "game.h"
#include "tags.h"
#include "ai.h"
#include "actor.h"
#include "track.h"

DECISION SkelBattle[] =
{
    {600,   InitActorMoveCloser         },
    {602,   InitActorAlertNoise         },
    {700,   InitActorRunAway            },
    {1024,  InitActorAttack             }
};

DECISION SkelOffense[] =
{
    {700,   InitActorMoveCloser         },
    {702,   InitActorAlertNoise         },
    {1024,  InitActorAttack             }
};

DECISION SkelBroadcast[] =
{
    {3,    InitActorAlertNoise        },
    {6,    InitActorAmbientNoise      },
    {1024,  InitActorDecide            }
};

DECISION SkelSurprised[] =
{
    {701,   InitActorMoveCloser         },
    {1024,  InitActorDecide            }
};

DECISION SkelEvasive[] =
{
    {22,     InitActorDuck             },
    {30,     InitActorEvade            },
    {1024,   NULL                      },
};

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

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

PERSONALITY SkelPersonality =
{
    SkelBattle,
    SkelOffense,
    SkelBroadcast,
    SkelSurprised,
    SkelEvasive,
    SkelLostTarget,
    SkelCloseRange,
    SkelCloseRange
};

ATTRIBUTE SkelAttrib =
{
    {60, 80, 100, 130},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                 // MaxWeapons;
    {
        DIGI_SPAMBIENT, DIGI_SPALERT, 0,
        DIGI_SPPAIN, DIGI_SPSCREAM, DIGI_SPBLADE,
        DIGI_SPELEC,DIGI_SPTELEPORT,0,0
    }
};

//////////////////////
//
// SKEL RUN
//
//////////////////////

#define SKEL_RUN_RATE 12

ANIMATOR DoSkelMove,NullSkel,DoActorDebris,NullSkel,DoSuicide,InitSkelSpell;

// +4 on frame #3 to add character

STATE s_SkelRun[5][6] =
{
    {
        {SKEL_RUN_R0 + 0, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[0][1]},
        {SKEL_RUN_R0 + 1, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[0][2]},
        {SKEL_RUN_R0 + 2, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[0][3]},
        {SKEL_RUN_R0 + 3, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[0][4]},
        {SKEL_RUN_R0 + 4, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[0][5]},
        {SKEL_RUN_R0 + 5, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[0][0]},
    },
    {
        {SKEL_RUN_R1 + 0, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[1][1]},
        {SKEL_RUN_R1 + 1, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[1][2]},
        {SKEL_RUN_R1 + 2, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[1][3]},
        {SKEL_RUN_R1 + 3, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[1][4]},
        {SKEL_RUN_R1 + 4, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[1][5]},
        {SKEL_RUN_R1 + 5, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[1][0]},
    },
    {
        {SKEL_RUN_R2 + 0, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[2][1]},
        {SKEL_RUN_R2 + 1, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[2][2]},
        {SKEL_RUN_R2 + 2, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[2][3]},
        {SKEL_RUN_R2 + 3, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[2][4]},
        {SKEL_RUN_R2 + 4, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[2][5]},
        {SKEL_RUN_R2 + 5, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[2][0]},
    },
    {
        {SKEL_RUN_R3 + 0, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[3][1]},
        {SKEL_RUN_R3 + 1, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[3][2]},
        {SKEL_RUN_R3 + 2, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[3][3]},
        {SKEL_RUN_R3 + 3, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[3][4]},
        {SKEL_RUN_R3 + 4, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[3][5]},
        {SKEL_RUN_R3 + 5, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[3][0]},
    },
    {
        {SKEL_RUN_R4 + 0, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[4][1]},
        {SKEL_RUN_R4 + 1, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[4][2]},
        {SKEL_RUN_R4 + 2, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[4][3]},
        {SKEL_RUN_R4 + 3, SKEL_RUN_RATE+4, DoSkelMove, &s_SkelRun[4][4]},
        {SKEL_RUN_R4 + 4, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[4][5]},
        {SKEL_RUN_R4 + 5, SKEL_RUN_RATE, DoSkelMove, &s_SkelRun[4][0]},
    }
};

STATEp sg_SkelRun[] =
{
    &s_SkelRun[0][0],
    &s_SkelRun[1][0],
    &s_SkelRun[2][0],
    &s_SkelRun[3][0],
    &s_SkelRun[4][0]
};

//////////////////////
//
// SKEL SLASH
//
//////////////////////

#define SKEL_SLASH_RATE 20
ANIMATOR InitActorDecide;
ANIMATOR InitSkelSlash;

STATE s_SkelSlash[5][7] =
{
    {
        {SKEL_SLASH_R0 + 0, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[0][1]},
        {SKEL_SLASH_R0 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[0][2]},
        {SKEL_SLASH_R0 + 2, 0|SF_QUICK_CALL, InitSkelSlash, &s_SkelSlash[0][3]},
        {SKEL_SLASH_R0 + 2, SKEL_SLASH_RATE*2, NullSkel, &s_SkelSlash[0][4]},
        {SKEL_SLASH_R0 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[0][5]},
        {SKEL_SLASH_R0 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSlash[0][6]},
        {SKEL_SLASH_R0 + 1, SKEL_SLASH_RATE, DoSkelMove, &s_SkelSlash[0][6]},
    },
    {
        {SKEL_SLASH_R1 + 0, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[1][1]},
        {SKEL_SLASH_R1 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[1][2]},
        {SKEL_SLASH_R1 + 2, 0|SF_QUICK_CALL, InitSkelSlash, &s_SkelSlash[1][3]},
        {SKEL_SLASH_R1 + 2, SKEL_SLASH_RATE*2, NullSkel, &s_SkelSlash[1][4]},
        {SKEL_SLASH_R1 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[1][5]},
        {SKEL_SLASH_R1 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSlash[1][6]},
        {SKEL_SLASH_R1 + 1, SKEL_SLASH_RATE, DoSkelMove, &s_SkelSlash[1][6]},
    },
    {
        {SKEL_SLASH_R2 + 0, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[2][1]},
        {SKEL_SLASH_R2 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[2][2]},
        {SKEL_SLASH_R2 + 2, 0|SF_QUICK_CALL, InitSkelSlash, &s_SkelSlash[2][3]},
        {SKEL_SLASH_R2 + 2, SKEL_SLASH_RATE*2, NullSkel, &s_SkelSlash[2][4]},
        {SKEL_SLASH_R2 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[2][5]},
        {SKEL_SLASH_R2 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSlash[2][6]},
        {SKEL_SLASH_R2 + 1, SKEL_SLASH_RATE, DoSkelMove, &s_SkelSlash[2][6]},
    },
    {
        {SKEL_SLASH_R3 + 0, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[3][1]},
        {SKEL_SLASH_R3 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[3][2]},
        {SKEL_SLASH_R3 + 2, 0|SF_QUICK_CALL, InitSkelSlash, &s_SkelSlash[3][3]},
        {SKEL_SLASH_R3 + 2, SKEL_SLASH_RATE*2, NullSkel, &s_SkelSlash[3][4]},
        {SKEL_SLASH_R3 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[3][5]},
        {SKEL_SLASH_R3 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSlash[3][6]},
        {SKEL_SLASH_R3 + 1, SKEL_SLASH_RATE, DoSkelMove, &s_SkelSlash[3][6]},
    },
    {
        {SKEL_SLASH_R4 + 0, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[4][1]},
        {SKEL_SLASH_R4 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[4][2]},
        {SKEL_SLASH_R4 + 2, 0|SF_QUICK_CALL, InitSkelSlash, &s_SkelSlash[4][3]},
        {SKEL_SLASH_R4 + 2, SKEL_SLASH_RATE*2, NullSkel, &s_SkelSlash[4][4]},
        {SKEL_SLASH_R4 + 1, SKEL_SLASH_RATE, NullSkel, &s_SkelSlash[4][5]},
        {SKEL_SLASH_R4 + 1, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSlash[4][6]},
        {SKEL_SLASH_R4 + 1, SKEL_SLASH_RATE, DoSkelMove, &s_SkelSlash[4][6]},
    }
};

STATEp sg_SkelSlash[] =
{
    &s_SkelSlash[0][0],
    &s_SkelSlash[1][0],
    &s_SkelSlash[2][0],
    &s_SkelSlash[3][0],
    &s_SkelSlash[4][0]
};

//////////////////////
//
// SKEL SPELL
//
//////////////////////

#define SKEL_SPELL_RATE 20

STATE s_SkelSpell[5][7] =
{
    {
        {SKEL_SPELL_R0 + 0, SKEL_SPELL_RATE+9, NullSkel, &s_SkelSpell[0][1]},
        {SKEL_SPELL_R0 + 1, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[0][2]},
        {SKEL_SPELL_R0 + 2, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[0][3]},
        {SKEL_SPELL_R0 + 3, SKEL_SPELL_RATE*2, NullSkel, &s_SkelSpell[0][4]},
        {SKEL_SPELL_R0 + 3, 0|SF_QUICK_CALL, InitSkelSpell, &s_SkelSpell[0][5]},
        {SKEL_SPELL_R0 + 3, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSpell[0][6]},
        {SKEL_SPELL_R0 + 3, SKEL_SPELL_RATE, DoSkelMove, &s_SkelSpell[0][6]},
    },
    {
        {SKEL_SPELL_R1 + 0, SKEL_SPELL_RATE+9, NullSkel, &s_SkelSpell[1][1]},
        {SKEL_SPELL_R1 + 1, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[1][2]},
        {SKEL_SPELL_R1 + 2, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[1][3]},
        {SKEL_SPELL_R1 + 3, SKEL_SPELL_RATE*2, NullSkel, &s_SkelSpell[1][4]},
        {SKEL_SPELL_R1 + 3, 0|SF_QUICK_CALL, InitSkelSpell, &s_SkelSpell[1][5]},
        {SKEL_SPELL_R1 + 3, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSpell[1][6]},
        {SKEL_SPELL_R1 + 3, SKEL_SPELL_RATE, DoSkelMove, &s_SkelSpell[1][6]},
    },
    {
        {SKEL_SPELL_R2 + 0, SKEL_SPELL_RATE+9, NullSkel, &s_SkelSpell[2][1]},
        {SKEL_SPELL_R2 + 1, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[2][2]},
        {SKEL_SPELL_R2 + 2, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[2][3]},
        {SKEL_SPELL_R2 + 3, SKEL_SPELL_RATE*2, NullSkel, &s_SkelSpell[2][4]},
        {SKEL_SPELL_R2 + 3, 0|SF_QUICK_CALL, InitSkelSpell, &s_SkelSpell[2][5]},
        {SKEL_SPELL_R2 + 3, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSpell[2][6]},
        {SKEL_SPELL_R2 + 3, SKEL_SPELL_RATE, DoSkelMove, &s_SkelSpell[2][6]},
    },
    {
        {SKEL_SPELL_R3 + 0, SKEL_SPELL_RATE+9, NullSkel, &s_SkelSpell[3][1]},
        {SKEL_SPELL_R3 + 1, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[3][2]},
        {SKEL_SPELL_R3 + 2, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[3][3]},
        {SKEL_SPELL_R3 + 3, SKEL_SPELL_RATE*2, NullSkel, &s_SkelSpell[3][4]},
        {SKEL_SPELL_R3 + 3, 0|SF_QUICK_CALL, InitSkelSpell, &s_SkelSpell[3][5]},
        {SKEL_SPELL_R3 + 3, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSpell[3][6]},
        {SKEL_SPELL_R3 + 3, SKEL_SPELL_RATE, DoSkelMove, &s_SkelSpell[3][6]},
    },
    {
        {SKEL_SPELL_R4 + 0, SKEL_SPELL_RATE+9, NullSkel, &s_SkelSpell[4][1]},
        {SKEL_SPELL_R4 + 1, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[4][2]},
        {SKEL_SPELL_R4 + 2, SKEL_SPELL_RATE, NullSkel, &s_SkelSpell[4][3]},
        {SKEL_SPELL_R4 + 3, SKEL_SPELL_RATE*2, NullSkel, &s_SkelSpell[4][4]},
        {SKEL_SPELL_R4 + 3, 0|SF_QUICK_CALL, InitSkelSpell, &s_SkelSpell[4][5]},
        {SKEL_SPELL_R4 + 3, 0|SF_QUICK_CALL, InitActorDecide, &s_SkelSpell[4][6]},
        {SKEL_SPELL_R4 + 3, SKEL_SPELL_RATE, DoSkelMove, &s_SkelSpell[4][6]},
    }
};

STATEp sg_SkelSpell[] =
{
    &s_SkelSpell[0][0],
    &s_SkelSpell[1][0],
    &s_SkelSpell[2][0],
    &s_SkelSpell[3][0],
    &s_SkelSpell[4][0]
};

//////////////////////
//
// SKEL PAIN
//
//////////////////////

#define SKEL_PAIN_RATE 38
ANIMATOR DoSkelPain;

STATE s_SkelPain[5][1] =
{
    {
        {SKEL_PAIN_R0 + 0, SKEL_PAIN_RATE, DoSkelPain, &s_SkelPain[0][0]},
    },
    {
        {SKEL_PAIN_R1 + 0, SKEL_PAIN_RATE, DoSkelPain, &s_SkelPain[1][0]},
    },
    {
        {SKEL_PAIN_R2 + 0, SKEL_PAIN_RATE, DoSkelPain, &s_SkelPain[2][0]},
    },
    {
        {SKEL_PAIN_R3 + 0, SKEL_PAIN_RATE, DoSkelPain, &s_SkelPain[3][0]},
    },
    {
        {SKEL_PAIN_R4 + 0, SKEL_PAIN_RATE, DoSkelPain, &s_SkelPain[4][0]},
    }
};

STATEp sg_SkelPain[] =
{
    &s_SkelPain[0][0],
    &s_SkelPain[1][0],
    &s_SkelPain[2][0],
    &s_SkelPain[3][0],
    &s_SkelPain[4][0]
};

//////////////////////
//
// SKEL TELEPORT
//
//////////////////////

ANIMATOR DoSkelInitTeleport, DoSkelTeleport, DoSkelTermTeleport;

#define SKEL_TELEPORT_RATE 20

STATE s_SkelTeleport[] =
{
    {SKEL_TELEPORT + 0,  1,                  NullAnimator, &s_SkelTeleport[1]},
    {SKEL_TELEPORT + 0,  0|SF_QUICK_CALL,    DoSkelInitTeleport, &s_SkelTeleport[2]},
    {SKEL_TELEPORT + 0,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[3]},
    {SKEL_TELEPORT + 1,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[4]},
    {SKEL_TELEPORT + 2,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[5]},
    {SKEL_TELEPORT + 3,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[6]},
    {SKEL_TELEPORT + 4,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[7]},
    {SKEL_TELEPORT + 5,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[8]},

    {SKEL_TELEPORT + 5,  0|SF_QUICK_CALL,    DoSkelTeleport, &s_SkelTeleport[9]},

    {SKEL_TELEPORT + 5,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[10]},
    {SKEL_TELEPORT + 4,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[11]},
    {SKEL_TELEPORT + 3,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[12]},
    {SKEL_TELEPORT + 2,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[13]},
    {SKEL_TELEPORT + 1,  SKEL_TELEPORT_RATE, NullAnimator, &s_SkelTeleport[14]},
    {SKEL_TELEPORT + 0,  SKEL_TELEPORT_RATE, DoSkelTermTeleport, &s_SkelTeleport[15]},
    {SKEL_TELEPORT + 0,  0|SF_QUICK_CALL, InitActorDecide, &s_SkelTeleport[16]},
    {SKEL_TELEPORT + 0,  SKEL_TELEPORT_RATE, DoSkelMove, &s_SkelTeleport[16]},
};

STATEp sg_SkelTeleport[] =
{
    s_SkelTeleport,
    s_SkelTeleport,
    s_SkelTeleport,
    s_SkelTeleport,
    s_SkelTeleport
};

//////////////////////
//
// SKEL STAND
//
//////////////////////

#define SKEL_STAND_RATE 12

STATE s_SkelStand[5][1] =
{
    {
        {SKEL_RUN_R0 + 0, SKEL_STAND_RATE, DoSkelMove, &s_SkelStand[0][0]},
    },
    {
        {SKEL_RUN_R1 + 0, SKEL_STAND_RATE, DoSkelMove, &s_SkelStand[1][0]},
    },
    {
        {SKEL_RUN_R2 + 0, SKEL_STAND_RATE, DoSkelMove, &s_SkelStand[2][0]},
    },
    {
        {SKEL_RUN_R3 + 0, SKEL_STAND_RATE, DoSkelMove, &s_SkelStand[3][0]},
    },
    {
        {SKEL_RUN_R4 + 0, SKEL_STAND_RATE, DoSkelMove, &s_SkelStand[4][0]},
    },
};

STATEp sg_SkelStand[] =
{
    s_SkelStand[0],
    s_SkelStand[1],
    s_SkelStand[2],
    s_SkelStand[3],
    s_SkelStand[4]
};

//////////////////////
//
// SKEL DIE
//
//////////////////////

#define SKEL_DIE_RATE 25

STATE s_SkelDie[] =
{
    {SKEL_DIE + 0, SKEL_DIE_RATE, NullAnimator, &s_SkelDie[1]},
    {SKEL_DIE + 1, SKEL_DIE_RATE, NullAnimator, &s_SkelDie[2]},
    {SKEL_DIE + 2, SKEL_DIE_RATE, NullAnimator, &s_SkelDie[3]},
    {SKEL_DIE + 3, SKEL_DIE_RATE, NullAnimator, &s_SkelDie[4]},
    {SKEL_DIE + 4, SKEL_DIE_RATE, NullAnimator, &s_SkelDie[5]},
    {SKEL_DIE + 5, SKEL_DIE_RATE, DoSuicide,   &s_SkelDie[5]},
};

STATEp sg_SkelDie[] =
{
    s_SkelDie
};

/*
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 SkelActionSet =
{
    sg_SkelStand,
    sg_SkelRun,
    NULL, //sg_SkelJump,
    NULL, //sg_SkelFall,
    NULL, //sg_SkelCrawl,
    NULL, //sg_SkelSwim,
    NULL, //sg_SkelFly,
    NULL, //sg_SkelRise,
    NULL, //sg_SkelSit,
    NULL, //sg_SkelLook,
    NULL, //climb
    sg_SkelPain, //pain
    sg_SkelDie,
    NULL, //sg_SkelHariKari,
    NULL, //sg_SkelDead,
    NULL, //sg_SkelDeathJump,
    NULL, //sg_SkelDeathFall,
    {sg_SkelSlash},
    {1024},
    {sg_SkelSpell},
    {1024},
    {NULL},
    sg_SkelTeleport,
    NULL
};

int
SetupSkel(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,SKEL_RUN_R0,s_SkelRun[0]);
        u->Health = HEALTH_SKEL_PRIEST;
    }

    ChangeState(SpriteNum, s_SkelRun[0]);
    u->Attrib = &SkelAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_SkelDie;
    u->Rot = sg_SkelRun;

    EnemyDefaults(SpriteNum, &SkelActionSet, &SkelPersonality);

    // 256 is default
    //sp->clipdist = 256 >> 2;
    SET(u->Flags, SPR_XFLIP_TOGGLE);

    return 0;
}

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

    RESET(sp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);

    PlaySpriteSound(SpriteNum,attr_extra3,v3df_follow);

    return 0;
}

int DoSkelTeleport(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    int x,y;

    x = sp->x;
    y = sp->y;

    while (TRUE)
    {
        sp->x = x;
        sp->y = y;

        if (RANDOM_P2(1024) < 512)
            sp->x += 512 + RANDOM_P2(1024);
        else
            sp->x -= 512 + RANDOM_P2(1024);

        if (RANDOM_P2(1024) < 512)
            sp->y += 512 + RANDOM_P2(1024);
        else
            sp->y -= 512 + RANDOM_P2(1024);

        setspritez(SpriteNum, &sp->xyz);
        //COVERupdatesector(sp->x, sp->y, &sp->sectnum);

        if (sp->sectnum != -1)
            break;
    }

    return 0;
}

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

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

    return 0;
}

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

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

    KeepActorOnFloor(SpriteNum);
    DoActorSectorDamage(SpriteNum);

    return 0;
}

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

    NullSkel(SpriteNum);

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

    return 0;
}

int DoSkelMove(short 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);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

#include "saveable.h"

static saveable_code saveable_skel_code[] =
{
    SAVE_CODE(SetupSkel),
    SAVE_CODE(DoSkelInitTeleport),
    SAVE_CODE(DoSkelTeleport),
    SAVE_CODE(DoSkelTermTeleport),
    SAVE_CODE(NullSkel),
    SAVE_CODE(DoSkelPain),
    SAVE_CODE(DoSkelMove),
};

static saveable_data saveable_skel_data[] =
{
    SAVE_DATA(SkelBattle),
    SAVE_DATA(SkelOffense),
    SAVE_DATA(SkelBroadcast),
    SAVE_DATA(SkelSurprised),
    SAVE_DATA(SkelEvasive),
    SAVE_DATA(SkelLostTarget),
    SAVE_DATA(SkelCloseRange),

    SAVE_DATA(SkelPersonality),

    SAVE_DATA(SkelAttrib),

    SAVE_DATA(s_SkelRun),
    SAVE_DATA(sg_SkelRun),
    SAVE_DATA(s_SkelSlash),
    SAVE_DATA(sg_SkelSlash),
    SAVE_DATA(s_SkelSpell),
    SAVE_DATA(sg_SkelSpell),
    SAVE_DATA(s_SkelPain),
    SAVE_DATA(sg_SkelPain),
    SAVE_DATA(s_SkelTeleport),
    SAVE_DATA(sg_SkelTeleport),
    SAVE_DATA(s_SkelStand),
    SAVE_DATA(sg_SkelStand),
    SAVE_DATA(s_SkelDie),
    SAVE_DATA(sg_SkelDie),

    SAVE_DATA(SkelActionSet),
};

saveable_module saveable_skel =
{
    // code
    saveable_skel_code,
    SIZ(saveable_skel_code),

    // data
    saveable_skel_data,
    SIZ(saveable_skel_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 "keys.h"
#include "names2.h"
#include "game.h"
#include "tags.h"
#include "ai.h"
#include "weapon.h"
#include "actor.h"

int InitSpriteGrenade(short SpriteNum);
int InitSpriteChemBomb(short SpriteNum);
int InitFlashBomb(short SpriteNum);
int InitCaltrops(short SpriteNum);
int InitPhosphorus(int16_t SpriteNum);

//////////////////////
//
// SKULL Wait
//
//////////////////////

extern DAMAGE_DATA DamageData[];
ANIMATOR DoSkullMove,NullAnimator,DoActorDebris;

#define SKULL_RATE 10
ANIMATOR DoSkullWait;

STATE s_SkullWait[5][1] =
{
    {
        {SKULL_R0 + 0, SKULL_RATE, DoSkullWait, &s_SkullWait[0][0]},
    },
    {
        {SKULL_R1 + 0, SKULL_RATE, DoSkullWait, &s_SkullWait[1][0]},
    },
    {
        {SKULL_R2 + 0, SKULL_RATE, DoSkullWait, &s_SkullWait[2][0]},
    },
    {
        {SKULL_R3 + 0, SKULL_RATE, DoSkullWait, &s_SkullWait[3][0]},
    },
    {
        {SKULL_R4 + 0, SKULL_RATE, DoSkullWait, &s_SkullWait[4][0]},
    }
};

STATEp sg_SkullWait[] =
{
    &s_SkullWait[0][0],
    &s_SkullWait[1][0],
    &s_SkullWait[2][0],
    &s_SkullWait[3][0],
    &s_SkullWait[4][0]
};

ATTRIBUTE SkullAttrib =
{
    {60, 80, 100, 130},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                 // MaxWeapons;
    {
        DIGI_AHAMBIENT, 0, 0, 0, DIGI_AHSCREAM,
        DIGI_AHEXPLODE,0,0,0,0
    }
};

//////////////////////
//
// SKULL for Serp God
//
//////////////////////

ANIMATOR DoSerpRing;

STATE s_SkullRing[5][1] =
{
    {
        {SKULL_R0 + 0, SKULL_RATE, DoSerpRing, &s_SkullRing[0][0]},
    },
    {
        {SKULL_R1 + 0, SKULL_RATE, DoSerpRing, &s_SkullRing[1][0]},
    },
    {
        {SKULL_R2 + 0, SKULL_RATE, DoSerpRing, &s_SkullRing[2][0]},
    },
    {
        {SKULL_R3 + 0, SKULL_RATE, DoSerpRing, &s_SkullRing[3][0]},
    },
    {
        {SKULL_R4 + 0, SKULL_RATE, DoSerpRing, &s_SkullRing[4][0]},
    }
};

STATEp sg_SkullRing[] =
{
    &s_SkullRing[0][0],
    &s_SkullRing[1][0],
    &s_SkullRing[2][0],
    &s_SkullRing[3][0],
    &s_SkullRing[4][0]
};

//////////////////////
//
// SKULL Jump
//
//////////////////////

ANIMATOR DoSkullJump;

STATE s_SkullJump[5][1] =
{
    {
        {SKULL_R0 + 0, SKULL_RATE, DoSkullJump, &s_SkullJump[0][0]},
    },
    {
        {SKULL_R1 + 0, SKULL_RATE, DoSkullJump, &s_SkullJump[1][0]},
    },
    {
        {SKULL_R2 + 0, SKULL_RATE, DoSkullJump, &s_SkullJump[2][0]},
    },
    {
        {SKULL_R3 + 0, SKULL_RATE, DoSkullJump, &s_SkullJump[3][0]},
    },
    {
        {SKULL_R4 + 0, SKULL_RATE, DoSkullJump, &s_SkullJump[4][0]},
    }
};

STATEp sg_SkullJump[] =
{
    &s_SkullJump[0][0],
    &s_SkullJump[1][0],
    &s_SkullJump[2][0],
    &s_SkullJump[3][0],
    &s_SkullJump[4][0]
};

//////////////////////
//
// SKULL Explode
//
//////////////////////

#define SKULL_EXPLODE_RATE 11
ANIMATOR DoSuicide;
ANIMATOR DoDamageTest;
ANIMATOR DoSkullSpawnShrap;

STATE s_SkullExplode[] =
{
    {SKULL_EXPLODE + 0, 1,                  NullAnimator, &s_SkullExplode[1]},
    {SKULL_EXPLODE + 0, SF_QUICK_CALL,      DoDamageTest, &s_SkullExplode[2]},
    {SKULL_EXPLODE + 0, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[3]},
    {SKULL_EXPLODE + 1, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[4]},
    {SKULL_EXPLODE + 2, SF_QUICK_CALL,      DoSkullSpawnShrap, &s_SkullExplode[5]},
    {SKULL_EXPLODE + 2, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[6]},
    {SKULL_EXPLODE + 3, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[7]},
    {SKULL_EXPLODE + 4, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[8]},
    {SKULL_EXPLODE + 5, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[9]},
    {SKULL_EXPLODE + 6, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[10]},
    {SKULL_EXPLODE + 7, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[11]},
    {SKULL_EXPLODE + 8, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[12]},
    {SKULL_EXPLODE + 9, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[13]},
    {SKULL_EXPLODE +10, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[14]},
    {SKULL_EXPLODE +11, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[15]},
    {SKULL_EXPLODE +12, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[16]},
    {SKULL_EXPLODE +13, SKULL_EXPLODE_RATE, NullAnimator, &s_SkullExplode[17]},
    {SKULL_EXPLODE +13, SKULL_EXPLODE_RATE, DoSuicide,    &s_SkullExplode[17]}
};

STATEp sg_SkullExplode[] =
{
    s_SkullExplode,
};

int
SetupSkull(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,SKULL_R0,s_SkullWait[0]);
        u->Health = HEALTH_SKULL;
    }

    ChangeState(SpriteNum, s_SkullWait[0]);
    u->Attrib = &SkullAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_SkullExplode;
    u->Rot = sg_SkullWait;

    u->ID = SKULL_R0;

    EnemyDefaults(SpriteNum, NULL, NULL);
    sp->clipdist = (128+64) >> 2;
    SET(u->Flags, SPR_XFLIP_TOGGLE);
    SET(sp->cstat, CSTAT_SPRITE_YCENTER);

    u->Radius = 400;

    if (SPRITEp_BOS(sp) > u->loz - Z(16))
    {
        sp->z = u->loz + Z(SPRITEp_YOFF(sp));

        u->loz = sp->z;
        // leave 8 pixels above the ground
        sp->z += SPRITEp_SIZE_TOS(sp) - Z(3);;
    }
    else
    {
        u->Counter = RANDOM_P2(2048);
        u->sz = sp->z;
    }

    return 0;
}

int
DoSkullMove(int16_t SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int32_t dax, day, daz;

    dax = MOVEx(sp->xvel, sp->ang);
    day = MOVEy(sp->xvel, sp->ang);
    daz = sp->zvel;

    u->ret = move_missile(SpriteNum, dax, day, daz, Z(16), Z(16), CLIPMASK_MISSILE, ACTORMOVETICS);

    DoFindGroundPoint(SpriteNum);
    return 0;
}

int
DoSkullBeginDeath(int16_t SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int16_t i,num_ord=0;
    //extern short *DamageRadiusSkull;

    // Decrease for Serp God
    if (sp->owner >= 0)
        User[sp->owner]->Counter--;

    // starts the explosion that does the actual damage

    switch (sp->hitag)
    {
    case 1:
        if (sp->lotag) num_ord = sp->lotag;
        else
            num_ord = 2;
        if (num_ord > 3) num_ord = 3;
        for (i=0; i<num_ord; i++)
        {
            sp->ang = NORM_ANGLE(sp->ang+(i*1024));
            InitSpriteChemBomb(SpriteNum);
        }
        break;

    case 2:
        if (sp->lotag) num_ord = sp->lotag;
        else
            num_ord = 5;
        if (num_ord > 10) num_ord = 10;
        for (i=0; i<num_ord; i++)
        {
            sp->ang = NORM_ANGLE(RANDOM_RANGE(2048));
            InitCaltrops(SpriteNum);
        }
        break;

    case 3:
        UpdateSinglePlayKills(SpriteNum);
        InitFlashBomb(SpriteNum);
        break;

    case 4:
        if (sp->lotag) num_ord = sp->lotag;
        else
            num_ord = 5;
        if (num_ord > 10) num_ord = 10;
        for (i=0; i<num_ord; i++)
        {
            sp->ang = NORM_ANGLE(sp->ang+(i*(2048/num_ord)));
            InitSpriteGrenade(SpriteNum);
        }
        break;
    default:
        SpawnMineExp(SpriteNum);
        for (i=0; i<3; i++)
        {
            sp->ang = NORM_ANGLE(RANDOM_RANGE(2048));
            InitPhosphorus(SpriteNum);
        }
        break;
    }

    RESET(sp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
    u->RotNum = 0;
    u->Tics = 0;
//    ChangeState(SpriteNum, u->StateEnd);
    //u->State = u->StateStart = u->StateEnd;
    //u->State = u->StateEnd;
    u->ID = SKULL_R0;
    u->Radius = DamageData[DMG_SKULL_EXP].radius; //*DamageRadiusSkull;
    u->OverlapZ = Z(64);
    change_sprite_stat(SpriteNum, STAT_DEAD_ACTOR);
    sp->shade = -40;

    SpawnLittleExp(SpriteNum);
    SetSuicide(SpriteNum);

    //u->spal = sp->pal = PALETTE_RED_LIGHTING;

    return 0;
}

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

    if (sp->xvel)
        DoSkullMove(SpriteNum);
    else
        sp->ang = NORM_ANGLE(sp->ang + (64 * ACTORMOVETICS));

    if (TEST(u->Flags,SPR_JUMPING))
    {
        DoJump(SpriteNum);
    }
    else if (TEST(u->Flags,SPR_FALLING))
    {
        DoFall(SpriteNum);

        // jump/fall type
        if (sp->xvel)
        {
            SWBOOL SpriteOverlapZ(int16_t, int16_t, int);

            int dist,a,b,c;

            DISTANCE(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y, dist, a, b, c);

            if (dist < 1000 &&
                SpriteOverlapZ(SpriteNum, u->tgt_sp - sprite, Z(32)))
            {
                UpdateSinglePlayKills(SpriteNum);
                DoSkullBeginDeath(SpriteNum);
                return 0;
            }

            if ((sp->z > u->loz - Z(36)))
            {
                sp->z = u->loz - Z(36);
                UpdateSinglePlayKills(SpriteNum);
                DoSkullBeginDeath(SpriteNum);
                return 0;
            }
        }
        // non jumping type
        else
        {
            if (u->jump_speed > 200)
            {
                UpdateSinglePlayKills(SpriteNum);
                DoSkullBeginDeath(SpriteNum);
            }
        }

    }
    else
    {
        UpdateSinglePlayKills(SpriteNum);
        DoSkullBeginDeath(SpriteNum);
    }

    return 0;
}

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

    // actor does a sine wave about u->sz - this is the z mid point
#define SKULL_BOB_AMT (Z(16))

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

    return 0;
}

int DoSkullSpawnShrap(short SpriteNum)
{
    int SpawnShrap(short, short);

    SpawnShrap(SpriteNum, -1);

    //PlaySpriteSound(SpriteNum,attr_extra1,v3df_none);
    return 0;
}

int DoSkullWait(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int a,b,c,dist;

    DISTANCE(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y, dist, a, b, c);

    DoActorPickClosePlayer(SpriteNum);

    //if (dist < u->active_range)
    //    return(0);

    if ((u->WaitTics -= ACTORMOVETICS) <= 0)
    {
        PlaySound(DIGI_AHSCREAM,&sp->x,&sp->y,&sp->z,v3df_none);
        u->WaitTics = SEC(3) + RANDOM_RANGE(360);
    }

    // below the floor type
    if (sp->z > u->loz)
    {
        // look for closest player every once in a while
        if (dist < 3500)
        {
            sp->xvel = 0;
            u->jump_speed = -600;
            NewStateGroup(SpriteNum, sg_SkullJump);
            DoBeginJump(SpriteNum);
        }
    }
    else
    // above the floor type
    {
        sp->ang = NORM_ANGLE(sp->ang + (48 * ACTORMOVETICS));

        DoSkullBob(SpriteNum);

        if (dist < 8000)
        {
            sp->ang = getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y);
            sp->xvel = 128 + (RANDOM_P2(256<<8)>>8);
            u->jump_speed = -700;
            NewStateGroup(SpriteNum, sg_SkullJump);
            DoBeginJump(SpriteNum);
        }
    }

    return 0;
}

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

//////////////////////
//
// BETTY Wait
//
//////////////////////

ANIMATOR DoBettyMove,NullAnimator,DoActorDebris;

#define BETTY_RATE 10
ANIMATOR DoBettyWait;

STATE s_BettyWait[5][3] =
{
    {
        {BETTY_R0 + 0, BETTY_RATE, DoBettyWait, &s_BettyWait[0][1]},
        {BETTY_R0 + 1, BETTY_RATE, DoBettyWait, &s_BettyWait[0][2]},
        {BETTY_R0 + 2, BETTY_RATE, DoBettyWait, &s_BettyWait[0][0]},
    },
    {
        {BETTY_R1 + 0, BETTY_RATE, DoBettyWait, &s_BettyWait[1][1]},
        {BETTY_R1 + 1, BETTY_RATE, DoBettyWait, &s_BettyWait[1][2]},
        {BETTY_R1 + 2, BETTY_RATE, DoBettyWait, &s_BettyWait[1][0]},
    },
    {
        {BETTY_R2 + 0, BETTY_RATE, DoBettyWait, &s_BettyWait[2][1]},
        {BETTY_R2 + 1, BETTY_RATE, DoBettyWait, &s_BettyWait[2][2]},
        {BETTY_R2 + 2, BETTY_RATE, DoBettyWait, &s_BettyWait[2][0]},
    },
    {
        {BETTY_R3 + 0, BETTY_RATE, DoBettyWait, &s_BettyWait[3][1]},
        {BETTY_R3 + 1, BETTY_RATE, DoBettyWait, &s_BettyWait[3][2]},
        {BETTY_R3 + 2, BETTY_RATE, DoBettyWait, &s_BettyWait[3][0]},
    },
    {
        {BETTY_R4 + 0, BETTY_RATE, DoBettyWait, &s_BettyWait[4][1]},
        {BETTY_R4 + 1, BETTY_RATE, DoBettyWait, &s_BettyWait[4][2]},
        {BETTY_R4 + 2, BETTY_RATE, DoBettyWait, &s_BettyWait[4][0]},
    }
};

STATEp sg_BettyWait[] =
{
    &s_BettyWait[0][0],
    &s_BettyWait[1][0],
    &s_BettyWait[2][0],
    &s_BettyWait[3][0],
    &s_BettyWait[4][0]
};

ATTRIBUTE BettyAttrib =
{
    {60, 80, 100, 130},                 // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                 // MaxWeapons;
    {0, 0, 0, 0, 0, 0,0,0,0,0}
};

//////////////////////
//
// BETTY Jump
//
//////////////////////

ANIMATOR DoBettyJump;

STATE s_BettyJump[5][1] =
{
    {
        {BETTY_R0 + 0, BETTY_RATE, DoBettyJump, &s_BettyJump[0][0]},
    },
    {
        {BETTY_R1 + 0, BETTY_RATE, DoBettyJump, &s_BettyJump[1][0]},
    },
    {
        {BETTY_R2 + 0, BETTY_RATE, DoBettyJump, &s_BettyJump[2][0]},
    },
    {
        {BETTY_R3 + 0, BETTY_RATE, DoBettyJump, &s_BettyJump[3][0]},
    },
    {
        {BETTY_R4 + 0, BETTY_RATE, DoBettyJump, &s_BettyJump[4][0]},
    }
};

STATEp sg_BettyJump[] =
{
    &s_BettyJump[0][0],
    &s_BettyJump[1][0],
    &s_BettyJump[2][0],
    &s_BettyJump[3][0],
    &s_BettyJump[4][0]
};

//////////////////////
//
// BETTY Explode
//
//////////////////////

#define BETTY_EXPLODE_RATE 11
#define BETTY_EXPLODE BETTY_R0
ANIMATOR DoSuicide;
ANIMATOR DoDamageTest;
ANIMATOR DoBettySpawnShrap;

STATE s_BettyExplode[] =
{
    {BETTY_EXPLODE + 0, SF_QUICK_CALL,      DoDamageTest, &s_BettyExplode[1]},
    {BETTY_EXPLODE + 0, BETTY_EXPLODE_RATE, DoSuicide, &s_BettyExplode[0]}
};

STATEp sg_BettyExplode[] =
{
    s_BettyExplode,
};

int
SetupBetty(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,BETTY_R0,s_BettyWait[0]);
        u->Health = HEALTH_SKULL;
    }

    ChangeState(SpriteNum, s_BettyWait[0]);
    u->Attrib = &BettyAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_BettyExplode;
    u->Rot = sg_BettyWait;

    u->ID = BETTY_R0;

    EnemyDefaults(SpriteNum, NULL, NULL);
    sp->clipdist = (128+64) >> 2;
    SET(u->Flags, SPR_XFLIP_TOGGLE);
    SET(sp->cstat, CSTAT_SPRITE_YCENTER);

    u->Radius = 400;

    if (SPRITEp_BOS(sp) > u->loz - Z(16))
    {
        sp->z = u->loz + Z(SPRITEp_YOFF(sp));

        u->loz = sp->z;
        // leave 8 pixels above the ground
        sp->z += SPRITEp_SIZE_TOS(sp) - Z(3);;
    }
    else
    {
        u->Counter = RANDOM_P2(2048);
        u->sz = sp->z;
    }

    return 0;
}

int
DoBettyMove(int16_t SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int32_t dax, day, daz;

    dax = MOVEx(sp->xvel, sp->ang);
    day = MOVEy(sp->xvel, sp->ang);
    daz = sp->zvel;

    u->ret = move_missile(SpriteNum, dax, day, daz, Z(16), Z(16), CLIPMASK_MISSILE, ACTORMOVETICS);

    DoFindGroundPoint(SpriteNum);
    return 0;
}

int
DoBettyBeginDeath(int16_t SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int16_t i,num_ord=0;
    //extern short *DamageRadiusBetty;

    // starts the explosion that does the actual damage

    switch (sp->hitag)
    {
    case 1:
        if (sp->lotag) num_ord = sp->lotag;
        else
            num_ord = 2;
        if (num_ord > 3) num_ord = 3;
        for (i=0; i<num_ord; i++)
        {
            sp->ang = NORM_ANGLE(sp->ang+(i*1024));
            InitSpriteChemBomb(SpriteNum);
        }
        break;

    case 2:
        if (sp->lotag) num_ord = sp->lotag;
        else
            num_ord = 5;
        if (num_ord > 10) num_ord = 10;
        for (i=0; i<num_ord; i++)
        {
            sp->ang = NORM_ANGLE(RANDOM_RANGE(2048));
            InitCaltrops(SpriteNum);
        }
        break;

    case 3:
        InitFlashBomb(SpriteNum);
        break;

    case 4:
        if (sp->lotag) num_ord = sp->lotag;
        else
            num_ord = 5;
        if (num_ord > 10) num_ord = 10;
        for (i=0; i<num_ord; i++)
        {
            sp->ang = NORM_ANGLE(sp->ang + (i*(2048/num_ord)));
            InitSpriteGrenade(SpriteNum);
        }
        break;
    default:
        for (i=0; i<5; i++)
        {
            sp->ang = NORM_ANGLE(RANDOM_RANGE(2048));
            InitPhosphorus(SpriteNum);
            SpawnMineExp(SpriteNum);
        }
        break;
    }

    RESET(sp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
    u->RotNum = 0;
    u->Tics = 0;
//    ChangeState(SpriteNum, u->StateEnd);
    //u->State = u->StateStart = u->StateEnd;
    //u->State = u->StateEnd;
    u->ID = BETTY_R0;
    u->Radius = DamageData[DMG_SKULL_EXP].radius; //*DamageRadiusBetty;
    u->OverlapZ = Z(64);
    change_sprite_stat(SpriteNum, STAT_DEAD_ACTOR);
    sp->shade = -40;

    SpawnLittleExp(SpriteNum);
    SetSuicide(SpriteNum);

    //u->spal = sp->pal = PALETTE_RED_LIGHTING;

    return 0;
}

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

    if (sp->xvel)
        DoBettyMove(SpriteNum);
    else
        sp->ang = NORM_ANGLE(sp->ang + (64 * ACTORMOVETICS));

    if (TEST(u->Flags,SPR_JUMPING))
    {
        DoJump(SpriteNum);
    }
    else if (TEST(u->Flags,SPR_FALLING))
    {
        DoFall(SpriteNum);

        // jump/fall type
        if (sp->xvel)
        {
            SWBOOL SpriteOverlapZ(int16_t, int16_t, int);

            int dist,a,b,c;

            DISTANCE(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y, dist, a, b, c);

            if (dist < 1000 &&
                SpriteOverlapZ(SpriteNum, u->tgt_sp - sprite, Z(32)))
            {
                UpdateSinglePlayKills(SpriteNum);
                DoBettyBeginDeath(SpriteNum);
                return 0;
            }

            if ((sp->z > u->loz - Z(36)))
            {
                sp->z = u->loz - Z(36);
                UpdateSinglePlayKills(SpriteNum);
                DoBettyBeginDeath(SpriteNum);
                return 0;
            }
        }
        // non jumping type
        else
        {
            if (u->jump_speed > 200)
            {
                UpdateSinglePlayKills(SpriteNum);
                DoBettyBeginDeath(SpriteNum);
            }
        }

    }
    else
    {
        UpdateSinglePlayKills(SpriteNum);
        DoBettyBeginDeath(SpriteNum);
    }
    return 0;
}

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

    // actor does a sine wave about u->sz - this is the z mid point
#define BETTY_BOB_AMT (Z(16))

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

    return 0;
}

int DoBettySpawnShrap(short SpriteNum)
{
    int SpawnShrap(short, short);
    SpawnShrap(SpriteNum, -1);
    //PlaySpriteSound(SpriteNum,attr_extra1,v3df_none);
    return 0;
}

int DoBettyWait(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    int a,b,c,dist;

    DISTANCE(sp->x, sp->y, u->tgt_sp->x, u->tgt_sp->y, dist, a, b, c);

    DoActorPickClosePlayer(SpriteNum);

    //if (dist < u->active_range)
    //    return(0);

    if ((u->WaitTics -= ACTORMOVETICS) <= 0)
    {
        PlaySound(DIGI_MINEBEEP,&sp->x,&sp->y,&sp->z,v3df_none);
        u->WaitTics = SEC(3);
    }

    // below the floor type
    if (sp->z > u->loz)
    {
        // look for closest player every once in a while
        if (dist < 3500)
        {
            sp->xvel = 0;
            u->jump_speed = -600;
            NewStateGroup(SpriteNum, sg_BettyJump);
            DoBeginJump(SpriteNum);
        }
    }
    else
    // above the floor type
    {
        sp->ang = NORM_ANGLE(sp->ang + (48 * ACTORMOVETICS));

        DoBettyBob(SpriteNum);

        if (dist < 8000)
        {
            sp->ang = getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y);
            sp->xvel = 128 + (RANDOM_P2(256<<8)>>8);
            u->jump_speed = -700;
            NewStateGroup(SpriteNum, sg_BettyJump);
            DoBeginJump(SpriteNum);
        }
    }

    return 0;
}

#include "saveable.h"

static saveable_code saveable_skull_code[] =
{
    SAVE_CODE(SetupSkull),
    SAVE_CODE(DoSkullMove),
    SAVE_CODE(DoSkullBeginDeath),
    SAVE_CODE(DoSkullJump),
    SAVE_CODE(DoSkullBob),
    SAVE_CODE(DoSkullSpawnShrap),
    SAVE_CODE(DoSkullWait),

    SAVE_CODE(SetupBetty),
    SAVE_CODE(DoBettyMove),
    SAVE_CODE(DoBettyBeginDeath),
    SAVE_CODE(DoBettyJump),
    SAVE_CODE(DoBettyBob),
    SAVE_CODE(DoBettySpawnShrap),
    SAVE_CODE(DoBettyWait),
};

static saveable_data saveable_skull_data[] =
{
    SAVE_DATA(s_SkullWait),
    SAVE_DATA(sg_SkullWait),

    SAVE_DATA(SkullAttrib),

    SAVE_DATA(s_SkullRing),
    SAVE_DATA(sg_SkullRing),
    SAVE_DATA(s_SkullJump),
    SAVE_DATA(sg_SkullJump),
    SAVE_DATA(s_SkullExplode),
    SAVE_DATA(sg_SkullExplode),
    SAVE_DATA(s_BettyWait),
    SAVE_DATA(sg_BettyWait),

    SAVE_DATA(BettyAttrib),

    SAVE_DATA(s_BettyJump),
    SAVE_DATA(sg_BettyJump),
    SAVE_DATA(s_BettyExplode),
    SAVE_DATA(sg_BettyExplode),
};

saveable_module saveable_skull =
{
    // code
    saveable_skull_code,
    SIZ(saveable_skull_code),

    // data
    saveable_skull_data,
    SIZ(saveable_skull_data)
};