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 "engine.h"
#include "exhumed.h"
#include "move.h"
#include "init.h"
#include "lighting.h"
#include "ramses.h"
#include "bubbles.h"
#include "object.h"
#include "player.h"
#include "view.h"
#include "status.h"
#include "runlist.h"
#include "items.h"
#include "sound.h"
#include "trigdat.h"
#include "anims.h"
#include "random.h"
#include "bullet.h"
#include "save.h"
#include <string.h>
#include <assert.h>
#ifndef __WATCOMC__
//#include <cmath>
#else
#include <stdlib.h>
//#include <math.h>
#endif

#define kMaxPushBlocks	100
#define kMaxChunks	75

short NearCount = -1;
short NearSector[kMaxSectors] = { 0 };

short nPushBlocks;
BlockInfo sBlockInfo[kMaxPushBlocks];

// TODO - moveme?
short overridesect;
short nBodySprite[50];
int hihit, sprceiling, sprfloor, lohit;

short nChunkSprite[kMaxChunks];

signed int lsqrt(int a1)
{
    int v1;
    int v2;
    signed int result;

    v1 = a1;
    v2 = a1 - 0x40000000;

    result = 0;

    if (v2 >= 0)
    {
        result = 32768;
        v1 = v2;
    }
    if (v1 - ((result << 15) + 0x10000000) >= 0)
    {
        v1 -= (result << 15) + 0x10000000;
        result += 16384;
    }
    if (v1 - ((result << 14) + 0x4000000) >= 0)
    {
        v1 -= (result << 14) + 0x4000000;
        result += 8192;
    }
    if (v1 - ((result << 13) + 0x1000000) >= 0)
    {
        v1 -= (result << 13) + 0x1000000;
        result += 4096;
    }
    if (v1 - ((result << 12) + 0x400000) >= 0)
    {
        v1 -= (result << 12) + 0x400000;
        result += 2048;
    }
    if (v1 - ((result << 11) + 0x100000) >= 0)
    {
        v1 -= (result << 11) + 0x100000;
        result += 1024;
    }
    if (v1 - ((result << 10) + 0x40000) >= 0)
    {
        v1 -= (result << 10) + 0x40000;
        result += 512;
    }
    if (v1 - ((result << 9) + 0x10000) >= 0)
    {
        v1 -= (result << 9) + 0x10000;
        result += 256;
    }
    if (v1 - ((result << 8) + 0x4000) >= 0)
    {
        v1 -= (result << 8) + 0x4000;
        result += 128;
    }
    if (v1 - ((result << 7) + 4096) >= 0)
    {
        v1 -= (result << 7) + 4096;
        result += 64;
    }
    if (v1 - ((result << 6) + 1024) >= 0)
    {
        v1 -= (result << 6) + 1024;
        result += 32;
    }
    if (v1 - (32 * result + 256) >= 0)
    {
        v1 -= 32 * result + 256;
        result += 16;
    }
    if (v1 - (16 * result + 64) >= 0)
    {
        v1 -= 16 * result + 64;
        result += 8;
    }
    if (v1 - (8 * result + 16) >= 0)
    {
        v1 -= 8 * result + 16;
        result += 4;
    }
    if (v1 - (4 * result + 4) >= 0)
    {
        v1 -= 4 * result + 4;
        result += 2;
    }
    if (v1 - (2 * result + 1) >= 0)
        result += 1;

    return result;
}

void MoveThings()
{
    UndoFlashes();
    DoLights();

    if (nFreeze)
    {
        if (nFreeze == 1 || nFreeze == 2) {
            DoSpiritHead();
        }
    }
    else
    {
        runlist_ExecObjects();
        runlist_CleanRunRecs();
    }

    MoveStatus();
    DoBubbleMachines();
    DoDrips();
    DoMovingSects();
    DoRegenerates();

    if (nCameraDist >= 0)
    {
        nCameraDist = min(nCameraDist + (((int32_t)totalclock - nCameraClock) << 10), 65536);
        nCameraClock = (int32_t)totalclock;
    }

    if (levelnum == kMap20)
    {
        DoFinale();
        if (lCountDown < 1800 && nDronePitch < 2400 && !lFinaleStart)
        {
            nDronePitch += 64;
            BendAmbientSound();
        }
    }
}

void ResetMoveFifo()
{
    localclock = (int)totalclock;
    movefifoend = 0;
    movefifopos = 0;
}

// not used
void clipwall()
{

}

void BuildNear(int x, int y, int walldist, int nSector)
{
    NearSector[0] = nSector;
    NearCount = 1;

    int i = 0;

    while (i < NearCount)
    {
        short nSector = NearSector[i];

        short nWall = sector[nSector].wallptr;
        short nWallCount = sector[nSector].wallnum;

        while (1)
        {
            nWallCount--;
            if (nWallCount < 0)
            {
                i++;
                break;
            }

            short nNextSector = wall[nWall].nextsector;

            if (nNextSector >= 0)
            {
                int j = 0;
                for (; j < NearCount; j++)
                {
                    // loc_14F4D:
                    if (nNextSector == NearSector[j])
                        break;
                }

                if (j >= NearCount)
                {
                    vec2_t pos = { x, y };
                    if (clipinsidebox(pos, nWall, walldist))
                    {
                        NearSector[NearCount] = wall[nWall].nextsector;
                        NearCount++;
                    }
                }
            }

            nWall++;
        }
    }
}

int BelowNear(short nSprite)
{
    short nSector = sprite[nSprite].sectnum;
    int z = sprite[nSprite].z;

    int var_24, z2;

    if ((lohit & 0xC000) == 0xC000)
    {
        var_24 = lohit & 0xC000;
        z2 = sprite[lohit & 0x3FFF].z;
    }
    else
    {
        var_24 = 0x20000;
        z2 = sector[nSector].floorz + SectDepth[nSector];

        if (NearCount > 0)
        {
            short edx;

            for (int i = 0; i < NearCount; i++)
            {
                int nSect2 = NearSector[i];

                while (nSect2 >= 0)
                {
                    edx = nSect2;
                    nSect2 = SectBelow[nSect2];
                }

                int ecx = sector[edx].floorz + SectDepth[edx];
                int eax = ecx - z;

                if (eax < 0 && eax >= -5120)
                {
                    z2 = ecx;
                    nSector = edx;
                }
            }
        }
    }

    if (z2 < sprite[nSprite].z)
    {
        sprite[nSprite].z = z2;
        overridesect = nSector;
        sprite[nSprite].zvel = 0;

        bTouchFloor = kTrue;

        return var_24;
    }
    else
    {
        return 0;
    }
}

int movespritez(short nSprite, int z, int height, int UNUSED(flordist), int clipdist)
{
    spritetype* pSprite = &sprite[nSprite];
    short nSector = pSprite->sectnum;
    assert(nSector >= 0 && nSector < kMaxSectors);

    overridesect = nSector;
    short edi = nSector;

    // backup cstat
    uint16_t cstat = pSprite->cstat;

    pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;

    int nRet = 0;

    short nSectFlags = SectFlag[nSector];

    if (nSectFlags & kSectUnderwater) {
        z >>= 1;
    }

    int spriteZ = pSprite->z;
    int floorZ = sector[nSector].floorz;

    int ebp = spriteZ + z;
    int eax = sector[nSector].ceilingz + (height >> 1);

    if ((nSectFlags & kSectUnderwater) && ebp < eax) {
        ebp = eax;
    }

    // loc_151E7:
    while (ebp > sector[pSprite->sectnum].floorz && SectBelow[pSprite->sectnum] >= 0)
    {
        edi = SectBelow[pSprite->sectnum];

        mychangespritesect(nSprite, edi);
    }

    if (edi != nSector)
    {
        pSprite->z = ebp;

        if (SectFlag[edi] & kSectUnderwater)
        {
            if (nSprite == PlayerList[nLocalPlayer].nSprite) {
                D3PlayFX(StaticSound[kSoundBubbleLow], nSprite);
            }

            if (pSprite->statnum <= 107) {
                pSprite->hitag = 0;
            }
        }
    }
    else
    {
        while ((ebp < sector[pSprite->sectnum].ceilingz) && (SectAbove[pSprite->sectnum] >= 0))
        {
            edi = SectAbove[pSprite->sectnum];

            mychangespritesect(nSprite, edi);
        }
    }

    // This function will keep the player from falling off cliffs when you're too close to the edge.
    // This function finds the highest and lowest z coordinates that your clipping BOX can get to.
    getzrange_old(pSprite->x, pSprite->y, pSprite->z - 256, pSprite->sectnum,
        &sprceiling, &hihit, &sprfloor, &lohit, 128, CLIPMASK0);

    int mySprfloor = sprfloor;

    if ((lohit & 0xC000) != 0xC000) {
        mySprfloor += SectDepth[pSprite->sectnum];
    }

    if (ebp > mySprfloor)
    {
        if (z > 0)
        {
            bTouchFloor = kTrue;

            if ((lohit & 0xC000) == 0xC000)
            {
                // Path A
                short nFloorSprite = lohit & 0x3FFF;

                if (pSprite->statnum == 100 && sprite[nFloorSprite].statnum != 0 && sprite[nFloorSprite].statnum < 100)
                {
                    short nDamage = (z >> 9);
                    if (nDamage)
                    {
                        runlist_DamageEnemy(nFloorSprite, nSprite, nDamage << 1);
                    }

                    pSprite->zvel = -z;
                }
                else
                {
                    if (sprite[nFloorSprite].statnum == 0 || sprite[nFloorSprite].statnum > 199)
                    {
                        nRet |= 0x20000;
                    }
                    else
                    {
                        nRet |= lohit;
                    }

                    pSprite->zvel = 0;
                }
            }
            else
            {
                // Path B
                if (SectBelow[pSprite->sectnum] == -1)
                {
                    nRet |= 0x20000;

                    short nSectDamage = SectDamage[pSprite->sectnum];

                    if (nSectDamage != 0)
                    {
                        if (pSprite->hitag < 15)
                        {
                            IgniteSprite(nSprite);
                            pSprite->hitag = 20;
                        }
                        nSectDamage >>= 2;
                        nSectDamage = nSectDamage - (nSectDamage>>2);
                        if (nSectDamage) {
                            runlist_DamageEnemy(nSprite, -1, nSectDamage);
                        }
                    }

                    pSprite->zvel = 0;
                }
            }
        }

        // loc_1543B:
        ebp = mySprfloor;
        pSprite->z = mySprfloor;
    }
    else
    {
        if ((ebp - height) < sprceiling && ((hihit & 0xC000) == 0xC000 || SectAbove[pSprite->sectnum] == -1))
        {
            ebp = sprceiling + height;
            nRet |= 0x10000;
        }
    }

    if (spriteZ <= floorZ && ebp > floorZ)
    {
        if ((SectDepth[nSector] != 0) || (edi != nSector && (SectFlag[edi] & kSectUnderwater)))
        {
            assert(nSector >= 0 && nSector < kMaxSectors);
            BuildSplash(nSprite, nSector);
        }
    }

    pSprite->cstat = cstat; // restore cstat
    pSprite->z = ebp;

    if (pSprite->statnum == 100)
    {
        BuildNear(pSprite->x, pSprite->y, clipdist + (clipdist / 2), pSprite->sectnum);
        nRet |= BelowNear(nSprite);
    }

    return nRet;
}

int GetSpriteHeight(int nSprite)
{
    return tilesiz[sprite[nSprite].picnum].y * sprite[nSprite].yrepeat * 4;
}

int movesprite(short nSprite, int dx, int dy, int dz, int UNUSED(ceildist), int flordist, unsigned int clipmask)
{
    spritetype *pSprite = &sprite[nSprite];
    bTouchFloor = kFalse;

    int x = pSprite->x;
    int y = pSprite->y;
    int z = pSprite->z;

    int nSpriteHeight = GetSpriteHeight(nSprite);

    int nClipDist = (int8_t)pSprite->clipdist << 2;

    short nSector = pSprite->sectnum;
    assert(nSector >= 0 && nSector < kMaxSectors);

    int floorZ = sector[nSector].floorz;

    int nRet = 0;

    if ((SectFlag[nSector] & kSectUnderwater) || (floorZ < z))
    {
        dx >>= 1;
        dy >>= 1;
    }

    nRet |= movespritez(nSprite, dz, nSpriteHeight, flordist, nClipDist);

    nSector = pSprite->sectnum; // modified in movespritez so re-grab this variable

    if (pSprite->statnum == 100)
    {
        short nPlayer = GetPlayerFromSprite(nSprite);

        int varA = 0;
        int varB = 0;

        CheckSectorFloor(overridesect, pSprite->z, &varB, &varA);

        if (varB || varA)
        {
            nXDamage[nPlayer] = varB;
            nYDamage[nPlayer] = varA;
        }

        dx += nXDamage[nPlayer];
        dy += nYDamage[nPlayer];
    }
    else
    {
        CheckSectorFloor(overridesect, pSprite->z, &dx, &dy);
    }

    nRet |= (uint16_t)clipmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector, dx, dy, nClipDist, nSpriteHeight, flordist, clipmask);

    if ((nSector != pSprite->sectnum) && nSector >= 0)
    {
        if (nRet & 0x20000) {
            dz = 0;
        }

        if ((sector[nSector].floorz - z) < (dz + flordist))
        {
            pSprite->x = x;
            pSprite->y = y;
        }
        else
        {
            mychangespritesect(nSprite, nSector);

            if (pSprite->pal < 5 && !pSprite->hitag)
            {
                pSprite->pal = sector[pSprite->sectnum].ceilingpal;
            }
        }
    }

    return nRet;
}

void Gravity(short nSprite)
{
    short nSector = sprite[nSprite].sectnum;

    if (SectFlag[nSector] & kSectUnderwater)
    {
        if (sprite[nSprite].statnum != 100)
        {
            if (sprite[nSprite].zvel <= 1024)
            {
                if (sprite[nSprite].zvel < 2048) {
                    sprite[nSprite].zvel += 512;
                }
            }
            else
            {
                sprite[nSprite].zvel -= 64;
            }
        }
        else
        {
            if (sprite[nSprite].zvel > 0)
            {
                sprite[nSprite].zvel -= 64;
                if (sprite[nSprite].zvel < 0) {
                    sprite[nSprite].zvel = 0;
                }
            }
            else if (sprite[nSprite].zvel < 0)
            {
                sprite[nSprite].zvel += 64;
                if (sprite[nSprite].zvel > 0) {
                    sprite[nSprite].zvel = 0;
                }
            }
        }
    }
    else
    {
        sprite[nSprite].zvel += 512;
        if (sprite[nSprite].zvel > 16384) {
            sprite[nSprite].zvel = 16384;
        }
    }
}

int MoveCreature(short nSprite)
{
    return movesprite(nSprite, sprite[nSprite].xvel << 8, sprite[nSprite].yvel << 8, sprite[nSprite].zvel, 15360, -5120, CLIPMASK0);
}

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

    int ecx = MoveCreature(nSprite);

    short nSector = sprite[nSprite].sectnum;

    if (nSector != nSectorPre)
    {
        int zDiff = sector[nSectorPre].floorz - sector[nSector].floorz;
        if (zDiff < 0) {
            zDiff = -zDiff;
        }

        if (zDiff > 15360 || (SectFlag[nSector] & kSectUnderwater) || (SectBelow[nSector] > -1 && SectFlag[SectBelow[nSector]]) || SectDamage[nSector])
        {
            sprite[nSprite].x = x;
            sprite[nSprite].y = y;
            sprite[nSprite].z = z;

            mychangespritesect(nSprite, nSectorPre);

            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
            sprite[nSprite].xvel = Sin(sprite[nSprite].ang + 512) >> 2;
            sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;
            return 0;
        }
    }

    return ecx;
}

int GetAngleToSprite(int nSprite1, int nSprite2)
{
    if (nSprite1 < 0 || nSprite2 < 0)
        return -1;

    return GetMyAngle(sprite[nSprite2].x - sprite[nSprite1].x, sprite[nSprite2].y - sprite[nSprite1].y);
}

int PlotCourseToSprite(int nSprite1, int nSprite2)
{
    if (nSprite1 < 0 || nSprite2 < 0)
        return -1;

    int x = sprite[nSprite2].x - sprite[nSprite1].x;
    int y = sprite[nSprite2].y - sprite[nSprite1].y;

    sprite[nSprite1].ang = GetMyAngle(x, y);

    uint32_t x2 = klabs(x);
    uint32_t y2 = klabs(y);

    uint32_t diff = x2 * x2 + y2 * y2;

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

    return ksqrt(diff);
}

int FindPlayer(int nSprite, int nDistance)
{
    int var_18 = 0;
    if (nSprite >= 0)
        var_18 = 1;

    if (nSprite < 0)
        nSprite = -nSprite;

    if (nDistance < 0)
        nDistance = 100;

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

    int z = sprite[nSprite].z - GetSpriteHeight(nSprite);

    nDistance <<= 8;

    short nPlayerSprite;
    int i = 0;

    while (1)
    {
        if (i >= nTotalPlayers)
            return -1;

        nPlayerSprite = PlayerList[i].nSprite;

        if ((sprite[nPlayerSprite].cstat & 0x101) && (!(sprite[nPlayerSprite].cstat & 0x8000)))
        {
            int v9 = klabs(sprite[nPlayerSprite].x - x);

            if (v9 < nDistance)
            {
                int v10 = klabs(sprite[nPlayerSprite].y - y);

                if (v10 < nDistance && cansee(sprite[nPlayerSprite].x, sprite[nPlayerSprite].y, sprite[nPlayerSprite].z - 7680, sprite[nPlayerSprite].sectnum, x, y, z, nSector))
                {
                    break;
                }
            }
        }

        i++;
    }

    if (var_18) {
        PlotCourseToSprite(nSprite, nPlayerSprite);
    }

    return nPlayerSprite;
}

void CheckSectorFloor(short nSector, int z, int *x, int *y)
{
    short nSpeed = SectSpeed[nSector];

    if (!nSpeed) {
        return;
    }

    short nFlag = SectFlag[nSector];
    short nAng = nFlag & kAngleMask;

    if (z >= sector[nSector].floorz)
    {
        *x += (Cos(nAng) << 3) * nSpeed;
        *y += (sintable[nAng] << 3) * nSpeed; // no anglemask in original code
    }
    else if (nFlag & 0x800)
    {
        *x += (Cos(nAng) << 4) * nSpeed;
        *y += (sintable[nAng] << 4) * nSpeed; // no anglemask in original code
    }
}

int GetUpAngle(short nSprite1, int nVal, short nSprite2, int ecx)
{
    int x = sprite[nSprite2].x - sprite[nSprite1].x;
    int y = sprite[nSprite2].y - sprite[nSprite1].y;

    int ebx = (sprite[nSprite2].z + ecx) - (sprite[nSprite1].z + nVal);
    int edx = (sprite[nSprite2].z + ecx) - (sprite[nSprite1].z + nVal);

    ebx >>= 4;
    edx >>= 8;

    ebx = -ebx;

    ebx -= edx;

    int nSqrt = lsqrt(x * x + y * y);

    return GetMyAngle(nSqrt, ebx);
}

void InitPushBlocks()
{
    nPushBlocks = 0;
}

int GrabPushBlock()
{
    if (nPushBlocks >= kMaxPushBlocks) {
        return -1;
    }

    return nPushBlocks++;
}

void CreatePushBlock(int nSector)
{
    int nBlock = GrabPushBlock();
    int i;

    int startwall = sector[nSector].wallptr;
    int nWalls = sector[nSector].wallnum;

    int xSum = 0;
    int ySum = 0;

    for (i = 0; i < nWalls; i++)
    {
        xSum += wall[startwall + i].x;
        ySum += wall[startwall + i].y;
    }

    int xAvg = xSum / nWalls;
    int yAvg = ySum / nWalls;

    sBlockInfo[nBlock].x = xAvg;
    sBlockInfo[nBlock].y = yAvg;

    int nSprite = insertsprite(nSector, 0);

    sBlockInfo[nBlock].nSprite = nSprite;

    sprite[nSprite].x = xAvg;
    sprite[nSprite].y = yAvg;
    sprite[nSprite].z = sector[nSector].floorz - 256;
    sprite[nSprite].cstat = 0x8000;

    int var_28 = 0;

    for (i = 0; i < nWalls; i++)
    {
        uint32_t xDiff = klabs(xAvg - wall[startwall + i].x);
        uint32_t yDiff = klabs(yAvg - wall[startwall + i].y);

        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);
        if (nSqrt > var_28) {
            var_28 = nSqrt;
        }
    }

    sBlockInfo[nBlock].walldist = var_28;

    sprite[nSprite].clipdist = (var_28 & 0xFF) << 2;
    sector[nSector].extra = nBlock;
}

void MoveSector(short nSector, int nAngle, int *nXVel, int *nYVel)
{
    int i;

    if (nSector == -1) {
        return;
    }

    int nXVect, nYVect;

    if (nAngle < 0)
    {
        nXVect = *nXVel;
        nYVect = *nYVel;
        nAngle = GetMyAngle(nXVect, nYVect);
    }
    else
    {
        nXVect = Sin(nAngle + 512) << 6;
        nYVect = Sin(nAngle) << 6;
    }

    short nBlock = sector[nSector].extra;
    short nSectFlag = SectFlag[nSector];

    sectortype *pSector = &sector[nSector];
    int nFloorZ = sector[nSector].floorz;
    int startwall = sector[nSector].wallptr;
    int nWalls = sector[nSector].wallnum;

    walltype *pStartWall = &wall[startwall];
    short nNextSector = wall[startwall].nextsector;

    BlockInfo *pBlockInfo = &sBlockInfo[nBlock];

    int x = sBlockInfo[nBlock].x;
    int x_b = sBlockInfo[nBlock].x;

    int y = sBlockInfo[nBlock].y;
    int y_b = sBlockInfo[nBlock].y;

    short nSectorB = nSector;

    int nZVal;
    int z;

    int bUnderwater = nSectFlag & kSectUnderwater;

    if (nSectFlag & kSectUnderwater)
    {
        nZVal = sector[nSector].ceilingz;
        z = sector[nNextSector].ceilingz + 256;

        sector[nSector].ceilingz = sector[nNextSector].ceilingz;
    }
    else
    {
        nZVal = sector[nSector].floorz;
        z = sector[nNextSector].floorz - 256;

        sector[nSector].floorz = sector[nNextSector].floorz;
    }

    clipmove_old((int32_t*)&x, (int32_t*)&y, (int32_t*)&z, &nSectorB, nXVect, nYVect, pBlockInfo->walldist, 0, 0, CLIPMASK1);

    int yvect = y - y_b;
    int xvect = x - x_b;

    if (nSectorB != nNextSector && nSectorB != nSector)
    {
        yvect = 0;
        xvect = 0;
    }
    else
    {
        if (!bUnderwater)
        {
            z = nZVal;
            x = x_b;
            y = y_b;

            clipmove_old((int32_t*)&x, (int32_t*)&y, (int32_t*)&z, &nSectorB, nXVect, nYVect, pBlockInfo->walldist, 0, 0, CLIPMASK1);

            int ebx = x;
            int ecx = x_b;
            int edx = y;
            int eax = xvect;
            int esi = y_b;

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

            ebx -= ecx;
            ecx = eax;
            eax = ebx;
            edx -= esi;

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

            if (ecx > eax)
            {
                xvect = ebx;
            }

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

            ebx = eax;
            eax = edx;

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

            if (ebx > eax) {
                yvect = edx;
            }
        }
    }

    // GREEN
    if (yvect || xvect)
    {
        for (i = headspritesect[nSector]; i != -1; i = nextspritesect[i])
        {
            if (sprite[i].statnum < 99)
            {
                sprite[i].x += xvect;
                sprite[i].y += yvect;
            }
            else
            {
                z = sprite[i].z;

                if ((nSectFlag & kSectUnderwater) || z != nZVal || sprite[i].cstat & 0x8000)
                {
                    x = sprite[i].x;
                    y = sprite[i].y;
                    nSectorB = nSector;

                    clipmove_old((int32_t*)&x, (int32_t*)&y, (int32_t*)&z, &nSectorB, -xvect, -yvect, 4 * sprite[i].clipdist, 0, 0, CLIPMASK0);

                    if (nSectorB >= 0 && nSectorB < kMaxSectors && nSectorB != nSector) {
                        mychangespritesect(i, nSectorB);
                    }
                }
            }
        }

        for (i = headspritesect[nNextSector]; i != -1; i = nextspritesect[i])
        {
            if (sprite[i].statnum >= 99)
            {
                x = sprite[i].x;
                y = sprite[i].y;
                z = sprite[i].z;
                nSectorB = nNextSector;

                clipmove_old((int32_t*)&x, (int32_t*)&y, (int32_t*)&z, &nSectorB,
                    -xvect - (Sin(nAngle + 512) * (4 * sprite[i].clipdist)),
                    -yvect - (Sin(nAngle) * (4 * sprite[i].clipdist)),
                    4 * sprite[i].clipdist, 0, 0, CLIPMASK0);

                if (nSectorB != nNextSector && (nSectorB == nSector || nNextSector == nSector))
                {
                    if (nSectorB != nSector || nFloorZ >= sprite[i].z)
                    {
                        if (nSectorB >= 0 && nSectorB < kMaxSectors) {
                            mychangespritesect(i, nSectorB);
                        }
                    }
                    else
                    {
                        movesprite(i,
                            (xvect << 14) + Sin(nAngle + 512) * sprite[i].clipdist,
                            (yvect << 14) + Sin(nAngle) * sprite[i].clipdist,
                            0, 0, 0, CLIPMASK0);
                    }
                }
            }
        }

        for (int i = 0; i < nWalls; i++)
        {
            dragpoint(startwall, xvect + pStartWall->x, yvect + pStartWall->y, 0);
            pStartWall++;
            startwall++;
        }

        pBlockInfo->x += xvect;
        pBlockInfo->y += yvect;
    }

    // loc_163DD
    xvect <<= 14;
    yvect <<= 14;

    if (!(nSectFlag & kSectUnderwater))
    {
        for (i = headspritesect[nSector]; i != -1; i = nextspritesect[i])
        {
            if (sprite[i].statnum >= 99 && nZVal == sprite[i].z && !(sprite[i].cstat & 0x8000))
            {
                nSectorB = nSector;
                clipmove_old(&sprite[i].x, &sprite[i].y, &sprite[i].z, &nSectorB, xvect, yvect, 4 * sprite[i].clipdist, 5120, -5120, CLIPMASK0);
            }
        }
    }

    if (nSectFlag & kSectUnderwater) {
        pSector->ceilingz = nZVal;
    }
    else {
        pSector->floorz = nZVal;
    }

    *nXVel = xvect;
    *nYVel = yvect;

    /*
        Update player position variables, in case the player sprite was moved by a sector.
        Otherwise these can be out of sync when used in sound code (before being updated in PlayerFunc()).
        Can cause local player sounds to play off-centre.
        TODO: Might need to be done elsewhere too?
    */
    int nPlayerSprite = PlayerList[nLocalPlayer].nSprite;
    initx = sprite[nPlayerSprite].x;
    inity = sprite[nPlayerSprite].y;
    initz = sprite[nPlayerSprite].z;
    inita = sprite[nPlayerSprite].ang;
    initsect = sprite[nPlayerSprite].sectnum;
}

void SetQuake(short nSprite, int nVal)
{
    int x = sprite[nSprite].x;
    int y = sprite[nSprite].y;

    nVal *= 256;

    for (int i = 0; i < nTotalPlayers; i++)
    {
        int nPlayerSprite = PlayerList[i].nSprite;

        uint32_t xDiff = klabs((int32_t)((sprite[nPlayerSprite].x - x) >> 8));
        uint32_t yDiff = klabs((int32_t)((sprite[nPlayerSprite].y - y) >> 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);

        int eax = nVal;

        if (nSqrt)
        {
            eax = eax / nSqrt;

            if (eax >= 256)
            {
                if (eax > 3840) {
                    eax = 3840;
                }
            }
            else
            {
                eax = 0;
            }
        }

        if (eax > nQuake[i]) {
            nQuake[i] = eax;
        }
    }
}

int AngleChase(int nSprite, int nSprite2, int ebx, int ecx, int push1)
{
    int nClipType = sprite[nSprite].statnum != 107;

    /* bjd - need to handle cliptype to clipmask change that occured in later build engine version */
    if (nClipType == 1) {
        nClipType = CLIPMASK1;
    }
    else {
        nClipType = CLIPMASK0;
    }

    short nAngle;

    if (nSprite2 < 0)
    {
        sprite[nSprite].zvel = 0;
        nAngle = sprite[nSprite].ang;
    }
    else
    {
        int nHeight = tilesiz[sprite[nSprite2].picnum].y * sprite[nSprite2].yrepeat * 2;

        int nMyAngle = GetMyAngle(sprite[nSprite2].x - sprite[nSprite].x, sprite[nSprite2].y - sprite[nSprite].y);

        uint32_t xDiff = klabs(sprite[nSprite2].x - sprite[nSprite].x);
        uint32_t yDiff = klabs(sprite[nSprite2].y - sprite[nSprite].y);

        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);

        int var_18 = GetMyAngle(nSqrt, ((sprite[nSprite2].z - nHeight) - sprite[nSprite].z) >> 8);

        int nAngDelta = AngleDelta(sprite[nSprite].ang, nMyAngle, 1024);
        int nAngDelta2 = klabs(nAngDelta);

        if (nAngDelta2 > 63)
        {
            nAngDelta2 = klabs(nAngDelta >> 6);

            ebx /= nAngDelta2;

            if (ebx < 5) {
                ebx = 5;
            }
        }

        int nAngDeltaC = klabs(nAngDelta);

        if (nAngDeltaC > push1)
        {
            if (nAngDelta >= 0)
                nAngDelta = push1;
            else
                nAngDelta = -push1;
        }

        nAngle = (nAngDelta + sprite[nSprite].ang) & kAngleMask;
        int nAngDeltaD = AngleDelta(sprite[nSprite].zvel, var_18, 24);

        sprite[nSprite].zvel = (sprite[nSprite].zvel + nAngDeltaD) & kAngleMask;
    }

    sprite[nSprite].ang = nAngle;

    int eax = klabs(Cos(sprite[nSprite].zvel));

    int x = ((Cos(nAngle) * ebx) >> 14) * eax;
    int y = ((Sin(nAngle) * ebx) >> 14) * eax;

    int xshift = x >> 8;
    int yshift = y >> 8;

    uint32_t sqrtNum = xshift * xshift + yshift * yshift;

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

    int z = Sin(sprite[nSprite].zvel) * ksqrt(sqrtNum);

    return movesprite(nSprite, x >> 2, y >> 2, (z >> 13) + (Sin(ecx) >> 5), 0, 0, nClipType);
}

int GetWallNormal(short nWall)
{
    nWall &= kMaxWalls-1;

    int nWall2 = wall[nWall].point2;

    int nAngle = GetMyAngle(wall[nWall2].x - wall[nWall].x, wall[nWall2].y - wall[nWall].y);
    return (nAngle + 512) & kAngleMask;
}

void WheresMyMouth(int nPlayer, int *x, int *y, int *z, short *sectnum)
{
    int nSprite = PlayerList[nPlayer].nSprite;

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

    int height = GetSpriteHeight(nSprite) / 2;

    *z = sprite[nSprite].z - height;
    *sectnum = sprite[nSprite].sectnum;

    clipmove_old((int32_t*)x, (int32_t*)y, (int32_t*)z, sectnum,
        Cos(sprite[nSprite].ang) << 7,
        Sin(sprite[nSprite].ang) << 7,
        5120, 1280, 1280, CLIPMASK1);
}

void InitChunks()
{
    nCurChunkNum = 0;
    memset(nChunkSprite,   -1, sizeof(nChunkSprite));
    memset(nBodyGunSprite, -1, sizeof(nBodyGunSprite));
    memset(nBodySprite,    -1, sizeof(nBodySprite));
    nCurBodyNum    = 0;
    nCurBodyGunNum = 0;
    nBodyTotal  = 0;
    nChunkTotal = 0;
}

int GrabBodyGunSprite()
{
    int nSprite = nBodyGunSprite[nCurBodyGunNum];

    if (nSprite == -1)
    {
        nSprite = insertsprite(0, 899);
        nBodyGunSprite[nCurBodyGunNum] = nSprite;

        sprite[nSprite].lotag = -1;
        sprite[nSprite].owner = -1;
    }
    else
    {
        int nAnim = sprite[nSprite].owner;

        if (nAnim != -1) {
            DestroyAnim(nAnim);
        }

        sprite[nSprite].lotag = -1;
        sprite[nSprite].owner = -1;
    }

    nCurBodyGunNum++;
    if (nCurBodyGunNum >= 50) { // TODO - enum/define
        nCurBodyGunNum = 0;
    }

    sprite[nSprite].cstat = 0;

    return nSprite;
}

int GrabBody()
{
    int nSprite;

    do
    {
        nSprite = nBodySprite[nCurBodyNum];

        if (nSprite == -1)
        {
            nSprite = insertsprite(0, 899);
            nBodySprite[nCurBodyNum] = nSprite;
            sprite[nSprite].cstat = 0x8000;
        }

        nCurBodyNum++;
        if (nCurBodyNum >= 50) {
            nCurBodyNum = 0;
        }
    } while (sprite[nSprite].cstat & 0x101);

    if (nBodyTotal < 50) {
        nBodyTotal++;
    }

    sprite[nSprite].cstat = 0;
    return nSprite;
}

int GrabChunkSprite()
{
    int nSprite = nChunkSprite[nCurChunkNum];

    if (nSprite == -1)
    {
        nSprite = insertsprite(0, 899);
        nChunkSprite[nCurChunkNum] = nSprite;
    }
    else if (sprite[nSprite].statnum)
    {
// TODO	MonoOut("too many chunks being used at once!\n");
        return -1;
    }

    changespritestat(nSprite, 899);

    nCurChunkNum++;
    if (nCurChunkNum >= kMaxChunks)
        nCurChunkNum = 0;

    if (nChunkTotal < kMaxChunks)
        nChunkTotal++;

    sprite[nSprite].cstat = 0x80;

    return nSprite;
}

int BuildCreatureChunk(int nVal, int nPic)
{
    int var_14;

    int nSprite = GrabChunkSprite();

    if (nSprite == -1) {
        return -1;
    }

    if (nVal & 0x4000)
    {
        nVal &= 0x3FFF;
        var_14 = 1;
    }
    else
    {
        var_14 = 0;
    }

    nVal &= 0xFFFF;

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

    mychangespritesect(nSprite, sprite[nVal].sectnum);

    sprite[nSprite].cstat = 0x80;
    sprite[nSprite].shade = -12;
    sprite[nSprite].pal = 0;

    sprite[nSprite].xvel = (RandomSize(5) - 16) << 7;
    sprite[nSprite].yvel = (RandomSize(5) - 16) << 7;
    sprite[nSprite].zvel = (-(RandomSize(8) + 512)) << 3;

    if (var_14)
    {
        sprite[nSprite].xvel *= 4;
        sprite[nSprite].yvel *= 4;
        sprite[nSprite].zvel *= 2;
    }

    sprite[nSprite].xrepeat = 64;
    sprite[nSprite].yrepeat = 64;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].picnum = nPic;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].clipdist = 40;

//	GrabTimeSlot(3);

    sprite[nSprite].extra = -1;
    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nSprite, kRunCreatureChunk);
    sprite[nSprite].hitag = runlist_AddRunRec(NewRun, nSprite, kRunCreatureChunk);

    return nSprite | 0xD0000; // TODO: FIXME
}

void FuncCreatureChunk(int a, int UNUSED(b), int nRun)
{
    int nSprite = RunData[nRun].nVal;
    assert(nSprite >= 0 && nSprite < kMaxSprites);

    int nMessage = a & 0x7F0000;

    if (nMessage != 0x20000)
        return;

    Gravity(nSprite);

    int nSector = sprite[nSprite].sectnum;
    sprite[nSprite].pal = sector[nSector].ceilingpal;

    int nVal = movesprite(nSprite, sprite[nSprite].xvel << 10, sprite[nSprite].yvel << 10, sprite[nSprite].zvel, 2560, -2560, CLIPMASK1);

    if (sprite[nSprite].z >= sector[nSector].floorz)
    {
        // re-grab this variable as it may have changed in movesprite(). Note the check above is against the value *before* movesprite so don't change it.
        nSector = sprite[nSprite].sectnum;

        sprite[nSprite].xvel = 0;
        sprite[nSprite].yvel = 0;
        sprite[nSprite].zvel = 0;
        sprite[nSprite].z = sector[nSector].floorz;
    }
    else
    {
        if (!nVal)
            return;

        short nAngle;

        if (nVal & 0x20000)
        {
            sprite[nSprite].cstat = 0x8000;
        }
        else
        {
            if ((nVal & 0x3C000) == 0x10000)
            {
                sprite[nSprite].xvel >>= 1;
                sprite[nSprite].yvel >>= 1;
                sprite[nSprite].zvel = -sprite[nSprite].zvel;
                return;
            }
            else if ((nVal & 0x3C000) == 0xC000)
            {
                nAngle = sprite[nVal & 0x3FFF].ang;
            }
            else if ((nVal & 0x3C000) == 0x8000)
            {
                nAngle = GetWallNormal(nVal & 0x3FFF);
            }
            else
            {
                return;
            }

            // loc_16E0C
            int nSqrt = lsqrt(((sprite[nSprite].yvel >> 10) * (sprite[nSprite].yvel >> 10)
                + (sprite[nSprite].xvel >> 10) * (sprite[nSprite].xvel >> 10)) >> 8);

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

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

    changespritestat(nSprite, 0);
    sprite[nSprite].hitag = 0;
    sprite[nSprite].lotag = 0;
}

short UpdateEnemy(short *nEnemy)
{
    if (*nEnemy >= 0)
    {
        if (!(sprite[*nEnemy].cstat & 0x101)) {
            *nEnemy = -1;
        }
    }

    return *nEnemy;
}

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

void MoveLoadSave::Load()
{
    Read(&NearCount, sizeof(NearCount));
    Read(NearSector, sizeof(NearSector[0]) * NearCount);
    Read(&nPushBlocks, sizeof(nPushBlocks));
    Read(sBlockInfo, sizeof(sBlockInfo[0]) * nPushBlocks);
    Read(&overridesect, sizeof(overridesect));

    Read(&nCurBodyNum, sizeof(nCurBodyNum));
    Read(nBodySprite, sizeof(nBodySprite[0]) * nCurBodyNum);
    Read(&nBodyTotal, sizeof(nBodyTotal));

    Read(&hihit, sizeof(hihit));
    Read(&sprceiling, sizeof(sprceiling));
    Read(&sprfloor, sizeof(sprfloor));
    Read(&lohit, sizeof(lohit));

    Read(&nCurChunkNum, sizeof(nCurChunkNum));
    Read(nChunkSprite, sizeof(nChunkSprite[0]) * nCurChunkNum);
}

void MoveLoadSave::Save()
{
    Write(&NearCount, sizeof(NearCount));
    Write(NearSector, sizeof(NearSector[0]) * NearCount);
    Write(&nPushBlocks, sizeof(nPushBlocks));
    Write(sBlockInfo, sizeof(sBlockInfo[0]) * nPushBlocks);
    Write(&overridesect, sizeof(overridesect));

    Write(&nCurBodyNum, sizeof(nCurBodyNum));
    Write(nBodySprite, sizeof(nBodySprite[0]) * nCurBodyNum);
    Write(&nBodyTotal, sizeof(nBodyTotal));

    Write(&hihit, sizeof(hihit));
    Write(&sprceiling, sizeof(sprceiling));
    Write(&sprfloor, sizeof(sprfloor));
    Write(&lohit, sizeof(lohit));

    Write(&nCurChunkNum, sizeof(nCurChunkNum));
    Write(nChunkSprite, sizeof(nChunkSprite[0]) * nCurChunkNum);
}

static MoveLoadSave* myLoadSave;

void MoveLoadSaveConstruct()
{
    myLoadSave = new MoveLoadSave();
}


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

// 16 bytes
struct BlockInfo
{
    int x;
    int y;
    int walldist;
    short nSprite;
};
extern BlockInfo sBlockInfo[];

extern int hihit;
extern short nChunkSprite[];
extern short nBodySprite[];

signed int lsqrt(int a1);
void MoveThings();
void ResetMoveFifo();
void InitChunks();
void InitPushBlocks();
void Gravity(short nSprite);
short UpdateEnemy(short *nEnemy);
int MoveCreature(short nSprite);
int MoveCreatureWithCaution(int nSprite);
void WheresMyMouth(int nPlayer, int *x, int *y, int *z, short *sectnum);

int GetSpriteHeight(int nSprite);

int GrabBody();

int GrabBodyGunSprite();
void CreatePushBlock(int nSector);

void FuncCreatureChunk(int a, int, int nRun);

int FindPlayer(int nSprite, int nDistance);

int BuildCreatureChunk(int nVal, int nPic);

void BuildNear(int x, int y, int walldist, int nSector);
int BelowNear(short nSprite);

int PlotCourseToSprite(int nSprite1, int nSprite2);

void CheckSectorFloor(short nSector, int z, int *x, int *y);

int GetAngleToSprite(int nSprite1, int nSprite2);

int GetWallNormal(short nWall);

int GetUpAngle(short nSprite1, int nVal, short nSprite2, int ecx);
void MoveSector(short nSector, int nAngle, int *nXVel, int *nYVel);

int AngleChase(int nSprite, int nSprite2, int ebx, int ecx, int push1);

void SetQuake(short nSprite, int nVal);

#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 "engine.h"
#include "exhumed.h"
#include "names.h"
#include "movie.h"
#include "light.h"
#include <cstdio>
#include <cstring>
#include "baselayer.h"
#include "typedefs.h"
#include "keyboard.h"
#include "fx_man.h"
#include "config.h"
#include "sound.h"
#include "mutex.h"
#include "memorystream.h"

enum {
    kFramePalette = 0,
    kFrameSound,
    kFrameImage,
    kFrameDone
};

#define kSampleRate     22050
#define kSampleSize     2205

uint8_t bankbuf[kSampleRate];
uint32_t bankptr = 0;
uint32_t banktail = 0;

uint32_t lSoundBytesRead = 0;
uint32_t lSoundBytesUsed = 0;

uint8_t lh[32] = { 0 };

static uint8_t* CurFrame = NULL;

bool bServedSample = false;
palette_t moviepal[256];
static mutex_t mutex;

bool LoadGOGBookMovie(uint8_t *pMovie, int nFileSize)
{
    const uint8_t kSyncMark[] = { 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00 };

    buildvfs_kfd hFile = kopen4load("game.gog", 0);
    if (hFile < 0)
        return false;

    int32_t fileSize = kfilelength(hFile);
    if (fileSize != 391203456) { // expected size of game.gog
        kclose(hFile);
        return false;
    }

    klseek(hFile, 13388 * 2352, SEEK_SET); // offset to start of file

    while (nFileSize)
    {
        uint8_t syncmark[12];

        kread(hFile, syncmark, sizeof(syncmark));
        klseek(hFile, 4, SEEK_CUR);

        if (memcmp(syncmark, kSyncMark, sizeof(kSyncMark)) != 0)
        {
            printf("invalid CD image file\n");
            kclose(hFile);
            return false;
        }

        int nRead = kread(hFile, pMovie, min(2048, nFileSize));
        pMovie += nRead;

        if (nFileSize < 2048)
            break;

        nFileSize -= 2048;

        // temp - check type
        klseek(hFile, 4 + 8 + 276, SEEK_CUR);
    }

    kclose(hFile);
    return true;
}

int ReadFrame(MemoryReadStream &ms)
{
    uint8_t nType;
    uint8_t var_1C;
    int nSize;
    uint16_t yOffset;
    uint8_t xOffset;
    uint8_t nPixels;
    uint8_t palette[768];

    while (1)
    {
        if (!ms.GetBytesLeft())
            return 0;

        nType = ms.GetByte();
        nSize = ms.GetUint32LE();

        nType--;
        if (nType > 3) {
            continue;
        }

        switch (nType)
        {
            case kFramePalette:
            {
                ms.GetBytes(palette, sizeof(palette));
                var_1C = ms.GetByte();

                for (auto &c : palette)
                    c <<= 2;

                paletteSetColorTable(ANIMPAL, palette);
                videoSetPalette(0, ANIMPAL, 2+8);

                memset(CurFrame, overscanindex, 4); //sizeof(CurFrame));
                continue;
            }
            case kFrameSound:
            {
                if (lSoundBytesRead - lSoundBytesUsed >= kSampleRate)
                {
                    DebugOut("ReadFrame() - Sound buffer full\n");
                    ms.SkipBytes(nSize);
                }
                else
                {
                    mutex_lock(&mutex);

                    int nRead = ms.GetBytes(bankbuf + bankptr, nSize);

                    lSoundBytesRead += nRead;
                    bankptr += nSize;

                    assert(nSize == nRead);
                    assert(bankptr <= kSampleRate);

                    if (bankptr >= kSampleRate) {
                        bankptr -= kSampleRate; // loop back to start
                    }

                    mutex_unlock(&mutex);
                }

                continue;
            }
            case kFrameImage:
            {
                if (nSize == 0) {
                    continue;
                }

                uint8_t *pFrame = CurFrame;

                yOffset = ms.GetUint16LE();
                nSize -= 2;

                pFrame += yOffset * 200; // row position

                while (nSize > 0)
                {
                    xOffset = ms.GetByte();
                    nPixels = ms.GetByte();
                    nSize -= 2;

                    pFrame += xOffset;

                    if (nPixels)
                    {
                        int nRead = ms.GetBytes(pFrame, nPixels);
                        pFrame += nRead;
                        nSize -= nRead;
                    }
                }

                tileInvalidate(kMovieTile, -1, -1);
                break;
            }
            case kFrameDone:
            {
                return 1;
                break;
            }
        }
    }
}

static void ServeSample(const char** ptr, uint32_t* length, void* userdata)
{
    mutex_lock(&mutex);

    *ptr = (char*)bankbuf + banktail;
    *length = kSampleSize;

    banktail += kSampleSize;
    if (banktail >= kSampleRate) {
        banktail -= kSampleRate; // rotate back to start
    }

    lSoundBytesUsed += kSampleSize;
    bServedSample = true;

    mutex_unlock(&mutex);
}

void PlayMovie(const char* fileName)
{
    int bDoFade = kTrue;
    int hFx = -1;
    uint8_t* pMovieFile = NULL;
    int fileSize = 0;

    if (!waloff[kMovieTile]) tileLoad(kMovieTile);
    CurFrame = (uint8_t*)waloff[kMovieTile];

    // try for a loose BOOK.MOV first
    buildvfs_kfd hFile = kopen4loadfrommod(fileName, 0);
    if (hFile >= 0)
    {
        fileSize = kfilelength(hFile);
        pMovieFile = new uint8_t[fileSize];
        if (pMovieFile)
            kread(hFile, pMovieFile, fileSize);

        kclose(hFile);
    }
    else
    {
        // try read from the GOG versions game.gog image file
        fileSize = 9938599; // BOOK.MOV file size
        pMovieFile = new uint8_t[fileSize];
        if (!pMovieFile)
        {
            DebugOut("Can't open movie file %s\n", fileName);
            return;
        }

        if (!LoadGOGBookMovie(pMovieFile, fileSize))
        {
            DebugOut("Can't open movie file %s\n", fileName);
            if (!pMovieFile)
                delete[] pMovieFile;
            return;
        }
    }

    if (pMovieFile == NULL)
    {
        DebugOut("Can't open movie file %s\n", fileName);
        return;
    }

    MemoryReadStream ms(pMovieFile, fileSize);

    ms.GetBytes(lh, sizeof(lh));

    // sound stuff
    mutex_init(&mutex);
    bankptr = 0;
    banktail = 0;

    // clear keys
    KB_FlushKeyboardQueue();
    KB_ClearKeysDown();

    if (bDoFade) {
        StartFadeIn();
    }

    int angle = 1536;
    int z = 0;
    int f = 255;

    videoSetPalette(0, ANIMPAL, 2 + 8);

    // Read a frame in first
    if (ReadFrame(ms))
    {
        // start audio playback
        hFx = FX_StartDemandFeedPlayback(ServeSample, 8, 1, kSampleRate, 0, MusicVolume, MusicVolume, MusicVolume, FX_MUSIC_PRIORITY, fix16_one, -1, nullptr);

        while (!KB_KeyWaiting())
        {
            HandleAsync();

            // audio is king for sync - if the backend doesn't need any more samples yet,
            // don't process any more movie file data.
            if (!bServedSample) {
                continue;
            }

            bServedSample = false;

            if (z < 65536) { // Zoom - normal zoom is 65536.
                z += 2048;
            }
            if (angle != 0) {
                angle += 16;
                if (angle == 2048) {
                    angle = 0;
                }
            }

            videoClearViewableArea(blackcol);
            rotatesprite(160 << 16, 100 << 16, z, angle, kMovieTile, 0, 1, 2, 0, 0, xdim - 1, ydim - 1);

            if (videoGetRenderMode() == REND_CLASSIC)
            {
                if (bDoFade) {
                    bDoFade = DoFadeIn();
                }
            }
#ifdef USE_OPENGL
            else
            {
                if (f >= 0)
                {
                    fullscreen_tint_gl(0, 0, 0, f);
                    f -= 8;
                }
            }
#endif

            videoNextPage();

            if (ReadFrame(ms) == 0) {
                break;
            }
        }
    }

    if (hFx > 0) {
        FX_StopSound(hFx);
    }

    if (KB_KeyWaiting()) {
        KB_GetCh();
    }

    mutex_destroy(&mutex);

    if (pMovieFile)
        delete[] pMovieFile;

#ifdef USE_OPENGL
    // need to do OpenGL fade out here
    f = 0;

    while (f <= 255)
    {
        HandleAsync();

        rotatesprite(160 << 16, 100 << 16, z, angle, kMovieTile, 0, 1, 2, 0, 0, xdim - 1, ydim - 1);

        fullscreen_tint_gl(0, 0, 0, f);
        f += 4;

        WaitTicks(2);

        videoNextPage();
    }
#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.
*/
//-------------------------------------------------------------------------

#ifndef __movie_h__
#define __movie_h__

void PlayMovie(const char *fileName);

#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 "mummy.h"
#include "sequence.h"
#include "move.h"
#include "map.h"
#include "sound.h"
#include "exhumed.h"
#include "random.h"
#include "trigdat.h"
#include "bullet.h"
#include "items.h"
#include "save.h"
#include <assert.h>
#include "engine.h"

struct Mummy
{
    short nHealth;
    short B;
    short nAction;
    short nSprite;
    short nTarget;
    short F;
    short G;
    short H;
};

static actionSeq ActionSeq[] = {
    {8,  0},
    {0,  0},
    {16, 0},
    {24, 0},
    {32, 1},
    {40, 1},
    {48, 1},
    {50, 0}
};

short nMummies = -1;
Mummy MummyList[kMaxMummies];

void InitMummy()
{
    nMummies = 0;
}

int BuildMummy(int nSprite, int x, int y, int z, int nSector, int nAngle)
{
    if (nMummies >= kMaxMummies) {
        return -1;
    }

    short nMummy = nMummies++;

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 102);
    }
    else
    {
        x = sprite[nSprite].x;
        y = sprite[nSprite].y;
        z = sprite[nSprite].z;
        nAngle = sprite[nSprite].ang;

        changespritestat(nSprite, 102);
    }

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

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

//  GrabTimeSlot(3);

    MummyList[nMummy].nAction = 0;
    MummyList[nMummy].nHealth = 640;
    MummyList[nMummy].B = 0;
    MummyList[nMummy].nSprite = nSprite;
    MummyList[nMummy].nTarget = -1;
    MummyList[nMummy].F = nMummy;
    MummyList[nMummy].G = 0;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nMummy, kRunMummy);

    MummyList[nMummy].H = runlist_AddRunRec(NewRun, nMummy, kRunMummy);

    nCreaturesLeft++;

    return nMummy;
}

void CheckMummyRevive(short nMummy)
{
    short nSprite = MummyList[nMummy].nSprite;

    for (int i = 0; i < nMummies; i++)
    {
        if (i != nMummy)
        {
            short nSprite2 = MummyList[i].nSprite;
            if (sprite[nSprite2].statnum != 102) {
                continue;
            }

            if (MummyList[i].nAction != 5) {
                continue;
            }

            int x = klabs(sprite[nSprite2].x - sprite[nSprite].x) >> 8;
            int y = klabs(sprite[nSprite2].y - sprite[nSprite].y) >> 8;

            if (x <= 20 && y <= 20)
            {
                if (cansee(sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z - 8192, sprite[nSprite].sectnum,
                          sprite[nSprite2].x, sprite[nSprite2].y, sprite[nSprite2].z - 8192, sprite[nSprite2].sectnum))
                {
                    sprite[nSprite2].cstat = 0;
                    MummyList[i].nAction = 6;
                    MummyList[i].B = 0;
                }
            }
        }
    }
}

void FuncMummy(int a, int nDamage, int nRun)
{
    short nMummy = RunData[nRun].nVal;
    assert(nMummy >= 0 && nMummy < kMaxMummies);

    short nTarget = UpdateEnemy(&MummyList[nMummy].nTarget);

    short nSprite = MummyList[nMummy].nSprite;
    short nAction = MummyList[nMummy].nAction;

    int nMessage = a & kMessageMask;

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

        case 0x20000:
        {
            Gravity(nSprite);

            int nSeq = SeqOffsets[kSeqMummy] + ActionSeq[nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, MummyList[nMummy].B);

            short nFrame = SeqBase[nSeq] + MummyList[nMummy].B;
            short nFrameFlag = FrameFlag[nFrame];

            seq_MoveSequence(nSprite, nSeq, MummyList[nMummy].B);

            bool bVal = false;

            MummyList[nMummy].B++;
            if (MummyList[nMummy].B >= SeqSize[nSeq])
            {
                MummyList[nMummy].B = 0;

                bVal = true;
            }

            if (nTarget != -1 && nAction < 4)
            {
                if ((!sprite[nTarget].cstat) && nAction)
                {
                    MummyList[nMummy].nAction = 0;
                    MummyList[nMummy].B = 0;
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                }
            }

            int nMov = MoveCreatureWithCaution(nSprite);

            if (nAction > 7)
                return;

            switch (nAction)
            {
                case 0:
                {
                    if ((MummyList[nMummy].F & 0x1F) == (totalmoves & 0x1F))
                    {
                        sprite[nSprite].cstat = 0x101;

                        if (nTarget < 0)
                        {
                            int nTarget = FindPlayer(nSprite, 100);
                            if (nTarget >= 0)
                            {
                                D3PlayFX(StaticSound[kSoundMummyChant0], nSprite);
                                MummyList[nMummy].B = 0;
                                MummyList[nMummy].nTarget = nTarget;
                                MummyList[nMummy].nAction = 1;
                                MummyList[nMummy].G = 90;

                                sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
                                sprite[nSprite].yvel = sintable[sprite[nSprite].ang] >> 2; // NOTE no angle masking in original code
                            }
                        }
                    }
                    return;
                }

                case 1:
                {
                    if (MummyList[nMummy].G > 0)
                    {
                        MummyList[nMummy].G--;
                    }

                    if ((MummyList[nMummy].F & 0x1F) == (totalmoves & 0x1F))
                    {
                        sprite[nSprite].cstat = 0x101;

                        PlotCourseToSprite(nSprite, nTarget);

                        if (MummyList[nMummy].nAction == 1)
                        {
                            if (RandomBit())
                            {
                                if (cansee(sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z - GetSpriteHeight(nSprite), sprite[nSprite].sectnum,
                                    sprite[nTarget].x, sprite[nTarget].y, sprite[nTarget].z - GetSpriteHeight(nTarget), sprite[nTarget].sectnum))
                                {
                                    MummyList[nMummy].nAction = 3;
                                    MummyList[nMummy].B = 0;

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

                    // loc_2B5A8
                    if (!MummyList[nMummy].B)
                    {
                        sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
                        sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
                    }

                    if (sprite[nSprite].xvel || sprite[nSprite].yvel)
                    {
                        if (sprite[nSprite].xvel > 0)
                        {
                            sprite[nSprite].xvel -= 1024;
                            if (sprite[nSprite].xvel < 0) {
                                sprite[nSprite].xvel = 0;
                            }
                        }
                        else if (sprite[nSprite].xvel < 0)
                        {
                            sprite[nSprite].xvel += 1024;
                            if (sprite[nSprite].xvel > 0) {
                                sprite[nSprite].xvel = 0;
                            }
                        }

                        if (sprite[nSprite].yvel > 0)
                        {
                            sprite[nSprite].yvel -= 1024;
                            if (sprite[nSprite].yvel < 0) {
                                sprite[nSprite].yvel = 0;
                            }
                        }
                        else if (sprite[nSprite].yvel < 0)
                        {
                            sprite[nSprite].yvel += 1024;
                            if (sprite[nSprite].yvel > 0) {
                                sprite[nSprite].yvel = 0;
                            }
                        }
                    }

                    if (nMov)
                    {
                        switch (nMov & 0xC000)
                        {
                            case 0x8000:
                            {
                                sprite[nSprite].ang = (sprite[nSprite].ang + ((RandomWord() & 0x3FF) + 1024)) & kAngleMask;
                                sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
                                sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;
                                return;
                            }

                            case 0xC000:
                            {
                                if ((nMov & 0x3FFF) == nTarget)
                                {
                                    int nAngle = getangle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);
                                    if (AngleDiff(sprite[nSprite].ang, nAngle) < 64)
                                    {
                                        MummyList[nMummy].nAction = 2;
                                        MummyList[nMummy].B = 0;

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

                    break;
                }

                case 2:
                {
                    if (nTarget == -1)
                    {
                        MummyList[nMummy].nAction = 0;
                        MummyList[nMummy].B = 0;
                    }
                    else
                    {
                        if (PlotCourseToSprite(nSprite, nTarget) >= 1024)
                        {
                            MummyList[nMummy].nAction = 1;
                            MummyList[nMummy].B = 0;
                        }
                        else if (nFrameFlag & 0x80)
                        {
                            runlist_DamageEnemy(nTarget, nSprite, 5);
                        }
                    }
                    return;
                }

                case 3:
                {
                    if (bVal)
                    {
                        MummyList[nMummy].B = 0;
                        MummyList[nMummy].nAction = 0;
                        MummyList[nMummy].G = 100;
                        MummyList[nMummy].nTarget = -1;
                        return;
                    }
                    else if (nFrameFlag & 0x80)
                    {
                        SetQuake(nSprite, 100);

                        // low 16 bits of returned var contains the sprite index, the high 16 the bullet number
                        int nBullet = BuildBullet(nSprite, 9, 0, 0, -15360, sprite[nSprite].ang, nTarget + 10000, 1);
                        CheckMummyRevive(nMummy);

                        if (nBullet > -1)
                        {
                            if (!RandomSize(3))
                            {
                                // FIXME CHECKME - nBullet & 0xFFFF can be -1. Original code doesn't handle this??

                                SetBulletEnemy(nBullet, nTarget);
                                int nBulletSprite = GetBulletSprite(nBullet);
                                sprite[nBulletSprite].pal = 5;
                            }
                        }
                    }
                    return;
                }

                case 4:
                {
                    if (bVal)
                    {
                        MummyList[nMummy].B = 0;
                        MummyList[nMummy].nAction = 5;
                    }
                    return;
                }

                case 5:
                {
                    MummyList[nMummy].B = 0;
                    return;
                }

                case 6:
                {
                    if (bVal)
                    {
                        sprite[nSprite].cstat = 0x101;

                        MummyList[nMummy].nAction = 0;
                        MummyList[nMummy].nHealth = 300;
                        MummyList[nMummy].nTarget = -1;

                        nCreaturesLeft++;
                    }
                    return;
                }

                case 7:
                {
                    if (nMov & 0x20000)
                    {
                        sprite[nSprite].xvel >>= 1;
                        sprite[nSprite].yvel >>= 1;
                    }

                    if (bVal)
                    {
                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                        sprite[nSprite].cstat = 0x101;

                        MummyList[nMummy].nAction = 0;
                        MummyList[nMummy].B = 0;
                        MummyList[nMummy].nTarget = -1;
                    }

                    return;
                }
            }

            return;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqMummy] + ActionSeq[nAction].a, MummyList[nMummy].B, ActionSeq[nAction].b);
            return;
        }

        case 0xA0000:
        {
            if (MummyList[nMummy].nHealth <= 0)
                return;

            nDamage = runlist_CheckRadialDamage(nSprite);
            // fall through to 0x80000
            fallthrough__;
        }
        case 0x80000:
        {
            if (nDamage <= 0)
                return;

            if (MummyList[nMummy].nHealth <= 0) {
                return;
            }

            MummyList[nMummy].nHealth -= nDamage;

            if (MummyList[nMummy].nHealth <= 0)
            {
                MummyList[nMummy].nHealth = 0;
                sprite[nSprite].cstat &= 0xFEFE;
                nCreaturesLeft--;

                DropMagic(nSprite);

                MummyList[nMummy].B = 0;
                MummyList[nMummy].nAction = 4;

                sprite[nSprite].xvel = 0;
                sprite[nSprite].yvel = 0;
                sprite[nSprite].zvel = 0;
                sprite[nSprite].z = sector[sprite[nSprite].sectnum].floorz;
            }
            else
            {
                if (!RandomSize(2))
                {
                    MummyList[nMummy].nAction = 7;
                    MummyList[nMummy].B = 0;

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

            return;
        }
    }
}

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

void MummyLoadSave::Load()
{
    Read(&nMummies, sizeof(nMummies));
    Read(&MummyList, sizeof(MummyList[0]) * nMummies);
}

void MummyLoadSave::Save()
{
    Write(&nMummies, sizeof(nMummies));
    Write(&MummyList, sizeof(MummyList[0]) * nMummies);
}

static MummyLoadSave* myLoadSave;

void MummyLoadSaveConstruct()
{
    myLoadSave = new MummyLoadSave();
}


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

#include "aistuff.h"
#include "runlist.h"

#define kMaxMummies 150

void InitMummy();
int BuildMummy(int val, int x, int y, int z, int nSector, int nAngle);
void FuncMummy(int nSector, int edx, int nRun);

#endif