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

#define kMaxRex 50

struct Rex
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short nTarget;
    short field_A;
};

static actionSeq ActionSeq[] = {
    {29, 0},
    {0,  0},
    {0,  0},
    {37, 0},
    {9,  0},
    {18, 0},
    {27, 1},
    {28, 1}
};

short RexCount = 0;

short RexChan[kMaxRex];
Rex RexList[kMaxRex];

void InitRexs()
{
    RexCount = kMaxRex;
}

int BuildRex(short nSprite, int x, int y, int z, short nSector, short nAngle, int nChannel)
{
    RexCount--;

    int nRex = RexCount;
    if (nRex < 0) {
        return -1;
    }

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 119);
    }
    else
    {
        changespritestat(nSprite, 119);
        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 = 80;
    sprite[nSprite].shade = -12;
    sprite[nSprite].xrepeat = 64;
    sprite[nSprite].yrepeat = 64;
    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].extra = -1;
    sprite[nSprite].hitag = 0;

    GrabTimeSlot(3);

    RexList[nRex].nAction = 0;
    RexList[nRex].nHealth = 4000;
    RexList[nRex].nFrame  = 0;
    RexList[nRex].nSprite = nSprite;
    RexList[nRex].nTarget = -1;
    RexList[nRex].field_A = 0;

    RexChan[nRex] = nChannel;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nRex, kRunRex);

    // this isn't stored anywhere.
    runlist_AddRunRec(NewRun, nRex, kRunRex);

    nCreaturesLeft++;

    return nRex;
}

void FuncRex(int a, int nDamage, int nRun)
{
    short nRex = RunData[nRun].nVal;
    assert(nRex >= 0 && nRex < kMaxRex);

    short nAction = RexList[nRex].nAction;
    short nSprite = RexList[nRex].nSprite;

    bool bVal = false;

    int nMessage = a & kMessageMask;

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

        case 0xA0000:
        {
            if (nAction == 5)
            {
                nDamage = runlist_CheckRadialDamage(nSprite);
            }
            // fall through to case 0x80000
            fallthrough__;
        }

        case 0x80000:
        {
            if (nDamage)
            {
                short nTarget = a & 0xFFFF;
                if (nTarget >= 0 && sprite[nTarget].statnum == 100)
                {
                    RexList[nRex].nTarget = nTarget;
                }

                if (RexList[nRex].nAction == 5 && RexList[nRex].nHealth > 0)
                {
                    RexList[nRex].nHealth -= nDamage;

                    if (RexList[nRex].nHealth <= 0)
                    {
                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                        sprite[nSprite].zvel = 0;
                        sprite[nSprite].cstat &= 0xFEFE;

                        RexList[nRex].nHealth = 0;

                        nCreaturesLeft--;

                        if (nAction < 6)
                        {
                            RexList[nRex].nAction = 6;
                            RexList[nRex].nFrame  = 0;
                        }
                    }
                }
            }
            return;
        }

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

        case 0x20000:
        {
            Gravity(nSprite);

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

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, RexList[nRex].nFrame);

            int ecx = 2;

            if (nAction != 2) {
                ecx = 1;
            }

            // moves the mouth open and closed as it's idle?
            while (--ecx != -1)
            {
                seq_MoveSequence(nSprite, nSeq, RexList[nRex].nFrame);

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

            int nFlag = FrameFlag[SeqBase[nSeq] + RexList[nRex].nFrame];

            short nTarget = RexList[nRex].nTarget;

            switch (nAction)
            {
                default:
                    return;

                case 0:
                {
                    if (!RexList[nRex].field_A)
                    {
                        if ((nRex & 0x1F) == (totalmoves & 0x1F))
                        {
                            if (nTarget < 0)
                            {
                                short nAngle = sprite[nSprite].ang; // make backup of this variable
                                RexList[nRex].nTarget = FindPlayer(nSprite, 60);
                                sprite[nSprite].ang = nAngle;
                            }
                            else
                            {
                                RexList[nRex].field_A = 60;
                            }
                        }
                    }
                    else
                    {
                        RexList[nRex].field_A--;
                        if (RexList[nRex].field_A <= 0)
                        {
                            RexList[nRex].nAction = 1;
                            RexList[nRex].nFrame  = 0;

                            sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
                            sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;

                            D3PlayFX(StaticSound[kSoundRexICU], nSprite);

                            RexList[nRex].field_A = 30;
                        }
                    }

                    return;
                }

                case 1:
                {
                    if (RexList[nRex].field_A > 0)
                    {
                        RexList[nRex].field_A--;
                    }

                    if ((nRex & 0x0F) == (totalmoves & 0x0F))
                    {
                        if (!RandomSize(1))
                        {
                            RexList[nRex].nAction = 5;
                            RexList[nRex].nFrame  = 0;
                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                            return;
                        }
                        else
                        {
                            if (((PlotCourseToSprite(nSprite, nTarget) >> 8) >= 60) || RexList[nRex].field_A > 0)
                            {
                                int nAngle = sprite[nSprite].ang & 0xFFF8;
                                sprite[nSprite].xvel = Cos(nAngle) >> 2;
                                sprite[nSprite].yvel = Sin(nAngle) >> 2;
                            }
                            else
                            {
                                RexList[nRex].nAction = 2;
                                RexList[nRex].field_A = 240;
                                D3PlayFX(StaticSound[kSoundRexICU], nSprite);
                                RexList[nRex].nFrame = 0;
                                return;
                            }
                        }
                    }

                    int nMov = MoveCreatureWithCaution(nSprite);

                    switch ((nMov & 0xC000))
                    {
                        case 0xC000:
                        {
                            if ((nMov & 0x3FFF) == nTarget)
                            {
                                PlotCourseToSprite(nSprite, nTarget);
                                RexList[nRex].nAction = 4;
                                RexList[nRex].nFrame  = 0;
                                break;
                            }
                            fallthrough__;
                        }
                        case 0x8000:
                        {
                            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                            sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
                            sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;
                            RexList[nRex].nAction = 1;
                            RexList[nRex].nFrame  = 0;
                            nAction = 1;
                            break;
                        }
                    }

                    break;
                }

                case 2:
                {
                    RexList[nRex].field_A--;
                    if (RexList[nRex].field_A > 0)
                    {
                        PlotCourseToSprite(nSprite, nTarget);

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

                        int nMov = MoveCreatureWithCaution(nSprite);

                        switch (nMov & 0xC000)
                        {
                            case 0x8000:
                            {
                                SetQuake(nSprite, 25);
                                RexList[nRex].field_A = 60;

                                sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                                sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 2;
                                sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 2;
                                RexList[nRex].nAction = 1;
                                RexList[nRex].nFrame  = 0;
                                nAction = 1;
                                break;
                            }
                            case 0xC000:
                            {
                                RexList[nRex].nAction = 3;
                                RexList[nRex].nFrame  = 0;

                                short nSprite2 = nMov & 0x3FFF;

                                if (sprite[nSprite2].statnum && sprite[nSprite2].statnum < 107)
                                {
                                    short nAngle = sprite[nSprite].ang;

                                    runlist_DamageEnemy(nSprite2, nSprite, 15);

                                    int xVel = Cos(nAngle) * 15;
                                    int yVel = Sin(nAngle) * 15;

                                    if (sprite[nSprite2].statnum == 100)
                                    {
                                        short nPlayer = GetPlayerFromSprite(nSprite2);
                                        nXDamage[nPlayer] += (xVel << 4);
                                        nYDamage[nPlayer] += (yVel << 4);
                                        sprite[nSprite2].zvel = -3584;
                                    }
                                    else
                                    {
                                        sprite[nSprite2].xvel += (xVel >> 3);
                                        sprite[nSprite2].yvel += (yVel >> 3);
                                        sprite[nSprite2].zvel = -2880;
                                    }
                                }

                                RexList[nRex].field_A >>= 2;
                                return;
                            }
                        }
                    }
                    else
                    {
                        RexList[nRex].nAction = 1;
                        RexList[nRex].nFrame  = 0;
                        RexList[nRex].field_A = 90;
                    }

                    return;
                }

                case 3:
                {
                    if (bVal)
                    {
                        RexList[nRex].nAction = 2;
                    }
                    return;
                }

                case 4:
                {
                    if (nTarget != -1)
                    {
                        if (PlotCourseToSprite(nSprite, nTarget) < 768)
                        {
                            if (nFlag & 0x80)
                            {
                                runlist_DamageEnemy(nTarget, nSprite, 15);
                            }

                            break;
                        }
                    }

                    RexList[nRex].nAction = 1;
                    break;
                }

                case 5:
                {
                    if (bVal)
                    {
                        RexList[nRex].nAction = 1;
                        RexList[nRex].field_A = 15;
                    }
                    return;
                }

                case 6:
                {
                    if (bVal)
                    {
                        RexList[nRex].nAction = 7;
                        RexList[nRex].nFrame  = 0;
                        runlist_ChangeChannel(RexChan[nRex], 1);
                    }
                    return;
                }

                case 7:
                {
                    sprite[nSprite].cstat &= 0xFEFE;
                    return;
                }
            }

            // break-ed
            if (nAction > 0)
            {
                if ((nTarget != -1) && (!(sprite[nTarget].cstat & 0x101)))
                {
                    RexList[nRex].nAction = 0;
                    RexList[nRex].nFrame  = 0;
                    RexList[nRex].field_A = 0;
                    RexList[nRex].nTarget = -1;
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                }
            }
            return;
        }
    }
}

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

void RexLoadSave::Load()
{
    Read(&RexCount, sizeof(RexCount));
    Read(RexChan, sizeof(RexChan));
    Read(&RexList[RexCount], sizeof(Rex) * (kMaxRex - RexCount));
}

void RexLoadSave::Save()
{
    Write(&RexCount, sizeof(RexCount));
    Write(RexChan, sizeof(RexChan));
    Write(&RexList[RexCount], sizeof(Rex) * (kMaxRex - RexCount));
}

static RexLoadSave* myLoadSave;

void RexLoadSaveConstruct()
{
    myLoadSave = new RexLoadSave();
}


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

void InitRexs();
int BuildRex(short nSprite, int x, int y, int z, short nSector, short nAngle, int nChannel);
void FuncRex(int, int, int);

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

#define kMaxRoach   100

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

struct Roach
{
    short nHealth;
    short field_2;
    short nAction;
    short nSprite;
    short nTarget;
    short field_A;
    short field_C;
    short field_E;
};

int16_t RoachCount = -1;

Roach RoachList[kMaxRoach];

/* Kilmaat Sentry */

void InitRoachs()
{
    RoachCount = kMaxRoach;
}

// TODO - make nType a bool?
int BuildRoach(int nType, int nSprite, int x, int y, int z, short nSector, int angle)
{
    RoachCount--;

    short nRoach = RoachCount;
    if (nRoach < 0) {
        return -1;
    }

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 105);
    }
    else
    {
        changespritestat(nSprite, 105);
        x = sprite[nSprite].x;
        y = sprite[nSprite].y;
        z = sector[sprite[nSprite].sectnum].floorz;
        angle = 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].shade = -12;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
    sprite[nSprite].clipdist = 60;
    sprite[nSprite].ang = angle;
    sprite[nSprite].xrepeat = 40;
    sprite[nSprite].yrepeat = 40;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].extra = -1;

    //	GrabTimeSlot(3);

    if (nType)
    {
        RoachList[nRoach].nAction = 0;
    }
    else
    {
        RoachList[nRoach].nAction = 1;
    }

    RoachList[nRoach].nSprite = nSprite;
    RoachList[nRoach].field_2 = 0;
    RoachList[nRoach].field_C = 0;
    RoachList[nRoach].nTarget = -1;
    RoachList[nRoach].nHealth = 600;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nRoach, kRunRoach);
    RoachList[nRoach].field_A = runlist_AddRunRec(NewRun, nRoach, kRunRoach);

    nCreaturesLeft++;

    return nRoach;
}

void GoRoach(short nSprite)
{
    sprite[nSprite].xvel = (Cos(sprite[nSprite].ang) >> 1) - (Cos(sprite[nSprite].ang) >> 3);
    sprite[nSprite].yvel = (Sin(sprite[nSprite].ang) >> 1) - (Sin(sprite[nSprite].ang) >> 3);
}

void FuncRoach(int a, int nDamage, int nRun)
{
    short nRoach = RunData[nRun].nVal;
    assert(nRoach >= 0 && nRoach < kMaxRoach);

    short nSprite = RoachList[nRoach].nSprite;
    short nAction = RoachList[nRoach].nAction;

    bool bVal = false;

    int nMessage = a & kMessageMask;

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

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, ActionSeq[nAction].a + SeqOffsets[kSeqRoach], RoachList[nRoach].field_2, ActionSeq[nAction].b);
            return;
        }

        case 0xA0000: // fall through to next case
        {
            nDamage = runlist_CheckRadialDamage(nSprite);
            fallthrough__;
        }
        case 0x80000:
        {
            if (nDamage)
            {
                if (RoachList[nRoach].nHealth <= 0) {
                    return;
                }

                RoachList[nRoach].nHealth -= nDamage;
                if (RoachList[nRoach].nHealth <= 0)
                {
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                    sprite[nSprite].zvel = 0;
                    sprite[nSprite].cstat &= 0xFEFE;

                    RoachList[nRoach].nHealth = 0;

                    if (nAction < 5)
                    {
                        DropMagic(nSprite);
                        RoachList[nRoach].nAction = 5;
                        RoachList[nRoach].field_2 = 0;
                    }

                    nCreaturesLeft--; // NOTE: This was incrementing in original code. Bug?
                }
                else
                {
                    short nSprite2 = a & 0xFFFF;
                    if (nSprite2 >= 0)
                    {
                        if (sprite[nSprite2].statnum < 199) {
                            RoachList[nRoach].nTarget = nSprite2;
                        }

                        if (nAction == 0)
                        {
                            RoachList[nRoach].nAction = 2;
                            GoRoach(nSprite);
                            RoachList[nRoach].field_2 = 0;
                        }
                        else
                        {
                            if (!RandomSize(4))
                            {
                                RoachList[nRoach].nAction = 4;
                                RoachList[nRoach].field_2 = 0;
                            }
                        }
                    }
                }
            }

            return;
        }

        case 0x20000:
        {
            Gravity(nSprite);

            int nSeq = SeqOffsets[kSeqRoach] + ActionSeq[RoachList[nRoach].nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, RoachList[nRoach].field_2);
            seq_MoveSequence(nSprite, nSeq, RoachList[nRoach].field_2);

            RoachList[nRoach].field_2++;
            if (RoachList[nRoach].field_2 >= SeqSize[nSeq])
            {
                bVal = true;
                RoachList[nRoach].field_2 = 0;
            }

            int nFlag = FrameFlag[SeqBase[nSeq] + RoachList[nRoach].field_2];
            short nTarget = RoachList[nRoach].nTarget;

            if (nAction > 5) {
                return;
            }

            switch (nAction)
            {
                case 0:
                {
                    if (RoachList[nRoach].field_2 == 1)
                    {
                        RoachList[nRoach].field_C--;
                        if (RoachList[nRoach].field_C <= 0)
                        {
                            RoachList[nRoach].field_C = RandomSize(6);
                        }
                        else
                        {
                            RoachList[nRoach].field_2 = 0;
                        }
                    }

                    if (((nRoach & 0xF) == (totalmoves & 0xF)) && nTarget < 0)
                    {
                        short nTarget = FindPlayer(nSprite, 50);
                        if (nTarget >= 0)
                        {
                            RoachList[nRoach].nAction = 2;
                            RoachList[nRoach].field_2 = 0;
                            RoachList[nRoach].nTarget = nTarget;
                            GoRoach(nSprite);
                        }
                    }

                    return;
                }

                case 1:
                {
                    // partly the same as case 0.
                    if (((nRoach & 0xF) == (totalmoves & 0xF)) && nTarget < 0)
                    {
                        short nTarget = FindPlayer(nSprite, 100);
                        if (nTarget >= 0)
                        {
                            RoachList[nRoach].nAction = 2;
                            RoachList[nRoach].field_2 = 0;
                            RoachList[nRoach].nTarget = nTarget;
                            GoRoach(nSprite);
                        }
                    }

                    return;
                }

                case 2:
                {
                    if ((totalmoves & 0xF) == (nRoach & 0xF))
                    {
                        PlotCourseToSprite(nSprite, nTarget);
                        GoRoach(nSprite);
                    }

                    int nMov = MoveCreatureWithCaution(nSprite);

                    if ((nMov & 0xC000) == 0xC000)
                    {
                        if ((nMov & 0x3FFF) == nTarget)
                        {
                            // repeated below
                            RoachList[nRoach].field_E = RandomSize(2) + 1;
                            RoachList[nRoach].nAction = 3;

                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                            sprite[nSprite].ang = GetMyAngle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);

                            RoachList[nRoach].field_2 = 0;
                        }
                        else
                        {
                            sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                            GoRoach(nSprite);
                        }
                    }
                    else if ((nMov & 0xC000) == 0x8000)
                    {
                        sprite[nSprite].ang = (sprite[nSprite].ang + 256) & kAngleMask;
                        GoRoach(nSprite);
                    }
                    else
                    {
                        if (RoachList[nRoach].field_C != 0)
                        {
                            RoachList[nRoach].field_C--;
                        }
                        else
                        {
                            // same as above
                            RoachList[nRoach].field_E = RandomSize(2) + 1;
                            RoachList[nRoach].nAction = 3;

                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                            sprite[nSprite].ang = GetMyAngle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);

                            RoachList[nRoach].field_2 = 0;
                        }
                    }

                    if (nTarget != -1 && !(sprite[nTarget].cstat & 0x101))
                    {
                        RoachList[nRoach].nAction = 1;
                        RoachList[nRoach].field_2 = 0;
                        RoachList[nRoach].field_C = 100;
                        RoachList[nRoach].nTarget = -1;
                        sprite[nSprite].xvel = 0;
                        sprite[nSprite].yvel = 0;
                    }

                    return;
                }

                case 3:
                {
                    if (bVal)
                    {
                        RoachList[nRoach].field_E--;
                        if (RoachList[nRoach].field_E <= 0)
                        {
                            RoachList[nRoach].nAction = 2;
                            GoRoach(nSprite);
                            RoachList[nRoach].field_2 = 0;
                            RoachList[nRoach].field_C = RandomSize(7);
                        }
                    }
                    else
                    {
                        if (nFlag & 0x80)
                        {
                            BuildBullet(nSprite, 13, 0, 0, -1, sprite[nSprite].ang, nTarget + 10000, 1);
                        }
                    }

                    return;
                }

                case 4:
                {
                    if (bVal)
                    {
                        RoachList[nRoach].nAction = 2;
                        RoachList[nRoach].field_2 = 0;
                    }

                    return;
                }

                case 5:
                {
                    if (bVal)
                    {
                        sprite[nSprite].cstat = 0;
                        RoachList[nRoach].nAction = 6;
                        RoachList[nRoach].field_2 = 0;
                    }

                    return;
                }
            }
        }
    }
}

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

void RoachLoadSave::Load()
{
    Read(&RoachCount, sizeof(RoachCount));
    Read(&RoachList[RoachCount], sizeof(Roach) * (kMaxRoach - RoachCount));
}

void RoachLoadSave::Save()
{
    Write(&RoachCount, sizeof(RoachCount));
    Write(&RoachList[RoachCount], sizeof(Roach) * (kMaxRoach - RoachCount));
}

static RoachLoadSave* myLoadSave;

void RoachLoadSaveConstruct()
{
    myLoadSave = new RoachLoadSave();
}


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

#include "aistuff.h"

void InitRoachs();
int BuildRoach(int nType, int nSprite, int x, int y, int z, short nSector, int angle);
void FuncRoach(int a, int nDamage, int nRun);

#endif


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

This file is part of PCExhumed.

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

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

See the GNU General Public License for more details.

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

#include "exhumed.h"
#include "engine.h"
#include "runlist.h"
#include "player.h"
#include "trigdat.h"
#include "move.h"
#include "random.h"
#include "mummy.h"
#include "fish.h"
#include "lion.h"
#include "rex.h"
#include "set.h"
#include "rat.h"
#include "wasp.h"
#include "anubis.h"
#include "snake.h"
#include "scorp.h"
#include "ra.h"
#include "spider.h"
#include "bullet.h"
#include "queen.h"
#include "roach.h"
#include "bubbles.h"
#include "lavadude.h"
#include "grenade.h"
#include "object.h"
#include "switch.h"
#include "anims.h"
#include "sound.h"
#include "init.h"
#include "lighting.h"
#include <assert.h>

#include "save.h"

//#define kFuncMax	0x260000 // the number 38 stored in the high word of an int
#define kFuncMax		39
#define kMaxRunStack	200

short RunCount = -1;
short nRadialSpr = -1;
short nStackCount = 0;
short word_966BE = 0;
short ChannelList = -1;
short ChannelLast = -1;

int nRadialOwner;
int nDamageRadius;
int nRadialDamage;
short RunChain;
short NewRun;

int sRunStack[kMaxRunStack];
short RunFree[kMaxRuns];
RunChannel sRunChannels[kMaxChannels];
RunStruct RunData[kMaxRuns];

AiFunc aiFunctions[kFuncMax] = {
    FuncElev,
    FuncSwReady,
    FuncSwPause,
    FuncSwStepOn,
    FuncSwNotOnPause,
    FuncSwPressSector,
    FuncSwPressWall,
    FuncWallFace,
    FuncSlide,
    FuncAnubis,
    FuncPlayer,
    FuncBullet,
    FuncSpider,
    FuncCreatureChunk,
    FuncMummy,
    FuncGrenade,
    FuncAnim,
    FuncSnake,
    FuncFish,
    FuncLion,
    FuncBubble,
    FuncLava,
    FuncLavaLimb,
    FuncObject,
    FuncRex,
    FuncSet,
    FuncQueen,
    FuncQueenHead,
    FuncRoach,
    FuncQueenEgg,
    FuncWasp,
    FuncTrap,
    FuncFishLimb,
    FuncRa,
    FuncScorp,
    FuncSoul,
    FuncRat,
    FuncEnergyBlock,
    FuncSpark,
};

int runlist_GrabRun()
{
    assert(RunCount > 0 && RunCount <= kMaxRuns);

    RunCount--;

    return RunFree[RunCount];
}

int runlist_FreeRun(int nRun)
{
    assert(RunCount >= 0 && RunCount < kMaxRuns);
    assert(nRun >= 0 && nRun < kMaxRuns);

    RunFree[RunCount] = nRun;
    RunCount++;

    RunData[nRun].nRef = -1;
    RunData[nRun].nVal = -1;
    RunData[nRun]._4 = -1;
    RunData[nRun]._6 = -1;

    return 1;
}

int runlist_HeadRun()
{
    int nRun = runlist_GrabRun();

    RunData[nRun]._4 = -1;
    RunData[nRun]._6 = -1;

    return nRun;
}

void runlist_InitRun()
{
    int i;

    RunCount = kMaxRuns;
    nStackCount = 0;

    for (i = 0; i < kMaxRuns; i++)
    {
        RunData[i].nRef = -1;
        RunData[i].nVal = -1;
        RunData[i]._4 = -1;
        RunData[i]._6 = -1;

        RunFree[i] = i;
    }

    int nRun = runlist_HeadRun();
    RunChain = nRun;
    NewRun = nRun;

    for (i = 0; i < kMaxPlayers; i++) {
        PlayerList[i].nRun = -1;
    }

    nRadialSpr = -1;
}

int runlist_UnlinkRun(int nRun)
{
    assert(nRun >= 0 && nRun < kMaxRuns);

    if (nRun == RunChain)
    {
        RunChain = RunData[nRun]._4;
        return nRun;
    }

    if (RunData[nRun]._6 >= 0)
    {
        RunData[RunData[nRun]._6]._4 = RunData[nRun]._4;
    }

    if (RunData[nRun]._4 >= 0)
    {
        RunData[RunData[nRun]._4]._6 = RunData[nRun]._6;
    }

    return nRun;
}

int runlist_InsertRun(int RunLst, int RunNum)
{
    assert(RunLst >= 0 && RunLst < kMaxRuns);
    assert(RunNum >= 0 && RunNum < kMaxRuns);

    RunData[RunNum]._6 = RunLst;
    int val = RunData[RunLst]._4;
    RunData[RunNum]._4 = val;

    if (val >= 0) {
        RunData[val]._6 = RunNum;
    }

    RunData[RunLst]._4 = RunNum;
    return RunNum;
}

int runlist_AddRunRec(int a, int16_t b, int16_t c)
{
    int nRun = runlist_GrabRun();

    RunData[nRun].nVal = b;
    RunData[nRun].nRef = c;

    runlist_InsertRun(a, nRun);
    return nRun;
}

void runlist_DoSubRunRec(int RunPtr)
{
    assert(RunPtr >= 0 && RunPtr < kMaxRuns);

    runlist_UnlinkRun(RunPtr);
    runlist_FreeRun(RunPtr);
}

void runlist_CleanRunRecs()
{
    int nextPtr = RunChain;

    if (nextPtr >= 0)
    {
        assert(nextPtr < kMaxRuns);
        nextPtr = RunData[nextPtr]._4;
    }

    while (nextPtr >= 0)
    {
        int runPtr = nextPtr;
        assert(runPtr < kMaxRuns);

        int val = RunData[runPtr].nRef; // >> 16;
        nextPtr = RunData[runPtr]._4;

        if (val < 0) {
            runlist_DoSubRunRec(runPtr);
        }
    }
}

void runlist_SubRunRec(int RunPtr)
{
    assert(RunPtr >= 0 && RunPtr < kMaxRuns);

    RunData[RunPtr].nRef = -1;
    RunData[RunPtr].nVal = -1;
}

void runlist_SendMessageToRunRec(int nRun, int nMessage, int nDamage)
{
    int nFunc = RunData[nRun].nRef;

    if (nFunc < 0) {
        return;
    }

    assert(nFunc >= 0 && nFunc <= kFuncMax);

    if (nFunc > kFuncMax) {
        return;
    }

    assert(nFunc < kFuncMax); // REMOVE

    // do function pointer call here.
    aiFunctions[nFunc](nMessage, nDamage, nRun);
}

void runlist_ExplodeSignalRun()
{
    short nextPtr = RunChain;

    if (nextPtr >= 0)
    {
        assert(nextPtr < kMaxRuns);
        nextPtr = RunData[nextPtr]._4;
    }

    while (nextPtr >= 0)
    {
        int runPtr = nextPtr;
        assert(runPtr < kMaxRuns);

        nextPtr = RunData[runPtr]._4;

        if ((RunData[runPtr].nRef >= 0) || (RunData[runPtr].nVal >= 0))
        {
            runlist_SendMessageToRunRec(runPtr, 0xA0000, 0);
        }
    }
}

void runlist_PushMoveRun(int eax)
{
    if (nStackCount < kMaxRunStack)
    {
        sRunStack[nStackCount] = eax;
        nStackCount++;
    }
}

int runlist_PopMoveRun()
{
    if (nStackCount <= 0) {
        bail2dos("PopMoveRun() called inappropriately\n");
        exit(-1);
    }

    nStackCount--;
    return sRunStack[nStackCount];
}

void runlist_SignalRun(int NxtPtr, int edx)
{
    if (NxtPtr == RunChain && word_966BE != 0) {
        runlist_PushMoveRun(edx);
        return;
    }

    while (1)
    {
        word_966BE = 1;

        if (NxtPtr >= 0)
        {
            assert(NxtPtr < kMaxRuns);
            NxtPtr = RunData[NxtPtr]._4;
        }

        while (NxtPtr >= 0)
        {
            int runPtr = NxtPtr;

            if (runPtr >= 0)
            {
                assert(runPtr < kMaxRuns);
                NxtPtr = RunData[runPtr]._4;

                if ((RunData[runPtr].nRef >= 0) || (RunData[runPtr].nVal >= 0))
                {
                    runlist_SendMessageToRunRec(runPtr, edx, 0);
                }
            }
        }

        word_966BE = 0;
        if (nStackCount == 0) {
            return;
        }

        edx = runlist_PopMoveRun();
        NxtPtr = RunChain;
    }
}

void runlist_InitChan()
{
    ChannelList = -1;
    ChannelLast = -1;

    for (int i = 0; i < kMaxChannels; i++)
    {
        sRunChannels[i].c = -1;
        sRunChannels[i].a = runlist_HeadRun();
        sRunChannels[i].b = -1;
        sRunChannels[i].d = 0;
    }
}

void runlist_ChangeChannel(int nChannel, short nVal)
{
    if (sRunChannels[nChannel].b < 0)
    {
        short tmpChannel = ChannelList;
        ChannelList = nChannel;
        sRunChannels[nChannel].b = tmpChannel;
    }

    sRunChannels[nChannel].c = nVal;
    sRunChannels[nChannel].d |= 2;
}

void runlist_ReadyChannel(int nChannel)
{
    if (sRunChannels[nChannel].b < 0)
    {
        short tmpChannel = ChannelList;
        ChannelList = nChannel;
        sRunChannels[nChannel].b = tmpChannel;
    }

    sRunChannels[nChannel].d |= 1;
}

void runlist_ProcessChannels()
{
    short v0;
    short v1;

    do
    {
        v0 = -1;
        v1 = -1;

        while (ChannelList >= 0)
        {
            short b = sRunChannels[ChannelList].b;
            short d = sRunChannels[ChannelList].d;

            if (d & 2)
            {
                sRunChannels[ChannelList].d ^= 2;
                runlist_SignalRun(sRunChannels[ChannelList].a, ChannelList | k0x10000);
            }

            if (d & 1)
            {
                sRunChannels[ChannelList].d ^= 1;
                runlist_SignalRun(sRunChannels[ChannelList].a, k0x30000);
            }

            if (sRunChannels[ChannelList].d)
            {
                if (v1 == -1)
                {
                    v1 = ChannelList;
                    v0 = ChannelList;
                }
                else
                {
                    int v5 = v0;
                    v0 = ChannelList;
                    sRunChannels[v5].b = ChannelList;
                }
            }
            else
            {
                sRunChannels[ChannelList].b = -1;
            }
            ChannelList = b;
        }
        ChannelList = v1;
    } while (v1 != -1);
}

int runlist_FindChannel(short ax)
{
    for (int i = 0; i < kMaxChannels; i++)
    {
        if (sRunChannels[i].c == -1)
        {
            sRunChannels[i].c = ax;
            return i;
        }
    }

    return -1;
}

int runlist_AllocChannel(int a)
{
    if (a)
    {
        for (int i = 0; i < kMaxChannels; i++)
        {
            if (sRunChannels[i].c == a) {
                return i;
            }
        }
    }

    return runlist_FindChannel(a);
}

void runlist_ExecObjects()
{
    runlist_ProcessChannels();
    runlist_SignalRun(RunChain, k0x20000);
}

void runlist_ProcessSectorTag(int nSector, int nLotag, int nHitag)
{
    int zListA[8];
    int zListB[8];

    int nChannel = runlist_AllocChannel(nHitag % 1000);
    assert(nChannel >= 0 && nChannel < kMaxChannels);

    int keyMask = (nHitag / 1000) << 12;
    int nSpeed = nLotag / 1000;

    if (!nSpeed) {
        nSpeed = 1;
    }

    nSpeed <<= 2;

    int nEffectTag = (nLotag % 1000);

    switch (nEffectTag)
    {
        case 1: // Ceiling Doom door
        {
            /*
                This function searches z-coordinates of neighboring sectors to find the
                closest (next) ceiling starting at the given z-coordinate (thez).
            */
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwPress = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwPress, kRunSwPressSector);

            int nSwPause = BuildSwPause(nChannel, BuildLink(2, -1, 0), 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwPause, kRunSwPause);
            return;
        }

        case 2: // Floor Doom door
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].ceilingz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwPress = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwPress, kRunSwPressSector);

            int nSwPause = BuildSwPause(nChannel, BuildLink(2, -1, 0), 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwPause, kRunSwPause);
            return;
        }

        case 3:
        case 4:
        case 19:
        case 20:
        case 22:
        case 29:
        case 30:
        case 46:
        case 47:
        case 60:
        case 65:
        case 66:
        case 67:
        case 69:
        case 72:
        case 73:
        case 74:
        case 76:
        case 77:
        case 78:
        case 79:
        case 81:
        case 82:
        case 83:
        case 84:
        case 85:
        {
            return;
        }

        case 5: // Permanent floor raise
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz + 1, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 6: // Touchplate floor lower, single
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 400, 400, 2, sector[nextSector].floorz, sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(2, 1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);

            sector[nSector].floorz = sector[nextSector].floorz;
            return;
        }

        case 7: // Touchplate floor lower, multiple
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);

            int nSwitch2 = BuildSwNotOnPause(nChannel, BuildLink(2, -1, 0), nSector, 8);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch2, kRunSwNotOnPause);
            return;
        }

        case 8: // Permanent floor lower
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 9: // Switch activated lift down
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 150);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 10: // Touchplate Floor Raise
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);

            int nSwitch2 = BuildSwNotOnPause(nChannel, BuildLink(2, -1, 0), nSector, 8);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch2, kRunSwNotOnPause);
            return;
        }

        case 11: // Permanent floor raise
        case 14: // Sector raise / lower
        case 38: // Sector raise / lower
        {
            /*
                fix for original behaviour - nextSector could be -1 the and game would do an invalid memory read
                when getting the floorz for nextSector. Here, we assume 0 and only set the correct value if nextSector
                is valid.
            */
            int zVal = 0;

            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, -1);
            if (nextSector >= 0) {
                zVal = sector[nextSector].floorz;
            }

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, zVal);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 12: // Switch activated lift up
        {
            /*
                fix for original behaviour - nextSector could be -1 the and game would do an invalid memory read
                when getting the floorz for nextSector. Here, we assume 0 and only set the correct value if nextSector
                is valid.
            */
            int zVal = 0;

            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, -1);
            if (nextSector >= 0) {
                zVal = sector[nextSector].floorz;
            }

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, zVal);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 150);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 13: // Bobbing floor
        {
            /*
                fix for original behaviour - nextSector could be -1 the and game would do an invalid memory read
                when getting the floorz for nextSector. Here, we assume 0 and only set the correct value if nextSector
                is valid.
            */
            int zVal = 0;

            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            if (nextSector >= 0) {
                zVal = sector[nextSector].floorz;
            }

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, zVal);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwReady(nChannel, BuildLink(2, 1, 0));

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwReady);
            return;
        }

        case 15: // Sector raise/lower
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 16: // Stuttering noise (floor makes noise)
        {
            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].ceilingz, sector[nSector].floorz - 8);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);

            int nSwitch2 = BuildSwReady(nChannel, BuildLink(2, -1, 0));

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch2, kRunSwReady);
            return;
        }

        case 17: // Reserved?
        {
            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].ceilingz, sector[nSector].floorz - 8);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwReady(nChannel, BuildLink(2, -1, 0));

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwReady);
            return;
        }

        case 18: // Raises floor AND lowers ceiling
        {
            int ebx = ((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz;

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].floorz, ebx);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int ebx2 = (((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz) - 8;

            int nElev2 = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].ceilingz, ebx2);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev2, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);
            return;
        }

        case 21: // Touchplate
        {
            int nSwitch = BuildSwStepOn(nChannel, BuildLink(2, 1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);
            return;
        }

        case 23: // Floor raise, Sychronize
        {
            /*
                fix for original behaviour - nextSector could be -1 the and game would do an invalid memory read
                when getting the floorz for nextSector. Here, we assume 0 and only set the correct value if nextSector
                is valid.
            */
            int zVal = 0;

            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            if (nextSector >= 0) {
                zVal = sector[nextSector].floorz;
            }

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 32767, 200, 2, sector[nSector].floorz, zVal);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), nSpeed * 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 24: // Ceiling door, channel trigger only
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 25:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 300);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 26:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 450);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 27:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 600);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 28:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 900);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 31: // Touchplate
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 0x7FFF, 0x7FFF, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);
            return;
        }

        case 32:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 0x7FFF, 0x7FFF, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 33: // Ceiling Crusher
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(20, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nextSector].ceilingz, sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 34: // Triggerable Ceiling Crusher(Inactive)
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(28, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nextSector].ceilingz, sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 35: // Destructible sector
        case 36:
        {
            nEnergyTowers++;

            int nEnergyBlock = BuildEnergyBlock(nSector);

            if (nLotag == 36) {
                nFinaleSpr = nEnergyBlock;
            }

            return;
        }

        case 37:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 39: // Touchplate
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 0x7FFF, 0x7FFF, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);

            int nSwitch2 = BuildSwNotOnPause(nChannel, BuildLink(2, -1, 0), nSector, 8);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch2, kRunSwNotOnPause);
            return;
        }

        case 40: // Moving sector(follows waypoints)
        {
            AddMovingSector(nSector, nLotag, nHitag % 1000, 2);
            return;
        }

        case 41: // Moving sector(follows waypoints)
        {
            AddMovingSector(nSector, nLotag, nHitag % 1000, 18);
            return;
        }

        case 42: // Moving sector(follows waypoints)
        {
            AddMovingSector(nSector, nLotag, nHitag % 1000, 58);
            return;
        }

        case 43: // Moving sector(follows waypoints)
        {
            AddMovingSector(nSector, nLotag, nHitag % 1000, 122);
            return;
        }

        case 44: // Moving sector(follows waypoints)
        {
            AddMovingSector(nSector, nLotag, nHitag % 1000, 90);
            return;
        }

        case 45: // Pushbox sector
        {
            CreatePushBlock(nSector);
            return;
        }

        case 48: // Ceiling lower
        {
            /*
                fix for original behaviour - nextSector could be -1 the and game would do an invalid memory read
                when getting the floorz for nextSector. Here, we assume 0 and only set the correct value if nextSector
                is valid.
            */
            int zVal = 0;

            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, 1);
            if (nextSector >= 0) {
                zVal = sector[nextSector].ceilingz;
            }

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].ceilingz, zVal);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 49:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].ceilingz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 50: // Floor lower / raise
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 0x7FFF, 200, 2, sector[nextSector].floorz, sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 51:
        {
            int edx = ((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz;

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].floorz, edx);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int eax = (((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz) - 8;

            nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), 200, nSpeed * 100, 2, sector[nSector].ceilingz, eax);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwReady(nChannel, BuildLink(2, 1, 0));

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwReady);
            return;
        }

        case 52:
        {
            int eax = ((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz;

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, eax, sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            eax = ((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz;

            nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, eax, sector[nSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressSector);

            nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 53:
        {
            int eax = ((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz;

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, eax, sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            eax = ((sector[nSector].floorz - sector[nSector].ceilingz) / 2) + sector[nSector].ceilingz;

            nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, eax, sector[nSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPause(nChannel, BuildLink(2, -1, 0), 150);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPause);
            return;
        }

        case 54:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressSector);
            return;
        }

        case 55:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressSector);
            return;
        }

        case 56:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 57:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].ceilingz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 58:
        {
            int nSwitch = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressSector);

            // Fall through to case 62
            fallthrough__;
        }
        case 63: // Ceiling door, kill trigger (Enemy death triggers door)
        {
            if (nLotag == 63) {
                nEnergyChan = nChannel;
            }

            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 59:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwStepOn(nChannel, BuildLink(1, 1), nSector);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);

            int nSwitch2 = BuildSwNotOnPause(nChannel, BuildLink(1, 1), nSector, 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch2, kRunSwNotOnPause);
            return;
        }

        case 61:
        {
            zListB[0] = sector[nSector].floorz;
            int var_1C = 1;

            while (1)
            {
                short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, -1);
                if (nextSector < 0 || var_1C >= 8) {
                    break;
                }

                zListB[var_1C] = sector[nextSector].floorz;

                var_1C++;
            }

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, var_1C,
                zListB[0], zListB[1], zListB[2], zListB[3], zListB[4], zListB[5], zListB[6], zListB[7]);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 62:
        {
            zListA[0] = sector[nSector].floorz;
            int var_20 = 1;

            while (1)
            {
                short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
                if (nextSector < 0 || var_20 >= 8) {
                    break;
                }

                zListA[var_20] = sector[nextSector].floorz;

                var_20++;
            }

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, var_20,
                zListA[0], zListA[1], zListA[2], zListA[3], zListA[4], zListA[5], zListA[6], zListA[7]);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 64:
        {
            int nSwitch = BuildSwStepOn(nChannel, BuildLink(2, 0, 0), nSector);
            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwStepOn);
            return;
        }

        case 68:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].floorz, 1, 1);
            assert(nextSector > -1);

            int nElev = BuildElevF(nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, sector[nSector].floorz, sector[nextSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 70:
        case 71:
        {
            short nextSector = nextsectorneighborz(nSector, sector[nSector].ceilingz, -1, -1);
            assert(nextSector > -1);

            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, (int)sector[nSector].floorz, (int)sector[nextSector].ceilingz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);

            int nSwitch = BuildSwPressSector(nChannel, BuildLink(1, 1), nSector, keyMask);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressSector);

            int nSwitch2 = BuildSwPause(nChannel, BuildLink(2, -1, 0), 60);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch2, kRunSwPause);
            return;
        }

        case 75:
        {
            int nElev = BuildElevC(0, nChannel, nSector, FindWallSprites(nSector), nSpeed * 100, nSpeed * 100, 2, (int)sector[nSector].ceilingz, (int)sector[nSector].floorz);

            runlist_AddRunRec(sRunChannels[nChannel].a, nElev, kRunElev);
            return;
        }

        case 80:
        {
            SectFlag[nSector] |= 0x8000;
            return;
        }
    }
}

void runlist_ProcessWallTag(int nWall, short nLotag, short nHitag)
{
    int nChannel = runlist_AllocChannel(nHitag % 1000);
    assert(nChannel >= 0 && nChannel < kMaxChannels);

    int nPanSpeed = nLotag / 1000;
    if (!nPanSpeed) {
        nPanSpeed = 1;
    }

    nPanSpeed <<= 2;

    int nEffectTag = nLotag % 1000;

    switch (nEffectTag)
    {
        default:
            return;

        case 1:
        {
            int nWallFace = BuildWallFace(nChannel, nWall, 2, wall[nWall].picnum, wall[nWall].picnum + 1);
            runlist_AddRunRec(sRunChannels[nChannel].a, nWallFace, kRunWallFace);

            int nSwitch = BuildSwPressWall(nChannel, BuildLink(2, nEffectTag, 0), nWall);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressWall);
            return;
        }

        case 6:
        {
            int nSwitch = BuildSwPressWall(nChannel, BuildLink(2, 1, 0), nWall);
            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressWall);
            return;
        }

        case 7: // Regular switch
        {
            int nWallFace = BuildWallFace(nChannel, nWall, 2, wall[nWall].picnum, wall[nWall].picnum + 1);
            runlist_AddRunRec(sRunChannels[nChannel].a, nWallFace, kRunWallFace);

            int nSwitch = BuildSwPressWall(nChannel, BuildLink(1, 1), nWall);
            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressWall);
            return;
        }

        case 8: // Reverse switch
        {
            int nWallFace = BuildWallFace(nChannel, nWall, 2, wall[nWall].picnum, wall[nWall].picnum + 1);
            runlist_AddRunRec(sRunChannels[nChannel].a, nWallFace, kRunWallFace);

            int nSwitch = BuildSwPressWall(nChannel, BuildLink(2, -1, 0), nWall);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressWall);
            return;
        }

        case 9: // Invisible switch
        {
            int nSwitch = BuildSwPressWall(nChannel, BuildLink(2, 1, 1), nWall);
            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressWall);
            return;
        }

        case 10:
        {
            int nSwitch = BuildSwPressWall(nChannel, BuildLink(2, -1, 0), nWall);
            runlist_AddRunRec(sRunChannels[nChannel].a, nSwitch, kRunSwPressWall);
            return;
        }

        case 12: // Twin star trek door
        case 14:
        case 16:
        case 19:
        case 20:
        {
            int nLastWall = 0;
            int n2ndLastWall = 0;

            short nStart = nWall;

            while (1)
            {
                nWall = wall[nWall].point2; // get the next (right side) wall point

                if (nStart == nWall) { // we've looped back around
                    break;
                }

                n2ndLastWall = nLastWall;
                nLastWall = nWall;
            }

            short nWall2 = wall[nStart].point2;
            short nWall3 = wall[nWall2].point2;
            short nWall4 = wall[nWall3].point2;

            int nSlide = BuildSlide(nChannel, nStart, nLastWall, n2ndLastWall, nWall2, nWall3, nWall4);

            runlist_AddRunRec(sRunChannels[nChannel].a, nSlide, kRunSlide);
            return;
        }

        case 24: // Waterfall
        {
            AddFlow(nWall, nPanSpeed, 3);
            return;
        }

        case 25: // Inverse waterfall
        {
            AddFlow(nWall, nPanSpeed, 2);
            return;
        }
    }
}

int runlist_CheckRadialDamage(short nSprite)
{
    if (nSprite == nRadialSpr) {
        return 0;
    }

    if (!(sprite[nSprite].cstat & 0x101)) {
        return 0;
    }

    if (sprite[nSprite].statnum >= kMaxStatus || sprite[nRadialSpr].statnum >= kMaxStatus) {
        return 0;
    }

    if (sprite[nSprite].statnum != 100 && nSprite == nRadialOwner) {
        return 0;
    }

    int x = (sprite[nSprite].x - sprite[nRadialSpr].x) >> 8;
    int y = (sprite[nSprite].y - sprite[nRadialSpr].y) >> 8;
    int z = (sprite[nSprite].z - sprite[nRadialSpr].z) >> 12;

    if (klabs(x) > nDamageRadius) {
        return 0;
    }

    if (klabs(y) > nDamageRadius) {
        return 0;
    }

    if (klabs(z) > nDamageRadius) {
        return 0;
    }

    int edi = 0;

    uint32_t xDiff = klabs(x);
    uint32_t yDiff = klabs(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 nDist = ksqrt(sqrtNum);

    if (nDist < nDamageRadius)
    {
        uint16_t nCStat = sprite[nSprite].cstat;
        sprite[nSprite].cstat = 0x101;

        if (((kStatExplodeTarget - sprite[nSprite].statnum) <= 1) ||
            cansee(sprite[nRadialSpr].x,
                sprite[nRadialSpr].y,
                sprite[nRadialSpr].z - 512,
                sprite[nRadialSpr].sectnum,
                sprite[nSprite].x,
                sprite[nSprite].y,
                sprite[nSprite].z - 8192,
                sprite[nSprite].sectnum))
        {
            edi = (nRadialDamage * (nDamageRadius - nDist)) / nDamageRadius;

            if (edi < 0) {
                edi = 0;
            }
            else if (edi > 20)
            {
                int nAngle = GetMyAngle(x, y);

                sprite[nSprite].xvel += (edi * Cos(nAngle)) >> 3;
                sprite[nSprite].yvel += (edi * Sin(nAngle)) >> 3;
                sprite[nSprite].zvel -= edi * 24;

                if (sprite[nSprite].zvel < -3584) {
                    sprite[nSprite].zvel = -3584;
                }
            }
        }

        sprite[nSprite].cstat = nCStat;
    }

    if (edi > 0x7FFF) {
        edi = 0x7FFF;
    }

    return edi;
}

void runlist_RadialDamageEnemy(short nSprite, short nDamage, short nRadius)
{
    if (!nRadius) {
        return;
    }

    if (nRadialSpr == -1)
    {
        nRadialDamage = nDamage * 4;
        nDamageRadius = nRadius;
        nRadialSpr = nSprite;
        nRadialOwner = sprite[nSprite].owner;

        runlist_ExplodeSignalRun();

        nRadialSpr = -1;
    }
}

void runlist_DamageEnemy(int nSprite, int nSprite2, short nDamage)
{
    if (sprite[nSprite].statnum >= kMaxStatus) {
        return;
    }

    short nRun = sprite[nSprite].owner;
    if (nRun <= -1) {
        return;
    }

    short nPreCreaturesLeft = nCreaturesLeft;

    runlist_SendMessageToRunRec(nRun, (nSprite2 & 0xFFFF) | 0x80000, nDamage * 4);

    // is there now one less creature? (has one died)
    if (nPreCreaturesLeft > nCreaturesLeft && nSprite2 > -1)
    {
        if (sprite[nSprite2].statnum != 100) {
            return;
        }

        short nPlayer = GetPlayerFromSprite(nSprite2);
        nTauntTimer[nPlayer]--;

        if (nTauntTimer[nPlayer] <= 0)
        {
            // Do a taunt
            int nPlayerSprite = PlayerList[nPlayer].nSprite;
            int nSector = sprite[nPlayerSprite].sectnum;

            if (!(SectFlag[nSector] & kSectUnderwater))
            {
                int ebx = 0x4000;

                if (nPlayer == nLocalPlayer) {
                    ebx = 0x6000;
                }

                int nDopSprite = nDoppleSprite[nPlayer];
                D3PlayFX(StaticSound[kSoundTauntStart + (RandomSize(3) % 5)], nDopSprite | ebx);
            }

            nTauntTimer[nPlayer] = RandomSize(3) + 3;
        }
    }
}

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

void RunListLoadSave::Load()
{
    Read(&RunCount, sizeof(RunCount));
    Read(&nRadialSpr, sizeof(nRadialSpr));
    Read(&nStackCount, sizeof(nStackCount));
    Read(&word_966BE, sizeof(word_966BE));
    Read(&ChannelList, sizeof(ChannelList));
    Read(&ChannelLast, sizeof(ChannelLast));
    Read(&nRadialOwner, sizeof(nRadialOwner));
    Read(&nDamageRadius, sizeof(nDamageRadius));
    Read(&nRadialDamage, sizeof(nRadialDamage));
    Read(&RunChain, sizeof(RunChain));
    Read(&NewRun, sizeof(NewRun));

    Read(sRunStack, sizeof(sRunStack[0]) * nStackCount);
    Read(RunFree, sizeof(RunFree));
    Read(sRunChannels, sizeof(sRunChannels));
    Read(RunData, sizeof(RunData));
}

void RunListLoadSave::Save()
{
    Write(&RunCount, sizeof(RunCount));
    Write(&nRadialSpr, sizeof(nRadialSpr));
    Write(&nStackCount, sizeof(nStackCount));
    Write(&word_966BE, sizeof(word_966BE));
    Write(&ChannelList, sizeof(ChannelList));
    Write(&ChannelLast, sizeof(ChannelLast));
    Write(&nRadialOwner, sizeof(nRadialOwner));
    Write(&nDamageRadius, sizeof(nDamageRadius));
    Write(&nRadialDamage, sizeof(nRadialDamage));
    Write(&RunChain, sizeof(RunChain));
    Write(&NewRun, sizeof(NewRun));

    Write(sRunStack, sizeof(sRunStack[0]) * nStackCount);
    Write(RunFree, sizeof(RunFree));
    Write(sRunChannels, sizeof(sRunChannels));
    Write(RunData, sizeof(RunData));
}

static RunListLoadSave* myLoadSave;

void RunListLoadSaveConstruct()
{
    myLoadSave = new RunListLoadSave();
}


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

#include "compat.h"

#define kMaxRuns		25600
#define kMaxChannels	4096
#define kMessageMask    0x7F0000

enum messageTypes
{
    k0x10000 = 0x10000,
    k0x20000 = 0x20000,
    k0x30000 = 0x30000,
    k0x80000 = 0x80000,
    k0x90000 = 0x90000,
    k0xA0000 = 0xA0000,
};

enum runTypes
{
    kRunElev = 0,
    kRunSwReady,
    kRunSwPause,
    kRunSwStepOn,
    kRunSwNotOnPause,
    kRunSwPressSector,
    kRunSwPressWall,
    kRunWallFace,
    kRunSlide,
    kRunAnubis,
    kRunPlayer,
    kRunBullet,
    kRunSpider,
    kRunCreatureChunk,
    kRunMummy,
    kRunGrenade,
    kRunAnim,
    kRunSnake,
    kRunFish,
    kRunLion,
    kRunBubble,
    kRunLavaDude,
    kRunLavaLimb,
    kRunObject,
    kRunRex,
    kRunSet,
    kRunQueen,
    kRunQueenHead, // also Tail part?
    kRunRoach,
    kRunQueenEgg,
    kRunWasp,
    kRunTrap,
    kRunFishLimb,
    kRunRa,
    kRunScorp,
    kRunSoul,
    kRunRat,
    kRunEnergyBlock,
    kRunSpark,
};

struct RunStruct
{
    int16_t nRef;
    int16_t nVal;
    int16_t _4;
    int16_t _6;
};

struct RunChannel
{
    short a;
    short b;
    short c;
    short d;
};

typedef void(*AiFunc)(int, int, int nRun);

extern RunStruct RunData[kMaxRuns];
extern RunChannel sRunChannels[kMaxChannels];
extern short NewRun;
extern int nRadialOwner;
extern short nRadialSpr;

void runlist_InitRun();

int runlist_GrabRun();
int runlist_FreeRun(int nRun);
int runlist_AddRunRec(int a, int16_t b, int16_t c);
int runlist_HeadRun();
void runlist_InitChan();
void runlist_ChangeChannel(int nChannel, short nVal);
void runlist_ReadyChannel(int nChannel);
void runlist_ProcessSectorTag(int nSector, int nLotag, int nHitag);
int runlist_AllocChannel(int a);
void runlist_DoSubRunRec(int RunPtr);
void runlist_SubRunRec(int RunPtr);
void runlist_ProcessWallTag(int nWall, short nLotag, short nHitag);
int runlist_CheckRadialDamage(short nSprite);
void runlist_RadialDamageEnemy(short nSprite, short nDamage, short nRadius);
void runlist_DamageEnemy(int nSprite, int nSprite2, short nDamage);
void runlist_SignalRun(int NxtPtr, int edx);

void runlist_CleanRunRecs();
void runlist_ExecObjects();

#endif