//------------------------------------------------------------------------- /* 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 "compat.h" #include "engine.h" #include "names.h" #include "view.h" #include "status.h" #include "exhumed.h" #include "player.h" #include "aistuff.h" #include "sound.h" #include "mapinfo.h" #include "v_video.h" #include "glbackend/glbackend.h" #include BEGIN_PS_NS short bSubTitles = true; int zbob; fix16_t nDestVertPan[kMaxPlayers] = { 0 }; short dVertPan[kMaxPlayers]; int nCamerax; int nCameray; int nCameraz; short bTouchFloor; short nQuake[kMaxPlayers] = { 0 }; short nChunkTotal = 0; fix16_t nCameraa; fix16_t nCamerapan; short nViewTop; short bCamera = false; int viewz; short enemy; short nEnemyPal = 0; #define MAXINTERPOLATIONS MAXSPRITES int32_t g_interpolationCnt; int32_t oldipos[MAXINTERPOLATIONS]; int32_t* curipos[MAXINTERPOLATIONS]; int32_t bakipos[MAXINTERPOLATIONS]; int viewSetInterpolation(int32_t *const posptr) { if (g_interpolationCnt >= MAXINTERPOLATIONS) return 1; for (bssize_t i = 0; i < g_interpolationCnt; ++i) if (curipos[i] == posptr) return 0; curipos[g_interpolationCnt] = posptr; oldipos[g_interpolationCnt] = *posptr; g_interpolationCnt++; return 0; } void viewStopInterpolation(const int32_t * const posptr) { for (bssize_t i = 0; i < g_interpolationCnt; ++i) if (curipos[i] == posptr) { g_interpolationCnt--; oldipos[i] = oldipos[g_interpolationCnt]; bakipos[i] = bakipos[g_interpolationCnt]; curipos[i] = curipos[g_interpolationCnt]; } } void viewDoInterpolations(int smoothRatio) { int32_t ndelta = 0; for (bssize_t i = 0, j = 0; i < g_interpolationCnt; ++i) { int32_t const odelta = ndelta; bakipos[i] = *curipos[i]; ndelta = (*curipos[i]) - oldipos[i]; if (odelta != ndelta) j = mulscale16(ndelta, smoothRatio); *curipos[i] = oldipos[i] + j; } } void viewUpdateInterpolations(void) //Stick at beginning of G_DoMoveThings { for (bssize_t i=g_interpolationCnt-1; i>=0; i--) oldipos[i] = *curipos[i]; } void viewRestoreInterpolations(void) //Stick at end of drawscreen { int32_t i=g_interpolationCnt-1; for (; i>=0; i--) *curipos[i] = bakipos[i]; } void InitView() { polymostcenterhoriz = 92; } // NOTE - not to be confused with Ken's analyzesprites() static void analyzesprites() { short nPlayerSprite = PlayerList[nLocalPlayer].nSprite; int var_38 = 20; int var_2C = 30000; spritetype *pPlayerSprite = &sprite[nPlayerSprite]; besttarget = -1; int x = pPlayerSprite->x; int y = pPlayerSprite->y; int z = pPlayerSprite->z - (GetSpriteHeight(nPlayerSprite) / 2); short nSector = pPlayerSprite->sectnum; int nAngle = (2048 - pPlayerSprite->ang) & kAngleMask; int nTSprite; tspritetype *pTSprite; // int var_20 = var_24; for (nTSprite = spritesortcnt-1, pTSprite = &tsprite[nTSprite]; nTSprite >= 0; nTSprite--, pTSprite--) { int nSprite = pTSprite->owner; spritetype *pSprite = &sprite[nSprite]; if (pTSprite->sectnum >= 0) { sectortype *pSector = §or[pTSprite->sectnum]; int nSectShade = (pSector->ceilingstat & 1) ? pSector->ceilingshade : pSector->floorshade; int nShade = pTSprite->shade + nSectShade + 6; pTSprite->shade = clamp(nShade, -128, 127); } pTSprite->pal = RemapPLU(pTSprite->pal); if (pSprite->statnum > 0) { runlist_SignalRun(pSprite->lotag - 1, nTSprite | 0x90000); if ((pSprite->statnum < 150) && (pSprite->cstat & 0x101) && (nSprite != nPlayerSprite)) { int xval = pSprite->x - x; int yval = pSprite->y - y; int vcos = Cos(nAngle); int vsin = Sin(nAngle); int edx = ((vcos * yval) + (xval * vsin)) >> 14; int ebx = klabs(((vcos * xval) - (yval * vsin)) >> 14); if (!ebx) continue; edx = (klabs(edx) * 32) / ebx; if (ebx < 1000 && ebx < var_2C && edx < 10) { besttarget = nSprite; var_38 = edx; var_2C = ebx; } else if (ebx < 30000) { int t = var_38 - edx; if (t > 3 || (ebx < var_2C && klabs(t) < 5)) { var_38 = edx; var_2C = ebx; besttarget = nSprite; } } } } } if (besttarget != -1) { spritetype *pTarget = &sprite[besttarget]; nCreepyTimer = kCreepyCount; if (!cansee(x, y, z, nSector, pTarget->x, pTarget->y, pTarget->z - GetSpriteHeight(besttarget), pTarget->sectnum)) { besttarget = -1; } } } void ResetView() { EraseScreen(overscanindex); #ifdef USE_OPENGL videoTintBlood(0, 0, 0); #endif } static inline int interpolate16(int a, int b, int smooth) { return a + mulscale16(b - a, smooth); } static TextOverlay subtitleOverlay; void DrawView(double smoothRatio, bool sceneonly) { int playerX; int playerY; int playerZ; short nSector; fix16_t nAngle; fix16_t pan; zbob = Sin(2 * bobangle) >> 3; int nPlayerSprite = PlayerList[nLocalPlayer].nSprite; int nPlayerOldCstat = sprite[nPlayerSprite].cstat; int nDoppleOldCstat = sprite[nDoppleSprite[nLocalPlayer]].cstat; if (nSnakeCam >= 0 && !sceneonly) { int nSprite = SnakeList[nSnakeCam].nSprites[0]; playerX = sprite[nSprite].x; playerY = sprite[nSprite].y; playerZ = sprite[nSprite].z; nSector = sprite[nSprite].sectnum; nAngle = fix16_from_int(sprite[nSprite].ang); SetGreenPal(); enemy = SnakeList[nSnakeCam].nEnemy; if (enemy <= -1 || totalmoves & 1) { nEnemyPal = -1; } else { nEnemyPal = sprite[enemy].pal; sprite[enemy].pal = 5; } } else { playerX = interpolate16(PlayerList[nLocalPlayer].opos.x, sprite[nPlayerSprite].x, smoothRatio); playerY = interpolate16(PlayerList[nLocalPlayer].opos.y, sprite[nPlayerSprite].y, smoothRatio); playerZ = interpolate16(PlayerList[nLocalPlayer].opos.z, sprite[nPlayerSprite].z, smoothRatio) + interpolate16(oeyelevel[nLocalPlayer], eyelevel[nLocalPlayer], smoothRatio); nSector = nPlayerViewSect[nLocalPlayer]; nAngle = PlayerList[nLocalPlayer].q16angle; if (!bCamera) { sprite[nPlayerSprite].cstat |= CSTAT_SPRITE_INVISIBLE; sprite[nDoppleSprite[nLocalPlayer]].cstat |= CSTAT_SPRITE_INVISIBLE; } } nCameraa = nAngle; if (!bCamera || nFreeze || sceneonly) { if (nSnakeCam >= 0 && !sceneonly) { pan = F16(92); viewz = playerZ; } else { viewz = playerZ + nQuake[nLocalPlayer]; int floorZ = sector[sprite[nPlayerSprite].sectnum].floorz; pan = PlayerList[nLocalPlayer].q16horiz; if (viewz > floorZ) viewz = floorZ; nCameraa += fix16_from_int((nQuake[nLocalPlayer] >> 7) % 31); nCameraa &= 0x7FFFFFF; } } else { clipmove_old((int32_t*)&playerX, (int32_t*)&playerY, (int32_t*)&playerZ, &nSector, -2000 * Sin(inita + 512), -2000 * Sin(inita), 4, 0, 0, CLIPMASK1); pan = F16(92); viewz = playerZ; } nCamerax = playerX; nCameray = playerY; nCameraz = playerZ; int Z = sector[nSector].ceilingz + 256; if (Z <= viewz) { Z = sector[nSector].floorz - 256; if (Z < viewz) viewz = Z; } else { viewz = Z; } nCamerapan = pan; if (nFreeze == 2 || nFreeze == 1) { nSnakeCam = -1; videoSetViewableArea(0, 0, xdim - 1, ydim - 1); } UpdateMap(); if (nFreeze != 3) { static uint8_t sectorFloorPal[MAXSECTORS]; static uint8_t sectorCeilingPal[MAXSECTORS]; static uint8_t wallPal[MAXWALLS]; int const viewingRange = viewingrange; int const vr = Blrintf(65536.f * tanf(r_fov * (fPI / 360.f))); if (r_usenewaspect) { newaspect_enable = 1; videoSetCorrectedAspect(); renderSetAspect(mulscale16(vr, viewingrange), yxaspect); } else renderSetAspect(vr, yxaspect); if (HavePLURemap()) { for (int i = 0; i < numsectors; i++) { sectorFloorPal[i] = sector[i].floorpal; sectorCeilingPal[i] = sector[i].ceilingpal; sector[i].floorpal = RemapPLU(sectorFloorPal[i]); sector[i].ceilingpal = RemapPLU(sectorCeilingPal[i]); } for (int i = 0; i < numwalls; i++) { wallPal[i] = wall[i].pal; wall[i].pal = RemapPLU(wallPal[i]); } } renderDrawRoomsQ16(nCamerax, nCameray, viewz, nCameraa, nCamerapan, nSector); analyzesprites(); renderDrawMasks(); if (HavePLURemap()) { for (int i = 0; i < numsectors; i++) { sector[i].floorpal = sectorFloorPal[i]; sector[i].ceilingpal = sectorCeilingPal[i]; } for (int i = 0; i < numwalls; i++) { wall[i].pal = wallPal[i]; } } if (r_usenewaspect) { newaspect_enable = 0; renderSetAspect(viewingRange, divscale16(ydim * 8, xdim * 5)); } if (nFreeze) { nSnakeCam = -1; if (nFreeze == 2) { if (nHeadStage == 4) { nHeadStage = 5; sprite[nPlayerSprite].cstat |= 0x8000; int ang2 = fix16_to_int(nCameraa) - sprite[nPlayerSprite].ang; if (ang2 < 0) ang2 = -ang2; if (ang2 > 10) { inita -= (ang2 >> 3); return; } if (bSubTitles) { subtitleOverlay.Start(gameclock); if (currentLevel->levelNumber == 1) subtitleOverlay.ReadyCinemaText(0); else subtitleOverlay.ReadyCinemaText(4); } inputState.ClearAllInput(); } else if (nHeadStage == 5) { if ((bSubTitles && !subtitleOverlay.AdvanceCinemaText(gameclock)) || inputState.CheckAllInput()) { inputState.ClearAllInput(); EndLevel = 2; if (CDplaying()) { fadecdaudio(); } } else subtitleOverlay.DisplayText(); } } } else if (!sceneonly) { if (nSnakeCam < 0) { DrawWeapons(smoothRatio); DrawMap(); } else { RestoreGreenPal(); if (nEnemyPal > -1) { sprite[enemy].pal = nEnemyPal; } DrawMap(); } } } else { twod->ClearScreen(); } sprite[nPlayerSprite].cstat = nPlayerOldCstat; sprite[nDoppleSprite[nLocalPlayer]].cstat = nDoppleOldCstat; flash = 0; } bool GameInterface::GenerateSavePic() { DrawView(65536, true); return true; } void NoClip() { } void Clip() { } static SavegameHelper sgh("view", SV(nCamerax), SV(nCameray), SV(nCameraz), SV(bTouchFloor), SV(nChunkTotal), SV(nCameraa), SV(nCamerapan), SV(bCamera), SV(viewz), SV(enemy), SV(nEnemyPal), SA(nDestVertPan), SA(dVertPan), SA(nQuake), SV(g_interpolationCnt), SA(oldipos), SA(curipos), SA(bakipos), nullptr); END_PS_NS