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 "pal.h"
#include "player.h"
#include "network.h"
#include "actor.h"
#include "track.h"
#include "sprite.h"

int DoHariKariBlood(short SpriteNum);
//int InitActorMoveCloser(short SpriteNum);

DECISION GirlNinjaBattle[] =
{
    {499, InitActorMoveCloser},
    //{509, InitActorAmbientNoise},
    //{710, InitActorRunAway},
    {1024, InitActorAttack}
};

DECISION GirlNinjaOffense[] =
{
    {499, InitActorMoveCloser},
    //{509, InitActorAmbientNoise},
    {1024, InitActorAttack}
};

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

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

DECISION GirlNinjaEvasive[] =
{
    {400,   InitActorDuck}, // 100
//    {300,   InitActorEvade},
//    {800,   InitActorRunAway},
    {1024,  NULL}
};

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

DECISION GirlNinjaCloseRange[] =
{
    {900,   InitActorAttack             },
    {1024,  InitActorReposition         }
};

/*

 !AIC - Collection of decision tables

*/

PERSONALITY GirlNinjaPersonality =
{
    GirlNinjaBattle,
    GirlNinjaOffense,
    GirlNinjaBroadcast,
    GirlNinjaSurprised,
    GirlNinjaEvasive,
    GirlNinjaLostTarget,
    GirlNinjaCloseRange,
    GirlNinjaCloseRange
};

ATTRIBUTE GirlNinjaAttrib =
{
    {120, 140, 160, 190},               // Speeds
    {4, 0, 0, -2},                      // Tic Adjusts
    3,                                 // MaxWeapons;
    {
        DIGI_GIRLNINJAALERT, DIGI_GIRLNINJAALERT, DIGI_STAR,
        DIGI_GIRLNINJAALERTT, DIGI_GIRLNINJASCREAM,0,0,0,0,0
    }
};

//////////////////////
//
// GIRLNINJA RUN
//
//////////////////////

ANIMATOR DoGirlNinjaMove, DoGirlNinjaCrawl, DoStayOnFloor, NullGirlNinja, DoActorJump, DoActorFall, DoActorDebris, DoGirlNinjaHariKari, DoActorSlide;
ANIMATOR InitActorDecide;

#define GIRLNINJA_RATE 18

STATE s_GirlNinjaRun[5][4] =
{

    {
        {GIRLNINJA_RUN_R0 + 0, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[0][1]},
        {GIRLNINJA_RUN_R0 + 1, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[0][2]},
        {GIRLNINJA_RUN_R0 + 2, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[0][3]},
        {GIRLNINJA_RUN_R0 + 3, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[0][0]},
    },
    {
        {GIRLNINJA_RUN_R1 + 0, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[1][1]},
        {GIRLNINJA_RUN_R1 + 1, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[1][2]},
        {GIRLNINJA_RUN_R1 + 2, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[1][3]},
        {GIRLNINJA_RUN_R1 + 3, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[1][0]},
    },
    {
        {GIRLNINJA_RUN_R2 + 0, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[2][1]},
        {GIRLNINJA_RUN_R2 + 1, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[2][2]},
        {GIRLNINJA_RUN_R2 + 2, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[2][3]},
        {GIRLNINJA_RUN_R2 + 3, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[2][0]},
    },
    {
        {GIRLNINJA_RUN_R3 + 0, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[3][1]},
        {GIRLNINJA_RUN_R3 + 1, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[3][2]},
        {GIRLNINJA_RUN_R3 + 2, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[3][3]},
        {GIRLNINJA_RUN_R3 + 3, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[3][0]},
    },
    {
        {GIRLNINJA_RUN_R4 + 0, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[4][1]},
        {GIRLNINJA_RUN_R4 + 1, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[4][2]},
        {GIRLNINJA_RUN_R4 + 2, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[4][3]},
        {GIRLNINJA_RUN_R4 + 3, GIRLNINJA_RATE | SF_TIC_ADJUST, DoGirlNinjaMove, &s_GirlNinjaRun[4][0]},
    },

};

STATEp sg_GirlNinjaRun[] =
{
    s_GirlNinjaRun[0],
    s_GirlNinjaRun[1],
    s_GirlNinjaRun[2],
    s_GirlNinjaRun[3],
    s_GirlNinjaRun[4]
};

//////////////////////
//
// GIRLNINJA STAND
//
//////////////////////

#define GIRLNINJA_STAND_RATE 10

STATE s_GirlNinjaStand[5][1] =
{
    {
        {GIRLNINJA_STAND_R0 + 0, GIRLNINJA_STAND_RATE, DoGirlNinjaMove, &s_GirlNinjaStand[0][0]},
    },
    {
        {GIRLNINJA_STAND_R1 + 0, GIRLNINJA_STAND_RATE, DoGirlNinjaMove, &s_GirlNinjaStand[1][0]},
    },
    {
        {GIRLNINJA_STAND_R2 + 0, GIRLNINJA_STAND_RATE, DoGirlNinjaMove, &s_GirlNinjaStand[2][0]},
    },
    {
        {GIRLNINJA_STAND_R3 + 0, GIRLNINJA_STAND_RATE, DoGirlNinjaMove, &s_GirlNinjaStand[3][0]},
    },
    {
        {GIRLNINJA_STAND_R4 + 0, GIRLNINJA_STAND_RATE, DoGirlNinjaMove, &s_GirlNinjaStand[4][0]},
    },
};

STATEp sg_GirlNinjaStand[] =
{
    s_GirlNinjaStand[0],
    s_GirlNinjaStand[1],
    s_GirlNinjaStand[2],
    s_GirlNinjaStand[3],
    s_GirlNinjaStand[4]
};

//////////////////////
//
// GIRLNINJA RISE
//
//////////////////////

#define GIRLNINJA_RISE_RATE 10

STATE s_GirlNinjaRise[5][3] =
{
    {
        {GIRLNINJA_KNEEL_R0 + 0, GIRLNINJA_RISE_RATE, NullGirlNinja, &s_GirlNinjaRise[0][1]},
        {GIRLNINJA_STAND_R0 + 0, GIRLNINJA_STAND_RATE, NullGirlNinja, &s_GirlNinjaRise[0][2]},
        {0, 0, NULL, (STATEp)sg_GirlNinjaRun},  // JBF: sg_GirlNinjaRun really is supposed to be the
        // pointer to the state group. See StateControl() where
        // it says "if (!u->State->Pic)".
    },
    {
        {GIRLNINJA_KNEEL_R1 + 0, GIRLNINJA_RISE_RATE, NullGirlNinja, &s_GirlNinjaRise[1][1]},
        {GIRLNINJA_STAND_R1 + 0, GIRLNINJA_STAND_RATE, NullGirlNinja, &s_GirlNinjaRise[1][2]},
        {0, 0, NULL, (STATEp)sg_GirlNinjaRun},
    },
    {
        {GIRLNINJA_KNEEL_R2 + 0, GIRLNINJA_RISE_RATE, NullGirlNinja, &s_GirlNinjaRise[2][1]},
        {GIRLNINJA_STAND_R2 + 0, GIRLNINJA_STAND_RATE, NullGirlNinja, &s_GirlNinjaRise[2][2]},
        {0, 0, NULL, (STATEp)sg_GirlNinjaRun},
    },
    {
        {GIRLNINJA_KNEEL_R3 + 0, GIRLNINJA_RISE_RATE, NullGirlNinja, &s_GirlNinjaRise[3][1]},
        {GIRLNINJA_STAND_R3 + 0, GIRLNINJA_STAND_RATE, NullGirlNinja, &s_GirlNinjaRise[3][2]},
        {0, 0, NULL, (STATEp)sg_GirlNinjaRun},
    },
    {
        {GIRLNINJA_KNEEL_R4 + 0, GIRLNINJA_RISE_RATE, NullGirlNinja, &s_GirlNinjaRise[4][1]},
        {GIRLNINJA_STAND_R4 + 0, GIRLNINJA_STAND_RATE, NullGirlNinja, &s_GirlNinjaRise[4][2]},
        {0, 0, NULL, (STATEp)sg_GirlNinjaRun},
    },
};

STATEp sg_GirlNinjaRise[] =
{
    s_GirlNinjaRise[0],
    s_GirlNinjaRise[1],
    s_GirlNinjaRise[2],
    s_GirlNinjaRise[3],
    s_GirlNinjaRise[4]
};

//////////////////////
//
// GIRLNINJA DUCK
//
//////////////////////

#define GIRLNINJA_DUCK_RATE 10
#define GIRLNINJA_CRAWL_RATE 14

STATE s_GirlNinjaDuck[5][2] =
{
    {
        {GIRLNINJA_KNEEL_R0 + 0, GIRLNINJA_DUCK_RATE, NullGirlNinja, &s_GirlNinjaDuck[0][1]},
        {GIRLNINJA_CRAWL_R0 + 0, GIRLNINJA_CRAWL_RATE, DoGirlNinjaMove, &s_GirlNinjaDuck[0][1]},
    },
    {
        {GIRLNINJA_KNEEL_R1 + 0, GIRLNINJA_DUCK_RATE, NullGirlNinja, &s_GirlNinjaDuck[1][1]},
        {GIRLNINJA_CRAWL_R1 + 0, GIRLNINJA_CRAWL_RATE, DoGirlNinjaMove, &s_GirlNinjaDuck[1][1]},
    },
    {
        {GIRLNINJA_KNEEL_R2 + 0, GIRLNINJA_DUCK_RATE, NullGirlNinja, &s_GirlNinjaDuck[2][1]},
        {GIRLNINJA_CRAWL_R2 + 0, GIRLNINJA_CRAWL_RATE, DoGirlNinjaMove, &s_GirlNinjaDuck[2][1]},
    },
    {
        {GIRLNINJA_KNEEL_R3 + 0, GIRLNINJA_DUCK_RATE, NullGirlNinja, &s_GirlNinjaDuck[3][1]},
        {GIRLNINJA_CRAWL_R3 + 0, GIRLNINJA_CRAWL_RATE, DoGirlNinjaMove, &s_GirlNinjaDuck[3][1]},
    },
    {
        {GIRLNINJA_KNEEL_R4 + 0, GIRLNINJA_DUCK_RATE, NullGirlNinja, &s_GirlNinjaDuck[4][1]},
        {GIRLNINJA_CRAWL_R4 + 0, GIRLNINJA_CRAWL_RATE, DoGirlNinjaMove, &s_GirlNinjaDuck[4][1]},
    },
};

STATEp sg_GirlNinjaDuck[] =
{
    s_GirlNinjaDuck[0],
    s_GirlNinjaDuck[1],
    s_GirlNinjaDuck[2],
    s_GirlNinjaDuck[3],
    s_GirlNinjaDuck[4]
};

//////////////////////
//
// GIRLNINJA SIT
//
//////////////////////

STATE s_GirlNinjaSit[5][1] =
{
    {
        {GIRLNINJA_KNEEL_R0 + 0, GIRLNINJA_RISE_RATE, DoGirlNinjaMove, &s_GirlNinjaSit[0][0]},
    },
    {
        {GIRLNINJA_KNEEL_R1 + 0, GIRLNINJA_RISE_RATE, DoGirlNinjaMove, &s_GirlNinjaSit[1][0]},
    },
    {
        {GIRLNINJA_KNEEL_R2 + 0, GIRLNINJA_RISE_RATE, DoGirlNinjaMove, &s_GirlNinjaSit[2][0]},
    },
    {
        {GIRLNINJA_KNEEL_R3 + 0, GIRLNINJA_RISE_RATE, DoGirlNinjaMove, &s_GirlNinjaSit[3][0]},
    },
    {
        {GIRLNINJA_KNEEL_R4 + 0, GIRLNINJA_RISE_RATE, DoGirlNinjaMove, &s_GirlNinjaSit[4][0]},
    },
};

STATEp sg_GirlNinjaSit[] =
{
    s_GirlNinjaSit[0],
    s_GirlNinjaSit[1],
    s_GirlNinjaSit[2],
    s_GirlNinjaSit[3],
    s_GirlNinjaSit[4]
};

//////////////////////
//
// GIRLNINJA JUMP
//
//////////////////////

#define GIRLNINJA_JUMP_RATE 24

STATE s_GirlNinjaJump[5][2] =
{
    {
        {GIRLNINJA_JUMP_R0 + 0, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[0][1]},
        {GIRLNINJA_JUMP_R0 + 1, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[0][1]},
    },
    {
        {GIRLNINJA_JUMP_R1 + 0, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[1][1]},
        {GIRLNINJA_JUMP_R1 + 1, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[1][1]},
    },
    {
        {GIRLNINJA_JUMP_R2 + 0, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[2][1]},
        {GIRLNINJA_JUMP_R2 + 1, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[2][1]},
    },
    {
        {GIRLNINJA_JUMP_R3 + 0, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[3][1]},
        {GIRLNINJA_JUMP_R3 + 1, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[3][1]},
    },
    {
        {GIRLNINJA_JUMP_R4 + 0, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[4][1]},
        {GIRLNINJA_JUMP_R4 + 1, GIRLNINJA_JUMP_RATE, DoGirlNinjaMove, &s_GirlNinjaJump[4][1]},
    },
};

STATEp sg_GirlNinjaJump[] =
{
    s_GirlNinjaJump[0],
    s_GirlNinjaJump[1],
    s_GirlNinjaJump[2],
    s_GirlNinjaJump[3],
    s_GirlNinjaJump[4]
};

//////////////////////
//
// GIRLNINJA FALL
//
//////////////////////

#define GIRLNINJA_FALL_RATE 16

STATE s_GirlNinjaFall[5][2] =
{
    {
        {GIRLNINJA_JUMP_R0 + 1, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[0][1]},
        {GIRLNINJA_JUMP_R0 + 2, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[0][1]},
    },
    {
        {GIRLNINJA_JUMP_R1 + 1, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[1][1]},
        {GIRLNINJA_JUMP_R1 + 2, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[1][1]},
    },
    {
        {GIRLNINJA_JUMP_R2 + 1, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[2][1]},
        {GIRLNINJA_JUMP_R2 + 2, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[2][1]},
    },
    {
        {GIRLNINJA_JUMP_R3 + 1, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[3][1]},
        {GIRLNINJA_JUMP_R3 + 2, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[3][1]},
    },
    {
        {GIRLNINJA_JUMP_R4 + 1, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[4][1]},
        {GIRLNINJA_JUMP_R4 + 2, GIRLNINJA_FALL_RATE, DoGirlNinjaMove, &s_GirlNinjaFall[4][1]},
    },
};

STATEp sg_GirlNinjaFall[] =
{
    s_GirlNinjaFall[0],
    s_GirlNinjaFall[1],
    s_GirlNinjaFall[2],
    s_GirlNinjaFall[3],
    s_GirlNinjaFall[4]
};

//////////////////////
//
// GIRLNINJA PAIN
//
//////////////////////

#define GIRLNINJA_PAIN_RATE 15
ANIMATOR DoGirlNinjaPain;

STATE s_GirlNinjaPain[5][1] =
{
    {
        {GIRLNINJA_PAIN_R0 + 0, GIRLNINJA_PAIN_RATE, DoGirlNinjaPain, &s_GirlNinjaPain[0][0]},
    },
    {
        {GIRLNINJA_PAIN_R1 + 0, GIRLNINJA_PAIN_RATE, DoGirlNinjaPain, &s_GirlNinjaPain[1][0]},
    },
    {
        {GIRLNINJA_PAIN_R2 + 0, GIRLNINJA_PAIN_RATE, DoGirlNinjaPain, &s_GirlNinjaPain[2][0]},
    },
    {
        {GIRLNINJA_PAIN_R3 + 0, GIRLNINJA_PAIN_RATE, DoGirlNinjaPain, &s_GirlNinjaPain[3][0]},
    },
    {
        {GIRLNINJA_PAIN_R4 + 0, GIRLNINJA_PAIN_RATE, DoGirlNinjaPain, &s_GirlNinjaPain[4][0]},
    },
};

STATEp sg_GirlNinjaPain[] =
{
    s_GirlNinjaPain[0],
    s_GirlNinjaPain[1],
    s_GirlNinjaPain[2],
    s_GirlNinjaPain[3],
    s_GirlNinjaPain[4]
};

//////////////////////
//
// GIRLNINJA STICKY
//
//////////////////////

#define GIRLNINJA_STICKY_RATE 32
ANIMATOR InitEnemyMine;

STATE s_GirlNinjaSticky[5][6] =
{
    {
        {GIRLNINJA_THROW_R0 + 0, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[0][1]},
        {GIRLNINJA_THROW_R0 + 0, GIRLNINJA_STICKY_RATE,       NullGirlNinja,          &s_GirlNinjaSticky[0][2]},
        {GIRLNINJA_THROW_R0 + 1, 0 | SF_QUICK_CALL,         InitEnemyMine,      &s_GirlNinjaSticky[0][3]},
        {GIRLNINJA_THROW_R0 + 1, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[0][4]},
        {GIRLNINJA_THROW_R0 + 2, 0 | SF_QUICK_CALL,         InitActorDecide,    &s_GirlNinjaSticky[0][5]},
        {GIRLNINJA_THROW_R0 + 2, GIRLNINJA_STICKY_RATE,       DoGirlNinjaMove,        &s_GirlNinjaSticky[0][5]},
    },
    {
        {GIRLNINJA_THROW_R1 + 0, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[1][1]},
        {GIRLNINJA_THROW_R1 + 0, GIRLNINJA_STICKY_RATE,       NullGirlNinja,          &s_GirlNinjaSticky[1][2]},
        {GIRLNINJA_THROW_R1 + 1, 0 | SF_QUICK_CALL,         InitEnemyMine,      &s_GirlNinjaSticky[1][3]},
        {GIRLNINJA_THROW_R1 + 1, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[1][4]},
        {GIRLNINJA_THROW_R1 + 2, 0 | SF_QUICK_CALL,         InitActorDecide,    &s_GirlNinjaSticky[1][5]},
        {GIRLNINJA_THROW_R1 + 2, GIRLNINJA_STICKY_RATE,       DoGirlNinjaMove,        &s_GirlNinjaSticky[1][5]},
    },
    {
        {GIRLNINJA_THROW_R2 + 0, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[2][1]},
        {GIRLNINJA_THROW_R2 + 0, GIRLNINJA_STICKY_RATE,       NullGirlNinja,          &s_GirlNinjaSticky[2][2]},
        {GIRLNINJA_THROW_R2 + 1, 0 | SF_QUICK_CALL,         InitEnemyMine,      &s_GirlNinjaSticky[2][3]},
        {GIRLNINJA_THROW_R2 + 1, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[2][4]},
        {GIRLNINJA_THROW_R2 + 2, 0 | SF_QUICK_CALL,         InitActorDecide,    &s_GirlNinjaSticky[2][5]},
        {GIRLNINJA_THROW_R2 + 2, GIRLNINJA_STICKY_RATE,       DoGirlNinjaMove,        &s_GirlNinjaSticky[2][5]},
    },
    {
        {GIRLNINJA_THROW_R2 + 0, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[3][1]},
        {GIRLNINJA_THROW_R2 + 0, GIRLNINJA_STICKY_RATE,       NullGirlNinja,          &s_GirlNinjaSticky[3][2]},
        {GIRLNINJA_THROW_R2 + 1, 0 | SF_QUICK_CALL,         InitEnemyMine,      &s_GirlNinjaSticky[3][3]},
        {GIRLNINJA_THROW_R2 + 1, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[3][4]},
        {GIRLNINJA_THROW_R2 + 2, 0 | SF_QUICK_CALL,         InitActorDecide,    &s_GirlNinjaSticky[3][5]},
        {GIRLNINJA_THROW_R2 + 2, GIRLNINJA_STICKY_RATE,       DoGirlNinjaMove,        &s_GirlNinjaSticky[3][5]},
    },
    {
        {GIRLNINJA_THROW_R2 + 0, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[4][1]},
        {GIRLNINJA_THROW_R2 + 0, GIRLNINJA_STICKY_RATE,       NullGirlNinja,          &s_GirlNinjaSticky[4][2]},
        {GIRLNINJA_THROW_R2 + 1, 0 | SF_QUICK_CALL,         InitEnemyMine,      &s_GirlNinjaSticky[4][3]},
        {GIRLNINJA_THROW_R2 + 1, GIRLNINJA_STICKY_RATE * 2,     NullGirlNinja,            &s_GirlNinjaSticky[4][4]},
        {GIRLNINJA_THROW_R2 + 2, 0 | SF_QUICK_CALL,         InitActorDecide,    &s_GirlNinjaSticky[4][5]},
        {GIRLNINJA_THROW_R2 + 2, GIRLNINJA_STICKY_RATE,       DoGirlNinjaMove,        &s_GirlNinjaSticky[4][5]},
    },
};

STATEp sg_GirlNinjaSticky[] =
{
    s_GirlNinjaSticky[0],
    s_GirlNinjaSticky[1],
    s_GirlNinjaSticky[2],
    s_GirlNinjaSticky[3],
    s_GirlNinjaSticky[4]
};

//////////////////////
//
// GIRLNINJA CROSSBOW
//
//////////////////////

#define GIRLNINJA_CROSSBOW_RATE 14
ANIMATOR InitEnemyCrossbow;

STATE s_GirlNinjaCrossbow[5][5] =
{
    {
        {GIRLNINJA_FIRE_R0 + 0, GIRLNINJA_CROSSBOW_RATE * 2, NullGirlNinja, &s_GirlNinjaCrossbow[0][1]},
        {GIRLNINJA_FIRE_R0 + 1, 0 | SF_QUICK_CALL, InitEnemyCrossbow, &s_GirlNinjaCrossbow[0][2]},
        {GIRLNINJA_FIRE_R0 + 1, GIRLNINJA_CROSSBOW_RATE, NullGirlNinja, &s_GirlNinjaCrossbow[0][3]},
        {GIRLNINJA_FIRE_R0 + 1, 0 | SF_QUICK_CALL, InitActorDecide, &s_GirlNinjaCrossbow[0][4]},
        {GIRLNINJA_FIRE_R0 + 1, GIRLNINJA_CROSSBOW_RATE, DoGirlNinjaMove, &s_GirlNinjaCrossbow[0][4]},
    },
    {
        {GIRLNINJA_FIRE_R1 + 0, GIRLNINJA_CROSSBOW_RATE * 2, NullGirlNinja, &s_GirlNinjaCrossbow[1][1]},
        {GIRLNINJA_FIRE_R1 + 1, 0 | SF_QUICK_CALL, InitEnemyCrossbow, &s_GirlNinjaCrossbow[1][2]},
        {GIRLNINJA_FIRE_R1 + 1, GIRLNINJA_CROSSBOW_RATE, NullGirlNinja, &s_GirlNinjaCrossbow[1][3]},
        {GIRLNINJA_FIRE_R1 + 1, 0 | SF_QUICK_CALL, InitActorDecide, &s_GirlNinjaCrossbow[1][4]},
        {GIRLNINJA_FIRE_R1 + 1, GIRLNINJA_CROSSBOW_RATE, DoGirlNinjaMove, &s_GirlNinjaCrossbow[1][4]},
    },
    {
        {GIRLNINJA_FIRE_R2 + 0, GIRLNINJA_CROSSBOW_RATE * 2, NullGirlNinja, &s_GirlNinjaCrossbow[2][1]},
        {GIRLNINJA_FIRE_R2 + 1, 0 | SF_QUICK_CALL, InitEnemyCrossbow, &s_GirlNinjaCrossbow[2][2]},
        {GIRLNINJA_FIRE_R2 + 1, GIRLNINJA_CROSSBOW_RATE, NullGirlNinja, &s_GirlNinjaCrossbow[2][3]},
        {GIRLNINJA_FIRE_R2 + 1, 0 | SF_QUICK_CALL, InitActorDecide, &s_GirlNinjaCrossbow[2][4]},
        {GIRLNINJA_FIRE_R2 + 1, GIRLNINJA_CROSSBOW_RATE, DoGirlNinjaMove, &s_GirlNinjaCrossbow[2][4]},
    },
    {
        {GIRLNINJA_FIRE_R3 + 0, GIRLNINJA_CROSSBOW_RATE * 2, NullGirlNinja, &s_GirlNinjaCrossbow[3][1]},
        {GIRLNINJA_FIRE_R3 + 1, 0 | SF_QUICK_CALL, InitEnemyCrossbow, &s_GirlNinjaCrossbow[3][2]},
        {GIRLNINJA_FIRE_R3 + 1, GIRLNINJA_CROSSBOW_RATE, NullGirlNinja, &s_GirlNinjaCrossbow[3][3]},
        {GIRLNINJA_FIRE_R3 + 1, 0 | SF_QUICK_CALL, InitActorDecide, &s_GirlNinjaCrossbow[3][4]},
        {GIRLNINJA_FIRE_R3 + 1, GIRLNINJA_CROSSBOW_RATE, DoGirlNinjaMove, &s_GirlNinjaCrossbow[3][4]},
    },
    {
        {GIRLNINJA_FIRE_R4 + 0, GIRLNINJA_CROSSBOW_RATE * 2, NullGirlNinja, &s_GirlNinjaCrossbow[4][1]},
        {GIRLNINJA_FIRE_R4 + 1, 0 | SF_QUICK_CALL, InitEnemyCrossbow, &s_GirlNinjaCrossbow[4][2]},
        {GIRLNINJA_FIRE_R4 + 1, GIRLNINJA_CROSSBOW_RATE, NullGirlNinja, &s_GirlNinjaCrossbow[4][3]},
        {GIRLNINJA_FIRE_R4 + 1, 0 | SF_QUICK_CALL, InitActorDecide, &s_GirlNinjaCrossbow[4][4]},
        {GIRLNINJA_FIRE_R4 + 1, GIRLNINJA_CROSSBOW_RATE, DoGirlNinjaMove, &s_GirlNinjaCrossbow[4][4]},
    },
};

STATEp sg_GirlNinjaCrossbow[] =
{
    s_GirlNinjaCrossbow[0],
    s_GirlNinjaCrossbow[1],
    s_GirlNinjaCrossbow[2],
    s_GirlNinjaCrossbow[3],
    s_GirlNinjaCrossbow[4]
};

//////////////////////
//
// GIRLNINJA DIE
//
//////////////////////

#define GIRLNINJA_DIE_RATE 30
ANIMATOR DoGirlNinjaSpecial;

STATE s_GirlNinjaDie[] =
{
    {GIRLNINJA_DIE + 0, GIRLNINJA_DIE_RATE*2, NullGirlNinja, &s_GirlNinjaDie[1]},
    {GIRLNINJA_DIE + 1, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[2]},
    {GIRLNINJA_DIE + 2, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[3]},
    {GIRLNINJA_DIE + 3, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[4]},
    {GIRLNINJA_DIE + 4, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[5]},
    {GIRLNINJA_DIE + 5, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[6]},
    {GIRLNINJA_DIE + 6, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[7]},
    {GIRLNINJA_DIE + 6, SF_QUICK_CALL, DoGirlNinjaSpecial, &s_GirlNinjaDie[8]},
    {GIRLNINJA_DIE + 7, GIRLNINJA_DIE_RATE, NullGirlNinja, &s_GirlNinjaDie[9]},
    {GIRLNINJA_DIE + 8, SF_QUICK_CALL, QueueFloorBlood, &s_GirlNinjaDie[10]},
    {GIRLNINJA_DIE + 8, GIRLNINJA_DIE_RATE, DoActorDebris, &s_GirlNinjaDie[10]},
};

STATE s_GirlNinjaDead[] =
{
    {GIRLNINJA_DIE + 6, GIRLNINJA_DIE_RATE, DoActorDebris, &s_GirlNinjaDead[1]},
    {GIRLNINJA_DIE + 7, SF_QUICK_CALL, DoGirlNinjaSpecial, &s_GirlNinjaDead[2]},
    {GIRLNINJA_DIE + 7, GIRLNINJA_DIE_RATE, DoActorDebris, &s_GirlNinjaDead[3]},
    {GIRLNINJA_DIE + 8, SF_QUICK_CALL, QueueFloorBlood,&s_GirlNinjaDead[4]},
    {GIRLNINJA_DIE + 8, GIRLNINJA_DIE_RATE, DoActorDebris, &s_GirlNinjaDead[4]},
};

STATE s_GirlNinjaDeathJump[] =
{
    {GIRLNINJA_DIE + 0, GIRLNINJA_DIE_RATE, DoActorDeathMove, &s_GirlNinjaDeathJump[1]},
    {GIRLNINJA_DIE + 1, GIRLNINJA_DIE_RATE, DoActorDeathMove, &s_GirlNinjaDeathJump[2]},
    {GIRLNINJA_DIE + 2, GIRLNINJA_DIE_RATE, DoActorDeathMove, &s_GirlNinjaDeathJump[2]},
};

STATE s_GirlNinjaDeathFall[] =
{
    {GIRLNINJA_DIE + 3, GIRLNINJA_DIE_RATE, DoActorDeathMove, &s_GirlNinjaDeathFall[1]},
    {GIRLNINJA_DIE + 4, GIRLNINJA_DIE_RATE, DoActorDeathMove, &s_GirlNinjaDeathFall[1]},
};

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

STATEp sg_GirlNinjaDie[] =
{
    s_GirlNinjaDie
};

STATEp sg_GirlNinjaDead[] =
{
    s_GirlNinjaDead
};

STATEp sg_GirlNinjaDeathJump[] =
{
    s_GirlNinjaDeathJump
};

STATEp sg_GirlNinjaDeathFall[] =
{
    s_GirlNinjaDeathFall
};

/*

 !AIC - Collection of states that connect action to states

*/

ACTOR_ACTION_SET GirlNinjaActionSet =
{
    sg_GirlNinjaStand,
    sg_GirlNinjaRun,
    sg_GirlNinjaJump,
    sg_GirlNinjaFall,
    NULL,
    NULL,
    NULL,
    sg_GirlNinjaRise,
    sg_GirlNinjaSit,
    NULL,
    NULL,
    sg_GirlNinjaPain,
    sg_GirlNinjaDie,
    NULL,
    sg_GirlNinjaDead,
    sg_GirlNinjaDeathJump,
    sg_GirlNinjaDeathFall,
    {sg_GirlNinjaCrossbow, sg_GirlNinjaSticky},
    {800, 1024},
    {sg_GirlNinjaCrossbow, sg_GirlNinjaSticky},
    {800, 1024},
    {NULL},
    sg_GirlNinjaDuck,
    NULL
};

int
SetupGirlNinja(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, GIRLNINJA_RUN_R0, s_GirlNinjaRun[0]);
        u->Health = 100;
    }

    u->StateEnd = s_GirlNinjaDie;
    u->Rot = sg_GirlNinjaRun;
    sp->xrepeat = 51;
    sp->yrepeat = 43;

    u->Attrib = &GirlNinjaAttrib;
    sp->pal = u->spal = 26;
    EnemyDefaults(SpriteNum, &GirlNinjaActionSet, &GirlNinjaPersonality);

    ChangeState(SpriteNum, s_GirlNinjaRun[0]);
    DoActorSetSpeed(SpriteNum, NORM_SPEED);

    u->Radius = 280;
    RESET(u->Flags, SPR_XFLIP_TOGGLE);

    return 0;
}

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

    // jumping and falling
    if (TEST(u->Flags, SPR_JUMPING | SPR_FALLING) && !TEST(u->Flags, SPR_CLIMBING))
    {
        if (TEST(u->Flags, SPR_JUMPING))
            DoActorJump(SpriteNum);
        else if (TEST(u->Flags, SPR_FALLING))
            DoActorFall(SpriteNum);
    }

    // sliding
    if (TEST(u->Flags, SPR_SLIDING) && !TEST(u->Flags, SPR_CLIMBING))
        DoActorSlide(SpriteNum);

    // !AIC - do track or call current action function - such as DoActorMoveCloser()
    if (u->track >= 0)
        ActorFollowTrack(SpriteNum, ACTORMOVETICS);
    else
    {
        (*u->ActorActionFunc)(SpriteNum);
    }

    // stay on floor unless doing certain things
    if (!TEST(u->Flags, SPR_JUMPING | SPR_FALLING | SPR_CLIMBING))
    {
        KeepActorOnFloor(SpriteNum);
    }

    // take damage from environment
    DoActorSectorDamage(SpriteNum);

    return 0;
}

int
GirlNinjaJumpActionFunc(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    int nx, ny;

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

    // if cannot move the sprite
    if (!move_actor(SpriteNum, nx, ny, 0L))
    {
        return 0;
    }

    if (!TEST(u->Flags, SPR_JUMPING|SPR_FALLING))
    {
        InitActorDecide(SpriteNum);
    }

    return 0;
}

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

    if (u->WaitTics > 0) u->WaitTics -= ACTORMOVETICS;

    if (TEST(u->Flags, SPR_SLIDING) && !TEST(u->Flags, SPR_CLIMBING) && !TEST(u->Flags, SPR_JUMPING|SPR_FALLING))
        DoActorSlide(SpriteNum);

    if (!TEST(u->Flags, SPR_CLIMBING) && !TEST(u->Flags, SPR_JUMPING|SPR_FALLING))
        KeepActorOnFloor(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

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

    NullGirlNinja(SpriteNum);

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

    return 0;
}

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

    if (u->spal == PALETTE_PLAYER5)
    {
        RESET(sp->cstat,CSTAT_SPRITE_TRANSLUCENT);
        sp->hitag = 0;
        sp->shade = -10;
    }

    return 0;
}

#include "saveable.h"

static saveable_code saveable_girlninj_code[] =
{
    SAVE_CODE(SetupGirlNinja),
    SAVE_CODE(DoGirlNinjaMove),
    SAVE_CODE(GirlNinjaJumpActionFunc),
    SAVE_CODE(NullGirlNinja),
    SAVE_CODE(DoGirlNinjaPain),
    SAVE_CODE(DoGirlNinjaSpecial),
};

static saveable_data saveable_girlninj_data[] =
{
    SAVE_DATA(GirlNinjaBattle),
    SAVE_DATA(GirlNinjaOffense),
    SAVE_DATA(GirlNinjaBroadcast),
    SAVE_DATA(GirlNinjaSurprised),
    SAVE_DATA(GirlNinjaEvasive),
    SAVE_DATA(GirlNinjaLostTarget),
    SAVE_DATA(GirlNinjaCloseRange),

    SAVE_DATA(GirlNinjaPersonality),

    SAVE_DATA(GirlNinjaAttrib),

    SAVE_DATA(s_GirlNinjaRun),
    SAVE_DATA(sg_GirlNinjaRun),
    SAVE_DATA(s_GirlNinjaStand),
    SAVE_DATA(sg_GirlNinjaStand),
    SAVE_DATA(s_GirlNinjaRise),
    SAVE_DATA(sg_GirlNinjaRise),
    SAVE_DATA(s_GirlNinjaDuck),
    SAVE_DATA(sg_GirlNinjaDuck),
    SAVE_DATA(s_GirlNinjaSit),
    SAVE_DATA(sg_GirlNinjaSit),
    SAVE_DATA(s_GirlNinjaJump),
    SAVE_DATA(sg_GirlNinjaJump),
    SAVE_DATA(s_GirlNinjaFall),
    SAVE_DATA(sg_GirlNinjaFall),
    SAVE_DATA(s_GirlNinjaPain),
    SAVE_DATA(sg_GirlNinjaPain),
    SAVE_DATA(s_GirlNinjaSticky),
    SAVE_DATA(sg_GirlNinjaSticky),
    SAVE_DATA(s_GirlNinjaCrossbow),
    SAVE_DATA(sg_GirlNinjaCrossbow),
    SAVE_DATA(s_GirlNinjaDie),
    SAVE_DATA(s_GirlNinjaDead),
    SAVE_DATA(s_GirlNinjaDeathJump),
    SAVE_DATA(s_GirlNinjaDeathFall),
    SAVE_DATA(sg_GirlNinjaDie),
    SAVE_DATA(sg_GirlNinjaDead),
    SAVE_DATA(sg_GirlNinjaDeathJump),
    SAVE_DATA(sg_GirlNinjaDeathFall),

    SAVE_DATA(GirlNinjaActionSet),
};

saveable_module saveable_girlninj =
{
    // code
    saveable_girlninj_code,
    SIZ(saveable_girlninj_code),

    // data
    saveable_girlninj_data,
    SIZ(saveable_girlninj_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 "tags.h"
#include "game.h"
#include "ai.h"
#include "actor.h"
#include "track.h"

#define GORO_PAIN_R0 GORO_STAND_R0
#define GORO_PAIN_R1 GORO_STAND_R1
#define GORO_PAIN_R2 GORO_STAND_R2
#define GORO_PAIN_R3 GORO_STAND_R3
#define GORO_PAIN_R4 GORO_STAND_R4

DECISION GoroBattle[] =
{
    {697,   InitActorMoveCloser         },
    {700,   InitActorAmbientNoise         },
    {1024,  InitActorAttack             }
};

DECISION GoroOffense[] =
{
    {797,   InitActorMoveCloser         },
    {800,   InitActorAttackNoise        },
    {1024,  InitActorAttack             }
};

DECISION GoroBroadcast[] =
{
    {3,    InitActorAmbientNoise          },
    {1024,  InitActorDecide            }
};

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

DECISION GoroEvasive[] =
{
    {10,    InitActorEvade            },
    {1024,  InitActorMoveCloser       }
};

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

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

PERSONALITY GoroPersonality =
{
    GoroBattle,
    GoroOffense,
    GoroBroadcast,
    GoroSurprised,
    GoroEvasive,
    GoroLostTarget,
    GoroCloseRange,
    GoroCloseRange
};

ATTRIBUTE GoroAttrib =
{
    {160, 180, 200, 230},               // Speeds
    {3, 0, -2, -3},                     // Tic Adjusts
    3,                                 // MaxWeapons;
    {
        DIGI_GRDAMBIENT, DIGI_GRDALERT, 0,
        DIGI_GRDPAIN, DIGI_GRDSCREAM, DIGI_GRDSWINGAXE,
        DIGI_GRDFIREBALL,0,0,0
    }
};

//////////////////////
//
// GORO RUN
//
//////////////////////

#define GORO_RUN_RATE 18

ANIMATOR DoGoroMove,NullGoro,DoActorDebris,NullAnimator,InitEnemyFireball;

STATE s_GoroRun[5][4] =
{
    {
        {GORO_RUN_R0 + 0, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[0][1]},
        {GORO_RUN_R0 + 1, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[0][2]},
        {GORO_RUN_R0 + 2, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[0][3]},
        {GORO_RUN_R0 + 3, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[0][0]},
    },
    {
        {GORO_RUN_R1 + 0, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[1][1]},
        {GORO_RUN_R1 + 1, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[1][2]},
        {GORO_RUN_R1 + 2, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[1][3]},
        {GORO_RUN_R1 + 3, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[1][0]},
    },
    {
        {GORO_RUN_R2 + 0, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[2][1]},
        {GORO_RUN_R2 + 1, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[2][2]},
        {GORO_RUN_R2 + 2, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[2][3]},
        {GORO_RUN_R2 + 3, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[2][0]},
    },
    {
        {GORO_RUN_R3 + 0, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[3][1]},
        {GORO_RUN_R3 + 1, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[3][2]},
        {GORO_RUN_R3 + 2, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[3][3]},
        {GORO_RUN_R3 + 3, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[3][0]},
    },
    {
        {GORO_RUN_R4 + 0, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[4][1]},
        {GORO_RUN_R4 + 1, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[4][2]},
        {GORO_RUN_R4 + 2, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[4][3]},
        {GORO_RUN_R4 + 3, GORO_RUN_RATE|SF_TIC_ADJUST, DoGoroMove, &s_GoroRun[4][0]},
    }
};

STATEp sg_GoroRun[] =
{
    &s_GoroRun[0][0],
    &s_GoroRun[1][0],
    &s_GoroRun[2][0],
    &s_GoroRun[3][0],
    &s_GoroRun[4][0]
};

//////////////////////
//
// GORO CHOP
//
//////////////////////

#define GORO_CHOP_RATE 14
ANIMATOR InitActorDecide;
ANIMATOR InitGoroChop;

STATE s_GoroChop[5][7] =
{
    {
        {GORO_CHOP_R0 + 0, GORO_CHOP_RATE, NullGoro, &s_GoroChop[0][1]},
        {GORO_CHOP_R0 + 1, GORO_CHOP_RATE, NullGoro, &s_GoroChop[0][2]},
        {GORO_CHOP_R0 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[0][3]},
        {GORO_CHOP_R0 + 2, 0|SF_QUICK_CALL, InitGoroChop, &s_GoroChop[0][4]},
        {GORO_CHOP_R0 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[0][5]},
        {GORO_CHOP_R0 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_GoroChop[0][6]},
        {GORO_CHOP_R0 + 2, GORO_CHOP_RATE, DoGoroMove, &s_GoroChop[0][6]},
    },
    {
        {GORO_CHOP_R1 + 0, GORO_CHOP_RATE, NullGoro, &s_GoroChop[1][1]},
        {GORO_CHOP_R1 + 1, GORO_CHOP_RATE, NullGoro, &s_GoroChop[1][2]},
        {GORO_CHOP_R1 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[1][3]},
        {GORO_CHOP_R1 + 2, 0|SF_QUICK_CALL, InitGoroChop, &s_GoroChop[1][4]},
        {GORO_CHOP_R1 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[1][5]},
        {GORO_CHOP_R1 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_GoroChop[1][6]},
        {GORO_CHOP_R1 + 2, GORO_CHOP_RATE, DoGoroMove, &s_GoroChop[1][6]},
    },
    {
        {GORO_CHOP_R2 + 0, GORO_CHOP_RATE, NullGoro, &s_GoroChop[2][1]},
        {GORO_CHOP_R2 + 1, GORO_CHOP_RATE, NullGoro, &s_GoroChop[2][2]},
        {GORO_CHOP_R2 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[2][3]},
        {GORO_CHOP_R2 + 2, 0|SF_QUICK_CALL, InitGoroChop, &s_GoroChop[2][4]},
        {GORO_CHOP_R2 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[2][5]},
        {GORO_CHOP_R2 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_GoroChop[2][6]},
        {GORO_CHOP_R2 + 2, GORO_CHOP_RATE, DoGoroMove, &s_GoroChop[2][6]},
    },
    {
        {GORO_CHOP_R3 + 0, GORO_CHOP_RATE, NullGoro, &s_GoroChop[3][1]},
        {GORO_CHOP_R3 + 1, GORO_CHOP_RATE, NullGoro, &s_GoroChop[3][2]},
        {GORO_CHOP_R3 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[3][3]},
        {GORO_CHOP_R3 + 2, 0|SF_QUICK_CALL, InitGoroChop, &s_GoroChop[3][4]},
        {GORO_CHOP_R3 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[3][5]},
        {GORO_CHOP_R3 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_GoroChop[3][6]},
        {GORO_CHOP_R3 + 2, GORO_CHOP_RATE, DoGoroMove, &s_GoroChop[3][6]},
    },
    {
        {GORO_CHOP_R4 + 0, GORO_CHOP_RATE, NullGoro, &s_GoroChop[4][1]},
        {GORO_CHOP_R4 + 1, GORO_CHOP_RATE, NullGoro, &s_GoroChop[4][2]},
        {GORO_CHOP_R4 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[4][3]},
        {GORO_CHOP_R4 + 2, 0|SF_QUICK_CALL, InitGoroChop, &s_GoroChop[4][4]},
        {GORO_CHOP_R4 + 2, GORO_CHOP_RATE, NullGoro, &s_GoroChop[4][5]},
        {GORO_CHOP_R4 + 2, 0|SF_QUICK_CALL, InitActorDecide, &s_GoroChop[4][6]},
        {GORO_CHOP_R4 + 2, GORO_CHOP_RATE, DoGoroMove, &s_GoroChop[4][6]},
    }
};

STATEp sg_GoroChop[] =
{
    &s_GoroChop[0][0],
    &s_GoroChop[1][0],
    &s_GoroChop[2][0],
    &s_GoroChop[3][0],
    &s_GoroChop[4][0]
};

//////////////////////
//
// GORO SPELL
//
//////////////////////

#define GORO_SPELL_RATE 6
#define GORO_SPELL_PAUSE 30

STATE s_GoroSpell[5][10] =
{
    {
        {GORO_SPELL_R0 + 0, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[0][1]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[0][2]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[0][3]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[0][4]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[0][5]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[0][6]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[0][7]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[0][8]},
        {GORO_SPELL_R0 + 1, 0|SF_QUICK_CALL, InitActorDecide,   &s_GoroSpell[0][9]},
        {GORO_SPELL_R0 + 1, GORO_SPELL_RATE, DoGoroMove,        &s_GoroSpell[0][9]},
    },
    {
        {GORO_SPELL_R1 + 0, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[1][1]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[1][2]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[1][3]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[1][4]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[1][5]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[1][6]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[1][7]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[1][8]},
        {GORO_SPELL_R1 + 1, 0|SF_QUICK_CALL, InitActorDecide,   &s_GoroSpell[1][9]},
        {GORO_SPELL_R1 + 1, GORO_SPELL_RATE, DoGoroMove,        &s_GoroSpell[1][9]},
    },
    {
        {GORO_SPELL_R2 + 0, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[2][1]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[2][2]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[2][3]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[2][4]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[2][5]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[2][6]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[2][7]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[2][8]},
        {GORO_SPELL_R2 + 1, 0|SF_QUICK_CALL, InitActorDecide,   &s_GoroSpell[2][9]},
        {GORO_SPELL_R2 + 1, GORO_SPELL_RATE, DoGoroMove,        &s_GoroSpell[2][9]},
    },
    {
        {GORO_SPELL_R3 + 0, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[3][1]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[3][2]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[3][3]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[3][4]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[3][5]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[3][6]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[3][7]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[3][8]},
        {GORO_SPELL_R3 + 1, 0|SF_QUICK_CALL, InitActorDecide,   &s_GoroSpell[3][9]},
        {GORO_SPELL_R3 + 1, GORO_SPELL_RATE, DoGoroMove,        &s_GoroSpell[3][9]},
    },
    {
        {GORO_SPELL_R4 + 0, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[4][1]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[4][2]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[4][3]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[4][4]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[4][5]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_RATE, NullGoro,      &s_GoroSpell[4][6]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_RATE, InitEnemyFireball, &s_GoroSpell[4][7]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_PAUSE, NullGoro,      &s_GoroSpell[4][8]},
        {GORO_SPELL_R4 + 1, 0|SF_QUICK_CALL, InitActorDecide,   &s_GoroSpell[4][9]},
        {GORO_SPELL_R4 + 1, GORO_SPELL_RATE, DoGoroMove,        &s_GoroSpell[4][9]},
    }
};

STATEp sg_GoroSpell[] =
{
    &s_GoroSpell[0][0],
    &s_GoroSpell[1][0],
    &s_GoroSpell[2][0],
    &s_GoroSpell[3][0],
    &s_GoroSpell[4][0]
};

//////////////////////
//
// GORO STAND
//
//////////////////////

#define GORO_STAND_RATE 12

STATE s_GoroStand[5][1] =
{
    {
        {GORO_STAND_R0 + 0, GORO_STAND_RATE, DoGoroMove, &s_GoroStand[0][0]},
    },
    {
        {GORO_STAND_R1 + 0, GORO_STAND_RATE, DoGoroMove, &s_GoroStand[1][0]},
    },
    {
        {GORO_STAND_R2 + 0, GORO_STAND_RATE, DoGoroMove, &s_GoroStand[2][0]},
    },
    {
        {GORO_STAND_R3 + 0, GORO_STAND_RATE, DoGoroMove, &s_GoroStand[3][0]},
    },
    {
        {GORO_STAND_R4 + 0, GORO_STAND_RATE, DoGoroMove, &s_GoroStand[4][0]},
    },
};

STATEp sg_GoroStand[] =
{
    s_GoroStand[0],
    s_GoroStand[1],
    s_GoroStand[2],
    s_GoroStand[3],
    s_GoroStand[4]
};

//////////////////////
//
// GORO PAIN
//
//////////////////////

#define GORO_PAIN_RATE 12
ANIMATOR DoGoroPain;

STATE s_GoroPain[5][1] =
{
    {
        {GORO_PAIN_R0 + 0, GORO_PAIN_RATE, DoGoroPain, &s_GoroPain[0][0]},
    },
    {
        {GORO_PAIN_R1 + 0, GORO_PAIN_RATE, DoGoroPain, &s_GoroPain[1][0]},
    },
    {
        {GORO_PAIN_R2 + 0, GORO_PAIN_RATE, DoGoroPain, &s_GoroPain[2][0]},
    },
    {
        {GORO_PAIN_R3 + 0, GORO_PAIN_RATE, DoGoroPain, &s_GoroPain[3][0]},
    },
    {
        {GORO_PAIN_R4 + 0, GORO_PAIN_RATE, DoGoroPain, &s_GoroPain[4][0]},
    },
};

STATEp sg_GoroPain[] =
{
    s_GoroPain[0],
    s_GoroPain[1],
    s_GoroPain[2],
    s_GoroPain[3],
    s_GoroPain[4]
};

//////////////////////
//
// GORO DIE
//
//////////////////////

#define GORO_DIE_RATE 16

STATE s_GoroDie[] =
{
    {GORO_DIE + 0, GORO_DIE_RATE, NullGoro, &s_GoroDie[1]},
    {GORO_DIE + 1, GORO_DIE_RATE, NullGoro, &s_GoroDie[2]},
    {GORO_DIE + 2, GORO_DIE_RATE, NullGoro, &s_GoroDie[3]},
    {GORO_DIE + 3, GORO_DIE_RATE, NullGoro, &s_GoroDie[4]},
    {GORO_DIE + 4, GORO_DIE_RATE, NullGoro, &s_GoroDie[5]},
    {GORO_DIE + 5, GORO_DIE_RATE, NullGoro, &s_GoroDie[6]},
    {GORO_DIE + 6, GORO_DIE_RATE, NullGoro, &s_GoroDie[7]},
    {GORO_DIE + 7, GORO_DIE_RATE, NullGoro, &s_GoroDie[8]},
    {GORO_DIE + 8, GORO_DIE_RATE, NullGoro, &s_GoroDie[9]},
    {GORO_DEAD, SF_QUICK_CALL, QueueFloorBlood, &s_GoroDie[10]},
    {GORO_DEAD,    GORO_DIE_RATE, DoActorDebris, &s_GoroDie[10]},
};

STATE s_GoroDead[] =
{
    {GORO_DEAD, GORO_DIE_RATE, DoActorDebris, &s_GoroDead[0]},
};

STATEp sg_GoroDie[] =
{
    s_GoroDie
};

STATEp sg_GoroDead[] =
{
    s_GoroDead
};

/*
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 GoroActionSet =
{
    sg_GoroStand,
    sg_GoroRun,
    NULL, //sg_GoroJump,
    NULL, //sg_GoroFall,
    NULL, //sg_GoroCrawl,
    NULL, //sg_GoroSwim,
    NULL, //sg_GoroFly,
    NULL, //sg_GoroRise,
    NULL, //sg_GoroSit,
    NULL, //sg_GoroLook,
    NULL, //climb
    sg_GoroPain,
    sg_GoroDie,
    NULL, //sg_GoroHariKari,
    sg_GoroDead,
    NULL, //sg_GoroDeathJump,
    NULL, //sg_GoroDeathFall,
    {sg_GoroChop},
    {1024},
    {sg_GoroSpell},
    {1024},
    {NULL,NULL},
    NULL,
    NULL
};

int
SetupGoro(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,GORO_RUN_R0,s_GoroRun[0]);
        u->Health = HEALTH_GORO;
    }

    ChangeState(SpriteNum, s_GoroRun[0]);
    u->Attrib = &GoroAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_GoroDie;
    u->Rot = sg_GoroRun;

    EnemyDefaults(SpriteNum, &GoroActionSet, &GoroPersonality);
    sp->clipdist = 512 >> 2;
    SET(u->Flags, SPR_XFLIP_TOGGLE);

    return 0;
}

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

    ASSERT(SpriteNum >= 0);

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

    KeepActorOnFloor(SpriteNum);

    DoActorSectorDamage(SpriteNum);
    return 0;
}

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

    ASSERT(SpriteNum >= 0);

    NullGoro(SpriteNum);

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

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

    ASSERT(SpriteNum >= 0);

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

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

    ASSERT(User[SpriteNum]);

    KeepActorOnFloor(SpriteNum);

    DoActorSectorDamage(SpriteNum);
    return 0;
}

#include "saveable.h"

static saveable_code saveable_goro_code[] =
{
    SAVE_CODE(SetupGoro),
    SAVE_CODE(NullGoro),
    SAVE_CODE(DoGoroPain),
    SAVE_CODE(DoGoroMove),
};

static saveable_data saveable_goro_data[] =
{
    SAVE_DATA(GoroBattle),
    SAVE_DATA(GoroOffense),
    SAVE_DATA(GoroBroadcast),
    SAVE_DATA(GoroSurprised),
    SAVE_DATA(GoroEvasive),
    SAVE_DATA(GoroLostTarget),
    SAVE_DATA(GoroCloseRange),

    SAVE_DATA(GoroPersonality),

    SAVE_DATA(GoroAttrib),

    SAVE_DATA(s_GoroRun),
    SAVE_DATA(sg_GoroRun),
    SAVE_DATA(s_GoroChop),
    SAVE_DATA(sg_GoroChop),
    SAVE_DATA(s_GoroSpell),
    SAVE_DATA(sg_GoroSpell),
    SAVE_DATA(s_GoroStand),
    SAVE_DATA(sg_GoroStand),
    SAVE_DATA(s_GoroPain),
    SAVE_DATA(sg_GoroPain),
    SAVE_DATA(s_GoroDie),
    SAVE_DATA(s_GoroDead),
    SAVE_DATA(sg_GoroDie),
    SAVE_DATA(sg_GoroDead),

    SAVE_DATA(GoroActionSet),
};

saveable_module saveable_goro =
{
    // code
    saveable_goro_code,
    SIZ(saveable_goro_code),

    // data
    saveable_goro_data,
    SIZ(saveable_goro_data)
};

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
 Copyright (C) 2007 Jonathon Fowler <jf@jonof.id.au>

 This file is part of JFShadowWarrior

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

#include "build.h"
#include "baselayer.h"

#include "scriptfile.h"
#include "cache1d.h"
#include "crc32.h"

#include "grpscan.h"
#include "common_game.h"

#define SWREG12_CRC 0x7545319Fu
#define SWWD_CRC 0xA9AAA7B7u
#define SWTD_CRC 0xA1A65BE8u

static void process_tdragongrp(int32_t crcval)
{
    krename(crcval, 53, "TDCUSTOM.TXT");
}

static internalgrpfile grpfiles[] =
{
    { "Shadow Warrior",               SWREG12_CRC, 47536148, 0, 0, 0, nullptr },
    { "Shadow Warrior (Europe)",      0xD4A1E153u, 47536148, 0, 0, 0, nullptr }, // only difference: corrupt tile #28
    { "Shadow Warrior (UK)",          0x3EE68767u, 47536148, 0, 0, 0, nullptr }, // only differences: corrupt tiles #2811, #4349
    { "Shadow Warrior (Censored)",    0x1A8776D2u, 47537951, 0, 0, 0, nullptr }, // only difference: added CREDITS.TXT
    { "Shadow Warrior Shareware 1.0", 0xDAA6BECEu, 25702245, GAMEFLAG_SHAREWARE, 0, 0, nullptr },
    { "Shadow Warrior Shareware 1.1", 0xF21A6B35u, 25833456, GAMEFLAG_SHAREWARE, 0, 0, nullptr },
    { "Shadow Warrior Shareware 1.2", 0x08A7FA1Fu, 26056769, GAMEFLAG_SHAREWARE, 0, 0, nullptr },
    { "Shadow Warrior Mac Demo",      0x4227F535u, 26056769, GAMEFLAG_SHAREWARE, 0, 0, nullptr },
    { "Wanton Destruction",           SWWD_CRC, 48698128, GAMEFLAG_SWWD, GRP_HAS_DEPENDENCY, SWREG12_CRC, nullptr },
    { "Twin Dragon",                  SWTD_CRC, 12499012, GAMEFLAG_SWTD, GRP_HAS_DEPENDENCY, SWREG12_CRC, process_tdragongrp },
    { "Twin Dragon",                  0xACC8DCDEu, 6235578, GAMEFLAG_SWTD, GRP_HAS_DEPENDENCY, SWREG12_CRC, nullptr },
    { "Twin Dragon",                  0xB5B71277u, 6236287, GAMEFLAG_SWTD, GRP_HAS_DEPENDENCY, SWREG12_CRC, nullptr }, // commonly available repack
};
grpfile *foundgrps = NULL;

#define GRPCACHEFILE "grpfiles.cache"
static struct grpcache
{
    struct grpcache *next;
    char name[BMAX_PATH+1];
    int size;
    int mtime;
    uint32_t crcval;
} *grpcache = NULL, *usedgrpcache = NULL;

static int LoadGroupsCache(void)
{
    struct grpcache *fg;

    int fsize, fmtime, fcrcval;
    char *fname;

    scriptfile *script;

    script = scriptfile_fromfile(GRPCACHEFILE);
    if (!script) return -1;

    while (!scriptfile_eof(script))
    {
        if (scriptfile_getstring(script, &fname)) break;    // filename
        if (scriptfile_getnumber(script, &fsize)) break;    // filesize
        if (scriptfile_getnumber(script, &fmtime)) break;   // modification time
        if (scriptfile_getnumber(script, &fcrcval)) break;  // crc checksum

        fg = (struct grpcache*)Xcalloc(1, sizeof(struct grpcache));
        fg->next = grpcache;
        grpcache = fg;

        strncpy(fg->name, fname, BMAX_PATH);
        fg->size = fsize;
        fg->mtime = fmtime;
        fg->crcval = fcrcval;
    }

    scriptfile_close(script);
    return 0;
}

static void FreeGroupsCache(void)
{
    struct grpcache *fg;

    while (grpcache)
    {
        fg = grpcache->next;
        Xfree(grpcache);
        grpcache = fg;
    }
}

static void RemoveGroup(grpfile_t *igrp)
{
    for (grpfile_t *prev = NULL, *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp == igrp)
        {
            if (grp == foundgrps)
                foundgrps = grp->next;
            else
                prev->next = grp->next;

            Xfree(grp->filename);
            Xfree(grp);

            return;
        }

        prev = grp;
    }
}

grpfile_t * FindGroup(uint32_t crcval)
{
    grpfile_t *grp;

    for (grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->crcval == crcval)
            return grp;
    }

    return NULL;
}

static struct internalgrpfile const * FindGrpInfo(uint32_t crcval, int32_t size)
{
    for (struct internalgrpfile const & grptype : grpfiles)
    {
        if (grptype.crcval == crcval && grptype.size == size)
            return &grptype;
    }

    return NULL;
}

static void ProcessGroups(BUILDVFS_FIND_REC *srch, native_t maxsize)
{
    BUILDVFS_FIND_REC *sidx;
    struct grpcache *fg, *fgg;
    char *fn;
    struct Bstat st;

    for (sidx = srch; sidx; sidx = sidx->next)
    {
        for (fg = grpcache; fg; fg = fg->next)
        {
            if (!Bstrcmp(fg->name, sidx->name)) break;
        }

        if (fg)
        {
            if (findfrompath(sidx->name, &fn)) continue;    // failed to resolve the filename
            if (Bstat(fn, &st)) { Xfree(fn); continue; } // failed to stat the file
            Xfree(fn);
            if (fg->size == st.st_size && fg->mtime == st.st_mtime)
            {
                struct internalgrpfile const * const grptype = FindGrpInfo(fg->crcval, fg->size);
                if (grptype)
                {
                    auto grp = (struct grpfile *)Xcalloc(1, sizeof(struct grpfile));
                    grp->filename = Xstrdup(sidx->name);
                    grp->type = grptype;
                    grp->next = foundgrps;
                    foundgrps = grp;
                }

                fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
                strcpy(fgg->name, fg->name);
                fgg->size = fg->size;
                fgg->mtime = fg->mtime;
                fgg->crcval = fg->crcval;
                fgg->next = usedgrpcache;
                usedgrpcache = fgg;
                continue;
            }
        }

        {
            int b, fh;
            unsigned int crcval = 0;
            unsigned char buf[16*512];

            fh = openfrompath(sidx->name, BO_RDONLY|BO_BINARY, BS_IREAD);
            if (fh < 0) continue;
            if (fstat(fh, &st)) continue;
            if (st.st_size > maxsize) continue;

            buildprintf(" Checksumming %s...", sidx->name);
            do
            {
                b = read(fh, buf, sizeof(buf));
                if (b > 0) crcval = Bcrc32(buf, b, crcval);
            }
            while (b == sizeof(buf));
            close(fh);
            buildputs(" Done\n");

            struct internalgrpfile const * const grptype = FindGrpInfo(crcval, st.st_size);
            if (grptype)
            {
                auto grp = (struct grpfile *)Xcalloc(1, sizeof(struct grpfile));
                grp->filename = Xstrdup(sidx->name);
                grp->type = grptype;
                grp->next = foundgrps;
                foundgrps = grp;
            }

            fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
            strncpy(fgg->name, sidx->name, BMAX_PATH);
            fgg->size = st.st_size;
            fgg->mtime = st.st_mtime;
            fgg->crcval = crcval;
            fgg->next = usedgrpcache;
            usedgrpcache = fgg;
        }
    }
}

int ScanGroups(void)
{
    struct grpcache *fg, *fgg;

    buildputs("Scanning for game data...\n");

    LoadGroupsCache();

    native_t maxsize = 0;
    for (struct internalgrpfile const & grptype : grpfiles)
    {
        if (maxsize < grptype.size)
            maxsize = grptype.size;
    }

    static char const * extensions[] =
    {
        "*.grp",
        "*.ssi",
        "*.dat",
        "*.zip",
    };

    for (char const * extension : extensions)
    {
        BUILDVFS_FIND_REC *srch = klistpath("/", extension, BUILDVFS_FIND_FILE);
        ProcessGroups(srch, maxsize);
        klistfree(srch);
    }

    FreeGroupsCache();

    for (grpfile_t *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->flags & GRP_HAS_DEPENDENCY)
        {
            if (FindGroup(grp->type->dependency) == NULL) // couldn't find dependency
            {
                RemoveGroup(grp);
                grp = foundgrps;
                // start from the beginning so we can remove anything that depended on this grp
                continue;
            }
        }
    }

    if (usedgrpcache)
    {
        FILE *fp;
        fp = fopen(GRPCACHEFILE, "wt");
        if (fp)
        {
            for (fg = usedgrpcache; fg; fg=fgg)
            {
                fgg = fg->next;
                fprintf(fp, "\"%s\" %d %d %d\n", fg->name, fg->size, fg->mtime, fg->crcval);
                Xfree(fg);
            }
            fclose(fp);
        }
    }

    return 0;
}

void FreeGroups(void)
{
    struct grpfile *fg;

    while (foundgrps)
    {
        fg = foundgrps->next;
        Xfree(foundgrps->filename);
        Xfree(foundgrps);
        foundgrps = fg;
    }
}

void SW_LoadAddon()
{
#ifndef EDUKE32_STANDALONE
    uint32_t crc;

    switch (g_addonNum)
    {
    case 0:
        crc = SWREG12_CRC;
        break;
    case 1:
        crc = SWWD_CRC;
        break;
    case 2:
        crc = SWTD_CRC;
        break;
    default:
        return;
    }

    grpfile_t const * const grp = FindGroup(crc);

    if (grp)
        g_selectedGrp = grp;
#endif
}


code:
//-------------------------------------------------------------------------
/*
 Copyright (C) 2007 Jonathon Fowler <jf@jonof.id.au>

 This file is part of JFShadowWarrior

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

#ifndef grpscan_h__
#define grpscan_h__

// List of internally-known GRP files
struct internalgrpfile
{
    const char *name;
    uint32_t crcval;
    int size;
    uint16_t gameflags;
    uint16_t flags;
    uint32_t dependency;
    void (*postprocessing)(int32_t);
};

enum
{
    GRP_HAS_DEPENDENCY = 1u<<0u,
};

typedef struct grpfile
{
    char * filename;
    struct internalgrpfile const * type;
    struct grpfile *next;
} grpfile_t;

extern grpfile *foundgrps;

extern grpfile_t * FindGroup(uint32_t crcval);

int ScanGroups(void);
void FreeGroups(void);

void SW_LoadAddon(void);

#endif


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

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

ANIMATOR DoHornetCircle, InitHornetCircle;

DECISION HornetBattle[] =
{
    {50,    InitHornetCircle          },
    {798,   InitActorMoveCloser         },
    {800,   InitActorAlertNoise        },
    {1024,  InitActorRunAway            }
};

DECISION HornetOffense[] =
{
    {1022,  InitActorMoveCloser        },
    {1024,  InitActorAlertNoise        }
};

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

DECISION HornetSurprised[] =
{
    {100,   InitHornetCircle           },
    {701,   InitActorMoveCloser         },
    {1024,  InitActorDecide             }
};

DECISION HornetEvasive[] =
{
    {20,     InitHornetCircle          },
    {1024,   NULL                      },
};

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

DECISION HornetCloseRange[] =
{
    {900,   InitActorMoveCloser         },
    {1024,  InitActorReposition         }
};

ANIMATOR InitHornetSting;

DECISION HornetTouchTarget[] =
{
    {500,   InitHornetCircle            },
    {1024,  InitHornetSting             }
};

PERSONALITY HornetPersonality =
{
    HornetBattle,
    HornetOffense,
    HornetBroadcast,
    HornetSurprised,
    HornetEvasive,
    HornetLostTarget,
    HornetCloseRange,
    HornetTouchTarget
};

ATTRIBUTE HornetAttrib =
{
    {300, 350, 375, 400}, // Speeds
    {0,  0, 0,  0}, // Tic Adjusts
    0,      //MaxWeapons;
    {
        0, 0, DIGI_HORNETSTING, DIGI_HORNETSTING, DIGI_HORNETDEATH,
        0,0,0,0,0
    }
};

//////////////////////
//
// HORNET RUN
//////////////////////

#define HORNET_RUN_RATE 7

ANIMATOR DoHornetMove,NullHornet,DoStayOnFloor, DoActorDebris, NullHornet, DoHornetBirth;

STATE s_HornetRun[5][2] =
{
    {
        {HORNET_RUN_R0 + 0, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[0][1]},
        {HORNET_RUN_R0 + 1, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[0][0]},
    },
    {
        {HORNET_RUN_R1 + 0, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[1][1]},
        {HORNET_RUN_R1 + 1, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[1][0]},
    },
    {
        {HORNET_RUN_R2 + 0, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[2][1]},
        {HORNET_RUN_R2 + 1, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[2][0]},
    },
    {
        {HORNET_RUN_R3 + 0, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[3][1]},
        {HORNET_RUN_R3 + 1, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[3][0]},
    },
    {
        {HORNET_RUN_R4 + 0, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[4][1]},
        {HORNET_RUN_R4 + 1, HORNET_RUN_RATE, DoHornetMove, &s_HornetRun[4][0]},
    }
};

STATEp sg_HornetRun[] =
{
    &s_HornetRun[0][0],
    &s_HornetRun[1][0],
    &s_HornetRun[2][0],
    &s_HornetRun[3][0],
    &s_HornetRun[4][0]
};

//////////////////////
//
// HORNET STAND
//
//////////////////////

#define HORNET_STAND_RATE (HORNET_RUN_RATE + 5)

STATE s_HornetStand[5][2] =
{
    {
        {HORNET_RUN_R0 + 0, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[0][1]},
        {HORNET_RUN_R0 + 1, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[0][0]}
    },
    {
        {HORNET_RUN_R1 + 0, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[1][1]},
        {HORNET_RUN_R1 + 1, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[1][0]}
    },
    {
        {HORNET_RUN_R2 + 0, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[2][1]},
        {HORNET_RUN_R2 + 1, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[2][0]}
    },
    {
        {HORNET_RUN_R3 + 0, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[3][1]},
        {HORNET_RUN_R3 + 1, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[3][0]}
    },
    {
        {HORNET_RUN_R4 + 0, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[4][1]},
        {HORNET_RUN_R4 + 1, HORNET_STAND_RATE, DoHornetMove, &s_HornetStand[4][0]}
    }
};

STATEp sg_HornetStand[] =
{
    &s_HornetStand[0][0],
    &s_HornetStand[1][0],
    &s_HornetStand[2][0],
    &s_HornetStand[3][0],
    &s_HornetStand[4][0]
};

//////////////////////
//
// HORNET DIE
//
//////////////////////

#define HORNET_DIE_RATE 20
ANIMATOR DoHornetDeath;
STATE s_HornetDie[] =
{
#if 0
    {HORNET_DIE + 0, HORNET_DIE_RATE, NullHornet, &s_HornetDie[1]},
    {HORNET_DEAD,    HORNET_DIE_RATE, DoActorDebris, &s_HornetDie[1]},
#else
    {HORNET_DIE + 0, HORNET_DIE_RATE, DoHornetDeath, &s_HornetDie[0]},
#endif
};

STATEp sg_HornetDie[] =
{
    s_HornetDie
};

STATE s_HornetDead[] =
{
    {HORNET_DEAD, HORNET_DIE_RATE, DoActorDebris, &s_HornetDead[0]},
};

STATEp sg_HornetDead[] =
{
    s_HornetDead
};

/*
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 HornetActionSet =
{
    sg_HornetStand,
    sg_HornetRun,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL, //climb
    NULL, //pain
    sg_HornetDie,
    NULL,
    sg_HornetDead,
    NULL,
    NULL,
    {NULL},
    {0},
    {NULL},
    {0},
    {NULL},
    NULL,
    NULL
};

int DoHornetMatchPlayerZ(short SpriteNum);

int
SetupHornet(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,HORNET_RUN_R0,s_HornetRun[0]);
        u->Health = HEALTH_HORNET;
    }

    ChangeState(SpriteNum, s_HornetRun[0]);
    u->Attrib = &HornetAttrib;
    DoActorSetSpeed(SpriteNum, NORM_SPEED);
    u->StateEnd = s_HornetDie;
    u->Rot = sg_HornetRun;

    EnemyDefaults(SpriteNum, &HornetActionSet, &HornetPersonality);

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

    sp->clipdist = (100) >> 2;
    u->floor_dist = Z(16);
    u->ceiling_dist = Z(16);

    u->sz = sp->z;

    sp->xrepeat = 37;
    sp->yrepeat = 32;

    // Special looping buzz sound attached to each hornet spawned
    PlaySound(DIGI_HORNETBUZZ,&sp->x,&sp->y,&sp->z,v3df_follow|v3df_init);
    Set3DSoundOwner(SpriteNum);

    return 0;
}

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

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

    DoHornetMatchPlayerZ(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

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

    long bound;

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

    //zdiff = (SPRITEp_LOWER(tsp) - Z(8)) - u->sz;
    zdiff = (SPRITEp_MID(tsp)) - u->sz;

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

#define HORNET_BOB_AMT (Z(16))

    // 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 - HORNET_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 + HORNET_BOB_AMT;

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

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

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

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

    return 0;
}

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

    u->ActorActionFunc = DoHornetCircle;

    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 = 200 + RANDOM_P2(128);
    if (labs(u->sz - u->hiz) < labs(u->sz - u->loz))
        u->jump_speed = -u->jump_speed;

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

    (*u->ActorActionFunc)(SpriteNum);

    return 0;
}

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

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

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

    if (!move_actor(SpriteNum, nx, ny, 0L))
    {
        //ActorMoveHitReact(SpriteNum);

        // try moving in the opposite direction
        u->Counter2 = -u->Counter2;
        sp->ang = NORM_ANGLE(sp->ang + 1024);
        nx = sp->xvel * (long) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
        ny = sp->xvel * (long) 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 + HORNET_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
DoHornetDeath(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    long nx, ny;

    if (TEST(u->Flags, SPR_FALLING))
    {
        u->loz = u->zclip;
        DoFall(SpriteNum);
    }
    else
    {
        RESET(sp->cstat, CSTAT_SPRITE_YCENTER);
        u->jump_speed = 0;
        u->floor_dist = 0;
        DoBeginFall(SpriteNum);
        DoFindGroundPoint(SpriteNum);
        u->zclip = u->loz;
    }

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

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

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

    // 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);
        DeleteNoSoundOwner(SpriteNum);
        return 0;
    }

    return 0;
}

// Hornets can swarm around other hornets or whatever is tagged as swarm target
int DoCheckSwarm(short SpriteNum)
{
    short i,nexti;
    SPRITEp sp = &sprite[SpriteNum], tsp;
    USERp u = User[SpriteNum], tu;
    long dist, pdist, a,b,c;
    PLAYERp pp;

    if (!MoveSkip8) return 0;     // Don't over check

    if (!u->tgt_sp) return 0;

    // Who's the closest meat!?
    DoActorPickClosePlayer(SpriteNum);

    if (User[u->tgt_sp - sprite]->PlayerP)
    {
        pp = User[u->tgt_sp - sprite]->PlayerP;
        DISTANCE(sp->x, sp->y, pp->posx, pp->posy, pdist, a, b, c);
    }
    else
        return 0;

    // all enemys
    TRAVERSE_SPRITE_STAT(headspritestat[STAT_ENEMY], i, nexti)
    {
        tsp = &sprite[i];
        tu = User[i];

        if (!tu) continue;

        if (tsp->hitag != TAG_SWARMSPOT || tsp->lotag != 2) continue;

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

        if (dist < pdist && u->ID == tu->ID) // Only flock to your own kind
        {
            u->tgt_sp = tsp; // Set target to swarm center
        }
    }

    return TRUE;

}

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

    // Check for swarming
    // lotag of 1 = Swarm around lotags of 2
    // lotag of 0 is normal
    if (sp->hitag == TAG_SWARMSPOT && sp->lotag == 1)
        DoCheckSwarm(SpriteNum);

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

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

    DoHornetMatchPlayerZ(SpriteNum);

    DoActorSectorDamage(SpriteNum);

    return 0;
}

#include "saveable.h"

static saveable_code saveable_hornet_code[] =
{
    SAVE_CODE(SetupHornet),
    SAVE_CODE(NullHornet),
    SAVE_CODE(DoHornetMatchPlayerZ),
    SAVE_CODE(InitHornetCircle),
    SAVE_CODE(DoHornetCircle),
    SAVE_CODE(DoHornetDeath),
    SAVE_CODE(DoCheckSwarm),
    SAVE_CODE(DoHornetMove),
};

static saveable_data saveable_hornet_data[] =
{
    SAVE_DATA(HornetBattle),
    SAVE_DATA(HornetOffense),
    SAVE_DATA(HornetBroadcast),
    SAVE_DATA(HornetSurprised),
    SAVE_DATA(HornetEvasive),
    SAVE_DATA(HornetLostTarget),
    SAVE_DATA(HornetCloseRange),
    SAVE_DATA(HornetTouchTarget),

    SAVE_DATA(HornetPersonality),

    SAVE_DATA(HornetAttrib),

    SAVE_DATA(s_HornetRun),
    SAVE_DATA(sg_HornetRun),
    SAVE_DATA(s_HornetStand),
    SAVE_DATA(sg_HornetStand),
    SAVE_DATA(s_HornetDie),
    SAVE_DATA(sg_HornetDie),
    SAVE_DATA(s_HornetDead),
    SAVE_DATA(sg_HornetDead),

    SAVE_DATA(HornetActionSet),
};

saveable_module saveable_hornet =
{
    // code
    saveable_hornet_code,
    SIZ(saveable_hornet_code),

    // data
    saveable_hornet_data,
    SIZ(saveable_hornet_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 "compat.h"
#include "pragmas.h"

#include "interp.h"

int numinterpolations = 0;
int oldipos[MAXINTERPOLATIONS];
int bakipos[MAXINTERPOLATIONS];
int *curipos[MAXINTERPOLATIONS];

void setinterpolation(int *posptr)
{
    int i;

    if (numinterpolations >= MAXINTERPOLATIONS)
        return;

    for (i = numinterpolations - 1; i >= 0; i--)
    {
        if (curipos[i] == posptr)
            return;
    }

    curipos[numinterpolations] = posptr;
    oldipos[numinterpolations] = *posptr;
    numinterpolations++;
}

void stopinterpolation(int *posptr)
{
    int i;

    for (i = numinterpolations - 1; i >= 0; i--)
    {
        if (curipos[i] == posptr)
        {
            numinterpolations--;
            oldipos[i] = oldipos[numinterpolations];
            bakipos[i] = bakipos[numinterpolations];
            curipos[i] = curipos[numinterpolations];
        }
    }
}

void updateinterpolations(void)                  // Stick at beginning of domovethings
{
    int i;

    for (i = numinterpolations - 1; i >= 0; i--)
        oldipos[i] = *curipos[i];
}

// must call restore for every do interpolations
// make sure you don't exit
void dointerpolations(int smoothratio)                      // Stick at beginning of drawscreen
{
    int i, j, odelta, ndelta;

    ndelta = 0;
    j = 0;

    for (i = numinterpolations - 1; i >= 0; i--)
    {
        bakipos[i] = *curipos[i];

        odelta = ndelta;
        ndelta = (*curipos[i]) - oldipos[i];

        if (odelta != ndelta)
            j = mulscale16(ndelta, smoothratio);

        *curipos[i] = oldipos[i] + j;
    }
}

void restoreinterpolations(void)                 // Stick at end of drawscreen
{
    int i;

    for (i = numinterpolations - 1; i >= 0; i--)
        *curipos[i] = bakipos[i];
}


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#ifndef INTERP_H

#define INTERP_H

#include "build.h"

#define SHORT_MAXINTERPOLATIONS 256
extern short short_numinterpolations;
extern short short_oldipos[SHORT_MAXINTERPOLATIONS];
extern short short_bakipos[SHORT_MAXINTERPOLATIONS];
extern short *short_curipos[SHORT_MAXINTERPOLATIONS];

#define MAXINTERPOLATIONS MAXSPRITES
extern int numinterpolations;
extern int oldipos[MAXINTERPOLATIONS];
extern int bakipos[MAXINTERPOLATIONS];
extern int *curipos[MAXINTERPOLATIONS];

void setinterpolation(int *posptr);
void stopinterpolation(int *posptr);
void updateinterpolations(void);
void dointerpolations(int smoothratio);
void restoreinterpolations(void);

#endif


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#include "compat.h"
#include "pragmas.h"

#include "interp.h"

#define SHORT_MAXINTERPOLATIONS 256
short short_numinterpolations = 0;
short short_oldipos[SHORT_MAXINTERPOLATIONS];
short short_bakipos[SHORT_MAXINTERPOLATIONS];
short *short_curipos[SHORT_MAXINTERPOLATIONS];

void short_setinterpolation(short *posptr)
{
    int i;

    if (short_numinterpolations >= SHORT_MAXINTERPOLATIONS)
        return;

    for (i = short_numinterpolations - 1; i >= 0; i--)
    {
        if (short_curipos[i] == posptr)
            return;
    }

    short_curipos[short_numinterpolations] = posptr;
    short_oldipos[short_numinterpolations] = *posptr;
    short_numinterpolations++;
}

void short_stopinterpolation(short *posptr)
{
    int i;

    for (i = short_numinterpolations - 1; i >= 0; i--)
    {
        if (short_curipos[i] == posptr)
        {
            short_numinterpolations--;
            short_oldipos[i] = short_oldipos[short_numinterpolations];
            short_bakipos[i] = short_bakipos[short_numinterpolations];
            short_curipos[i] = short_curipos[short_numinterpolations];
        }
    }
}

void short_updateinterpolations(void)                  // Stick at beginning of domovethings
{
    int i;

    for (i = short_numinterpolations - 1; i >= 0; i--)
        short_oldipos[i] = *short_curipos[i];
}

// must call restore for every do interpolations
// make sure you don't exit
void short_dointerpolations(int smoothratio)                      // Stick at beginning of drawscreen
{
    int i, j, odelta, ndelta;

    ndelta = 0;
    j = 0;

    for (i = short_numinterpolations - 1; i >= 0; i--)
    {
        short_bakipos[i] = *short_curipos[i];

        odelta = ndelta;
        ndelta = (*short_curipos[i]) - short_oldipos[i];

        if (odelta != ndelta)
            j = mulscale16(ndelta, smoothratio);

        *short_curipos[i] = short_oldipos[i] + j;
    }
}

void short_restoreinterpolations(void)                 // Stick at end of drawscreen
{
    int i;

    for (i = short_numinterpolations - 1; i >= 0; i--)
        *short_curipos[i] = short_bakipos[i];
}


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#include "compat.h"
#include "pragmas.h"

#include "game.h"
#include "interp.h"
#include "interpso.h"
#include "names2.h"

#define SO_MAXINTERPOLATIONS MAXINTERPOLATIONS

static struct so_interp
{
    struct interp_data
    {
        void *curipos;
        int32_t oldipos;
        int32_t bakipos;
        int32_t lastipos;
        int32_t lastoldipos;
        int32_t lastangdiff;
        int32_t spriteofang;
    } data[SO_MAXINTERPOLATIONS];

    int32_t numinterpolations;
    int32_t tic, lasttic;
    SWBOOL hasvator;
} so_interpdata[MAX_SECTOR_OBJECTS];

static void so_setpointinterpolation(so_interp *interp, int32_t *posptr)
{
    int32_t i;
    if (interp->numinterpolations >= SO_MAXINTERPOLATIONS)
        return;

    for (i = 0; i < interp->numinterpolations; i++)
        if (interp->data[i].curipos == posptr)
            return;

    so_interp::interp_data *data = &interp->data[interp->numinterpolations++];

    data->curipos = posptr;
    data->oldipos = *posptr;
    data->lastipos = *posptr;
    data->lastoldipos = *posptr;
    data->spriteofang = -1;
}

static void so_setspriteanginterpolation(so_interp *interp, int16_t *posptr, int32_t spritenum)
{
    int32_t i;
    if (interp->numinterpolations >= SO_MAXINTERPOLATIONS)
        return;

    for (i = 0; i < interp->numinterpolations; i++)
        if (interp->data[i].curipos == posptr)
            return;

    so_interp::interp_data *data = &interp->data[interp->numinterpolations++];

    data->curipos = posptr;
    data->oldipos = *posptr;
    data->lastipos = *posptr;
    data->lastoldipos = *posptr;
    data->lastangdiff = 0;
    data->spriteofang = spritenum;
}

// Covers points and angles altogether
static void so_stopdatainterpolation(so_interp *interp, void *posptr)
{
    int32_t i;

    for (i = 0; i < interp->numinterpolations; i++)
        if (interp->data[i].curipos == posptr)
            break;

    if (i == interp->numinterpolations)
        return;

    interp->data[i] = interp->data[--(interp->numinterpolations)];
}

void so_addinterpolation(SECTOR_OBJECTp sop)
{
    SECTORp *sectp;
    int32_t startwall, endwall;
    int32_t i;

    so_interp *interp = &so_interpdata[sop - SectorObject];
    interp->numinterpolations = 0;
    interp->hasvator = FALSE;

    for (sectp = sop->sectp; *sectp; sectp++)
    {
        startwall = (*sectp)->wallptr;
        endwall = startwall + (*sectp)->wallnum - 1;

        for (i = startwall; i <= endwall; i++)
        {
            int32_t nextwall = wall[i].nextwall;

            so_setpointinterpolation(interp, &wall[i].x);
            so_setpointinterpolation(interp, &wall[i].y);

            if (nextwall >= 0)
            {
                so_setpointinterpolation(interp, &wall[wall[nextwall].point2].x);
                so_setpointinterpolation(interp, &wall[wall[nextwall].point2].y);
            }
        }

        for (SPRITES_OF_SECT(*sectp - sector, i))
            if (sprite[i].statnum == STAT_VATOR && SP_TAG1(sprite+i) == SECT_VATOR)
                break;
        interp->hasvator |= (i >= 0);
    }

    if (!interp->hasvator)
        for (sectp = sop->sectp; *sectp; sectp++)
        {
            so_setpointinterpolation(interp, &(*sectp)->ceilingz);
            so_setpointinterpolation(interp, &(*sectp)->floorz);
        }

    // interpolate midpoint, for aiming at a remote controlled SO
    so_setpointinterpolation(interp, &sop->xmid);
    so_setpointinterpolation(interp, &sop->ymid);
    so_setpointinterpolation(interp, &sop->zmid);

    interp->tic = 0;
    interp->lasttic = synctics;
}

void so_setspriteinterpolation(SECTOR_OBJECTp sop, spritetype *sp)
{
    so_interp *interp = &so_interpdata[sop - SectorObject];

    so_setpointinterpolation(interp, &sp->x);
    so_setpointinterpolation(interp, &sp->y);
    if (!interp->hasvator)
        so_setpointinterpolation(interp, &sp->z);
    so_setspriteanginterpolation(interp, &sp->ang, sp - sprite);
}

void so_stopspriteinterpolation(SECTOR_OBJECTp sop, spritetype *sp)
{
    so_interp *interp = &so_interpdata[sop - SectorObject];

    so_stopdatainterpolation(interp, &sp->x);
    so_stopdatainterpolation(interp, &sp->y);
    if (!interp->hasvator)
        so_stopdatainterpolation(interp, &sp->z);
    so_stopdatainterpolation(interp, &sp->ang);
}

void so_setinterpolationtics(SECTOR_OBJECTp sop, int16_t locktics)
{
    so_interp *interp = &so_interpdata[sop - SectorObject];

    interp->tic = 0;
    interp->lasttic = locktics;
}

void so_updateinterpolations(void) // Stick at beginning of domovethings
{
    int32_t i;
    SECTOR_OBJECTp sop;
    so_interp *interp;
    so_interp::interp_data *data;
    SWBOOL interpolating = gs.InterpolateSO && !CommEnabled; // If changing from menu

    for (sop = SectorObject, interp = so_interpdata;
         sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++, interp++)
    {
        if (SO_EMPTY(sop))
            continue;
        if (interp->tic < interp->lasttic)
            interp->tic += synctics;
        for (i = 0, data = interp->data; i < interp->numinterpolations; i++, data++)
        {
            if (data->spriteofang >= 0)
            {
                USERp u = User[data->spriteofang];
                if (u)
                    u->oangdiff = 0;
                if (!interpolating)
                    data->lastangdiff = 0;
                data->oldipos = *(int16_t *)(data->curipos);
            }
            else
                data->oldipos = *(int32_t *)(data->curipos);

            if (!interpolating)
                data->lastipos = data->lastoldipos = data->oldipos;
        }
    }
}

// must call restore for every do interpolations
// make sure you don't exit
void so_dointerpolations(int32_t smoothratio)                      // Stick at beginning of drawscreen
{
    int32_t i, delta;
    SECTOR_OBJECTp sop;
    so_interp *interp;
    so_interp::interp_data *data;

    // Set the bakipos values separately, in case a point is shared.
    // Also set lastipos if there's been an actual change in a point.
    for (sop = SectorObject, interp = so_interpdata;
         sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++, interp++)
    {
        if (SO_EMPTY(sop))
            continue;

        for (i = 0; i < interp->numinterpolations; i++)
            interp->data[i].bakipos = (interp->data[i].spriteofang >= 0) ?
                                      *(int16_t *)(interp->data[i].curipos) :
                                      *(int32_t *)(interp->data[i].curipos);

        if (interp->tic == 0) // Only if the SO has just moved
        {
            for (i = 0, data = interp->data; i < interp->numinterpolations; i++, data++)
            {
                data->lastipos = data->bakipos;
                data->lastoldipos = data->oldipos;
                if (data->spriteofang >= 0)
                {
                    USERp u = User[data->spriteofang];
                    data->lastangdiff = u ? u->oangdiff : 0;
                }
            }
        }
    }

    for (sop = SectorObject, interp = so_interpdata;
         sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++, interp++)
    {
        if (SO_EMPTY(sop))
            continue;

        // Check if interpolation has been explicitly disabled
        if (interp->lasttic == 0)
            continue;

        // Unfortunately, interpolating over less samples doesn't work well
        // in multiplayer. We also skip any sector object not
        // remotely controlled by some player.
        if (CommEnabled &&
            ((interp->lasttic != synctics) ||
             !(sop->controller) ||
             ((Player[screenpeek].sop_control == sop) &&
               !Player[screenpeek].sop_remote)))
            continue;

        int32_t ratio = smoothratio * synctics + 65536 * interp->tic;
        ratio /= interp->lasttic;
        ratio = (interp->tic == interp->lasttic) ? 65536 : ratio;

        for (i = 0, data = interp->data; i < interp->numinterpolations; i++, data++)
        {
            // Hack for jittery coolies in level 1's train.
            // Based in idea on code from draw.cpp:analyzesprites.
            // TODO: It could be better. In particular, it could be better
            // to conditionally disable the interpolation from analyzesprites
            // instead, using TSPRITE info if possible.
            if (((uintptr_t)(data->curipos) >= (uintptr_t)sprite) &&
                ((uintptr_t)(data->curipos) < (uintptr_t)(sprite + Numsprites)))
            {
                int32_t sprnum = ((char *)data->curipos - (char *)sprite) / sizeof(*sprite);
                USERp u = User[sprnum];
                if (u && (sprite[sprnum].statnum != STAT_DEFAULT) &&
                    ((TEST(u->Flags, SPR_SKIP4) && (sprite[sprnum].statnum <= STAT_SKIP4_INTERP_END)) ||
                     (TEST(u->Flags, SPR_SKIP2) && (sprite[sprnum].statnum <= STAT_SKIP2_INTERP_END))))
                    continue;
            }

            if (data->spriteofang >= 0)
                *(int16_t *)(data->curipos) = NORM_ANGLE(data->lastoldipos + mulscale16(data->lastangdiff, ratio));
            else
            {
                delta = data->lastipos - data->lastoldipos;
                *(int32_t *)(data->curipos) = data->lastoldipos + mulscale16(delta, ratio);
            }
        }
    }
}

void so_restoreinterpolations(void)                 // Stick at end of drawscreen
{
    int32_t i;
    SECTOR_OBJECTp sop;
    so_interp *interp;
    so_interp::interp_data *data;

    for (sop = SectorObject, interp = so_interpdata;
         sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++, interp++)
    {
        if (SO_EMPTY(sop))
            continue;

        for (i = 0, data = interp->data; i < interp->numinterpolations; i++, data++)
            if (data->spriteofang >= 0)
                *(int16_t *)(data->curipos) = data->bakipos;
            else
                *(int32_t *)(data->curipos) = data->bakipos;
    }
}

int SaveSymDataInfo(MFILE_WRITE fil, void *ptr);

SWBOOL so_writeinterpolations(MFILE_WRITE fil)
{
    int32_t i;
    SECTOR_OBJECTp sop;
    const so_interp *interp;
    SWBOOL saveisshot = FALSE;

    for (sop = SectorObject, interp = so_interpdata;
         sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++, interp++)
    {
        const so_interp::interp_data *data = interp->data;
        MWRITE(&interp->numinterpolations,sizeof(interp->numinterpolations),1,fil);
        MWRITE(&interp->hasvator,sizeof(interp->hasvator),1,fil);
        for (i = 0; i < interp->numinterpolations; i++, data++)
        {
            saveisshot |= SaveSymDataInfo(fil, data->curipos);
            MWRITE(&data->oldipos,sizeof(data->oldipos),1,fil);
            MWRITE(&data->spriteofang,sizeof(data->spriteofang),1,fil);
        }
    }
    return saveisshot;
}

int LoadSymDataInfo(MFILE_READ fil, void **ptr);

SWBOOL so_readinterpolations(MFILE_READ fil)
{
    int32_t i;
    SECTOR_OBJECTp sop;
    so_interp *interp;
    SWBOOL saveisshot = FALSE;

    for (sop = SectorObject, interp = so_interpdata;
         sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++, interp++)
    {
        so_interp::interp_data *data = interp->data;
        MREAD(&interp->numinterpolations,sizeof(interp->numinterpolations),1,fil);
        MREAD(&interp->hasvator,sizeof(interp->hasvator),1,fil);
        for (i = 0; i < interp->numinterpolations; i++, data++)
        {
            saveisshot |= LoadSymDataInfo(fil, (void **)&data->curipos);
            MREAD(&data->oldipos,sizeof(data->oldipos),1,fil);
            MREAD(&data->spriteofang,sizeof(data->spriteofang),1,fil);
            data->lastipos = data->lastoldipos = data->oldipos;
            data->lastangdiff = 0;
        }
        interp->tic = 0;
        interp->lasttic = synctics;
    }
    return saveisshot;
}


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

This file is part of Shadow Warrior version 1.2

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

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

See the GNU General Public License for more details.

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

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

#ifndef INTERPSO_H
#define INTERPSO_H

#include "mfile.h"

extern int32_t so_numinterpolations;

void so_addinterpolation(SECTOR_OBJECTp sop);
void so_setspriteinterpolation(SECTOR_OBJECTp sop, spritetype *sp);
void so_stopspriteinterpolation(SECTOR_OBJECTp sop, spritetype *sp);
void so_setinterpolationangdiff(SECTOR_OBJECTp sop, int16_t angdiff);
void so_setinterpolationtics(SECTOR_OBJECTp sop, int16_t locktics);
void so_updateinterpolations(void);
void so_dointerpolations(int32_t smoothratio);
void so_restoreinterpolations(void);
SWBOOL so_writeinterpolations(MFILE_WRITE fil);
SWBOOL so_readinterpolations(MFILE_READ fil);

#endif