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

// this is net.c in the original code

#include "typedefs.h"
#include "network.h"
#include "serial.h"
#include "input.h"

short nNetMoveFrames = 0;

void SendGoodbye()
{
    bSendBye = kTrue;
    UpdateInputs();
}

void UpdateNetInputs()
{

}
/*
int InitNet(short nSocket, int nPlayers)
{
    return 0;
}
*/

int InitSerial()
{
    return 1;
}

void AbortNetworkPlay()
{

}

void UnInitNet()
{

}

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

#ifndef __network_h__
#define __network_h__

extern short nNetMoveFrames;

void SendGoodbye();
void UpdateNetInputs();
int InitNet(short nSocket, int nPlayers);
int InitSerial();
void AbortNetworkPlay();
void UnInitNet();

#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 "engine.h"
#include "object.h"
#include "exhumed.h"
#include "move.h"
#include "random.h"
#include "view.h"
#include "sound.h"
#include "init.h"
#include "runlist.h"
#include "names.h"
#include "sequence.h"
#include "light.h"
#include "lighting.h"
#include "anims.h"
#include "items.h"
#include "player.h"
#include "trigdat.h"
#include "bullet.h"
#include "save.h"
#include "status.h"
#include <string.h>
#include <assert.h>

#define kMaxBobs		200
#define kMaxDrips		50
#define kMaxMoveSects	50
#define kMaxObjects		128
#define kMaxWallFace	4096
#define kMaxSlideData	128
#define kMaxPoints		1024
#define kMaxTraps		40
#define kMaxTrails		20
#define kMaxTrailPoints	100
#define kMaxWallFacePics   8

static short ObjectSeq[] = {
    46, -1, 72, -1
};

static short ObjectStatnum[] = {
    kStatExplodeTrigger, kStatExplodeTarget, 98, kStatDestructibleSprite
};

struct Trail
{
    short field_0;
    short field_2;
    short field_4;
//  short pad;
};

struct TrailPoint
{
    int x;
    int y;
};

struct Bob
{
    short nSector;
    char field_2;
    char field_3;
    int z;
};

struct Drip
{
    short nSprite;
    short field_2;
};

// 56 bytes
struct Elev
{
    short field_0;
    short nChannel;
    short nSector;
    int field_6;
    int field_A;
    short nCountZOffsets; // count of items in zOffsets
    short nCurZOffset;
    int zOffsets[8]; // different Z offsets
    short field_32;
    short nSprite;
    short field_36;
};

// 16 bytes
struct MoveSect
{
    short nSector;
    short nTrail;
    short nTrailPoint;
    short field_6;
    short field_8; // nSector?
    int field_10;
    short field_14; // nChannel?
};

struct Object
{
    short field_0;
    short nHealth;
    short field_4;
    short nSprite;
    short field_8;
    short field_10;
    short field_12;
};

struct wallFace
{
    short nChannel;
    short nWall;
    short nPicCount;
    short picList[kMaxWallFacePics];
};

// TODO - rename
struct slideData2
{
    short nChannel;
    short field_2;
    short field_4;
    short field_6;
    short field_8;
//  uint8_t field_A[6]; // pad?
};

struct slideData
{
    int field_0;
    int field_4;
    int field_8;
    int field_C;
    int x1;
    int y1;
    int x2;
    int y2;
    int field_20;
    int field_24;
    int field_28;
    int field_2C;
    int field_30;
    int field_34;
    int field_38;
    int field_3C;
};

struct Point
{
    short field_0;
    short field_2;
    short field_4;
    short field_6;
    short field_8;
    short field_A;
    short field_C;
    short field_E;
};

struct Trap
{
    short field_0;
    short nSprite;
    short nType;
    short field_6;
    short field_8;
    short field_A;
    short field_C;
    short field_E;
};

Trap sTrap[kMaxTraps];
Bob sBob[kMaxBobs];
Trail sTrail[kMaxTrails];
TrailPoint sTrailPoint[kMaxTrailPoints];
Elev Elevator[kMaxElevs];
Object ObjectList[kMaxObjects];
MoveSect sMoveSect[kMaxMoveSects];
slideData SlideData[kMaxSlideData];
slideData2 SlideData2[kMaxSlideData];
short sMoveDir[kMaxMoveSects];
wallFace WallFace[kMaxWallFace];

Point PointList[kMaxPoints];

short nTrapInterval[kMaxTraps];
short sBobID[kMaxBobs];

short PointCount;
short PointFree[kMaxPoints];

short SlideCount = 0;
short SlideFree[kMaxSlides];

char nTrailPointVal[kMaxTrailPoints];
short nTrailPointPrev[kMaxTrailPoints];
short nTrailPointNext[kMaxTrailPoints];

Drip sDrip[kMaxDrips];

short ElevCount = -1;
short WallFaceCount = -1;

int lFinaleStart;

short nTrailPoints;
short nEnergyBlocks;
short nMoveSects;
short nFinaleStage;
short nTrails;
short nTraps;
short nFinaleSpr;
short ObjectCount;
short nDrips;

short nBobs = 0;
short nDronePitch = 0;
short nSmokeSparks = 0;

void InitObjects()
{
    ObjectCount = 0;
    nTraps = 0;
    nDrips = 0;
    nBobs = 0;
    nTrails = 0;
    nTrailPoints = 0;
    nMoveSects = 0;

    memset(sTrail, -1, sizeof(sTrail));

    nEnergyBlocks = 0;
    nDronePitch = 0;
    nFinaleStage = 0;
    lFinaleStart = 0;
    nSmokeSparks = 0;
}

void InitElev()
{
    ElevCount = 0;
}

int BuildWallSprite(int nSector)
{
    int nWall = sector[nSector].wallptr;

    int x = wall[nWall].x;
    int y = wall[nWall].y;

    int x2 = wall[nWall + 1].x;
    int y2 = wall[nWall + 1].y;

    int nSprite = insertsprite(nSector, 401);

    sprite[nSprite].x = (x + x2) / 2;
    sprite[nSprite].y = (y + y2) / 2;
    sprite[nSprite].z = (sector[nSector].floorz + sector[nSector].ceilingz) / 2;
    sprite[nSprite].cstat = 0x8000;

    return nSprite;
}

short FindWallSprites(short nSector)
{
    int var_24 = 0x7FFFFFFF;
    int ecx    = 0x7FFFFFFF;

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

    int esi = 0x80000002;
    int edi = 0x80000002;

    int i;

    for (i = 0; i < nWallCount; i++)
    {
        if (wall[nWall].x < var_24) {
            var_24 = wall[nWall].x;
        }

        if (esi < wall[nWall].x) {
            esi = wall[nWall].x;
        }

        if (ecx > wall[nWall].y) {
            ecx = wall[nWall].y;
        }

        if (edi < wall[nWall].y) {
            edi = wall[nWall].y;
        }

        nWall++;
    }

    ecx -= 5;
    esi += 5;
    edi += 5;
    var_24 -= 5;

    int nSprite = -1;

    for (i = 0; i < kMaxSprites; i++)
    {
        if (sprite[i].lotag == 0)
        {
            if ((sprite[i].cstat & 0x50) == 80)
            {
                int var_28 = sprite[i].x;
                int ebx = sprite[i].y;

                if ((var_28 >= var_24) && (esi >= var_28) && (ebx >= ecx) && (ebx <= edi))
                {
                    sprite[i].owner = nSprite;
                    nSprite = i;
                }
            }
        }
    }

    if (nSprite < 0)
    {
        nSprite = insertsprite(nSector, 401);

        sprite[nSprite].x = (var_24 + esi) / 2;
        sprite[nSprite].y = (ecx + edi) / 2;
        sprite[nSprite].z = sector[nSector].floorz;
        sprite[nSprite].cstat = 0x8000;
        sprite[nSprite].owner = -1;
        sprite[nSprite].lotag = 0;
        sprite[nSprite].hitag = 0;
    }

    return nSprite;
}

int BuildElevF(int nChannel, int nSector, int nWallSprite, int arg_4, int arg_5, int nCount, ...)
{
    if (ElevCount >= kMaxElevs) {
        return -1;
    }

    int nElev = ElevCount;
    ElevCount++;

    Elevator[nElev].field_0 = 2;
    Elevator[nElev].field_6 = arg_4;
    Elevator[nElev].field_32 = -1;
    Elevator[nElev].field_A = arg_5;
    Elevator[nElev].nChannel = nChannel;
    Elevator[nElev].nSector = nSector;
    Elevator[nElev].nCountZOffsets = 0;
    Elevator[nElev].nCurZOffset = 0;
    Elevator[nElev].field_36 = 0;

    if (nWallSprite < 0) {
        nWallSprite = BuildWallSprite(nSector);
    }

    Elevator[nElev].nSprite = nWallSprite;

    va_list zlist;
    va_start(zlist, nCount);

    while (1)
    {
        if (Elevator[nElev].nCountZOffsets >= nCount) {
            return nElev;
        }

        int nVal = Elevator[nElev].nCountZOffsets;

        Elevator[nElev].nCountZOffsets++;

        Elevator[nElev].zOffsets[nVal] = va_arg(zlist, int);
    }
    va_end(zlist);

    return nElev;
}

int BuildElevC(int arg1, int nChannel, int nSector, int nWallSprite, int arg5, int arg6, int nCount, ...)
{
    if (ElevCount >= kMaxElevs) {
        return -1;
    }

    int nElev = ElevCount;
    ElevCount++;

    Elevator[nElev].field_0 = arg1;

    if (arg1 & 4)
    {
        arg5 /= 2;
    }

    Elevator[nElev].field_6 = arg5;
    Elevator[nElev].nCountZOffsets = 0;
    Elevator[nElev].field_36 = 0;
    Elevator[nElev].nCurZOffset = 0;
    Elevator[nElev].field_A = arg6;
    Elevator[nElev].field_32 = -1;
    Elevator[nElev].nChannel = nChannel;
    Elevator[nElev].nSector = nSector;

    if (nWallSprite < 0) {
        nWallSprite = BuildWallSprite(nSector);
    }

    Elevator[nElev].nSprite = nWallSprite;

    va_list zlist;
    va_start(zlist, nCount);

    while (1)
    {
        if (Elevator[nElev].nCountZOffsets >= nCount) {
            return nElev;
        }

        int nVal = Elevator[nElev].nCountZOffsets;

        Elevator[nElev].nCountZOffsets++;

        Elevator[nElev].zOffsets[nVal] = va_arg(zlist, int);
    }
    va_end(zlist);

    return nElev;
}

// TODO - tidy me up
// RENAME param A - not always Z
// Confirmed 100% correct with original .exe
int LongSeek(int *pZVal, int a2, int a3, int a4)
{
    int v4; // edx@1
    int v5; // ebx@2

    v4 = a2 - *pZVal;

    if (v4 < 0)
    {
        v5 = -a3;
        if (v5 > v4)
            v4 = v5;
        (*pZVal) += v4;
    }

    if (v4 > 0)
    {
        if (a4 < v4)
            v4 = a4;
        (*pZVal) += v4;
    }

    return v4;
}

int CheckSectorSprites(short nSector, int nVal)
{
    int b = 0;

    if (nVal)
    {
        short nSprite = headspritesect[nSector];

        int nZDiff = sector[nSector].floorz - sector[nSector].ceilingz;

        while (nSprite != -1)
        {
            if ((sprite[nSprite].cstat & 0x101) && (nZDiff < GetSpriteHeight(nSprite)))
            {
                if (nVal != 1) {
                    return 1;
                }

                b = 1;

                runlist_DamageEnemy(nSprite, -1, 5);

                if (sprite[nSprite].statnum == 100 && PlayerList[GetPlayerFromSprite(nSprite)].nHealth <= 0)
                {
                    PlayFXAtXYZ(StaticSound[kSoundJonFDie],
                        sprite[nSprite].x,
                        sprite[nSprite].y,
                        sprite[nSprite].z,
                        sprite[nSprite].sectnum | 0x4000);
                }
            }
            nSprite = nextspritesect[nSprite];
        }
    }
    else
    {
        for (int i = headspritesect[nSector]; i != -1; i = nextspritesect[i])
        {
            if (sprite[i].cstat & 0x101) {
                return 1;
            }
        }
        b = 0;
    }

    return b;
}

void MoveSectorSprites(int nSector, int z)
{
    int nSprite = headspritesect[nSector];

    while (nSprite != -1)
    {
        if (sprite[nSprite].statnum != 200) {
            sprite[nSprite].z += z;
        }

        nSprite = nextspritesect[nSprite];
    }
}

void StartElevSound(short nSprite, int nVal)
{
    short nSound;

    if (nVal & 2) {
        nSound = nElevSound;
    }
    else {
        nSound = nStoneSound;
    }

    D3PlayFX(StaticSound[nSound], nSprite);
}

void FuncElev(int a, int UNUSED(b), int nRun)
{
    short nElev = RunData[nRun].nVal;
    assert(nElev >= 0 && nElev < kMaxElevs);

    short nChannel = Elevator[nElev].nChannel;
    short var_18 = Elevator[nElev].field_0;

    assert(nChannel >= 0 && nChannel < kMaxChannels);

    int nMessage = a & 0x7F0000;

    if (nMessage < 0x10000) {
        return;
    }

//	int var_24 = var_18 & 0x10; // floor based?

    switch (nMessage)
    {
        default:
        {
            return;
        }

        case 0x10000:
        {
//			short ax = var_18 & 8;
            short dx = sRunChannels[nChannel].c;

            int edi = 0; // FIXME CHECKME - this isn't default set to anything in the ASM that I can see - if ax is 0 and var_24 is 0, this will never be set to a known value otherwise!

            if (var_18 & 0x8)
            {
                if (dx) {
                    edi = 1;
                }
                else {
                    edi = 0;
                }
            }
            else
            {
                // loc_20D48:
                if (var_18 & 0x10) // was var_24
                {
                    if (Elevator[nElev].field_32 < 0)
                    {
                        Elevator[nElev].field_32 = runlist_AddRunRec(NewRun, RunData[nRun].nVal, RunData[nRun].nRef);
                        StartElevSound(Elevator[nElev].nSprite, var_18);

                        edi = 1;
                    }
                }
                else
                {
                    if (dx < 0) {
                        edi = 0;
                    }
                    else
                    {
                        if (dx == Elevator[nElev].nCurZOffset || dx >= Elevator[nElev].nCountZOffsets)
                        {
                            Elevator[nElev].field_36 = dx;
                            edi = 1;
                        }
                        else
                        {
                            Elevator[nElev].nCurZOffset = sRunChannels[nChannel].c;
                            edi = 1;
                        }
                    }
                }
            }

            // loc_20DF9:
            if (edi)
            {
                if (Elevator[nElev].field_32 < 0)
                {
                    Elevator[nElev].field_32 = runlist_AddRunRec(NewRun, RunData[nRun].nVal, RunData[nRun].nRef);

                    StartElevSound(Elevator[nElev].nSprite, var_18);
                }
            }
            else
            {
                //loc_20E4E:
                if (Elevator[nElev].field_32 >= 0)
                {
                    runlist_SubRunRec(Elevator[nElev].field_32);
                    Elevator[nElev].field_32 = -1;
                }
            }

            return;
        }

        case 0x20000:
        {
            short nSector = Elevator[nElev].nSector;
            short di = Elevator[nElev].nSprite;

            int ebp = 0; // initialise to *something*

            if (var_18 & 0x2)
            {
                int nZOffset = Elevator[nElev].nCurZOffset;
                int nZVal = Elevator[nElev].zOffsets[nZOffset];

                short nSectorB = nSector;

                int nVal = LongSeek((int*)&sector[nSector].floorz, nZVal, Elevator[nElev].field_6, Elevator[nElev].field_A);
                ebp = nVal;

                if (!nVal)
                {
                    if (var_18 & 0x10)
                    {
                        Elevator[nElev].nCurZOffset ^= 1;
                        StartElevSound(di, var_18);
                    }
                    else
                    {
                        StopSpriteSound(di);
                        runlist_SubRunRec(nRun);
                        Elevator[nElev].field_32 = -1;
                        runlist_ReadyChannel(nChannel);

                        D3PlayFX(StaticSound[nStopSound], Elevator[nElev].nSprite);
                    }
                }
                else
                {
                    assert(nSector == nSectorB);
                    MoveSectorSprites(nSector, nVal);

                    if (nVal < 0 && CheckSectorSprites(nSector, 2))
                    {
                        runlist_ChangeChannel(nChannel, sRunChannels[nChannel].c == 0);
                        return;
                    }
                }
            }
            else
            {
                // loc_20FC3:
                int ceilZ = sector[nSector].ceilingz;
                sectortype *var_28 = &sector[nSector];

                int nZOffset = Elevator[nElev].nCurZOffset;
                int zVal = Elevator[nElev].zOffsets[nZOffset];

                int nVal = LongSeek(&ceilZ, zVal, Elevator[nElev].field_6, Elevator[nElev].field_A);
                ebp = nVal;

                if (!nVal)
                {
                    if (var_18 & 0x10)
                    {
                        Elevator[nElev].nCurZOffset ^= 1;

                        StartElevSound(Elevator[nElev].nSprite, var_18);
                    }
                    else
                    {
                        runlist_SubRunRec(nRun);
                        Elevator[nElev].field_32 = -1;
                        StopSpriteSound(Elevator[nElev].nSprite);
                        D3PlayFX(StaticSound[nStopSound], Elevator[nElev].nSprite);
                        runlist_ReadyChannel(nChannel);
                    }

                    return;
                }
                else if (nVal > 0)
                {
                    if (ceilZ == zVal)
                    {
                        if (var_18 & 0x4) {
                            SetQuake(di, 30);
                        }

                        PlayFXAtXYZ(StaticSound[kSoundSetLand], sprite[di].x, sprite[di].y, sprite[di].z, sprite[di].sectnum);
                    }

                    if (var_18 & 0x4)
                    {
                        if (CheckSectorSprites(nSector, 1)) {
                            return;
                        }
                    }
                    else
                    {
                        if (CheckSectorSprites(nSector, 0))
                        {
                            runlist_ChangeChannel(nChannel, sRunChannels[nChannel].c == 0);
                            return;
                        }
                    }
                }

                var_28->ceilingz = ceilZ;
            }

            // maybe this doesn't go here?
            while (di != -1)
            {
                sprite[di].z += ebp;
                di = sprite[di].owner;
            }

            return;
        }
    }
}

void InitWallFace()
{
    WallFaceCount = 0;
}

int BuildWallFace(short nChannel, short nWall, short nCount, ...)
{
    if (WallFaceCount >= kMaxWallFace) {
        bail2dos("Too many wall faces!\n");
    }

    int nWallFace = WallFaceCount;
    WallFaceCount++;

    WallFace[nWallFace].nPicCount = 0;
    WallFace[nWallFace].nWall = nWall;
    WallFace[nWallFace].nChannel = nChannel;

    if (nCount > kMaxWallFacePics) {
        nCount = kMaxWallFacePics;
    }

    va_list piclist;
    va_start(piclist, nCount);

    while (WallFace[nWallFace].nPicCount < nCount)
    {
        int i = WallFace[nWallFace].nPicCount;
        WallFace[nWallFace].nPicCount++;

        WallFace[nWallFace].picList[i] = (short)va_arg(piclist, int);
    }
    va_end(piclist);

    return nWallFace;
}

void FuncWallFace(int a, int UNUSED(b), int nRun)
{
    int nWallFace = RunData[nRun].nVal;
    assert(nWallFace >= 0 && nWallFace < kMaxWallFace);

    short nChannel = WallFace[nWallFace].nChannel;

    if ((a & 0x7F0000) != 0x10000)
        return;

    short nPic = sRunChannels[nChannel].c;

    if ((nPic <= WallFace[nWallFace].nPicCount) && (nPic >= 0))
    {
        wall[WallFace[nWallFace].nWall].picnum = WallFace[nWallFace].picList[nPic];
    }
}

void InitPoint()
{
    PointCount = 0;

    for (int i = 0; i < kMaxPoints; i++) {
        PointFree[i] = i;
    }
}

int GrabPoint()
{
    return PointFree[PointCount++];
}

void InitSlide()
{
    SlideCount = 0;

    for (int i = 0; i < kMaxSlides; i++) {
        SlideFree[i] = i;
    }
}

int IdentifySector(int nVal)
{
    for (int i = 0; i < numsectors; i++)
    {
        for (int j = 0; ; j++)
        {
            if (j < sector[i].wallnum)
            {
                int nWall = sector[i].wallptr;
                if (nWall + j == nVal)
                    return i;
            }
            else {
                break;
            }
        }
    }

    return -1;
}

int BuildSlide(int nChannel, int nStartWall, int ebx, int ecx, int nWall2, int nWall3, int nWall4)
{
    if (SlideCount >= kMaxSlides) {
        bail2dos("Too many slides!\n");
        return -1;
    }

    int nSlide = SlideCount;
    SlideCount++;

    short nSector = IdentifySector(nStartWall);

    SlideData2[nSlide].field_4 = -1;
    SlideData2[nSlide].nChannel = nChannel;
    SlideData2[nSlide].field_2 = -1;

    int nPoint = GrabPoint();

    SlideData2[nSlide].field_2 = nPoint;

    PointList[nPoint].field_E = -1;
    PointList[nPoint].field_0 = nSector;

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

    for (int nWall = startwall; nWall < endwall; nWall++)
    {
        short ax = SlideData2[nSlide].field_2;

        if (ax >= 0)
        {
            while (ax >= 0)
            {
                if (wall[nWall].nextsector == PointList[ax].field_0) {
                    break;
                }

                ax = PointList[ax].field_E;
            }
        }
        else
        {
            if (wall[nWall].nextsector >= 0)
            {
                nPoint = GrabPoint();

                PointList[nPoint].field_E = SlideData2[nSlide].field_2;
                PointList[nPoint].field_0 = wall[nWall].nextsector;

                SlideData2[nSlide].field_2 = nPoint;
            }
        }
    }

    SlideData[nSlide].field_0 = nStartWall;
    SlideData[nSlide].field_4 = ebx;
    SlideData[nSlide].field_8 = nWall2;
    SlideData[nSlide].field_C = nWall3;

    SlideData[nSlide].x1 = wall[nStartWall].x;
    SlideData[nSlide].y1 = wall[nStartWall].y;

    SlideData[nSlide].x2 = wall[nWall2].x;
    SlideData[nSlide].y2 = wall[nWall2].y;

    SlideData[nSlide].field_20 = wall[ebx].x;
    SlideData[nSlide].field_24 = wall[ebx].y;

    SlideData[nSlide].field_28 = wall[nWall3].x;
    SlideData[nSlide].field_2C = wall[nWall3].y;

    SlideData[nSlide].field_30 = wall[ecx].x;
    SlideData[nSlide].field_34 = wall[ecx].y;

    SlideData[nSlide].field_38 = wall[nWall4].x;
    SlideData[nSlide].field_3C = wall[nWall4].y;

    int nSprite = insertsprite(nSector, 899);

    SlideData2[nSlide].field_6 = nSprite;
    sprite[nSprite].cstat = 0x8000;
    sprite[nSprite].x = wall[nStartWall].x;
    sprite[nSprite].y = wall[nStartWall].y;
    sprite[nSprite].z = sector[nSector].floorz;

    SlideData2[nSlide].field_8 = 0;

    return nSlide;
}

void FuncSlide(int a, int UNUSED(b), int nRun)
{
    int nSlide = RunData[nRun].nVal;
    assert(nSlide >= 0 && nSlide < kMaxSlides);

    short nChannel = SlideData2[nSlide].nChannel;

    int nMessage = a & 0x7F0000;

    int ebp = 0;

    short cx = sRunChannels[nChannel].c;

    switch (nMessage)
    {
        case 0x10000:
        {
            if (SlideData2[nSlide].field_4 >= 0)
            {
                runlist_SubRunRec(SlideData2[nSlide].field_4);
                SlideData2[nSlide].field_4 = -1;
            }

            if (sRunChannels[nChannel].c && sRunChannels[nChannel].c != 1) {
                return;
            }

            SlideData2[nSlide].field_4 = runlist_AddRunRec(NewRun, RunData[nRun].nVal, RunData[nRun].nRef);

            if (SlideData2[nSlide].field_8 != sRunChannels[nChannel].c)
            {
                D3PlayFX(StaticSound[kSoundAmbientStone], SlideData2[nSlide].field_6);
                SlideData2[nSlide].field_8 = sRunChannels[nChannel].c;
            }

            return;
        }

        case 0x20000:
        {
            int clipmask = ebp + 1; // RENAME

            if (cx == 1)
            {
                short nWall = SlideData[nSlide].field_4;
                int x = wall[nWall].x;
                int y = wall[nWall].y;

                int nSeekA = LongSeek(&x, SlideData[nSlide].field_30, 20, 20);
                int var_34 = nSeekA;
                int var_20 = nSeekA;

                int nSeekB = LongSeek(&y, SlideData[nSlide].field_34, 20, 20);
                int var_2C = nSeekB;
                int var_24 = nSeekB;

                dragpoint(SlideData[nSlide].field_4, x, y, 0);
                movesprite(SlideData2[nSlide].field_6, var_34 << 14, var_2C << 14, 0, 0, 0, CLIPMASK1);

                if (var_34 == 0)
                {
                    if (var_2C == 0)
                    {
                        ebp = clipmask;
                    }
                }

                nWall = SlideData[nSlide].field_0;

                y = wall[nWall].y + var_24;
                x = wall[nWall].x + var_20;

                dragpoint(SlideData[nSlide].field_0, x, y, 0);

                nWall = SlideData[nSlide].field_C;

                x = wall[nWall].x;
                y = wall[nWall].y;

                int nSeekC = LongSeek(&x, SlideData[nSlide].field_38, 20, 20);
                int var_30 = nSeekC;
                var_20 = nSeekC;

                int nSeekD = LongSeek(&y, SlideData[nSlide].field_3C, 20, 20);
                int edi = nSeekD;
                var_24 = nSeekD;

                dragpoint(SlideData[nSlide].field_C, x, y, 0);

                if (var_30 == 0 && edi == 0) {
                    ebp++;
                }

                nWall = SlideData[nSlide].field_8;

                x = wall[nWall].x + var_20;
                y = wall[nWall].y + var_24;

                dragpoint(SlideData[nSlide].field_8, x, y, 0);
            }
            else if (cx == 0) // right branch
            {
                short nWall = SlideData[nSlide].field_0;
                int x = wall[nWall].x;
                int y = wall[nWall].y;

                int nSeekA = LongSeek(&x, SlideData[nSlide].x1, 20, 20);
                int edi = nSeekA;
                int var_1C = nSeekA;

                int nSeekB = LongSeek(&y, SlideData[nSlide].y1, 20, 20);
                int ecx = nSeekB;
                int var_28 = nSeekB;

                dragpoint(SlideData[nSlide].field_0, x, y, 0);

                if (edi == 0 && ecx == 0) {
                    ebp = clipmask;
                }

                nWall = SlideData[nSlide].field_4;

                y = wall[nWall].y + var_28;
                x = wall[nWall].x + var_1C;

                dragpoint(SlideData[nSlide].field_4, x, y, 0);

                nWall = SlideData[nSlide].field_8;

                x = wall[nWall].x;
                y = wall[nWall].y;

                int nSeekC = LongSeek(&x, SlideData[nSlide].x2, 20, 20);
                edi = nSeekC;
                var_1C = nSeekC;

                int nSeekD = LongSeek(&y, SlideData[nSlide].y2, 20, 20);
                ecx = nSeekD;
                var_28 = nSeekD;

                dragpoint(SlideData[nSlide].field_8, x, y, 0);

                if (edi == 0 && ecx == 0) {
                    ebp++;
                }

                nWall = SlideData[nSlide].field_C;

                y = wall[nWall].y + var_28;
                x = wall[nWall].x + var_1C;

                dragpoint(SlideData[nSlide].field_C, x, y, 0);
            }

            // loc_21A51:
            if (ebp >= 2)
            {
                runlist_SubRunRec(SlideData2[nSlide].field_4);

                SlideData2[nSlide].field_4 = -1;
                D3PlayFX(StaticSound[nStopSound], SlideData2[nSlide].field_6);

                runlist_ReadyChannel(nChannel);
            }

            return;
        }
    }
}

int BuildTrap(int nSprite, int edx, int ebx, int ecx)
{
    int var_14 = edx;
    int var_18 = ebx;
    int var_10 = ecx;

    if (nTraps >= kMaxTraps) {
        bail2dos("Too many traps!\n");
    }

    short nTrap = nTraps;
    nTraps++;

    changespritestat(nSprite, 0);

    sprite[nSprite].cstat = 0x8000;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].extra = -1;

    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].hitag = runlist_AddRunRec(NewRun, nTrap, kRunTrap);
    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nTrap, kRunTrap);

//	GrabTimeSlot(3);

    sTrap[nTrap].nSprite = nSprite;
    sTrap[nTrap].nType = (var_14 == 0) + 14;
    sTrap[nTrap].field_0 = -1;

    nTrapInterval[nTrap] = 64 - (2 * var_10);
    if (nTrapInterval[nTrap] < 5) {
        nTrapInterval[nTrap] = 5;
    }

    sTrap[nTrap].field_C = 0;
    sTrap[nTrap].field_A = 0;

    if (var_18 == -1) {
        return nTrap;
    }

    sTrap[nTrap].field_6 = -1;
    sTrap[nTrap].field_8 = -1;

    short nSector = sprite[nSprite].sectnum;
    short nWall = sector[nSector].wallptr;

    int i = 0;

    while (1)
    {
        if (sector[nSector].wallnum >= i) {
            return nTrap;
        }

        if (var_18 == wall[nWall].hitag)
        {
            if (sTrap[nTrap].field_6 != -1)
            {
                sTrap[nTrap].field_8 = nWall;
                sTrap[nTrap].field_C = wall[nWall].picnum;
                return nTrap;
            }
            else
            {
                sTrap[nTrap].field_6 = nWall;
                sTrap[nTrap].field_A = wall[nWall].picnum;
            }
        }

        ecx++;
        nWall++;
    }
}

void FuncTrap(int a, int UNUSED(b), int nRun)
{
    short nTrap = RunData[nRun].nVal;
    short nSprite = sTrap[nTrap].nSprite;

    int nMessage = a & 0x7F0000;

    switch (nMessage)
    {
        case 0x10000:
        {
            short nChannel = a & 0x3FFF;

            if (sRunChannels[nChannel].c > 0)
            {
                sTrap[nTrap].field_0 = 12;
            }
            else
            {
                sTrap[nTrap].field_0 = -1;
            }

            return;
        }

        case 0x20000:
        {
            if (sTrap[nTrap].field_0 >= 0)
            {
                sTrap[nTrap].field_0--;
                if (sTrap[nTrap].field_0 > 10) {
                    return;
                }

                short nType = sTrap[nTrap].nType;

                if (sTrap[nTrap].field_0 == 0)
                {
                    sTrap[nTrap].field_0 = nTrapInterval[nTrap];

                    if (nType == 14)
                    {
                        short nWall = sTrap[nTrap].field_6;
                        if (nWall > -1)
                        {
                            wall[nWall].picnum = sTrap[nTrap].field_A;
                        }

                        nWall = sTrap[nTrap].field_8;
                        if (nWall > -1)
                        {
                            wall[nWall].picnum = sTrap[nTrap].field_C;
                        }
                    }
                }
                else
                {
                    // loc_21D92:
                    if (sTrap[nTrap].field_0 != 5) {
                        return;
                    }

                    int nBullet = BuildBullet(nSprite, nType, 0, 0, 0, sprite[nSprite].ang, 0, 1);
                    if (nBullet > -1)
                    {
                        int nBulletSprite = GetBulletSprite(nBullet);
                        assert(nBulletSprite >= 0);

                        if (nType == 15)
                        {
                            sprite[nBulletSprite].ang = (sprite[nBulletSprite].ang - 512) & kAngleMask;
                            D3PlayFX(StaticSound[kSoundTrapArrow], nSprite);
                        }
                        else
                        {
                            sprite[nBulletSprite].clipdist = 50;

                            short nWall = sTrap[nTrap].field_6;
                            if (nWall > -1)
                            {
                                wall[nWall].picnum = sTrap[nTrap].field_A + 1;
                            }

                            nWall = sTrap[nTrap].field_8;
                            if (nWall > -1)
                            {
                                wall[nWall].picnum = sTrap[nTrap].field_C + 1;
                            }

                            D3PlayFX(StaticSound[kSoundTrapFire], nSprite);
                        }
                    }
                }
            }

            return;
        }

        case 0x30000:
        case 0x90000:
        case 0x80000:
        case 0xA0000:
            return;

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

int BuildArrow(int nSprite, int nVal)
{
    return BuildTrap(nSprite, 0, -1, nVal);
}

int BuildFireBall(int nSprite, int a, int b)
{
    return BuildTrap(nSprite, 1, a, b);
}

int BuildSpark(int nSprite, int nVal)
{
    int nSparkSprite = insertsprite(sprite[nSprite].sectnum, 0);

    if (nSparkSprite < 0) {
        return -1;
    }

    assert(nSparkSprite < kMaxSprites);

    sprite[nSparkSprite].x = sprite[nSprite].x;
    sprite[nSparkSprite].y = sprite[nSprite].y;
    sprite[nSparkSprite].cstat = 0;
    sprite[nSparkSprite].shade = -127;
    sprite[nSparkSprite].pal = 1;
    sprite[nSparkSprite].xoffset = 0;
    sprite[nSparkSprite].yoffset = 0;
    sprite[nSparkSprite].xrepeat = 50;
    sprite[nSparkSprite].yrepeat = 50;

    if (nVal >= 2)
    {
        sprite[nSparkSprite].picnum = kEnergy2;
        nSmokeSparks++;

        if (nVal == 3)
        {
            sprite[nSparkSprite].xrepeat = 120;
            sprite[nSparkSprite].yrepeat = 120;
        }
        else
        {
            sprite[nSparkSprite].xrepeat = sprite[nSprite].xrepeat + 15;
            sprite[nSparkSprite].yrepeat = sprite[nSprite].xrepeat + 15;
        }
    }
    else
    {
        int nAngle = (sprite[nSprite].ang + 256) - RandomSize(9);

        if (nVal)
        {
            sprite[nSparkSprite].xvel = Cos(nAngle) >> 5;
            sprite[nSparkSprite].yvel = Sin(nAngle) >> 5;
        }
        else
        {
            sprite[nSparkSprite].xvel = Cos(nAngle) >> 6;
            sprite[nSparkSprite].yvel = Sin(nAngle) >> 6;
        }

        sprite[nSparkSprite].zvel = -(RandomSize(4) << 7);
        sprite[nSparkSprite].picnum = kTile985 + nVal;
    }

    sprite[nSparkSprite].z = sprite[nSprite].z;
    sprite[nSparkSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSparkSprite].clipdist = 1;
    sprite[nSparkSprite].hitag = 0;

//	GrabTimeSlot(3);

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

    return nSparkSprite;
}

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

    int nMessage = a & 0x7F0000;

    if (nMessage != 0x20000) {
        return;
    }

    sprite[nSprite].shade += 3;
    sprite[nSprite].xrepeat -= 2;

    if (sprite[nSprite].xrepeat >= 4 && sprite[nSprite].shade <= 100)
    {
        sprite[nSprite].yrepeat -= 2;

        // calling BuildSpark() with 2nd parameter as '1' will set kTile986
        if (sprite[nSprite].picnum == kTile986 && (sprite[nSprite].xrepeat & 2))
        {
            BuildSpark(nSprite, 2);
        }

        if (sprite[nSprite].picnum >= kTile3000) {
            return;
        }

        sprite[nSprite].zvel += 128;

        int nMov = movesprite(nSprite, sprite[nSprite].xvel << 12, sprite[nSprite].yvel << 12, sprite[nSprite].zvel, 2560, -2560, CLIPMASK1);
        if (!nMov) {
            return;
        }

        if (sprite[nSprite].zvel <= 0) {
            return;
        }
    }

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

    if (sprite[nSprite].picnum > kTile3000) {
        nSmokeSparks--;
    }

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

void DimLights()
{
    static short word_96786 = 0;

    word_96786 = word_96786 == 0;
    if (word_96786 == 0)
        return;

    for (int i = 0; i < numsectors; i++)
    {
        if (sector[i].ceilingshade < 100)
            sector[i].ceilingshade++;

        if (sector[i].floorshade < 100)
            sector[i].floorshade++;

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

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

void DoFinale()
{
    static int dword_96788 = 0;
    static int dword_1542FC = 0;

    if (!lFinaleStart)
        return;

    dword_96788++;

    if (dword_96788 < 90)
    {
        if (!(dword_96788 & 2))
        {
            int nAng = RandomSize(11);
            sprite[nFinaleSpr].ang = nAng;
            BuildSpark(nFinaleSpr, 1);
        }

        if (!RandomSize(2))
        {
            PlayFX2(StaticSound[kSoundBlasted] | 0x2000, nFinaleSpr);

            for (int i = 0; i < nTotalPlayers; i++) {
                nQuake[i] = 1280;
            }
        }
    }
    else
    {
        DimLights();

        if (nDronePitch <= -2400)
        {
            if (nFinaleStage < 2)
            {
                if (nFinaleStage == 1)
                {
                    StopLocalSound();
                    PlayLocalSound(StaticSound[kSoundJonTaunt3], 0);
                    dword_1542FC = (int)totalclock + 120;
                    nFinaleStage++;
                }
            }
            else if (nFinaleStage <= 2)
            {
                if ((int)totalclock >= dword_1542FC)
                {
                    PlayLocalSound(StaticSound[kSoundJonLaugh1], 0);
                    nFinaleStage++;
                    dword_1542FC = (int)totalclock + 360;
                }
            }
            else if (nFinaleStage == 3 && (int)totalclock >= dword_1542FC)
            {
                FinishLevel();
            }
        }
        else
        {
            nDronePitch -= 128;
            BendAmbientSound();
            nFinaleStage = 1;
        }
    }
}

int BuildEnergyBlock(short nSector)
{
    short startwall = sector[nSector].wallptr;
    short nWalls = sector[nSector].wallnum;

    int x = 0;
    int y = 0;

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

        wall[startwall + i].picnum = kClockSymbol16;
        wall[startwall + i].pal = 0;
        wall[startwall + i].shade = 50;
    }

    int xAvg = x / nWalls;
    int yAvg = y / nWalls;

    int nSprite = insertsprite(nSector, 406);

    short nextsector = wall[startwall].nextsector;

    sprite[nSprite].x = xAvg;
    sprite[nSprite].y = yAvg;

    sector[nSector].extra = nSprite;

//	GrabTimeSlot(3);

    sprite[nSprite].z = sector[nextsector].floorz;

    // CHECKME - name of this variable?
    int nRepeat = (sprite[nSprite].z - sector[nSector].floorz) >> 8;
    if (nRepeat > 255) {
        nRepeat = 255;
    }

    sprite[nSprite].xrepeat = nRepeat;
    sprite[nSprite].cstat = 0x8000;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].extra = -1;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nSprite, kRunEnergyBlock);

    nEnergyBlocks++;

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

// TODO - tidy
void KillCreatures()
{
    signed int v0;
    signed int v1;
    int i;

    v0 = 99;
    v1 = 99;

    while (1)
    {
        if (v0 != 100)
        {
            for (i = headspritestat[v1]; i != -1; i = nextspritestat[i])
            {
                runlist_DamageEnemy(i, -1, 1600);
            }
        }
        ++v0;
        ++v1;

        if (v0 > 107) {
            return;
        }
    }
}

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

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

    int i;

    for (i = 0; i < nWalls; i++)
    {
        short nextwall = wall[startwall + i].nextwall;

        if (wall[nextwall].pal >= 4) {
            wall[nextwall].pal = 7;
        }
        else {
            wall[nextwall].pal = 0;
        }

        wall[nextwall].shade = 50;
    }

    if (sector[nSector].floorpal >= 4) {
        sector[nSector].floorpal = 7;
    }
    else {
        sector[nSector].floorpal = 0;
    }

    sector[nSector].floorshade = 50;
    sector[nSector].extra = -1;
    sector[nSector].floorz = sprite[nSprite].z;

    sprite[nSprite].z = (sprite[nSprite].z + sector[nSector].floorz) / 2;

    BuildSpark(nSprite, 3);

    sprite[nSprite].cstat = 0;
    sprite[nSprite].xrepeat = 100;

    PlayFX2(StaticSound[kSoundBlasted], nSprite);

    sprite[nSprite].xrepeat = 0;

    nEnergyTowers--;

    for (i = 0; i < 20; i++)
    {
        sprite[nSprite].ang = RandomSize(11);
        BuildSpark(nSprite, 1); // shoot out blue orbs
    }

    TintPalette(64, 64, 64);

    if (nEnergyTowers == 1)
    {
        runlist_ChangeChannel(nEnergyChan, nEnergyTowers);
        StatusMessage(1000, "TAKE OUT THE CONTROL CENTER!");
    }
    else if (nEnergyTowers != 0)
    {
        StatusMessage(500, "%d TOWERS REMAINING", nEnergyTowers);
    }
    else
    {
        nFinaleSpr = nSprite;
        lFinaleStart = (int)totalclock;

        if (!lFinaleStart) {
            lFinaleStart = lFinaleStart + 1;
        }

        for (i = 0; i < numsectors; i++)
        {
            if (sector[i].ceilingpal == 1) {
                sector[i].ceilingpal = 0;
            }

            if (sector[i].floorpal == 1) {
                sector[i].floorpal = 0;
            }

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

            for (int nWall = startwall; nWall < endwall; nWall++)
            {
                if (wall[nWall].pal == 1) {
                    wall[nWall].pal = 0;
                }
            }
        }

        KillCreatures();
    }

    changespritestat(nSprite, 0);
}

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

    int nMessage = a & 0x7F0000;

    switch (nMessage)
    {
        case 0x20000:
        case 0x30000:
        case 0x90000:
        {
            return;
        }

        case 0xA0000:
        {
            short nSector = sprite[nSprite].sectnum;

            if (sector[nSector].extra == -1) {
                return;
            }

            int nFloorZ = sector[nSector].floorz;

            sector[nSector].floorz = sprite[nSprite].z;
            sprite[nSprite].z -= 256;

            nDamage = runlist_CheckRadialDamage(nSprite);

            // restore previous values
            sector[nSector].floorz = nFloorZ;
            sprite[nSprite].z += 256;

            if (nDamage <= 0) {
                return;
            }

            // fall through to case 0x80000
            fallthrough__;
        }

        case 0x80000:
        {
            nDamage >>= 2;
            if (nDamage <= 0) {
                return;
            }

            if (nDamage < sprite[nSprite].xrepeat)
            {
                sprite[nSprite].xrepeat -= nDamage;

                int nSprite2 = insertsprite(lasthitsect, 0);

                sprite[nSprite2].ang = a & 0xFFFF;
                sprite[nSprite2].x = lasthitx;
                sprite[nSprite2].y = lasthity;
                sprite[nSprite2].z = lasthitz;

                BuildSpark(nSprite2, 0); // shoot out blue orb when damaged
                mydeletesprite(nSprite2);
            }
            else
            {
                sprite[nSprite].xrepeat = 0; // using xrepeat to store health
                ExplodeEnergyBlock(nSprite);
            }

            return;
        }
    }
}

int BuildObject(short nSprite, int nOjectType, int nHitag)
{
    if (ObjectCount >= kMaxObjects) {
        bail2dos("Too many objects!\n");
    }

    short nObject = ObjectCount;
    ObjectCount++;

    changespritestat(nSprite, ObjectStatnum[nOjectType]);

    // 0x7FFD to ensure set as blocking ('B' and 'H') sprite and also disable translucency and set not invisible
    sprite[nSprite].cstat = (sprite[nSprite].cstat | 0x101) & 0x7FFD;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].extra = -1;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nObject, kRunObject);

//	GrabTimeSlot(3);

    if (sprite[nSprite].statnum == kStatDestructibleSprite) {
        ObjectList[nObject].nHealth = 4;
    }
    else {
        ObjectList[nObject].nHealth = 120;
    }

    ObjectList[nObject].nSprite = nSprite;
    ObjectList[nObject].field_4 = runlist_AddRunRec(NewRun, nObject, kRunObject);

    short nSeq = ObjectSeq[nOjectType];

    if (nSeq > -1)
    {
        ObjectList[nObject].field_8 = SeqOffsets[nSeq];

        if (!nOjectType) // if not Explosion Trigger (e.g. Exploding Fire Cauldron)
        {
            ObjectList[nObject].field_0 = RandomSize(4) % (SeqSize[ObjectList[nObject].field_8] - 1);
        }

        int nSprite2 = insertsprite(sprite[nSprite].sectnum, 0);
        ObjectList[nObject].field_10 = nSprite2;

        sprite[nSprite2].cstat = 0x8000;
        sprite[nSprite2].x = sprite[nSprite].x;
        sprite[nSprite2].y = sprite[nSprite].y;
        sprite[nSprite2].z = sprite[nSprite].z;
    }
    else
    {
        ObjectList[nObject].field_0 = 0;
        ObjectList[nObject].field_8 = -1;

        if (sprite[nSprite].statnum == kStatDestructibleSprite) {
            ObjectList[nObject].field_10 = -1;
        }
        else {
            ObjectList[nObject].field_10 = -nHitag;
        }
    }

    return nObject;
}

// in-game destructable wall mounted screen
void ExplodeScreen(short nSprite)
{
    sprite[nSprite].z -= GetSpriteHeight(nSprite) / 2;

    for (int i = 0; i < 30; i++) {
        BuildSpark(nSprite, 0); // shoot out blue orbs
    }

    sprite[nSprite].cstat = 0x8000;
    PlayFX2(StaticSound[kSoundBlasted], nSprite);
}

void FuncObject(int a, int b, int nRun)
{
    short nObject = RunData[nRun].nVal;

    short nSprite = ObjectList[nObject].nSprite;
    short nStat = sprite[nSprite].statnum;
    short bx = ObjectList[nObject].field_8;

    int nMessage = a & 0x7F0000;

    switch (nMessage)
    {
        default:
        {
            DebugOut("unknown msg %d for Object\n", a & 0x7F0000);
            return;
        }

        case 0x30000:
            return;

        case 0x80000:
        {
            if (nStat >= 150 || ObjectList[nObject].nHealth <= 0) {
                return;
            }

            if (nStat == 98)
            {
                D3PlayFX((StaticSound[kSoundDrum4] | 0x2000) | (RandomSize(2) << 9), nSprite);
                return;
            }

            ObjectList[nObject].nHealth -= (short)b;
            if (ObjectList[nObject].nHealth > 0) {
                return;
            }

            if (nStat == kStatDestructibleSprite)
            {
                ExplodeScreen(nSprite);
            }
            else
            {
                ObjectList[nObject].nHealth = -(RandomSize(3) + 1);
            }

            return;
        }

        case 0x90000:
        {
            if (bx > -1)
            {
                seq_PlotSequence(a & 0xFFFF, bx, ObjectList[nObject].field_0, 1);
            }
            return;
        }

        case 0xA0000:
        {
            if (ObjectList[nObject].nHealth > 0 && sprite[nSprite].cstat & 0x101
                && (nStat != kStatExplodeTarget
                    || sprite[nRadialSpr].statnum == 201
                    || nRadialBullet != 3 && nRadialBullet > -1
                    || sprite[nRadialSpr].statnum == kStatExplodeTrigger))
            {
                int nDamage = runlist_CheckRadialDamage(nSprite);
                if (nDamage <= 0) {
                    return;
                }

                if (sprite[nSprite].statnum != kStatAnubisDrum) {
                    ObjectList[nObject].nHealth -= nDamage;
                }

                if (sprite[nSprite].statnum == kStatExplodeTarget)
                {
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                    sprite[nSprite].zvel = 0;
                }
                else if (sprite[nSprite].statnum != kStatAnubisDrum)
                {
                    sprite[nSprite].xvel >>= 1;
                    sprite[nSprite].yvel >>= 1;
                    sprite[nSprite].zvel >>= 1;
                }

                if (ObjectList[nObject].nHealth > 0) {
                    return;
                }

                if (sprite[nSprite].statnum == kStatExplodeTarget)
                {
                    ObjectList[nObject].nHealth = -1;
                    short ax = ObjectList[nObject].field_10;

                    if (ax < 0 || ObjectList[ax].nHealth <= 0) {
                        return;
                    }

                    ObjectList[ax].nHealth = -1;
                }
                else if (sprite[nSprite].statnum == kStatDestructibleSprite)
                {
                    ObjectList[nObject].nHealth = 0;

                    ExplodeScreen(nSprite);
                }
                else
                {
                    ObjectList[nObject].nHealth = -(RandomSize(4) + 1);
                }
            }

            return;
        }

        case 0x20000:
        {
            if (nStat == 97 || (!(sprite[nSprite].cstat & 0x101))) {
                return;
            }

            if (nStat != kStatExplodeTarget) {
                Gravity(nSprite);
            }

            // do animation
            if (bx != -1)
            {
                ObjectList[nObject].field_0++;
                if (ObjectList[nObject].field_0 >= SeqSize[bx]) {
                    ObjectList[nObject].field_0 = 0;
                }

                sprite[nSprite].picnum = seq_GetSeqPicnum2(bx, ObjectList[nObject].field_0);
            }

            if (ObjectList[nObject].nHealth >= 0) {
                goto FUNCOBJECT_GOTO;
            }

            ObjectList[nObject].nHealth++;

            if (ObjectList[nObject].nHealth)
            {
FUNCOBJECT_GOTO:
                if (nStat != kStatExplodeTarget)
                {
                    int nMov = movesprite(nSprite, sprite[nSprite].xvel << 6, sprite[nSprite].yvel << 6, sprite[nSprite].zvel, 0, 0, CLIPMASK0);

                    if (sprite[nSprite].statnum == kStatExplodeTrigger) {
                        sprite[nSprite].pal = 1;
                    }

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

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

                return;
            }
            else
            {
                int var_18;

                // red branch
                if ((nStat == kStatExplodeTarget) || (sprite[nSprite].z < sector[sprite[nSprite].sectnum].floorz))
                {
                    var_18 = 36;
                }
                else
                {
                    var_18 = 34;
                }

                AddFlash(sprite[nSprite].sectnum, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, 128);
                BuildAnim(-1, var_18, 0, sprite[nSprite].x, sprite[nSprite].y, sector[sprite[nSprite].sectnum].floorz, sprite[nSprite].sectnum, 240, 4);

//				int edi = nSprite | 0x4000;

                if (nStat == kStatExplodeTrigger)
                {
                    for (int i = 4; i < 8; i++) {
                        BuildCreatureChunk(nSprite | 0x4000, seq_GetSeqPicnum(kSeqFirePot, (i >> 2) + 1, 0));
                    }

                    runlist_RadialDamageEnemy(nSprite, 200, 20);
                }
                else if (nStat == kStatExplodeTarget)
                {
                    for (int i = 0; i < 8; i++) {
                        BuildCreatureChunk(nSprite | 0x4000, seq_GetSeqPicnum(kSeqFirePot, (i >> 1) + 3, 0));
                    }
                }

                if (levelnum <= 20 || nStat != kStatExplodeTrigger)
                {
                    runlist_SubRunRec(sprite[nSprite].owner);
                    runlist_SubRunRec(ObjectList[nObject].field_4);

                    mydeletesprite(nSprite);
                    return;
                }
                else
                {
                    StartRegenerate(nSprite);
                    ObjectList[nObject].nHealth = 120;

                    sprite[nSprite].x = sprite[ObjectList[nObject].field_10].x;
                    sprite[nSprite].y = sprite[ObjectList[nObject].field_10].y;
                    sprite[nSprite].z = sprite[ObjectList[nObject].field_10].z;

                    mychangespritesect(nSprite, sprite[ObjectList[nObject].field_10].sectnum);
                    return;
                }
            }
        }
    }
}

void BuildDrip(int nSprite)
{
    if (nDrips >= kMaxDrips) {
        bail2dos("Too many drips!\n");
    }

    sDrip[nDrips].nSprite = nSprite;
    sDrip[nDrips].field_2 = RandomSize(8) + 90;

    nDrips++;

    sprite[nSprite].cstat = 0x8000u;
}

void DoDrips()
{
    int i;

    for (i = 0; i < nDrips; i++)
    {
        sDrip[i].field_2--;
        if (sDrip[i].field_2 <= 0)
        {
            short nSprite = sDrip[i].nSprite;

            short nSeqOffset = SeqOffsets[kSeqDrips];

            if (!(SectFlag[sprite[nSprite].sectnum] & kSectLava)) {
                nSeqOffset++;
            }

            seq_MoveSequence(nSprite, nSeqOffset, RandomSize(2) % SeqSize[nSeqOffset]);

            sDrip[i].field_2 = RandomSize(8) + 90;
        }
    }

    for (i = 0; i < nBobs; i++)
    {
        sBob[i].field_2 += 4;

        int edx = Sin(sBob[i].field_2 << 3) >> 4;
        short nSector = sBob[i].nSector;

        if (sBob[i].field_3)
        {
            sector[nSector].ceilingz = edx + sBob[i].z;
        }
        else
        {
            int nFloorZ = sector[nSector].floorz;

            sector[nSector].floorz = edx + sBob[i].z;

            MoveSectorSprites(nSector, sector[nSector].floorz - nFloorZ);
        }
    }
}

void SnapBobs(short nSectorA, short nSectorB)
{
    int ecx = -1;
    int ebx = ecx;
//	int var_14 = nSector;
//	int edi = edx;

    for (int i = 0; i < nBobs; i++)
    {
        int esi = sBob[i].nSector;

        if (esi != nSectorA)
        {
            if (nSectorB != esi)
                continue;

            esi = ebx;
            ecx = i;
        }
        else
        {
            esi = ecx;
            ebx = i;
        }

        if (esi != -1) {
            break;
        }
    }

    if (ecx <= -1) {
        return;
    }

    if (ebx <= -1) {
        return;
    }

    sBob[ecx].field_2 = sBob[ebx].field_2;
}

void AddSectorBob(int nSector, int nHitag, int bx)
{
    if (nBobs >= kMaxBobs) {
        bail2dos("Too many bobs!\n");
    }

    sBob[nBobs].field_3 = bx;

    int z;

    if (bx == 0) {
        z = sector[nSector].floorz;
    }
    else {
        z = sector[nSector].ceilingz;
    }

    sBob[nBobs].z = z;
    sBob[nBobs].field_2 = nHitag << 4;
    sBobID[nBobs] = nHitag;

    sBob[nBobs].nSector = nSector;

    SectFlag[nSector] |= 0x0010;

    nBobs++;
}

int FindTrail(int nVal)
{
    for (int i = 0; i < nTrails; i++)
    {
        if (sTrail[i].field_2 == nVal)
            return i;
    }

    sTrail[nTrails].field_2 = nVal;
    sTrail[nTrails].field_0 = -1;

    return nTrails++;
}

void ProcessTrailSprite(int nSprite, int nLotag, int nHitag)
{
    if (nTrailPoints >= 100) {
        bail2dos("Too many trail point sprites (900-949)... increase MAX_TRAILPOINTS\n");
    }

    short nPoint = nTrailPoints;
    nTrailPoints++;

    sTrailPoint[nPoint].x = sprite[nSprite].x;
    sTrailPoint[nPoint].y = sprite[nSprite].y;

    int nTrail = FindTrail(nHitag);

    int var_14 = nLotag - 900;

    nTrailPointVal[nPoint] = var_14;

    int field0 = sTrail[nTrail].field_0;

    if (field0 == -1)
    {
        sTrail[nTrail].field_0 = nPoint;
        sTrail[nTrail].field_4 = nPoint;

        nTrailPointNext[nPoint] = -1;
        nTrailPointPrev[nPoint] = -1;
    }
    else
    {
        int ecx = -1;

        while (field0 != -1)
        {
            if (nTrailPointVal[field0] > var_14)
            {
                nTrailPointPrev[nPoint] = nTrailPointPrev[field0];
                nTrailPointPrev[field0] = nPoint;
                nTrailPointNext[nPoint] = field0;

                if (field0 == sTrail[nTrail].field_0) {
                    sTrail[nTrail].field_0 = nPoint;
                }

                break;
            }

            ecx = field0;
            field0 = nTrailPointNext[field0];
        }

        if (field0 == -1)
        {
            nTrailPointNext[ecx] = nPoint;
            nTrailPointPrev[nPoint] = ecx;
            nTrailPointNext[nPoint] = -1;
            sTrail[nTrail].field_4 = nPoint;
        }
    }

    mydeletesprite(nSprite);
}

void AddMovingSector(int nSector, int edx, int ebx, int ecx)
{
    if (nMoveSects >= kMaxMoveSects) {
        bail2dos("Too many moving sectors\n");
    }

    CreatePushBlock(nSector);

    short nTrail = FindTrail(ebx);

    sMoveDir[nMoveSects] = 1;

    MoveSect *pMoveSect = &sMoveSect[nMoveSects];
    nMoveSects++;

    pMoveSect->nTrail = nTrail;
    pMoveSect->nTrailPoint = -1;
    pMoveSect->field_8 = -1;
    pMoveSect->field_6 = ecx;
    pMoveSect->field_10 = (edx / 1000) + 1;
    pMoveSect->nSector = nSector;

    if (ecx & 8)
    {
        pMoveSect->field_14 = runlist_AllocChannel(ebx % 1000);
    }
    else
    {
        pMoveSect->field_14 = -1;
    }

    sector[nSector].floorstat |= 0x40;
}

void DoMovingSects()
{
    for (int i = 0; i < nMoveSects; i++)
    {
        if (sMoveSect[i].nSector == -1) {
            continue;
        }

        if (sMoveSect[i].field_14 != -1 && !sRunChannels[sMoveSect[i].field_14].c) {
            continue;
        }

        short nSector = sMoveSect[i].nSector;
        short nBlock = sector[nSector].extra;

        BlockInfo *pBlockInfo = &sBlockInfo[nBlock];

        if (sMoveSect[i].nTrailPoint == -1)
        {
            if (sMoveSect[i].field_6 & 0x20)
            {
                runlist_ChangeChannel(sMoveSect[i].field_14, 0);
            }

            short ax;

            if (sMoveSect[i].field_6 & 0x10)
            {
                sMoveDir[i] = -sMoveDir[i];
                if (sMoveDir[i] > 0)
                {
                    ax = sTrail[sMoveSect[i].nTrail].field_0;
                }
                else
                {
                    ax = sTrail[sMoveSect[i].nTrail].field_4;
                }
            }
            else
            {
                ax = sTrail[sMoveSect[i].nTrail].field_0;
            }

            sMoveSect[i].nTrailPoint = ax;
        }

        short nTrail = sMoveSect[i].nTrailPoint;
//		TrailPoint *pTrail = &sTrailPoint[nTrail];

        // loc_23872:
        int nAngle = GetMyAngle(sTrailPoint[nTrail].x - pBlockInfo->x, sTrailPoint[nTrail].y - pBlockInfo->y);

        int nXVel = (Sin(nAngle + 512) << 4) * sMoveSect[i].field_10;
        int nYVel = (Sin(nAngle) << 4) * sMoveSect[i].field_10;

        int ebx = (sTrailPoint[nTrail].x - pBlockInfo->x) << 14;

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

        int edx = eax;
        eax = ebx;

        int ecx = (sTrailPoint[nTrail].y - pBlockInfo->y) << 14;

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

        // loc_238EC:
        if (edx <= eax)
        {
            eax = nYVel;
            if (eax < 0) {
                eax = -eax;
            }

            edx = eax;
            eax = ecx;

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

            if (edx > eax)
            {
                // loc_23908:
                nYVel = ecx;
                nXVel = ebx;

                if (sMoveDir[i] > 0)
                {
                    sMoveSect[i].nTrailPoint = nTrailPointNext[sMoveSect[i].nTrailPoint];
                }
                else
                {
                    sMoveSect[i].nTrailPoint = nTrailPointPrev[sMoveSect[i].nTrailPoint];
                }
            }
        }
        else
        {
            // repeat of code from loc_23908
            nYVel = ecx;
            nXVel = ebx;

            if (sMoveDir[i] > 0)
            {
                sMoveSect[i].nTrailPoint = nTrailPointNext[sMoveSect[i].nTrailPoint];
            }
            else
            {
                sMoveSect[i].nTrailPoint = nTrailPointPrev[sMoveSect[i].nTrailPoint];
            }
        }

        // loc_2393A:
        if (sMoveSect[i].field_8 != -1)
        {
            MoveSector(sMoveSect[i].field_8, -1, &nXVel, &nYVel);
        }

        int var_2C = nXVel;
        int var_30 = nYVel;

        MoveSector(nSector, -1, &nXVel, &nYVel);

        if (nXVel != var_2C || nYVel != var_30)
        {
            MoveSector(sMoveSect[i].field_8, -1, &var_2C, &var_30);
            MoveSector(sMoveSect[i].field_8, -1, &nXVel, &nYVel);
        }
    }
}

void PostProcess()
{
    int i, j;

    for (i = 0; i < nMoveSects; i++)
    {
        int nTrail = sMoveSect[i].nTrail;
        sMoveSect[i].nTrailPoint = sTrail[nTrail].field_0;

        if (sMoveSect[i].field_6 & 0x40) {
            runlist_ChangeChannel(sMoveSect[i].field_14, 1);
        }

        short nSector = sMoveSect[i].nSector;

        if (SectFlag[nSector] & kSectUnderwater)
        {
            sector[nSector].ceilingstat |= 0x40;
            sector[nSector].floorstat &= 0xBFFF;

            for (j = 0; j < nMoveSects; j++)
            {
                if (j != i && sMoveSect[i].nTrail == sMoveSect[j].nTrail)
                {
                    sMoveSect[j].field_8 = sMoveSect[i].nSector;

                    SnapSectors(sMoveSect[j].nSector, sMoveSect[i].nSector, 0);
                    sMoveSect[i].nSector = -1;
                }
            }
        }
    }

    for (i = 0; i < nBobs; i++)
    {
        if (sBob[i].field_3 == 0)
        {
            int bobID = sBobID[i];

            for (j = 0; j < nBobs; j++)
            {
                if (j != i)
                {
                    if (sBob[i].field_3 != 0 && sBobID[j] == bobID) {
                        SnapSectors(i, j, 0);
                    }
                }
            }
        }
    }

    if (levelnew != kMap20)
    {
        // esi is i
        for (i = 0; i < numsectors; i++)
        {
            int var_20 = 30000;

            if (SectSpeed[i] && SectDepth[i] && !(SectFlag[i] & kSectLava))
            {
                SectSoundSect[i] = i;
                SectSound[i] = StaticSound[kSoundAmbientWater];
            }
            else
            {
                // ebp and ecx are j
                for (j = 0; j < numsectors; j++)
                {
                    // loc_23CA6:

                    if (i != j && SectSpeed[j] && !(SectFlag[i] & kSectLava))
                    {
                        int xVal = wall[sector[i].wallptr].x - wall[sector[j].wallptr].x;
                        if (xVal < 0) {
                            xVal = -xVal;
                        }

                        int yVal = wall[sector[i].wallptr].x - wall[sector[j].wallptr].x;
                        if (yVal < 0) {
                            yVal = -yVal;
                        }

                        if (xVal < 15000 && yVal < 15000 && (xVal + yVal < var_20))
                        {
                            var_20 = xVal + yVal;
                            SectSoundSect[i] = j;
                            SectSound[i] = StaticSound[kSoundAmbientWater];
                        }
                    }
                }
            }
        }
    }
    else // nMap == kMap20)
    {
        //		int var_24 = 0;
        int ebp = 0;

        for (i = 0; i < numsectors; i++)
        {
            SectSoundSect[i] = i;
            SectSound[i] = StaticSound[kSoundShip1];

            int startwall = sector[ebp].wallptr;
            int endwall = sector[ebp].wallptr + sector[ebp].wallnum;

            int nWall = startwall;

            while (nWall < endwall)
            {
                if (wall[nWall].picnum == kTile3603)
                {
                    wall[nWall].pal = 1;
                    int nSprite = insertsprite(i, 407);
                    sprite[nSprite].cstat = 0x8000;
                }

                nWall++;
            }
        }

        for (i = 0; i < kMaxSprites; i++)
        {
            if (sprite[i].statnum < kMaxStatus && sprite[i].picnum == kTile3603)
            {
                changespritestat(i, 407);
                sprite[i].pal = 1;
            }
        }
    }

    // esi is i
    for (i = 0; i < ObjectCount; i++)
    {
        int nObjectSprite = ObjectList[i].nSprite;

        if (sprite[nObjectSprite].statnum == kStatExplodeTarget)
        {
            if (!ObjectList[i].field_10) {
                ObjectList[i].field_10 = -1;
            }
            else
            {
                int edi = ObjectList[i].field_10;
                ObjectList[i].field_10 = -1;

                // ecx, ebx is j
                for (j = 0; j < ObjectCount; j++)
                {
                    if (i != j && sprite[ObjectList[j].nSprite].statnum == kStatExplodeTarget && edi == ObjectList[j].field_10)
                    {
                        ObjectList[i].field_10 = j;
                        ObjectList[j].field_10 = i;
                    }
                }
            }
        }
    }
}

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

void ObjectLoadSave::Load()
{
    Read(&nTraps, sizeof(nTraps));

    Read(&ElevCount, sizeof(ElevCount));
    Read(&WallFaceCount, sizeof(WallFaceCount));
    Read(&nTrailPoints, sizeof(nTrailPoints));
    Read(&nEnergyBlocks, sizeof(nEnergyBlocks));
    Read(&nMoveSects, sizeof(nMoveSects));
    Read(&nTrails, sizeof(nTrails));
    Read(&SlideCount, sizeof(SlideCount));

    Read(&nFinaleSpr, sizeof(nFinaleSpr));
    Read(&ObjectCount, sizeof(ObjectCount));
    Read(&nDrips, sizeof(nDrips));
    Read(&nBobs, sizeof(nBobs));
    Read(&nDronePitch, sizeof(nDronePitch));
    Read(&nSmokeSparks, sizeof(nSmokeSparks));

    Read(&nFinaleStage, sizeof(nFinaleStage));
    Read(&lFinaleStart, sizeof(lFinaleStart));

    Read(sTrap, sizeof(sTrap[0]) * nTraps);
    Read(sBob, sizeof(sBob[0]) * nBobs);
    Read(sTrail, sizeof(sTrail[0]) * nTrails);
    Read(sTrailPoint, sizeof(sTrailPoint[0]) * nTrailPoints);
    Read(Elevator, sizeof(Elevator[0]) * ElevCount);
    Read(ObjectList, sizeof(ObjectList[0]) * ObjectCount);
    Read(sMoveSect, sizeof(sMoveSect[0]) * nMoveSects);
    Read(SlideData, sizeof(SlideData[0]) * SlideCount);
    Read(SlideData2, sizeof(SlideData2[0]) * SlideCount);
    Read(SlideFree, sizeof(SlideFree));
    Read(sMoveDir, sizeof(sMoveDir[0]) * nMoveSects);
    Read(WallFace, sizeof(WallFace[0]) * WallFaceCount);

    Read(PointFree, sizeof(PointFree));
    Read(nTrailPointVal, sizeof(nTrailPointVal[0]) * nTrailPoints);
    Read(nTrailPointPrev, sizeof(nTrailPointPrev[0]) * nTrailPoints);
    Read(nTrailPointNext, sizeof(nTrailPointNext[0]) * nTrailPoints);
    Read(sDrip, sizeof(sDrip[0]) * nDrips);

    Read(SectSoundSect, sizeof(SectSoundSect[0]) * kMaxSectors);
    Read(SectSound, sizeof(SectSound[0]) * kMaxSectors);
    Read(SectFlag, sizeof(SectFlag[0]) * kMaxSectors);
    Read(SectDepth, sizeof(SectDepth[0]) * kMaxSectors);
    Read(SectAbove, sizeof(SectAbove[0]) * kMaxSectors);
    Read(SectDamage, sizeof(SectDamage[0]) * kMaxSectors);
    Read(SectSpeed, sizeof(SectSpeed[0]) * kMaxSectors);
    Read(SectBelow, sizeof(SectBelow[0]) * kMaxSectors);
}

void ObjectLoadSave::Save()
{
    Write(&nTraps, sizeof(nTraps));

    Write(&ElevCount, sizeof(ElevCount));
    Write(&WallFaceCount, sizeof(WallFaceCount));
    Write(&nTrailPoints, sizeof(nTrailPoints));
    Write(&nEnergyBlocks, sizeof(nEnergyBlocks));
    Write(&nMoveSects, sizeof(nMoveSects));
    Write(&nTrails, sizeof(nTrails));
    Write(&SlideCount, sizeof(SlideCount));

    Write(&nFinaleSpr, sizeof(nFinaleSpr));
    Write(&ObjectCount, sizeof(ObjectCount));
    Write(&nDrips, sizeof(nDrips));
    Write(&nBobs, sizeof(nBobs));
    Write(&nDronePitch, sizeof(nDronePitch));
    Write(&nSmokeSparks, sizeof(nSmokeSparks));

    Write(&nFinaleStage, sizeof(nFinaleStage));
    Write(&lFinaleStart, sizeof(lFinaleStart));

    Write(sTrap, sizeof(sTrap[0]) * nTraps);
    Write(sBob, sizeof(sBob[0]) * nBobs);
    Write(sTrail, sizeof(sTrail[0]) * nTrails);
    Write(sTrailPoint, sizeof(sTrailPoint[0]) * nTrailPoints);
    Write(Elevator, sizeof(Elevator[0]) * ElevCount);
    Write(ObjectList, sizeof(ObjectList[0]) * ObjectCount);
    Write(sMoveSect, sizeof(sMoveSect[0]) * nMoveSects);
    Write(SlideData, sizeof(SlideData[0]) * SlideCount);
    Write(SlideData2, sizeof(SlideData2[0]) * SlideCount);
    Write(SlideFree, sizeof(SlideFree));
    Write(sMoveDir, sizeof(sMoveDir[0]) * nMoveSects);
    Write(WallFace, sizeof(WallFace[0]) * WallFaceCount);

    Write(PointFree, sizeof(PointFree));
    Write(nTrailPointVal, sizeof(nTrailPointVal[0]) * nTrailPoints);
    Write(nTrailPointPrev, sizeof(nTrailPointPrev[0]) * nTrailPoints);
    Write(nTrailPointNext, sizeof(nTrailPointNext[0]) * nTrailPoints);
    Write(sDrip, sizeof(sDrip[0]) * nDrips);

    Write(SectSoundSect, sizeof(SectSoundSect[0]) * kMaxSectors);
    Write(SectSound,     sizeof(SectSound[0])     * kMaxSectors);
    Write(SectFlag,      sizeof(SectFlag[0])      * kMaxSectors);
    Write(SectDepth,     sizeof(SectDepth[0])     * kMaxSectors);
    Write(SectAbove,     sizeof(SectAbove[0])     * kMaxSectors);
    Write(SectDamage,    sizeof(SectDamage[0])    * kMaxSectors);
    Write(SectSpeed,     sizeof(SectSpeed[0])     * kMaxSectors);
    Write(SectBelow,     sizeof(SectBelow[0])     * kMaxSectors);
}

static ObjectLoadSave* myLoadSave;

void ObjectLoadSaveConstruct()
{
    myLoadSave = new ObjectLoadSave();
}


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

#define kMaxPoints	1024
#define kMaxSlides	128
#define kMaxElevs	1024

enum kStatus
{
    kStatDestructibleSprite = 97,
    kStatAnubisDrum,
    kStatExplodeTrigger = 141,
    kStatExplodeTarget = 152
};

extern short nSmokeSparks;
extern short nDronePitch;
extern int lFinaleStart;
extern short nFinaleSpr;

void InitObjects();
void InitElev();
void InitPoint();
void InitSlide();
void InitWallFace();
void DoDrips();
void DoMovingSects();
void DoFinale();
void PostProcess();

void FuncElev(int, int, int);
void FuncWallFace(int, int, int);
void FuncSlide(int, int, int);
void FuncObject(int, int, int);
void FuncTrap(int, int, int);
void FuncEnergyBlock(int, int, int);
void FuncSpark(int, int, int);

void SnapBobs(short nSectorA, short nSectorB);
short FindWallSprites(short nSector);
void AddMovingSector(int nSector, int edx, int ebx, int ecx);
int BuildWallSprite(int nSector);
void ProcessTrailSprite(int nSprite, int nLotag, int nHitag);
void AddSectorBob(int nSector, int nHitag, int bx);
int BuildObject(short nSprite, int nOjectType, int nHitag);
int BuildArrow(int nSprite, int nVal);
int BuildFireBall(int nSprite, int a, int b);
void BuildDrip(int nSprite);
int BuildEnergyBlock(short nSector);
int BuildElevC(int arg1, int nChannel, int nSector, int nWallSprite, int arg5, int arg6, int nCount, ...);
int BuildElevF(int nChannel, int nSector, int nWallSprite, int arg_4, int arg_5, int nCount, ...);
int BuildWallFace(short nChannel, short nWall, short nCount, ...);
int BuildSlide(int nChannel, int edx, int ebx, int ecx, int arg1, int arg2, int arg3);

#endif