//------------------------------------------------------------------------- /* 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 "ns.h" #include "engine.h" #include "aistuff.h" #include "exhumed.h" #include "view.h" #include "sound.h" #include "names.h" #include "sequence.h" #include "player.h" #include "interpolate.h" #include "mapinfo.h" #include #include BEGIN_PS_NS 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; }; struct TrailPoint { int x; int y; char nTrailPointVal; short nTrailPointPrev; short nTrailPointNext; }; struct Bob { short nSector; char field_2; char field_3; int z; short sBobID; }; 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? short sMoveDir; }; 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 field_4; short field_6[8]; }; struct slideData { DExhumedActor* pActor; short nChannel; short nStart; short field_4a; short field_8a; int field_0; int field_4; int field_8; int field_C; int x1; int y1; int x2; int y2; int x3; int y3; int x4; int y4; int x5; int y5; int x6; int y6; }; struct Point { short nSector; short field_2; short field_4; short field_6; short field_8; short field_A; short field_C; short nNext; }; struct Trap { DExhumedActor* pActor; short field_0; short nType; short field_6; short field_8; short field_A; short field_C; short field_E; short nTrapInterval; }; TArray PointList; TArray sTrap; TArray sBob; TArray sTrail; TArray sTrailPoint; TArray Elevator; TArray ObjectList; TArray sMoveSect; TArray SlideData; TArray WallFace; TArray sDrip; TArray EnergyBlocks; int lFinaleStart; short nFinaleStage; DExhumedActor* pFinaleSpr; short nDronePitch = 0; short nSmokeSparks = 0; FSerializer& Serialize(FSerializer& arc, const char* keyname, Trail& w, Trail* def) { if (arc.BeginObject(keyname)) { arc("at0", w.field_0) ("at2", w.field_2) ("at4", w.field_4) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, TrailPoint& w, TrailPoint* def) { if (arc.BeginObject(keyname)) { arc("x", w.x) ("y", w.y) ("val", w.nTrailPointVal) ("next", w.nTrailPointNext) ("prev", w.nTrailPointPrev) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, Bob& w, Bob* def) { if (arc.BeginObject(keyname)) { arc("sector", w.nSector) ("at2", w.field_2) ("at3", w.field_3) ("z", w.z) ("id", w.sBobID) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, Drip& w, Drip* def) { if (arc.BeginObject(keyname)) { arc("sprite", w.nSprite) ("at2", w.field_2) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, Elev& w, Elev* def) { if (arc.BeginObject(keyname)) { arc("at0", w.field_0) ("channel", w.nChannel) ("sector", w.nSector) ("at6", w.field_6) ("ata", w.field_A) ("countz", w.nCountZOffsets) ("curz", w.nCurZOffset) .Array("zofs", w.zOffsets, 8) ("at32", w.field_32) ("sprite", w.nSprite) ("at36", w.field_36) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, MoveSect& w, MoveSect* def) { if (arc.BeginObject(keyname)) { arc("sector", w.nSector) ("trail", w.nTrail) ("trailpoint", w.nTrailPoint) ("at6", w.field_6) ("at8", w.field_8) ("at10", w.field_10) ("at14", w.field_14) ("movedir", w.sMoveDir) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, Object& w, Object* def) { if (arc.BeginObject(keyname)) { arc("at0", w.field_0) ("health", w.nHealth) ("at4", w.field_4) ("at8", w.field_8) ("sprite", w.nSprite) ("at8", w.field_8) ("at10", w.field_10) ("at12", w.field_12) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, wallFace& w, wallFace* def) { if (arc.BeginObject(keyname)) { arc("channel", w.nChannel) ("wall", w.nWall) ("at4", w.field_4) .Array("at6", w.field_6, 8) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, slideData& w, slideData* def) { if (arc.BeginObject(keyname)) { arc("at0", w.field_0) ("at4", w.field_4) ("at8", w.field_8) ("atc", w.field_C) ("x1", w.x1) ("y1", w.y1) ("x2", w.x2) ("y2", w.y2) ("at20", w.x3) ("at24", w.y3) ("at28", w.x4) ("at2c", w.y4) ("at30", w.x5) ("at34", w.y5) ("at38", w.x6) ("at3c", w.y6) ("channel", w.nChannel) ("at2a", w.nStart) ("at4a", w.field_4a) ("at6a", w.pActor) ("at8a", w.field_8a) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, Point& w, Point* def) { if (arc.BeginObject(keyname)) { arc("at0", w.nSector) ("at2", w.field_2) ("at4", w.field_4) ("at6", w.field_6) ("at8", w.field_8) ("ata", w.field_A) ("atc", w.field_C) ("ate", w.nNext) .EndObject(); } return arc; } FSerializer& Serialize(FSerializer& arc, const char* keyname, Trap& w, Trap* def) { if (arc.BeginObject(keyname)) { arc("at0", w.field_0) ("sprite", w.pActor) ("type", w.nType) ("at6", w.field_6) ("at8", w.field_8) ("ata", w.field_A) ("atc", w.field_C) ("ate", w.field_E) ("interval", w.nTrapInterval) .EndObject(); } return arc; } void SerializeObjects(FSerializer& arc) { if (arc.BeginObject("objects")) { arc("points", PointList) ("traps", sTrap) ("bob", sBob) ("trails", sTrail) ("trailpoints", sTrailPoint) ("elevators", Elevator) ("objects", ObjectList) ("movesect", sMoveSect) ("slides", SlideData) ("wallface", WallFace) ("drips", sDrip) ("finalestart", lFinaleStart) ("energyblocks", EnergyBlocks) ("finalestage", nFinaleStage) ("finalespr", pFinaleSpr) ("dronepitch", nDronePitch) ("smokesparks", nSmokeSparks) .EndObject(); } } // done void InitObjects() { sTrap.Clear(); sBob.Clear(); sTrail.Clear(); sTrailPoint.Clear(); ObjectList.Clear(); sMoveSect.Clear(); sDrip.Clear(); EnergyBlocks.Clear(); nDronePitch = 0; nFinaleStage = 0; lFinaleStart = 0; nSmokeSparks = 0; } // done void InitElev() { Elevator.Clear(); } // done DExhumedActor* 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; auto pActor = insertActor(nSector, 401); auto pSprite = &pActor->s(); pSprite->x = (x + x2) / 2; pSprite->y = (y + y2) / 2; pSprite->z = (sector[nSector].floorz + sector[nSector].ceilingz) / 2; pSprite->cstat = 0x8000; return pActor; } // done 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; DExhumedActor* pAct = nullptr; ExhumedSpriteIterator it; while (auto actor = it.Next()) { auto spr = &actor->s(); if (spr->lotag == 0) { if ((spr->cstat & 0x50) == 80) { int var_28 = spr->x; int ebx = spr->y; if ((var_28 >= var_24) && (esi >= var_28) && (ebx >= ecx) && (ebx <= edi)) { actor->pTarget = pAct; pAct = actor; } } } } if (pAct == nullptr) { pAct = insertActor(nSector, 401); auto pSprite = &pAct->s(); pSprite->x = (var_24 + esi) / 2; pSprite->y = (ecx + edi) / 2; pSprite->z = sector[nSector].floorz; pSprite->cstat = 0x8000; pSprite->owner = -1; pSprite->lotag = 0; pSprite->hitag = 0; } return pAct->GetSpriteIndex(); } int BuildElevF(int nChannel, int nSector, int nWallSprite, int arg_4, int arg_5, int nCount, ...) { auto ElevCount = Elevator.Reserve(1); Elevator[ElevCount].field_0 = 2; Elevator[ElevCount].field_6 = arg_4; Elevator[ElevCount].field_32 = -1; Elevator[ElevCount].field_A = arg_5; Elevator[ElevCount].nChannel = nChannel; Elevator[ElevCount].nSector = nSector; Elevator[ElevCount].nCountZOffsets = 0; Elevator[ElevCount].nCurZOffset = 0; Elevator[ElevCount].field_36 = 0; if (nWallSprite < 0) { nWallSprite = BuildWallSprite(nSector)->GetSpriteIndex(); } Elevator[ElevCount].nSprite = nWallSprite; va_list zlist; va_start(zlist, nCount); while (1) { if (Elevator[ElevCount].nCountZOffsets >= nCount) { return ElevCount; } int nVal = Elevator[ElevCount].nCountZOffsets; Elevator[ElevCount].nCountZOffsets++; Elevator[ElevCount].zOffsets[nVal] = va_arg(zlist, int); } va_end(zlist); return ElevCount; } int BuildElevC(int arg1, int nChannel, int nSector, int nWallSprite, int arg5, int arg6, int nCount, ...) { int edi = arg5; auto ElevCount = Elevator.Reserve(1); Elevator[ElevCount].field_0 = arg1; if (arg1 & 4) { edi = arg5 / 2; } Elevator[ElevCount].field_6 = edi; Elevator[ElevCount].nCountZOffsets = 0; Elevator[ElevCount].field_36 = 0; Elevator[ElevCount].nCurZOffset = 0; Elevator[ElevCount].field_A = arg6; Elevator[ElevCount].field_32 = -1; Elevator[ElevCount].nChannel = nChannel; Elevator[ElevCount].nSector = nSector; if (nWallSprite < 0) { nWallSprite = BuildWallSprite(nSector)->GetSpriteIndex(); } Elevator[ElevCount].nSprite = nWallSprite; va_list zlist; va_start(zlist, nCount); while (1) { if (Elevator[ElevCount].nCountZOffsets >= nCount) { return ElevCount; } int nVal = Elevator[ElevCount].nCountZOffsets; Elevator[ElevCount].nCountZOffsets++; Elevator[ElevCount].zOffsets[nVal] = va_arg(zlist, int); } va_end(zlist); return ElevCount; } // 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; } // done int CheckSectorSprites(short nSector, int nVal) { int b = 0; if (nVal) { int nZDiff = sector[nSector].floorz - sector[nSector].ceilingz; ExhumedSectIterator it(nSector); while (auto pActor= it.Next()) { auto pSprite = &pActor->s(); if ((pSprite->cstat & 0x101) && (nZDiff < GetActorHeight(pActor))) { if (nVal != 1) { return 1; } b = 1; runlist_DamageEnemy(pActor->GetSpriteIndex(), -1, 5); if (pSprite->statnum == 100 && PlayerList[GetPlayerFromActor(pActor)].nHealth <= 0) { PlayFXAtXYZ(StaticSound[kSoundJonFDie], pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, CHANF_NONE, 0x4000); } } } } else { ExhumedSectIterator it(nSector); while (auto pActor = it.Next()) { if (pActor->s().cstat & 0x101) { return 1; } } b = 0; } return b; } // done void MoveSectorSprites(int nSector, int z) { int newz = sector[nSector].floorz; int oldz = newz - z; int minz = std::min(newz, oldz); int maxz = std::max(newz, oldz); ExhumedSectIterator it(nSector); while (auto pActor = it.Next()) { auto pSprite = &pActor->s(); int z = pSprite->z; if ((pSprite->statnum != 200 && z >= minz && z <= maxz) || pSprite->statnum >= 900) { pSprite->z = newz; } } } void StartElevSound(short nSprite, int nVal) { short nSound; if (nVal & 2) { nSound = nElevSound; } else { nSound = nStoneSound; } D3PlayFX(StaticSound[nSound], nSprite); } void AIElev::ProcessChannel(RunListEvent* ev) { int nRun = ev->nRun; short nElev = RunData[nRun].nObjIndex; assert(nElev >= 0 && nElev < (int)Elevator.Size()); short nChannel = Elevator[nElev].nChannel; short var_18 = Elevator[nElev].field_0; assert(nChannel >= 0 && nChannel < kMaxChannels); // short ax = var_18 & 8; short dx = sRunChannels[nChannel].c; int edi = 999; // 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]); 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; } } } } assert(edi != 999); // loc_20DF9: if (edi) { if (Elevator[nElev].field_32 < 0) { Elevator[nElev].field_32 = runlist_AddRunRec(NewRun, &RunData[nRun]); 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; } } } void AIElev::Tick(RunListEvent* ev) { int nRun = ev->nRun; short nElev = RunData[nRun].nObjIndex; assert(nElev >= 0 && nElev < (int)Elevator.Size()); short nChannel = Elevator[nElev].nChannel; short var_18 = Elevator[nElev].field_0; assert(nChannel >= 0 && nChannel < kMaxChannels); short nSector = Elevator[nElev].nSector; auto pElevSpr = &exhumedActors[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; StartInterpolation(nSector, Interp_Sect_Floorz); 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(pElevSpr->GetSpriteIndex(), var_18); } else { StopActorSound(pElevSpr); 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* cursect = §or[nSector]; int nZOffset = Elevator[nElev].nCurZOffset; int zVal = Elevator[nElev].zOffsets[nZOffset]; StartInterpolation(nSector, Interp_Sect_Ceilingz); 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(pElevSpr, 30); } auto sp = &pElevSpr->s(); PlayFXAtXYZ(StaticSound[kSound26], sp->x, sp->y, sp->z, sp->sectnum); } if (var_18 & 0x4) { if (CheckSectorSprites(nSector, 1)) { return; } } else { if (CheckSectorSprites(nSector, 0)) { runlist_ChangeChannel(nChannel, sRunChannels[nChannel].c == 0); return; } } } StartInterpolation(nSector, Interp_Sect_Ceilingz); cursect->ceilingz = ceilZ; } // maybe this doesn't go here? while (pElevSpr) { pElevSpr->s().z += ebp; pElevSpr = pElevSpr->pTarget; } } void FuncElev(int nObject, int nMessage, int nDamage, int nRun) { AIElev ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } // done void InitWallFace() { WallFace.Clear(); } int BuildWallFace(short nChannel, short nWall, int nCount, ...) { auto WallFaceCount = WallFace.Reserve(1); WallFace[WallFaceCount].field_4 = 0; WallFace[WallFaceCount].nWall = nWall; WallFace[WallFaceCount].nChannel = nChannel; if (nCount > 8) { nCount = 8; } va_list piclist; va_start(piclist, nCount); while (WallFace[WallFaceCount].field_4 < nCount) { int i = WallFace[WallFaceCount].field_4; WallFace[WallFaceCount].field_4++; WallFace[WallFaceCount].field_6[i] = (short)va_arg(piclist, int); } va_end(piclist); return WallFaceCount; } void AIWallFace::ProcessChannel(RunListEvent* ev) { int nWallFace = RunData[ev->nRun].nObjIndex; assert(nWallFace >= 0 && nWallFace < (int)WallFace.Size()); short nChannel = WallFace[nWallFace].nChannel; short si = sRunChannels[nChannel].c; if ((si <= WallFace[nWallFace].field_4) && (si >= 0)) { wall[WallFace[nWallFace].nWall].picnum = WallFace[nWallFace].field_6[si]; } } void FuncWallFace(int nObject, int nMessage, int nDamage, int nRun) { AIWallFace ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } // done void InitPoint() { PointList.Clear(); } // done int GrabPoint() { return PointList.Reserve(1); } // done void InitSlide() { SlideData.Clear(); } 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 nWall1, int ecx, int nWall2, int nWall3, int nWall4) { auto nSlide = SlideData.Reserve(1); short nSector = IdentifySector(nStartWall); SlideData[nSlide].field_4a = -1; SlideData[nSlide].nChannel = nChannel; SlideData[nSlide].nStart = -1; int nPoint = GrabPoint(); SlideData[nSlide].nStart = nPoint; PointList[nPoint].nNext = -1; PointList[nPoint].nSector = nSector; short startwall = sector[nSector].wallptr; short endwall = startwall + sector[nSector].wallnum; for (int nWall = startwall; nWall < endwall; nWall++) { short ax = SlideData[nSlide].nStart; if (ax >= 0) { while (ax >= 0) { if (wall[nWall].nextsector == PointList[ax].nSector) { break; } ax = PointList[ax].nNext; } } else { if (wall[nWall].nextsector >= 0) { nPoint = GrabPoint(); PointList[nPoint].nNext = SlideData[nSlide].nStart; PointList[nPoint].nSector = wall[nWall].nextsector; SlideData[nSlide].nStart = nPoint; } } } SlideData[nSlide].field_0 = nStartWall; SlideData[nSlide].field_4 = nWall1; 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].x3 = wall[nWall1].x; SlideData[nSlide].y3 = wall[nWall1].y; SlideData[nSlide].x4 = wall[nWall3].x; SlideData[nSlide].y4 = wall[nWall3].y; SlideData[nSlide].x5 = wall[ecx].x; SlideData[nSlide].y5 = wall[ecx].y; SlideData[nSlide].x6 = wall[nWall4].x; SlideData[nSlide].y6 = wall[nWall4].y; StartInterpolation(nStartWall, Interp_Wall_X); StartInterpolation(nStartWall, Interp_Wall_Y); StartInterpolation(nWall1, Interp_Wall_X); StartInterpolation(nWall1, Interp_Wall_Y); StartInterpolation(nWall2, Interp_Wall_X); StartInterpolation(nWall2, Interp_Wall_Y); StartInterpolation(nWall3, Interp_Wall_X); StartInterpolation(nWall3, Interp_Wall_Y); auto pActor = insertActor(nSector, 899); auto pSprite = &pActor->s(); SlideData[nSlide].pActor = pActor; pSprite->cstat = 0x8000; pSprite->x = wall[nStartWall].x; pSprite->y = wall[nStartWall].y; pSprite->z = sector[nSector].floorz; pSprite->backuppos(); SlideData[nSlide].field_8a = 0; return nSlide; } void AISlide::ProcessChannel(RunListEvent* ev) { int nRun = ev->nRun; int nSlide = RunData[nRun].nObjIndex; assert(nSlide >= 0 && nSlide < (int)SlideData.Size()); short nChannel = SlideData[nSlide].nChannel; if (SlideData[nSlide].field_4a >= 0) { runlist_SubRunRec(SlideData[nSlide].field_4a); SlideData[nSlide].field_4a = -1; } if (sRunChannels[nChannel].c && sRunChannels[nChannel].c != 1) { return; } SlideData[nSlide].field_4a = runlist_AddRunRec(NewRun, &RunData[nRun]); if (SlideData[nSlide].field_8a != sRunChannels[nChannel].c) { D3PlayFX(StaticSound[kSound23], SlideData[nSlide].pActor); SlideData[nSlide].field_8a = sRunChannels[nChannel].c; } } void AISlide::Tick(RunListEvent* ev) { int nRun = ev->nRun; int nSlide = RunData[nRun].nObjIndex; assert(nSlide >= 0 && nSlide < (int)SlideData.Size()); short nChannel = SlideData[nSlide].nChannel; int ebp = 0; short cx = sRunChannels[nChannel].c; 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].x5, 20, 20); int var_34 = nSeekA; int var_20 = nSeekA; int nSeekB = LongSeek(&y, SlideData[nSlide].y5, 20, 20); int var_2C = nSeekB; int var_24 = nSeekB; dragpoint(SlideData[nSlide].field_4, x, y, 0); movesprite(SlideData[nSlide].pActor, 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].x6, 20, 20); int var_30 = nSeekC; var_20 = nSeekC; int nSeekD = LongSeek(&y, SlideData[nSlide].y6, 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(SlideData[nSlide].field_4a); SlideData[nSlide].field_4a = -1; D3PlayFX(StaticSound[nStopSound], SlideData[nSlide].pActor); runlist_ReadyChannel(nChannel); } } void FuncSlide(int nObject, int nMessage, int nDamage, int nRun) { AISlide ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } int BuildTrap(DExhumedActor* pActor, int edx, int ebx, int ecx) { auto pSprite = &pActor->s(); int var_14 = edx; int var_18 = ebx; int var_10 = ecx; int nTrap = sTrap.Reserve(1); ChangeActorStat(pActor, 0); pSprite->cstat = 0x8000; pSprite->xvel = 0; pSprite->yvel = 0; pSprite->zvel = 0; pSprite->extra = -1; pSprite->lotag = runlist_HeadRun() + 1; pSprite->hitag = runlist_AddRunRec(NewRun, nTrap, 0x1F0000); pSprite->owner = runlist_AddRunRec(pSprite->lotag - 1, nTrap, 0x1F0000); // GrabTimeSlot(3); sTrap[nTrap].pActor = pActor; sTrap[nTrap].nType = (var_14 == 0) + 14; sTrap[nTrap].field_0 = -1; sTrap[nTrap].nTrapInterval = 64 - (2 * var_10); if (sTrap[nTrap].nTrapInterval < 5) { sTrap[nTrap].nTrapInterval = 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 = pSprite->sectnum; short nWall = sector[nSector].wallptr; int i = 0; while (1) { if (sector[nSector].wallnum >= i) { break; } if (var_18 == wall[nWall].hitag) { if (sTrap[nTrap].field_6 != -1) { sTrap[nTrap].field_8 = nWall; sTrap[nTrap].field_C = wall[nWall].picnum; break; } else { sTrap[nTrap].field_6 = nWall; sTrap[nTrap].field_A = wall[nWall].picnum; } } ecx++; nWall++; } pSprite->backuppos(); return nTrap; } void AITrap::ProcessChannel(RunListEvent* ev) { short nChannel = ev->nParam & 0x3FFF; short nTrap = RunData[ev->nRun].nObjIndex; if (sRunChannels[nChannel].c > 0) { sTrap[nTrap].field_0 = 12; } else { sTrap[nTrap].field_0 = -1; } } void AITrap::Tick(RunListEvent* ev) { short nTrap = RunData[ev->nRun].nObjIndex; auto pActor = sTrap[nTrap].pActor; auto pSprite = &pActor->s(); 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 = sTrap[nTrap].nTrapInterval; 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; } auto pBullet = BuildBullet(pActor, nType, 0, pSprite->ang, nullptr, 1); if (pBullet) { if (nType == 15) { pBullet->s().ang = (pBullet->s().ang - 512) & kAngleMask; D3PlayFX(StaticSound[kSound32], pBullet); } else { pBullet->s().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[kSound36], pBullet); } } } } } void FuncTrap(int nObject, int nMessage, int nDamage, int nRun) { AITrap ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } int BuildArrow(DExhumedActor* nSprite, int nVal) { return BuildTrap(nSprite, 0, -1, nVal); } int BuildFireBall(DExhumedActor* nSprite, int a, int b) { return BuildTrap(nSprite, 1, a, b); } DExhumedActor* BuildSpark(DExhumedActor* pActor, int nVal) { auto pSprite = &pActor->s(); auto pSpark = insertActor(pSprite->sectnum, 0); auto spr = &pSpark->s(); spr->x = pSprite->x; spr->y = pSprite->y; spr->cstat = 0; spr->shade = -127; spr->pal = 1; spr->xoffset = 0; spr->yoffset = 0; spr->xrepeat = 50; spr->yrepeat = 50; if (nVal >= 2) { spr->picnum = kEnergy2; nSmokeSparks++; if (nVal == 3) { spr->xrepeat = 120; spr->yrepeat = 120; } else { spr->xrepeat = pSprite->xrepeat + 15; spr->yrepeat = pSprite->xrepeat + 15; } } else { int nAngle = (pSprite->ang + 256) - RandomSize(9); if (nVal) { spr->xvel = bcos(nAngle, -5); spr->yvel = bsin(nAngle, -5); } else { spr->xvel = bcos(nAngle, -6); spr->yvel = bsin(nAngle, -6); } spr->zvel = -(RandomSize(4) << 7); spr->picnum = kTile985 + nVal; } spr->z = pSprite->z; spr->lotag = runlist_HeadRun() + 1; spr->clipdist = 1; spr->hitag = 0; spr->backuppos(); // GrabTimeSlot(3); spr->extra = -1; spr->owner = runlist_AddRunRec(spr->lotag - 1, pSpark, 0x260000); spr->hitag = runlist_AddRunRec(NewRun, pSpark, 0x260000); return pSpark; } void AISpark::Tick(RunListEvent* ev) { auto pActor = ev->pObjActor; if (!pActor) return; auto pSprite = &pActor->s(); pSprite->shade += 3; pSprite->xrepeat -= 2; if (pSprite->xrepeat >= 4 && pSprite->shade <= 100) { pSprite->yrepeat -= 2; // calling BuildSpark() with 2nd parameter as '1' will set kTile986 if (pSprite->picnum == kTile986 && (pSprite->xrepeat & 2)) { BuildSpark(pActor, 2); } if (pSprite->picnum >= kTile3000) { return; } pSprite->zvel += 128; auto nMov = movesprite(pActor, pSprite->xvel << 12, pSprite->yvel << 12, pSprite->zvel, 2560, -2560, CLIPMASK1); if (!nMov.type && !nMov.exbits) { return; } if (pSprite->zvel <= 0) { return; } } pSprite->xvel = 0; pSprite->yvel = 0; pSprite->zvel = 0; if (pSprite->picnum > kTile3000) { nSmokeSparks--; } runlist_DoSubRunRec(pSprite->owner); runlist_FreeRun(pSprite->lotag - 1); runlist_SubRunRec(pSprite->hitag); DeleteActor(pActor); } void FuncSpark(int nObject, int nMessage, int nDamage, int nRun) { AISpark ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } 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 nextstage = 0; if (!lFinaleStart) return; dword_96788++; if (dword_96788 < 90) { if (!(dword_96788 & 2)) { int nAng = RandomSize(11); pFinaleSpr->s().ang = nAng; BuildSpark(pFinaleSpr, 1); } if (!RandomSize(2)) { PlayFX2(StaticSound[kSound78] | 0x2000, pFinaleSpr); for (int i = 0; i < nTotalPlayers; i++) { nQuake[i] = 1280; } } } else { DimLights(); if (nDronePitch <= -2400) { if (nFinaleStage < 2) { if (nFinaleStage == 1) { StopLocalSound(); PlayLocalSound(StaticSound[kSound76], 0); nextstage = PlayClock + 120; nFinaleStage++; } } else if (nFinaleStage <= 2) { if (PlayClock >= nextstage) { PlayLocalSound(StaticSound[kSound77], 0); nFinaleStage++; nextstage = PlayClock + 360; } } else if (nFinaleStage == 3 && PlayClock >= nextstage) { LevelFinished(); } } else { nDronePitch -= 128; BendAmbientSound(); nFinaleStage = 1; } } } DExhumedActor* 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; auto pActor = insertActor(nSector, 406); auto spr = &pActor->s(); short nextsector = wall[startwall].nextsector; spr->x = xAvg; spr->y = yAvg; sector[nSector].extra = (int16_t)EnergyBlocks.Push(pActor); // GrabTimeSlot(3); spr->z = sector[nextsector].floorz; // CHECKME - name of this variable? int nRepeat = (spr->z - sector[nSector].floorz) >> 8; if (nRepeat > 255) { nRepeat = 255; } spr->xrepeat = nRepeat; spr->cstat = 0x8000; spr->xvel = 0; spr->yvel = 0; spr->zvel = 0; spr->extra = -1; spr->lotag = runlist_HeadRun() + 1; spr->hitag = 0; spr->owner = runlist_AddRunRec(spr->lotag - 1, pActor, 0x250000); spr->backuppos(); return pActor; } // TODO - tidy void KillCreatures() { signed int v0; signed int v1; v0 = 99; v1 = 99; while (1) { if (v0 != 100) { ExhumedStatIterator it(v1); while (auto i = it.Next()) { runlist_DamageEnemy(i, nullptr, 1600); } } ++v0; ++v1; if (v0 > 107) { return; } } } void ExplodeEnergyBlock(DExhumedActor* pActor) { auto pSprite = &pActor->s(); short nSector = pSprite->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 = pSprite->z; pSprite->z = (pSprite->z + sector[nSector].floorz) / 2; BuildSpark(pActor, 3); pSprite->cstat = 0; pSprite->xrepeat = 100; PlayFX2(StaticSound[kSound78], pActor); pSprite->xrepeat = 0; nEnergyTowers--; for (i = 0; i < 20; i++) { pSprite->ang = RandomSize(11); BuildSpark(pActor, 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 { pFinaleSpr = pActor; lFinaleStart = PlayClock; 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(); } ChangeActorStat(pActor, 0); } void AIEnergyBlock::Damage(RunListEvent* ev) { auto pActor = ev->pObjActor; if (!pActor) return; auto spr = &pActor->s(); ev->nDamage >>= 2; if (ev->nDamage <= 0) { return; } if (ev->nDamage < spr->xrepeat) { spr->xrepeat -= ev->nDamage; auto pActor2 = insertActor(lasthitsect, 0); auto pSprite2 = &pActor2->s(); pSprite2->ang = ev->nParam; pSprite2->x = lasthitx; pSprite2->y = lasthity; pSprite2->z = lasthitz; BuildSpark(pActor2, 0); // shoot out blue orb when damaged DeleteActor(pActor2); } else { spr->xrepeat = 0; // using xrepeat to store health ExplodeEnergyBlock(pActor); } } void AIEnergyBlock::RadialDamage(RunListEvent* ev) { auto pActor = ev->pObjActor; if (!pActor) return; auto spr = &pActor->s(); short nSector = spr->sectnum; if (sector[nSector].extra == -1) { return; } int nFloorZ = sector[nSector].floorz; sector[nSector].floorz = spr->z; spr->z -= 256; ev->nDamage = runlist_CheckRadialDamage(pActor); // restore previous values sector[nSector].floorz = nFloorZ; spr->z += 256; if (ev->nDamage <= 0) { return; } // fall through to case 0x80000 Damage(ev); } void FuncEnergyBlock(int nObject, int nMessage, int nDamage, int nRun) { AIEnergyBlock ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } int BuildObject(int const nSprite, int nOjectType, int nHitag) { auto spr = &sprite[nSprite]; auto nObject = ObjectList.Reserve(1); ObjectList[nObject] = {}; changespritestat(nSprite, ObjectStatnum[nOjectType]); // 0x7FFD to ensure set as blocking ('B' and 'H') sprite and also disable translucency and set not invisible spr->cstat = (spr->cstat | 0x101) & 0x7FFD; spr->xvel = 0; spr->yvel = 0; spr->zvel = 0; spr->extra = -1; spr->lotag = runlist_HeadRun() + 1; spr->hitag = 0; spr->owner = runlist_AddRunRec(spr->lotag - 1, nObject, 0x170000); // GrabTimeSlot(3); if (spr->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(spr->sectnum, 0); auto pSprite2 = &sprite[nSprite2]; ObjectList[nObject].field_10 = nSprite2; pSprite2->cstat = 0x8000; pSprite2->x = spr->x; pSprite2->y = spr->y; pSprite2->z = spr->z; } else { ObjectList[nObject].field_0 = 0; ObjectList[nObject].field_8 = -1; if (spr->statnum == kStatDestructibleSprite) { ObjectList[nObject].field_10 = -1; } else { ObjectList[nObject].field_10 = -nHitag; } } spr->backuppos(); return nObject | 0x170000; } // in-game destructable wall mounted screen void ExplodeScreen(short nSprite) { auto pSprite = &sprite[nSprite]; pSprite->z -= GetSpriteHeight(nSprite) / 2; for (int i = 0; i < 30; i++) { BuildSpark(&exhumedActors[nSprite], 0); // shoot out blue orbs } pSprite->cstat = 0x8000; PlayFX2(StaticSound[kSound78], nSprite); } void AIObject::Tick(RunListEvent* ev) { short nObject = RunData[ev->nRun].nObjIndex; auto pObject = &ObjectList[nObject]; auto pActor = &exhumedActors[pObject->nSprite];// ev->pObjActor; short nSprite = pObject->nSprite; auto pSprite = &sprite[nSprite]; short nStat = pSprite->statnum; short bx = pObject->field_8; if (nStat == 97 || (!(pSprite->cstat & 0x101))) { return; } if (nStat != kStatExplodeTarget) { Gravity(pActor); } // do animation if (bx != -1) { pObject->field_0++; if (pObject->field_0 >= SeqSize[bx]) { pObject->field_0 = 0; } pSprite->picnum = seq_GetSeqPicnum2(bx, pObject->field_0); } if (pObject->nHealth >= 0) { goto FUNCOBJECT_GOTO; } pObject->nHealth++; if (pObject->nHealth) { FUNCOBJECT_GOTO: if (nStat != kStatExplodeTarget) { int nMov = movesprite(nSprite, pSprite->xvel << 6, pSprite->yvel << 6, pSprite->zvel, 0, 0, CLIPMASK0); if (pSprite->statnum == kStatExplodeTrigger) { pSprite->pal = 1; } if (nMov & 0x20000) { pSprite->xvel -= pSprite->xvel >> 3; pSprite->yvel -= pSprite->yvel >> 3; } if (((nMov & 0xC000) > 0x8000) && ((nMov & 0xC000) == 0xC000)) { pSprite->yvel = 0; pSprite->xvel = 0; } } return; } else { int var_18; // red branch if ((nStat == kStatExplodeTarget) || (pSprite->z < sector[pSprite->sectnum].floorz)) { var_18 = 36; } else { var_18 = 34; } AddFlash(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 128); BuildAnim(nullptr, var_18, 0, pSprite->x, pSprite->y, sector[pSprite->sectnum].floorz, pSprite->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 (!(currentLevel->gameflags & LEVEL_EX_MULTI) || nStat != kStatExplodeTrigger) { runlist_SubRunRec(pSprite->owner); runlist_SubRunRec(pObject->field_4); mydeletesprite(nSprite); return; } else { StartRegenerate(pActor); pObject->nHealth = 120; pSprite->x = sprite[pObject->field_10].x; pSprite->y = sprite[pObject->field_10].y; pSprite->z = sprite[pObject->field_10].z; mychangespritesect(nSprite, sprite[pObject->field_10].sectnum); return; } } } void AIObject::Damage(RunListEvent* ev) { short nObject = RunData[ev->nRun].nObjIndex; auto pObject = &ObjectList[nObject]; short nSprite = pObject->nSprite; auto pSprite = &sprite[nSprite]; short nStat = pSprite->statnum; short bx = pObject->field_8; if (nStat >= 150 || pObject->nHealth <= 0) { return; } if (nStat == 98) { D3PlayFX((StaticSound[kSound47] | 0x2000) | (RandomSize(2) << 9), nSprite); return; } pObject->nHealth -= (short)ev->nDamage; if (pObject->nHealth > 0) { return; } if (nStat == kStatDestructibleSprite) { ExplodeScreen(nSprite); } else { pObject->nHealth = -(RandomSize(3) + 1); } } void AIObject::Draw(RunListEvent* ev) { short nObject = RunData[ev->nRun].nObjIndex; auto pObject = &ObjectList[nObject]; short bx = pObject->field_8; if (bx > -1) { seq_PlotSequence(ev->nParam, bx, pObject->field_0, 1); } return; } void AIObject::RadialDamage(RunListEvent* ev) { short nObject = RunData[ev->nRun].nObjIndex; auto pObject = &ObjectList[nObject]; short nSprite = pObject->nSprite; auto pSprite = &sprite[nSprite]; short nStat = pSprite->statnum; if (pObject->nHealth > 0 && pSprite->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 (pSprite->statnum != kStatAnubisDrum) { pObject->nHealth -= nDamage; } if (pSprite->statnum == kStatExplodeTarget) { pSprite->xvel = 0; pSprite->yvel = 0; pSprite->zvel = 0; } else if (pSprite->statnum != kStatAnubisDrum) { pSprite->xvel >>= 1; pSprite->yvel >>= 1; pSprite->zvel >>= 1; } if (pObject->nHealth > 0) { return; } if (pSprite->statnum == kStatExplodeTarget) { pObject->nHealth = -1; short ax = pObject->field_10; if (ax < 0 || ObjectList[ax].nHealth <= 0) { return; } ObjectList[ax].nHealth = -1; } else if (pSprite->statnum == kStatDestructibleSprite) { pObject->nHealth = 0; ExplodeScreen(nSprite); } else { pObject->nHealth = -(RandomSize(4) + 1); } } } void FuncObject(int nObject, int nMessage, int nDamage, int nRun) { AIObject ai; runlist_DispatchEvent(&ai, nObject, nMessage, nDamage, nRun); } void BuildDrip(int nSprite) { auto pSprite = &sprite[nSprite]; auto nDrips = sDrip.Reserve(1); sDrip[nDrips].nSprite = nSprite; sDrip[nDrips].field_2 = RandomSize(8) + 90; pSprite->cstat = 0x8000u; } void DoDrips() { for (unsigned i = 0; i < sDrip.Size(); i++) { sDrip[i].field_2--; if (sDrip[i].field_2 <= 0) { short nSprite = sDrip[i].nSprite; auto pSprite = &sprite[nSprite]; short nSeqOffset = SeqOffsets[kSeqDrips]; if (!(SectFlag[pSprite->sectnum] & kSectLava)) { nSeqOffset++; } seq_MoveSequence(nSprite, nSeqOffset, RandomSize(2) % SeqSize[nSeqOffset]); sDrip[i].field_2 = RandomSize(8) + 90; } } for (unsigned i = 0; i < sBob.Size(); i++) { sBob[i].field_2 += 4; int edx = bsin(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 (unsigned i = 0; i < sBob.Size(); 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) { auto nBobs = sBob.Reserve(1); 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; sBob[nBobs].sBobID = nHitag; sBob[nBobs].nSector = nSector; StartInterpolation(nSector, bx == 0 ? Interp_Sect_Floorz : Interp_Sect_Ceilingz); SectFlag[nSector] |= 0x0010; } int FindTrail(int nVal) { for (unsigned i = 0; i < sTrail.Size(); i++) { if (sTrail[i].field_2 == nVal) return i; } auto nTrails = sTrail.Reserve(1); sTrail[nTrails].field_2 = nVal; sTrail[nTrails].field_0 = -1; sTrail[nTrails].field_4 = -1; return nTrails; } // ok ? void ProcessTrailSprite(int nSprite, int nLotag, int nHitag) { auto pSprite = &sprite[nSprite]; auto nPoint = sTrailPoint.Reserve(1); sTrailPoint[nPoint].x = pSprite->x; sTrailPoint[nPoint].y = pSprite->y; int nTrail = FindTrail(nHitag); int var_14 = nLotag - 900; sTrailPoint[nPoint].nTrailPointVal = var_14; int field0 = sTrail[nTrail].field_0; if (field0 == -1) { sTrail[nTrail].field_0 = nPoint; sTrail[nTrail].field_4 = nPoint; sTrailPoint[nPoint].nTrailPointNext = -1; sTrailPoint[nPoint].nTrailPointPrev = -1; } else { int ecx = -1; while (field0 != -1) { if (sTrailPoint[field0].nTrailPointVal > var_14) { sTrailPoint[nPoint].nTrailPointPrev = sTrailPoint[field0].nTrailPointPrev; sTrailPoint[field0].nTrailPointPrev = nPoint; sTrailPoint[nPoint].nTrailPointNext = field0; if (field0 == sTrail[nTrail].field_0) { sTrail[nTrail].field_0 = nPoint; } break; } ecx = field0; field0 = sTrailPoint[field0].nTrailPointNext; } if (field0 == -1) { sTrailPoint[ecx].nTrailPointNext = nPoint; sTrailPoint[nPoint].nTrailPointPrev = ecx; sTrailPoint[nPoint].nTrailPointNext = -1; sTrail[nTrail].field_4 = nPoint; } } mydeletesprite(nSprite); } // ok? void AddMovingSector(int nSector, int edx, int ebx, int ecx) { CreatePushBlock(nSector); setsectinterpolate(nSector); short nTrail = FindTrail(ebx); auto nMoveSects = sMoveSect.Reserve(1); MoveSect* pMoveSect = &sMoveSect[nMoveSects]; pMoveSect->sMoveDir = 1; 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 (unsigned i = 0; i < sMoveSect.Size(); 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) { sMoveSect[i].sMoveDir = -sMoveSect[i].sMoveDir; if (sMoveSect[i].sMoveDir > 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 = bcos(nAngle, 4) * sMoveSect[i].field_10; int nYVel = bsin(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 (sMoveSect[i].sMoveDir > 0) { sMoveSect[i].nTrailPoint = sTrailPoint[sMoveSect[i].nTrailPoint].nTrailPointNext; } else { sMoveSect[i].nTrailPoint = sTrailPoint[sMoveSect[i].nTrailPoint].nTrailPointPrev; } } } else { // repeat of code from loc_23908 nYVel = ecx; nXVel = ebx; if (sMoveSect[i].sMoveDir > 0) { sMoveSect[i].nTrailPoint = sTrailPoint[sMoveSect[i].nTrailPoint].nTrailPointNext; } else { sMoveSect[i].nTrailPoint = sTrailPoint[sMoveSect[i].nTrailPoint].nTrailPointPrev; } } // 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 (unsigned i = 0; i < sMoveSect.Size(); 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 (unsigned j = 0; j < sMoveSect.Size(); 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 (unsigned i = 0; i < sBob.Size(); i++) { if (sBob[i].field_3 == 0) { int bobID = sBob[i].sBobID; for (unsigned j = 0; j < sBob.Size(); j++) { if (j != i) { if (sBob[i].field_3 != 0 && sBob[j].sBobID == bobID) { SnapSectors(i, j, 0); } } } } } if (!(currentLevel->gameflags & LEVEL_EX_COUNTDOWN)) { // 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[kSound43]; } 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[kSound43]; } } } } } } else // nMap == kMap20) { // int var_24 = 0; int ebp = 0; for (i = 0; i < numsectors; i++) { SectSoundSect[i] = i; SectSound[i] = StaticSound[kSound62]; 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); auto pSprite = &sprite[nSprite]; pSprite->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; } } } for (unsigned i = 0; i < ObjectList.Size(); 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; for (unsigned j = 0; j < ObjectList.Size(); 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; } } } } } } END_PS_NS