//------------------------------------------------------------------------- /* 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 #include #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].nMoves); 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].nMoves); 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*)§or[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 = §or[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 | 0x70000; } 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 | 0x80000; } 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].nMoves); 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 | 0x1F0000); sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nTrap | 0x1F0000); // 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 | 0x1F0000; } 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 | 0x1F0000; } 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 | 0x1F0000; } 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) { short nBulletSprite = nBullet & 0xFFFF; // isolate the sprite index (disregard top 16 bits) 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 var_14 = insertsprite(sprite[nSprite].sectnum, 0); if (var_14 < 0) { return -1; } assert(var_14 < kMaxSprites); sprite[var_14].x = sprite[nSprite].x; sprite[var_14].y = sprite[nSprite].y; sprite[var_14].cstat = 0; sprite[var_14].shade = -127; sprite[var_14].pal = 1; sprite[var_14].xoffset = 0; sprite[var_14].yoffset = 0; sprite[var_14].xrepeat = 50; sprite[var_14].yrepeat = 50; if (nVal >= 2) { sprite[var_14].picnum = kEnergy2; nSmokeSparks++; if (nVal == 3) { sprite[var_14].xrepeat = 120; sprite[var_14].yrepeat = 120; } else { sprite[var_14].xrepeat = sprite[nSprite].xrepeat + 15; sprite[var_14].yrepeat = sprite[nSprite].xrepeat + 15; } } else { int nAngle = (sprite[nSprite].ang + 256) - RandomSize(9); if (nVal) { sprite[var_14].xvel = Cos(nAngle) >> 5; sprite[var_14].yvel = Sin(nAngle) >> 5; } else { sprite[var_14].xvel = Cos(nAngle) >> 6; sprite[var_14].yvel = Sin(nAngle) >> 6; } sprite[var_14].zvel = -(RandomSize(4) << 7); sprite[var_14].picnum = kTile985 + nVal; } sprite[var_14].z = sprite[nSprite].z; sprite[var_14].lotag = runlist_HeadRun() + 1; sprite[var_14].clipdist = 1; sprite[var_14].hitag = 0; // GrabTimeSlot(3); sprite[var_14].extra = -1; sprite[var_14].owner = runlist_AddRunRec(sprite[var_14].lotag - 1, var_14 | 0x260000); sprite[var_14].hitag = runlist_AddRunRec(NewRun, var_14 | 0x260000); return var_14; } 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 | 0x250000); nEnergyBlocks++; return nSprite | 0x250000; } // 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 | 0x170000); // 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 | 0x170000); 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 | 0x170000; } // 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(); }