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 "typedefs.h"
#include "lighting.h"
#include "player.h"
#include "engine.h"
#include "exhumed.h"
#include "sound.h"
#include "light.h"
#include "random.h"
#include "save.h"
#include "status.h"
#include <string.h>
#include <assert.h>

#define kMaxFlashes			2000
#define kMaxFlickerMask		25
#define kMaxGlows			50
#define kMaxFlickers		100
#define kMaxFlows			375

struct Flash
{
    char field_0;
    short field_1;
    int8_t shade;
};

struct Glow
{
    short field_0;
    short field_2;
    short nSector;
    short field_6;
};

struct Flicker
{
    short field_0;
    short nSector;
    unsigned int field_4;
};

struct Flow
{
    short field_0;
    short field_2;
    int field_4;
    int field_8;
    int field_C;
    int field_10;
    int field_14;
    int field_18;
};

Flash sFlash[kMaxFlashes];
Glow sGlow[kMaxGlows];
short nNextFlash[kMaxFlashes];
Flicker sFlicker[kMaxFlickers];
short nFreeFlash[kMaxFlashes];
Flow sFlowInfo[kMaxFlows];
int flickermask[kMaxFlickerMask];

short bTorch = 0;
short nFirstFlash = -1;
short nLastFlash = -1;
short nFlashDepth = 2;
short nFlashes;
short nFlowCount;
short nFlickerCount;
short nGlowCount;

int bDoFlicks = 0;
int bDoGlows = 0;

int GrabFlash()
{
    if (nFlashes >= kMaxFlashes) {
        return -1;
    }

    short nFlash = nFreeFlash[nFlashes];
    nNextFlash[nFlash] = -1;

    nFlashes++;

    if (nLastFlash <= -1)
    {
        nFirstFlash = nFlash;
    }
    else
    {
        nNextFlash[nLastFlash] = nFlash;
    }

    nLastFlash = nFlash;

    return nLastFlash;
}

void InitLights()
{
    int i;
    nFlickerCount = 0;

    for (i = 0; i < kMaxFlickerMask; i++) {
        flickermask[i] = RandomSize(0x1F) * 2;
    }

    nGlowCount = 0;
    nFlowCount = 0;
    nFlashes  = 0;
    bDoFlicks = kFalse;
    bDoGlows  = kFalse;

    for (i = 0; i < kMaxFlashes; i++) {
        nFreeFlash[i] = i;
    }

    nFirstFlash = -1;
    nLastFlash  = -1;
}

void AddFlash(short nSector, int x, int y, int z, int val)
{
    assert(nSector >= 0 && nSector < kMaxSectors);

    int var_28 = 0;
    unsigned int var_1C = val >> 8;

    if (var_1C >= nFlashDepth) {
        return;
    }

    unsigned int var_20 = val & 0x80;
    unsigned int var_18 = val & 0x40;

    val = ((var_1C + 1) << 8) | (char)val;

    int var_14 = 0;

    short startwall = sector[nSector].wallptr;
    short endwall = sector[nSector].wallptr + sector[nSector].wallnum;

    for (int i = startwall; i < endwall; i++)
    {
        short wall2 = wall[i].point2;

        int xAverage = (wall[i].x + wall[wall2].x) / 2;
        int yAverage = (wall[i].y + wall[wall2].y) / 2;

        sectortype *pNextSector = NULL;
        if (wall[i].nextsector > -1) {
            pNextSector = &sector[wall[i].nextsector];
        }

        int ebx = -255;

        if (!var_18)
        {
            int x2 = x - xAverage;
            if (x2 < 0) {
                x2 = -x2;
            }

            ebx = x2;

            int y2 = y - yAverage;
            if (y2 < 0) {
                y2 = -y2;
            }

            ebx = ((y2 + ebx) >> 4) - 255;
        }

        if (ebx < 0)
        {
            var_14++;
            var_28 += ebx;

            if (wall[i].pal < 5)
            {
                if (!pNextSector || pNextSector->floorz < sector[nSector].floorz)
                {
                    short nFlash = GrabFlash();
                    if (nFlash < 0) {
                        return;
                    }

                    sFlash[nFlash].field_0 = var_20 | 2;
                    sFlash[nFlash].shade = wall[i].shade;
                    sFlash[nFlash].field_1 = i;

                    wall[i].pal += 7;

                    ebx += wall[i].shade;
                    int eax = ebx;

                    if (ebx < -127) {
                        eax = -127;
                    }

                    wall[i].shade = eax;

                    if (!var_1C && !wall[i].overpicnum && pNextSector)
                    {
                        AddFlash(wall[i].nextsector, x, y, z, val);
                    }
                }
            }
        }
    }

    if (var_14 && sector[nSector].floorpal < 4)
    {
        short nFlash = GrabFlash();
        if (nFlash < 0) {
            return;
        }

        sFlash[nFlash].field_0 = var_20 | 1;
        sFlash[nFlash].field_1 = nSector;
        sFlash[nFlash].shade = sector[nSector].floorshade;

        sector[nSector].floorpal += 7;

        int edx = sector[nSector].floorshade + var_28;
        int eax = edx;

        if (edx < -127) {
            eax = -127;
        }

        sector[nSector].floorshade = eax;

        if (!(sector[nSector].ceilingstat & 1))
        {
            if (sector[nSector].ceilingpal < 4)
            {
                short nFlash2 = GrabFlash();
                if (nFlash2 >= 0)
                {
                    sFlash[nFlash2].field_0 = var_20 | 3;
                    sFlash[nFlash2].field_1 = nSector;
                    sFlash[nFlash2].shade = sector[nSector].ceilingshade;

                    sector[nSector].ceilingpal += 7;

                    int edx = sector[nSector].ceilingshade + var_28;
                    int eax = edx;

                    if (edx < -127) {
                        eax = -127;
                    }

                    sector[nSector].ceilingshade = eax;
                }
            }
        }

        for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
        {
            if (sprite[nSprite].pal < 4)
            {
                short nFlash3 = GrabFlash();
                if (nFlash3 >= 0)
                {
                    sFlash[nFlash3].field_0 = var_20 | 4;
                    sFlash[nFlash3].shade = sprite[nSprite].shade;
                    sFlash[nFlash3].field_1 = nSprite;

                    sprite[nSprite].pal += 7;

                    int eax = -255;

                    if (!var_18)
                    {
                        int xDiff = x - sprite[nSprite].x;
                        if (xDiff < 0) {
                            xDiff = -xDiff;
                        }

                        int yDiff = y - sprite[nSprite].y;
                        if (yDiff < 0) {
                            yDiff = -yDiff;
                        }

                        eax = ((xDiff + yDiff) >> 4) - 255;
                    }

                    if (eax < 0)
                    {
                        short shade = sprite[nSprite].shade + eax;
                        if (shade < -127) {
                            shade = -127;
                        }

                        sprite[nSprite].shade = shade;
                    }
                }
            }
        }
    }
}

void UndoFlashes()
{
    if (!nFlashes) {
        return;
    }

    int var_24 = 0; // CHECKME - Watcom error "initializer for variable var_24 may not execute

    int edi = -1;

    for (short nFlash = nFirstFlash; nFlash >= 0; nFlash = nNextFlash[nFlash])
    {
        assert(nFlash < 2000 && nFlash >= 0);

        uint8_t var_28 = sFlash[nFlash].field_0 & 0x3F;
        short nIndex = sFlash[nFlash].field_1;

        if (sFlash[nFlash].field_0 & 0x80)
        {
            int var_20 = var_28 - 1;
            assert(var_20 >= 0);

            int8_t *pShade = NULL;

            switch (var_20)
            {
                case 0:
                {
                    assert(nIndex >= 0 && nIndex < kMaxSectors);

                    pShade = &sector[nIndex].floorshade;
                    break;
                }

                case 1:
                {
                    assert(nIndex >= 0 && nIndex < kMaxWalls);

                    pShade = &wall[nIndex].shade;
                    break;
                }

                case 2:
                {
                    assert(nIndex >= 0 && nIndex < kMaxSectors);

                    pShade = &sector[nIndex].ceilingshade;
                    break;
                }

                case 3:
                {
                    assert(nIndex >= 0 && nIndex < kMaxSprites);

                    if (sprite[nIndex].pal >= 7)
                    {
                        pShade = &sprite[nIndex].shade;
                    }
                    else {
                        goto loc_1868A;
                    }

                    break;
                }

                default:
                    break;
            }

            assert(pShade != NULL);

            short var_2C = (*pShade) + 6;
            int var_30 = sFlash[nFlash].shade;

            if (var_2C < var_30)
            {
                *pShade = var_2C;
                edi = nFlash;
                continue;
            }
        }

        // loc_185FE
        var_24 = var_28 - 1; // CHECKME - Watcom error "initializer for variable var_24 may not execute
        assert(var_24 >= 0);

        switch (var_24)
        {
            default:
                break;

            case 0:
            {
                sector[nIndex].floorpal -= 7;
                sector[nIndex].floorshade = sFlash[nFlash].shade;
                break;
            }

            case 1:
            {
                wall[nIndex].pal -= 7;
                wall[nIndex].shade = sFlash[nFlash].shade;
                break;
            }

            case 2:
            {
                sector[nIndex].ceilingpal -= 7;
                sector[nIndex].ceilingshade = sFlash[nFlash].shade;
                break;
            }

            case 3:
            {
                if (sprite[nIndex].pal >= 7)
                {
                    sprite[nIndex].pal -= 7;
                    sprite[nIndex].shade = sFlash[nFlash].shade;
                }

                break;
            }
        }

loc_1868A:

        nFlashes--;
        assert(nFlashes >= 0);

        nFreeFlash[nFlashes] = nFlash;

        if (edi != -1)
        {
            nNextFlash[edi] = nNextFlash[nFlash];
        }

        if (nFlash == nFirstFlash)
        {
            nFirstFlash = nNextFlash[nFirstFlash];
        }

        if (nFlash == nLastFlash)
        {
            nLastFlash = edi;
        }
    }
}

void AddGlow(short nSector, int nVal)
{
    if (nGlowCount >= kMaxGlows) {
        return;
    }

    sGlow[nGlowCount].field_6 = nVal;
    sGlow[nGlowCount].nSector = nSector;
    sGlow[nGlowCount].field_0 = -1;
    sGlow[nGlowCount].field_2 = 0;

    nGlowCount++;
}

void AddFlicker(short nSector, int nVal)
{
    if (nFlickerCount >= kMaxFlickers) {
        return;
    }

    sFlicker[nFlickerCount].field_0 = nVal;
    sFlicker[nFlickerCount].nSector = nSector;

    if (nVal >= 25) {
        nVal = 24;
    }

    sFlicker[nFlickerCount].field_4 = flickermask[nVal];

    nFlickerCount++;
}

void DoGlows()
{
    bDoGlows++;

    if (bDoGlows < 3) {
        return;
    }

    bDoGlows = 0;

    for (int i = 0; i < nGlowCount; i++)
    {
        sGlow[i].field_2++;

        short nSector = sGlow[i].nSector;
        short nShade = sGlow[i].field_0;

        if (sGlow[i].field_2 >= sGlow[i].field_6)
        {
            sGlow[i].field_2 = 0;
            sGlow[i].field_0 = -sGlow[i].field_0;
        }

        sector[nSector].ceilingshade += nShade;
        sector[nSector].floorshade   += nShade;

        int startwall = sector[nSector].wallptr;
        int endwall = startwall + sector[nSector].wallnum - 1;

        for (int nWall = startwall; nWall <= endwall; nWall++)
        {
            wall[nWall].shade += nShade;

            // CHECKME - ASM has edx decreasing here. why?
        }
    }
}

void DoFlickers()
{
    bDoFlicks ^= 1;
    if (!bDoFlicks) {
        return;
    }

    for (int i = 0; i < nFlickerCount; i++)
    {
        short nSector = sFlicker[i].nSector;

        unsigned int eax = (sFlicker[i].field_4 & 1);
        unsigned int edx = (sFlicker[i].field_4 & 1) << 31;
        unsigned int ebp = sFlicker[i].field_4 >> 1;

        ebp |= edx;
        edx = ebp & 1;

        sFlicker[i].field_4 = ebp;

        if (edx ^ eax)
        {
            short shade;

            if (eax)
            {
                shade = sFlicker[i].field_0;
            }
            else
            {
                shade = -sFlicker[i].field_0;
            }

            sector[nSector].ceilingshade += shade;
            sector[nSector].floorshade += shade;

            int startwall = sector[nSector].wallptr;
            int endwall = startwall + sector[nSector].wallnum - 1;

            for (int nWall = endwall; nWall >= startwall; nWall--)
            {
                wall[nWall].shade += shade;

                // CHECKME - ASM has edx decreasing here. why?
            }
        }
    }
}

// nWall can also be passed in here via nSprite parameter - TODO - rename nSprite parameter :)
void AddFlow(int nSprite, int nSpeed, int b)
{
    if (nFlowCount >= kMaxFlows)
        return;

    short nFlow = nFlowCount;
    nFlowCount++;

    short var_18;

    if (b < 2)
    {
        var_18 = sprite[nSprite].sectnum;
        short nPic = sector[var_18].floorpicnum;
        short nAngle = sprite[nSprite].ang;

        sFlowInfo[nFlow].field_14 = (tilesiz[nPic].x << 14) - 1;
        sFlowInfo[nFlow].field_18 = (tilesiz[nPic].y << 14) - 1;
        sFlowInfo[nFlow].field_C  = -Cos(nAngle) * nSpeed;
        sFlowInfo[nFlow].field_10 = Sin(nAngle) * nSpeed;
    }
    else
    {
        short nAngle;

        if (b == 2) {
            nAngle = 512;
        }
        else {
            nAngle = 1536;
        }

        var_18 = nSprite;
        short nPic = wall[var_18].picnum;

        sFlowInfo[nFlow].field_14 = (tilesiz[nPic].x * wall[var_18].xrepeat) << 8;
        sFlowInfo[nFlow].field_18 = (tilesiz[nPic].y * wall[var_18].yrepeat) << 8;
        sFlowInfo[nFlow].field_C = -Cos(nAngle) * nSpeed;
        sFlowInfo[nFlow].field_10 = Sin(nAngle) * nSpeed;
    }

    sFlowInfo[nFlow].field_8 = 0;
    sFlowInfo[nFlow].field_4 = 0;
    sFlowInfo[nFlow].field_0 = var_18;
    sFlowInfo[nFlow].field_2 = b;
}

void DoFlows()
{
    for (int i = 0; i < nFlowCount; i++)
    {
        sFlowInfo[i].field_4 += sFlowInfo[i].field_C;
        sFlowInfo[i].field_8 += sFlowInfo[i].field_10;

        switch (sFlowInfo[i].field_2)
        {
            case 0:
            {
                sFlowInfo[i].field_4 &= sFlowInfo[i].field_14;
                sFlowInfo[i].field_8 &= sFlowInfo[i].field_18;

                short nSector = sFlowInfo[i].field_0;
                sector[nSector].floorxpanning = sFlowInfo[i].field_4 >> 14;
                sector[nSector].floorypanning = sFlowInfo[i].field_8 >> 14;
                break;
            }

            case 1:
            {
                short nSector = sFlowInfo[i].field_0;

                sector[nSector].ceilingxpanning = sFlowInfo[i].field_4 >> 14;
                sector[nSector].ceilingypanning = sFlowInfo[i].field_8 >> 14;

                sFlowInfo[i].field_4 &= sFlowInfo[i].field_14;
                sFlowInfo[i].field_8 &= sFlowInfo[i].field_18;
                break;
            }

            case 2:
            {
                short nWall = sFlowInfo[i].field_0;

                wall[nWall].xpanning = sFlowInfo[i].field_4 >> 14;
                wall[nWall].ypanning = sFlowInfo[i].field_8 >> 14;

                if (sFlowInfo[i].field_4 < 0)
                {
                    sFlowInfo[i].field_4 += sFlowInfo[i].field_14;
                }

                if (sFlowInfo[i].field_8 < 0)
                {
                    sFlowInfo[i].field_8 += sFlowInfo[i].field_18;
                }

                break;
            }

            case 3:
            {
                short nWall = sFlowInfo[i].field_0;

                wall[nWall].xpanning = sFlowInfo[i].field_4 >> 14;
                wall[nWall].ypanning = sFlowInfo[i].field_8 >> 14;

                if (sFlowInfo[i].field_4 >= sFlowInfo[i].field_14)
                {
                    sFlowInfo[i].field_4 -= sFlowInfo[i].field_14;
                }

                if (sFlowInfo[i].field_8 >= sFlowInfo[i].field_18)
                {
                    sFlowInfo[i].field_8 -= sFlowInfo[i].field_18;
                }

                break;
            }
        }
    }
}

void DoLights()
{
    DoFlickers();
    DoGlows();
    DoFlows();
}

void SetTorch(int nPlayer, int bTorchOnOff)
{
    char buf[40];

    if (bTorchOnOff == bTorch) {
        return;
    }

    if (nPlayer != nLocalPlayer) {
        return;
    }

    // char *pTempPal = origpalookup[kPalTorch];
    // palookup[kPalTorch] = palookup[kPalNoTorch];
    // palookup[kPalNoTorch] = pTempPal;
    //
    // pTempPal = origpalookup[kPalTorch];
    // origpalookup[kPalTorch] = origpalookup[kPalNoTorch];
    // origpalookup[kPalNoTorch] = pTempPal;
    //
    // pTempPal = origpalookup[kPalTorch2];
    // origpalookup[kPalTorch2] = origpalookup[kPalNoTorch2];
    // origpalookup[kPalNoTorch2] = pTempPal;
    //
    // pTempPal = palookup[kPalTorch2];
    // palookup[kPalNoTorch2] = palookup[kPalTorch2];
    // palookup[kPalTorch2] = pTempPal;

    if (bTorchOnOff == 2) {
        bTorch = !bTorch;
    }
    else {
        bTorch = bTorchOnOff;
    }

    if (bTorch) {
        PlayLocalSound(kSoundTorchOn, 0);
    }

    strcpy(buf, "TORCH IS ");

    if (bTorch) {
        strcat(buf, "LIT");
    }
    else {
        strcat(buf, "OUT");
    }

    StatusMessage(150, buf);
}

void BuildFlash(short nPlayer, short UNUSED(nSector), int nVal)
{
    if (nPlayer == nLocalPlayer)
    {
        flash = nVal;
        flash = -nVal; // ???
    }
}

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

void LightingLoadSave::Load()
{
    Read(&bTorch, sizeof(bTorch));
    Read(&nFirstFlash, sizeof(nFirstFlash));
    Read(&nLastFlash, sizeof(nLastFlash));
    Read(&nFlashDepth, sizeof(nFlashDepth));
    Read(&nFlashes, sizeof(nFlashes));
    Read(&nFlowCount, sizeof(nFlowCount));
    Read(&nFlickerCount, sizeof(nFlickerCount));
    Read(&nGlowCount, sizeof(nGlowCount));
    Read(&bDoFlicks, sizeof(bDoFlicks));
    Read(&bDoGlows, sizeof(bDoGlows));

    Read(sFlash, sizeof(sFlash));
    Read(sGlow, sizeof(sGlow));
    Read(nNextFlash, sizeof(nNextFlash));
    Read(sFlicker, sizeof(sFlicker));
    Read(nFreeFlash, sizeof(nFreeFlash));
    Read(sFlowInfo, sizeof(sFlowInfo));
    Read(flickermask, sizeof(flickermask));
}

void LightingLoadSave::Save()
{
    Write(&bTorch, sizeof(bTorch));
    Write(&nFirstFlash, sizeof(nFirstFlash));
    Write(&nLastFlash, sizeof(nLastFlash));
    Write(&nFlashDepth, sizeof(nFlashDepth));
    Write(&nFlashes, sizeof(nFlashes));
    Write(&nFlowCount, sizeof(nFlowCount));
    Write(&nFlickerCount, sizeof(nFlickerCount));
    Write(&nGlowCount, sizeof(nGlowCount));
    Write(&bDoFlicks, sizeof(bDoFlicks));
    Write(&bDoGlows, sizeof(bDoGlows));

    Write(sFlash, sizeof(sFlash));
    Write(sGlow, sizeof(sGlow));
    Write(nNextFlash, sizeof(nNextFlash));
    Write(sFlicker, sizeof(sFlicker));
    Write(nFreeFlash, sizeof(nFreeFlash));
    Write(sFlowInfo, sizeof(sFlowInfo));
    Write(flickermask, sizeof(flickermask));
}

static LightingLoadSave* myLoadSave;

void LightingLoadSaveConstruct()
{
    myLoadSave = new LightingLoadSave();
}


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

extern short nFlashDepth;

void InitLights();
void AddFlash(short nSector, int x, int y, int z, int val);
void SetTorch(int nPlayer, int bTorchOnOff);
void UndoFlashes();
void DoLights();
void AddFlow(int nSprite, int nSpeed, int b);
void BuildFlash(short nPlayer, short nSector, int nVal);
void AddGlow(short nSector, int nVal);
void AddFlicker(short nSector, int nVal);

extern short bTorch;

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

#define kMaxLions   40

static actionSeq ActionSeq[] = {
    {54, 1},
    {18, 0},
    {0,  0},
    {10, 0},
    {44, 0},
    {18, 0},
    {26, 0},
    {34, 0},
    {8,  1},
    {9,  1},
    {52, 1},
    {53, 1}
};

struct Lion
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short nTarget;
    short _f;
    short _g;
};

short LionCount = 0;
short MoveHook[kMaxLions];
Lion LionList[kMaxLions];

void InitLion()
{
    LionCount = kMaxLions;
}

int BuildLion(short nSprite, int x, int y, int z, short nSector, short nAngle)
{
    LionCount--;

    short nLion = LionCount;
    if (nLion < 0) {
        return -1;
    }

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 104);
    }
    else
    {
        changespritestat(nSprite, 104);
        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].clipdist = 60;
    sprite[nSprite].shade = -12;
    sprite[nSprite].xrepeat = 40;
    sprite[nSprite].yrepeat = 40;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].ang = nAngle;
    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;

//	GrabTimeSlot(3);

    LionList[nLion].nAction = 0;
    LionList[nLion].nHealth = 500;
    LionList[nLion].nFrame = 0;
    LionList[nLion].nSprite = nSprite;
    LionList[nLion].nTarget = -1;
    LionList[nLion]._g = 0;
    LionList[nLion]._f = nLion;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nLion, kRunLion);

    MoveHook[nLion] = runlist_AddRunRec(NewRun, nLion, kRunLion);

    nCreaturesLeft++;

    return nLion;
}

void FuncLion(int a, int nDamage, int nRun)
{
    short nLion = RunData[nRun].nVal;
    assert(nLion >= 0 && nLion < kMaxLions);

    short nSprite = LionList[nLion].nSprite;
    short nAction = LionList[nLion].nAction;

    bool bVal = false;

    int nMessage = a & kMessageMask;

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

        case 0x90000:
        {
            seq_PlotSequence(a, SeqOffsets[kSeqLion] + ActionSeq[nAction].a, LionList[nLion].nFrame, ActionSeq[nAction].b);
            return;
        }

        case 0xA0000:
        {
            nDamage = runlist_CheckRadialDamage(nSprite);
            // now fall through to 0x80000
            fallthrough__;
        }
        case 0x80000:
        {
            if (nDamage && LionList[nLion].nHealth > 0)
            {
                LionList[nLion].nHealth -= nDamage;
                if (LionList[nLion].nHealth <= 0)
                {
                    // R.I.P.
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                    sprite[nSprite].zvel = 0;
                    sprite[nSprite].cstat &= 0xFEFE;

                    LionList[nLion].nHealth = 0;

                    nCreaturesLeft--;

                    if (nAction < 10)
                    {
                        DropMagic(nSprite);

                        if (nMessage == 0xA0000) {
                            LionList[nLion].nAction = 11;
                        }
                        else
                        {
                            LionList[nLion].nAction = 10;
                        }

                        LionList[nLion].nFrame = 0;
                        return;
                    }
                }
                else
                {
                    short nTarget = a & 0xFFFF;

                    if (nTarget > -1)
                    {
                        if (sprite[nTarget].statnum < 199) {
                            LionList[nLion].nTarget = nTarget;
                        }

                        if (nAction != 6)
                        {
                            if (RandomSize(8) <= (LionList[nLion].nHealth >> 2))
                            {
                                LionList[nLion].nAction = 4;
                                sprite[nSprite].xvel = 0;
                                sprite[nSprite].yvel = 0;
                            }
                            else if (RandomSize(1))
                            {
                                PlotCourseToSprite(nSprite, nTarget);
                                LionList[nLion].nAction = 5;
                                LionList[nLion]._g = RandomSize(3);
                                sprite[nSprite].ang = (sprite[nSprite].ang - (RandomSize(1) << 8)) + (RandomSize(1) << 8); // NOTE: no angle mask in original code
                            }
                            else
                            {
                                LionList[nLion].nAction = 8;
                                sprite[nSprite].xvel = 0;
                                sprite[nSprite].yvel = 0;
                                sprite[nSprite].cstat &= 0xFEFE;
                            }

                            LionList[nLion].nFrame = 0;
                        }
                    }
                }
            }
            return;
        }

        case 0x20000:
        {
            if (nAction != 7) {
                Gravity(nSprite);
            }

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

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, LionList[nLion].nFrame);

            seq_MoveSequence(nSprite, nSeq, LionList[nLion].nFrame);

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

            short nFlag = FrameFlag[SeqBase[nSeq] + LionList[nLion].nFrame];
            short nTarget = LionList[nLion].nTarget;

            int nMov = MoveCreatureWithCaution(nSprite);

            switch (nAction)
            {
                default:
                    return;

                case 0:
                case 1:
                {
                    if ((LionList[nLion]._f & 0x1F) == (totalmoves & 0x1F))
                    {
                        if (nTarget < 0)
                        {
                            nTarget = FindPlayer(nSprite, 40);
                            if (nTarget >= 0)
                            {
                                D3PlayFX(StaticSound[kSoundCatICU], nSprite);
                                LionList[nLion].nAction = 2;
                                LionList[nLion].nFrame = 0;

                                sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
                                sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
                                LionList[nLion].nTarget = nTarget;
                                return;
                            }
                        }
                    }

                    if (nAction)
                    {
                        LionList[nLion]._g--;
                        if (LionList[nLion]._g <= 0)
                        {
                            if (RandomBit())
                            {
                                sprite[nSprite].ang = RandomWord() & kAngleMask;
                                sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
                                sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
                            }
                            else
                            {
                                sprite[nSprite].xvel = 0;
                                sprite[nSprite].yvel = 0;
                            }

                            LionList[nLion]._g = 100;
                        }
                    }

                    return;
                }

                case 2:
                {
                    if ((totalmoves & 0x1F) == (LionList[nLion]._f & 0x1F))
                    {
                        PlotCourseToSprite(nSprite, nTarget);

                        int nAng = sprite[nSprite].ang & 0xFFF8;

                        if (sprite[nSprite].cstat & 0x8000)
                        {
                            sprite[nSprite].xvel = Cos(nAng) * 2;
                            sprite[nSprite].yvel = Sin(nAng) * 2;
                        }
                        else
                        {
                            sprite[nSprite].xvel = Cos(nAng) >> 1;
                            sprite[nSprite].yvel = Sin(nAng) >> 1;
                        }
                    }

                    if ((nMov & 0xC000) < 0x8000)
                    {
                        break;
                    }
                    else if ((nMov & 0xC000) == 0x8000)
                    {
                        // loc_378FA:
                        sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                        sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
                        sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
                        break;
                    }
                    else if ((nMov & 0xC000) == 0xC000)
                    {
                        if ((nMov & 0x3FFF) == nTarget)
                        {
                            if (sprite[nSprite].cstat & 0x8000)
                            {
                                LionList[nLion].nAction = 9;
                                sprite[nSprite].cstat &= 0x7FFF;
                                sprite[nSprite].xvel = 0;
                                sprite[nSprite].yvel = 0;
                            }
                            else
                            {
                                int nAng = getangle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);

                                if (AngleDiff(sprite[nSprite].ang, nAng) < 64)
                                {
                                    LionList[nLion].nAction = 3;
                                }
                            }

                            LionList[nLion].nFrame = 0;
                            break;
                        }
                        else
                        {
                            // loc_378FA:
                            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                            sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
                            sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
                            break;
                        }
                    }

                    break;
                }

                case 3:
                {
                    if (nTarget == -1)
                    {
                        LionList[nLion].nAction = 1;
                        LionList[nLion]._g = 50;
                    }
                    else
                    {
                        if (PlotCourseToSprite(nSprite, nTarget) >= 768)
                        {
                            LionList[nLion].nAction = 2;
                        }
                        else if (nFlag & 0x80)
                        {
                            runlist_DamageEnemy(nTarget, nSprite, 10);
                        }
                    }

                    break;
                }

                case 4:
                {
                    if (bVal)
                    {
                        LionList[nLion].nAction = 2;
                        LionList[nLion].nFrame = 0;
                    }

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

                    return;
                }

                case 5: // Jump away when damaged
                {
                    LionList[nLion]._g--;
                    if (LionList[nLion]._g <= 0)
                    {
                        sprite[nSprite].zvel = -4000;
                        LionList[nLion]._g = 0;

                        int x = sprite[nSprite].x;
                        int y = sprite[nSprite].y;
                        int z = sprite[nSprite].z - (GetSpriteHeight(nSprite) >> 1);

                        int nCheckDist = 0x7FFFFFFF;

                        short nAngle = sprite[nSprite].ang;
                        short nScanAngle = (sprite[nSprite].ang - 512) & kAngleMask;

                        for (int i = 0; i < 5; i++)
                        {
                            short hitwall;
                            int hitx, hity;
                            vec3_t startPos = { x, y, z };
                            hitdata_t hitData;

                            hitscan(&startPos, sprite[nSprite].sectnum, Cos(nScanAngle), Sin(nScanAngle), 0, &hitData, CLIPMASK1);

                            hitx = hitData.x;
                            hity = hitData.y;
                            hitwall = hitData.wall;

                            if (hitwall > -1)
                            {
                                int theX = klabs(hitx - x);
                                int theY = klabs(hity - y);

                                if ((theX + theY) < nCheckDist)
                                {
                                    nCheckDist = theX;
                                    nAngle = nScanAngle;
                                }
                            }

                            nScanAngle += 256;
                            nScanAngle &= kAngleMask;
                        }

                        sprite[nSprite].ang = nAngle;

                        LionList[nLion].nAction = 6;
                        sprite[nSprite].xvel = (Cos(sprite[nSprite].ang)) - (Cos(sprite[nSprite].ang) >> 3);
                        sprite[nSprite].yvel = (Sin(sprite[nSprite].ang)) - (Sin(sprite[nSprite].ang) >> 3);
                        D3PlayFX(StaticSound[kSoundCatICU], nSprite);
                    }

                    return;
                }

                case 6:
                {
                    if (nMov & 0x30000)
                    {
                        LionList[nLion].nAction = 2;
                        LionList[nLion].nFrame = 0;
                        return;
                    }

                    if ((nMov & 0xC000) == 0x8000)
                    {
                        LionList[nLion].nAction = 7;
                        sprite[nSprite].ang = (GetWallNormal(nMov & 0x3FFF) + 1024) & kAngleMask;
                        LionList[nLion]._g = RandomSize(4);
                        return;
                    }
                    else if ((nMov & 0xC000) == 0xC000)
                    {
                        if ((nMov & 0x3FFF) == nTarget)
                        {
                            int nAng = getangle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);
                            if (AngleDiff(sprite[nSprite].ang, nAng) < 64)
                            {
                                LionList[nLion].nAction = 3;
                                LionList[nLion].nFrame = 0;
                            }
                        }
                        else
                        {
                            // loc_378FA:
                            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                            sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 1;
                            sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 1;
                            break;
                        }
                    }

                    return;
                }

                case 7:
                {
                    LionList[nLion]._g--;

                    if (LionList[nLion]._g <= 0)
                    {
                        LionList[nLion]._g = 0;
                        if (nTarget > -1)
                        {
                            PlotCourseToSprite(nSprite, nTarget);
                        }
                        else
                        {
                            sprite[nSprite].ang = (RandomSize(9) + (sprite[nSprite].ang + 768)) & kAngleMask;
                        }

                        sprite[nSprite].zvel = -1000;

                        LionList[nLion].nAction = 6;
                        sprite[nSprite].xvel = (Cos(sprite[nSprite].ang)) - (Cos(sprite[nSprite].ang) >> 3);
                        sprite[nSprite].yvel = (Sin(sprite[nSprite].ang)) - (Sin(sprite[nSprite].ang) >> 3);
                        D3PlayFX(StaticSound[kSoundCatICU], nSprite);
                    }

                    return;
                }

                case 8:
                {
                    if (bVal)
                    {
                        LionList[nLion].nAction = 2;
                        LionList[nLion].nFrame  = 0;
                        sprite[nSprite].cstat |= 0x8000;
                    }
                    return;
                }

                case 9:
                {
                    if (bVal)
                    {
                        LionList[nLion].nFrame  = 0;
                        LionList[nLion].nAction = 2;
                        sprite[nSprite].cstat |= 0x101;
                    }
                    return;
                }

                case 10:
                case 11:
                {
                    if (bVal)
                    {
                        runlist_SubRunRec(sprite[nSprite].owner);
                        runlist_SubRunRec(MoveHook[nLion]);
                        sprite[nSprite].cstat = 0x8000;
                    }
                    return;
                }
            }

            // loc_379AD: ?
            if (nAction != 1 && nTarget != -1)
            {
                if (!(sprite[nTarget].cstat & 0x101))
                {
                    LionList[nLion].nAction = 1;
                    LionList[nLion].nFrame = 0;
                    LionList[nLion]._g = 100;
                    LionList[nLion].nTarget = -1;
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                }
            }

            return;
        }
    }
}

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

void LionLoadSave::Load()
{
    Read(&LionCount, sizeof(LionCount));
    Read(&MoveHook, sizeof(MoveHook));
    Read(&LionList[LionCount], sizeof(Lion) * (kMaxLions - LionCount));
}

void LionLoadSave::Save()
{
    Write(&LionCount, sizeof(LionCount));
    Write(&MoveHook, sizeof(MoveHook));
    Write(&LionList[LionCount], sizeof(Lion) * (kMaxLions - LionCount));
}

static LionLoadSave* myLoadSave;

void LionLoadSaveConstruct()
{
    myLoadSave = new LionLoadSave();
}


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

#include "aistuff.h"

void InitLion();
int BuildLion(short nSprite, int x, int y, int z, short nSector, short nAngle);
void FuncLion(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 "typedefs.h"
#include <string.h>
#include "player.h"
#include "init.h"
#include "engine.h"
#include "exhumed.h"
#include "view.h"
#include "names.h"
#include "util.h"

int bShowTowers = kFalse;
int bFollowMode = kTrue;
int ldMapZoom;
int lMapZoom;
int32_t mapx, mapy, mapang;

int32_t x = 0;
int32_t y = 0;
int16_t nAngle = 0;

int mapForward, mapStrafe;
fix16_t mapTurn;

int32_t nMapPic = -1;

void MarkSectorSeen(short nSector);

// TEMP - taken from Blood
template<typename T> void GetSpriteExtents(T const* const pSprite, int* top, int* bottom)
{
    *top = *bottom = pSprite->z;
    if ((pSprite->cstat & 0x30) != 0x20)
    {
        int height = tilesiz[pSprite->picnum].y;
        int center = height / 2 + picanm[pSprite->picnum].yofs;
        *top -= (pSprite->yrepeat << 2) * center;
        *bottom += (pSprite->yrepeat << 2) * (height - center);
    }
}

void InitMap()
{
    memset(show2dsector, 0, sizeof(show2dsector));
    memset(show2dwall,   0, sizeof(show2dwall));
    memset(show2dsprite, 0, sizeof(show2dsprite));

    mapForward = 0;
    mapStrafe = 0;
    mapTurn = 0;

    nMapPic = -1;
    ldMapZoom = 64;
    lMapZoom  = 1000;
    bFollowMode = kTrue;
}

void GrabMap()
{
    for (int i = 0; i < numsectors; i++) {
        MarkSectorSeen(i);
    }
}

void MarkSectorSeen(short nSector)
{
    int i = nSector;

    if (!(pow2char[i & 7] & show2dsector[i >> 3]))
    {
        show2dsector[i >> 3] |= pow2char[i & 7];
        walltype* wal = &wall[sector[i].wallptr];

        for (int j = sector[i].wallnum; j > 0; j--, wal++)
        {
            i = wal->nextsector;
            if (i < 0)
                continue;

            // if it's a dark sector and the torch is off, don't reveal the next sector
            if (sector[i].ceilingpal == 3 && !nPlayerTorch[nLocalPlayer])
                continue;

            uint16_t const nextwall = wal->nextwall;
            if (nextwall < MAXWALLS && wall[nextwall].cstat & 0x0071)
                continue;

            if (sector[i].ceilingz >= sector[i].floorz)
                continue;

            show2dsector[i >> 3] |= pow2char[i & 7];
        }
    }
}

static void G_DrawOverheadMap(int32_t cposx, int32_t cposy, int32_t czoom, int16_t cang)
{
    int32_t i, j, k, x1, y1, x2=0, y2=0, ox, oy;
    int32_t z1, z2, startwall, endwall;
    int32_t xvect, yvect, xvect2, yvect2;
    char col;
    uwallptr_t wal, wal2;

    int32_t tmpydim = (xdim*5)/8;

    renderSetAspect(65536, divscale16(tmpydim*320, xdim*200));

    xvect = Sin(-cang) * czoom;
    yvect = Sin(1536 - cang) * czoom;

    xvect2 = mulscale16(xvect, yxaspect);
    yvect2 = mulscale16(yvect, yxaspect);

    renderDisableFog();

    // draw player position arrow
    /*
    renderDrawLine(xdim << 11, (ydim << 11) - 20480, xdim << 11, (ydim << 11) + 20480, 24);
    renderDrawLine((xdim << 11) - 20480, ydim << 11, xdim << 11, (ydim << 11) - 20480, 24);
    renderDrawLine((xdim << 11) + 20480, ydim << 11, xdim << 11, (ydim << 11) - 20480, 24);
    */
    short nPlayerSprite = PlayerList[nLocalPlayer].nSprite;

    int nPlayerZ = sprite[nPlayerSprite].z;

    // Draw red lines
    for (i=numsectors-1; i>=0; i--)
    {
        if (!(show2dsector[i>>3]&pow2char[i&7])) continue;

        startwall = sector[i].wallptr;
        endwall = sector[i].wallptr + sector[i].wallnum;

        z1 = sector[i].ceilingz;
        z2 = sector[i].floorz;

        for (j=startwall, wal=(uwallptr_t)&wall[startwall]; j<endwall; j++, wal++)
        {
            k = wal->nextwall;
            if (k < 0) continue;

            if (sector[wal->nextsector].ceilingz == z1 && sector[wal->nextsector].floorz == z2)
                if (((wal->cstat|wall[wal->nextwall].cstat)&(16+32)) == 0) continue;

            if (nMapMode == 2)
                col = 111;
            else
                col = 111 - min(klabs(z2 - nPlayerZ) >> 13, 12);

            ox = wal->x-cposx;
            oy = wal->y-cposy;
            x1 = dmulscale16(ox, xvect, -oy, yvect)+(xdim<<11);
            y1 = dmulscale16(oy, xvect2, ox, yvect2)+(ydim<<11);

            wal2 = (uwallptr_t)&wall[wal->point2];
            ox = wal2->x-cposx;
            oy = wal2->y-cposy;
            x2 = dmulscale16(ox, xvect, -oy, yvect)+(xdim<<11);
            y2 = dmulscale16(oy, xvect2, ox, yvect2)+(ydim<<11);

            renderDrawLine(x1, y1, x2, y2, col);
        }
    }

    // Draw white lines
    for (i=numsectors-1; i>=0; i--)
    {
        if (!(show2dsector[i>>3]&pow2char[i&7])) continue;

        startwall = sector[i].wallptr;
        endwall = sector[i].wallptr + sector[i].wallnum;
        z2 = sector[i].floorz;

        if (nMapMode == 2)
        {
            col = 111;
        }
        else
        {
            col = klabs(z2 - nPlayerZ) >> 13;
            if (col > 15)
                continue;
            col = 111 - col;
        }

        k = -1;
        for (j=startwall, wal=(uwallptr_t)&wall[startwall]; j<endwall; j++, wal++)
        {
            if (wal->nextwall >= 0) continue;

            if (tilesiz[wal->picnum].x == 0) continue;
            if (tilesiz[wal->picnum].y == 0) continue;

            if (j == k)
            {
                x1 = x2;
                y1 = y2;
            }
            else
            {
                ox = wal->x-cposx;
                oy = wal->y-cposy;
                x1 = dmulscale16(ox, xvect, -oy, yvect)+(xdim<<11);
                y1 = dmulscale16(oy, xvect2, ox, yvect2)+(ydim<<11);
            }

            k = wal->point2;
            wal2 = (uwallptr_t)&wall[k];
            ox = wal2->x-cposx;
            oy = wal2->y-cposy;
            x2 = dmulscale16(ox, xvect, -oy, yvect)+(xdim<<11);
            y2 = dmulscale16(oy, xvect2, ox, yvect2)+(ydim<<11);

            renderDrawLine(x1, y1, x2, y2, col);
        }
    }

    if (bShowTowers)
    {
        for (int nSprite = headspritestat[406]; nSprite != -1; nSprite = nextspritestat[nSprite])
        {
            int ox = sprite[nSprite].x - cposx;
            int oy = sprite[nSprite].y - cposy;

            int x1 = mulscale16(ox, xvect) - mulscale16(oy, yvect);
            int y1 = mulscale16(oy, xvect2) + mulscale16(ox, yvect2);

            //int nTile = kTile72 + (((int)totalclock / 64) & 3);
            int nTile = kTile67 + (((int)totalclock / 64) & 2);

            int8_t xofsbak = picanm[nTile].xofs;
            int8_t yofsbak = picanm[nTile].yofs;
            picanm[nTile].xofs = 0; picanm[nTile].yofs = 0;

            rotatesprite_win((x1 << 4) + (xdim << 15), (y1 << 4) + (ydim << 15), 65535, 0, nTile, 0, 0, 0);

            picanm[nTile].xofs = xofsbak;
            picanm[nTile].yofs = yofsbak;

            #if 0
            char nCol = 170;
            int val = 2048;

            renderDrawLine(
                (x1 - val) + (xdim << 11),
                (y1 - val) + (ydim << 11),
                (x1 - val) + (xdim << 11),
                (y1 + val) + (ydim << 11),
                nCol);

            renderDrawLine(
                x1 + (xdim << 11),
                (y1 - val) + (ydim << 11),
                x1 + (xdim << 11),
                (y1 + val) + (ydim << 11),
                nCol);

            renderDrawLine(
                (x1 + val) + (xdim << 11),
                (y1 - val) + (ydim << 11),
                (x1 + val) + (xdim << 11),
                (y1 + val) + (ydim << 11),
                nCol);
            #endif
        }
    }

    renderEnableFog();

    videoSetCorrectedAspect();

    // Draw player sprite position representation
    if (bFollowMode && nMapPic >= 0)
    {
        spritetype* pSprite = &sprite[PlayerList[nLocalPlayer].nSprite];

        int px = 0;
        int py = 0;
        int pa = 0;
        int x1 = dmulscale16(px, xvect, -py, yvect);
        int y1 = dmulscale16(py, xvect2, px, yvect);

        {
            int ceilZ, ceilHit, floorZ, floorHit;

            vec3_t pos;
            getzrange(&pos, pSprite->sectnum, &ceilZ, &ceilHit, &floorZ, &floorHit, (pSprite->clipdist << 2) + 16, CLIPMASK0);

            int nTop, nBottom;
            GetSpriteExtents(pSprite, &nTop, &nBottom);
            int nScale = mulscale((pSprite->yrepeat + ((floorZ - nBottom) >> 8)) * czoom, yxaspect, 16);
            nScale = ClipRange(nScale, 22000, 65536 << 1);

            nScale = mulscale16(czoom* (pSprite->yrepeat), yxaspect);

            rotatesprite((xdim << 15) + (x1 << 4), (ydim << 15) + (y1 << 4), nScale, pa, nMapPic, pSprite->shade, pSprite->pal, (pSprite->cstat & 2) >> 1,
                windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
        }
    }

#if 0
    for (TRAVERSE_CONNECT(p))
    {
        if (ud.scrollmode && p == screenpeek) continue;

        auto const pPlayer = g_player[p].ps;
        auto const pSprite = (uspriteptr_t)&sprite[pPlayer->i];

        ox = pSprite->x - cposx;
        oy = pSprite->y - cposy;
        daang = (pSprite->ang - cang) & 2047;
        if (p == screenpeek)
        {
            ox = 0;
            oy = 0;
            daang = 0;
        }
        x1 = mulscale16(ox, xvect) - mulscale16(oy, yvect);
        y1 = mulscale16(oy, xvect2) + mulscale16(ox, yvect2);

        if (p == screenpeek || GTFLAGS(GAMETYPE_OTHERPLAYERSINMAP))
        {
            if (pSprite->xvel > 16 && pPlayer->on_ground)
                i = APLAYERTOP+(((int32_t) totalclock>>4)&3);
            else
                i = APLAYERTOP;

            i = VM_OnEventWithReturn(EVENT_DISPLAYOVERHEADMAPPLAYER, pPlayer->i, p, i);

            if (i < 0)
                continue;

            j = klabs(pPlayer->truefz - pPlayer->pos.z) >> 8;
            j = mulscale16(czoom * (pSprite->yrepeat + j), yxaspect);

            if (j < 22000) j = 22000;
            else if (j > (65536<<1)) j = (65536<<1);

            rotatesprite_win((x1<<4)+(xdim<<15), (y1<<4)+(ydim<<15), j, daang, i, pSprite->shade,
                P_GetOverheadPal(pPlayer), 0);
        }
    }
#endif
}

void UpdateMap()
{
    // ceilingpal 3 is a dark area for the torch.
    if (sector[initsect].ceilingpal != 3 || (nPlayerTorch[nLocalPlayer] != 0)) {
        MarkSectorSeen(initsect);
    }
}

void SetMapPosition(int32_t _x, int32_t _y, int16_t _nAngle)
{
    x = _x;
    y = _y;
    nAngle = _nAngle;
}

void DrawMap()
{
    if (bFollowMode)
    {
        x = initx;
        y = inity;
        nAngle = inita;
    }
    else
    {
        nAngle += fix16_to_int(mapTurn) >> 3;
        x += mulscale18(mapForward, Cos(nAngle));
        y += mulscale18(mapForward, Sin(nAngle));
        x -= mulscale18(mapStrafe, Cos(nAngle + 512));
        y -= mulscale18(mapStrafe, Sin(nAngle + 512));

        mapTurn = 0;
        mapForward = 0;
        mapStrafe = 0;
    }

    if (!nFreeze && nMapMode)
    {
        if (nMapMode == 2)
        {
            videoClearViewableArea(blackcol);
            RefreshBackground();
            renderDrawMapView(x, y, lMapZoom, nAngle);
        }
        G_DrawOverheadMap(x, y, lMapZoom, nAngle);

        if (bFollowMode) {
            // TODO printext(320 - 120, 0, "Map Follow Mode", kTileFont);
        }
        else {
            // TODO printext(0, 60, "Map Scroll Mode", kTileFont);
        }
    }
}


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

#include "compat.h"

extern int bShowTowers;
extern int bFollowMode;
extern int ldMapZoom;
extern int lMapZoom;

extern int mapForward;
extern int mapStrafe;
extern fix16_t mapTurn;

extern int32_t nMapPic;

void InitMap();
void GrabMap();
void UpdateMap();
void DrawMap();
void SetMapPosition(int32_t _x, int32_t _y, int16_t _nAngle);

#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 "memorystream.h"

MemoryReadStream::MemoryReadStream(uint8_t *buffer, bsize_t size)
{
    _size  = size;
    _start = buffer;
    _end   = buffer + size;
    _currentOffset = _start;
}

MemoryReadStream::~MemoryReadStream()
{
}

void MemoryReadStream::SkipBytes(bsize_t nBytes)
{
    _currentOffset += nBytes;
}

bsize_t MemoryReadStream::GetBytesRead()
{
    return _currentOffset - _start;
}

bsize_t MemoryReadStream::GetBytesLeft()
{
    return _end - _currentOffset;
}

uint8_t MemoryReadStream::GetByte()
{
    uint8_t ret;
    memcpy(&ret, _currentOffset, sizeof(ret));
    _currentOffset += sizeof(ret);
    return ret;
}

uint16_t MemoryReadStream::GetUint16LE()
{
    uint16_t ret;
    memcpy(&ret, _currentOffset, sizeof(ret));
    _currentOffset += sizeof(ret);
    return B_LITTLE16(ret);
}

uint32_t MemoryReadStream::GetUint32LE()
{
    uint32_t ret;
    memcpy(&ret, _currentOffset, sizeof(ret));
    _currentOffset += sizeof(ret);
    return B_LITTLE32(ret);
}

uint64_t MemoryReadStream::GetUint64LE()
{
    uint64_t ret;
    memcpy(&ret, _currentOffset, sizeof(ret));
    _currentOffset += sizeof(ret);
    return B_LITTLE64(ret);
}

uint8_t MemoryReadStream::PeekByte()
{
    return *_currentOffset;
}

bsize_t MemoryReadStream::GetBytes(void *buffer, bsize_t nBytes)
{
    if (!buffer) {
        return 0;
    }

    bsize_t count = std::min((bsize_t)(_end - _currentOffset), nBytes);

    memcpy(buffer, _currentOffset, count);
    _currentOffset += count;
    return count;
}


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

#include "compat.h"

class MemoryReadStream
{
    public:
        MemoryReadStream(uint8_t *buffer, bsize_t size);
        ~MemoryReadStream();

        uint8_t  GetByte();
        uint16_t GetUint16LE();
        uint32_t GetUint32LE();
        uint64_t GetUint64LE();
        uint8_t  PeekByte();
        bsize_t GetBytesRead();
        bsize_t GetBytesLeft();
        bsize_t GetBytes(void *buffer, bsize_t nBytes);
        void    SkipBytes(bsize_t nBytes);

    private:
        bsize_t  _size;
        uint8_t *_start;
        uint8_t *_currentOffset;
        uint8_t *_end;
};

#endif