code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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

#include "compat.h"

void PlayerInterruptKeys();
void RestoreSavePoint(int nPlayer, int *x, int *y, int *z, short *nSector, short *nAngle);
void SetSavePoint(int nPlayer, int x, int y, int z, short nSector, short nAngle);
void InitPlayer();
void InitPlayerKeys(short nPlayer);
void DoKenTest();
int GrabPlayer();
void InitPlayerInventory(short nPlayer);
void RestartPlayer(short nPlayer);

void FuncPlayer(int nSector, int nSprite, int nRun);

#define kMaxPlayers			8
#define kDefaultLives		3
#define kMaxPlayerLives		5
#define kMaxHealth			800
#define kMaxMagic           1000

extern bool bDemoPlayerFinishedLevel;

extern int nLocalPlayer;

extern int lPlayerXVel;
extern int lPlayerYVel;
extern fix16_t nPlayerDAng;

struct Player
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short bIsMummified;
    short someNetVal;
    short invincibility;
    short nAir;
    short nSeq;
    short nMaskAmount;
    uint16_t keys;
    short nMagic;
    char items[8];
    short nAmmo[7]; // TODO - kMaxWeapons?
    short pad[2];

    short nCurrentWeapon;
    short nWeaponFrame;
    short bIsFiring;
    short nNewWeapon;
    short nWeaponState;
    short nLastWeapon;
    short nRun;

    fix16_t q16angle, q16oangle;
    fix16_t q16horiz, q16ohoriz;
    vec3_t opos;
};

extern short PlayerCount;

extern short nPlayerTorch[];

extern short nPlayerLives[];
extern short nPlayerItem[];
extern Player PlayerList[];
extern short nPlayerInvisible[];
extern short nPlayerDouble[];
extern short nPlayerViewSect[];
extern short nPlayerFloorSprite[];

extern short nTauntTimer[];

extern short nDoppleSprite[];

extern uint16_t nPlayerWeapons[];

extern short nPlayerOldWeapon[];
extern short nPlayerGrenade[kMaxPlayers];
extern short nGrenadePlayer[50];

extern short nPistolClip[];

extern short nPlayerScore[];

extern short nPlayerClip[];

extern short obobangle, bobangle;

extern int totalvel[];
extern int16_t eyelevel[], oeyelevel[];

extern short nNetStartSprite[kMaxPlayers];
extern short nNetStartSprites;
extern short nCurStartSprite;

extern int nXDamage[kMaxPlayers];
extern int nYDamage[kMaxPlayers];

extern int nPlayerDY[kMaxPlayers];
extern int nPlayerDX[kMaxPlayers];

extern short nLocalSpr;

short GetPlayerFromSprite(short nSprite);
void SetPlayerMummified(int nPlayer, int bIsMummified);
int AddAmmo(int nPlayer, int nWeapon, int nAmmoAmount);
void ShootStaff(int nPlayer);

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 "queen.h"
#include "exhumed.h"
#include "engine.h"
#include "move.h"
#include "sequence.h"
#include "runlist.h"
#include "random.h"
#include "wasp.h"
#include "trigdat.h"
#include "anims.h"
#include "player.h"
#include "sound.h"
#include "names.h"
#include "save.h"
#include <assert.h>

#define kMaxQueens  1
#define kMaxEggs    10
#define kMaxTails   7

static actionSeq ActionSeq[] = {
    {0,  0},
    {0,  0},
    {9,  0},
    {36, 0},
    {18, 0},
    {27, 0},
    {45, 0},
    {45, 0},
    {54, 1},
    {53, 1},
    {55, 1}
};

static actionSeq HeadSeq[] = {
    {56, 1},
    {65, 0},
    {65, 0},
    {65, 0},
    {65, 0},
    {65, 0},
    {74, 0},
    {82, 0},
    {90, 0}
};

static actionSeq EggSeq[] = {
    {19, 1},
    {18, 1},
    {0,  0},
    {9,  0},
    {23, 1},
};

struct Queen
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short nTarget;
    short field_A;
    short field_C;
    short pad;
    short field_10;
    short field_12;
};

struct Egg
{
    short nHealth;
    short field_2;
    short nAction;
    short nSprite;
    short field_8;
    short nTarget;
    short field_C;
    short field_E;
};

struct Head
{
    short nHealth;
    short field_2;
    short nAction;
    short nSprite;
    short field_8;
    short nTarget;
    short field_C;
    short field_E;
};

short QueenCount = 0;
int nQHead = 0;

short nEggsFree;
short nHeadVel;
short nVelShift;

short tailspr[kMaxTails];
short nEggFree[kMaxEggs];

short QueenChan[kMaxQueens];
Egg QueenEgg[kMaxEggs];
Queen QueenList[kMaxQueens];
Head QueenHead;

int MoveQX[25];
int MoveQY[25];
int MoveQZ[25];
short MoveQS[25];
short MoveQA[25];

void InitQueens()
{
    QueenCount = 1;

    for (int i = 0; i < kMaxEggs; i++)
    {
        nEggFree[i] = i;
        QueenEgg[i].field_8 = -1;
    }

    nEggsFree = kMaxEggs;
}

int GrabEgg()
{
    if (!nEggsFree) {
        return -1;
    }

    nEggsFree--;
    return nEggFree[nEggsFree];
}

void BlowChunks(int nSprite)
{
    for (int i = 0; i < 4; i++)
    {
        BuildCreatureChunk(nSprite, seq_GetSeqPicnum(16, i + 41, 0));
    }
}

void DestroyEgg(short nEgg)
{
    short nSprite = QueenEgg[nEgg].nSprite;

    if (QueenEgg[nEgg].nAction != 4)
    {
        BuildAnim(-1, 34, 0, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum, sprite[nSprite].xrepeat, 4);
    }
    else
    {
        for (int i = 0; i < 4; i++)
        {
            BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqQueenEgg, (i % 2) + 24, 0));
        }
    }

    runlist_DoSubRunRec(sprite[nSprite].owner);
    runlist_DoSubRunRec(sprite[nSprite].lotag - 1);
    runlist_SubRunRec(QueenEgg[nEgg].field_8);

    QueenEgg[nEgg].field_8 = -1;

    mydeletesprite(nSprite);

    nEggFree[nEggsFree] = nEgg;
    nEggsFree++;
}

void DestroyAllEggs()
{
    for (int i = 0; i < kMaxEggs; i++)
    {
        if (QueenEgg[i].field_8 > -1)
        {
            DestroyEgg(i);
        }
    }
}

void SetHeadVel(short nSprite)
{
    short nAngle = sprite[nSprite].ang;

    if (nVelShift >= 0)
    {
        sprite[nSprite].xvel = Cos(nAngle) >> (int8_t)(nVelShift);
        sprite[nSprite].yvel = Sin(nAngle) >> (int8_t)(nVelShift);
    }
    else
    {
        sprite[nSprite].xvel = Cos(nAngle) << (int8_t)(-nVelShift);
        sprite[nSprite].yvel = Sin(nAngle) << (int8_t)(-nVelShift);
    }
}

int QueenAngleChase(short nSprite, short nSprite2, int val1, int val2)
{
    short nAngle;

    spritetype *pSprite = &sprite[nSprite];
    if (nSprite2 < 0)
    {
        pSprite->zvel = 0;
        nAngle = pSprite->ang;
    }
    else
    {
        spritetype *pSprite2 = &sprite[nSprite2];
        int nTileY = (tilesiz[pSprite2->picnum].y * pSprite2->yrepeat) * 2;

        int nMyAngle = GetMyAngle(pSprite2->x - pSprite->x, pSprite2->y - pSprite->y);

        int edx = ((pSprite2->z - nTileY) - pSprite->z) >> 8;

        uint32_t xDiff = klabs(pSprite2->x - pSprite->x);
        uint32_t yDiff = klabs(pSprite2->y - pSprite->y);

        uint32_t sqrtVal = xDiff * xDiff + yDiff * yDiff;

        if (sqrtVal > INT_MAX)
        {
            OSD_Printf("%s %d: overflow\n", EDUKE32_FUNCTION, __LINE__);
            sqrtVal = INT_MAX;
        }

        int nSqrt = ksqrt(sqrtVal);

        int var_14 = GetMyAngle(nSqrt, edx);

        int nAngDelta = AngleDelta(pSprite->ang, nMyAngle, 1024);

        if (klabs(nAngDelta) > 127)
        {
            val1 /= klabs(nAngDelta>>7);
            if (val1 < 256)
                val1 = 256;
        }

        if (klabs(nAngDelta) > val2)
        {
            if (nAngDelta < 0)
                nAngDelta = -val2;
            else
                nAngDelta = val2;
        }

        nAngle = (nAngDelta + sprite[nSprite].ang) & kAngleMask;

        pSprite->zvel = (AngleDelta(pSprite->zvel, var_14, 24) + pSprite->zvel) & kAngleMask;
    }

    pSprite->ang = nAngle;

    int da = pSprite->zvel;
    int x = klabs(Cos(da));

    int v26 = x * ((val1 * Cos(nAngle)) >> 14);
    int v27 = x * ((val1 * Sin(nAngle)) >> 14);

    uint32_t xDiff = klabs((int32_t)(v26 >> 8));
    uint32_t yDiff = klabs((int32_t)(v27 >> 8));

    uint32_t sqrtNum = xDiff * xDiff + yDiff * yDiff;

    if (sqrtNum > INT_MAX)
    {
        OSD_Printf("%s %d: overflow\n", EDUKE32_FUNCTION, __LINE__);
        sqrtNum = INT_MAX;
    }

    int nSqrt = ksqrt(sqrtNum) * Sin(da);

    return movesprite(nSprite, v26 >> 2, v27 >> 2, (Sin(bobangle) >> 5) + (nSqrt >> 13), 0, 0, CLIPMASK1);
}

int DestroyTailPart()
{
    if (!QueenHead.field_E) {
        return 0;
    }

    short nSprite = tailspr[--QueenHead.field_E];

    BlowChunks(nSprite);
    BuildExplosion(nSprite);

    for (int i = 0; i < 5; i++)
    {
        short nHeight = GetSpriteHeight(nSprite);
        BuildLavaLimb(nSprite, i, nHeight);
    }

    mydeletesprite(nSprite);
    return 1;
}

void BuildTail()
{
    short nSprite = QueenHead.nSprite;

    int x = sprite[nSprite].x;
    int y = sprite[nSprite].x;
    int z = sprite[nSprite].x;
    short nSector = sprite[nSprite].sectnum;

    int i;

    for (i = 0; i < kMaxTails; i++)
    {
        short nTailSprite = insertsprite(nSector, 121);
        tailspr[i] = nTailSprite;

        if (nTailSprite < 0) {
            bail2dos("Can't create queen's tail!\n");
        }

        sprite[nTailSprite].lotag = runlist_HeadRun() + 1;
        sprite[nTailSprite].owner = runlist_AddRunRec(sprite[nTailSprite].lotag - 1, (i + 1), kRunQueenHead);
        sprite[nTailSprite].shade = -12;
        sprite[nTailSprite].x = x;
        sprite[nTailSprite].y = y;
        sprite[nTailSprite].hitag = 0;
        sprite[nTailSprite].cstat = 0;
        sprite[nTailSprite].clipdist = 100;
        sprite[nTailSprite].xrepeat = 80;
        sprite[nTailSprite].yrepeat = 80;
        sprite[nTailSprite].picnum = 1;
        sprite[nTailSprite].pal = sector[sprite[nTailSprite].sectnum].ceilingpal;
        sprite[nTailSprite].xoffset = 0;
        sprite[nTailSprite].yoffset = 0;
        sprite[nTailSprite].z = z;
        sprite[nTailSprite].extra = -1;
    }

    for (i = 0; i < 24 + 1; i++)
    {
        MoveQX[i] = x;
        MoveQZ[i] = z;
        MoveQY[i] = y;
        assert(nSector >= 0 && nSector < kMaxSectors);
        MoveQS[i] = nSector;
    }

    nQHead = 0;
    QueenHead.field_E = 7;
}

int BuildQueenEgg(short nQueen, int nVal)
{
    int nEgg = GrabEgg();
    if (nEgg < 0) {
        return -1;
    }

    short nSprite = QueenList[nQueen].nSprite;

    int x = sprite[nSprite].x;
    int y = sprite[nSprite].y;
    short nSector = sprite[nSprite].sectnum;
    int nFloorZ = sector[nSector].floorz;
    short nAngle = sprite[nSprite].ang;

    int nSprite2 = insertsprite(nSector, 121);
    assert(nSprite2 >= 0 && nSprite2 < kMaxSprites);

    sprite[nSprite2].x = x;
    sprite[nSprite2].y = y;
    sprite[nSprite2].z = nFloorZ;
    sprite[nSprite2].pal = 0;
    sprite[nSprite2].clipdist = 50;
    sprite[nSprite2].xoffset = 0;
    sprite[nSprite2].yoffset = 0;
    sprite[nSprite2].shade = -12;
    sprite[nSprite2].picnum = 1;
    sprite[nSprite2].ang = (RandomSize(9) + (nAngle - 256)) & kAngleMask;

    if (!nVal)
    {
        sprite[nSprite2].xrepeat = 30;
        sprite[nSprite2].yrepeat = 30;
        sprite[nSprite2].xvel = Cos(sprite[nSprite2].ang);
        sprite[nSprite2].yvel = Sin(sprite[nSprite2].ang);
        sprite[nSprite2].zvel = -6000;
        sprite[nSprite2].cstat = 0;
    }
    else
    {
        sprite[nSprite2].xrepeat = 60;
        sprite[nSprite2].yrepeat = 60;
        sprite[nSprite2].xvel = 0;
        sprite[nSprite2].yvel = 0;
        sprite[nSprite2].zvel = -2000;
        sprite[nSprite2].cstat = 0x101;
    }

    sprite[nSprite2].lotag = runlist_HeadRun() + 1;
    sprite[nSprite2].extra = -1;
    sprite[nSprite2].hitag = 0;

    GrabTimeSlot(3);

    QueenEgg[nEgg].nHealth = 200;
    QueenEgg[nEgg].field_2 = 0;
    QueenEgg[nEgg].nSprite = nSprite2;
    QueenEgg[nEgg].field_E = nVal;
    QueenEgg[nEgg].nTarget = QueenList[nQueen].nTarget;

    if (nVal)
    {
        nVal = 4;
        QueenEgg[nEgg].field_C = 200;
    }

    QueenEgg[nEgg].nAction = nVal;

    sprite[nSprite2].owner = runlist_AddRunRec(sprite[nSprite2].lotag - 1, nEgg, kRunQueenEgg);
    QueenEgg[nEgg].field_8 = runlist_AddRunRec(NewRun, nEgg, kRunQueenEgg);

    return 0;
}

void FuncQueenEgg(int a, int nDamage, int nRun)
{
    short nEgg = RunData[nRun].nVal;

    Egg *pEgg = &QueenEgg[nEgg];
    short nSprite = pEgg->nSprite;
    short nAction = pEgg->nAction;

    short nTarget;

    bool bVal = false;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        default:
        {
            DebugOut("unknown msg %d for Queenhead\n", nMessage);
            break;
        }

        case 0x20000:
        {
            if (pEgg->nHealth <= 0)
            {
                DestroyEgg(nEgg);
                return;
            }

            if (nAction == 0 || nAction == 4) {
                Gravity(nSprite);
            }

            short nSeq = SeqOffsets[kSeqQueenEgg] + EggSeq[nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, pEgg->field_2);

            if (nAction != 4)
            {
                seq_MoveSequence(nSprite, nSeq, pEgg->field_2);

                pEgg->field_2++;
                if (pEgg->field_2 >= SeqSize[nSeq])
                {
                    pEgg->field_2 = 0;
                    bVal = true;
                }

                nTarget = UpdateEnemy(&pEgg->nTarget);
                pEgg->nTarget = nTarget;

                if (nTarget >= 0 && (sprite[nTarget].cstat & 0x101) == 0)
                {
                    pEgg->nTarget = -1;
                    pEgg->nAction = 0;
                }
                else
                {
                    nTarget = FindPlayer(-nSprite, 1000);
                    pEgg->nTarget = nTarget;
                }
            }

            switch (nAction)
            {
                case 0:
                {
                    int nMov = MoveCreature(nSprite);
                    if (!nMov) {
                        break;
                    }

                    if (nMov & 0x20000)
                    {
                        if (!RandomSize(1))
                        {
                            pEgg->nAction = 1;
                            pEgg->field_2 = 0;
                        }
                        else
                        {
                            DestroyEgg(nEgg);
                        }
                    }
                    else
                    {
                        short nAngle;

                        switch (nMov & 0xC000)
                        {
                        default:
                            return;
                        case 0x8000:
                            nAngle = GetWallNormal(nMov & 0x3FFF);
                            break;
                        case 0xC000:
                            nAngle = sprite[nMov & 0x3FFF].ang;
                            break;
                        }

                        sprite[nSprite].ang = nAngle;
                        sprite[nSprite].xvel = Cos(nAngle) >> 1;
                        sprite[nSprite].yvel = Sin(nAngle) >> 1;
                    }

                    break;
                }

                case 1:
                {
                    if (bVal)
                    {
                        pEgg->nAction = 3;
                        sprite[nSprite].cstat = 0x101;
                    }
                    break;
                }

                case 2:
                case 3:
                {
                    int nMov = QueenAngleChase(nSprite, nTarget, nHeadVel, 64);

                    switch (nMov & 0xC000)
                    {
                    case 0xC000:
                        if (sprite[nMov & 0x3FFF].statnum != 121)
                        {
                            runlist_DamageEnemy(nMov & 0x3FFF, nSprite, 5);
                        }
                        fallthrough__;
                    case 0x8000:
                        sprite[nSprite].ang += (RandomSize(9) + 768);
                        sprite[nSprite].ang &= kAngleMask;
                        sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 3;
                        sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 3;
                        sprite[nSprite].zvel = -RandomSize(5);
                        break;
                    }

                    return;
                }

                case 4:
                {
                    int nMov = MoveCreature(nSprite);

                    if (nMov & 0x20000)
                    {
                        sprite[nSprite].zvel = -(sprite[nSprite].zvel - 256);
                        if (sprite[nSprite].zvel < -512)
                        {
                            sprite[nSprite].zvel = 0;
                        }
                    }

                    pEgg->field_C--;
                    if (pEgg->field_C <= 0)
                    {
                        int nWasp = BuildWasp(-2, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum, sprite[nSprite].ang);
                        int nWaspSprite = GetWaspSprite(nWasp);
                        sprite[nSprite].z = sprite[nWaspSprite].z;

                        DestroyEgg(nEgg);
                    }
                    break;
                }
            }
            break;
        }

        case 0xA0000:
        {
            if (sprite[nRadialSpr].statnum != 121 && (sprite[nSprite].cstat & 0x101) != 0)
            {
                nDamage = runlist_CheckRadialDamage(nSprite);

                pEgg->nHealth -= nDamage;
            }
            break;
        }

        case 0x80000:
        {
            if (nDamage != 0 && pEgg->nHealth > 0)
            {
                QueenEgg[nEgg].nHealth -= nDamage;

                if (QueenEgg[nEgg].nHealth <= 0)
                    DestroyEgg(nEgg);
            }
            break;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqQueenEgg] + EggSeq[nAction].a, pEgg->field_2, EggSeq[nAction].b);
            break;
        }
    }
}

int BuildQueenHead(short nQueen)
{
    short nSprite = QueenList[nQueen].nSprite;

    int x = sprite[nSprite].x;
    int y = sprite[nSprite].y;
    short nAngle = sprite[nSprite].ang;
    short nSector = sprite[nSprite].sectnum;
    int z = sector[nSector].floorz;

    int nSprite2 = insertsprite(nSector, 121);
    assert(nSprite2 >= 0 && nSprite2 < kMaxSprites);

    sprite[nSprite2].x = x;
    sprite[nSprite2].y = y;
    sprite[nSprite2].z = z;
    sprite[nSprite2].clipdist = 70;
    sprite[nSprite2].xrepeat = 80;
    sprite[nSprite2].yrepeat = 80;
    sprite[nSprite2].cstat = 0;
    sprite[nSprite2].picnum = 1;
    sprite[nSprite2].shade = -12;
    sprite[nSprite2].pal = 0;
    sprite[nSprite2].xoffset = 0;
    sprite[nSprite2].yoffset = 0;
    sprite[nSprite2].ang = nAngle;

    nVelShift = 2;
    SetHeadVel(nSprite2);

    sprite[nSprite2].zvel = -8192;
    sprite[nSprite2].lotag = runlist_HeadRun() + 1;
    sprite[nSprite2].hitag = 0;
    sprite[nSprite2].extra = -1;

    GrabTimeSlot(3);

    QueenHead.nHealth = 800;
    QueenHead.nAction = 0;
    QueenHead.nTarget = QueenList[nQueen].nTarget;
    QueenHead.field_2 = 0;
    QueenHead.nSprite = nSprite2;
    QueenHead.field_C = 0;

    sprite[nSprite2].owner = runlist_AddRunRec(sprite[nSprite2].lotag - 1, nQueen, kRunQueenHead);

    QueenHead.field_8 = runlist_AddRunRec(NewRun, nQueen, kRunQueenHead);
    QueenHead.field_E = 0;

    return 0;
}

void FuncQueenHead(int a, int nDamage, int nRun)
{
    short nHead = RunData[nRun].nVal;

    short nSprite = QueenHead.nSprite;
    int nSector = sprite[nSprite].sectnum;
    assert(nSector >= 0 && nSector < kMaxSectors);

    short nAction = QueenHead.nAction;

    short nTarget, nHd;

    int var_14 = 0;

    int nMessage = a & 0x7F0000;

    switch (nMessage)
    {
        case 0x20000:
        {
            if (nAction == 0) {
                Gravity(nSprite);
            }

            short nSeq = SeqOffsets[kSeqQueen] + HeadSeq[QueenHead.nAction].a;

            seq_MoveSequence(nSprite, nSeq, QueenHead.field_2);

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, QueenHead.field_2);

            QueenHead.field_2++;
            if (QueenHead.field_2 >= SeqSize[nSeq])
            {
                QueenHead.field_2 = 0;
                var_14 = 1;
            }

            nTarget = QueenHead.nTarget;

            if (nTarget > -1)
            {
                if (!(sprite[nTarget].cstat & 0x101))
                {
                    nTarget = -1;
                    QueenHead.nTarget = nTarget;
                }
            }
            else
            {
                nTarget = FindPlayer(nSprite, 1000);
                QueenHead.nTarget = nTarget;
            }

            switch (nAction)
            {
                case 0:
                    if (QueenHead.field_C > 0)
                    {
                        QueenHead.field_C--;
                        if (QueenHead.field_C == 0)
                        {
                            BuildTail();

                            QueenHead.nAction = 6;
                            nHeadVel = 800;
                            sprite[nSprite].cstat = 0x101;
                        }
                        else if (QueenHead.field_C < 60)
                        {
                            sprite[nSprite].shade--;
                        }
                    }
                    else
                    {
                        int nMov = MoveCreature(nSprite);

                        // original BUG - this line doesn't exist in original code?
                        short nNewAng = sprite[nSprite].ang;

                        switch (nMov & 0xFC000)
                        {
                        default:
                            return;
                        case 0xC000:
                            nNewAng = sprite[nMov & 0x3FFF].ang;
                            break;
                        case 0x8000:
                            nNewAng = GetWallNormal(nMov & 0x3FFF);
                            break;
                        case 0x20000:
                            sprite[nSprite].zvel = -(sprite[nSprite].zvel >> 1);

                            if (sprite[nSprite].zvel > -256)
                            {
                                nVelShift = 100;
                                sprite[nSprite].zvel = 0;
                            }
                            break;
                        }

                        // original BUG - var_18 isn't being set if the check above == 0x20000 ?
                        sprite[nSprite].ang = nNewAng;
                        nVelShift++;

                        if (nVelShift < 5)
                        {
                            SetHeadVel(nSprite);
                        }
                        else
                        {
                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;

                            if (sprite[nSprite].zvel == 0)
                            {
                                QueenHead.field_C = 120;
                            }
                        }
                    }

                    break;

                case 6:
                    if (var_14)
                    {
                        QueenHead.nAction = 1;
                        QueenHead.field_2 = 0;
                        break;
                    }
                    fallthrough__;

                case 1:
                    if ((sprite[nTarget].z - 51200) > sprite[nSprite].z)
                    {
                        QueenHead.nAction = 4;
                        QueenHead.field_2 = 0;
                    }
                    else
                    {
                        sprite[nSprite].z -= 2048;
                        goto __MOVEQS;
                    }
                    break;

                case 4:
                case 7:
                case 8:
                    if (var_14)
                    {
                        int nRnd = RandomSize(2);

                        if (nRnd == 0)
                        {
                            QueenHead.nAction = 4;
                        }
                        else if (nRnd == 1)
                        {
                            QueenHead.nAction = 7;
                        }
                        else
                        {
                            QueenHead.nAction = 8;
                        }
                    }

                    if (nTarget > -1)
                    {
                        int nMov = QueenAngleChase(nSprite, nTarget, nHeadVel, 64);

                        switch (nMov & 0xC000)
                        {
                        case 0x8000:
                            break;
                        case 0xC000:
                            if ((nMov & 0x3FFF) == nTarget)
                            {
                                runlist_DamageEnemy(nTarget, nSprite, 10);
                                D3PlayFX(StaticSound[kSoundQTail] | 0x2000, nSprite);

                                sprite[nSprite].ang += RandomSize(9) + 768;
                                sprite[nSprite].ang &= kAngleMask;

                                sprite[nSprite].zvel = (-20) - RandomSize(6);

                                SetHeadVel(nSprite);
                            }
                            break;
                        }
                    }

                    // switch break. MoveQS stuff?
__MOVEQS:
                    MoveQX[nQHead] = sprite[nSprite].x;
                    MoveQY[nQHead] = sprite[nSprite].y;
                    MoveQZ[nQHead] = sprite[nSprite].z;
                    assert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
                    MoveQS[nQHead] = sprite[nSprite].sectnum;
                    MoveQA[nQHead] = sprite[nSprite].ang;

                    nHd = nQHead;

                    for (int i = 0; i < QueenHead.field_E; i++)
                    {
                        nHd -= 3;
                        if (nHd < 0) {
                            nHd += (24 + 1); // TODO - enum/define for these
                            //assert(nHd < 24 && nHd >= 0);
                        }

                        int var_20 = MoveQS[nHd];
                        short nTSprite = tailspr[i];

                        if (var_20 != sprite[nTSprite].sectnum)
                        {
                            assert(var_20 >= 0 && var_20 < kMaxSectors);
                            mychangespritesect(nTSprite, var_20);
                        }

                        sprite[nTSprite].x = MoveQX[nHd];
                        sprite[nTSprite].y = MoveQY[nHd];
                        sprite[nTSprite].z = MoveQZ[nHd];
                        sprite[nTSprite].ang = MoveQA[nHd];
                    }

                    nQHead++;
                    if (nQHead >= 25)
                    {
                        nQHead = 0;
                    }

                    break;

                case 5:
                    QueenHead.field_C--;
                    if (QueenHead.field_C <= 0)
                    {
                        QueenHead.field_C = 3;

                        if (QueenHead.field_E--)
                        {
                            if (QueenHead.field_E >= 15 || QueenHead.field_E < 10)
                            {
                                int x = sprite[nSprite].x;
                                int y = sprite[nSprite].y;
                                int z = sprite[nSprite].z;
                                short nSector = sprite[nSprite].sectnum;
                                int nAngle = RandomSize(11) & kAngleMask;

                                sprite[nSprite].xrepeat = 127 - QueenHead.field_E;
                                sprite[nSprite].yrepeat = 127 - QueenHead.field_E;

                                sprite[nSprite].cstat = 0x8000;

                                // DEMO-TODO: in disassembly angle was used without masking and thus causing OOB issue.
                                // This behavior probably would be needed emulated for demo compatibility
                                // int dx = sintable[nAngle + 512] << 10;
                                int dx = Cos(nAngle) << 10;
                                int dy = Sin(nAngle) << 10;
                                int dz = (RandomSize(5) - RandomSize(5)) << 7;

                                movesprite(nSprite, dx, dy, dz, 0, 0, CLIPMASK1);

                                BlowChunks(nSprite);
                                BuildExplosion(nSprite);

                                mychangespritesect(nSprite, nSector);

                                sprite[nSprite].x = x;
                                sprite[nSprite].y = y;
                                sprite[nSprite].z = z;

                                if (QueenHead.field_E < 10)
                                {
                                    for (int i = (10 - QueenHead.field_E) * 2; i > 0; i--)
                                    {
                                        BuildLavaLimb(nSprite, i, GetSpriteHeight(nSprite));
                                    }
                                }
                            }
                        }
                        else
                        {
                            BuildExplosion(nSprite);

                            int i;

                            for (i = 0; i < 10; i++)
                            {
                                BlowChunks(nSprite);
                            }

                            for (i = 0; i < 20; i++)
                            {
                                BuildLavaLimb(nSprite, i, GetSpriteHeight(nSprite));
                            }

                            runlist_SubRunRec(sprite[nSprite].owner);
                            runlist_SubRunRec(QueenHead.field_8);
                            mydeletesprite(nSprite);
                            runlist_ChangeChannel(QueenChan[0], 1);
                        }
                    }
                    break;
            }
            break;
        }

        case 0xA0000:
            if (sprite[nRadialSpr].statnum != 121 && (sprite[nSprite].cstat & 0x101) != 0)
            {
                nDamage = runlist_CheckRadialDamage(nSprite);
                if (!nDamage)
                    break;
            }
            else
                break;
            // fall through to case 0x80000
            fallthrough__;

        case 0x80000:
            if (QueenHead.nHealth > 0 && nDamage != 0)
            {
                QueenHead.nHealth -= nDamage;

                if (!RandomSize(4))
                {
                    QueenHead.nTarget = a & 0xFFFF;
                    QueenHead.nAction = 7;
                    QueenHead.field_2 = 0;
                }

                if (QueenHead.nHealth <= 0)
                {
                    if (DestroyTailPart())
                    {
                        QueenHead.nHealth = 200;
                        nHeadVel += 100;
                    }
                    else
                    {
                        QueenHead.nAction = 5;
                        QueenHead.field_2 = 0;
                        QueenHead.field_C = 0;
                        QueenHead.field_E = 80;
                        sprite[nSprite].cstat = 0;
                    }
                }
            }
            break;

        case 0x90000:
        {
            short nSeq = SeqOffsets[kSeqQueen];

            int edx;

            if (nHead == 0)
            {
                edx = HeadSeq[nAction].b;
                nSeq += HeadSeq[nAction].a;
            }
            else
            {
                edx = 1;
                nSeq += 73;
            }

            seq_PlotSequence(a & 0xFFFF, nSeq, QueenHead.field_2, edx);
            break;
        }

        default:
        {
            DebugOut("unknown msg %d for Queenhead\n", a & 0x7F0000);
            break;
        }
    }
}

int BuildQueen(int nSprite, int x, int y, int z, int nSector, int nAngle, int nChannel)
{
    QueenCount--;

    short nQueen = QueenCount;
    if (nQueen < 0) {
        return -1;
    }

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 121);
    }
    else
    {
        changespritestat(nSprite, 121);
        x = sprite[nSprite].x;
        y = sprite[nSprite].y;
        z = sector[sprite[nSprite].sectnum].floorz;
        nAngle = sprite[nSprite].ang;
    }

    assert(nSprite >= 0 && nSprite < kMaxSprites);

    sprite[nSprite].x = x;
    sprite[nSprite].y = y;
    sprite[nSprite].z = z;
    sprite[nSprite].cstat = 0x101;
    sprite[nSprite].pal = 0;
    sprite[nSprite].shade = -12;
    sprite[nSprite].clipdist = 100;
    sprite[nSprite].xrepeat = 80;
    sprite[nSprite].yrepeat = 80;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].ang = nAngle;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].extra = -1;
    sprite[nSprite].hitag = 0;

    GrabTimeSlot(3);

    QueenList[nQueen].nAction = 0;
    QueenList[nQueen].nHealth = 4000;
    QueenList[nQueen].nFrame = 0;
    QueenList[nQueen].nSprite = nSprite;
    QueenList[nQueen].nTarget = -1;
    QueenList[nQueen].field_A = 0;
    QueenList[nQueen].field_10 = 5;
    QueenList[nQueen].field_C = 0;

    QueenChan[nQueen] = nChannel;

    nHeadVel = 800;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nQueen, kRunQueen);

    runlist_AddRunRec(NewRun, nQueen, kRunQueen);

    nCreaturesLeft++;

    return nQueen;
}

void SetQueenSpeed(short nSprite, int nSpeed)
{
    sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> (2 - nSpeed);
    sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> (2 - nSpeed);
}

void FuncQueen(int a, int nDamage, int nRun)
{
    short nQueen = RunData[nRun].nVal;
    assert(nQueen >= 0 && nQueen < kMaxQueens);

    short nSprite = QueenList[nQueen].nSprite;
    short nAction = QueenList[nQueen].nAction;
    short si = QueenList[nQueen].field_A;
    short nTarget = QueenList[nQueen].nTarget;

    bool bVal = false;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {

        default:
        {
            DebugOut("unknown msg %d for Queen\n", nMessage);
            break;
        }

        case 0x20000:
        {
            if (si < 3) {
                Gravity(nSprite);
            }

            short nSeq = SeqOffsets[kSeqQueen] + ActionSeq[nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, QueenList[nQueen].nFrame);

            seq_MoveSequence(nSprite, nSeq, QueenList[nQueen].nFrame);

            QueenList[nQueen].nFrame++;
            if (QueenList[nQueen].nFrame >= SeqSize[nSeq])
            {
                QueenList[nQueen].nFrame = 0;
                bVal = true;
            }

            short nFlag = FrameFlag[SeqBase[nSeq] + QueenList[nQueen].nFrame];

            if (nTarget > -1)
            {
                if (nAction < 7)
                {
                    if (!(sprite[nSprite].cstat & 0x101))
                    {
                        nTarget = -1;
                        QueenList[nQueen].nTarget = -1;
                        QueenList[nQueen].nAction = 0;
                    }
                }
            }
            switch (nAction)
            {
                case 0:
                {
                    if (nTarget < 0)
                    {
                        nTarget = FindPlayer(nSprite, 60);
                    }

                    if (nTarget >= 0)
                    {
                        QueenList[nQueen].nAction = QueenList[nQueen].field_A + 1;
                        QueenList[nQueen].nFrame  = 0;
                        QueenList[nQueen].nTarget = nTarget;
                        QueenList[nQueen].field_C = RandomSize(7);

                        SetQueenSpeed(nSprite, si);
                    }
                    break;
                }

                case 6:
                {
                    if (bVal)
                    {
                        BuildQueenEgg(nQueen, 1);
                        QueenList[nQueen].nAction = 3;
                        QueenList[nQueen].field_C = RandomSize(6) + 60;
                    }

                    break;
                }

                case 1:
                case 2:
                case 3:
                {
                    QueenList[nQueen].field_C--;

                    if ((nQueen & 0x1F) == (totalmoves & 0x1F))
                    {
                        if (si < 2)
                        {
                            if (QueenList[nQueen].field_C <= 0)
                            {
                                QueenList[nQueen].nFrame = 0;
                                sprite[nSprite].xvel = 0;
                                sprite[nSprite].yvel = 0;
                                QueenList[nQueen].nAction = si + 4;
                                QueenList[nQueen].field_C = RandomSize(6) + 30;
                                break;
                            }
                            else
                            {
                                if (QueenList[nQueen].field_10 < 5)
                                {
                                    QueenList[nQueen].field_10++;
                                }

                                // then to PLOTSPRITE
                            }
                        }
                        else
                        {
                            if (QueenList[nQueen].field_C <= 0)
                            {
                                if (nWaspCount < 100)
                                {
                                    QueenList[nQueen].nAction = 6;
                                    QueenList[nQueen].nFrame  = 0;
                                    break;
                                }
                                else
                                {
                                    QueenList[nQueen].field_C = 30000;
                                    // then to PLOTSPRITE
                                }
                            }
                        }

                        // loc_35B4B
                        PlotCourseToSprite(nSprite, nTarget);
                        SetQueenSpeed(nSprite, si);
                    }

                    int nMov = MoveCreatureWithCaution(nSprite);

                    switch (nMov & 0xC000)
                    {
                        case 0xC000:
                            if ((si == 2) && ((nMov & 0x3FFF) == nTarget))
                            {
                                runlist_DamageEnemy(nTarget, nSprite, 5);
                                break;
                            }
                            fallthrough__;
                        case 0x8000:
                            sprite[nSprite].ang += 256;
                            sprite[nSprite].ang &= kAngleMask;

                            SetQueenSpeed(nSprite, si);
                            break;
                    }

                    // loc_35BD2
                    if (nAction && nTarget != -1)
                    {
                        if (!(sprite[nTarget].cstat & 0x101))
                        {
                            QueenList[nQueen].nAction = 0;
                            QueenList[nQueen].nFrame  = 0;
                            QueenList[nQueen].field_C = 100;
                            QueenList[nQueen].nTarget = -1;

                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                        }
                    }

                    break;
                }

                case 4:
                case 5:
                {
                    if (bVal && QueenList[nQueen].field_10 <= 0)
                    {
                        QueenList[nQueen].nAction = 0;
                        QueenList[nQueen].field_C = 15;
                    }
                    else
                    {
                        if (nFlag & 0x80)
                        {
                            QueenList[nQueen].field_10--;

                            PlotCourseToSprite(nSprite, nTarget);

                            if (!si)
                            {
                                BuildBullet(nSprite, 12, 0, 0, -1, sprite[nSprite].ang, nTarget + 10000, 1);
                            }
                            else
                            {
                                BuildQueenEgg(nQueen, 0);
                            }
                        }
                    }

                    break;
                }

                case 7:
                {
                    if (bVal)
                    {
                        QueenList[nQueen].nAction = 0;
                        QueenList[nQueen].nFrame  = 0;
                    }

                    break;
                }

                case 8:
                case 9:
                {
                    if (bVal)
                    {
                        if (nAction == 9)
                        {
                            QueenList[nQueen].field_C--;
                            if (QueenList[nQueen].field_C <= 0)
                            {
                                sprite[nSprite].cstat = 0;

                                for (int i = 0; i < 20; i++)
                                {
                                    short nChunkSprite = BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqQueen, 57, 0)) & 0xFFFF;

                                    sprite[nChunkSprite].picnum = kTile3117 + (i % 3);
                                    sprite[nChunkSprite].xrepeat = 100;
                                    sprite[nChunkSprite].yrepeat = 100;
                                }

                                short nChunkSprite = BuildCreatureChunk(nSprite, seq_GetSeqPicnum(kSeqQueen, 57, 0));

                                sprite[nChunkSprite].picnum = kTile3126;
                                sprite[nChunkSprite].yrepeat = 100;
                                sprite[nChunkSprite].xrepeat = 100;

                                PlayFXAtXYZ(
                                    StaticSound[kSoundFishDies],
                                    sprite[nSprite].x,
                                    sprite[nSprite].y,
                                    sprite[nSprite].z,
                                    sprite[nSprite].sectnum);

                                BuildQueenHead(nQueen);

                                QueenList[nQueen].nAction++;
                            }
                        }
                        else
                            QueenList[nQueen].nAction++;
                    }

                    break;
                }

                case 10:
                {
                    sprite[nSprite].cstat &= 0xFEFE;
                    break;
                }
            }
            break;
        }

        case 0xA0000:
        {
            if (sprite[nRadialSpr].statnum != 121 && (sprite[nSprite].cstat & 0x101) != 0)
            {
                nDamage = runlist_CheckRadialDamage(nSprite);

                if (!nDamage) {
                    break;
                }
            }
            else
                break;

            fallthrough__;
        } // fall through to case 0x80000

        case 0x80000:
        {
            if (QueenList[nQueen].nHealth > 0)
            {
                QueenList[nQueen].nHealth -= nDamage;

                if (QueenList[nQueen].nHealth <= 0)
                {
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                    sprite[nSprite].zvel = 0;

                    QueenList[nQueen].field_A++;

                    switch (QueenList[nQueen].field_A)
                    {
                    case 1:
                        QueenList[nQueen].nHealth = 4000;
                        QueenList[nQueen].nAction = 7;

                        BuildAnim(-1, 36, 0, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z - 7680, sprite[nSprite].sectnum, sprite[nSprite].xrepeat, 4);
                        break;
                    case 2:
                        QueenList[nQueen].nHealth = 4000;
                        QueenList[nQueen].nAction = 7;

                        DestroyAllEggs();
                        break;
                    case 3:
                        QueenList[nQueen].nAction = 8;
                        QueenList[nQueen].nHealth = 0;
                        QueenList[nQueen].field_C = 5;

                        nCreaturesLeft--;
                        break;
                    }

                    QueenList[nQueen].nFrame = 0;
                }
                else
                {
                    if (si > 0 && !RandomSize(4))
                    {
                        QueenList[nQueen].nAction = 7;
                        QueenList[nQueen].nFrame  = 0;
                    }
                }
            }
            break;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqQueen] + ActionSeq[nAction].a, QueenList[nQueen].nFrame, ActionSeq[nAction].b);
            break;
        }
    }
}

class QueenLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void QueenLoadSave::Load()
{
    Read(&QueenCount, sizeof(QueenCount));
    Read(&nQHead, sizeof(nQHead));
    Read(&nEggsFree, sizeof(nEggsFree));
    Read(&nHeadVel, sizeof(nHeadVel));
    Read(&nVelShift, sizeof(nVelShift));
    Read(tailspr, sizeof(tailspr));
    Read(nEggFree, sizeof(nEggFree));
    Read(QueenChan, sizeof(QueenChan));
    Read(QueenEgg, sizeof(QueenEgg));
    Read(QueenList, sizeof(QueenList));
    Read(&QueenHead, sizeof(QueenHead));
    Read(MoveQX, sizeof(MoveQX));
    Read(MoveQY, sizeof(MoveQY));
    Read(MoveQZ, sizeof(MoveQZ));
    Read(MoveQS, sizeof(MoveQS));
    Read(MoveQA, sizeof(MoveQA));
}

void QueenLoadSave::Save()
{
    Write(&QueenCount, sizeof(QueenCount));
    Write(&nQHead, sizeof(nQHead));
    Write(&nEggsFree, sizeof(nEggsFree));
    Write(&nHeadVel,  sizeof(nHeadVel));
    Write(&nVelShift, sizeof(nVelShift));
    Write(tailspr, sizeof(tailspr));
    Write(nEggFree,  sizeof(nEggFree));
    Write(QueenChan, sizeof(QueenChan));
    Write(QueenEgg, sizeof(QueenEgg));
    Write(QueenList,  sizeof(QueenList));
    Write(&QueenHead, sizeof(QueenHead));
    Write(MoveQX, sizeof(MoveQX));
    Write(MoveQY, sizeof(MoveQY));
    Write(MoveQZ, sizeof(MoveQZ));
    Write(MoveQS, sizeof(MoveQS));
    Write(MoveQA, sizeof(MoveQA));
}

static QueenLoadSave* myLoadSave;

void QueenLoadSaveConstruct()
{
    myLoadSave = new QueenLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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

#include "aistuff.h"

void InitQueens();
int BuildQueen(int nSprite, int x, int y, int z, int nSector, int nAngle, int nChannel);
void FuncQueenEgg(int, int, int);
void FuncQueenHead(int, int, int);
void FuncQueen(int, int, int);

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 "ra.h"
#include "runlist.h"
#include "engine.h"
#include "exhumed.h"
#include "player.h"
#include "move.h"
#include "sequence.h"
#include "input.h"
#include "gun.h"
#include "bullet.h"
#include "save.h"
#include <string.h>

/* bjd - the content of the ra.* files originally resided in gun.c I think... */

RA Ra[kMaxPlayers]; // one Ra for each player

static actionSeq ActionSeq[] = {
    {2, 1},
    {0, 0},
    {1, 0},
    {2, 0}
};

void FreeRa(short nPlayer)
{
    int nRun = Ra[nPlayer].nRun;
    int nSprite = Ra[nPlayer].nSprite;

    runlist_SubRunRec(nRun);
    runlist_DoSubRunRec(sprite[nSprite].owner);
    runlist_FreeRun(sprite[nSprite].lotag - 1);

    mydeletesprite(nSprite);
}

int BuildRa(short nPlayer)
{
    short nPlayerSprite = PlayerList[nPlayer].nSprite;

    int nSprite = insertsprite(sprite[nPlayerSprite].sectnum, 203);

    sprite[nSprite].cstat = 0x8000;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].extra = -1;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nPlayer, kRunRa);
    sprite[nSprite].pal = 1;
    sprite[nSprite].xrepeat = 64;
    sprite[nSprite].yrepeat = 64;
    sprite[nSprite].x = sprite[nPlayerSprite].x;
    sprite[nSprite].y = sprite[nPlayerSprite].y;
    sprite[nSprite].z = sprite[nPlayerSprite].z;

//	GrabTimeSlot(3);

    Ra[nPlayer].nSprite = nSprite;
    Ra[nPlayer].nRun = runlist_AddRunRec(NewRun, nPlayer, kRunRa);
    Ra[nPlayer].nTarget = -1;
    Ra[nPlayer].nFrame  = 0;
    Ra[nPlayer].nAction = 0;
    Ra[nPlayer].field_C = 0;
    Ra[nPlayer].nPlayer = nPlayer;

    return nPlayer;
}

void InitRa()
{
    memset(Ra, 0, sizeof(RA) * kMaxPlayers);
}

void MoveRaToEnemy(short nPlayer)
{
    short nTarget = Ra[nPlayer].nTarget;
    short nSprite = Ra[nPlayer].nSprite;
    short nAction = Ra[nPlayer].nAction;

    if (nTarget != -1)
    {
        if (!(sprite[nTarget].cstat & 0x101) || sprite[nTarget].sectnum == MAXSECTORS)
        {
            Ra[nPlayer].nTarget = -1;
            if (nAction == 0 || nAction == 3) {
                return;
            }

            Ra[nPlayer].nAction = 3;
            Ra[nPlayer].nFrame  = 0;
            return;
        }
        else
        {
            if (sprite[nSprite].sectnum != sprite[nTarget].sectnum) {
                mychangespritesect(nSprite, sprite[nTarget].sectnum);
            }
        }
    }
    else
    {
        if (nAction == 1 || nAction == 2)
        {
            Ra[nPlayer].nAction = 3;
            Ra[nPlayer].nFrame  = 0;
            return;
        }

        if (nAction) {
            return;
        }

        sprite[nSprite].cstat = 0x8000;
        nTarget = PlayerList[nPlayer].nSprite;
    }

    sprite[nSprite].x = sprite[nTarget].x;
    sprite[nSprite].y = sprite[nTarget].y;
    sprite[nSprite].z = sprite[nTarget].z - GetSpriteHeight(nTarget);

    if (sprite[nSprite].sectnum != sprite[nTarget].sectnum) {
        mychangespritesect(nSprite, sprite[nTarget].sectnum);
    }
}

void FuncRa(int a, int UNUSED(nDamage), int nRun)
{
    short nPlayer = RunData[nRun].nVal;
    short nCurrentWeapon = PlayerList[nPlayer].nCurrentWeapon;

    short nSeq = SeqOffsets[kSeqEyeHit] + ActionSeq[Ra[nPlayer].nAction].a;
    short nSprite = Ra[nPlayer].nSprite;

    bool bVal = false;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        default:
        {
            DebugOut("unknown msg %d for Ra\n", nMessage);
            return;
        }

        case 0x30000:
        case 0xA0000:
            return;

        case 0x20000:
        {
            Ra[nPlayer].nTarget = sPlayerInput[nPlayer].nTarget;
            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, Ra[nPlayer].nFrame);

            if (Ra[nPlayer].nAction)
            {
                seq_MoveSequence(nSprite, nSeq, Ra[nPlayer].nFrame);

                Ra[nPlayer].nFrame++;
                if (Ra[nPlayer].nFrame >= SeqSize[nSeq])
                {
                    Ra[nPlayer].nFrame = 0;
                    bVal = true;
                }
            }

            switch (Ra[nPlayer].nAction)
            {
                case 0:
                {
                    MoveRaToEnemy(nPlayer);

                    if (!Ra[nPlayer].field_C || Ra[nPlayer].nTarget <= -1)
                    {
                        sprite[nSprite].cstat = 0x8000;
                    }
                    else
                    {
                        sprite[nSprite].cstat &= 0x7FFF;
                        Ra[nPlayer].nAction = 1;
                        Ra[nPlayer].nFrame  = 0;
                    }

                    return;
                }

                case 1:
                {
                    if (!Ra[nPlayer].field_C)
                    {
                        Ra[nPlayer].nAction = 3;
                        Ra[nPlayer].nFrame  = 0;
                    }
                    else
                    {
                        if (bVal) {
                            Ra[nPlayer].nAction = 2;
                        }

                        MoveRaToEnemy(nPlayer);
                    }

                    return;
                }

                case 2:
                {
                    MoveRaToEnemy(nPlayer);

                    if (nCurrentWeapon != kWeaponRing)
                    {
                        Ra[nPlayer].nAction = 3;
                        Ra[nPlayer].nFrame  = 0;
                    }
                    else
                    {
                        if (Ra[nPlayer].nFrame || Ra[nPlayer].nTarget <= -1)
                        {
                            if (!bVal) {
                                return;
                            }

                            Ra[nPlayer].nAction = 3;
                            Ra[nPlayer].nFrame  = 0;
                        }
                        else
                        {
                            if (PlayerList[nPlayer].nAmmo[kWeaponRing] > 0)
                            {
                                runlist_DamageEnemy(Ra[nPlayer].nTarget, PlayerList[Ra[nPlayer].nPlayer].nSprite, BulletInfo[kWeaponRing].nDamage);
                                AddAmmo(nPlayer, kWeaponRing, -1); // remove '1' from ammo count
                                SetQuake(nSprite, 100);
                            }
                            else
                            {
                                Ra[nPlayer].nAction = 3;
                                Ra[nPlayer].nFrame  = 0;
                                SelectNewWeapon(nPlayer);
                            }
                        }
                    }

                    return;
                }

                case 3:
                {
                    if (bVal)
                    {
                        sprite[nSprite].cstat |= 0x8000;
                        Ra[nPlayer].nAction = 0;
                        Ra[nPlayer].nFrame  = 0;
                        Ra[nPlayer].field_C = 0;
                    }

                    return;
                }

                default:
                    return;
            }
        }

        case 0x90000:
        {
            short nSprite2 = a & 0xFFFF;
            seq_PlotSequence(nSprite2, nSeq, Ra[nPlayer].nFrame, 1);
            tsprite[nSprite2].owner = -1;
            return;
        }
    }
}

class RaLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void RaLoadSave::Load()
{
    Read(Ra, sizeof(Ra));
}

void RaLoadSave::Save()
{
    Write(Ra, sizeof(Ra));
}

static RaLoadSave* myLoadSave;

void RaLoadSaveConstruct()
{
    myLoadSave = new RaLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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

#include "aistuff.h"

struct RA
{
    short nAction;
    short nFrame;
    short nRun;
    short nSprite;
    short nTarget;
    short field_A;
    short field_C;
    short nPlayer;
};

extern RA Ra[];

void FreeRa(short nPlayer);
int BuildRa(short nPlayer);
void InitRa();
void MoveRaToEnemy(short nPlayer);
void FuncRa(int, int, int);

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2020 EDuke32 developers and contributors
Copyright (C) 2020 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 "ramses.h"
#include "engine.h"
#include "exhumed.h"
#include "random.h"
#include "cd.h"
#include "sound.h"
#include "view.h"
#include "light.h"
#include "lighting.h"
#include "names.h"

#define kSpiritX   106
#define kSpiritY   97

short cPupData[300];
uint8_t worktile[(kSpiritY * 2) * (kSpiritX * 2)] = {0};
int lHeadStartClock;
short* pPupData;
int lNextStateChange;
int nPixels;
int nHeadTimeStart;
short nHeadStage;
short curx[kSpiritY * kSpiritX];
short cury[kSpiritY * kSpiritX];
int8_t destvelx[kSpiritY * kSpiritX];
int8_t destvely[kSpiritY * kSpiritX];
uint8_t pixelval[kSpiritY * kSpiritX];
int8_t origy[kSpiritY * kSpiritX];
int8_t origx[kSpiritY * kSpiritX];
int8_t velx[kSpiritY * kSpiritX];
int8_t vely[kSpiritY * kSpiritX];
short nMouthTile;

short nPupData = 0;
buildvfs_kfd headfd = buildvfs_kfd_invalid;

short word_964E8 = 0;
short word_964EA = 0;
short word_964EC = 10;

short nSpiritRepeatX;
short nSpiritRepeatY;
short nSpiritSprite;
short nPixelsToShow;
short nTalkTime = 0;

void InitSpiritHead()
{
    char filename[20];

    nPixels = 0;

    nSpiritRepeatX = sprite[nSpiritSprite].xrepeat;
    nSpiritRepeatY = sprite[nSpiritSprite].yrepeat;

    if (!waloff[kTileRamsesNormal]) tileLoad(kTileRamsesNormal); // Ramses Normal Head

    for (int i = 0; i < kMaxSprites; i++)
    {
        if (sprite[i].statnum) {
            sprite[i].cstat |= 0x8000;
        }
    }

    uint8_t* pTile = (uint8_t*)waloff[kTileRamsesNormal];

    for (int x = 0; x < kSpiritY; x++)
    {
        for (int y = 0; y < kSpiritX; y++)
        {
            if (*pTile != 255)
            {
                pixelval[nPixels] = *(uint8_t*)(waloff[kTileRamsesGold] + x * kSpiritX + y);
                origx[nPixels] = x - 48;
                origy[nPixels] = y - 53;
                curx[nPixels] = 0;
                cury[nPixels] = 0;
                vely[nPixels] = 0;
                velx[nPixels] = 0;

                destvelx[nPixels] = RandomSize(2) + 1;

                if (curx[nPixels] > 0) {
                    destvelx[nPixels] = -destvelx[nPixels];
                }

                destvely[nPixels] = RandomSize(2) + 1;

                if (cury[nPixels] > 0) {
                    destvely[nPixels] = -destvely[nPixels];
                }

                nPixels++;
            }

            pTile++;
        }
    }

    waloff[kTileRamsesWorkTile] = (intptr_t)worktile;

    sprite[nSpiritSprite].yrepeat = 140;
    sprite[nSpiritSprite].xrepeat = 140;
    sprite[nSpiritSprite].picnum = kTileRamsesWorkTile;

    nHeadStage = 0;

    // work tile is twice as big as the normal head size
    tilesiz[kTileRamsesWorkTile].x = kSpiritY * 2;
    tilesiz[kTileRamsesWorkTile].y = kSpiritX * 2;

    sprite[nSpiritSprite].cstat &= 0x7FFF;

    nHeadTimeStart = (int)totalclock;

    memset(worktile, -1, sizeof(worktile));
    tileInvalidate(kTileRamsesWorkTile, -1, -1);

    nPixelsToShow = 0;

    fadecdaudio();

    int nTrack;

    if (levelnum == 1) {
        nTrack = 3;
    }
    else {
        nTrack = 7;
    }

    bSubTitles = playCDtrack(nTrack, false) == 0;

    StartSwirlies();

    sprintf(filename, "LEV%d.PUP", levelnum);
    lNextStateChange = (int)totalclock;
    lHeadStartClock = (int)totalclock;

    headfd = kopen4loadfrommod(filename, 0);
    nPupData = kread(headfd, cPupData, sizeof(cPupData));
    #if B_BIG_ENDIAN == 1
    for (int i = 0; i < ARRAY_SIZE(cPupData); i++)
    {
        cPupData[i] = B_LITTLE16(cPupData[i]);
    }
    #endif
    pPupData = cPupData;
    kclose(headfd);
    headfd = buildvfs_kfd_invalid;
    nMouthTile = 0;
    nTalkTime = 1;
}

void DimSector(short nSector)
{
    short startwall = sector[nSector].wallptr;
    short nWalls = sector[nSector].wallnum;

    for (int i = 0; i < nWalls; i++)
    {
        if (wall[startwall + i].shade < 40) {
            wall[startwall + i].shade++;
        }
    }

    if (sector[nSector].floorshade < 40) {
        sector[nSector].floorshade++;
    }

    if (sector[nSector].ceilingshade < 40) {
        sector[nSector].ceilingshade++;
    }
}

void CopyHeadToWorkTile(short nTile)
{
    if (!waloff[nTile]) tileLoad(nTile);

    uint8_t* pSrc = (uint8_t*)waloff[nTile];
    uint8_t* pDest = (uint8_t*)&worktile[212 * 49 + 53];

    for (int i = 0; i < kSpiritY; i++)
    {
        memcpy(pDest, pSrc, kSpiritX);

        pDest += 212;
        pSrc += kSpiritX;
    }
}

int DoSpiritHead()
{
    static short word_964E6 = 0;

    nVertPan[0] += (nDestVertPan[0] - nVertPan[0]) / 4;

    tileInvalidate(kTileRamsesWorkTile, -1, -1);

    if (nHeadStage < 2) {
        memset(worktile, -1, sizeof(worktile));
    }

    if (nHeadStage < 2 || nHeadStage != 5)
    {
        nPixelsToShow = ((int)totalclock - nHeadTimeStart) * 15;

        if (nPixelsToShow > nPixels) {
            nPixelsToShow = nPixels;
        }

        if (nHeadStage < 3)
        {
            UpdateSwirlies();

            if (sprite[nSpiritSprite].shade > -127) {
                sprite[nSpiritSprite].shade--;
            }

            word_964E6--;
            if (word_964E6 < 0)
            {
                DimSector(sprite[nSpiritSprite].sectnum);
                word_964E6 = 5;
            }

            if (!nHeadStage)
            {
                if (((int)totalclock - nHeadTimeStart) > 480)
                {
                    nHeadStage = 1;
                    nHeadTimeStart = (int)totalclock + 480;
                }

                for (int i = 0; i < nPixelsToShow; i++)
                {
                    if (destvely[i] >= 0)
                    {
                        vely[i]++;

                        if (vely[i] >= destvely[i]) {
                            destvely[i] = -(RandomSize(2) + 1);
                        }
                    }
                    else
                    {
                        vely[i]--;

                        if (vely[i] <= destvely[i]) {
                            destvely[i] = RandomSize(2) + 1;
                        }
                    }

                    if (destvelx[i] >= 0)
                    {
                        velx[i]++;

                        if (velx[i] >= destvelx[i]) {
                            destvelx[i] = -(RandomSize(2) + 1);
                        }
                    }
                    else
                    {
                        velx[i]--;

                        if (velx[i] <= destvelx[i]) {
                            destvelx[i] = RandomSize(2) + 1;
                        }
                    }

                    int esi = vely[i] + (cury[i] >> 8);

                    if (esi < kSpiritX)
                    {
                        if (esi < -105)
                        {
                            vely[i] = 0;
                            esi = 0;
                        }
                    }
                    else
                    {
                        vely[i] = 0;
                        esi = 0;
                    }

                    int ebx = velx[i] + (curx[i] >> 8);

                    if (ebx < kSpiritY)
                    {
                        if (ebx < -96)
                        {
                            velx[i] = 0;
                            ebx = 0;
                        }
                    }
                    else
                    {
                        velx[i] = 0;
                        ebx = 0;
                    }

                    curx[i] = ebx * 256;
                    cury[i] = esi * 256;

                    esi += (ebx + kSpiritY) * 212;

                    worktile[kSpiritX + esi] = pixelval[i];
                }

                return 1;
            }
            else
            {
                if (nHeadStage != 1) {
                    return 1;
                }

                uint8_t nXRepeat = sprite[nSpiritSprite].xrepeat;
                if (nXRepeat > nSpiritRepeatX)
                {
                    sprite[nSpiritSprite].xrepeat -= 2;

                    nXRepeat = sprite[nSpiritSprite].xrepeat;
                    if (nXRepeat < nSpiritRepeatX) {
                        sprite[nSpiritSprite].xrepeat = nSpiritRepeatX;
                    }
                }

                uint8_t nYRepeat = sprite[nSpiritSprite].yrepeat;
                if (nYRepeat > nSpiritRepeatY)
                {
                    sprite[nSpiritSprite].yrepeat -= 2;

                    nYRepeat = sprite[nSpiritSprite].yrepeat;
                    if (nYRepeat < nSpiritRepeatY) {
                        sprite[nSpiritSprite].yrepeat = nSpiritRepeatY;
                    }
                }

                int esi = 0;

                for (int i = 0; i < nPixels; i++)
                {
                    int eax = (origx[i] << 8) - curx[i];
                    int ecx = eax;

                    if (eax)
                    {
                        if (eax < 0) {
                            eax = -eax;
                        }

                        if (eax < 8)
                        {
                            curx[i] = origx[i] << 8;
                            ecx = 0;
                        }
                        else {
                            ecx >>= 3;
                        }
                    }
                    else
                    {
                        ecx >>= 3;
                    }

                    int var_1C = (origy[i] << 8) - cury[i];
                    int ebp = var_1C;

                    if (var_1C)
                    {
                        eax = ebp;

                        if (eax < 0) {
                            eax = -eax;
                        }

                        if (eax < 8)
                        {
                            cury[i] = origy[i] << 8;
                            var_1C = 0;
                        }
                        else {
                            var_1C >>= 3;
                        }
                    }
                    else
                    {
                        var_1C >>= 3;
                    }

                    if (var_1C || ecx)
                    {
                        curx[i] += ecx;
                        cury[i] += var_1C;

                        esi++;
                    }

                    ecx = (((curx[i] >> 8) + kSpiritY) * 212) + (cury[i] >> 8);

                    worktile[kSpiritX + ecx] = pixelval[i];
                }

                if (((int)totalclock - lHeadStartClock) > 600)
                {
                    CopyHeadToWorkTile(kTileRamsesGold);
                }

                int eax = ((nPixels << 4) - nPixels) / 16;

                if (esi < eax)
                {
                    SoundBigEntrance();
                    AddGlow(sprite[nSpiritSprite].sectnum, 20);
                    AddFlash(
                        sprite[nSpiritSprite].sectnum,
                        sprite[nSpiritSprite].x,
                        sprite[nSpiritSprite].y,
                        sprite[nSpiritSprite].z,
                        128);

                    nHeadStage = 3;
                    TintPalette(255, 255, 255);
                    CopyHeadToWorkTile(kTileRamsesNormal);
                }

                return 1;
            }
        }
        else
        {
            FixPalette();

            if (!nPalDiff)
            {
                nFreeze = 2;
                nHeadStage++;
            }

            return 0;
        }
    }
    else
    {
        if (lNextStateChange <= (int)totalclock)
        {
            if (nPupData)
            {
                short nPupVal = *pPupData;
                pPupData++;
                nPupData -= 2;

                if (nPupData > 0)
                {
                    lNextStateChange = (nPupVal + lHeadStartClock) - 10;
                    nTalkTime = !nTalkTime;
                }
                else
                {
                    nTalkTime = 0;
                    nPupData = 0;
                }
            }
            else if (!bSubTitles)
            {
                if (!CDplaying())
                {
                    levelnew = levelnum + 1;
                    fadecdaudio();
                }
            }
        }

        word_964E8--;
        if (word_964E8 <= 0)
        {
            word_964EA = RandomBit() * 2;
            word_964E8 = RandomSize(5) + 4;
        }

        int ebx = 592;
        word_964EC--;

        if (word_964EC < 3)
        {
            ebx = 593;
            if (word_964EC <= 0) {
                word_964EC = RandomSize(6) + 4;
            }
        }

        ebx += word_964EA;

        if (!waloff[ebx]) tileLoad(ebx);

        uint8_t* pDest = (uint8_t*)&worktile[10441];
        uint8_t* pSrc = (uint8_t*)waloff[ebx];

        for (int i = 0; i < kSpiritY; i++)
        {
            memcpy(pDest, pSrc, kSpiritX);

            pDest += 212;
            pSrc += kSpiritX;
        }

        if (nTalkTime)
        {
            if (nMouthTile < 2) {
                nMouthTile++;
            }
        }
        else if (nMouthTile != 0)
        {
            nMouthTile--;
        }

        if (nMouthTile)
        {
            short nTile = nMouthTile + 598;

            if (!waloff[nTile]) tileLoad(nTile);

            short nTileSizeX = tilesiz[nTile].x;
            short nTileSizeY = tilesiz[nTile].y;

            uint8_t* pDest = (uint8_t*)&worktile[212 * (kSpiritY - nTileSizeX / 2)] + (159 - nTileSizeY);
            uint8_t* pSrc = (uint8_t*)waloff[nTile];

            while (nTileSizeX > 0)
            {
                memcpy(pDest, pSrc, nTileSizeY);

                nTileSizeX--;
                pDest += 212;
                pSrc += nTileSizeY;
            }
        }

        return 1;
    }

    return 0;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2020 EDuke32 developers and contributors
Copyright (C) 2020 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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

extern short nSpiritSprite;
extern short nHeadStage;

void InitSpiritHead();
int DoSpiritHead();

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 "random.h"
#include "save.h"

static int randseed = 0x1010101;

void InitRandom()
{
    randseed = 0x1010101;
}

int RandomBit()
{
    randseed = (randseed >> 1) | ((((randseed >> 1) ^ (randseed >> 28)) & 1) << 28);
    return randseed & 1;
}

char RandomByte()
{
    char randByte = RandomBit() << 7;
    randByte |= RandomBit() << 6;
    randByte |= RandomBit() << 5;
    randByte |= RandomBit() << 4;
    randByte |= RandomBit() << 3;
    randByte |= RandomBit() << 2;
    randByte |= RandomBit() << 1;
    randByte |= RandomBit();
    return randByte;
}

uint16_t RandomWord()
{
    short randWord = RandomByte() << 8;
    randWord |= RandomByte();
    return randWord;
}

int RandomLong()
{
    int randLong = RandomWord() << 16;
    randLong |= RandomWord();
    return randLong;
}

int RandomSize(int nSize)
{
    int randSize = 0;

    while (nSize > 0)
    {
        randSize = randSize * 2 | RandomBit();
        nSize--;
    }

    return randSize;
}

class RandomLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void RandomLoadSave::Load()
{
    Read(&randseed, sizeof(randseed));
}

void RandomLoadSave::Save()
{
    Write(&randseed, sizeof(randseed));
}

static RandomLoadSave* myLoadSave;

void RandomLoadSaveConstruct()
{
    myLoadSave = new RandomLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 __random_h__
#define __random_h__
#include "compat.h"

void InitRandom();
int RandomBit();
char RandomByte();
uint16_t RandomWord();
int RandomLong();
int RandomSize(int nSize);

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 "rat.h"
#include "engine.h"
#include "sequence.h"
#include "runlist.h"
#include "random.h"
#include "view.h"
#include "init.h"
#include "exhumed.h"
#include "move.h"
#include "save.h"
#include <assert.h>

#define kMaxRats    50

short nMinChunk;
short nPlayerPic;
short nRatCount;
short nMaxChunk;

struct Rat
{
    short nFrame;
    short nAction;
    short nSprite;
    short nRun;
    short nTarget;
    short f;
    short g;
};

Rat RatList[kMaxRats];

static actionSeq ActionSeq[] = {
    {0, 1},
    {1, 0},
    {1, 0},
    {9, 1},
    {0, 1}
};

void InitRats()
{
    nRatCount = 0;
    nMinChunk = 9999;
    nMaxChunk = -1;

    for (int i = 122; i <= 131; i++)
    {
        int nPic = seq_GetSeqPicnum(kSeqJoe, i, 0);

        if (nPic < nMinChunk)
            nMinChunk = nPic;

        if (nPic > nMaxChunk)
            nMaxChunk = nPic;
    }

    nPlayerPic = seq_GetSeqPicnum(kSeqJoe, 120, 0);
}

void SetRatVel(short nSprite)
{
    sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
    sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;
}

int BuildRat(short nSprite, int x, int y, int z, short nSector, int nAngle)
{
    if (nRatCount >= kMaxRats) {
        return -1;
    }

    short nRat = nRatCount++;

    if (nSprite < 0)
    {
        nSprite = insertsprite(nSector, 108);
        assert(nSprite >= 0 && nSprite < kMaxSprites);

        sprite[nSprite].x = x;
        sprite[nSprite].y = y;
        sprite[nSprite].z = z;
    }
    else
    {
        nAngle = sprite[nSprite].ang;
        changespritestat(nSprite, 108);
    }

    sprite[nSprite].cstat = 0x101;
    sprite[nSprite].shade = -12;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
    sprite[nSprite].clipdist = 30;
    sprite[nSprite].ang = nAngle;
    sprite[nSprite].xrepeat = 50;
    sprite[nSprite].yrepeat = 50;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].extra = -1;

    if (nAngle >= 0) {
        RatList[nRat].nAction = 2;
    }
    else {
        RatList[nRat].nAction = 4;
    }

    RatList[nRat].nFrame = 0;
    RatList[nRat].nSprite = nSprite;
    RatList[nRat].nTarget = -1;
    RatList[nRat].f = RandomSize(5);
    RatList[nRat].g = RandomSize(3);

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nRat, kRunRat);

    RatList[nRat].nRun = runlist_AddRunRec(NewRun, nRat, kRunRat);

    return nRat;
}

int FindFood(short nSprite)
{
    short nSector = sprite[nSprite].sectnum;
    int x = sprite[nSprite].x;
    int y = sprite[nSprite].y;
    int z = sprite[nSprite].z;

    int z2 = (z + sector[nSector].ceilingz) / 2;

    if (nChunkTotal)
    {
        int nSprite2 = nChunkSprite[RandomSize(7) % nChunkTotal];
        if (nSprite2 != -1)
        {
            if (cansee(x, y, z2, nSector, sprite[nSprite2].x, sprite[nSprite2].y, sprite[nSprite2].z, sprite[nSprite2].sectnum)) {
                return nSprite2;
            }
        }
    }

    if (!nBodyTotal) {
        return -1;
    }

    int nSprite2 = nBodySprite[RandomSize(7) % nBodyTotal];
    if (nSprite2 != -1)
    {
        if (nPlayerPic == sprite[nSprite2].picnum)
        {
            if (cansee(x, y, z, nSector, sprite[nSprite2].x, sprite[nSprite2].y, sprite[nSprite2].z, sprite[nSprite2].sectnum)) {
                return nSprite2;
            }
        }
    }

    return -1;
}

void FuncRat(int a, int nDamage, int nRun)
{
    short nRat = RunData[nRun].nVal;
    short nSprite = RatList[nRat].nSprite;
    short nAction = RatList[nRat].nAction;

    bool bVal = false;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        default:
        {
            DebugOut("unknown msg %d for Rathead\n", nMessage);
            return;
        }

        case 0xA0000:
        {
            nDamage = runlist_CheckRadialDamage(nSprite);
            // fall through to 0x80000
            fallthrough__;
        }
        case 0x80000:
        {
            if (nDamage)
            {
                sprite[nSprite].cstat = 0;
                sprite[nSprite].xvel = 0;
                sprite[nSprite].yvel = 0;
                RatList[nRat].nAction = 3;
                RatList[nRat].nFrame = 0;
            }
            return;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqRat] + ActionSeq[nAction].a, RatList[nRat].nFrame, ActionSeq[nAction].b);
            return;
        }

        case 0x20000:
        {
            int nSeq = SeqOffsets[kSeqRat] + ActionSeq[nAction].a;
            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, RatList[nRat].nFrame);

            seq_MoveSequence(nSprite, nSeq, RatList[nRat].nFrame);

            RatList[nRat].nFrame++;
            if (RatList[nRat].nFrame >= SeqSize[nSeq])
            {
                bVal = true;
                RatList[nRat].nFrame = 0;
            }

            short nTarget = RatList[nRat].nTarget;

            Gravity(nSprite);

            switch (nAction)
            {
                default:
                    return;

                case 0:
                {
                    RatList[nRat].f--;
                    if (RatList[nRat].f > 0) {
                        return;
                    }

                    int xVal = klabs(sprite[nSprite].x - sprite[nTarget].x);
                    int yVal = klabs(sprite[nSprite].y - sprite[nTarget].y);

                    if (xVal > 50 || yVal > 50)
                    {
                        RatList[nRat].nAction = 2;
                        RatList[nRat].nFrame  = 0;
                        RatList[nRat].nTarget = -1;

                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                        return;
                    }

                    RatList[nRat].nFrame ^= 1;
                    RatList[nRat].f = RandomSize(5) + 4;
                    RatList[nRat].g--;

                    if (RatList[nRat].g <= 0)
                    {
                        short nFoodSprite = FindFood(nSprite);
                        if (nFoodSprite == -1) {
                            return;
                        }

                        RatList[nRat].nTarget = nFoodSprite;

                        PlotCourseToSprite(nSprite, nFoodSprite);
                        SetRatVel(nSprite);

                        RatList[nRat].nAction = 1;
                        RatList[nRat].g = 900;
                        RatList[nRat].nFrame = 0;
                    }

                    return;
                }
                case 1:
                {
                    RatList[nRat].g--;

                    if (RatList[nRat].g <= 0)
                    {
                        RatList[nRat].nAction = 2;
                        RatList[nRat].nFrame = 0;
                        RatList[nRat].nTarget = -1;

                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                    }

                    MoveCreature(nSprite);

                    int xVal = klabs(sprite[nSprite].x - sprite[nTarget].x);
                    int yVal = klabs(sprite[nSprite].y - sprite[nTarget].y);

                    if (xVal >= 50 || yVal >= 50)
                    {
                        RatList[nRat].f--;
                        if (RatList[nRat].f < 0)
                        {
                            PlotCourseToSprite(nSprite, nTarget);
                            SetRatVel(nSprite);

                            RatList[nRat].f = 32;
                        }

                        return;
                    }

                    RatList[nRat].nAction = 0;
                    RatList[nRat].nFrame  = 0;
                    RatList[nRat].g = RandomSize(3);

                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                    return;
                }
                case 2:
                {
                    if (sprite[nSprite].xvel || sprite[nSprite].yvel || sprite[nSprite].zvel) {
                        MoveCreature(nSprite);
                    }

                    RatList[nRat].f--;
                    if (RatList[nRat].f <= 0)
                    {
                        RatList[nRat].nTarget = FindFood(nSprite);

                        if (RatList[nRat].nTarget <= -1)
                        {
                            RatList[nRat].f = RandomSize(6);
                            if (sprite[nSprite].xvel || sprite[nSprite].yvel)
                            {
                                sprite[nSprite].xvel = 0;
                                sprite[nSprite].yvel = 0;
                                return;
                            }

                            sprite[nSprite].ang = RandomSize(11);
                            SetRatVel(nSprite);
                            return;
                        }
                        else
                        {
                            PlotCourseToSprite(nSprite, RatList[nRat].nTarget);
                            SetRatVel(nSprite);
                            RatList[nRat].nAction = 1;
                            RatList[nRat].g = 900;
                            RatList[nRat].nFrame = 0;
                            return;
                        }
                    }

                    return;
                }
                case 3:
                {
                    if (bVal)
                    {
                        runlist_DoSubRunRec(sprite[nSprite].owner);
                        runlist_FreeRun(sprite[nSprite].lotag - 1);
                        runlist_SubRunRec(RatList[nRat].nRun);

                        sprite[nSprite].cstat = 0x8000;
                        mydeletesprite(nSprite);
                    }
                    return;
                }
            }

            break;
        }
    }
}

class RatLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void RatLoadSave::Load()
{
    Read(&nMinChunk, sizeof(nMinChunk));
    Read(&nPlayerPic, sizeof(nPlayerPic));
    Read(&nRatCount, sizeof(nRatCount));
    Read(&nMaxChunk, sizeof(nMaxChunk));
    Read(RatList, sizeof(Rat) * nRatCount);
}

void RatLoadSave::Save()
{
    Write(&nMinChunk, sizeof(nMinChunk));
    Write(&nPlayerPic, sizeof(nPlayerPic));
    Write(&nRatCount, sizeof(nRatCount));
    Write(&nMaxChunk, sizeof(nMaxChunk));
    Write(RatList, sizeof(Rat) * nRatCount);
}

static RatLoadSave* myLoadSave;

void RatLoadSaveConstruct()
{
    myLoadSave = new RatLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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

#include "aistuff.h"

void InitRats();
void SetRatVel(short nSprite);
int BuildRat(short nSprite, int x, int y, int z, short nSector, int nAngle);
int FindFood(short nSprite);
void FuncRat(int a, int b, int nRun);

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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 "compat.h"
#include "record.h"
#include "typedefs.h"
#include "save.h"
#include <stdio.h>
#include <string.h>

short record_mode = 0;
int record_limit = -1;
int record_index = 16384;
uint8_t record_buffer[16384];
FILE *record_file;

struct RecordHeader
{
    char signature[4];
    short b;
};

RecordHeader record_head;

uint8_t GetRecord()
{
    if (record_index >= 16384)
    {
        record_index = 0;

        int nRead = fread(record_buffer, 1, 16384, record_file);

        if (nRead < 16384) {
            record_limit = 16384;
        }
        else {
            record_limit = -1;
        }
    }

    if (record_limit > 0)
    {
        if (record_limit <= record_index)
        {
            record_mode = 3;
        }
    }

    return record_buffer[record_index++];
}

void PutRecord(uint8_t record)
{
    uint8_t val_10 = record;

    if (record_index >= 16384)
    {
        record_index = 0;

        fwrite(record_buffer, 16384, 1, record_file);
    }

    record_buffer[record_index++] = val_10;
}

int OpenRecord(const char *filename, short *edx)
{
    record_file = fopen(filename, "rb");
    if (record_file)
    {
        if (!fread(&record_head, sizeof(record_head), 1, record_file)) {
            return 0;
        }

        if (memcmp(record_head.signature, "LOBO", 4) == 0) {
            return 0;
        }

        *edx = record_head.b;

        record_index = 16384;
        record_limit = -1;
        record_mode  = 2;

        return 1;
    }
    else
    {
        record_file = fopen(filename, "wb");
        if (!record_file) {
            return 0;
        }

        strncpy(record_head.signature, "LOBO", 4);
        record_head.b = *edx;

        if (!fwrite(&record_head, sizeof(record_head), 1, record_file)) {
            return 0;
        }

        record_index = 0;
        record_limit = -1;
        record_mode = 1;

        return 1;
    }
}

int ExecRecord(uint8_t *pRecord, int nSize)
{
    if (record_mode == 2)
    {
        for (int i = 0; i < nSize; i++)
        {
            pRecord[i] = GetRecord();
        }
    }
    else if (record_mode == 1)
    {
        for (int i = 0; i < nSize; i++)
        {
            PutRecord(pRecord[i]);
        }
    }

    if (record_mode == 3) {
        return 0;
    }
    else {
        return 1;
    }
}

int CloseRecord()
{
    if (record_mode == 1)
    {
        loadgame(0);

        if (record_index)
        {
            if (!fwrite(record_buffer, record_index, 1, record_file)) {
                return 0;
            }
        }
    }
    else if (record_mode == 2 || record_mode == 3)
    {
        loadgame(1);
    }

    fclose(record_file);
    return 1;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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

extern short record_mode;

#endif