/ source / blood / src / view.cpp
view.cpp
   1  //-------------------------------------------------------------------------
   2  /*
   3  Copyright (C) 2010-2019 EDuke32 developers and contributors
   4  Copyright (C) 2019 Nuke.YKT
   5  
   6  This file is part of NBlood.
   7  
   8  NBlood is free software; you can redistribute it and/or
   9  modify it under the terms of the GNU General Public License version 2
  10  as published by the Free Software Foundation.
  11  
  12  This program is distributed in the hope that it will be useful,
  13  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15  
  16  See the GNU General Public License for more details.
  17  
  18  You should have received a copy of the GNU General Public License
  19  along with this program; if not, write to the Free Software
  20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  21  */
  22  //-------------------------------------------------------------------------
  23  #include <stdlib.h>
  24  #include <string.h>
  25  
  26  #include "compat.h"
  27  #include "a.h"
  28  #include "build.h"
  29  #include "colmatch.h"
  30  #include "pragmas.h"
  31  #include "mmulti.h"
  32  #include "osd.h"
  33  #include "common_game.h"
  34  
  35  #include "aihand.h"
  36  #include "blood.h"
  37  #include "choke.h"
  38  #include "config.h"
  39  #include "db.h"
  40  #include "demo.h"
  41  #include "endgame.h"
  42  #include "gamemenu.h"
  43  #include "gameutil.h"
  44  #include "globals.h"
  45  #include "gfx.h"
  46  #include "levels.h"
  47  #include "loadsave.h"
  48  #include "map2d.h"
  49  #include "messages.h"
  50  #include "menu.h"
  51  #include "mirrors.h"
  52  #include "network.h"
  53  #include "player.h"
  54  #include "replace.h"
  55  #include "screen.h"
  56  #include "sectorfx.h"
  57  #include "tile.h"
  58  #include "trig.h"
  59  #include "view.h"
  60  #include "warp.h"
  61  #include "weapon.h"
  62  #ifdef NOONE_EXTENSIONS
  63  #include "nnexts.h"
  64  #endif
  65  
  66  struct VIEW {
  67      int at0;
  68      int at4;
  69      int at8; // bob height
  70      int atc; // bob width
  71      int at10;
  72      int at14;
  73      int at18; // bob sway y
  74      int at1c; // bob sway x
  75      fix16_t at20;
  76      fix16_t at24; // horiz
  77      int at28; // horizoff
  78      int at2c;
  79      fix16_t at30; // angle
  80      int at34; // weapon z
  81      int at38; // view z
  82      int at3c;
  83      int at40;
  84      int at44;
  85      int at48; // posture
  86      int at4c; // spin
  87      int at50; // x
  88      int at54; // y
  89      int at58; // z
  90      int at5c; //xvel
  91      int at60; //yvel
  92      int at64; //zvel
  93      short at68; // sectnum
  94      unsigned int at6a; // floordist
  95      char at6e; // look center
  96      char at6f;
  97      char at70; // run
  98      char at71; // jump
  99      char at72; // underwater
 100      short at73; // sprite flags
 101      SPRITEHIT at75;
 102  };
 103  
 104  VIEW gPrevView[kMaxPlayers];
 105  VIEWPOS gViewPos;
 106  int gViewIndex;
 107  
 108  struct INTERPOLATE {
 109      void *pointer;
 110      int value;
 111      int value2;
 112      INTERPOLATE_TYPE type;
 113  };
 114  
 115  int gViewMode = 3;
 116  int gViewSize = 2;
 117  
 118  bool gPrediction = true;
 119  
 120  VIEW predict, predictOld;
 121  
 122  VIEW predictFifo[256];
 123  
 124  int gInterpolate;
 125  int nInterpolations;
 126  int nInterpolationsPanning;
 127  char gInterpolateSprite[bitmap_size(kMaxSprites)];
 128  char gInterpolateWall[bitmap_size(kMaxWalls)];
 129  char gInterpolateSector[bitmap_size(kMaxSectors)];
 130  char gInterpolatePanningWall[bitmap_size(kMaxWalls)];
 131  char gInterpolatePanningCeiling[bitmap_size(kMaxSectors)];
 132  char gInterpolatePanningFloor[bitmap_size(kMaxSectors)];
 133  
 134  #define kMaxInterpolationsPanning (1024) // dedicate number of interpolations to texture panning
 135  #define kMaxInterpolationsVanilla 4096
 136  #define kMaxInterpolations (16384+kMaxInterpolationsPanning)
 137  
 138  INTERPOLATE gInterpolation[kMaxInterpolations];
 139  
 140  int gViewXCenter, gViewYCenter;
 141  int gViewX0, gViewY0, gViewX1, gViewY1;
 142  int gViewX0S, gViewY0S, gViewX1S, gViewY1S;
 143  int xscale, xscalecorrect, yscale, xstep, ystep;
 144  int xscalehud = 0, xscalestats = 0, yscalestats = 0, xscalepowerups = 0, xscalectfhud = 0;
 145  
 146  int gScreenTilt;
 147  
 148  CGameMessageMgr gGameMessageMgr;
 149  
 150  bool bLoadScreenCrcMatch = false;
 151  
 152  void RotateYZ(int *pX, int *pY, int *pZ, int ang)
 153  {
 154      UNREFERENCED_PARAMETER(pX);
 155      int oY, oZ, angSin, angCos;
 156      oY = *pY;
 157      oZ = *pZ;
 158      angSin = Sin(ang);
 159      angCos = Cos(ang);
 160      *pY = dmulscale30r(oY,angCos,oZ,-angSin);
 161      *pZ = dmulscale30r(oY,angSin,oZ,angCos);
 162  }
 163  
 164  void RotateXZ(int *pX, int *pY, int *pZ, int ang)
 165  {
 166      UNREFERENCED_PARAMETER(pY);
 167      int oX, oZ, angSin, angCos;
 168      oX = *pX;
 169      oZ = *pZ;
 170      angSin = Sin(ang);
 171      angCos = Cos(ang);
 172      *pX = dmulscale30r(oX,angCos,oZ,-angSin);
 173      *pZ = dmulscale30r(oX,angSin,oZ,angCos);
 174  }
 175  
 176  void RotateXY(int *pX, int *pY, int *pZ, int ang)
 177  {
 178      UNREFERENCED_PARAMETER(pZ);
 179      int oX, oY, angSin, angCos;
 180      oX = *pX;
 181      oY = *pY;
 182      angSin = Sin(ang);
 183      angCos = Cos(ang);
 184      *pX = dmulscale30r(oX,angCos,oY,-angSin);
 185      *pY = dmulscale30r(oX,angSin,oY,angCos);
 186  }
 187  
 188  FONT gFont[kFontNum];
 189  
 190  void FontSet(int id, int tile, int space)
 191  {
 192      if (id < 0 || id >= kFontNum || tile < 0 || tile >= kMaxTiles)
 193          return;
 194  
 195      FONT* pFont = &gFont[id];
 196      int yoff = 0;
 197  
 198      DICTNODE* hQFont = gSysRes.Lookup(id, "QFN");
 199      if (hQFont)
 200      {
 201          QFONT *pQFont = (QFONT*)gSysRes.Load(hQFont);
 202          for (int i = 32; i < 128; i++)
 203          {
 204              QFONTCHAR* pChar = &pQFont->at20[i];
 205              yoff = min(yoff, pQFont->atf + pChar->oy);
 206          }
 207          for (int i = 32; i < 128; i++)
 208          {
 209              int const nTile = tile + i - 32;
 210              if (waloff[nTile])
 211                  continue;
 212              QFONTCHAR *pChar = &pQFont->at20[i];
 213              int const width = max(pChar->w, pChar->ox);
 214              int const height = max(pQFont->atf+pChar->oy+pChar->h-yoff, 1);
 215              char *tilePtr = (char*)tileCreate(nTile, width, height);
 216              if (!tilePtr)
 217                  continue;
 218              Bmemset(tilePtr, 255, width * height);
 219              for (int x = 0; x < pChar->w; x++)
 220              {
 221                  for (int y = 0; y < pChar->h; y++)
 222                  {
 223                      int const dx = x;
 224                      int const dy = y + pQFont->atf + pChar->oy-yoff;
 225                      if (dx >= 0 && dx < width && dy >= 0 && dy < height)
 226                          tilePtr[dx*height + dy] = pQFont->at820[pChar->offset + x * pChar->h + y];
 227                  }
 228              }
 229          }
 230  
 231          pFont->tile = tile;
 232          pFont->xSize = pQFont->at12;
 233          pFont->ySize = pQFont->at13;
 234          pFont->space = pQFont->at11;
 235          pFont->yoff = yoff;
 236  
 237          return;
 238      }
 239      int xSize = 0;
 240      int ySize = 0;
 241      pFont->tile = tile;
 242      for (int i = 0; i < 96; i++)
 243      {
 244          if (tilesiz[tile+i].x > xSize)
 245              xSize = tilesiz[tile+i].x;
 246          if (tilesiz[tile+i].y > ySize)
 247              ySize = tilesiz[tile+i].y;
 248      }
 249      pFont->xSize = xSize;
 250      pFont->ySize = ySize;
 251      pFont->space = space;
 252      pFont->yoff = yoff;
 253  }
 254  
 255  void viewGetFontInfo(int id, const char *unk1, int *pXSize, int *pYSize)
 256  {
 257      if (id < 0 || id >= kFontNum)
 258          return;
 259      FONT *pFont = &gFont[id];
 260      if (!unk1)
 261      {
 262          if (pXSize)
 263              *pXSize = pFont->xSize;
 264          if (pYSize)
 265              *pYSize = pFont->ySize;
 266      }
 267      else
 268      {
 269          int width = -pFont->space;
 270          for (const char *pBuf = unk1; *pBuf != 0; pBuf++)
 271          {
 272              int tile = ((*pBuf-32)&127)+pFont->tile;
 273              if (tilesiz[tile].x != 0 && tilesiz[tile].y != 0)
 274                  width += tilesiz[tile].x+pFont->space;
 275          }
 276          if (pXSize)
 277              *pXSize = width;
 278          if (pYSize)
 279              *pYSize = pFont->ySize;
 280      }
 281  }
 282  
 283  void viewToggle(int viewMode)
 284  {
 285      if (viewMode == 3)
 286          gViewMode = 4;
 287      else
 288      {
 289          gViewMode = 3;
 290          viewResizeView(gViewSize);
 291      }
 292  }
 293  
 294  void viewInitializePrediction(void)
 295  {
 296      predict.at30 = gMe->q16ang;
 297      predict.at20 = gMe->q16look;
 298      predict.at24 = gMe->q16horiz;
 299      predict.at28 = gMe->q16slopehoriz;
 300      predict.at2c = gMe->slope;
 301      predict.at6f = gMe->cantJump;
 302      predict.at70 = gMe->isRunning;
 303      predict.at72 = gMe->isUnderwater;
 304      predict.at71 = gMe->input.buttonFlags.jump;
 305      predict.at50 = gMe->pSprite->x;
 306      predict.at54 = gMe->pSprite->y;
 307      predict.at58 = gMe->pSprite->z;
 308      predict.at68 = gMe->pSprite->sectnum;
 309      predict.at73 = gMe->pSprite->flags;
 310      predict.at5c = xvel[gMe->pSprite->index];
 311      predict.at60 = yvel[gMe->pSprite->index];
 312      predict.at64 = zvel[gMe->pSprite->index];
 313      predict.at6a = gMe->pXSprite->height;
 314      predict.at48 = gMe->posture;
 315      predict.at4c = gMe->spin;
 316      predict.at6e = gMe->input.keyFlags.lookCenter;
 317      memcpy(&predict.at75,&gSpriteHit[gMe->pSprite->extra],sizeof(SPRITEHIT));
 318      predict.at0 = gMe->bobPhase;
 319      predict.at4 = gMe->bobAmp;
 320      predict.at8 = gMe->bobHeight;
 321      predict.atc = gMe->bobWidth;
 322      predict.at10 = gMe->swayPhase;
 323      predict.at14 = gMe->swayAmp;
 324      predict.at18 = gMe->swayHeight;
 325      predict.at1c = gMe->swayWidth;
 326      predict.at34 = gMe->zWeapon-gMe->zView-(12<<8);
 327      predict.at38 = gMe->zView;
 328      predict.at3c = gMe->zViewVel;
 329      predict.at40 = gMe->zWeapon;
 330      predict.at44 = gMe->zWeaponVel;
 331      predictOld = predict;
 332      if (numplayers != 1)
 333      {
 334          gViewAngle = predict.at30;
 335          gViewLook = predict.at20;
 336      }
 337  }
 338  
 339  void viewUpdatePrediction(GINPUT *pInput)
 340  {
 341      predictOld = predict;
 342      short bakCstat = gMe->pSprite->cstat;
 343      gMe->pSprite->cstat = 0;
 344      fakePlayerProcess(gMe, pInput);
 345      fakeActProcessSprites();
 346      gMe->pSprite->cstat = bakCstat;
 347      predictFifo[gPredictTail&255] = predict;
 348      gPredictTail++;
 349      if (numplayers != 1)
 350      {
 351          gViewAngle = predict.at30;
 352          gViewLook = predict.at20;
 353      }
 354  }
 355  
 356  void sub_158B4(PLAYER *pPlayer)
 357  {
 358      predict.at38 = predict.at58 - pPlayer->pPosture[pPlayer->lifeMode][predict.at48].eyeAboveZ;
 359      predict.at40 = predict.at58 - pPlayer->pPosture[pPlayer->lifeMode][predict.at48].weaponAboveZ;
 360  }
 361  
 362  void fakeProcessInput(PLAYER *pPlayer, GINPUT *pInput)
 363  {
 364      POSTURE *pPosture = &pPlayer->pPosture[pPlayer->lifeMode][predict.at48];
 365  
 366      if (numplayers > 1 && gPrediction)
 367      {
 368          gViewAngleAdjust = 0.f;
 369          gViewLookRecenter = false;
 370          gViewLookAdjust = 0.f;
 371      }
 372  
 373      predict.at70 = (gProfile[pPlayer->nPlayer].nWeaponHBobbing == 2) || VanillaMode() ? pInput->syncFlags.run : 0; // v1.0x weapon swaying (vanilla 1.21 multiplayer hardcoded this)
 374      predict.at71 = pInput->buttonFlags.jump;
 375      if (predict.at48 == 1 || gFlyMode)
 376      {
 377          int x = Cos(fix16_to_int(predict.at30));
 378          int y = Sin(fix16_to_int(predict.at30));
 379          if (pInput->forward)
 380          {
 381              int forward = pInput->forward;
 382              if (forward > 0)
 383                  forward = mulscale8(pPosture->frontAccel, forward);
 384              else
 385                  forward = mulscale8(pPosture->backAccel, forward);
 386              predict.at5c += mulscale30(forward, x);
 387              predict.at60 += mulscale30(forward, y);
 388          }
 389          if (pInput->strafe)
 390          {
 391              int strafe = pInput->strafe;
 392              strafe = mulscale8(pPosture->sideAccel, strafe);
 393              predict.at5c += mulscale30(strafe, y);
 394              predict.at60 -= mulscale30(strafe, x);
 395          }
 396      }
 397      else if (predict.at6a < 0x100)
 398      {
 399          int speed = 0x10000;
 400          if (predict.at6a > 0)
 401              speed -= divscale16(predict.at6a, 0x100);
 402          int x = Cos(fix16_to_int(predict.at30));
 403          int y = Sin(fix16_to_int(predict.at30));
 404          if (pInput->forward)
 405          {
 406              int forward = pInput->forward;
 407              if (forward > 0)
 408                  forward = mulscale8(pPosture->frontAccel, forward);
 409              else
 410                  forward = mulscale8(pPosture->backAccel, forward);
 411              if (predict.at6a)
 412                  forward = mulscale16(forward, speed);
 413              predict.at5c += mulscale30(forward, x);
 414              predict.at60 += mulscale30(forward, y);
 415          }
 416          if (pInput->strafe)
 417          {
 418              int strafe = pInput->strafe;
 419              strafe = mulscale8(pPosture->sideAccel, strafe);
 420              if (predict.at6a)
 421                  strafe = mulscale16(strafe, speed);
 422              predict.at5c += mulscale30(strafe, y);
 423              predict.at60 -= mulscale30(strafe, x);
 424          }
 425      }
 426      if (pInput->q16turn)
 427      {
 428          if (VanillaMode())
 429              predict.at30 = ((predict.at30&0x7ff0000)+(pInput->q16turn&0x7ff0000))&0x7ffffff;
 430          else
 431              predict.at30 = (predict.at30+pInput->q16turn)&0x7ffffff;
 432      }
 433      if (pInput->keyFlags.spin180)
 434          if (!predict.at4c)
 435              predict.at4c = -kAng180;
 436      if (predict.at4c < 0)
 437      {
 438          const int speed = (predict.at48 == 1) ? 64 : 128;
 439          predict.at4c = min(predict.at4c+speed, 0);
 440          predict.at30 += fix16_from_int(speed);
 441          if (numplayers > 1 && gPrediction)
 442              gViewAngleAdjust += float(ClipHigh(-predict.at4c, speed)); // don't overturn when nearing end of spin
 443      }
 444  
 445      if (!predict.at71)
 446          predict.at6f = 0;
 447  
 448      switch (predict.at48)
 449      {
 450      case 1:
 451          if (predict.at71)
 452              predict.at64 -= pPosture->normalJumpZ;//0x5b05;
 453          if (pInput->buttonFlags.crouch)
 454              predict.at64 += pPosture->normalJumpZ;//0x5b05;
 455          break;
 456      case 2:
 457          if (!pInput->buttonFlags.crouch)
 458              predict.at48 = 0;
 459          break;
 460      default:
 461          if (gFlyMode)
 462              break;
 463          if (!predict.at6f && predict.at71 && predict.at6a == 0) {
 464              if (packItemActive(pPlayer, kPackJumpBoots)) predict.at64 = pPosture->pwupJumpZ;//-0x175555;
 465              else predict.at64 = pPosture->normalJumpZ;//-0xbaaaa;
 466              predict.at6f = 1;
 467          }
 468          if (pInput->buttonFlags.crouch)
 469              predict.at48 = 2;
 470          break;
 471      }
 472  #if 0
 473      if (predict.at6e && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown)
 474      {
 475          if (predict.at20 < 0)
 476              predict.at20 = fix16_min(predict.at20+F16(4), F16(0));
 477          if (predict.at20 > 0)
 478              predict.at20 = fix16_max(predict.at20-F16(4), F16(0));
 479          if (predict.at20 == 0)
 480              predict.at6e = 0;
 481      }
 482      else
 483      {
 484          if (pInput->buttonFlags.lookUp)
 485              predict.at20 = fix16_min(predict.at20+F16(4), F16(60));
 486          if (pInput->buttonFlags.lookDown)
 487              predict.at20 = fix16_max(predict.at20-F16(4), F16(-60));
 488      }
 489      predict.at20 = fix16_clamp(predict.at20+pInput->q16mlook, F16(-60), F16(60));
 490  
 491      if (predict.at20 > 0)
 492          predict.at24 = mulscale30(F16(120), Sin(fix16_to_int(predict.at20<<3)));
 493      else if (predict.at20 < 0)
 494          predict.at24 = mulscale30(F16(180), Sin(fix16_to_int(predict.at20<<3)));
 495      else
 496          predict.at24 = 0;
 497  #endif
 498      CONSTEXPR int upAngle = 289;
 499      CONSTEXPR int downAngle = -347;
 500      CONSTEXPR double lookStepUp = 4.0*upAngle/60.0;
 501      CONSTEXPR double lookStepDown = -4.0*downAngle/60.0;
 502      if (predict.at6e && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown)
 503      {
 504          if (predict.at20 < 0)
 505              predict.at20 = fix16_min(predict.at20+F16(lookStepDown), F16(0));
 506          if (predict.at20 > 0)
 507              predict.at20 = fix16_max(predict.at20-F16(lookStepUp), F16(0));
 508          if (predict.at20 == 0)
 509              predict.at6e = 0;
 510      }
 511      else
 512      {
 513          if (pInput->buttonFlags.lookUp)
 514              predict.at20 = fix16_min(predict.at20+F16(lookStepUp), F16(upAngle));
 515          if (pInput->buttonFlags.lookDown)
 516              predict.at20 = fix16_max(predict.at20-F16(lookStepDown), F16(downAngle));
 517      }
 518      if (numplayers > 1 && gPrediction)
 519      {
 520          if (pInput->buttonFlags.lookUp)
 521          {
 522              gViewLookAdjust += float(lookStepUp);
 523          }
 524          if (pInput->buttonFlags.lookDown)
 525          {
 526              gViewLookAdjust -= float(lookStepDown);
 527          }
 528          gViewLookRecenter = predict.at6e && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown;
 529      }
 530      predict.at20 = fix16_clamp(predict.at20+(pInput->q16mlook<<3), F16(downAngle), F16(upAngle));
 531      predict.at24 = fix16_from_float(100.f*tanf(fix16_to_float(predict.at20)*fPI/1024.f));
 532  
 533      int nSector = predict.at68;
 534      int florhit = predict.at75.florhit & 0xc000;
 535      char va;
 536      if (predict.at6a < 16 && (florhit == 0x4000 || florhit == 0))
 537          va = 1;
 538      else
 539          va = 0;
 540      if (va && (sector[nSector].floorstat&2) != 0)
 541      {
 542          int z1 = getflorzofslope(nSector, predict.at50, predict.at54);
 543          int x2 = predict.at50+mulscale30(64, Cos(fix16_to_int(predict.at30)));
 544          int y2 = predict.at54+mulscale30(64, Sin(fix16_to_int(predict.at30)));
 545          short nSector2 = nSector;
 546          updatesector(x2, y2, &nSector2);
 547          if (nSector2 == nSector)
 548          {
 549              int z2 = getflorzofslope(nSector2, x2, y2);
 550              predict.at28 = interpolate(predict.at28, fix16_from_int(z1-z2)>>3, 0x4000, 1);
 551          }
 552      }
 553      else
 554      {
 555          predict.at28 = interpolate(predict.at28, 0, 0x4000, 1);
 556          if (klabs(predict.at28) < 4)
 557              predict.at28 = 0;
 558      }
 559      predict.at2c = (-fix16_to_int(predict.at24))<<7;
 560  }
 561  
 562  void fakePlayerProcess(PLAYER *pPlayer, GINPUT *pInput)
 563  {
 564      spritetype *pSprite = pPlayer->pSprite;
 565      XSPRITE *pXSprite = pPlayer->pXSprite;
 566      POSTURE* pPosture = &pPlayer->pPosture[pPlayer->lifeMode][predict.at48];
 567  
 568      int top, bottom;
 569      GetSpriteExtents(pSprite, &top, &bottom);
 570  
 571      top += predict.at58-pSprite->z;
 572      bottom += predict.at58-pSprite->z;
 573  
 574      int dzb = (bottom-predict.at58)/4;
 575      int dzt = (predict.at58-top)/4;
 576  
 577      int dw = pSprite->clipdist<<2;
 578      short nSector = predict.at68;
 579      if (!gNoClip)
 580      {
 581          pushmove_old((int32_t*)&predict.at50, (int32_t*)&predict.at54, (int32_t*)&predict.at58, &predict.at68, dw, dzt, dzb, CLIPMASK0);
 582          if (predict.at68 == -1)
 583              predict.at68 = nSector;
 584      }
 585      fakeProcessInput(pPlayer, pInput);
 586  
 587      int nSpeed = approxDist(predict.at5c, predict.at60);
 588  
 589      predict.at3c = interpolate(predict.at3c, predict.at64, 0x7000, 1);
 590      int dz = predict.at58-pPosture->eyeAboveZ-predict.at38;
 591      if (dz > 0)
 592          predict.at3c += mulscale16(dz<<8, 0xa000);
 593      else
 594          predict.at3c += mulscale16(dz<<8, 0x1800);
 595      predict.at38 += predict.at3c>>8;
 596  
 597      predict.at44 = interpolate(predict.at44, predict.at64, 0x5000, 1);
 598      dz = predict.at58-pPosture->weaponAboveZ-predict.at40;
 599      if (dz > 0)
 600          predict.at44 += mulscale16(dz<<8, 0x8000);
 601      else
 602          predict.at44 += mulscale16(dz<<8, 0xc00);
 603      predict.at40 += predict.at44>>8;
 604  
 605      predict.at34 = predict.at40 - predict.at38 - (12<<8);
 606  
 607      predict.at0 = ClipLow(predict.at0-kTicsPerFrame, 0);
 608  
 609      nSpeed >>= 16;
 610      if (predict.at48 == 1)
 611      {
 612          predict.at4 = (predict.at4+17)&2047;
 613          predict.at14 = (predict.at14+17)&2047;
 614          predict.at8 = mulscale30(10*pPosture->bobV,Sin(predict.at4*2));
 615          predict.atc = mulscale30(predict.at0*pPosture->bobH,Sin(predict.at4-256));
 616          predict.at18 = mulscale30(predict.at0*pPosture->swayV,Sin(predict.at14*2));
 617          predict.at1c = mulscale30(predict.at0*pPosture->swayH,Sin(predict.at14-0x155));
 618      }
 619      else
 620      {
 621          if (pXSprite->height < 256)
 622          {
 623              predict.at4 = (predict.at4+(pPosture->pace[predict.at70]*4))&2047;
 624              predict.at14 = (predict.at14+(pPosture->pace[predict.at70]*4)/2)&2047;
 625              const int clampPhase = predict.at70 ? 60 : 30;
 626              if (predict.at0 < clampPhase)
 627                  predict.at0 = ClipHigh(predict.at0 + nSpeed, clampPhase);
 628          }
 629          predict.at8 = mulscale30(predict.at0*pPosture->bobV,Sin(predict.at4*2));
 630          predict.atc = mulscale30(predict.at0*pPosture->bobH,Sin(predict.at4-256));
 631          predict.at18 = mulscale30(predict.at0*pPosture->swayV,Sin(predict.at14*2));
 632          predict.at1c = mulscale30(predict.at0*pPosture->swayH,Sin(predict.at14-0x155));
 633      }
 634      if (!pXSprite->health)
 635          return;
 636      predict.at72 = 0;
 637      if (predict.at48 == 1)
 638      {
 639          predict.at72 = 1;
 640          int nSector = predict.at68;
 641          int nLink = gLowerLink[nSector];
 642          if (nLink > 0 && (sprite[nLink].type == kMarkerLowGoo || sprite[nLink].type == kMarkerLowWater))
 643          {
 644              if (getceilzofslope(nSector, predict.at50, predict.at54) > predict.at38)
 645                  predict.at72 = 0;
 646          }
 647      }
 648  }
 649  
 650  void fakeMoveDude(spritetype *pSprite)
 651  {
 652      PLAYER *pPlayer = NULL;
 653      int bottom, top;
 654      if (IsPlayerSprite(pSprite))
 655          pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
 656      dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
 657      GetSpriteExtents(pSprite, &top, &bottom);
 658      top += predict.at58 - pSprite->z;
 659      bottom += predict.at58 - pSprite->z;
 660      int bz = (bottom-predict.at58)/4;
 661      int tz = (predict.at58-top)/4;
 662      int wd = pSprite->clipdist*4;
 663      int nSector = predict.at68;
 664      dassert(nSector >= 0 && nSector < kMaxSectors);
 665      if (predict.at5c || predict.at60)
 666      {
 667          int vx = predict.at5c;
 668          int vy = predict.at60;
 669          if (gGameOptions.nEnemySpeed && !pPlayer) // if enemy speed modifier is enabled
 670          {
 671              vx = mulscale16(vx, (fix16_from_int(gGameOptions.nEnemySpeed)>>1) + fix16_from_int(1));
 672              vy = mulscale16(vy, (fix16_from_int(gGameOptions.nEnemySpeed)>>1) + fix16_from_int(1));
 673          }
 674          else if (pPlayer && gGameOptions.nPlayerSpeed && !VanillaMode())
 675          {
 676              vx = mulscale16(vx, (fix16_from_int(gGameOptions.nPlayerSpeed)>>2) + fix16_from_int(1));
 677              vy = mulscale16(vy, (fix16_from_int(gGameOptions.nPlayerSpeed)>>2) + fix16_from_int(1));
 678          }
 679          vx >>= 12;
 680          vy >>= 12;
 681          if (pPlayer && gNoClip)
 682          {
 683              predict.at50 += vx;
 684              predict.at54 += vy;
 685              if (!FindSector(predict.at50, predict.at54, &nSector))
 686                  nSector = predict.at68;
 687          }
 688          else
 689          {
 690              short bakCstat = pSprite->cstat;
 691              pSprite->cstat &= ~257;
 692              predict.at75.hit = ClipMove(&predict.at50, &predict.at54, &predict.at58, &nSector, vx, vy, wd, tz, bz, CLIPMASK0);
 693              if (nSector == -1)
 694                  nSector = predict.at68;
 695                      
 696              if (sector[nSector].type >= kSectorPath && sector[nSector].type <= kSectorRotate)
 697              {
 698                  short nSector2 = nSector;
 699                  pushmove_old((int32_t*)&predict.at50, (int32_t*)&predict.at54, (int32_t*)&predict.at58, &nSector2, wd, tz, bz, CLIPMASK0);
 700                  if (nSector2 != -1)
 701                      nSector = nSector2;
 702              }
 703  
 704              dassert(nSector >= 0);
 705  
 706              pSprite->cstat = bakCstat;
 707          }
 708          switch (predict.at75.hit&0xc000)
 709          {
 710          case 0x8000:
 711          {
 712              int nHitWall = predict.at75.hit&0x3fff;
 713              walltype *pHitWall = &wall[nHitWall];
 714              if (pHitWall->nextsector != -1)
 715              {
 716                  sectortype *pHitSector = &sector[pHitWall->nextsector];
 717                  if (top < pHitSector->ceilingz || bottom > pHitSector->floorz)
 718                  {
 719                      // ???
 720                  }
 721              }
 722              actWallBounceVector(&predict.at5c, &predict.at60, nHitWall, 0);
 723              break;
 724          }
 725          }
 726      }
 727      if (predict.at68 != nSector)
 728      {
 729          dassert(nSector >= 0 && nSector < kMaxSectors);
 730          predict.at68 = nSector;
 731      }
 732      char bUnderwater = 0;
 733      char bDepth = 0;
 734      int nXSector = sector[nSector].extra;
 735      if (nXSector > 0)
 736      {
 737          XSECTOR *pXSector = &xsector[nXSector];
 738          if (pXSector->Underwater)
 739              bUnderwater = 1;
 740          if (pXSector->Depth)
 741              bDepth = 1;
 742      }
 743      int nUpperLink = gUpperLink[nSector];
 744      int nLowerLink = gLowerLink[nSector];
 745      if (nUpperLink >= 0 && (sprite[nUpperLink].type == kMarkerUpWater || sprite[nUpperLink].type == kMarkerUpGoo))
 746          bDepth = 1;
 747      if (nLowerLink >= 0 && (sprite[nLowerLink].type == kMarkerLowWater || sprite[nLowerLink].type == kMarkerLowGoo))
 748          bDepth = 1;
 749      if (pPlayer)
 750          wd += 16;
 751  
 752      if (predict.at64)
 753          predict.at58 += predict.at64 >> 8;
 754  
 755      spritetype pSpriteBak = *pSprite;
 756      spritetype *pTempSprite = pSprite;
 757      pTempSprite->x = predict.at50;
 758      pTempSprite->y = predict.at54;
 759      pTempSprite->z = predict.at58;
 760      pTempSprite->sectnum = predict.at68;
 761      int ceilZ, ceilHit, floorZ, floorHit;
 762      GetZRange(pTempSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, CLIPMASK0);
 763      GetSpriteExtents(pTempSprite, &top, &bottom);
 764      if (predict.at73 & 2)
 765      {
 766          int vc = 58254;
 767          if (bDepth)
 768          {
 769              if (bUnderwater)
 770              {
 771                  int cz = getceilzofslope(nSector, predict.at50, predict.at54);
 772                  if (cz > top)
 773                      vc += ((bottom-cz)*-80099) / (bottom-top);
 774                  else
 775                      vc = 0;
 776              }
 777              else
 778              {
 779                  int fz = getflorzofslope(nSector, predict.at50, predict.at54);
 780                  if (fz < bottom)
 781                      vc += ((bottom-fz)*-80099) / (bottom-top);
 782              }
 783          }
 784          else
 785          {
 786              if (bUnderwater)
 787                  vc = 0;
 788              else if (bottom >= floorZ)
 789                  vc = 0;
 790          }
 791          if (vc)
 792          {
 793              predict.at58 += ((vc*4)/2)>>8;
 794              predict.at64 += vc;
 795          }
 796      }
 797      GetSpriteExtents(pTempSprite, &top, &bottom);
 798      if (bottom >= floorZ)
 799      {
 800          int floorZ2 = floorZ;
 801          int floorHit2 = floorHit;
 802          GetZRange(pTempSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0, PARALLAXCLIP_CEILING|PARALLAXCLIP_FLOOR);
 803          if (bottom <= floorZ && predict.at58-floorZ2 < bz)
 804          {
 805              floorZ = floorZ2;
 806              floorHit = floorHit2;
 807          }
 808      }
 809      if (floorZ <= bottom)
 810      {
 811          predict.at75.florhit = floorHit;
 812          predict.at58 += floorZ-bottom;
 813          int var44 = predict.at64-velFloor[predict.at68];
 814          if (var44 > 0)
 815          {
 816              actFloorBounceVector(&predict.at5c, &predict.at60, &var44, predict.at68, 0);
 817              predict.at64 = var44;
 818              if (klabs(predict.at64) < 0x10000)
 819              {
 820                  predict.at64 = velFloor[predict.at68];
 821                  predict.at73 &= ~4;
 822              }
 823              else
 824                  predict.at73 |= 4;
 825          }
 826          else if (predict.at64 == 0)
 827              predict.at73 &= ~4;
 828      }
 829      else
 830      {
 831          predict.at75.florhit = 0;
 832          if (predict.at73 & 2)
 833              predict.at73 |= 4;
 834      }
 835      if (top <= ceilZ)
 836      {
 837          predict.at75.ceilhit = ceilHit;
 838          predict.at58 += ClipLow(ceilZ-top, 0);
 839          if (predict.at64 <= 0 && (predict.at73&4))
 840              predict.at64 = mulscale16(-predict.at64, 0x2000);
 841      }
 842      else
 843          predict.at75.ceilhit = 0;
 844  
 845      GetSpriteExtents(pTempSprite, &top, &bottom);
 846      *pSprite = pSpriteBak;
 847      predict.at6a = ClipLow(floorZ-bottom, 0)>>8;
 848      if (predict.at5c || predict.at60)
 849      {
 850          if ((floorHit & 0xc000) == 0xc000)
 851          {
 852              int nHitSprite = floorHit & 0x3fff;
 853              if ((sprite[nHitSprite].cstat & 0x30) == 0)
 854              {
 855                  predict.at5c += mulscale2(4, predict.at50 - sprite[nHitSprite].x);
 856                  predict.at60 += mulscale2(4, predict.at54 - sprite[nHitSprite].y);
 857                  return;
 858              }
 859          }
 860          int nXSector = sector[pSprite->sectnum].extra;
 861          if (nXSector > 0 && xsector[nXSector].Underwater)
 862              return;
 863          if (predict.at6a >= 0x100)
 864              return;
 865          int nDrag = gDudeDrag;
 866          if (predict.at6a > 0)
 867              nDrag -= scale(gDudeDrag, predict.at6a, 0x100);
 868          predict.at5c -= mulscale16r(predict.at5c, nDrag);
 869          predict.at60 -= mulscale16r(predict.at60, nDrag);
 870          if (approxDist(predict.at5c, predict.at60) < 0x1000)
 871              predict.at5c = predict.at60 = 0;
 872      }
 873  }
 874  
 875  void fakeActAirDrag(spritetype *pSprite, int num)
 876  {
 877      UNREFERENCED_PARAMETER(pSprite);
 878      int xvec = 0;
 879      int yvec = 0;
 880      int nSector = predict.at68;
 881      dassert(nSector >= 0 && nSector < kMaxSectors);
 882      sectortype *pSector = &sector[nSector];
 883      int nXSector = pSector->extra;
 884      if (nXSector > 0)
 885      {
 886          dassert(nXSector < kMaxXSectors);
 887          XSECTOR *pXSector = &xsector[nXSector];
 888          if (pXSector->windVel && (pXSector->windAlways || pXSector->busy))
 889          {
 890              int vel = pXSector->windVel<<12;
 891              if (!pXSector->windAlways && pXSector->busy)
 892                  vel = mulscale16(vel, pXSector->busy);
 893              xvec = mulscale30(vel, Cos(pXSector->windAng));
 894              yvec = mulscale30(vel, Sin(pXSector->windAng));
 895          }
 896      }
 897      predict.at5c += mulscale16(xvec-predict.at5c, num);
 898      predict.at60 += mulscale16(yvec-predict.at60, num);
 899      predict.at64 -= mulscale16(predict.at64, num);
 900  }
 901  
 902  void fakeActProcessSprites(void)
 903  {
 904      spritetype *pSprite = gMe->pSprite;
 905      if (pSprite->statnum == kStatDude)
 906      {
 907          int nXSprite = pSprite->extra;
 908          dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
 909          int nSector = predict.at68;
 910          int nXSector = sector[nSector].extra;
 911          XSECTOR *pXSector = NULL;
 912          if (nXSector > 0)
 913          {
 914              dassert(nXSector > 0 && nXSector < kMaxXSectors);
 915              dassert(xsector[nXSector].reference == nSector);
 916              pXSector = &xsector[nXSector];
 917          }
 918          if (pXSector)
 919          {
 920              int top, bottom;
 921              GetSpriteExtents(pSprite, &top, &bottom);
 922              top += predict.at58 - pSprite->z;
 923              bottom += predict.at58 - pSprite->z;
 924              if (getflorzofslope(nSector, predict.at50, predict.at54) < bottom)
 925              {
 926                  int angle = pXSector->panAngle;
 927                  int speed = 0;
 928                  if (pXSector->panAlways || pXSector->state || pXSector->busy)
 929                  {
 930                      speed = pXSector->panVel << 9;
 931                      if (!pXSector->panAlways && pXSector->busy)
 932                          speed = mulscale16(speed, pXSector->busy);
 933                  }
 934                  if (sector[nSector].floorstat&64)
 935                      angle = (GetWallAngle(sector[nSector].wallptr)+512)&2047;
 936                  predict.at5c += mulscale30(speed,Cos(angle));
 937                  predict.at60 += mulscale30(speed,Sin(angle));
 938              }
 939          }
 940          if (pXSector && pXSector->Underwater)
 941              fakeActAirDrag(pSprite, 5376);
 942          else
 943              fakeActAirDrag(pSprite, 128);
 944  
 945          if ((predict.at73 & 4) != 0 || predict.at5c != 0 || predict.at60 != 0 || predict.at64 != 0 || velFloor[predict.at68] != 0 || velCeil[predict.at68] != 0)
 946          {
 947              fakeMoveDude(pSprite);
 948          }
 949      }
 950  }
 951  
 952  void viewCorrectPrediction(void)
 953  {
 954      if (numplayers == 1)
 955      {
 956          gViewLook = gMe->q16look;
 957          gViewAngle = gMe->q16ang;
 958          return;
 959      }
 960      spritetype *pSprite = gMe->pSprite;
 961      VIEW *pView = &predictFifo[(gNetFifoTail-1)&255];
 962      const char bCalPrediction = !VanillaMode() || (gMe->q16ang != pView->at30 || pView->at24 != gMe->q16horiz || pView->at50 != pSprite->x || pView->at54 != pSprite->y || pView->at58 != pSprite->z);
 963      if (bCalPrediction)
 964      {
 965          viewInitializePrediction();
 966          predictOld = gPrevView[myconnectindex];
 967          gPredictTail = gNetFifoTail;
 968          while (gPredictTail < gNetFifoHead[myconnectindex])
 969          {
 970              viewUpdatePrediction(&gFifoInput[gPredictTail&255][myconnectindex]);
 971          }
 972      }
 973  }
 974  
 975  void viewBackupView(int nPlayer)
 976  {
 977      PLAYER *pPlayer = &gPlayer[nPlayer];
 978      VIEW *pView = &gPrevView[nPlayer];
 979      pView->at30 = pPlayer->q16ang;
 980      pView->at50 = pPlayer->pSprite->x;
 981      pView->at54 = pPlayer->pSprite->y;
 982      if (!VanillaMode())
 983          pView->at58 = pPlayer->pSprite->z;
 984      pView->at5c = xvel[pPlayer->pSprite->index];
 985      pView->at60 = yvel[pPlayer->pSprite->index];
 986      pView->at38 = pPlayer->zView;
 987      pView->at34 = pPlayer->zWeapon-pPlayer->zView-0xc00;
 988      pView->at24 = pPlayer->q16horiz;
 989      pView->at28 = pPlayer->q16slopehoriz;
 990      pView->at2c = pPlayer->slope;
 991      pView->at8 = pPlayer->bobHeight;
 992      pView->atc = pPlayer->bobWidth;
 993      pView->at18 = pPlayer->swayHeight;
 994      pView->at1c = pPlayer->swayWidth;
 995  }
 996  
 997  void viewCorrectViewOffsets(int nPlayer, vec3_t const *oldpos)
 998  {
 999      PLAYER *pPlayer = &gPlayer[nPlayer];
1000      VIEW *pView = &gPrevView[nPlayer];
1001      pView->at50 += pPlayer->pSprite->x-oldpos->x;
1002      pView->at54 += pPlayer->pSprite->y-oldpos->y;
1003      if (!VanillaMode())
1004          pView->at58 += pPlayer->pSprite->z-oldpos->z;
1005      pView->at38 += pPlayer->pSprite->z-oldpos->z;
1006  }
1007  
1008  static char bInterpWarnVanilla = 0;
1009  
1010  void viewClearInterpolations(void)
1011  {
1012      nInterpolations = 0;
1013      nInterpolationsPanning = 0;
1014      memset(gInterpolateSprite, 0, sizeof(gInterpolateSprite));
1015      memset(gInterpolateWall, 0, sizeof(gInterpolateWall));
1016      memset(gInterpolateSector, 0, sizeof(gInterpolateSector));
1017      memset(gInterpolatePanningWall, 0, sizeof(gInterpolatePanningWall));
1018      memset(gInterpolatePanningCeiling, 0, sizeof(gInterpolatePanningCeiling));
1019      memset(gInterpolatePanningFloor, 0, sizeof(gInterpolatePanningFloor));
1020      bInterpWarnVanilla = 0;
1021  }
1022  
1023  void viewAddInterpolation(void *data, INTERPOLATE_TYPE type)
1024  {
1025      if (nInterpolations == kMaxInterpolations)
1026      {
1027          ThrowError("Too many interpolations");
1028      }
1029      else if (!bInterpWarnVanilla && (nInterpolations >= kMaxInterpolationsVanilla) && VanillaMode())
1030      {
1031          viewSetSystemMessage("Warning: Interpolations over vanilla limit (%d/%d)\n", nInterpolations, kMaxInterpolationsVanilla);
1032          bInterpWarnVanilla = 1;
1033      }
1034      if ((nInterpolationsPanning == kMaxInterpolationsPanning) && (type == INTERPOLATE_TYPE_CHAR_PANNING)) // too many texture panning interpolations, don't add anymore
1035          return;
1036      INTERPOLATE *pInterpolate = &gInterpolation[nInterpolations++];
1037      pInterpolate->pointer = data;
1038      pInterpolate->type = type;
1039      switch (type)
1040      {
1041      case INTERPOLATE_TYPE_INT:
1042          pInterpolate->value = *((int*)data);
1043          break;
1044      case INTERPOLATE_TYPE_SHORT:
1045          pInterpolate->value = *((short*)data);
1046          break;
1047      case INTERPOLATE_TYPE_CHAR_PANNING:
1048          nInterpolationsPanning++;
1049          fallthrough__;
1050      case INTERPOLATE_TYPE_CHAR:
1051          pInterpolate->value = *((char*)data);
1052          break;
1053      }
1054  }
1055  
1056  void CalcInterpolations(void)
1057  {
1058      int i, value;
1059      INTERPOLATE *pInterpolate = gInterpolation;
1060      for (i = 0; i < nInterpolations; i++, pInterpolate++)
1061      {
1062          value = pInterpolate->value;
1063          switch (pInterpolate->type)
1064          {
1065          case INTERPOLATE_TYPE_INT:
1066          {
1067              const int value2 = *((int*)pInterpolate->pointer);
1068              pInterpolate->value2 = value2;
1069              if (value2 != value)
1070                  *((int*)pInterpolate->pointer) = (int)interpolate(value, value2, gInterpolate);
1071              break;
1072          }
1073          case INTERPOLATE_TYPE_SHORT:
1074          {
1075              const int value2 = *((short*)pInterpolate->pointer);
1076              pInterpolate->value2 = value2;
1077              if (value2 != value)
1078                  *((short*)pInterpolate->pointer) = (short)interpolate(value, value2, gInterpolate);
1079              break;
1080          }
1081          case INTERPOLATE_TYPE_CHAR_PANNING:
1082          case INTERPOLATE_TYPE_CHAR:
1083          {
1084              const int value2 = *((char*)pInterpolate->pointer);
1085              pInterpolate->value2 = value2;
1086              const int nDiff = value - value2;
1087              if (nDiff == 0)
1088                  break;
1089              if (nDiff > 127) // handle overflow gracefully
1090                  value -= 256;
1091              else if (nDiff < -128)
1092                  value += 256;
1093              *((char*)pInterpolate->pointer) = (char)interpolate(value, value2, gInterpolate);
1094              break;
1095          }
1096          }
1097      }
1098  }
1099  
1100  void RestoreInterpolations(void)
1101  {
1102      int i;
1103      INTERPOLATE *pInterpolate = gInterpolation;
1104      for (i = 0; i < nInterpolations; i++, pInterpolate++)
1105      {
1106          switch (pInterpolate->type)
1107          {
1108          case INTERPOLATE_TYPE_INT:
1109              *((int*)pInterpolate->pointer) = pInterpolate->value2;
1110              break;
1111          case INTERPOLATE_TYPE_SHORT:
1112              *((short*)pInterpolate->pointer) = pInterpolate->value2;
1113              break;
1114          case INTERPOLATE_TYPE_CHAR_PANNING:
1115          case INTERPOLATE_TYPE_CHAR:
1116              *((char*)pInterpolate->pointer) = pInterpolate->value2;
1117              break;
1118          }
1119      }
1120  }
1121  
1122  void viewDrawText(int nFont, const char *pString, int x, int y, int nShade, int nPalette, int position, char shadow, unsigned int nStat, uint8_t alpha, COLORSTR *pColorStr)
1123  {
1124      const int bakPal = nPalette;
1125      if (nFont < 0 || nFont >= kFontNum || !pString) return;
1126      FONT *pFont = &gFont[nFont];
1127  
1128      y += pFont->yoff;
1129  
1130      if (position)
1131      {
1132          const char *s = pString;
1133          int width = -pFont->space;
1134          while (*s)
1135          {
1136              int nTile = ((*s-' ')&127)+pFont->tile;
1137              if (tilesiz[nTile].x && tilesiz[nTile].y)
1138                  width += tilesiz[nTile].x+pFont->space;
1139              s++;
1140          }
1141          if (position == 1)
1142              width >>= 1;
1143          x -= width;
1144      }
1145      for (int i = 0; pString[i]; i++)
1146      {
1147          if (pColorStr)
1148          {
1149              if (i == pColorStr->nColor1[0]) // first color position, change palette
1150                  nPalette = pColorStr->nPal1;
1151              else if (i == pColorStr->nColor2[0]) // second color position, change palette
1152                  nPalette = pColorStr->nPal2;
1153              else if ((i == pColorStr->nColor1[1]) || (i == pColorStr->nColor2[1])) // end of colored text, restore palette
1154                  nPalette = bakPal;
1155          }
1156          int nTile = ((pString[i]-' ')&127) + pFont->tile;
1157          if (tilesiz[nTile].x && tilesiz[nTile].y)
1158          {
1159              if (shadow)
1160              {
1161                  rotatesprite_fs_alpha((x+1)<<16, (y+1)<<16, 65536, 0, nTile, 127, nPalette, 26|nStat, alpha);
1162              }
1163              rotatesprite_fs_alpha(x<<16, y<<16, 65536, 0, nTile, nShade, nPalette, 26|nStat, alpha);
1164              x += tilesiz[nTile].x+pFont->space;
1165          }
1166      }
1167  }
1168  
1169  void viewTileSprite(int nTile, int nShade, int nPalette, int x1, int y1, int x2, int y2, int nWidth, int nHeight, int nScale)
1170  {
1171      Rect rect1 = Rect(x1, y1, x2, y2);
1172      Rect rect2 = Rect(0, 0, xdim, ydim);
1173      rect1 &= rect2;
1174  
1175      if (!rect1)
1176          return;
1177  
1178      dassert(nTile >= 0 && nTile < kMaxTiles);
1179      int width = !nWidth ? tilesiz[nTile].x : nWidth;
1180      int height = !nHeight ? tilesiz[nTile].y : nHeight;
1181      int bx1 = DecBy(rect1.x0+1, width);
1182      int by1 = DecBy(rect1.y0+1, height);
1183      int bx2 = IncBy(rect1.x1-1, width);
1184      int by2 = IncBy(rect1.y1-1, height);
1185      for (int x = bx1; x < bx2; x += width)
1186          for (int y = by1; y < by2; y += height)
1187              rotatesprite(x<<16, y<<16, nScale, 0, nTile, nShade, nPalette, 64+16+8, x1, y1, x2-1, y2-1);
1188  }
1189  
1190  void InitStatusBar(void)
1191  {
1192      tileLoadTile(2200);
1193  }
1194  void DrawStatSprite(int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat, int nScale)
1195  {
1196      rotatesprite(x<<16, y<<16, nScale, 0, nTile, nShade, nPalette, nStat | 74, 0, 0, xdim-1, ydim-1);
1197  }
1198  void DrawStatMaskedSprite(int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat, int nScale, char bMirror)
1199  {
1200      int16_t ang = 0;
1201      if (bMirror)
1202      {
1203          nStat |= RS_YFLIP;
1204          ang = kAng180;
1205      }
1206      rotatesprite(x<<16, y<<16, nScale, ang, nTile, nShade, nPalette, nStat | 10, 0, 0, xdim-1, ydim-1);
1207  }
1208  
1209  void DrawStatNumber(const char *pFormat, int nNumber, int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat, int nScale, char bShadow)
1210  {
1211      if (bShadow == 1) // do shadow first
1212          DrawStatNumber(pFormat, nNumber, nTile, x, y, nShade, nPalette, nStat, nScale, 2);
1213      char tempbuf[80];
1214      int width = tilesiz[nTile].x+1;
1215      x <<= 16;
1216      y <<= 16;
1217      if (bShadow == 2) // offset for shadow based on scale of number
1218      {
1219          x += nScale;
1220          y += nScale;
1221          nShade = 127;
1222      }
1223      sprintf(tempbuf, pFormat, nNumber);
1224      const size_t nLength = strlen(tempbuf);
1225      for (size_t i = 0; i < nLength; i++, x += width*nScale)
1226      {
1227          int numTile, numScale, numY;
1228          if (tempbuf[i] == ' ')
1229              continue;
1230          numY = y;
1231          if (tempbuf[i] == '-')
1232          {
1233              switch (nTile)
1234              {
1235              case 2190:
1236              case kSBarNumberHealth:
1237                  numTile = kSBarNegative;
1238                  break;
1239              case 2240:
1240              case kSBarNumberAmmo:
1241                  numTile = kSBarNegative+1;
1242                  break;
1243              case kSBarNumberInv:
1244                  numTile = kSBarNegative+2;
1245                  break;
1246              case kSBarNumberArmor1:
1247                  numTile = kSBarNegative+3;
1248                  break;
1249              case kSBarNumberArmor2:
1250                  numTile = kSBarNegative+4;
1251                  break;
1252              case kSBarNumberArmor3:
1253                  numTile = kSBarNegative+5;
1254                  break;
1255              default: // unknown font tile type, skip drawing minus sign
1256                  continue;
1257              }
1258              numScale = nScale/3;
1259              numY += (1>>15); // offset to center of number row
1260          }
1261          else // regular number
1262          {
1263              numTile = nTile+tempbuf[i]-'0';
1264              numScale = nScale;
1265          }
1266          rotatesprite(x, numY, numScale, 0, numTile, nShade, nPalette, nStat | 10, 0, 0, xdim-1, ydim-1);
1267      }
1268  }
1269  
1270  void TileHGauge(int nTile, int x, int y, int nMult, int nDiv, int nStat, int nScale)
1271  {
1272      int bx = scale(mulscale16(tilesiz[nTile].x,nScale),nMult,nDiv)+x;
1273      int sbx;
1274      switch (nStat&(512+256))
1275      {
1276      case 256:
1277          sbx = mulscale16(bx, xscalecorrect)-1;
1278          break;
1279      case 512:
1280          bx -= 320;
1281          sbx = xdim+mulscale16(bx, xscalecorrect)-1;
1282          break;
1283      default:
1284          bx -= 160;
1285          sbx = (xdim>>1)+mulscale16(bx, xscalecorrect)-1;
1286          break;
1287      }
1288      rotatesprite(x<<16, y<<16, nScale, 0, nTile, 0, 0, nStat|90, 0, 0, sbx, ydim-1);
1289  }
1290  
1291  int gPackIcons[kPackMax] = {
1292      2569, 2564, 2566, 2568, 2560
1293  };
1294  
1295  struct PACKICON2 {
1296      short nTile;
1297      int nScale;
1298      int nYOffs;
1299  };
1300  
1301  PACKICON2 gPackIcons2[kPackMax] = {
1302      { 519, (int)(65536*0.5), 0 },
1303      { 830, (int)(65536*0.3), 0 },
1304      { 760, (int)(65536*0.6), 0 },
1305      { 839, (int)(65536*0.5), -4 },
1306      { 827, (int)(65536*0.4), 0 },
1307  };
1308  
1309  struct AMMOICON {
1310      short nTile;
1311      int nScale;
1312      int nYOffs;
1313  };
1314  
1315  AMMOICON gAmmoIcons[] = {
1316      { -1, 0, 0 },
1317      { 816, (int)(65536 * 0.5), 0 },
1318      { 619, (int)(65536 * 0.8), 0 },
1319      { 817, (int)(65536 * 0.7), 3 },
1320      { 801, (int)(65536 * 0.5), -6 },
1321      { 589, (int)(65536 * 0.7), 2 },
1322      { 618, (int)(65536 * 0.5), 4 },
1323      { 548, (int)(65536 * 0.3), -6 },
1324      { 820, (int)(65536 * 0.3), -6 },
1325      { 525, (int)(65536 * 0.6), -6 },
1326      { 811, (int)(65536 * 0.5), 2 },
1327      { 810, (int)(65536 * 0.45), 2 },
1328  };
1329  
1330  struct WEAPONICON {
1331      short nTile;
1332      char zOffset;
1333  };
1334  
1335  WEAPONICON gWeaponIcon[] = {
1336      { -1, 0 },
1337      { -1, 0 }, // 1: pitchfork
1338      { 524, 4 }, // 2: flare gun
1339      { 559, 7 }, // 3: shotgun
1340      { 558, 5 }, // 4: tommy gun
1341      { 526, 11 }, // 5: napalm launcher
1342      { 589, 7 }, // 6: dynamite
1343      { 618, 6 }, // 7: spray can
1344      { 539, 11 }, // 8: tesla gun
1345      { 800, 0 }, // 9: life leech
1346      { 525, 13 }, // 10: voodoo doll
1347      { 811, 7 }, // 11: proxy bomb
1348      { 810, 7 }, // 12: remote bomb
1349      { -1, 0 },
1350  };
1351  
1352  WEAPONICON gWeaponIconVoxel[] = {
1353      { -1, 0 },
1354      { -1, 0 }, // 1: pitchfork
1355      { 524, 6 }, // 2: flare gun
1356      { 559, 6 }, // 3: shotgun
1357      { 558, 8 }, // 4: tommy gun
1358      { 526, 6 }, // 5: napalm launcher
1359      { 589, 11 }, // 6: dynamite
1360      { 618, 11 }, // 7: spray can
1361      { 539, 6 }, // 8: tesla gun
1362      { 800, 0 }, // 9: life leech
1363      { 525, 11 }, // 10: voodoo doll
1364      { 811, 11 }, // 11: proxy bomb
1365      { 810, 11 }, // 12: remote bomb
1366      { -1, 0 },
1367  };
1368  
1369  void viewDrawStats(PLAYER *pPlayer, int x, int y)
1370  {
1371      UNREFERENCED_PARAMETER(pPlayer);
1372      COLORSTR colorStr, colorStrKills, colorStrSecrets;
1373      const int nFont = 3;
1374      char buffer[128];
1375      if (!gLevelStats || (gLevelStatsOnlyOnMap && (gViewMode == 3)))
1376          return;
1377  
1378      colorStr.nPal1 = 2; // set text group palette
1379      colorStr.nPal2 = 8; // 8: gold
1380      colorStr.nColor1[0] = 0; // color first two characters of stat string
1381      colorStr.nColor1[1] = 2;
1382      colorStr.nColor2[0] = colorStr.nColor2[1] = -1; // unused
1383      colorStrKills = colorStrSecrets = colorStr;
1384  
1385      y += 20;
1386      int nHeight;
1387      viewGetFontInfo(nFont, NULL, NULL, &nHeight);
1388      if (gGameOptions.nGameType <= kGameTypeCoop) // only show secrets counter for single-player/co-op mode
1389      {
1390          sprintf(buffer, "S:%d/%d", gSecretMgr.nNormalSecretsFound, VanillaMode() ? gSecretMgr.nAllSecrets : max(gSecretMgr.nNormalSecretsFound, gSecretMgr.nAllSecrets)); // if we found more than there are, increase the total - some levels have a bugged counter
1391          if (gSecretMgr.nNormalSecretsFound && (gSecretMgr.nNormalSecretsFound >= gSecretMgr.nAllSecrets)) // if all secrets found, set counter to gold
1392              colorStrSecrets.nColor2[0] = 2; // set valid start position for gold color
1393          viewDrawText(3, buffer, x, y, 20, 0, 0, true, 256, 0, &colorStrSecrets);
1394          y -= (nHeight + 1);
1395      }
1396      if ((gGameOptions.nMonsterSettings > 0) && (max(gKillMgr.at4, gKillMgr.at0) > 0))
1397      {
1398          sprintf(buffer, "K:%d/%d", gKillMgr.at4, max(gKillMgr.at4, gKillMgr.at0));
1399          if (gKillMgr.at0 && (gKillMgr.at4 >= gKillMgr.at0)) // if killed all enemies in level, set counter to gold
1400              colorStrKills.nColor2[0] = 2; // set valid start position for gold color
1401          viewDrawText(3, buffer, x, y, 20, 0, 0, true, 256, 0, &colorStrKills);
1402          y -= (nHeight + 1);
1403      }
1404      const int nLevelTime = (gGameOptions.nGameType >= kGameTypeBloodBath) && (gGameOptions.uNetGameFlags&kNetGameFlagTimeLimitMask) ? ClipLow(gPlayerRoundTimeLimit - gLevelTime, 0) : gLevelTime;
1405      sprintf(buffer, "T:%d:%02d.%02d",
1406          (nLevelTime / (kTicsPerSec * 60)),
1407          (nLevelTime / kTicsPerSec) % 60,
1408          ((nLevelTime % kTicsPerSec) * 33) / 10
1409      );
1410      viewDrawText(3, buffer, x, y, 20, 0, 0, true, 256, 0, &colorStr);
1411  }
1412  
1413  void viewDrawSpeed(void)
1414  {
1415      if (!gShowSpeed)
1416          return;
1417      char buffer[128];
1418  
1419      sprintf(buffer, "%d", gPlayerSpeed / 10000);
1420      viewDrawText(3, buffer, 160, 157, -128, 0, 1, 1, 0, 0);
1421  }
1422  
1423  #define kMaxBurnFlames 9
1424  
1425  const struct BURNTABLE {
1426      short nTile;
1427      unsigned char nStat;
1428      unsigned char nPal;
1429      int nScale;
1430      short nX, nY;
1431  } gBurnTable[kMaxBurnFlames] = {
1432      {2101, RS_AUTO, 0, 118784,  10, 220},
1433      {2101, RS_AUTO, 0, 110592,  40, 220},
1434      {2101, RS_AUTO, 0,  81920,  85, 220},
1435      {2101, RS_AUTO, 0,  69632, 120, 220},
1436      {2101, RS_AUTO, 0,  61440, 160, 220},
1437      {2101, RS_AUTO, 0,  73728, 200, 220},
1438      {2101, RS_AUTO, 0,  77824, 235, 220},
1439      {2101, RS_AUTO, 0, 110592, 275, 220},
1440      {2101, RS_AUTO, 0, 122880, 310, 220}
1441  };
1442  
1443  int gBurnTableAspectOffset[kMaxBurnFlames] = {0};
1444  
1445  void viewBurnTimeInit(void)
1446  {
1447      if (!r_usenewaspect) return;
1448  
1449      for (int i = 0; i < kMaxBurnFlames; i++)
1450      {
1451          int nX = gBurnTable[i].nX;
1452          nX = scale(nX-(320>>1), 320>>1, 266>>1); // scale flame position
1453          nX = scale(nX<<16, xscale, yscale); // multiply by window ratio
1454          nX += (320>>1)<<16; // offset to center
1455          gBurnTableAspectOffset[i] = nX;
1456      }
1457  }
1458  
1459  void viewBurnTime(int gScale)
1460  {
1461      if (!gScale) return;
1462  
1463      for (int i = 0; i < kMaxBurnFlames; i++)
1464      {
1465          const BURNTABLE *pBurnTable = &gBurnTable[i];
1466          const int nTile = gBurnTable[i].nTile+qanimateoffs(pBurnTable->nTile,32768+i);
1467          int nScale = pBurnTable->nScale;
1468          if (gScale < 600)
1469              nScale = scale(nScale, gScale, 600);
1470          const int nX = r_usenewaspect ? gBurnTableAspectOffset[i] : pBurnTable->nX<<16;
1471          rotatesprite(nX, pBurnTable->nY<<16, nScale, 0, nTile,
1472              0, pBurnTable->nPal, pBurnTable->nStat, windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
1473      }
1474  }
1475  
1476  #define kPowerUps 14
1477  
1478  const struct POWERUPDISPLAY {
1479      int nTile;
1480      int nScaleRatio;
1481      int yOffset;
1482  } gPowerups[kPowerUps] = {
1483      {gPowerUpInfo[kPwUpShadowCloak].picnum, fix16_from_float(0.4f), 0}, // invisibility
1484      {gPowerUpInfo[kPwUpReflectShots].picnum, fix16_from_float(0.4f), 5}, // reflects enemy shots
1485      {gPowerUpInfo[kPwUpDeathMask].picnum, fix16_from_float(0.3f), 9}, // invulnerability
1486      {gPowerUpInfo[kPwUpTwoGuns].picnum, fix16_from_float(0.25f), 4}, // guns akimbo
1487      {9300, fix16_from_float(0.45f), 7}, // quad damage
1488      {gPowerUpInfo[kPwUpShadowCloakUseless].picnum, fix16_from_float(0.4f), 9}, // shadow cloak (does nothing, only appears at near the end of CP04)
1489  
1490      // not in official maps
1491      {gPowerUpInfo[kPwUpFeatherFall].picnum, fix16_from_float(0.3f), 7}, // feather fall
1492      {gPowerUpInfo[kPwUpGasMask].picnum, fix16_from_float(0.4f), 4}, // gas mask
1493      {gPowerUpInfo[kPwUpDoppleganger].picnum, fix16_from_float(0.5f), 5}, // doppelganger
1494      {gPowerUpInfo[kPwUpAsbestArmor].picnum, fix16_from_float(0.3f), 9}, // asbestos armor
1495      {gPowerUpInfo[kPwUpGrowShroom].picnum, fix16_from_float(0.4f), 4}, // grow shroom
1496      {gPowerUpInfo[kPwUpShrinkShroom].picnum, fix16_from_float(0.4f), 4}, // shrink shroom
1497  
1498      {gPowerUpInfo[kPwUpDivingSuit].picnum, fix16_from_float(0.3f), 9}, // diving suit supply
1499      {1131, fix16_from_float(0.32f), -1}, // underwater air supply
1500  };
1501  
1502  void viewDrawPowerUps(PLAYER* pPlayer)
1503  {
1504      if (!gPowerupDuration)
1505          return;
1506  
1507      int nPowerActive[kPowerUps];
1508      nPowerActive[0] = pPlayer->pwUpTime[kPwUpShadowCloak]; // invisibility
1509      nPowerActive[1] = pPlayer->pwUpTime[kPwUpReflectShots]; // reflects enemy shots
1510      nPowerActive[2] = pPlayer->pwUpTime[kPwUpDeathMask]; // invulnerability
1511      if (powerupCheck(pPlayer, kPwUpTwoGuns) && gGameOptions.bQuadDamagePowerup && !VanillaMode()) // if quad damage is enabled, use quad damage icon from notblood.pk3/TILES099.ART
1512          nPowerActive[3] = 0, nPowerActive[4] = !gMatrixMode ? pPlayer->pwUpTime[kPwUpTwoGuns] : 0; // quad damage
1513      else
1514          nPowerActive[3] = !gMatrixMode ? pPlayer->pwUpTime[kPwUpTwoGuns] : 0, nPowerActive[4] = 0; // guns akimbo
1515      nPowerActive[5] = pPlayer->pwUpTime[kPwUpShadowCloakUseless]; // shadow cloak
1516  
1517      // not in official maps
1518      nPowerActive[6] = pPlayer->pwUpTime[kPwUpFeatherFall]; // feather fall
1519      nPowerActive[7] = pPlayer->pwUpTime[kPwUpGasMask]; // gas mask
1520      nPowerActive[8] = pPlayer->pwUpTime[kPwUpDoppleganger]; // doppelganger
1521      nPowerActive[9] = pPlayer->pwUpTime[kPwUpAsbestArmor]; // asbestos armor
1522      nPowerActive[10] = pPlayer->pwUpTime[kPwUpGrowShroom]; // grow shroom
1523      nPowerActive[11] = pPlayer->pwUpTime[kPwUpShrinkShroom]; // shrink shroom
1524  
1525      nPowerActive[12] = pPlayer->isUnderwater && packItemActive(pPlayer, kPackDivingSuit) ? pPlayer->pwUpTime[kPwUpDivingSuit] : 0; // diving suit supply
1526      nPowerActive[13] = !gPowerupShowOxygenSupply ? 0 : pPlayer->isUnderwater && !packItemActive(pPlayer, kPackDivingSuit) ? ClipLow(pPlayer->underwaterTime, 1) : 0; // underwater air supply
1527  
1528      int nSortPower[kPowerUps+1];
1529      unsigned char nSortIndex[kPowerUps+1];
1530      unsigned char nSortCount = 0;
1531      for (int i = 0; i < kPowerUps; i++) // sort powerups
1532      {
1533          if (!nPowerActive[i])
1534              continue;
1535          nSortIndex[nSortCount] = i;
1536          nSortPower[nSortCount] = nPowerActive[i];
1537          nSortCount++;
1538      }
1539      for (int i = 1; i < nSortCount; i++)
1540      {
1541          for (int j = 0; j < nSortCount-i; j++)
1542          {
1543              if (nSortPower[j] <= nSortPower[j+1])
1544                  continue;
1545              nSortPower[kPowerUps] = nSortPower[j];
1546              nSortPower[j] = nSortPower[j+1];
1547              nSortPower[j+1] = nSortPower[kPowerUps];
1548              nSortIndex[kPowerUps] = nSortIndex[j];
1549              nSortIndex[j] = nSortIndex[j+1];
1550              nSortIndex[j+1] = nSortIndex[kPowerUps];
1551          }
1552      }
1553  
1554      const int nWarning = 5;
1555      int y = 50;
1556      char buffer[8];
1557      for (int i = 0; i < nSortCount; i++)
1558      {
1559          const POWERUPDISPLAY *pPowerups = &gPowerups[nSortIndex[i]];
1560          int nTime = nSortPower[i] / gPowerupTicks;
1561          if (nTime > nWarning || ((int)totalclock & 32))
1562          {
1563              if (gPowerupStyle)
1564                  DrawStatMaskedSprite(pPowerups->nTile, 283+xscalepowerups, y + pPowerups->yOffset - 2, 0, 0, 512, mulscale16(fix16_from_float(1.75f), pPowerups->nScaleRatio));
1565              else
1566                  DrawStatMaskedSprite(pPowerups->nTile, 15-xscalepowerups, y + pPowerups->yOffset, 0, 0, 256, pPowerups->nScaleRatio);
1567          }
1568  
1569          if (gPowerupStyle)
1570          {
1571              Bsprintf(buffer, "%02d", nTime);
1572              viewDrawText(3, buffer, 309+xscalepowerups, y-6, 0, nTime > nWarning ? 0 : 2, 2, 0, 512);
1573              y += 35;
1574          }
1575          else
1576          {
1577              DrawStatNumber("%d", nTime, kSBarNumberInv, 15 - xscalepowerups + 15, y, 0, nTime > nWarning ? 0 : 2, 256, fix16_from_float(0.5f));
1578              y += 20;
1579          }
1580      }
1581  }
1582  
1583  static float viewDrawParametricBlend(const float t)
1584  {
1585      const float sqt = t * t;
1586      return sqt / (2.0f * (sqt - t) + 1.0f);
1587  }
1588  
1589  void viewDrawWeaponSelect(PLAYER* pPlayer, XSPRITE *pXSprite)
1590  {
1591      const int weaponIcons[][4] =
1592      {
1593          {  -1, 0, 0x8000, 0}, // NULL
1594          {3131, 0, 0x6000, 0}, // 1: pitchfork
1595          { 524, 8, 0x8000, 0}, // 2: flare gun
1596          { 559, 6, 0x8000, 0}, // 3: shotgun
1597          { 558, 8, 0x8000, 0}, // 4: tommy gun
1598          { 526, 2, 0x8000, 1}, // 5: napalm launcher
1599          { 589, 8, 0xB000, 0}, // 6: dynamite
1600          { 618, 9, 0x8000, 0}, // 7: spray can
1601          { 539, 0, 0x8000, 0}, // 8: tesla gun
1602          { 800, 0, 0x7000, 0}, // 9: life leech
1603          { 525, 1, 0x8000, 0}, // 10: voodoo doll
1604          { 811, 8, 0x8000, 0}, // 11: proxy bomb
1605          { 810, 8, 0x8000, 0}, // 12: remote bomb
1606          {  -1, 0, 0x8000, 0}, // NULL
1607      };
1608  
1609      const float animAdjustMinPos = 44 / 34; // adjusting the animation minimum position requires also adding a factor multiply to attack/decay speed so existing config settings are compatible
1610      const float animPosMax = gShowWeaponSelectPosition, animPosMin = -20;
1611      const int attackTime = (int)((float)(gShowWeaponSelectTimeStart*kTicsPerFrame)*animAdjustMinPos);
1612      const int holdTime = gShowWeaponSelectTimeHold*kTicsPerFrame;
1613      const int decayTime = (int)((float)(gShowWeaponSelectTimeEnd*kTicsPerFrame)*animAdjustMinPos);
1614  
1615      const float animPosRange = animPosMax + (-animPosMin);
1616      const int lerpTime = gViewInterpolate ? rotatespritesmoothratio / (65536 / kTicsPerFrame) : 0; // don't use interpolate value if view interpolation is disabled
1617      const int curClock = numplayers > 1 ? int(totalclock)/4U : gLevelTime; // use totalclock for multiplayer (lag friendly 120-based timer)
1618      const int curTime = (curClock*kTicsPerFrame)+lerpTime;
1619      static int animClock = 0, animState = 0;
1620      float animPos = 0;
1621  
1622      const bool timeDiffTooBig = ((animClock - (attackTime+holdTime+decayTime)) > curTime) || ((animClock + (attackTime+holdTime+decayTime)) < curTime);
1623      if ((gWeaponRadialMenuState > 0) || (curTime < (50*kTicsPerFrame)) || (animState && timeDiffTooBig)) // if radial menu is active, player just started level, or the clock is impossibly far ahead (eg player quickloaded)
1624      {
1625          animClock = curTime;
1626          animState = 0;
1627          return;
1628      }
1629  
1630      int statsOffset = 0;
1631      if (gGameOptions.nGameType != kGameTypeSinglePlayer) // offset for multiplayer stats bar
1632      {
1633          for (int nRows = (gNetPlayers - 1) / 4; nRows >= 0; nRows--)
1634          {
1635              statsOffset += 9;
1636          }
1637      }
1638  
1639      const bool playerIsDead = !pXSprite || (pXSprite->health == 0);
1640      switch (animState)
1641      {
1642      default:
1643      case 0: // idle
1644          animClock = curTime;
1645          animState = 0;
1646          animPos = 0;
1647          if (playerIsDead) // if player is dead, don't allow weapon bar to start animation
1648              return;
1649          if (pPlayer->input.newWeapon != 0) // player switched weapon, set start weapon animation state
1650              animState = 1;
1651          return;
1652      case 1: // init animation
1653          if (curTime == animClock) // if we're at the same tick, wait for next frame
1654              return;
1655          animPos = 0;
1656          animState = 2;
1657          break;
1658      case 2: // travel animation
1659          animPos = (float)(curTime - animClock) / (float)attackTime;
1660          if ((animClock + attackTime) <= curTime) // finish opening animation
1661          {
1662              animState = 3;
1663              animClock = curTime;
1664              animPos = 1;
1665          }
1666          break;
1667      case 3: // hold animation
1668          animPos = 1;
1669          if (playerIsDead) // player has died, transition to closing
1670          {
1671              animState = 4;
1672              animClock = curTime;
1673          }
1674          if (pPlayer->input.newWeapon != 0) // changed weapon, reset the clock
1675              animClock = curTime;
1676          if ((animClock + holdTime) <= curTime) // time's up, transition to closing
1677          {
1678              animState = 4;
1679              animClock = curTime;
1680          }
1681          break;
1682      case 4: // closing animation
1683          animPos = 1.f-((float)(curTime - animClock) / (float)decayTime);
1684          if (pPlayer->input.newWeapon != 0) // changed weapon, set to opening state
1685          {
1686              animState = 2;
1687              animClock = curTime - (int)(animPos * (float)attackTime);
1688              return;
1689          }
1690          if ((animClock + decayTime) <= curTime) // finished closing
1691          {
1692              animState = 0;
1693              animClock = curTime;
1694              animPos = 0;
1695          }
1696          break;
1697      }
1698  
1699      const int bakCurWeapon = pPlayer->curWeapon;
1700      while ((pPlayer->curWeapon == kWeaponNone) || WeaponIsEquipable(pPlayer, pPlayer->input.newWeapon)) // if we're switching between weapons, use the next weapon value or find a valid weapon
1701      {
1702          pPlayer->curWeapon = pPlayer->input.newWeapon;
1703          if (WeaponIsEquipable(pPlayer, pPlayer->curWeapon, false))
1704              break;
1705          pPlayer->curWeapon = pPlayer->nextWeapon;
1706          if (WeaponIsEquipable(pPlayer, pPlayer->curWeapon, false))
1707              break;
1708          pPlayer->curWeapon = (pPlayer->weaponAmmo != -1) ? pPlayer->weaponAmmo+1 : kWeaponPitchfork;
1709          if (WeaponIsEquipable(pPlayer, pPlayer->curWeapon, false))
1710              break;
1711          pPlayer->curWeapon = WeaponFindLoaded(pPlayer, NULL);
1712          if (WeaponIsEquipable(pPlayer, pPlayer->curWeapon, false))
1713              break;
1714          pPlayer->curWeapon = kWeaponNone;
1715          break;
1716      }
1717      const char weaponPrev = ClipRange(WeaponFindNext(pPlayer, NULL, 0), 1, 12);
1718      const char weaponCur = ClipRange(pPlayer->curWeapon, 0, 13);
1719      const char weaponNext = ClipRange(WeaponFindNext(pPlayer, NULL, 1), 1, 12);
1720      const bool showThreeWeapons = (weaponPrev != weaponCur) && (weaponNext != weaponCur);
1721      pPlayer->curWeapon = bakCurWeapon;
1722  
1723      const float nScale = (float)gShowWeaponSelectScale/10.f;
1724      const int x = 640/4;
1725      const int xoffset = (int)(640.f/(11.f/nScale));
1726      int yPrimary = (int)((viewDrawParametricBlend(animPos * 1.1f) * animPosRange) + animPosMin);
1727      if ((gViewSize > 3) && (gShowWeaponSelect == 1)) // if full hud is displayed, bump up by a few pixels
1728          yPrimary += 24;
1729      else if (gShowWeaponSelect == 2) // if drawn at the top, offset by the multiplayer stats bar height
1730          yPrimary += statsOffset;
1731      int ySecondary = yPrimary;
1732      if (gShowWeaponSelect == 1) // draw at the bottom instead
1733          yPrimary = -yPrimary + 195, ySecondary = -ySecondary + 195;
1734      yPrimary += (int)(6.f*nScale); // lower center icon
1735  
1736      const int nShadeTicks = 75;
1737      char nShade = (char)(curTime%nShadeTicks)-(nShadeTicks/2);
1738      if (nShade > (nShadeTicks/2)) // ping-pong fade effect
1739          nShade = -nShade;
1740      nShade = (nShade>>2)+(nShade>>3); // decrease overall shade difference by 37.5%
1741  
1742      const int picnumCur = weaponIcons[weaponCur][0];
1743      const int yCur = yPrimary + (int)((float)weaponIcons[weaponCur][1]*nScale);
1744      const int scaleCur = (int)((float)weaponIcons[weaponCur][2]*nScale);
1745      const bool mirrorCur = weaponIcons[weaponCur][3];
1746      DrawStatMaskedSprite(picnumCur, x, yCur, 256+nShade, 0, 0, scaleCur, mirrorCur);
1747      if (!WeaponIsEquipable(pPlayer, weaponCur)) // if current weapon is unavailable, draw cross over icon
1748          DrawStatMaskedSprite(1142, x, yPrimary, 256, 12, 0, 0x2000);
1749      if (!showThreeWeapons)
1750          return;
1751      const int picnumPrev = weaponIcons[weaponPrev][0];
1752      const int picnumNext = weaponIcons[weaponNext][0];
1753      const int yPrev = ySecondary + (int)((float)weaponIcons[weaponPrev][1]*nScale);
1754      const int yNext = ySecondary + (int)((float)weaponIcons[weaponNext][1]*nScale);
1755      const int scalePrev = (int)((float)weaponIcons[weaponPrev][2]*nScale);
1756      const int scaleNext = (int)((float)weaponIcons[weaponNext][2]*nScale);
1757      const bool mirrorPrev = weaponIcons[weaponPrev][3];
1758      const bool mirrorNext = weaponIcons[weaponNext][3];
1759      DrawStatMaskedSprite(picnumPrev, x-xoffset, yPrev, 256, 0, 0, scalePrev, mirrorPrev);
1760      DrawStatMaskedSprite(picnumNext, x+xoffset, yNext, 256, 0, 0, scaleNext, mirrorNext);
1761      if (!WeaponIsEquipable(pPlayer, weaponPrev)) // if previous weapon is unavailable, draw cross over icon
1762          DrawStatMaskedSprite(1142, x-xoffset, ySecondary, 256, 12, 0, 0x2000);
1763      if (!WeaponIsEquipable(pPlayer, weaponNext)) // if next weapon is unavailable, draw cross over icon
1764          DrawStatMaskedSprite(1142, x+xoffset, ySecondary, 256, 12, 0, 0x2000);
1765  }
1766  
1767  void viewDrawWeaponRadialMenu(PLAYER *pPlayer, XSPRITE *pXSprite, const int nPal)
1768  {
1769      const struct WEAPONICON
1770      {
1771          int nTile;
1772          int nSlot : 8;
1773          int nX : 8;
1774          int nY : 8;
1775          int bMirror : 8;
1776          unsigned short nScale;
1777      } weaponRadialInfo[kWeaponMax] =
1778      {
1779          {  -1, 0,  0,  0, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // NULL
1780          {3131, 0,  0, -6, 0, -fix16_from_float(0.1f)+fix16_from_float(0.35f),  }, // 1: pitchfork
1781          { 524, 1,  0,  0, 0, -fix16_from_float(0.1f)+fix16_from_float(0.55f),  }, // 2: flare gun
1782          { 559, 3,  0,  0, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 3: shotgun
1783          { 558, 2,  0, -2, 0, -fix16_from_float(0.1f)+fix16_from_float(0.45f),  }, // 4: tommy gun
1784          { 526, 8,  0, -1, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 5: napalm launcher
1785          { 589, 5, -1,  4, 0, -fix16_from_float(0.1f)+fix16_from_float(0.6875f),}, // 6: dynamite
1786          { 618, 4,  0,  5, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 7: spray can
1787          { 539, 9,  0, -6, 1, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 8: tesla gun
1788          { 800, 11, 2,  0, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 9: life leech
1789          { 525, 10, 2, -7, 1, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 10: voodoo doll
1790          { 811, 6, -1,  3, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 11: proxy bomb
1791          { 810, 7,  1,  4, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // 12: remote bomb
1792          {  -1, 0,  0,  0, 0, -fix16_from_float(0.1f)+fix16_from_float(0.5f),   }, // NULL
1793      };
1794      const short nWeaponRadialPos[12][2] = // weapon icon slot to angle position
1795      {
1796          {(short)mulscale30(46, Sin( 1*kAng30)), (short)-mulscale30(46, Cos( 1*kAng30))},
1797          {(short)mulscale30(46, Sin( 2*kAng30)), (short)-mulscale30(46, Cos( 2*kAng30))},
1798          {(short)mulscale30(46, Sin( 3*kAng30)), (short)-mulscale30(46, Cos( 3*kAng30))},
1799          {(short)mulscale30(46, Sin( 4*kAng30)), (short)-mulscale30(46, Cos( 4*kAng30))},
1800          {(short)mulscale30(46, Sin( 5*kAng30)), (short)-mulscale30(46, Cos( 5*kAng30))},
1801          {(short)mulscale30(46, Sin( 6*kAng30)), (short)-mulscale30(46, Cos( 6*kAng30))},
1802          {(short)mulscale30(46, Sin( 7*kAng30)), (short)-mulscale30(46, Cos( 7*kAng30))},
1803          {(short)mulscale30(46, Sin( 8*kAng30)), (short)-mulscale30(46, Cos( 8*kAng30))},
1804          {(short)mulscale30(46, Sin( 9*kAng30)), (short)-mulscale30(46, Cos( 9*kAng30))},
1805          {(short)mulscale30(46, Sin(10*kAng30)), (short)-mulscale30(46, Cos(10*kAng30))},
1806          {(short)mulscale30(46, Sin(11*kAng30)), (short)-mulscale30(46, Cos(11*kAng30))},
1807          {(short)mulscale30(46, Sin(12*kAng30)), (short)-mulscale30(46, Cos(12*kAng30))},
1808      };
1809      const short nWeaponRadialReticlePos[12][2] = // select reticle slot to angle position
1810      {
1811          {(short)mulscale30(45, Sin(short( 5.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 5.f * (kAng360 / 12.f))))},
1812          {(short)mulscale30(45, Sin(short( 4.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 4.f * (kAng360 / 12.f))))},
1813          {(short)mulscale30(45, Sin(short( 3.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 3.f * (kAng360 / 12.f))))},
1814          {(short)mulscale30(45, Sin(short( 2.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 2.f * (kAng360 / 12.f))))},
1815          {(short)mulscale30(45, Sin(short( 1.02f*(kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 1.02f*(kAng360 / 12.f))))},
1816          {(short)mulscale30(45, Sin(0)),                              (short)mulscale30(45, Cos(0))},
1817          {(short)mulscale30(45, Sin(short(11.02f*(kAng360 / 12.f)))), (short)mulscale30(45, Cos(short(11.02f*(kAng360 / 12.f))))},
1818          {(short)mulscale30(45, Sin(short(10.05f*(kAng360 / 12.f)))), (short)mulscale30(45, Cos(short(10.05f*(kAng360 / 12.f))))},
1819          {(short)mulscale30(45, Sin(short( 9.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 9.f * (kAng360 / 12.f))))},
1820          {(short)mulscale30(45, Sin(short( 8.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 8.f * (kAng360 / 12.f))))},
1821          {(short)mulscale30(45, Sin(short( 7.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 7.f * (kAng360 / 12.f))))},
1822          {(short)mulscale30(45, Sin(short( 6.f * (kAng360 / 12.f)))), (short)mulscale30(45, Cos(short( 6.f * (kAng360 / 12.f))))},
1823      };
1824      static int nOldValScale[kWeaponMax] = {0};
1825  
1826      const char bPlayerIsDead = !pXSprite || (pXSprite->health == 0);
1827      if (bPlayerIsDead)
1828          return;
1829  
1830      static int nLastLerpTime = 0, nLastLerpDelta = 0;
1831      int tempLerp = rotatespritesmoothratio;
1832      if (tempLerp > nLastLerpTime)
1833          tempLerp = (65536+tempLerp)-nLastLerpTime;
1834      else
1835          tempLerp = nLastLerpTime-tempLerp;
1836      int nLerpTime = tempLerp&65535;
1837      if (nLerpTime > 65536>>1) // fix weird overflow
1838          nLerpTime = 65536-nLerpTime;
1839      if (!gRadialMenuSlowDown || (gGameOptions.nGameType > kGameTypeSinglePlayer)) // not slowed down
1840          nLerpTime = divscale16(nLerpTime, fix16_from_int(17));
1841      nLastLerpTime = rotatespritesmoothratio;
1842      nLastLerpDelta = (nLerpTime+nLastLerpDelta)&65535;
1843  
1844      const char nWeaponCur = ClipRange(gWeaponRadialMenuChoice, kWeaponNone, kWeaponBeast);
1845      const int nPingPong = Sin((nLastLerpDelta>>5)&2047)>>20;
1846  
1847      if (gRadialMenuDimBackground)
1848          viewDimScreen();
1849      if (!gRadialMenuDimHud)
1850          DrawStatMaskedSprite(9287, gRadialMenuPosition, (200>>1)-(200>>5), 16, nPal, RS_AUTO, fix16_from_float(0.56f));
1851      if (gWeaponRadialMenuChoice != -1) // render reticle
1852      {
1853          const int nSlot = weaponRadialInfo[nWeaponCur].nSlot;
1854          const int nX = (int)nWeaponRadialReticlePos[nSlot][0];
1855          const int nY = (int)nWeaponRadialReticlePos[nSlot][1];
1856          DrawStatMaskedSprite(624, gRadialMenuPosition+nX, (200>>1)-(200>>5)+nY, gRadialMenuDimHud ? -64 : 0, 9, gRadialMenuDimHud ? RS_AUTO : RS_AUTO|RS_TRANS_MASK, fix16_from_float(71.f / 64.f * 0.25f));
1857      }
1858      if (gRadialMenuDimHud)
1859          DrawStatMaskedSprite(9287, gRadialMenuPosition, (200>>1)-(200>>5), 24, nPal, RS_AUTO|RS_TRANS_MASK, fix16_from_float(0.56f));
1860      for (int i = kWeaponPitchfork; i <= kWeaponRemoteTNT; i++)
1861      {
1862          if (!WeaponIsEquipable(pPlayer, i))
1863              continue;
1864          const int nWheelSlot = weaponRadialInfo[i].nSlot;
1865          const int nTile = weaponRadialInfo[i].nTile;
1866          const int nShade = i == nWeaponCur ? ClipLow(-8-(nPingPong>>5), -128) : 14;
1867          int nPal = i == nWeaponCur ? 0 : 5;
1868          const int nFlags = i == nWeaponCur ? RS_AUTO : RS_AUTO|RS_TRANS_MASK;
1869          const int nScale = (int)weaponRadialInfo[i].nScale;
1870          const char bMirror = weaponRadialInfo[i].bMirror;
1871  
1872          int nX = (int)nWeaponRadialPos[nWheelSlot][0];
1873          int nY = (int)nWeaponRadialPos[nWheelSlot][1];
1874          if (nY > 0)
1875              nY -= 5;
1876          nX += weaponRadialInfo[i].nX;
1877          nY += weaponRadialInfo[i].nY;
1878          if (i == nWeaponCur) // drop shadow
1879          {
1880              DrawStatMaskedSprite(nTile, gRadialMenuPosition+nX+1, (200>>1)+nY+1, 127, nPal, nFlags|RS_TRANS_MASK, nScale, bMirror);
1881              nOldValScale[i] = interpolate(nOldValScale[i], nPingPong+0x1800, ClipHigh(nLerpTime<<4, fix16_one));
1882          }
1883          else
1884              nOldValScale[i] = interpolate(nOldValScale[i], 0, ClipHigh(nLerpTime<<5, fix16_one));
1885          DrawStatMaskedSprite(nTile, gRadialMenuPosition+nX, (200>>1)+nY, nShade, nPal, nFlags, nScale+nOldValScale[i], bMirror);
1886          int nAmmo = pPlayer->ammoCount[i-1];
1887          if (i == kWeaponPitchfork)
1888              continue;
1889          const int nMaxAmmo = gAmmoInfo[i-1].max;
1890          if (nAmmo >= nMaxAmmo>>1) // above half ammo
1891              nPal = 10; // 10: blue
1892          else if (nAmmo >= (nMaxAmmo>>2)-(nMaxAmmo>>3)) // above eighth ammo
1893              nPal = 8; // 8: gold
1894          else // low ammo
1895              nPal = 7; // 7: red
1896          if (i == kWeaponSprayCan)
1897              nAmmo /= 10;
1898          nX = gRadialMenuPosition+nX+(nX>>4);
1899          nY = (200>>1)+(int)nWeaponRadialReticlePos[nWheelSlot][1]+(nY>>4);
1900          DrawStatNumber("%3d", nAmmo, 2230, nX, nY, i == nWeaponCur ? -128 : 24, nPal, i == nWeaponCur ? 0 : RS_TRANS_MASK, fix16_from_float(0.8f), 1);
1901      }
1902  }
1903  
1904  void viewDrawMapTitle(void)
1905  {
1906      if (!gShowMapTitle || gGameMenuMgr.m_bActive)
1907          return;
1908  
1909      int const fadeStartTic = int((videoGetRenderMode() == REND_CLASSIC ? 1.25f : 1.f)*kTicsPerSec);
1910      int const fadeEndTic = int(1.5f*kTicsPerSec);
1911      if (gLevelTime > fadeEndTic)
1912          return;
1913      uint8_t const alpha = clamp((gLevelTime-fadeStartTic)*255/(fadeEndTic-fadeStartTic), 0, 255);
1914  
1915      if (alpha != 255)
1916      {
1917          viewDrawText(1, levelGetTitle(), 160, 50, -128, 0, 1, 1, 0, alpha);
1918      }
1919  }
1920  
1921  void viewDrawAimedPlayerName(void)
1922  {
1923      if (!gShowPlayerNames || (gGameOptions.nGameType == kGameTypeSinglePlayer) || !gView->pSprite)
1924          return;
1925      const int nX = gAimReticle == 2 ? gView->aim.dx : Cos(gView->pSprite->ang)>>16;
1926      const int nY = gAimReticle == 2 ? gView->aim.dy : Sin(gView->pSprite->ang)>>16;
1927      if (nX == 0 && nY == 0)
1928          return;
1929      const int nZ = gAimReticle == 2 ? gView->aim.dz : gView->slope;
1930  
1931      const int nDist = (gGameOptions.nGameType == kGameTypeCoop) ? 640 : 512; // set hitscan distance to 20/16 meters for co-op mode
1932      const int hit = HitScan(gView->pSprite, gView->zView, nX, nY, nZ, CLIPMASK0, nDist);
1933      if (hit == 3)
1934      {
1935          spritetype* pSprite = &sprite[gHitInfo.hitsprite];
1936          if (!IsPlayerSprite(pSprite))
1937              return;
1938          char nPlayer = pSprite->type-kDudePlayer1;
1939          if (powerupCheck(&gPlayer[nPlayer], kPwUpDoppleganger) && !IsTargetTeammate(gView, gPlayer[nPlayer].pSprite)) // if doppleganger powerup is active, set player id as viewer
1940              nPlayer = gView->pSprite->type-kDudePlayer1;
1941          char* szName = gProfile[nPlayer].name;
1942          int nPalette = !VanillaMode() ? playerColorPalAimName(gPlayer[nPlayer].teamIdPal) : playerColorPalDefault(gPlayer[nPlayer].teamIdPal);
1943          viewDrawText(4, szName, 160, 125, -128, nPalette, 1, 1);
1944      }
1945  }
1946  
1947  void viewDrawPack(PLAYER *pPlayer, int x, int y)
1948  {
1949      int packs[kPackMax];
1950      if (pPlayer->packItemTime)
1951      {
1952          int nPacks = 0;
1953          int width = 0;
1954          for (int i = 0; i < kPackMax; i++)
1955          {
1956              if (pPlayer->packSlots[i].curAmount)
1957              {
1958                  packs[nPacks++] = i;
1959                  width += tilesiz[gPackIcons[i]].x + 1;
1960              }
1961          }
1962          width /= 2;
1963          x -= width;
1964          for (int i = 0; i < nPacks; i++)
1965          {
1966              int nPack = packs[i];
1967              DrawStatSprite(2568, x+1, y-8);
1968              DrawStatSprite(2568, x+1, y-6);
1969              DrawStatSprite(gPackIcons[nPack], x+1, y+1);
1970              if (nPack == pPlayer->packItemId)
1971                  DrawStatMaskedSprite(2559, x+1, y+1);
1972              int nShade;
1973              if (pPlayer->packSlots[nPack].isActive)
1974                  nShade = 4;
1975              else
1976                  nShade = 24;
1977              DrawStatNumber("%3d", pPlayer->packSlots[nPack].curAmount, 2250, x-4, y-13, nShade, 0);
1978              x += tilesiz[gPackIcons[nPack]].x + 1;
1979          }
1980      }
1981  }
1982  
1983  void DrawPackItemInStatusBar(PLAYER *pPlayer, int x, int y, int x2, int y2, int nStat)
1984  {
1985      if (pPlayer->packItemId < 0) return;
1986  
1987      DrawStatSprite(gPackIcons[pPlayer->packItemId], x, y, 0, 0, nStat);
1988      DrawStatNumber("%3d", pPlayer->packSlots[pPlayer->packItemId].curAmount, 2250, x2, y2, 4, 0, nStat);
1989  }
1990  
1991  void DrawPackItemInStatusBar2(PLAYER *pPlayer, int x, int y, int x2, int y2, int nStat, int nScale)
1992  {
1993      if (pPlayer->packItemId < 0) return;
1994  
1995      DrawStatMaskedSprite(gPackIcons2[pPlayer->packItemId].nTile, x, y+gPackIcons2[pPlayer->packItemId].nYOffs, 0, 0, nStat, gPackIcons2[pPlayer->packItemId].nScale);
1996      DrawStatNumber("%3d", pPlayer->packSlots[pPlayer->packItemId].curAmount, kSBarNumberInv, x2, y2, 4, 0, nStat, nScale);
1997  }
1998  
1999  void viewDrawPlayerSlots(void)
2000  {
2001      for (int nRows = (gNetPlayers - 1) / 4; nRows >= 0; nRows--)
2002      {
2003          for (int nCol = 0; nCol < 4; nCol++)
2004          {
2005              DrawStatSprite(2229, 40 + nCol * 80, 4 + nRows * 9, 16);
2006          }
2007      }
2008  }
2009  
2010  char gTempStr[128];
2011  
2012  void viewDrawPlayerFrags(void)
2013  {
2014      viewDrawPlayerSlots();
2015      for (int i = 0, p = connecthead; p >= 0; i++, p = connectpoint2[p])
2016      {
2017          int x = 80 * (i & 3);
2018          int y = 9 * (i / 4);
2019          int col = playerColorPalDefault(gPlayer[p].teamIdPal);
2020          char* name = gProfile[p].name;
2021          if ((gProfile[p].skill == 2) || (gGameOptions.uNetGameFlags&kNetGameFlagSkillIssue))
2022              sprintf(gTempStr, "%s", name);
2023          else
2024              sprintf(gTempStr, "%s [%d]", name, gProfile[p].skill);
2025          Bstrupr(gTempStr);
2026          viewDrawText(4, gTempStr, x + 4, y + 1, -128, col, 0, 0);
2027          sprintf(gTempStr, "%2d", gPlayer[p].fragCount);
2028          viewDrawText(4, gTempStr, x + 76, y + 1, -128, col, 2, 0);
2029      }
2030  }
2031  
2032  void viewDrawPlayerFlags(void)
2033  {
2034      viewDrawPlayerSlots();
2035      for (int i = 0, p = connecthead; p >= 0; i++, p = connectpoint2[p])
2036      {
2037          int x = 80 * (i & 3);
2038          int y = 9 * (i / 4);
2039          int col = playerColorPalDefault(gPlayer[p].teamIdPal);
2040          char* name = gProfile[p].name;
2041          if ((gProfile[p].skill == 2) || (gGameOptions.uNetGameFlags&kNetGameFlagSkillIssue))
2042              sprintf(gTempStr, "%s", name);
2043          else
2044              sprintf(gTempStr, "%s [%d]", name, gProfile[p].skill);
2045          Bstrupr(gTempStr);
2046          viewDrawText(4, gTempStr, x + 4, y + 1, -128, col, 0, 0);
2047  
2048          sprintf(gTempStr, "F");
2049          x += 76;
2050          if (gPlayer[p].hasFlag & 2)
2051          {
2052              viewDrawText(4, gTempStr, x, y + 1, -128, playerColorPalDefault(1), 2, 0);
2053              x -= 6;
2054          }
2055  
2056          if (gPlayer[p].hasFlag & 1)
2057              viewDrawText(4, gTempStr, x, y + 1, -128, playerColorPalDefault(0), 2, 0);
2058      }
2059  }
2060  
2061  void viewDrawCtfHudVanilla(ClockTicks arg, int yOffset)
2062  {
2063      int x = 1, y = 1 + yOffset;
2064      if (gPlayerScoreTicks[0] == 0 || ((int)totalclock & 8))
2065      {
2066          viewDrawText(0, "BLUE", x, y, -128, kFlagBluePal, 0, 0, 256);
2067          gPlayerScoreTicks[0] = gPlayerScoreTicks[0] - arg;
2068          if (gPlayerScoreTicks[0] < 0)
2069              gPlayerScoreTicks[0] = 0;
2070          sprintf(gTempStr, "%-3d", gPlayerScores[0]);
2071          viewDrawText(0, gTempStr, x, y + 10, -128, kFlagBluePal, 0, 0, 256);
2072      }
2073      x = 319;
2074      if (gPlayerScoreTicks[1] == 0 || ((int)totalclock & 8))
2075      {
2076          viewDrawText(0, "RED", x, y, -128, kFlagRedPal, 2, 0, 512);
2077          gPlayerScoreTicks[1] = gPlayerScoreTicks[1] - arg;
2078          if (gPlayerScoreTicks[1] < 0)
2079              gPlayerScoreTicks[1] = 0;
2080          sprintf(gTempStr, "%3d", gPlayerScores[1]);
2081          viewDrawText(0, gTempStr, x, y + 10, -128, kFlagRedPal, 2, 0, 512);
2082      }
2083  }
2084  
2085  void flashTeamScore(ClockTicks arg, int team, bool show)
2086  {
2087      dassert(0 == team || 1 == team); // 0: blue, 1: red
2088  
2089      if (gPlayerScoreTicks[team] == 0 || ((int)totalclock & 8))
2090      {
2091          gPlayerScoreTicks[team] = gPlayerScoreTicks[team] - arg;
2092          if (gPlayerScoreTicks[team] < 0)
2093              gPlayerScoreTicks[team] = 0;
2094  
2095          if (show)
2096              DrawStatNumber("%d", gPlayerScores[team], kSBarNumberInv, 290+xscalectfhud, team ? 125 : 90, 0, playerColorPalSprite(team), 512, 65536 * 0.75);
2097      }
2098  }
2099  
2100  void viewDrawCtfHud(ClockTicks arg)
2101  {
2102      if (gViewSize == 0)
2103      {
2104          flashTeamScore(arg, 0, false);
2105          flashTeamScore(arg, 1, false);
2106          return;
2107      }
2108  
2109      bool blueFlagTaken = false;
2110      bool redFlagTaken = false;
2111      int blueFlagCarrierColor = 0;
2112      int redFlagCarrierColor = 0;
2113      for (int p = connecthead; p >= 0; p = connectpoint2[p])
2114      {
2115          if ((gPlayer[p].hasFlag & 1) != 0)
2116          {
2117              blueFlagTaken = true;
2118              blueFlagCarrierColor = playerColorPalSprite(gPlayer[p].teamId);
2119          }
2120          if ((gPlayer[p].hasFlag & 2) != 0)
2121          {
2122              redFlagTaken = true;
2123              redFlagCarrierColor = playerColorPalSprite(gPlayer[p].teamId);
2124          }
2125      }
2126  
2127      bool meHaveBlueFlag = gMe->hasFlag & 1;
2128      DrawStatMaskedSprite(meHaveBlueFlag ? 3558 : 3559, 320+xscalectfhud, 75, 0, playerColorPalSprite(0), 512, 65536 * 0.35);
2129      if (gBlueFlagDropped)
2130          DrawStatMaskedSprite(2332, 305+xscalectfhud, 83, 0, playerColorPalSprite(0), 512, 65536);
2131      else if (blueFlagTaken)
2132          DrawStatMaskedSprite(4097, 307+xscalectfhud, 77, 0, blueFlagCarrierColor, 512, 65536);
2133      flashTeamScore(arg, 0, true);
2134  
2135      bool meHaveRedFlag = gMe->hasFlag & 2;
2136      DrawStatMaskedSprite(meHaveRedFlag ? 3558 : 3559, 320+xscalectfhud, 110, 0, playerColorPalSprite(1), 512, 65536 * 0.35);
2137      if (gRedFlagDropped)
2138          DrawStatMaskedSprite(2332, 305+xscalectfhud, 117, 0, playerColorPalSprite(1), 512, 65536);
2139      else if (redFlagTaken)
2140          DrawStatMaskedSprite(4097, 307+xscalectfhud, 111, 0, redFlagCarrierColor, 512, 65536);
2141      flashTeamScore(arg, 1, true);
2142  }
2143  
2144  void viewDrawKillMsg(ClockTicks arg)
2145  {
2146      if (gKillMsg == 0)
2147          return;
2148  
2149      const char bShowKillerMsg = (gPlayerKillMsgTicks > 0) && (gPlayerLastVictim >= 0) && (gPlayerLastVictim < kMaxPlayers);
2150      const char bShowVictimMsg = (gPlayerKillMsgTicks > 0) && (gPlayerLastKiller >= 0) && (gPlayerLastKiller < kMaxPlayers);
2151      if (!bShowKillerMsg && !bShowVictimMsg)
2152          return;
2153  
2154      int nPal;
2155      char buffer[128] = "";
2156      if (bShowVictimMsg)
2157      {
2158          nPal = playerColorPalMessage(gPlayer[gPlayerLastKiller].teamId);
2159          sprintf(buffer, "Killed by %s", gProfile[gPlayerLastKiller].name);
2160          COLORSTR colorStr = {nPal, 0, {10, 127}, {-1, -1}};
2161          viewDrawText(0, buffer, 160, 137, -128, 0, 1, 1, 0, 0, &colorStr);
2162      }
2163      else if (bShowKillerMsg)
2164      {
2165          nPal = playerColorPalMessage(gPlayer[gPlayerLastVictim].teamId);
2166          sprintf(buffer, "Killed %s", gProfile[gPlayerLastVictim].name);
2167          COLORSTR colorStr = {nPal, 0, {7, 127}, {-1, -1}};
2168          viewDrawText(0, buffer, 160, 137, -128, 0, 1, 1, 0, 0, &colorStr);
2169      }
2170      gPlayerKillMsgTicks = gPlayerKillMsgTicks - arg;
2171      if (gPlayerKillMsgTicks <= 0)
2172          playerResetKillMsg();
2173  }
2174  
2175  void viewDrawMultiKill(ClockTicks arg)
2176  {
2177      if (gMultiKill == 0)
2178          return;
2179  
2180      int nY = 40;
2181      if (gGameOptions.nGameType != kGameTypeSinglePlayer) // offset for multiplayer stats bar
2182      {
2183          for (int nRows = (gNetPlayers - 1) / 4; nRows >= 0; nRows--)
2184          {
2185              nY += 5;
2186          }
2187      }
2188      const char bShowMultiKill = (gFrameClock - gMultiKillsTicks[gMe->nPlayer]) < (int)(kTicRate * 1.5); // show multi kill message for 1.5 seconds
2189      if (bShowMultiKill)
2190      {
2191          const int nPalette = playerColorPalMultiKill(gMe->teamId);
2192          if ((int)totalclock & 16) // flash multi kill message
2193              return;
2194          switch (gMultiKillsFrags[gMe->nPlayer])
2195          {
2196              case 0:
2197              case 1:
2198                  break;
2199              case 2:
2200                  viewDrawText(0, "Double Kill!", 160, nY, -128, nPalette, 1, 1);
2201                  break;
2202              case 3:
2203                  viewDrawText(0, "Multi Kill!", 160, nY, -128, nPalette, 1, 1);
2204                  break;
2205              case 4:
2206                  viewDrawText(0, "ULTRA KILL!!", 160, nY, -128, nPalette, 1, 1);
2207                  break;
2208              default:
2209                  viewDrawText(0, "M O N S T E R  K I L L !!!", 160, nY, -128, nPalette, 1, 1);
2210                  break;
2211          }
2212      }
2213      else if ((gAnnounceKillingSpreeTicks > 0) && (gAnnounceKillingSpreePlayer < kMaxPlayers)) // announce player's kill streak
2214      {
2215          const int nPalette = playerColorPalMultiKill(gPlayer[gAnnounceKillingSpreePlayer].teamId);
2216          char buffer[128] = "";
2217          switch (gMultiKillsFrags[gAnnounceKillingSpreePlayer])
2218          {
2219              case 0:
2220              case 1:
2221              case 2:
2222              case 3:
2223              case 4:
2224                  return;
2225              case 5:
2226              case 6:
2227              case 7:
2228              case 8:
2229              case 9:
2230                  sprintf(buffer, "%s is on a killing spree!", gProfile[gAnnounceKillingSpreePlayer].name); // 5-9
2231                  break;
2232              case 10:
2233              case 11:
2234              case 12:
2235              case 13:
2236              case 14:
2237                  sprintf(buffer, "%s is on a rampage!", gProfile[gAnnounceKillingSpreePlayer].name); // 10-14
2238                  break;
2239              case 15:
2240              case 16:
2241              case 17:
2242              case 18:
2243              case 19:
2244                  sprintf(buffer, "%s is dominating!", gProfile[gAnnounceKillingSpreePlayer].name); // 15-19
2245                  break;
2246              case 20:
2247              case 21:
2248              case 22:
2249              case 23:
2250              case 24:
2251                  sprintf(buffer, "%s is unstoppable!", gProfile[gAnnounceKillingSpreePlayer].name); // 20-24
2252                  break;
2253              default:
2254                  sprintf(buffer, "%s is Godlike!", gProfile[gAnnounceKillingSpreePlayer].name); // 25+
2255                  break;
2256          }
2257          uint8_t nAlpha = 0;
2258          if (gAnnounceKillingSpreeTicks <= 255)
2259          {
2260              if (videoGetRenderMode() != REND_CLASSIC) // high quality fade
2261                  nAlpha = 255 - (int)gAnnounceKillingSpreeTicks;
2262              else
2263                  nAlpha = ClipLow(100 - (int)gAnnounceKillingSpreeTicks, 0);
2264          }
2265          viewDrawText(0, buffer, 160, nY, -128, nPalette, 1, 1, 0, nAlpha);
2266          gAnnounceKillingSpreeTicks = gAnnounceKillingSpreeTicks - arg;
2267          if (gAnnounceKillingSpreeTicks <= 0) // reset currently announced kill streak
2268              playerResetAnnounceKillingSpree();
2269      }
2270  }
2271  
2272  void viewDrawWinner(const char *pString, int nPal)
2273  {
2274      static char buffer[kMaxMessageTextLength] = "";
2275      static COLORSTR colorStr = {0, 0, {0, 0}, {0, 0}}; // set info for coloring sub-strings within string
2276  
2277      if (pString)
2278      {
2279          int nColorPart = 0;
2280          int nColorOffsets[4] = {-1, -1, -1, -1}; // stores 4 points in string where color is to be set for player names/flags
2281          strncpy(buffer, pString, kMaxMessageTextLength);
2282          size_t nLength = strnlen(buffer, kMaxMessageTextLength);
2283  
2284          for (size_t i = 0; i < nLength; i++)
2285          {
2286              if (buffer[i] != '\r') // this is the start/stop flag used to detect color offsets
2287                  continue;
2288              Bmemmove((void *)&buffer[i], (void *)&buffer[i + 1], nLength - i); // remove \r character from string
2289              if (nColorPart < 4)
2290              {
2291                  nColorOffsets[nColorPart] = i;
2292                  nColorPart++;
2293              }
2294          }
2295  
2296          if ((nColorPart != 2) && (nColorPart != 4)) // something went very wrong, don't color message
2297              nColorOffsets[0] = nColorOffsets[1] = nColorOffsets[2] = nColorOffsets[3] = -1;
2298  
2299          colorStr = {nPal, 0, {nColorOffsets[0], nColorOffsets[1]}, {nColorOffsets[2], nColorOffsets[3]}}; // set info for coloring sub-strings within string
2300      }
2301  
2302      if (!gPlayerRoundEnding)
2303          return;
2304  
2305      if ((int)totalclock & 64) // flash winner
2306          return;
2307  
2308      int nY = 40;
2309      if (gGameOptions.nGameType != kGameTypeSinglePlayer) // offset for multiplayer stats bar
2310      {
2311          for (int nRows = (gNetPlayers - 1) / 4; nRows >= 0; nRows--)
2312          {
2313              nY += 5;
2314          }
2315      }
2316  
2317      viewDrawText(0, buffer, 160, nY, -128, 0, 1, 1, 0, 0, &colorStr);
2318      return;
2319  }
2320  
2321  void viewDrawSecret(void)
2322  {
2323      static int nLastTick = 0;
2324      if (gGameOptions.nGameType != kGameTypeSinglePlayer || gSecretStyle < 3)
2325          return;
2326      if (gSecretStyle == 3)
2327      {
2328          nLastTick = (int)gFrameClock;
2329          gSecretStyle = 4;
2330      }
2331      if (klabs((int)gFrameClock - nLastTick) < (kTicRate<<1))
2332      {
2333          viewDrawText(0, "You've found a secret!", 160, 50, -128, 2, 1, 1, 0, 0);
2334          return;
2335      }
2336      gSecretStyle = 2;
2337      return;
2338  }
2339  
2340  void UpdateStatusBar(ClockTicks arg)
2341  {
2342      PLAYER *pPlayer = gView;
2343      XSPRITE *pXSprite = pPlayer->pXSprite;
2344  
2345      const int nPalette = playerColorPalHud(pPlayer->teamId);
2346  
2347      if (gViewSize < 0) return;
2348  
2349      char bDrawWeaponHud = gShowWeaponSelect && !VanillaMode();
2350      if (bDrawWeaponHud && (gViewSize > 3)) // if hud size above 3, draw weapon select bar behind hud
2351      {
2352          viewDrawWeaponSelect(pPlayer, pXSprite);
2353          bDrawWeaponHud = 0;
2354      }
2355  
2356      if (gViewSize == 1)
2357      {
2358          DrawStatMaskedSprite(2169, 12-xscalehud, 195, 0, 0, 256, (int)(65536*0.56));
2359          if (pXSprite->health >= (gHealthBlink && !VanillaMode() ? 16<<4 : 16) || ((int)totalclock&16) || pXSprite->health == 0)
2360          {
2361              DrawStatNumber("%d", pXSprite->health>>4, kSBarNumberHealth, 28-xscalehud, 187, 0, 0, 256);
2362          }
2363          if (pPlayer->armor[1])
2364          {
2365              DrawStatMaskedSprite(2578, 70-xscalehud, 186, 0, 0, 256, (int)(65536*0.5));
2366              DrawStatNumber("%3d", pPlayer->armor[1]>>4, kSBarNumberArmor2, 83-xscalehud, 187, 0, 0, 256, (int)(65536*0.65));
2367          }
2368          if (pPlayer->armor[0])
2369          {
2370              DrawStatMaskedSprite(2586, 112-xscalehud, 195, 0, 0, 256, (int)(65536*0.5));
2371              DrawStatNumber("%3d", pPlayer->armor[0]>>4, kSBarNumberArmor1, 125-xscalehud, 187, 0, 0, 256, (int)(65536*0.65));
2372          }
2373          if (pPlayer->armor[2])
2374          {
2375              DrawStatMaskedSprite(2602, 155-xscalehud, 196, 0, 0, 256, (int)(65536*0.5));
2376              DrawStatNumber("%3d", pPlayer->armor[2]>>4, kSBarNumberArmor3, 170-xscalehud, 187, 0, 0, 256, (int)(65536*0.65));
2377          }
2378  
2379          DrawPackItemInStatusBar2(pPlayer, 225+xscalehud, 194, 240+xscalehud, 187, 512, (int)(65536*0.7));
2380  
2381          if (pPlayer->curWeapon && pPlayer->weaponAmmo != -1)
2382          {
2383              int num = pPlayer->ammoCount[pPlayer->weaponAmmo];
2384              if (pPlayer->weaponAmmo == 6)
2385                  num /= 10;
2386              if ((unsigned int)gAmmoIcons[pPlayer->weaponAmmo].nTile < kMaxTiles)
2387                  DrawStatMaskedSprite(gAmmoIcons[pPlayer->weaponAmmo].nTile, 304+xscalehud, 192+gAmmoIcons[pPlayer->weaponAmmo].nYOffs,
2388                      0, 0, 512, gAmmoIcons[pPlayer->weaponAmmo].nScale);
2389              DrawStatNumber("%3d", num, kSBarNumberAmmo, 267+xscalehud, 187, 0, 0, 512);
2390          }
2391  
2392          if (gGameOptions.nGameType <= kGameTypeCoop) // don't show keys for bloodbath/teams as all players have every key
2393          {
2394              int nKeys = 5;
2395              for (int i = 0; i < 6; i++)
2396              {
2397                  if (!pPlayer->hasKey[i+1])
2398                      continue;
2399                  DrawStatSprite(2220+i, (260+10*nKeys)+xscalehud, 170, 0, 0, 512);
2400                  nKeys--;
2401              }
2402          }
2403  
2404          if (pPlayer->throwPower && pXSprite->health > 0)
2405              TileHGauge(2260, 124, 175-10, pPlayer->throwPower, 65536);
2406          else
2407              viewDrawPack(pPlayer, 166, 200-tilesiz[2201].y/2-30);
2408          viewDrawStats(pPlayer, 2-xscalestats, 140);
2409          viewDrawSpeed();
2410          viewDrawPowerUps(pPlayer);
2411      }
2412      else if (gViewSize <= 3)
2413      {
2414          if (pPlayer->throwPower && pXSprite->health > 0)
2415              TileHGauge(2260, 124, 175, pPlayer->throwPower, 65536);
2416          else
2417              viewDrawPack(pPlayer, 166, 200-tilesiz[2201].y/2);
2418      }
2419      if ((gViewSize == 2) || (gViewSize == 3))
2420      {
2421          const char bKeyHolder = (gViewSize == 3); // if hud size 3, use custom key holder attached to health/ammo hud tile
2422          if (bKeyHolder)
2423              DrawStatSprite(9294, (37/2)+(34-xscalehud), 187, 16, nPalette, 256); // use key holder hud tile from notblood.pk3/TILES099.ART
2424          else
2425              DrawStatSprite(2201, 34-xscalehud, 187, 16, nPalette, 256);
2426          if (pXSprite->health >= (gHealthBlink && !VanillaMode() ? 16<<4 : 16) || ((int)totalclock&16) || pXSprite->health == 0)
2427          {
2428              DrawStatNumber("%3d", pXSprite->health>>4, 2190, 8-xscalehud, 183, 0, 0, 256);
2429          }
2430          if (pPlayer->curWeapon && pPlayer->weaponAmmo != -1)
2431          {
2432              int num = pPlayer->ammoCount[pPlayer->weaponAmmo];
2433              if (pPlayer->weaponAmmo == 6)
2434                  num /= 10;
2435              DrawStatNumber("%3d", num, 2240, 42-xscalehud, 183, 0, 0, 256);
2436          }
2437          DrawStatSprite(2173, 284+xscalehud, 187, 16, nPalette, 512);
2438          if (pPlayer->armor[1])
2439          {
2440              TileHGauge(2207, 250+xscalehud, 175, pPlayer->armor[1], 3200, 512);
2441              DrawStatNumber("%3d", pPlayer->armor[1]>>4, 2230, 255+xscalehud, 178, 0, 0, 512);
2442          }
2443          if (pPlayer->armor[0])
2444          {
2445              TileHGauge(2209, 250+xscalehud, 183, pPlayer->armor[0], 3200, 512);
2446              DrawStatNumber("%3d", pPlayer->armor[0]>>4, 2230, 255+xscalehud, 186, 0, 0, 512);
2447          }
2448          if (pPlayer->armor[2])
2449          {
2450              TileHGauge(2208, 250+xscalehud, 191, pPlayer->armor[2], 3200, 512);
2451              DrawStatNumber("%3d", pPlayer->armor[2]>>4, 2230, 255+xscalehud, 194, 0, 0, 512);
2452          }
2453          DrawPackItemInStatusBar(pPlayer, 286+xscalehud, 186, 302+xscalehud, 183, 512);
2454  
2455          if (bKeyHolder)
2456          {
2457              for (int i = 0; i < 6; i++)
2458              {
2459                  const int nTile = 2220+i;
2460                  int x, y;
2461                  x = 75+(i>>1)*11;
2462                  x -= xscalehud;
2463                  if (i&1)
2464                      y = 200-8;
2465                  else
2466                      y = 200-19;
2467                  if (pPlayer->hasKey[i+1])
2468                      DrawStatSprite(nTile, x, y, 0, 0, 256);
2469                  else
2470                      DrawStatSprite(nTile, x, y, 40, 5, 256);
2471              }
2472          }
2473          else if (gGameOptions.nGameType <= kGameTypeCoop) // don't show keys for bloodbath/teams as all players have every key
2474          {
2475              for (int i = 0; i < 6; i++)
2476              {
2477                  int nTile = 2220+i;
2478                  int x, nStat = 0;
2479                  int y = 200-6;
2480                  if (i&1)
2481                  {
2482                      x = 320-(78+(i>>1)*10);
2483                      x += xscalehud;
2484                      nStat |= 512;
2485                  }
2486                  else
2487                  {
2488                      x = 73+(i>>1)*10;
2489                      x -= xscalehud;
2490                      nStat |= 256;
2491                  }
2492  
2493                  if (pPlayer->hasKey[i+1])
2494                      DrawStatSprite(nTile, x, y, 0, 0, nStat);
2495  #if 0
2496                  else
2497                      DrawStatSprite(nTile, x, y, 40, 5, nStat);
2498  #endif
2499              }
2500          }
2501          viewDrawStats(pPlayer, 2-xscalestats, 140);
2502          viewDrawSpeed();
2503          viewDrawPowerUps(pPlayer);
2504      }
2505      else if (gViewSize > 3)
2506      {
2507          viewDrawPack(pPlayer, 160, 200-tilesiz[2200].y);
2508          if (!gHudCompetitiveMode || (gViewSize != 4))
2509              DrawStatMaskedSprite(2200, 160, 172, 16, nPalette);
2510          DrawPackItemInStatusBar(pPlayer, 265, 186, 260, 172);
2511          if (pXSprite->health >= (gHealthBlink && !VanillaMode() ? 16<<4 : 16) || ((int)totalclock&16) || pXSprite->health == 0)
2512          {
2513              DrawStatNumber("%3d", pXSprite->health>>4, 2190, 86, 183, 0, 0);
2514          }
2515          if (pPlayer->curWeapon && pPlayer->weaponAmmo != -1)
2516          {
2517              int num = pPlayer->ammoCount[pPlayer->weaponAmmo];
2518              if (pPlayer->weaponAmmo == 6)
2519                  num /= 10;
2520              DrawStatNumber("%3d", num, 2240, 216, 183, 0, 0);
2521          }
2522          for (int i = 9; i >= 1; i--)
2523          {
2524              int x = 135+((i-1)/3)*23;
2525              int y = 182+((i-1)%3)*6;
2526              int num = pPlayer->ammoCount[i];
2527              if (i == 6)
2528                  num /= 10;
2529              if (i == pPlayer->weaponAmmo)
2530              {
2531                  DrawStatNumber("%3d", num, 2230, x, y, -128, 10);
2532              }
2533              else
2534              {
2535                  DrawStatNumber("%3d", num, 2230, x, y, 32, 10);
2536              }
2537          }
2538  
2539          if (pPlayer->weaponAmmo == 10)
2540          {
2541              DrawStatNumber("%2d", pPlayer->ammoCount[10], 2230, 291, 194, -128, 10);
2542          }
2543          else
2544          {
2545              DrawStatNumber("%2d", pPlayer->ammoCount[10], 2230, 291, 194, 32, 10);
2546          }
2547  
2548          if (pPlayer->weaponAmmo == 11)
2549          {
2550              DrawStatNumber("%2d", pPlayer->ammoCount[11], 2230, 309, 194, -128, 10);
2551          }
2552          else
2553          {
2554              DrawStatNumber("%2d", pPlayer->ammoCount[11], 2230, 309, 194, 32, 10);
2555          }
2556  
2557          if (pPlayer->armor[1])
2558          {
2559              TileHGauge(2207, 44, 174, pPlayer->armor[1], 3200);
2560              DrawStatNumber("%3d", pPlayer->armor[1]>>4, 2230, 50, 177, 0, 0);
2561          }
2562          if (pPlayer->armor[0])
2563          {
2564              TileHGauge(2209, 44, 182, pPlayer->armor[0], 3200);
2565              DrawStatNumber("%3d", pPlayer->armor[0]>>4, 2230, 50, 185, 0, 0);
2566          }
2567          if (pPlayer->armor[2])
2568          {
2569              TileHGauge(2208, 44, 190, pPlayer->armor[2], 3200);
2570              DrawStatNumber("%3d", pPlayer->armor[2]>>4, 2230, 50, 193, 0, 0);
2571          }
2572          if (!gHudCompetitiveMode)
2573          {
2574              sprintf(gTempStr, "v%s", VanillaMode() ? "1.21" : GetVersionString());
2575              viewDrawText(3, gTempStr, 20, 191, 32, 0, 1, 0);
2576          }
2577  
2578          for (int i = 0; i < 6; i++)
2579          {
2580              int nTile = 2220+i;
2581              int x = 73+(i&1)*173;
2582              int y = 171+(i>>1)*11;
2583              if (pPlayer->hasKey[i+1])
2584                  DrawStatSprite(nTile, x, y);
2585              else
2586                  DrawStatSprite(nTile, x, y, 40, 5);
2587          }
2588          if (!gHudCompetitiveMode || (gViewSize != 4))
2589          {
2590              DrawStatMaskedSprite(2202, 118, 185, pPlayer->isRunning ? 16 : 40);
2591              DrawStatMaskedSprite(2202, 201, 185, pPlayer->isRunning ? 16 : 40);
2592          }
2593          if (pPlayer->throwPower && pXSprite->health > 0)
2594          {
2595              TileHGauge(2260, 124, 175, pPlayer->throwPower, 65536);
2596          }
2597          viewDrawStats(pPlayer, 2-xscalestats, 140-yscalestats);
2598          viewDrawSpeed();
2599          viewDrawPowerUps(pPlayer);
2600      }
2601  
2602      if (bDrawWeaponHud) // draw weapon select bar over hud
2603          viewDrawWeaponSelect(pPlayer, pXSprite);
2604  
2605      if (gWeaponRadialMenuState > 0) // draw weapon radial menu over hud
2606          viewDrawWeaponRadialMenu(pPlayer, pXSprite, nPalette);
2607  
2608      if (gGameOptions.nGameType == kGameTypeSinglePlayer)
2609      {
2610          viewDrawSecret();
2611          return;
2612      }
2613  
2614      if (gGameOptions.nGameType >= kGameTypeBloodBath)
2615      {
2616          viewDrawKillMsg(arg);
2617          if (gPlayerRoundEnding) // let winner message override multikill message
2618              viewDrawWinner();
2619          else
2620              viewDrawMultiKill(arg);
2621      }
2622  
2623      if (gGameOptions.nGameType == kGameTypeTeams)
2624      {
2625          if (VanillaMode())
2626          {
2627              viewDrawCtfHudVanilla(arg, 0);
2628          }
2629          else
2630          {
2631              if (!gTeamsScoreStyle)
2632              {
2633                  int nY = 0;
2634                  for (int nRows = (gNetPlayers - 1) / 4; nRows >= 0; nRows--)
2635                  {
2636                      nY += 9;
2637                  }
2638                  viewDrawCtfHudVanilla(arg, nY);
2639              }
2640              else
2641                  viewDrawCtfHud(arg);
2642              viewDrawPlayerFlags();
2643          }
2644      }
2645      else
2646      {
2647          viewDrawPlayerFrags();
2648      }
2649  }
2650  
2651  void viewPrecacheTiles(void)
2652  {
2653      tilePrecacheTile(2173, 0);
2654      tilePrecacheTile(2200, 0);
2655      tilePrecacheTile(2201, 0);
2656      tilePrecacheTile(2202, 0);
2657      tilePrecacheTile(2207, 0);
2658      tilePrecacheTile(2208, 0);
2659      tilePrecacheTile(2209, 0);
2660      tilePrecacheTile(2229, 0);
2661      tilePrecacheTile(2260, 0);
2662      tilePrecacheTile(2559, 0);
2663      tilePrecacheTile(2169, 0);
2664      tilePrecacheTile(2578, 0);
2665      tilePrecacheTile(2586, 0);
2666      tilePrecacheTile(2602, 0);
2667      tilePrecacheTile(kHudFullBackTile, 0);
2668      for (int i = 0; i < 10; i++)
2669      {
2670          tilePrecacheTile(2190 + i, 0);
2671          tilePrecacheTile(2230 + i, 0);
2672          tilePrecacheTile(2240 + i, 0);
2673          tilePrecacheTile(2250 + i, 0);
2674          tilePrecacheTile(kSBarNumberHealth + i, 0);
2675          tilePrecacheTile(kSBarNumberAmmo + i, 0);
2676          tilePrecacheTile(kSBarNumberInv + i, 0);
2677          tilePrecacheTile(kSBarNumberArmor1 + i, 0);
2678          tilePrecacheTile(kSBarNumberArmor2 + i, 0);
2679          tilePrecacheTile(kSBarNumberArmor3 + i, 0);
2680      }
2681      for (int i = 0; i < 6; i++)
2682      {
2683          tilePrecacheTile(kSBarNegative + i, 0);
2684      }
2685      for (int i = 0; i < kPackMax; i++)
2686      {
2687          tilePrecacheTile(gPackIcons[i], 0);
2688          tilePrecacheTile(gPackIcons2[i].nTile, 0);
2689      }
2690      for (int i = 0; i < 6; i++)
2691      {
2692          tilePrecacheTile(2220 + i, 0);
2693          tilePrecacheTile(2552 + i, 0);
2694      }
2695      // pre-cache all notblood.pk3/TILES099.ART
2696      for (int i = 9287; i <= 9300; i++)
2697          tilePrecacheTile(i, 0);
2698  }
2699  
2700  int *lensTable;
2701  
2702  int gZoom = 1024;
2703  
2704  int dword_172CE0[16][3];
2705  
2706  void viewInit(void)
2707  {
2708      LOG_F(INFO, "Initializing status bar");
2709      InitStatusBar();
2710      FontSet(0, 4096, 0);
2711      FontSet(1, 4192, 1);
2712      FontSet(2, 4288, 1);
2713      FontSet(3, 4384, 1);
2714      FontSet(4, 4480, 0);
2715  
2716      DICTNODE *hLens = gSysRes.Lookup("LENS", "DAT");
2717      dassert(hLens != NULL);
2718      dassert(gSysRes.Size(hLens) == kLensSize * kLensSize * sizeof(int));
2719  
2720      lensTable = (int*)gSysRes.Lock(hLens);
2721  #if B_BIG_ENDIAN == 1
2722      for (int i = 0; i < kLensSize*kLensSize; i++)
2723      {
2724          lensTable[i] = B_LITTLE32(lensTable[i]);
2725      }
2726  #endif
2727      char *data = tileAllocTile(LENSBUFFER, kLensSize, kLensSize, 0, 0);
2728      memset(data, 255, kLensSize*kLensSize);
2729      gGameMessageMgr.SetState(gMessageState);
2730      gGameMessageMgr.SetCoordinates(1, 1);
2731      char nFont;
2732      if (gMessageFont == 0)
2733          nFont = 3;
2734      else
2735          nFont = 0;
2736  
2737      gGameMessageMgr.SetFont(nFont);
2738      gGameMessageMgr.SetMaxMessages(gMessageCount);
2739      gGameMessageMgr.SetMessageTime(gMessageTime);
2740  
2741      for (int i = 0; i < 16; i++)
2742      {
2743          dword_172CE0[i][0] = mulscale16(wrand(), 2048);
2744          dword_172CE0[i][1] = mulscale16(wrand(), 2048);
2745          dword_172CE0[i][2] = mulscale16(wrand(), 2048);
2746      }
2747      gViewMap.Init(0, 0, gZoom, 0, gFollowMap);
2748  
2749      g_frameDelay = calcFrameDelay(r_maxfps);
2750  
2751      bLoadScreenCrcMatch = tileGetCRC32(kLoadScreen) == kLoadScreenCRC;
2752  }
2753  
2754  inline int viewCalculateOffetRatio(int nRatio)
2755  {
2756      const int ratios[] = {320, (int)((4. / 3.) / (16. / 10.) * 320.), (int)((4. / 3.) / (16. / 9.) * 320.), (int)((4. / 3.) / (21. / 9.) * 320.)}; // 4:3, 16:10, 16:9, 21:9
2757      if (nRatio >= (int)ARRAY_SIZE(ratios)) // if ratio selection is outside of array
2758          return 0;
2759      int nOffset = scale(0-(ratios[nRatio]>>1), ratios[nRatio]>>1, 266>>1); // scale position
2760      nOffset = scale(nOffset, xscale, yscale); // multiply by window ratio
2761      nOffset += ratios[nRatio]>>1; // offset to center
2762      if (nOffset > 0) // if ratio is beyond screen width
2763          return 0;
2764      return nOffset;
2765  }
2766  
2767  void viewUpdateHudRatio(void)
2768  {
2769      const char bFullHud = (gViewSize > 3);
2770      xscalehud = xscalestats = yscalestats = xscalepowerups = 0;
2771  
2772      if (gHudRatio > 0)
2773          xscalehud = viewCalculateOffetRatio(gHudRatio-1);
2774      if (gLevelStats > 1)
2775          xscalestats = viewCalculateOffetRatio(gLevelStats-2);
2776      if (gLevelStats && bFullHud) // calculate level stats y position for full hud
2777      {
2778          const int kScreenRatio = scale(320, xscale, yscale);
2779          if ((gLevelStats == 2) || (kScreenRatio <= 300)) // adjust level stats y pos when resolution ratio is less than 16:10
2780              yscalestats = 30;
2781          else if ((gLevelStats == 3) || (kScreenRatio <= 330)) // adjust level stats y pos when resolution ratio is less than 16:10
2782              yscalestats = 10;
2783      }
2784      if (gPowerupDuration > 1)
2785          xscalepowerups = viewCalculateOffetRatio(gPowerupDuration-2);
2786  
2787      gPlayerMsg.xoffset = gGameMessageMgr.xoffset = gViewMap.xoffset = (gViewMode == 3 && gViewSize < 6) ? xscalehud : 0;
2788  
2789      if (gPowerupDuration)
2790          xscalectfhud = xscalepowerups;
2791      else if (gLevelStats)
2792          xscalectfhud = xscalestats;
2793      else
2794          xscalectfhud = xscalehud;
2795  }
2796  
2797  void viewUpdateSkyRatio(void)
2798  {
2799      psky_t *pSky = tileSetupSky(0);
2800      if (!pSky)
2801          return;
2802      if (gFov > 75) // using a math curve, calculate the scale using the FOV
2803      {
2804          pSky->yscale = divscale16(fix16_from_int(gFov * gFov), fix16_from_float(1.f / 0.000177989f));
2805          pSky->yscale -= divscale16(fix16_from_int(gFov), fix16_from_float(1.f / 0.0241556f));
2806          pSky->yscale += fix16_from_float(1.81923f);
2807      }
2808      else
2809          pSky->yscale = 65536;
2810  }
2811  
2812  void viewResizeView(int size)
2813  {
2814      const char bDrawFragsBg = (gGameOptions.nGameType != kGameTypeSinglePlayer) && (!VanillaMode() || gGameOptions.nGameType != kGameTypeTeams);
2815      int xdimcorrect = ClipHigh(scale(ydim, 4, 3), xdim);
2816      gViewXCenter = xdim-xdim/2;
2817      gViewYCenter = ydim-ydim/2;
2818      xscale = divscale16(xdim, 320);
2819      xscalecorrect = divscale16(xdimcorrect, 320);
2820      yscale = divscale16(ydim, 200);
2821      xstep = divscale16(320, xdim);
2822      ystep = divscale16(200, ydim);
2823      gViewSize = ClipRange(size, 0, 9);
2824      if (gViewSize <= 4)
2825      {
2826          gViewX0 = 0;
2827          gViewX1 = xdim-1;
2828          gViewY0 = 0;
2829          gViewY1 = ydim-1;
2830          if (bDrawFragsBg)
2831          {
2832              gViewY0 = (tilesiz[2229].y*ydim*((gNetPlayers+3)/4))/200;
2833          }
2834          gViewX0S = divscale16(gViewX0, xscalecorrect);
2835          gViewY0S = divscale16(gViewY0, yscale);
2836          gViewX1S = divscale16(gViewX1, xscalecorrect);
2837          gViewY1S = divscale16(gViewY1, yscale);
2838      }
2839      else
2840      {
2841          gViewX0 = 0;
2842          gViewY0 = 0;
2843          gViewX1 = xdim-1;
2844          gViewY1 = ydim-1-(25*ydim)/200;
2845          if (bDrawFragsBg)
2846          {
2847              gViewY0 = (tilesiz[2229].y*ydim*((gNetPlayers+3)/4))/200;
2848          }
2849  
2850          int height = gViewY1-gViewY0;
2851          gViewX0 += mulscale16(xdim*(gViewSize-5),4096);
2852          gViewX1 -= mulscale16(xdim*(gViewSize-5),4096);
2853          gViewY0 += mulscale16(height*(gViewSize-5),4096);
2854          gViewY1 -= mulscale16(height*(gViewSize-5),4096);
2855          gViewX0S = divscale16(gViewX0, xscalecorrect);
2856          gViewY0S = divscale16(gViewY0, yscale);
2857          gViewX1S = divscale16(gViewX1, xscalecorrect);
2858          gViewY1S = divscale16(gViewY1, yscale);
2859      }
2860      videoSetViewableArea(gViewX0, gViewY0, gViewX1, gViewY1);
2861      if (gViewMode == 4) // 2D map view
2862      {
2863          int nOffset = bDrawFragsBg && !VanillaMode() ? (tilesiz[2229].y*ydim*((gNetPlayers+3)/4))/200 : 0;
2864          nOffset = divscale16(nOffset, yscale);
2865          nOffset += gGameOptions.nGameType == kGameTypeSinglePlayer && !VanillaMode() ? 6 : 1;
2866          gGameMessageMgr.SetCoordinates(1, nOffset);
2867      }
2868      else
2869      {
2870          int nOffset = 1;
2871          if ((gGameOptions.nGameType == kGameTypeTeams) && VanillaMode()) // lower text for vanilla CTF hud (v1.21 did not do this)
2872              nOffset = 15;
2873          else if ((gGameOptions.nGameType == kGameTypeSinglePlayer) && (gViewSize < 6) && !VanillaMode()) // lower message position for single-player
2874              nOffset = 6;
2875          gGameMessageMgr.SetCoordinates(gViewX0S + 1, gViewY0S + nOffset);
2876      }
2877      gGameMessageMgr.maxNumberOfMessagesToDisplay = !VanillaMode() && (gGameOptions.nGameType != kGameTypeSinglePlayer) ? 3 : 4; // set max displayed messages to 3 for multiplayer (reduces on screen clutter)
2878      viewSetCrosshairColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);
2879      viewBurnTimeInit();
2880      viewSetRenderScale(0);
2881      viewUpdateHudRatio();
2882      viewUpdateSkyRatio();
2883  }
2884  
2885  #define kBackTile 253
2886  #define kBackTileVanilla 230
2887  
2888  void UpdateFrame(void)
2889  {
2890      const char bOrigTile = !gHudBgVanilla ? VanillaMode() : (gHudBgVanilla == 2);
2891      const int nPalette = !bOrigTile ? playerColorPalHud(gView->teamId) : 0;
2892      char bDrawNewBottomBorder = gHudBgNewBorder && (gViewSize == 5);
2893  
2894      if (bDrawNewBottomBorder)
2895      {
2896          const int nTile = kHudFullBackTile;
2897          const int nHalfScreen = klabs(gViewX1S-gViewX0S)>>1;
2898          if (tilesiz[nTile].x == 64) // if for whatever reason this changed, DO NOT attempt to render the new border
2899          {
2900              for (int i = 0; i <= nHalfScreen; i += (int)tilesiz[nTile].x) // extend new bottom border across screen
2901              {
2902                  DrawStatMaskedSprite(nTile, -i, 172, 16, nPalette); // left side
2903                  DrawStatMaskedSprite(nTile, i+320, 172, 16, nPalette); // right side
2904              }
2905          }
2906          else
2907              bDrawNewBottomBorder = 0;
2908      }
2909  
2910      const int nTile = !bOrigTile ? kBackTile : kBackTileVanilla;
2911      int nScale = 65536;
2912      int nWidth = 0, nHeight = 0;
2913  
2914      if (gHudBgScale) // scale background tiles to match hud pixel density scale
2915      {
2916          nWidth = tilesiz[nTile].x;
2917          nHeight = tilesiz[nTile].y;
2918          if (nWidth == nHeight) // if tile is perfectly square
2919          {
2920              nWidth = nHeight = mulscale16(nWidth<<16, xscalecorrect)>>16;
2921              nScale = mulscale16(nScale, xscalecorrect);
2922          }
2923          else // unexpected non-square tile, don't scale
2924          {
2925              nWidth = nHeight = 0;
2926          }
2927      }
2928  
2929      viewTileSprite(nTile, 0, nPalette, 0, 0, xdim, gViewY0-3, nWidth, nHeight, nScale);
2930      if (!bDrawNewBottomBorder) // don't render rest of border tiles for full hud mode
2931      {
2932          viewTileSprite(nTile, 0, nPalette, 0, gViewY1+4, xdim, ydim, nWidth, nHeight, nScale);
2933          viewTileSprite(nTile, 0, nPalette, 0, gViewY0-3, gViewX0-3, gViewY1+4, nWidth, nHeight, nScale);
2934          viewTileSprite(nTile, 0, nPalette, gViewX1+4, gViewY0-3, xdim, gViewY1+4, nWidth, nHeight, nScale);
2935      }
2936  
2937      viewTileSprite(nTile, 20, nPalette, gViewX0, gViewY0-3, gViewX1+4, gViewY0, nWidth, nHeight, nScale);
2938      if (!bDrawNewBottomBorder) // don't render rest of border tiles for full hud mode
2939      {
2940          viewTileSprite(nTile, 20, nPalette, gViewX0-3, gViewY0-3, gViewX0, gViewY1+1, nWidth, nHeight, nScale);
2941          viewTileSprite(nTile, 10, nPalette+1, gViewX1+1, gViewY0, gViewX1+4, gViewY1+4, nWidth, nHeight, nScale);
2942          viewTileSprite(nTile, 10, nPalette+1, gViewX0-3, gViewY1+1, gViewX1+1, gViewY1+4, nWidth, nHeight, nScale);
2943      }
2944  }
2945  
2946  void viewDimScreen(void)
2947  {
2948      const int shadow_pal = 5;
2949      if (gGameMenuMgr.pActiveMenu != &menuOptionsDisplayColor) // if current menu is not on color correction menu, dim screen
2950          rotatesprite_fs_alpha(fix16_from_int(320<<1),fix16_from_int(220<<1),fix16_from_int(127),0,0,127,shadow_pal,RS_STRETCH|RS_NOCLIP,192); // stretch tile across entire screen
2951  }
2952  
2953  void viewDrawInterface(ClockTicks arg)
2954  {
2955      if ((gViewMode == 3) && ((gViewSize > 4) || (gViewSize <= 4 && gGameOptions.nGameType != kGameTypeSinglePlayer)))
2956          UpdateFrame();
2957      UpdateStatusBar(arg);
2958  }
2959  
2960  fix16_t gCameraAng = 0;
2961  
2962  
2963  int effectDetail[kViewEffectMax] = {
2964      4, 4, 4, 4, 0, 0, 0, 0, 0, 1, 4, 4, 0, 0, 0, 1, 0, 0, 0
2965  };
2966  
2967  char viewUseItemRespawnMarkers(spritetype* pSpr)
2968  {
2969  #ifdef NOONE_EXTENSIONS
2970      if (IsUserItemSprite(pSpr))
2971          return userItemViewUseRespawnMarkers(userItemGet(pSpr->type));
2972  #endif
2973  
2974      if ((IsItemSprite(pSpr) || IsAmmoSprite(pSpr))
2975          && gGameOptions.nItemSettings == 2)
2976              return 1;
2977  
2978      if (IsWeaponSprite(pSpr)
2979          && gGameOptions.nWeaponSettings == 3)
2980              return 1;
2981  
2982      return 0;
2983  }
2984  
2985  tspritetype *viewAddEffect(int nTSprite, VIEW_EFFECT nViewEffect)
2986  {
2987      dassert(nViewEffect >= 0 && nViewEffect < kViewEffectMax);
2988      auto pTSprite = &tsprite[nTSprite];
2989      if (gDetail < effectDetail[nViewEffect] || nTSprite >= kMaxViewSprites) return NULL;
2990      switch (nViewEffect)
2991      {
2992  #ifdef NOONE_EXTENSIONS
2993      case kViewEffectSpotProgress: {
2994          XSPRITE* pXSprite = &xsprite[pTSprite->extra];
2995          int perc = (100 * pXSprite->data3) / kMaxPatrolSpotValue;
2996          int width = (94 * pXSprite->data3) / kMaxPatrolSpotValue;
2997  
2998          int top, bottom;
2999          GetSpriteExtents(pTSprite, &top, &bottom);
3000  
3001          if (videoGetRenderMode() != REND_CLASSIC) {
3002              
3003              auto pNSprite2 = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3004              if (!pNSprite2)
3005                  break;
3006  
3007              pNSprite2->picnum = 2203;
3008  
3009              pNSprite2->xrepeat = width;
3010              pNSprite2->yrepeat = 20;
3011              pNSprite2->pal = 10;
3012              if (perc >= 75) pNSprite2->pal = 0;
3013              else if (perc >= 50) pNSprite2->pal = 6;
3014              
3015              pNSprite2->z = top - 2048;
3016              pNSprite2->shade = -128;
3017  
3018  
3019          } else {
3020              
3021  
3022              auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3023              auto pNSprite2 = viewInsertTSprite(pTSprite->sectnum, 32766, pTSprite);
3024              if (!pNSprite || !pNSprite2)
3025                  break;
3026              pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT_INVERT | CSTAT_SPRITE_TRANSLUCENT;
3027  
3028              pNSprite->picnum = 2229;
3029              pNSprite2->picnum = 2203;
3030  
3031              pNSprite->xoffset = -1;
3032              pNSprite->xrepeat = 40;
3033              pNSprite->yrepeat = 64;
3034              pNSprite->pal = 5;
3035  
3036              pNSprite2->xrepeat = width;
3037              pNSprite2->yrepeat = 34;
3038              pNSprite2->pal = 10;
3039              if (perc >= 75) pNSprite2->pal = 0;
3040              else if (perc >= 50) pNSprite2->pal = 6;
3041  
3042              pNSprite->z = pNSprite2->z = top - 2048;
3043              pNSprite->shade = pNSprite2->shade = -128;
3044  
3045          }
3046          break;
3047      }
3048  #endif
3049      case kViewEffectAtom:
3050          for (int i = 0; i < 16; i++)
3051          {
3052              auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3053              if (!pNSprite)
3054                  break;
3055              int ang = ((int)gFrameClock*2048)/120;
3056              int nRand1 = dword_172CE0[i][0];
3057              int nRand2 = dword_172CE0[i][1];
3058              int nRand3 = dword_172CE0[i][2];
3059              ang += nRand3;
3060              int x = mulscale30(512, Cos(ang));
3061              int y = mulscale30(512, Sin(ang));
3062              int z = 0;
3063              RotateYZ(&x, &y, &z, nRand1);
3064              RotateXZ(&x, &y, &z, nRand2);
3065              pNSprite->x = pTSprite->x + x;
3066              pNSprite->y = pTSprite->y + y;
3067              pNSprite->z = pTSprite->z + (z<<4);
3068              pNSprite->picnum = 1720;
3069              pNSprite->shade = -128;
3070          }
3071          break;
3072      case kViewEffectFlag:
3073      case kViewEffectBigFlag:
3074      {
3075          int top, bottom;
3076          GetSpriteExtents(pTSprite, &top, &bottom);
3077          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3078          if (!pNSprite)
3079              break;
3080          pNSprite->shade = -128;
3081          pNSprite->pal = 0;
3082          pNSprite->z = top;
3083          if (nViewEffect == kViewEffectFlag)
3084              pNSprite->xrepeat = pNSprite->yrepeat = 24;
3085          else
3086              pNSprite->xrepeat = pNSprite->yrepeat = 64;
3087          pNSprite->picnum = 3558;
3088          return pNSprite;
3089      }
3090      case kViewEffectTesla:
3091      {
3092          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3093          if (!pNSprite)
3094              break;
3095          pNSprite->z = pTSprite->z;
3096          pNSprite->cstat |= 2;
3097          pNSprite->shade = -128;
3098          pNSprite->xrepeat = pTSprite->xrepeat;
3099          pNSprite->yrepeat = pTSprite->yrepeat;
3100          pNSprite->picnum = 2135;
3101          break;
3102      }
3103      case kViewEffectShoot:
3104      {
3105          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3106          if (!pNSprite)
3107              break;
3108          pNSprite->shade = -128;
3109          pNSprite->pal = 0;
3110          pNSprite->xrepeat = pNSprite->yrepeat = 64;
3111          pNSprite->picnum = 2605;
3112          return pNSprite;
3113      }
3114      case kViewEffectReflectiveBall:
3115      {
3116          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3117          if (!pNSprite)
3118              break;
3119          pNSprite->shade = 26;
3120          pNSprite->pal = 0;
3121          pNSprite->cstat |= 2;
3122          pNSprite->xrepeat = pNSprite->yrepeat = 64;
3123          pNSprite->picnum = 2089;
3124          break;
3125      }
3126      case kViewEffectPhase:
3127      {
3128          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3129          if (!pNSprite)
3130              break;
3131          int top, bottom;
3132          GetSpriteExtents(pTSprite, &top, &bottom);
3133          pNSprite->shade = 26;
3134          pNSprite->pal = 0;
3135          pNSprite->cstat |= 2;
3136          pNSprite->xrepeat = pNSprite->yrepeat = 24;
3137          pNSprite->picnum = 626;
3138          pNSprite->z = top;
3139          break;
3140      }
3141      case kViewEffectTrail:
3142      {
3143          int nAng = pTSprite->ang;
3144          if (pTSprite->cstat & 16)
3145          {
3146              nAng = (nAng+512)&2047;
3147          }
3148          else
3149          {
3150              nAng = (nAng+1024)&2047;
3151          }
3152          for (int i = 0; i < 5; i++)
3153          {
3154              int nSector = pTSprite->sectnum;
3155              auto pNSprite = viewInsertTSprite<tspritetype>(nSector, 32767, NULL);
3156              if (!pNSprite)
3157                  break;
3158              int nLen = 128+(i<<7);
3159              int x = mulscale30(nLen, Cos(nAng));
3160              pNSprite->x = pTSprite->x + x;
3161              int y = mulscale30(nLen, Sin(nAng));
3162              pNSprite->y = pTSprite->y + y;
3163              pNSprite->z = pTSprite->z;
3164              dassert(nSector >= 0 && nSector < kMaxSectors);
3165              FindSector(pNSprite->x, pNSprite->y, pNSprite->z, &nSector);
3166              pNSprite->sectnum = nSector;
3167              pNSprite->owner = pTSprite->owner;
3168              pNSprite->picnum = pTSprite->picnum;
3169              pNSprite->cstat |= 2;
3170              if (i < 2)
3171                  pNSprite->cstat |= 514;
3172              pNSprite->shade = ClipLow(pTSprite->shade-16, -128);
3173              pNSprite->xrepeat = pTSprite->xrepeat;
3174              pNSprite->yrepeat = pTSprite->yrepeat;
3175              pNSprite->picnum = pTSprite->picnum;
3176          }
3177          break;
3178      }
3179      case kViewEffectFlame:
3180      {
3181          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3182          if (!pNSprite)
3183              break;
3184          pNSprite->shade = -128;
3185          pNSprite->z = pTSprite->z;
3186          pNSprite->picnum = 908;
3187          pNSprite->statnum = kStatDecoration;
3188          pNSprite->xrepeat = pNSprite->yrepeat = (tilesiz[pTSprite->picnum].x*pTSprite->xrepeat)/64;
3189          break;
3190      }
3191      case kViewEffectSmokeHigh:
3192      {
3193          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3194          if (!pNSprite)
3195              break;
3196          int top, bottom;
3197          GetSpriteExtents(pTSprite, &top, &bottom);
3198          pNSprite->z = top;
3199          if (IsDudeSprite(pTSprite))
3200              pNSprite->picnum = 672;
3201          else
3202              pNSprite->picnum = 754;
3203          pNSprite->cstat |= 2;
3204          pNSprite->shade = 8;
3205          pNSprite->xrepeat = pTSprite->xrepeat;
3206          pNSprite->yrepeat = pTSprite->yrepeat;
3207          break;
3208      }
3209      case kViewEffectSmokeLow:
3210      {
3211          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3212          if (!pNSprite)
3213              break;
3214          int top, bottom;
3215          GetSpriteExtents(pTSprite, &top, &bottom);
3216          pNSprite->z = bottom;
3217          if (pTSprite->type >= kDudeBase && pTSprite->type < kDudeMax)
3218              pNSprite->picnum = 672;
3219          else
3220              pNSprite->picnum = 754;
3221          pNSprite->cstat |= 2;
3222          pNSprite->shade = 8;
3223          pNSprite->xrepeat = pTSprite->xrepeat;
3224          pNSprite->yrepeat = pTSprite->yrepeat;
3225          break;
3226      }
3227      case kViewEffectTorchHigh:
3228      {
3229          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3230          if (!pNSprite)
3231              break;
3232          int top, bottom;
3233          GetSpriteExtents(pTSprite, &top, &bottom);
3234          pNSprite->z = top;
3235          pNSprite->picnum = 2101;
3236          pNSprite->shade = -128;
3237          pNSprite->xrepeat = pNSprite->yrepeat = (tilesiz[pTSprite->picnum].x*pTSprite->xrepeat)/32;
3238          break;
3239      }
3240      case kViewEffectTorchLow:
3241      {
3242          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3243          if (!pNSprite)
3244              break;
3245          int top, bottom;
3246          GetSpriteExtents(pTSprite, &top, &bottom);
3247          pNSprite->z = bottom;
3248          pNSprite->picnum = 2101;
3249          pNSprite->shade = -128;
3250          pNSprite->xrepeat = pNSprite->yrepeat = (tilesiz[pTSprite->picnum].x*pTSprite->xrepeat)/32;
3251          break;
3252      }
3253      case kViewEffectShadow:
3254      {
3255          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3256          if (!pNSprite)
3257              break;
3258          pNSprite->z = getflorzofslope(pTSprite->sectnum, pNSprite->x, pNSprite->y);
3259          if (!VanillaMode()) // support better floor detection for shadows (detect fake floors/allows ROR traversal)
3260          {
3261              char bHitFakeFloor = 0;
3262              short nFakeFloorSprite;
3263              if (spriRangeIsFine(pTSprite->owner) && !gMirrorDrawing) // don't attempt to check for fake floors if we're rendering a mirror due to getzrange mirrorsector crash
3264              {
3265                  spritetype *pSprite = &sprite[pTSprite->owner];
3266                  int bakCstat = pSprite->cstat;
3267                  pSprite->cstat &= ~257;
3268                  int ceilZ, ceilHit, floorZ, floorHit;
3269                  GetZRangeAtXYZ(pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0, PARALLAXCLIP_CEILING|PARALLAXCLIP_FLOOR);
3270                  nFakeFloorSprite = floorHit&0x3fff;
3271                  if ((floorHit&0xc000) == 0xc000)
3272                      bHitFakeFloor = (sprite[nFakeFloorSprite].cstat & (CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_ALIGNMENT_FLOOR|CSTAT_SPRITE_INVISIBLE)) == (CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_ALIGNMENT_FLOOR);
3273                  pSprite->cstat = bakCstat;
3274              }
3275              if (bHitFakeFloor) // if there is a fake floor under us, use fake floor as the shadow position
3276              {
3277                  int top, bottom;
3278                  GetSpriteExtents(&sprite[nFakeFloorSprite], &top, &bottom);
3279                  pNSprite->z = top;
3280                  pNSprite->z--; // offset from fake floor so it isn't z-fighting when being rendered
3281              }
3282              else if ((sector[pNSprite->sectnum].floorpicnum >= 4080) && (sector[pNSprite->sectnum].floorpicnum <= 4095)) // if floor has ror, find actual floor
3283              {
3284                  int cX = pNSprite->x, cY = pNSprite->y, cZ = pNSprite->z, cZrel = pNSprite->z, nSectnum = pNSprite->sectnum;
3285                  for (int i = 0; i < 16; i++) // scan through max stacked sectors
3286                  {
3287                      if (!CheckLink(&cX, &cY, &cZ, &nSectnum)) // if no more floors underneath, abort
3288                          break;
3289                      const int newFloorZ = getflorzofslope(nSectnum, cX, cZ);
3290                      cZrel += newFloorZ - cZ; // get height difference for next sector's ceiling/floor, and add to relative height for shadow
3291                      if ((sector[nSectnum].floorpicnum < 4080) || (sector[nSectnum].floorpicnum > 4095)) // if current sector is not open air, use as floor for shadow casting, otherwise continue to next sector
3292                          break;
3293                      cZ = newFloorZ;
3294                  }
3295                  pNSprite->z = cZrel;
3296              }
3297          }
3298          pNSprite->shade = 127;
3299          pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
3300          pNSprite->xrepeat = pTSprite->xrepeat;
3301          pNSprite->yrepeat = pTSprite->yrepeat>>2;
3302          pNSprite->picnum = pTSprite->picnum;
3303          if (!VanillaMode() && (pTSprite->type == kThingDroppedLifeLeech)) // fix shadow for thrown lifeleech
3304              pNSprite->picnum = 800;
3305          pNSprite->pal = 5;
3306          int height = tilesiz[pNSprite->picnum].y;
3307          int center = height/2+picanm[pNSprite->picnum].yofs;
3308          if (gShadowsFake3D && !VanillaMode())
3309          {
3310              height -= picanm[pNSprite->picnum].yofs*2;
3311              pNSprite->yrepeat = (int)((float)pTSprite->yrepeat*0.375f); // set shadows 37.5% tall of original sprite
3312              const float nOffset = videoGetRenderMode() == REND_CLASSIC ? 8.f : 9.15f; // offset shadow distance depending on render mode
3313              const float nDist = (float)(pNSprite->yrepeat*height)/nOffset;
3314              pNSprite->cstat |= (pTSprite->cstat & (CSTAT_SPRITE_XFLIP|CSTAT_SPRITE_YFLIP)) | CSTAT_SPRITE_ALIGNMENT_FLOOR; // inherit flags from parent sprite and set to floor sprite render type
3315              pNSprite->cstat &= ~CSTAT_SPRITE_YCENTER; // don't align by center
3316              pNSprite->x += mulscale30((int)nDist, Cos(gCameraAng));
3317              pNSprite->y += mulscale30((int)nDist, Sin(gCameraAng));
3318              pNSprite->ang = gCameraAng;
3319              break;
3320          }
3321          pNSprite->z -= (pNSprite->yrepeat<<2)*(height-center);
3322          break;
3323      }
3324      case kViewEffectFlareHalo:
3325      {
3326          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3327          if (!pNSprite)
3328              break;
3329          pNSprite->shade = -128;
3330          pNSprite->pal = 2;
3331          pNSprite->cstat |= 2;
3332          pNSprite->z = pTSprite->z;
3333          pNSprite->xrepeat = pTSprite->xrepeat;
3334          pNSprite->yrepeat = pTSprite->yrepeat;
3335          pNSprite->picnum = 2427;
3336          break;
3337      }
3338      case kViewEffectCeilGlow:
3339      {
3340          sectortype *pSector = &sector[pTSprite->sectnum];
3341          if (!VanillaMode()) // if ceiling has ror, don't render effect
3342          {
3343              if ((pSector->ceilingpicnum >= 4080) && (pSector->ceilingpicnum <= 4095))
3344                  break;
3345          }
3346          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3347          if (!pNSprite)
3348              break;
3349          const char bCalcSlope = gGameOptions.bSectorBehavior && !VanillaMode();
3350          int zDiff = bCalcSlope ? getceilzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) : pSector->ceilingz;
3351          pNSprite->x = pTSprite->x;
3352          pNSprite->y = pTSprite->y;
3353          pNSprite->z = zDiff;
3354          pNSprite->picnum = 624;
3355          pNSprite->shade = ((pTSprite->z-zDiff)>>8)-64;
3356          pNSprite->pal = 2;
3357          pNSprite->xrepeat = pNSprite->yrepeat = 64;
3358          pNSprite->cstat |= 106;
3359          pNSprite->ang = pTSprite->ang;
3360          pNSprite->owner = pTSprite->owner;
3361          if (bCalcSlope && (sector[pNSprite->sectnum].ceilingstat&2) && (sector[pNSprite->sectnum].ceilingheinum != 0)) // align sprite to slope
3362          {
3363              walltype *pWall1 = &wall[sector[pNSprite->sectnum].wallptr];
3364              walltype *pWall2 = &wall[pWall1->point2];
3365              pNSprite->xoffset = sector[pNSprite->sectnum].ceilingheinum & 255;
3366              pNSprite->yoffset = (sector[pNSprite->sectnum].ceilingheinum >> 8) & 255;
3367              pNSprite->cstat |= CSTAT_SPRITE_ALIGNMENT_SLOPE;
3368              pNSprite->ang = getangle(pWall2->x-pWall1->x, pWall2->y-pWall1->y)+kAng270;
3369          }
3370          return pNSprite;
3371      }
3372      case kViewEffectFloorGlow:
3373      {
3374          sectortype *pSector = &sector[pTSprite->sectnum];
3375          if (!VanillaMode()) // if floor has ror, don't render effect
3376          {
3377              if ((pSector->floorpicnum >= 4080) && (pSector->floorpicnum <= 4095))
3378                  break;
3379          }
3380          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3381          if (!pNSprite)
3382              break;
3383          const char bCalcSlope = gGameOptions.bSectorBehavior && !VanillaMode();
3384          int zDiff = bCalcSlope ? getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) : pSector->floorz;
3385          pNSprite->x = pTSprite->x;
3386          pNSprite->y = pTSprite->y;
3387          pNSprite->z = zDiff;
3388          pNSprite->picnum = 624;
3389          char nShade = (zDiff-pTSprite->z)>>8; 
3390          pNSprite->shade = nShade-32;
3391          pNSprite->pal = 2;
3392          pNSprite->xrepeat = pNSprite->yrepeat = nShade;
3393          pNSprite->cstat |= 98;
3394          pNSprite->ang = pTSprite->ang;
3395          pNSprite->owner = pTSprite->owner;
3396          if (bCalcSlope && (sector[pNSprite->sectnum].floorstat&2) && (sector[pNSprite->sectnum].floorheinum != 0)) // align sprite to slope
3397          {
3398              walltype *pWall1 = &wall[sector[pNSprite->sectnum].wallptr];
3399              walltype *pWall2 = &wall[pWall1->point2];
3400              pNSprite->xoffset = sector[pNSprite->sectnum].floorheinum & 255;
3401              pNSprite->yoffset = (sector[pNSprite->sectnum].floorheinum >> 8) & 255;
3402              pNSprite->cstat |= CSTAT_SPRITE_ALIGNMENT_SLOPE;
3403              pNSprite->ang = getangle(pWall2->x-pWall1->x, pWall2->y-pWall1->y)+kAng270;
3404          }
3405          return pNSprite;
3406      }
3407      case kViewEffectSpear:
3408      {
3409          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3410          if (!pNSprite)
3411              break;
3412          pNSprite->z = pTSprite->z;
3413          if (gDetail > 1)
3414              pNSprite->cstat |= 514;
3415          pNSprite->shade = ClipLow(pTSprite->shade-32, -128);
3416          pNSprite->xrepeat = pTSprite->xrepeat;
3417          pNSprite->yrepeat = 64;
3418          pNSprite->picnum = 775;
3419          break;
3420      }
3421      case kViewEffectShowWeapon:
3422      {
3423          dassert(pTSprite->type >= kDudePlayer1 && pTSprite->type <= kDudePlayer8);
3424          PLAYER *pPlayer = &gPlayer[pTSprite->type-kDudePlayer1];
3425          WEAPONICON weaponIcon = gWeaponIcon[pPlayer->curWeapon];
3426          const int nTile = weaponIcon.nTile;
3427          if (nTile < 0)
3428              break;
3429          if (pPlayer->pXSprite->health == 0)
3430              break;
3431          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
3432          if (!pNSprite)
3433              break;
3434          pNSprite->x = pTSprite->x;
3435          pNSprite->y = pTSprite->y;
3436          pNSprite->z = pTSprite->z-(32<<8);
3437          if (!VanillaMode() && (pPlayer->posture == kPostureCrouch)) // if player is crouching
3438              pNSprite->z += pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture].zOffset<<5;
3439          pNSprite->picnum = nTile;
3440          pNSprite->shade = pTSprite->shade;
3441          pNSprite->xrepeat = 32;
3442          pNSprite->yrepeat = 32;
3443          pNSprite->ang = (gCameraAng + kAng90) & kAngMask; // always face viewer
3444          if (VanillaMode())
3445              break;
3446          const int nVoxel = voxelIndex[nTile];
3447          if ((pPlayer == gView) && (gViewPos != VIEWPOS_0)) // if viewing current player in third person, set sprite to transparent
3448          {
3449              pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
3450              pNSprite->z -= weaponIcon.zOffset<<8; // offset up
3451          }
3452          else if (gShowWeapon == 2 && usevoxels && gDetail >= 4 && videoGetRenderMode() != REND_POLYMER && nVoxel != -1)
3453          {
3454              pNSprite->clipdist |= TSPR_FLAGS_SLAB;
3455              pNSprite->cstat &= ~(8|CSTAT_SPRITE_ALIGNMENT);
3456              pNSprite->picnum = nVoxel;
3457              if (pPlayer->curWeapon == kWeaponLifeLeech) // position lifeleech behind player
3458              {
3459                  pNSprite->x += mulscale30(128, Cos(gCameraAng));
3460                  pNSprite->y += mulscale30(128, Sin(gCameraAng));
3461              }
3462              if ((pPlayer->curWeapon == kWeaponLifeLeech) || (pPlayer->curWeapon == kWeaponVoodoo)) // make lifeleech/voodoo doll always face viewer like sprite
3463                  pNSprite->ang = (gCameraAng + kAng180) & kAngMask;
3464              else if ((pPlayer->curWeapon == kWeaponProxyTNT) || (pPlayer->curWeapon == kWeaponRemoteTNT)) // make proxy/remote tnt always face viewers like sprite
3465                  pNSprite->ang = (gCameraAng + kAng180 + kAng45) & kAngMask;
3466              pNSprite->z -= gWeaponIconVoxel[pPlayer->curWeapon].zOffset<<8; // offset up
3467          }
3468          else
3469              pNSprite->z -= weaponIcon.zOffset<<8; // offset up
3470          break;
3471      }
3472      }
3473      return NULL;
3474  }
3475  
3476  inline char viewApplyPlayerAsCultist(int *nTile)
3477  {
3478      switch (*nTile)
3479      {
3480          // 3150-6 DESPAWN
3481          case 3150: *nTile = 2583; break;
3482          case 3151: *nTile = 2584; break;
3483          case 3152: *nTile = 2585; break;
3484          case 3153: *nTile = 2587; break;
3485          case 3154: *nTile = 2589; break;
3486          case 3155: *nTile = 2590; break;
3487          case 3156: *nTile = 2591; break;
3488          // 3840 L IMPACT
3489          case 3840: *nTile = 2865; break;
3490          // 3845 L LIFT
3491          case 3845: *nTile = 2870; break;
3492          // 3850 MID
3493          case 3850: *nTile = 2875; break;
3494          // 3855 R IMPACT
3495          case 3855: *nTile = 2880; break;
3496          // 3860 R LIFT
3497          case 3860: *nTile = 2885; break;
3498          // 3865 MID
3499          case 3865: *nTile = 2860; break;
3500          // 3870 STAND
3501          case 3870: *nTile = 2825; break;
3502          // 3875 LEANED
3503          case 3875: *nTile = 2925; break;
3504          // 3880-4 SHOT AND FALL
3505          case 3880: *nTile = 2930; break;
3506          case 3881: *nTile = 2931; break;
3507          case 3882: *nTile = 2932; break;
3508          case 3883: *nTile = 2933; break;
3509          case 3884: *nTile = 2934; break;
3510          // 3885-6 DEAD FLAT
3511          case 3885: *nTile = 2935; break;
3512          case 3886: *nTile = 2936; break;
3513          // 3887 SWIM L IMPACT
3514          case 3887: *nTile = 3241; break;
3515          // 3892 SWIM L LIFT
3516          case 3892: *nTile = 3241; break;
3517          // 3897 MID
3518          case 3897: *nTile = 3246; break;
3519          // 3902 SWIM R IMPACT
3520          case 3902: *nTile = 3246; break;
3521          // 3907 SWIM R LIFT
3522          case 3907: *nTile = 3251; break;
3523          // 3912 SWIM MID
3524          case 3912: *nTile = 3256; break;
3525          // 3917 PRONE R1
3526          case 3917: *nTile = 3375; break;
3527          // 3922 PRONE R2
3528          case 3922: *nTile = 3380; break;
3529          // 3927 PRONE L1
3530          case 3927: *nTile = 3385; break;
3531          // 3932 PRONE L2
3532          case 3932: *nTile = 3390; break;
3533          // 3937-48 FINISH HIM DEATH SEQ
3534          case 3937: *nTile = 2930; break;
3535          case 3938: *nTile = 2931; break;
3536          case 3939: *nTile = 2932; break;
3537          case 3940: *nTile = 2932; break;
3538          case 3941: *nTile = 2932; break;
3539          case 3942: *nTile = 2933; break;
3540          case 3943: *nTile = 2933; break;
3541          case 3944: *nTile = 2933; break;
3542          case 3945: *nTile = 2934; break;
3543          case 3946: *nTile = 2935; break;
3544          case 3947: *nTile = 2936; break;
3545          case 3948: *nTile = 2937; break;
3546          // 3949-53 FINISH HIM HEADLESS SEQ
3547          case 3949: *nTile = 2933; break;
3548          case 3950: *nTile = 2934; break;
3549          case 3951: *nTile = 2935; break;
3550          case 3952: *nTile = 2936; break;
3551          case 3953: *nTile = 2937; break;
3552          // 3959 UNUSED JUMP
3553          case 3959: *nTile = 2895; break;
3554          // 3964-65 FINISH HIM KNEEL LOOP
3555          case 3964: *nTile = 2932; break;
3556          case 3965: *nTile = 2933; break;
3557          default: return 0;
3558      }
3559      return 1;
3560  }
3561  
3562  void viewReplacePlayerAsCultist(tspritetype *pTSprite, int nSpriteOrig, int nXSpriteOrig)
3563  {
3564      if (!(pTSprite->statnum == kStatDude || pTSprite->type == kThingVoodooHead))
3565          return;
3566      int nTile = pTSprite->picnum;
3567      if (nTile < 0 || nTile >= kMaxTiles)
3568          return;
3569      char bReplacedPlayerTile = 0;
3570      while ((gGameOptions.nGameType != kGameTypeSinglePlayer) && !(gGameOptions.uNetGameFlags&kNetGameFlagCalebOnly)) // replace player caleb sprite with cultist sprite
3571      {
3572          const int nPlayer = IsPlayerSprite(pTSprite) ? (pTSprite->type-kDudePlayer1)&7 : kMaxPlayers;
3573          if (!(nPlayer < kMaxPlayers && gProfile[nPlayer].nModel) && !(pTSprite->type == kThingVoodooHead && sprite[nSpriteOrig].inittype >= kDudePlayer1 && sprite[nSpriteOrig].inittype <= kDudePlayer8)) // if profile uses caleb, don't replace sprite
3574              break;
3575          bReplacedPlayerTile = viewApplyPlayerAsCultist(&nTile);
3576          if (bReplacedPlayerTile) // set TSprite picnum and adjust tile to floor
3577          {
3578              if (gSpriteHit[nXSpriteOrig].florhit) // only do this if player is standing on ground
3579              {
3580                  int topnew, topold, bottomnew, bottomold;
3581                  GetSpriteExtents(pTSprite, &topold, &bottomold);
3582                  pTSprite->picnum = nTile;
3583                  GetSpriteExtents(pTSprite, &topnew, &bottomnew);
3584                  if (bottomnew != bottomold) // align bottom of new tile to old tile
3585                      pTSprite->z -= bottomnew - bottomold;
3586                  break;
3587              }
3588              pTSprite->picnum = nTile;
3589          }
3590          break;
3591      }
3592      if ((EnemiesNotBlood() || bReplacedPlayerTile) && !gSpriteHit[nXSpriteOrig].florhit && (zvel[nSpriteOrig] > 250000) && ((nTile == 2825) || (nTile >= 2860 && nTile <= 2885))) // replace tile with unused jump tile for falling cultists
3593      {
3594          const char bReplaceTile = bReplacedPlayerTile || (pTSprite->type == kDudeCultistTommy) || (pTSprite->type == kDudeCultistShotgun) || (pTSprite->type == kDudeCultistTommyProne) || (pTSprite->type == kDudeCultistShotgunProne) || (pTSprite->type == kDudeCultistTesla) || (pTSprite->type == kDudeCultistTNT) || (pTSprite->type == kDudeCultistBeast);
3595          if (bReplaceTile)
3596              pTSprite->picnum = (zvel[nSpriteOrig] <= 500000) ? 2890 : ((zvel[nSpriteOrig] <= 750000) ? 2895 : 2900);
3597      }
3598  }
3599  
3600  LOCATION gPrevSpriteLoc[kMaxSprites];
3601  static LOCATION gViewSpritePredictLoc;
3602  
3603  inline void viewApplyFloorPal(tspritetype *pTSprite, uint8_t nPal)
3604  {
3605      if (nPal == 0 && !VanillaMode()) // keep original sprite's palette when floors are using default palette (fixes tommy gun cultists in E3M2)
3606          return;
3607      pTSprite->pal = nPal;
3608  }
3609  
3610  void viewProcessSprites(int32_t cX, int32_t cY, int32_t cZ, int32_t cA, int32_t smooth)
3611  {
3612      UNREFERENCED_PARAMETER(smooth);
3613      dassert(spritesortcnt <= kMaxViewSprites);
3614      gCameraAng = cA;
3615      int nViewSprites = spritesortcnt;
3616      for (int nTSprite = spritesortcnt-1; nTSprite >= 0; nTSprite--)
3617      {
3618          tspritetype *pTSprite = &tsprite[nTSprite];
3619          //int nXSprite = pTSprite->extra;
3620          int nSprite = pTSprite->owner;
3621          int nXSprite = sprite[nSprite].extra;
3622          XSPRITE *pTXSprite = NULL;
3623          if ((qsprite_filler[nSprite] > gDetail) || (sprite[nSprite].sectnum == -1))
3624          {
3625              pTSprite->xrepeat = 0;
3626              continue;
3627          }
3628          if (nXSprite > 0)
3629          {
3630              pTXSprite = &xsprite[nXSprite];
3631          }
3632          int nTile = pTSprite->picnum;
3633          if (nTile < 0 || nTile >= kMaxTiles)
3634          {
3635              continue;
3636          }
3637  
3638          auto const tsprflags = pTSprite->clipdist;
3639  
3640          if (gView && (gView->pSprite == &sprite[nSprite]) && IsPlayerSprite(pTSprite) && gViewInterpolate && !VanillaMode()) // improve network player prediction while in third person/co-op view
3641          {
3642              pTSprite->x = gViewSpritePredictLoc.x;
3643              pTSprite->y = gViewSpritePredictLoc.y;
3644              pTSprite->z = gViewSpritePredictLoc.z;
3645              pTSprite->ang = fix16_to_int(gViewSpritePredictLoc.ang);
3646          }
3647          else if (gViewInterpolate && TestBitString(gInterpolateSprite, nSprite) && !(pTSprite->flags&512))
3648          {
3649              LOCATION *pPrevLoc = &gPrevSpriteLoc[nSprite];
3650              pTSprite->x = interpolate(pPrevLoc->x, pTSprite->x, gInterpolate);
3651              pTSprite->y = interpolate(pPrevLoc->y, pTSprite->y, gInterpolate);
3652              pTSprite->z = interpolate(pPrevLoc->z, pTSprite->z, gInterpolate);
3653              pTSprite->ang = pPrevLoc->ang+mulscale16(((pTSprite->ang-pPrevLoc->ang+1024)&2047)-1024, gInterpolate);
3654          }
3655          if (!VanillaMode())
3656          {
3657              if ((pTSprite->statnum == kStatItem) && (pTSprite->type == kItemTwoGuns) && (pTSprite->picnum == gPowerUpInfo[kPwUpTwoGuns].picnum) && gGameOptions.bQuadDamagePowerup)
3658                  pTSprite->picnum = nTile = 9300; // if quad damage is enabled, replace guns akimbo icon with quad damage icon from notblood.pk3/TILES099.ART
3659              else
3660              {
3661                  viewReplacePlayerAsCultist(pTSprite, nSprite, nXSprite);
3662                  nTile = pTSprite->picnum;
3663              }
3664          }
3665          int nAnim = 0;
3666          switch (picanm[nTile].extra & 7) {
3667              case 0:
3668                  //dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
3669                  if (nXSprite <= 0 || nXSprite >= kMaxXSprites) break;
3670                  switch (pTSprite->type) {
3671                      #ifdef NOONE_EXTENSIONS
3672                      case kModernCondition:
3673                      case kModernConditionFalse:
3674                          if (!gModernMap) break;
3675                          fallthrough__;
3676                      #endif
3677                      case kSwitchToggle:
3678                      case kSwitchOneWay:
3679                          if (xsprite[nXSprite].state) nAnim = 1;
3680                          break;
3681                      case kSwitchCombo:
3682                          nAnim = xsprite[nXSprite].data1;
3683                          break;
3684                  }
3685                  break;
3686              case 1:
3687              {
3688                  if (tilehasmodelorvoxel(pTSprite->picnum, pTSprite->pal) && !(spriteext[nSprite].flags&SPREXT_NOTMD))
3689                  {
3690                      pTSprite->cstat &= ~4;
3691                      break;
3692                  }
3693                  int dX = cX - pTSprite->x;
3694                  int dY = cY - pTSprite->y;
3695                  RotateVector(&dX, &dY, 128-pTSprite->ang);
3696                  nAnim = GetOctant(dX, dY);
3697                  if (nAnim <= 4)
3698                  {
3699                      pTSprite->cstat &= ~4;
3700                  }
3701                  else
3702                  {
3703                      nAnim = 8 - nAnim;
3704                      pTSprite->cstat |= 4;
3705                  }
3706                  break;
3707              }
3708              case 2:
3709              {
3710                  if (tilehasmodelorvoxel(pTSprite->picnum, pTSprite->pal) && !(spriteext[nSprite].flags&SPREXT_NOTMD))
3711                  {
3712                      pTSprite->cstat &= ~4;
3713                      break;
3714                  }
3715                  int dX = cX - pTSprite->x;
3716                  int dY = cY - pTSprite->y;
3717                  RotateVector(&dX, &dY, 128-pTSprite->ang);
3718                  nAnim = GetOctant(dX, dY);
3719                  break;
3720              }
3721              case 3:
3722              {
3723                  if (nXSprite > 0)
3724                  {
3725                      if (gSpriteHit[nXSprite].florhit == 0)
3726                          nAnim = 1;
3727                  }
3728                  else
3729                  {
3730                      int top, bottom;
3731                      GetSpriteExtents(pTSprite, &top, &bottom);
3732                      if (getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) > bottom)
3733                          nAnim = 1;
3734                  }
3735                  break;
3736              }
3737              case 6:
3738              case 7:
3739              {
3740  #ifdef USE_OPENGL
3741                  if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pTSprite->picnum, pTSprite->pal) >= 0 && !(spriteext[nSprite].flags&SPREXT_NOTMD))
3742                      break;
3743  #endif
3744                  // Can be overridden by def script
3745                  if (usevoxels && gDetail >= 4 && videoGetRenderMode() != REND_POLYMER && tiletovox[pTSprite->picnum] == -1 && voxelIndex[pTSprite->picnum] != -1 && !(spriteext[nSprite].flags&SPREXT_NOTMD))
3746                  {
3747                      if ((pTSprite->flags&kHitagRespawn) == 0)
3748                      {
3749                          pTSprite->clipdist |= TSPR_FLAGS_SLAB;
3750                          pTSprite->cstat &= ~(4|8|CSTAT_SPRITE_ALIGNMENT);
3751                          pTSprite->yoffset += picanm[pTSprite->picnum].yofs;
3752                          pTSprite->picnum = voxelIndex[pTSprite->picnum];
3753                          if (!voxoff[pTSprite->picnum][0])
3754                              qloadvoxel(pTSprite->picnum);
3755                          if ((picanm[nTile].extra&7) == 7)
3756                          {
3757                              pTSprite->ang = ((int)totalclock<<3)&2047;
3758                          }
3759                      }
3760                  }
3761                  break;
3762              }
3763          }
3764          while (nAnim > 0)
3765          {
3766              pTSprite->picnum += picanm[pTSprite->picnum].num+1;
3767              nAnim--;
3768          }
3769  
3770          if (!(tsprflags & TSPR_FLAGS_SLAB) && usevoxels && videoGetRenderMode() != REND_POLYMER && !(spriteext[nSprite].flags&SPREXT_NOTMD))
3771          {
3772              int const nRootTile = pTSprite->picnum;
3773  #if 0
3774              int nAnimTile = pTSprite->picnum + animateoffs_replace(pTSprite->picnum, 32768+nSprite);
3775              if (tiletovox[nAnimTile] != -1)
3776              {
3777                  pTSprite->yoffset += picanm[nAnimTile].yofs;
3778                  pTSprite->xoffset += picanm[nAnimTile].xofs;
3779              }
3780  #endif
3781  
3782              int const nVoxel = tiletovox[pTSprite->picnum];
3783  
3784              if (nVoxel != -1 && ((voxflags[nVoxel] & VF_ROTATE) || (picanm[nRootTile].extra&7) == 7))
3785                  pTSprite->ang = (pTSprite->ang+((int)totalclock<<3))&2047;
3786          }
3787  
3788  #ifdef USE_OPENGL
3789          if (!(tsprflags & TSPR_FLAGS_SLAB) && usemodels && !(spriteext[nSprite].flags&SPREXT_NOTMD))
3790          {
3791              int const nRootTile = pTSprite->picnum;
3792              int nAnimTile = pTSprite->picnum + animateoffs_replace(pTSprite->picnum, 32768+nSprite);
3793  
3794              if (usemodels && tile2model[Ptile2tile(nAnimTile, pTSprite->pal)].modelid >= 0 &&
3795                  tile2model[Ptile2tile(nAnimTile, pTSprite->pal)].framenum >= 0)
3796              {
3797                  pTSprite->yoffset += picanm[nAnimTile].yofs;
3798                  pTSprite->xoffset += picanm[nAnimTile].xofs;
3799  
3800                  if ((picanm[nRootTile].extra&7) == 7)
3801                      pTSprite->ang = (pTSprite->ang+((int)totalclock<<3))&2047;
3802              }
3803          }
3804  #endif
3805  
3806          sectortype *pSector = &sector[pTSprite->sectnum];
3807          XSECTOR *pXSector;
3808          int nShade = pTSprite->shade;
3809          if (pSector->extra > 0)
3810          {
3811              pXSector = &xsector[pSector->extra];
3812          }
3813          else
3814          {
3815              pXSector = NULL;
3816          }
3817          if ((pSector->ceilingstat&1) && (pSector->floorstat&32768) == 0)
3818          {
3819              nShade += tileShade[pSector->ceilingpicnum]+pSector->ceilingshade;
3820          }
3821          else
3822          {
3823              nShade += tileShade[pSector->floorpicnum]+pSector->floorshade;
3824          }
3825          nShade += tileShade[pTSprite->picnum];
3826          pTSprite->shade = ClipRange(nShade, -128, 127);
3827          if ((pTSprite->flags&kHitagRespawn) && sprite[nSprite].owner == 3)
3828          {
3829              dassert(pTXSprite != NULL);
3830              pTSprite->xrepeat = 48;
3831              pTSprite->yrepeat = 48;
3832              pTSprite->shade = -128;
3833              pTSprite->picnum = 2272 + 2*pTXSprite->respawnPending;
3834              pTSprite->cstat &= ~514;
3835              if (viewUseItemRespawnMarkers(&sprite[nSprite]))
3836              {
3837                  pTSprite->xrepeat = pTSprite->yrepeat = 48;
3838              }
3839              else
3840              {
3841                  pTSprite->xrepeat = pTSprite->yrepeat = 0;
3842              }
3843          }
3844          if (spritesortcnt >= kMaxViewSprites) continue;
3845          if (pTXSprite && pTXSprite->burnTime > 0)
3846          {
3847              pTSprite->shade = ClipRange(pTSprite->shade-16-QRandom(8), -128, 127);
3848          }
3849          if (pTSprite->flags&256)
3850          {
3851              viewAddEffect(nTSprite, kViewEffectSmokeHigh);
3852          }
3853          if (pTSprite->flags&1024)
3854          {
3855              pTSprite->cstat |= 4;
3856          }
3857          if (pTSprite->flags&2048)
3858          {
3859              pTSprite->cstat |= 8;
3860          }
3861          switch (pTSprite->statnum) {
3862          case kStatDecoration: {
3863              switch (pTSprite->type) {
3864                  case kDecorationCandle:
3865                      if (!pTXSprite || pTXSprite->state == 1) {
3866                          pTSprite->shade = -128;
3867                          viewAddEffect(nTSprite, kViewEffectPhase);
3868                      } else {
3869                          pTSprite->shade = -8;
3870                      }
3871                      break;
3872                  case kDecorationTorch:
3873                      if (!pTXSprite || pTXSprite->state == 1) {
3874                          pTSprite->picnum++;
3875                          viewAddEffect(nTSprite, kViewEffectTorchHigh);
3876                      } else {
3877                          viewAddEffect(nTSprite, kViewEffectSmokeHigh);
3878                      }
3879                      break;
3880                  default:
3881                      if (pXSector && pXSector->color)
3882                          viewApplyFloorPal(pTSprite, pSector->floorpal);
3883                      break;
3884              }
3885          }
3886          break;
3887          case kStatItem: {
3888              switch (pTSprite->type) {
3889                  case kItemFlagABase:
3890                      if (pTXSprite && pTXSprite->state > 0 && gGameOptions.nGameType == kGameTypeTeams) {
3891                          auto pNTSprite = viewAddEffect(nTSprite, kViewEffectBigFlag);
3892                          if (pNTSprite) pNTSprite->pal = kFlagBluePal;
3893                      }
3894                      break;
3895                  case kItemFlagBBase:
3896                      if (pTXSprite && pTXSprite->state > 0 && gGameOptions.nGameType == kGameTypeTeams) {
3897                          auto pNTSprite = viewAddEffect(nTSprite, kViewEffectBigFlag);
3898                          if (pNTSprite) pNTSprite->pal = kFlagRedPal;
3899                      }
3900                      break;
3901                  case kItemFlagA:
3902                      pTSprite->pal = kFlagBluePal;
3903                      pTSprite->cstat |= 4;
3904                      break;
3905                  case kItemFlagB:
3906                      pTSprite->pal = kFlagRedPal;
3907                      pTSprite->cstat |= 4;
3908                      break;
3909                  default:
3910                      if (pTSprite->type >= kItemKeySkull && pTSprite->type < kItemKeyMax)
3911                          pTSprite->shade = -128;
3912                      if (pXSector && pXSector->color)
3913                          viewApplyFloorPal(pTSprite, pSector->floorpal);
3914                      break;
3915              }
3916          }
3917          break;
3918          case kStatProjectile: {
3919              switch (pTSprite->type) {
3920                  case kMissileTeslaAlt:
3921                      pTSprite->yrepeat = 128;
3922                      pTSprite->cstat |= 32;
3923                      break;
3924                  case kMissileTeslaRegular:
3925                      viewAddEffect(nTSprite, kViewEffectTesla);
3926                      break;
3927                  case kMissileButcherKnife:
3928                      viewAddEffect(nTSprite, kViewEffectTrail);
3929                      break;
3930                  case kMissileShell:
3931                  case kMissileBullet: {
3932                      sectortype *pSector = &sector[pTSprite->sectnum];
3933                      int zDiff = getceilzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y);
3934                      zDiff = (pTSprite->z - zDiff) >> 8;
3935                      if ((pSector->ceilingstat&1) == 0 && zDiff < 64) {
3936                          auto pNTSprite = viewAddEffect(nTSprite, kViewEffectCeilGlow);
3937                          if (pNTSprite) {
3938                              pNTSprite->xrepeat >>= 3;
3939                              pNTSprite->yrepeat >>= 3;
3940                              pNTSprite->shade >>= 2;
3941                          }
3942                      }
3943  
3944                      zDiff = getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y);
3945                      zDiff = (zDiff - pTSprite->z) >> 8;
3946                      if ((pSector->floorstat&1) == 0 && zDiff < 64) {
3947                          auto pNTSprite = viewAddEffect(nTSprite, kViewEffectFloorGlow);
3948                          if (pNTSprite) {
3949                              pNTSprite->xrepeat >>= 2;
3950                              pNTSprite->yrepeat >>= 2;
3951                              pNTSprite->shade >>= 1;
3952                          }
3953                      }
3954                      break;
3955                  }
3956                  case kMissileFlareRegular:
3957                  case kMissileFlareAlt:
3958                      if (pTSprite->statnum == kStatFlare) {
3959                          dassert(pTXSprite != NULL);
3960                          if (pTXSprite->target == gView->nSprite) {
3961                              pTSprite->xrepeat = 0;
3962                              break;
3963                          }
3964                      }
3965                      
3966                      viewAddEffect(nTSprite, kViewEffectFlareHalo);
3967                      if (pTSprite->type != kMissileFlareRegular) break;
3968                      sectortype *pSector = &sector[pTSprite->sectnum];
3969                      
3970                      const char bCalcSlope = gGameOptions.bSectorBehavior && !VanillaMode();
3971                      int zDiff = bCalcSlope ? getceilzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) : pSector->ceilingz;
3972                      zDiff = (pTSprite->z - zDiff) >> 8;
3973                      if ((pSector->ceilingstat&1) == 0 && zDiff < 64) {
3974                          viewAddEffect(nTSprite, kViewEffectCeilGlow);
3975                      }
3976                      
3977                      zDiff = bCalcSlope ? getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) : pSector->floorz;
3978                      zDiff = (zDiff - pTSprite->z) >> 8;
3979                      if ((pSector->floorstat&1) == 0 && zDiff < 64) {
3980                          viewAddEffect(nTSprite, kViewEffectFloorGlow);
3981                      }
3982                      break;
3983                  }
3984              break;
3985          }
3986          case kStatDude:
3987          {
3988              if (pTSprite->type == kDudeHand && pTXSprite->aiState == &hand13A3B4)
3989              {
3990                  spritetype *pTTarget = &sprite[pTXSprite->target];
3991                  dassert(pTXSprite != NULL && pTTarget != NULL);
3992                  if (IsPlayerSprite(pTTarget))
3993                  {
3994                      pTSprite->xrepeat = 0;
3995                      break;
3996                  }
3997              }
3998  
3999              if (pXSector && pXSector->color) viewApplyFloorPal(pTSprite, pSector->floorpal);
4000              if (powerupCheck(gView, kPwUpBeastVision) > 0)
4001              {
4002                  pTSprite->shade = -128;
4003                  while (WeaponsNotBlood() && !VanillaMode() && (nSprite != gView->pSprite->index) && gKillMgr.AllowedType(&sprite[nSprite]) && !cansee(cX, cY, cZ, gView->pSprite->sectnum, pTSprite->x, pTSprite->y, pTSprite->z, pTSprite->sectnum)) // janky x-ray vision
4004                  {
4005                      auto pNSprite = viewInsertTSprite(gView->pSprite->sectnum, 32767, pTSprite);
4006                      if (!pNSprite)
4007                          break;
4008                      pNSprite->ang = pTSprite->ang;
4009                      pNSprite->shade = -64;
4010                      pNSprite->pal = pTSprite->pal;
4011                      pNSprite->cstat = pTSprite->cstat;
4012                      pNSprite->cstat |= 2;
4013                      pNSprite->picnum = pTSprite->picnum;
4014                      pNSprite->x = interpolate(pTSprite->x, cX, 65536-(65536/0x30));
4015                      pNSprite->y = interpolate(pTSprite->y, cY, 65536-(65536/0x30));
4016                      pNSprite->z = interpolate(pTSprite->z, cZ, 65536-(65536/0x30));
4017                      pNSprite->xrepeat = pNSprite->yrepeat = 1;
4018                      break;
4019                  }
4020              }
4021  
4022              if (IsPlayerSprite(pTSprite)) {
4023                  PLAYER *pPlayer = &gPlayer[pTSprite->type-kDudePlayer1];
4024                  const char bIsTeammate = IsTargetTeammate(gView, pPlayer->pSprite);
4025                  const char bIsDoppleganger = (gGameOptions.nGameType == kGameTypeTeams) && powerupCheck(pPlayer, kPwUpDoppleganger);
4026                  const char bIsTeammateOrDoppleganger = bIsTeammate || bIsDoppleganger;
4027                  if (powerupCheck(pPlayer, kPwUpShadowCloak) && !powerupCheck(gView, kPwUpBeastVision)) {
4028                      pTSprite->cstat |= 2;
4029                      pTSprite->pal = 5;
4030                  }  else if (powerupCheck(pPlayer, kPwUpDeathMask) && (VanillaMode() || !bIsTeammateOrDoppleganger || (bIsTeammateOrDoppleganger && ((int)totalclock & 32)))) { // mute color if player has deathmask powerup (but don't do this if teammate)
4031                      pTSprite->shade = -128;
4032                      pTSprite->pal = 5;
4033                  } else if (powerupCheck(pPlayer, kPwUpDoppleganger) && (VanillaMode() || !bIsTeammate)) {
4034                      if (!VanillaMode() && !(gGameOptions.uNetGameFlags&kNetGameFlagNoTeamColors) && (gGameOptions.uNetGameFlags&kNetGameFlagCalebOnly || !gProfile[gView->nPlayer].nModel))
4035                          pTSprite->pal = playerColorPalSprite(gView->teamIdPal);
4036                      else
4037                          pTSprite->pal = playerColorPalDefault(gView->teamIdPal);
4038                  }
4039                  
4040                  if (powerupCheck(pPlayer, kPwUpReflectShots)) {
4041                      viewAddEffect(nTSprite, kViewEffectReflectiveBall);
4042                  }
4043  
4044                  if (gPlayerTyping[pPlayer->nPlayer] && !VanillaMode()) {
4045                      auto pNTSprite = viewAddEffect(nTSprite, kViewEffectFlag);
4046                      if (pNTSprite) {
4047                          pNTSprite->picnum = 9293; // use typing indicator icon tile from notblood.pk3/TILES099.ART
4048                          pNTSprite->x += mulscale30(Cos((gCameraAng + kAng90) & kAngMask), 96); // offset to the right
4049                          pNTSprite->y += mulscale30(Sin((gCameraAng + kAng90) & kAngMask), 96);
4050                          pNTSprite->z -= 12<<8; // offset up
4051                      }
4052                  } else if ((gGameOptions.nGameType == kGameTypeTeams) && (gGameOptions.uNetGameFlags&kNetGameFlagHideWeaponsTeams)) {
4053                      if (pPlayer->pXSprite->health > 0) {
4054                          auto pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
4055                          if (pNSprite)
4056                          {
4057                              pNSprite->x = pTSprite->x;
4058                              pNSprite->y = pTSprite->y;
4059                              pNSprite->z = pTSprite->z-(32<<8);
4060                              if (pPlayer->posture == kPostureCrouch) // if player is crouching
4061                                  pNSprite->z += pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture].zOffset<<5;
4062                              pNSprite->z -= 5<<8; // offset up
4063                              pNSprite->picnum = 2275; // skull
4064                              pNSprite->shade = pTSprite->shade;
4065                              pNSprite->xrepeat = 36;
4066                              pNSprite->yrepeat = 36;
4067                              pNSprite->ang = (gCameraAng + kAng90) & kAngMask; // always face viewer
4068                              const int nPal = powerupCheck(pPlayer, kPwUpDoppleganger) ? playerColorPalSprite(gView->teamId) : playerColorPalSprite(pPlayer->teamId);
4069                              pNSprite->pal = nPal;
4070                              if (pPlayer == gView && gViewPos == VIEWPOS_1)
4071                                  pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
4072                          }
4073                      }
4074                  } else if (gShowWeapon && (gGameOptions.nGameType != kGameTypeSinglePlayer) && gView && ((pPlayer == gView && gViewPos == VIEWPOS_1) || !(gGameOptions.uNetGameFlags&kNetGameFlagHideWeaponsAlways))) {
4075                      const char bDrawDudeWeap = !(powerupCheck(pPlayer, kPwUpShadowCloak) && (gGameOptions.uNetGameFlags&kNetGameFlagHideWeaponsCloak)) || bIsTeammateOrDoppleganger || (pPlayer == gView && gViewPos == VIEWPOS_1); // don't draw enemy weapon if they are cloaked
4076                      if ((!VanillaMode() && bDrawDudeWeap) || (VanillaMode() && (pPlayer != gView)))
4077                          viewAddEffect(nTSprite, kViewEffectShowWeapon);
4078                  }
4079  
4080                  if (pPlayer->flashEffect && (gView != pPlayer || gViewPos != VIEWPOS_0)) {
4081                      const char bTwoGuns = powerupCheck(pPlayer, kPwUpTwoGuns) && !gGameOptions.bQuadDamagePowerup && !VanillaMode();
4082                      auto pNTSprite = viewAddEffect(nTSprite, kViewEffectShoot);
4083                      if (pNTSprite) {
4084                          POSTURE *pPosture = &pPlayer->pPosture[pPlayer->lifeMode][pPlayer->posture];
4085                          pNTSprite->x += mulscale28(pPosture->zOffset, Cos(pTSprite->ang));
4086                          pNTSprite->y += mulscale28(pPosture->zOffset, Sin(pTSprite->ang));
4087                          pNTSprite->z = pPlayer->pSprite->z-pPosture->xOffset;
4088                          if (bTwoGuns) {
4089                              auto pNTSpriteOtherGun = viewAddEffect(nTSprite, kViewEffectShoot);
4090                              if (pNTSpriteOtherGun) {
4091                                  pNTSpriteOtherGun->x += mulscale28(pPosture->zOffset, Cos(pTSprite->ang));
4092                                  pNTSpriteOtherGun->y += mulscale28(pPosture->zOffset, Sin(pTSprite->ang));
4093                                  pNTSpriteOtherGun->z = pPlayer->pSprite->z-pPosture->xOffset;
4094  
4095                                  const int dx = mulscale30(Cos(pPlayer->pSprite->ang + 512), 128); // offset guns to left/right
4096                                  const int dy = mulscale30(Sin(pPlayer->pSprite->ang + 512), 128);
4097                                  pNTSprite->x += dx;
4098                                  pNTSprite->y += dy;
4099                                  pNTSpriteOtherGun->x -= dx;
4100                                  pNTSpriteOtherGun->y -= dy;
4101                              }
4102                          }
4103                      }
4104                  }
4105  
4106                  if ((pPlayer->hasFlag > 0) && (gGameOptions.nGameType == kGameTypeTeams)) { // if teams mode and has flag
4107                      const char bThirdPerson = (pPlayer == gView) && (gViewPos != VIEWPOS_0) && !VanillaMode();
4108                      if (pPlayer->hasFlag&1)  {
4109                          auto pNTSprite = viewAddEffect(nTSprite, kViewEffectFlag);
4110                          if (pNTSprite) {
4111                              pNTSprite->pal = kFlagBluePal;
4112                              pNTSprite->cstat |= CSTAT_SPRITE_XFLIP;
4113                              if (bThirdPerson) // if viewing current player in third person, set flag to transparent
4114                                  pNTSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
4115                          }
4116                      }
4117                      if (pPlayer->hasFlag&2) {
4118                          auto pNTSprite = viewAddEffect(nTSprite, kViewEffectFlag);
4119                          if (pNTSprite) {
4120                              pNTSprite->pal = kFlagRedPal;
4121                              pNTSprite->cstat |= CSTAT_SPRITE_XFLIP;
4122                              if (bThirdPerson) // if viewing current player in third person, set flag to transparent
4123                                  pNTSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
4124                          }
4125                      }
4126                  }
4127              }
4128              
4129              if (nSprite != gView->pSprite->index || gViewPos != VIEWPOS_0 || (gMirrorDrawing && !VanillaMode())) {
4130                  if (getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) >= cZ)
4131                  {
4132                      viewAddEffect(nTSprite, kViewEffectShadow);
4133                  }
4134              }
4135              
4136              #ifdef NOONE_EXTENSIONS
4137              if (gModernMap) { // add target spot indicator for patrol dudes
4138                  XSPRITE* pTXSprite = &xsprite[pTSprite->extra];
4139                  if (pTXSprite->dudeFlag4 && aiInPatrolState(pTXSprite->aiState) && pTXSprite->data3 > 0 && pTXSprite->data3 <= kMaxPatrolSpotValue)
4140                      viewAddEffect(nTSprite, kViewEffectSpotProgress);
4141              }
4142              #endif
4143              break;
4144          }
4145          case kStatTraps: {
4146              if (pTSprite->type == kTrapSawCircular) {
4147                  if (pTXSprite->state) {
4148                      if (pTXSprite->data1) {
4149                          pTSprite->picnum = 772;
4150                          if (pTXSprite->data2)
4151                              viewAddEffect(nTSprite, kViewEffectSpear);
4152                      }
4153                  } 
4154                  else if (pTXSprite->data1) pTSprite->picnum = 773;
4155                  else pTSprite->picnum = 656;
4156                  
4157              }
4158              break;
4159          }
4160          case kStatThing: {
4161              if (pXSector && pXSector->color)
4162                  viewApplyFloorPal(pTSprite, pSector->floorpal);
4163  
4164              if (pTSprite->type < kThingBase || pTSprite->type >= kThingMax || !gSpriteHit[nXSprite].florhit) {
4165                  if ((pTSprite->flags & kPhysMove) && getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) >= cZ)
4166                      viewAddEffect(nTSprite, kViewEffectShadow);
4167              }
4168              const char bArmedBomb = (pTSprite->type == kThingArmedTNTStick) || (pTSprite->type == kThingArmedTNTBundle) || (pTSprite->type == kThingArmedProxBomb) || (pTSprite->type == kThingArmedRemoteBomb);
4169              if ((powerupCheck(gView, kPwUpBeastVision) > 0) && WeaponsNotBlood() && !VanillaMode() && bArmedBomb && !cansee(cX, cY, cZ, gView->pSprite->sectnum, pTSprite->x, pTSprite->y, pTSprite->z, pTSprite->sectnum)) // janky x-ray vision
4170              {
4171                  auto pNSprite = viewInsertTSprite(gView->pSprite->sectnum, 32767, pTSprite);
4172                  if (!pNSprite)
4173                      break;
4174                  pNSprite->ang = pTSprite->ang;
4175                  pNSprite->shade = -64;
4176                  pNSprite->pal = pTSprite->pal;
4177                  pNSprite->cstat = pTSprite->cstat;
4178                  pNSprite->cstat |= 2;
4179                  pNSprite->picnum = pTSprite->picnum;
4180                  pNSprite->x = interpolate(pTSprite->x, cX, 65536-(65536/0x30));
4181                  pNSprite->y = interpolate(pTSprite->y, cY, 65536-(65536/0x30));
4182                  pNSprite->z = interpolate(pTSprite->z, cZ, 65536-(65536/0x30));
4183                  pNSprite->xrepeat = pNSprite->yrepeat = 1;
4184              }
4185          }
4186          break;
4187          }
4188      }
4189  
4190      for (int nTSprite = spritesortcnt-1; nTSprite >= nViewSprites; nTSprite--)
4191      {
4192          tspritetype *pTSprite = &tsprite[nTSprite];
4193          int nAnim = 0;
4194          switch (picanm[pTSprite->picnum].extra&7)
4195          {
4196              case 1:
4197              {
4198                  int dX = cX - pTSprite->x;
4199                  int dY = cY - pTSprite->y;
4200                  RotateVector(&dX, &dY, 128-pTSprite->ang);
4201                  nAnim = GetOctant(dX, dY);
4202                  if (nAnim <= 4)
4203                  {
4204                      pTSprite->cstat &= ~4;
4205                  }
4206                  else
4207                  {
4208                      nAnim = 8 - nAnim;
4209                      pTSprite->cstat |= 4;
4210                  }
4211                  break;
4212              }
4213              case 2:
4214              {
4215                  int dX = cX - pTSprite->x;
4216                  int dY = cY - pTSprite->y;
4217                  RotateVector(&dX, &dY, 128-pTSprite->ang);
4218                  nAnim = GetOctant(dX, dY);
4219                  break;
4220              }
4221          }
4222          while (nAnim > 0)
4223          {
4224              pTSprite->picnum += picanm[pTSprite->picnum].num+1;
4225              nAnim--;
4226          }
4227      }
4228  
4229  #ifdef NOONE_EXTENSIONS
4230      if (gModernMap) lasersProcessView3D(cX, cY, cZ, cA, -1, 0);
4231  #endif
4232  
4233  }
4234  
4235  int othercameradist = 1280;
4236  int cameradist = -1;
4237  int othercameraclock, cameraclock;
4238  
4239  void CalcOtherPosition(spritetype *pSprite, int *pX, int *pY, int *pZ, int *vsectnum, int nAng, fix16_t zm)
4240  {
4241      int vX = mulscale30(-Cos(nAng), 1280);
4242      int vY = mulscale30(-Sin(nAng), 1280);
4243      int vZ = fix16_to_int(mulscale3(zm, 1280))-(16<<8);
4244      int bakCstat = pSprite->cstat;
4245      pSprite->cstat &= ~256;
4246      dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
4247      FindSector(*pX, *pY, *pZ, vsectnum);
4248      short nHSector;
4249      int hX, hY;
4250      vec3_t pos = {*pX, *pY, *pZ};
4251      hitdata_t hitdata;
4252      hitscan(&pos, *vsectnum, vX, vY, vZ, &hitdata, CLIPMASK1);
4253      nHSector = hitdata.sect;
4254      hX = hitdata.xyz.x;
4255      hY = hitdata.xyz.y;
4256      int dX = hX-*pX;
4257      int dY = hY-*pY;
4258      if (klabs(vX)+klabs(vY) > klabs(dX)+klabs(dY))
4259      {
4260          *vsectnum = nHSector;
4261          dX -= ksgn(vX)<<6;
4262          dY -= ksgn(vY)<<6;
4263          int nDist;
4264          if (klabs(vX) > klabs(vY))
4265          {
4266              nDist = ClipHigh(divscale16(dX,vX), othercameradist);
4267          }
4268          else
4269          {
4270              nDist = ClipHigh(divscale16(dY,vY), othercameradist);
4271          }
4272          othercameradist = nDist;
4273      }
4274      *pX += mulscale16(vX, othercameradist);
4275      *pY += mulscale16(vY, othercameradist);
4276      *pZ += mulscale16(vZ, othercameradist);
4277      othercameradist = ClipHigh(othercameradist+(((int)(totalclock-othercameraclock))<<10), 65536);
4278      othercameraclock = (int)totalclock;
4279      if (*vsectnum >= 0 && *vsectnum < kMaxSectors)
4280          FindSector(*pX, *pY, *pZ, vsectnum);
4281      else // sector was not found, likely viewpoint is within wall - use sprite sect and continue
4282          *vsectnum = pSprite->sectnum;
4283      dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
4284      pSprite->cstat = bakCstat;
4285  }
4286  
4287  void CalcPosition(spritetype *pSprite, int *pX, int *pY, int *pZ, int *vsectnum, int nAng, fix16_t zm)
4288  {
4289      int vX = mulscale30(-Cos(nAng), 1280);
4290      int vY = mulscale30(-Sin(nAng), 1280);
4291      int vZ = fix16_to_int(mulscale3(zm, 1280))-(16<<8);
4292      int bakCstat = pSprite->cstat;
4293      pSprite->cstat &= ~256;
4294      dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
4295      FindSector(*pX, *pY, *pZ, vsectnum);
4296      short nHSector;
4297      int hX, hY;
4298      hitscangoal.x = hitscangoal.y = 0x1fffffff;
4299      vec3_t pos = { *pX, *pY, *pZ };
4300      hitdata_t hitdata;
4301      hitscan(&pos, *vsectnum, vX, vY, vZ, &hitdata, CLIPMASK1);
4302      nHSector = hitdata.sect;
4303      hX = hitdata.xyz.x;
4304      hY = hitdata.xyz.y;
4305      int dX = hX-*pX;
4306      int dY = hY-*pY;
4307      if (klabs(vX)+klabs(vY) > klabs(dX)+klabs(dY))
4308      {
4309          *vsectnum = nHSector;
4310          dX -= ksgn(vX)<<6;
4311          dY -= ksgn(vY)<<6;
4312          int nDist;
4313          if (klabs(vX) > klabs(vY))
4314          {
4315              nDist = ClipHigh(divscale16(dX,vX), cameradist);
4316          }
4317          else
4318          {
4319              nDist = ClipHigh(divscale16(dY,vY), cameradist);
4320          }
4321          cameradist = nDist;
4322      }
4323      *pX += mulscale16(vX, cameradist);
4324      *pY += mulscale16(vY, cameradist);
4325      *pZ += mulscale16(vZ, cameradist);
4326      cameradist = ClipHigh(cameradist+(((int)(totalclock-cameraclock))<<10), 65536);
4327      cameraclock = (int)totalclock;
4328      dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
4329      FindSector(*pX, *pY, *pZ, vsectnum);
4330      pSprite->cstat = bakCstat;
4331  }
4332  
4333  inline bool viewPaused(void)
4334  {
4335      if (gDemo.bPlaying)
4336          return false;
4337      return gPaused || gEndGameMgr.at0 || (gGameOptions.nGameType == kGameTypeSinglePlayer && (gGameMenuMgr.m_bActive || ((osd->flags & OSD_DRAW) == OSD_DRAW)));
4338  }
4339  
4340  inline void viewAimReticle(PLAYER *pPlayer, int defaultHoriz, fix16_t q16slopehoriz, float fFov)
4341  {
4342      const int32_t nStat = r_usenewaspect ? RS_AUTO : RS_AUTO | RS_STRETCH;
4343      const char bBannedWeapon = (pPlayer->curWeapon == kWeaponNone) || (pPlayer->curWeapon == kWeaponTNT) || (pPlayer->curWeapon == kWeaponProxyTNT) || (pPlayer->curWeapon == kWeaponRemoteTNT);
4344      const char bShowAutoAimTarget = (gAimReticle == 2) && pPlayer->aimTargetsCount && !bBannedWeapon;
4345      const char bPaused = viewPaused();
4346      int q16SlopeTilt = fix16_from_float(0.82f);
4347      int cX = 160;
4348      int cY = defaultHoriz;
4349      if (!gCenterHoriz && (MIRRORMODE & 2)) // offset crosshair if mirror mode is set to vertical mode
4350          cY += 19;
4351      cX <<= 16;
4352      cY <<= 16;
4353  
4354      static int cXOld = cX, cYOld = cY;
4355      if (bShowAutoAimTarget) // move crosshair depending on autoaim target
4356      {
4357          const int q16hfov = divscale16(90, gFov);
4358          const int q16vfov = Blrintf(float(fix16_one) * fFov);
4359          int cZ = pPlayer->relAim.dy * 160 / pPlayer->relAim.dx; // calculate aiming target offset from center
4360          cX += mulscale16(cZ<<16, q16hfov) * (MIRRORMODE & 1 ? -1 : 1); // scale to current fov
4361          if (MIRRORMODE & 2) // mirror mode flip
4362              cZ = -cZ;
4363          cZ = mulscale16((8<<4)<<16, q16vfov)>>16; // calculate vertical fov scale
4364          cZ = (pPlayer->relAim.dz / cZ)<<16; // convert target z to on screen units
4365          if (MIRRORMODE & 2) // mirror mode flip
4366              cZ = -cZ;
4367          cY += cZ;
4368  
4369          if (gSlopeTilting) // scale tilt with fov
4370              q16SlopeTilt = mulscale16(q16SlopeTilt, q16hfov);
4371      }
4372      if (gAimReticleOffsetX)
4373          cX += ((MIRRORMODE & 1) ? -gAimReticleOffsetX : gAimReticleOffsetX)<<15;
4374      if (gAimReticleOffsetY)
4375          cY += ((MIRRORMODE & 2) ? -gAimReticleOffsetY : gAimReticleOffsetY)<<15;
4376  
4377      if (!bPaused && gViewInterpolate && ((cXOld != cX) || (cY != cYOld)))
4378      {
4379          int nSteps = gFrameRate / kTicsPerSec; // get number of steps to interpolate using current fps
4380          if (nSteps >= 2) // if fps is double the game tickrate (60 and above), interpolate position
4381          {
4382              nSteps >>= 1; // reduce the interpolation speed by half so crosshair doesn't behave too snappy
4383              const int nInterpolate = ClipLow(gInterpolate, 1) / nSteps;
4384              cX = interpolate(cXOld, cX, nInterpolate);
4385              cY = interpolate(cYOld, cY, nInterpolate);
4386          }
4387      }
4388      cXOld = cX, cYOld = cY;
4389  
4390      if (gSlopeTilting && (gSlopeReticle || bShowAutoAimTarget)) // adjust crosshair for slope tilting/auto aim
4391      {
4392          q16SlopeTilt = mulscale16(q16slopehoriz, q16SlopeTilt);
4393          if (MIRRORMODE & 2) // mirror mode flip
4394              q16SlopeTilt = -q16SlopeTilt;
4395          cY += q16SlopeTilt;
4396      }
4397  
4398      rotatesprite(cX, cY, 65536, 0, kCrosshairTile, 0, g_isAlterDefaultCrosshair ? CROSSHAIR_PAL : 0, nStat, gViewX0, gViewY0, gViewX1, gViewY1);
4399  }
4400  
4401  // by NoOne: show warning msgs in game instead of throwing errors (in some cases)
4402  void viewSetSystemMessage(const char* pMessage, ...) {
4403      char buffer[1024]; va_list args; va_start(args, pMessage);
4404      vsprintf(buffer, pMessage, args);
4405      
4406      OSD_Printf("%s\n", buffer); // print it also in console
4407      gGameMessageMgr.Add(buffer, 15, 7, MESSAGE_PRIORITY_NORMAL);
4408  }
4409  
4410  void viewSetMessage(const char *pMessage, const int nPal, const MESSAGE_PRIORITY nPriority)
4411  {
4412      OSD_Printf("%s\n", pMessage);
4413      gGameMessageMgr.Add(pMessage, 15, nPal, nPriority);
4414  }
4415  
4416  void viewSetMessageColor(char *pMessage, const int nPal, const MESSAGE_PRIORITY nPriority, const int nPal1, const int nPal2)
4417  {
4418      int nColorPart = 0;
4419      int nColorOffsets[4] = {-1, -1, -1, -1}; // stores 4 points in string where color is to be set for player names/flags
4420      size_t nLength = strlen(pMessage);
4421  
4422      for (size_t i = 0; i < nLength; i++)
4423      {
4424          if (pMessage[i] != '\r') // this is the start/stop flag used to detect color offsets
4425              continue;
4426          Bmemmove((void *)&pMessage[i], (void *)&pMessage[i + 1], nLength - i); // remove \r character from string
4427          if (nColorPart < 4)
4428          {
4429              nColorOffsets[nColorPart] = i;
4430              nColorPart++;
4431          }
4432          nLength--;
4433      }
4434      if ((nColorPart != 2) && (nColorPart != 4)) // something went very wrong, don't color message
4435          nColorOffsets[0] = nColorOffsets[1] = nColorOffsets[2] = nColorOffsets[3] = -1;
4436  
4437      OSD_Printf("%s\n", pMessage);
4438      if ((nPal == nPal1) || VanillaMode())
4439      {
4440          gGameMessageMgr.Add(pMessage, 15, nPal, nPriority);
4441          return;
4442      }
4443  
4444      COLORSTR colorStr = {nPal1, nPal2, {nColorOffsets[0], nColorOffsets[1]}, {nColorOffsets[2], nColorOffsets[3]}}; // set info for coloring sub-strings within string
4445      gGameMessageMgr.Add(pMessage, 15, nPal, nPriority, &colorStr);
4446  }
4447  
4448  void viewDisplayMessage(void)
4449  {
4450      gGameMessageMgr.Display();
4451  }
4452  
4453  char errMsg[256];
4454  
4455  void viewSetErrorMessage(const char *pMessage)
4456  {
4457      if (!pMessage)
4458      {
4459          strcpy(errMsg, "");
4460      }
4461      else
4462      {
4463          strcpy(errMsg, pMessage);
4464      }
4465  }
4466  
4467  void DoLensEffect(void)
4468  {
4469      char *d = (char*)waloff[LENSBUFFER];
4470      dassert(d != NULL);
4471      char *s = (char*)waloff[CRYSTALBALLBUFFER];
4472      dassert(s != NULL);
4473      for (int i = 0; i < kLensSize*kLensSize; i++, d++)
4474      {
4475          if (lensTable[i] >= 0)
4476              *d = s[lensTable[i]];
4477      }
4478      tileInvalidate(LENSBUFFER, -1, -1);
4479  }
4480  
4481  void UpdateDacs(int nPalette, bool bNoTint)
4482  {
4483      static RGB newDAC[256];
4484      static int oldPalette;
4485      if (oldPalette != nPalette)
4486      {
4487          scrSetPalette(nPalette);
4488          oldPalette = nPalette;
4489      }
4490  
4491  #ifdef USE_OPENGL
4492      if (videoGetRenderMode() >= REND_POLYMOST)
4493      {
4494          gLastPal = 0;
4495          polytint_t *tint = &hictinting[MAXPALOOKUPS-1];
4496          int nRed = 0;
4497          int nGreen = 0;
4498          int nBlue = 0;
4499          tint->f = 0;
4500          switch (nPalette)
4501          {
4502          case 0:
4503          default:
4504              tint->r = 255;
4505              tint->g = 255;
4506              tint->b = 255;
4507              break;
4508          case 1:
4509              tint->r = 132;
4510              tint->g = 164;
4511              tint->b = 255;
4512              break;
4513          case 2:
4514              tint->r = 255;
4515              tint->g = 126;
4516              tint->b = 105;
4517              break;
4518          case 3:
4519              tint->r = 162;
4520              tint->g = 186;
4521              tint->b = 15;
4522              break;
4523          case 4:
4524              tint->r = 255;
4525              tint->g = 255;
4526              tint->b = 255;
4527              break;
4528          }
4529          if (!bNoTint && gView != nullptr)
4530          {
4531              nRed += gView->pickupEffect;
4532              nGreen += gView->pickupEffect;
4533              nBlue -= gView->pickupEffect;
4534  
4535              nRed += ClipHigh(gView->painEffect, 85)*2;
4536              nGreen -= ClipHigh(gView->painEffect, 85)*3;
4537              nBlue -= ClipHigh(gView->painEffect, 85)*3;
4538  
4539              nRed -= gView->blindEffect;
4540              nGreen -= gView->blindEffect;
4541              nBlue -= gView->blindEffect;
4542  
4543              nRed -= gView->chokeEffect>>6;
4544              nGreen -= gView->chokeEffect>>5;
4545              nBlue -= gView->chokeEffect>>6;
4546          }
4547          nRed = ClipRange(nRed, -255, 255);
4548          nGreen = ClipRange(nGreen, -255, 255);
4549          nBlue = ClipRange(nBlue, -255, 255);
4550  
4551          videoSetPalette(0, nPalette, 2);
4552          videoTintBlood(nRed, nGreen, nBlue);
4553      }
4554      else
4555  #endif
4556      {
4557          gLastPal = nPalette;
4558          if (bNoTint || gView == nullptr)
4559          {
4560              memcpy(newDAC, baseDAC, sizeof(newDAC));
4561          }
4562          else
4563          {
4564              for (int i = 0; i < 256; i++)
4565              {
4566                  int nRed = baseDAC[i].red;
4567                  int nGreen = baseDAC[i].green;
4568                  int nBlue = baseDAC[i].blue;
4569                  nRed += gView->pickupEffect;
4570                  nGreen += gView->pickupEffect;
4571                  nBlue -= gView->pickupEffect;
4572  
4573                  nRed += ClipHigh(gView->painEffect, 85)*2;
4574                  nGreen -= ClipHigh(gView->painEffect, 85)*3;
4575                  nBlue -= ClipHigh(gView->painEffect, 85)*3;
4576  
4577                  nRed -= gView->blindEffect;
4578                  nGreen -= gView->blindEffect;
4579                  nBlue -= gView->blindEffect;
4580  
4581                  nRed -= gView->chokeEffect>>6;
4582                  nGreen -= gView->chokeEffect>>5;
4583                  nBlue -= gView->chokeEffect>>6;
4584  
4585                  newDAC[i].red = ClipRange(nRed, 0, 255);
4586                  newDAC[i].green = ClipRange(nGreen, 0, 255);
4587                  newDAC[i].blue = ClipRange(nBlue, 0, 255);
4588              }
4589          }
4590          if (memcmp(newDAC, curDAC, 768) != 0)
4591          {
4592              memcpy(curDAC, newDAC, 768);
4593              gSetDacRange(0, 256, curDAC);
4594          }
4595      }
4596  }
4597  
4598  char otherMirrorGotpic[2];
4599  char bakMirrorGotpic[2];
4600  // int gVisibility;
4601  
4602  int deliriumTilt, deliriumTurn, deliriumPitch;
4603  int gScreenTiltO, deliriumTurnO, deliriumPitchO;
4604  
4605  int gShowFrameRate = 1;
4606  
4607  void viewUpdateDelirium(void)
4608  {
4609      gScreenTiltO = gScreenTilt;
4610      deliriumTurnO = deliriumTurn;
4611      deliriumPitchO = deliriumPitch;
4612      int powerCount;
4613      if ((powerCount = powerupCheck(gView, kPwUpDeliriumShroom)) != 0)
4614      {
4615          int tilt1 = 170, tilt2 = 170, pitch = 20;
4616          int timer = (int)gFrameClock << 1;
4617          if (powerCount < 512)
4618          {
4619              int powerScale = (powerCount<<16) / 512;
4620              tilt1 = mulscale16(tilt1, powerScale);
4621              tilt2 = mulscale16(tilt2, powerScale);
4622              pitch = mulscale16(pitch, powerScale);
4623          }
4624          int sin2 = costable[(2*timer-512)&2047] / 2;
4625          int sin3 = costable[(3*timer-512)&2047] / 2;
4626          gScreenTilt = mulscale30(sin2+sin3,tilt1);
4627          int sin4 = costable[(4*timer-512)&2047] / 2;
4628          deliriumTurn = mulscale30(sin3+sin4,tilt2);
4629          int sin5 = costable[(5*timer-512)&2047] / 2;
4630          deliriumPitch = mulscale30(sin4+sin5,pitch);
4631          return;
4632      }
4633      gScreenTilt = ((gScreenTilt+1024)&2047)-1024;
4634      if (gScreenTilt > 0)
4635      {
4636          gScreenTilt -= 8;
4637          if (gScreenTilt < 0)
4638              gScreenTilt = 0;
4639      }
4640      else if (gScreenTilt < 0)
4641      {
4642          gScreenTilt += 8;
4643          if (gScreenTilt >= 0)
4644              gScreenTilt = 0;
4645      }
4646  }
4647  
4648  int shakeHoriz, shakeAngle, shakeX, shakeY, shakeZ, shakeBobX, shakeBobY;
4649  
4650  void viewUpdateShake(void)
4651  {
4652      shakeHoriz = 0;
4653      shakeAngle = 0;
4654      shakeX = 0;
4655      shakeY = 0;
4656      shakeZ = 0;
4657      shakeBobX = 0;
4658      shakeBobY = 0;
4659      if (gView->flickerEffect)
4660      {
4661          int nValue = ClipHigh(gView->flickerEffect * 8, 2000);
4662          shakeHoriz += QRandom2(nValue >> 8);
4663          shakeAngle += QRandom2(nValue >> 8);
4664          shakeX += QRandom2(nValue >> 4);
4665          shakeY += QRandom2(nValue >> 4);
4666          shakeZ += QRandom2(nValue);
4667          shakeBobX += QRandom2(nValue);
4668          shakeBobY += QRandom2(nValue);
4669      }
4670      if (gView->quakeEffect)
4671      {
4672          int nValue = ClipHigh(gView->quakeEffect * 8, 2000);
4673          shakeHoriz += QRandom2(nValue >> 8);
4674          shakeAngle += QRandom2(nValue >> 8);
4675          shakeX += QRandom2(nValue >> 4);
4676          shakeY += QRandom2(nValue >> 4);
4677          shakeZ += QRandom2(nValue);
4678          shakeBobX += QRandom2(nValue);
4679          shakeBobY += QRandom2(nValue);
4680      }
4681  }
4682  
4683  float r_ambientlight = 1.0, r_ambientlightrecip = 1.0;
4684  
4685  int gLastPal = 0;
4686  
4687  int32_t g_frameRate;
4688  
4689  static char bRenderScaleRefresh = 0;
4690  
4691  void viewDrawScreen(void)
4692  {
4693      int nPalette = 0;
4694      static ClockTicks lastUpdate;
4695      int defaultHoriz = gCenterHoriz ? 100 : 90;
4696  
4697  #ifdef USE_OPENGL
4698      polymostcenterhoriz = defaultHoriz;
4699  #endif
4700      ClockTicks delta = totalclock - lastUpdate;
4701      if (delta < 0)
4702          delta = 0;
4703      lastUpdate = totalclock;
4704      if (!viewPaused())
4705      {
4706          gInterpolate = ((totalclock-gNetFifoClock)+kTicsPerFrame).toScale16()/kTicsPerFrame;
4707      }
4708      if (gInterpolate < 0 || gInterpolate > 65536)
4709      {
4710          gInterpolate = ClipRange(gInterpolate, 0, 65536);
4711      }
4712      if (gViewInterpolate)
4713      {
4714          CalcInterpolations();
4715      }
4716  
4717      rotatespritesmoothratio = !viewPaused() ? gInterpolate : 65536;
4718  
4719      if (gViewMode == 3 || gViewMode == 4 || gOverlayMap)
4720      {
4721          char bDoLighting = !gSlowRoomFlicker;
4722          if (gSlowRoomFlicker) // slow down sector lighting
4723          {
4724              const int kSectorLightingSpeed = kTicsPerFrame*2; // process sector lighting at half speed
4725              static ClockTicks nLastFrameClock = 0;
4726              const ClockTicks nDelta = (gFrameClock - nLastFrameClock);
4727              bDoLighting = (nDelta < 0) || (nDelta >= kSectorLightingSpeed);
4728              if (bDoLighting)
4729                  nLastFrameClock = gFrameClock;
4730          }
4731          if (bDoLighting)
4732              DoSectorLighting();
4733      }
4734      if (gViewMode == 3 || gOverlayMap)
4735      {
4736          int yxAspect = yxaspect;
4737          int viewingRange = viewingrange;
4738          if (r_usenewaspect)
4739          {
4740              newaspect_enable = 1;
4741              videoSetCorrectedAspect();
4742          }
4743          const float fFov = tanf(gFov * (PI/360.f));
4744          const int viewingRange_fov = Blrintf(float(viewingrange) * fFov);
4745          renderSetAspect(viewingRange_fov, yxaspect);
4746          int cX = gView->pSprite->x;
4747          int cY = gView->pSprite->y;
4748          gViewSpritePredictLoc.z = gView->pSprite->z;
4749          int cZ = gView->zView;
4750          int zDelta = gView->zWeapon-gView->zView-(12<<8);
4751          fix16_t cA = gView->q16ang;
4752          fix16_t q16horiz = gView->q16horiz;
4753          fix16_t q16slopehoriz = gView->q16slopehoriz;
4754          int v74 = gView->bobWidth;
4755          int v8c = gView->bobHeight;
4756          int v4c = gView->swayWidth;
4757          int v48 = gView->swayHeight;
4758          int nSectnum = gView->pSprite->sectnum;
4759          if (gViewInterpolate)
4760          {
4761              if (numplayers > 1 && gView == gMe && gPrediction && gMe->pXSprite->health > 0)
4762              {
4763                  nSectnum = predict.at68;
4764                  cX = interpolate(predictOld.at50, predict.at50, gInterpolate);
4765                  cY = interpolate(predictOld.at54, predict.at54, gInterpolate);
4766                  cZ = interpolate(predictOld.at38, predict.at38, gInterpolate);
4767                  zDelta = interpolate(predictOld.at34, predict.at34, gInterpolate);
4768                  cA = interpolateangfix16(predictOld.at30, predict.at30, gInterpolate);
4769                  q16horiz = interpolate(predictOld.at24, predict.at24, gInterpolate);
4770                  q16slopehoriz = interpolate(predictOld.at28, predict.at28, gInterpolate);
4771                  v74 = interpolate(predictOld.atc, predict.atc, gInterpolate);
4772                  v8c = interpolate(predictOld.at8, predict.at8, gInterpolate);
4773                  v4c = interpolate(predictOld.at1c, predict.at1c, gInterpolate);
4774                  v48 = interpolate(predictOld.at18, predict.at18, gInterpolate);
4775                  gViewSpritePredictLoc.z = interpolate(predictOld.at58, predict.at58, gInterpolate);
4776              }
4777              else
4778              {
4779                  VIEW *pView = &gPrevView[gViewIndex];
4780                  cX = interpolate(pView->at50, cX, gInterpolate);
4781                  cY = interpolate(pView->at54, cY, gInterpolate);
4782                  cZ = interpolate(pView->at38, cZ, gInterpolate);
4783                  zDelta = interpolate(pView->at34, zDelta, gInterpolate);
4784                  cA = interpolateangfix16(pView->at30, cA, gInterpolate);
4785                  q16horiz = interpolate(pView->at24, q16horiz, gInterpolate);
4786                  q16slopehoriz = interpolate(pView->at28, q16slopehoriz, gInterpolate);
4787                  v74 = interpolate(pView->atc, v74, gInterpolate);
4788                  v8c = interpolate(pView->at8, v8c, gInterpolate);
4789                  v4c = interpolate(pView->at1c, v4c, gInterpolate);
4790                  v48 = interpolate(pView->at18, v48, gInterpolate);
4791                  gViewSpritePredictLoc.z = interpolate(pView->at58, gViewSpritePredictLoc.z, gInterpolate);
4792              }
4793          }
4794          if (gView == gMe && (numplayers <= 1 || gPrediction) && gView->pXSprite->health != 0 && !gDemo.bPlaying && !VanillaMode())
4795          {
4796              fix16_t q16look;
4797              cA = gViewAngle;
4798              q16look = gViewLook;
4799              q16horiz = fix16_from_float(100.f * tanf(fix16_to_float(q16look) * fPI / 1024.f));
4800          }
4801          gViewSpritePredictLoc.x = cX, gViewSpritePredictLoc.y = cY, gViewSpritePredictLoc.ang = cA;
4802          viewUpdateShake();
4803          q16horiz += fix16_from_int(shakeHoriz);
4804          cA += fix16_from_int(shakeAngle);
4805          cX += shakeX;
4806          cY += shakeY;
4807          cZ += shakeZ;
4808          v4c += shakeBobX;
4809          v48 += shakeBobY;
4810          const fix16_t q16tilt = fix16_from_int(mulscale30(0x40000000-Cos(gView->tiltEffect<<2), 30));
4811          if (gViewInterpolate && (gView->tiltEffect > 0) && !VanillaMode())
4812              q16horiz += interpolate(fix16_from_int(mulscale30(0x40000000-Cos((gView->tiltEffect+4)<<2), 30)), q16tilt, gInterpolate);
4813          else
4814              q16horiz += q16tilt;
4815          if (gViewPos == VIEWPOS_0)
4816          {
4817              if (gViewHBobbing)
4818              {
4819                  cX -= mulscale30(v74, Sin(fix16_to_int(cA)))>>4;
4820                  cY += mulscale30(v74, Cos(fix16_to_int(cA)))>>4;
4821              }
4822              if (gViewVBobbing)
4823              {
4824                  cZ += v8c;
4825              }
4826              if (gSlopeTilting)
4827              {
4828                  q16horiz += q16slopehoriz;
4829              }
4830              cZ += fix16_to_int(q16horiz*10);
4831              cameradist = -1;
4832              cameraclock = (int)totalclock;
4833          }
4834          else
4835          {
4836              CalcPosition(gView->pSprite, (int*)&cX, (int*)&cY, (int*)&cZ, &nSectnum, fix16_to_int(cA), q16horiz);
4837          }
4838          const char bLink = sectRangeIsFine(nSectnum) ? CheckLink((int*)&cX, (int*)&cY, (int*)&cZ, &nSectnum) : 0;
4839          if (!bLink && gViewInterpolate && !VanillaMode() && (nSectnum != -1)) // double check current sector for interpolated movement (fixes ROR glitch such as E3M5's scanning room doorway)
4840          {
4841              int nFoundSect = nSectnum;
4842              if (FindSector(cX, cY, cZ, &nFoundSect) && (nFoundSect != nSectnum) && AreSectorsNeighbors(nSectnum, nFoundSect, 1)) // if newly found sector is connected to current sector, set as view sector
4843                  nSectnum = nFoundSect;
4844          }
4845          int nTilt = gViewInterpolate ? interpolateang(gScreenTiltO, gScreenTilt, gInterpolate) : gScreenTilt;
4846          char nPalCrystalBall = 0;
4847          bool bDelirium = powerupCheck(gView, kPwUpDeliriumShroom) > 0;
4848          static bool bDeliriumOld = false;
4849          int tiltcs = 0, tiltdim = 320;
4850          const char bCrystalBall = (powerupCheck(gView, kPwUpCrystalBall) > 0) && (gNetPlayers > 1);
4851  #ifdef USE_OPENGL
4852          int nRollAngle = 0;
4853          if ((videoGetRenderMode() != REND_CLASSIC) && gRollAngle && !gNoClip)
4854          {
4855              int nXVel = xvel[gView->pSprite->index], nYVel = yvel[gView->pSprite->index];
4856              if (gViewInterpolate)
4857              {
4858                  if (numplayers > 1 && gView == gMe && gPrediction && gMe->pXSprite->health > 0)
4859                  {
4860                      nXVel = interpolate(predictOld.at5c, predict.at5c, gInterpolate);
4861                      nYVel = interpolate(predictOld.at60, predict.at60, gInterpolate);
4862                  }
4863                  else
4864                  {
4865                      VIEW *pView = &gPrevView[gViewIndex];
4866                      nXVel = interpolate(pView->at5c, nXVel, gInterpolate);
4867                      nYVel = interpolate(pView->at60, nYVel, gInterpolate);
4868                  }
4869              }
4870              if ((nXVel != 0) || (nYVel != 0))
4871              {
4872                  const int nAng = fix16_to_int(gViewAngle)&kAngMask;
4873                  RotateVector(&nXVel, &nYVel, -nAng);
4874                  nRollAngle = 13 + (5 - klabs(gRollAngle));
4875                  nRollAngle = ClipRange(nYVel>>nRollAngle, -(kAng15+kAng5), kAng15+kAng5);
4876                  if (gRollAngle < 0)
4877                      nRollAngle = -nRollAngle;
4878              }
4879          }
4880          renderSetRollAngle(nRollAngle);
4881  #endif
4882          if (nTilt || bDelirium)
4883          {
4884              if (videoGetRenderMode() == REND_CLASSIC)
4885              {
4886                  int vr = viewingrange;
4887                  walock[TILTBUFFER] = CACHE1D_PERMANENT;
4888                  if (!waloff[TILTBUFFER])
4889                  {
4890                      tileAllocTile(TILTBUFFER, 640, 640, 0, 0);
4891                  }
4892                  if (xdim >= 640 && ydim >= 640)
4893                  {
4894                      tiltcs = 1;
4895                      tiltdim = 640;
4896                  }
4897                  renderSetTarget(TILTBUFFER, tiltdim, tiltdim);
4898                  int nAng = nTilt&(kAng90-1);
4899                  if (nAng > kAng45)
4900                  {
4901                      nAng = kAng90-nAng;
4902                  }
4903                  renderSetAspect(mulscale16(vr, dmulscale32(Cos(nAng), 262144, Sin(nAng), 163840)), yxaspect);
4904              }
4905  #ifdef USE_OPENGL
4906              else
4907                  renderSetRollAngle(nTilt+nRollAngle);
4908  #endif
4909          }
4910          else if (bCrystalBall)
4911          {
4912              int tmp = ((int)totalclock/240)%(gNetPlayers-1);
4913              int i = connecthead;
4914              PLAYER *pOther = NULL;
4915              if (!VanillaMode()) // find nearest enemy (dead or alive)
4916              {
4917                  int nOther = -1;
4918                  int nDist = INT_MAX;
4919                  for (int j = 0, p = connecthead; p >= 0; j++, p = connectpoint2[p])
4920                  {
4921                      if (gViewIndex == j) // skip self
4922                          continue;
4923                      spritetype *pSprite = gPlayer[j].pSprite;
4924                      if (pSprite && (IsTargetTeammate(gView, pSprite) || !sectRangeIsFine(pSprite->sectnum))) // skip teammates/player at invalid sector
4925                          continue;
4926                      const int nDistEnemy = approxDist(gView->pSprite->x-pSprite->x, gView->pSprite->y-pSprite->y);
4927                      if (nDist > nDistEnemy) // enemy is closer than last compared enemy, set to found player
4928                      {
4929                          nDist = nDistEnemy;
4930                          nOther = gPlayer[j].nPlayer;
4931                      }
4932                  }
4933                  if (nOther != -1) // if found a valid player
4934                      pOther = &gPlayer[nOther];
4935              }
4936              while (!pOther)
4937              {
4938                  if (i == gViewIndex)
4939                      i = connectpoint2[i];
4940                  if (tmp == 0)
4941                  {
4942                      pOther = &gPlayer[i];
4943                      break;
4944                  }
4945                  i = connectpoint2[i];
4946                  tmp--;
4947              }
4948              if (!sectRangeIsFine(pOther->pSprite->sectnum)) // sector is invalid, use self for crystal ball target
4949                  pOther = &gPlayer[gViewIndex];
4950              if (!waloff[CRYSTALBALLBUFFER])
4951              {
4952                  tileAllocTile(CRYSTALBALLBUFFER, 128, 128, 0, 0);
4953              }
4954              renderSetTarget(CRYSTALBALLBUFFER, 128, 128);
4955              renderSetAspect(65536, 78643);
4956              int vd8 = pOther->pSprite->x;
4957              int vd4 = pOther->pSprite->y;
4958              int vd0 = pOther->zView;
4959              int vcc = pOther->pSprite->sectnum;
4960              int v50 = pOther->pSprite->ang;
4961              int v54 = 0;
4962              if (pOther->flickerEffect)
4963              {
4964                  int nValue = ClipHigh(pOther->flickerEffect*8, 2000);
4965                  v54 += QRandom2(nValue>>8);
4966                  v50 += QRandom2(nValue>>8);
4967                  vd8 += QRandom2(nValue>>4);
4968                  vd4 += QRandom2(nValue>>4);
4969                  vd0 += QRandom2(nValue);
4970              }
4971              if (pOther->quakeEffect)
4972              {
4973                  int nValue = ClipHigh(pOther->quakeEffect*8, 2000);
4974                  v54 += QRandom2(nValue >> 8);
4975                  v50 += QRandom2(nValue >> 8);
4976                  vd8 += QRandom2(nValue >> 4);
4977                  vd4 += QRandom2(nValue >> 4);
4978                  vd0 += QRandom2(nValue);
4979              }
4980              CalcOtherPosition(pOther->pSprite, &vd8, &vd4, &vd0, &vcc, v50, 0);
4981              CheckLink(&vd8, &vd4, &vd0, &vcc);
4982              if (IsUnderwaterSector(vcc))
4983                  nPalCrystalBall = 10;
4984              memcpy(bakMirrorGotpic, gotpic+510, 2);
4985              memcpy(gotpic+510, otherMirrorGotpic, 2);
4986              g_visibility = (int32_t)(ClipLow(gVisibility-32*pOther->visibility, 0) * (numplayers > 1 ? 1.f : r_ambientlightrecip));
4987              int vc4, vc8;
4988              getzsofslope(vcc, vd8, vd4, &vc8, &vc4);
4989              if (VanillaMode() ? (vd0 >= vc4) : (vd0 > vc4-(8<<4)) && (gUpperLink[vcc] == -1)) // clamp to floor
4990              {
4991                  vd0 = vc4-(8<<4);
4992              }
4993              if (VanillaMode() ? (vd0 <= vc8) : (vd0 < vc8+(8<<4)) && (gLowerLink[vcc] == -1)) // clamp to ceiling
4994              {
4995                  vd0 = vc8+(8<<4);
4996              }
4997              v54 = ClipRange(v54, -200, 200);
4998              int nRORLimit = 32; // limit ROR rendering to 32 times
4999  RORHACKOTHER:
5000              int ror_status[16];
5001              for (int i = 0; i < 16; i++)
5002                  ror_status[i] = TestBitString(gotpic, 4080 + i);
5003              yax_preparedrawrooms();
5004              DrawMirrors(vd8, vd4, vd0, fix16_from_int(v50), fix16_from_int(v54 + defaultHoriz), gInterpolate, -1);
5005              drawrooms(vd8, vd4, vd0, v50, v54 + defaultHoriz, vcc);
5006              yax_drawrooms(viewProcessSprites, vcc, 0, gInterpolate);
5007              for (int i = 0; nRORLimit && (i < 16); i++) // check if ror needs to be rendered
5008              {
5009                  if (ror_status[i] != TestBitString(gotpic, 4080 + i))
5010                  {
5011                      spritesortcnt = 0;
5012                      nRORLimit--;
5013                      goto RORHACKOTHER;
5014                  }
5015              }
5016              memcpy(otherMirrorGotpic, gotpic+510, 2);
5017              memcpy(gotpic+510, bakMirrorGotpic, 2);
5018              viewProcessSprites(vd8, vd4, vd0, v50, gInterpolate);
5019              renderDrawMasks();
5020              renderRestoreTarget();
5021              renderSetAspect(viewingRange_fov, yxaspect);
5022          }
5023          else
5024          {
5025              othercameraclock = (int)totalclock;
5026          }
5027          if ((videoGetRenderMode() == REND_CLASSIC) && (gRenderScale > 1) && !bRenderScaleRefresh && !nTilt && !bDelirium)
5028          {
5029              if (!waloff[DOWNSCALEBUFFER])
5030                  viewSetRenderScale(0);
5031              if (waloff[DOWNSCALEBUFFER])
5032              {
5033                  renderSetTarget(DOWNSCALEBUFFER, tilesiz[DOWNSCALEBUFFER].x, tilesiz[DOWNSCALEBUFFER].y);
5034                  renderSetAspect(viewingRange_fov, yxaspect);
5035              }
5036          }
5037  
5038          if (!bDelirium)
5039          {
5040              deliriumTilt = 0;
5041              deliriumTurn = 0;
5042              deliriumPitch = 0;
5043          }
5044          int nSprite = headspritestat[kStatExplosion];
5045          int unk = 0;
5046          while (nSprite >= 0)
5047          {
5048              spritetype *pSprite = &sprite[nSprite];
5049              int nXSprite = pSprite->extra;
5050              dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
5051              XSPRITE *pXSprite = &xsprite[nXSprite];
5052              if (TestBitString(gotsector, pSprite->sectnum))
5053              {
5054                  unk += pXSprite->data3*32;
5055              }
5056              nSprite = nextspritestat[nSprite];
5057          }
5058          nSprite = headspritestat[kStatProjectile];
5059          while (nSprite >= 0) {
5060              spritetype *pSprite = &sprite[nSprite];
5061              switch (pSprite->type) {
5062                  case kMissileFlareRegular:
5063                  case kMissileTeslaAlt:
5064                  case kMissileFlareAlt:
5065                  case kMissileTeslaRegular:
5066                      if (TestBitString(gotsector, pSprite->sectnum)) unk += 256;
5067                      break;
5068              }
5069              nSprite = nextspritestat[nSprite];
5070          }
5071          g_visibility = (int32_t)(ClipLow(gVisibility - 32 * gView->visibility - unk, 0) * (numplayers > 1 ? 1.f : r_ambientlightrecip));
5072          if (!gViewInterpolate) 
5073          {
5074              cA += fix16_from_int(deliriumTurn);
5075          }
5076          else
5077          {
5078              cA = (cA + interpolateangfix16(fix16_from_int(deliriumTurnO), fix16_from_int(deliriumTurn), gInterpolate)) & 0x7ffffff;
5079          }
5080          int vfc, vf8;
5081          getzsofslope(nSectnum, cX, cY, &vfc, &vf8);
5082          if (VanillaMode() ? (cZ >= vf8) : (cZ > vf8-(8<<4)) && (gUpperLink[nSectnum] == -1)) // clamp to floor
5083          {
5084              cZ = vf8-(8<<4);
5085          }
5086          if (VanillaMode() ? (cZ <= vfc) : (cZ < vfc+(8<<4)) && (gLowerLink[nSectnum] == -1)) // clamp to ceiling
5087          {
5088              cZ = vfc+(8<<4);
5089          }
5090          q16horiz = ClipRange(q16horiz, F16(-200), F16(200));
5091          int nRORLimit = 32; // limit ROR rendering to 32 times
5092          ClearGotSectorSectorFX();
5093  RORHACK:
5094          int ror_status[16];
5095          for (int i = 0; i < 16; i++)
5096              ror_status[i] = TestBitString(gotpic, 4080+i);
5097          fix16_t deliriumPitchI = gViewInterpolate ? interpolate(fix16_from_int(deliriumPitchO), fix16_from_int(deliriumPitch), gInterpolate) : fix16_from_int(deliriumPitch);
5098          DrawMirrors(cX, cY, cZ, cA, q16horiz + fix16_from_int(defaultHoriz) + deliriumPitchI, gInterpolate, bLink && !VanillaMode() ? gViewIndex : -1); // only hide self sprite while traversing between sector
5099          int bakCstat = gView->pSprite->cstat;
5100          if (gViewPos == VIEWPOS_0) // don't render self while in first person view
5101          {
5102              gView->pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
5103          }
5104          else // chase camera - render as transparent
5105          {
5106              gView->pSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT_INVERT | CSTAT_SPRITE_TRANSLUCENT;
5107          }
5108  #ifdef POLYMER
5109          if (videoGetRenderMode() == REND_POLYMER)
5110              polymer_setanimatesprites(viewProcessSprites, cX, cY, cZ, fix16_to_int(cA), gInterpolate);
5111  #endif
5112          yax_preparedrawrooms();
5113          renderDrawRoomsQ16(cX, cY, cZ, cA, q16horiz + fix16_from_int(defaultHoriz) + deliriumPitchI, nSectnum);
5114          UpdateGotSectorSectorFX();
5115          yax_drawrooms(viewProcessSprites, nSectnum, 0, gInterpolate);
5116          viewProcessSprites(cX, cY, cZ, fix16_to_int(cA), gInterpolate);
5117          for (int i = 0; nRORLimit && (i < 16); i++) // check if ror needs to be rendered
5118          {
5119              if (ror_status[i] != TestBitString(gotpic, 4080+i))
5120              {
5121                  gView->pSprite->cstat = bakCstat;
5122                  spritesortcnt = 0;
5123                  nRORLimit--;
5124                  goto RORHACK;
5125              }
5126          }
5127          sub_5571C(1);
5128          int nSpriteSortCnt = spritesortcnt;
5129          renderDrawMasks();
5130          spritesortcnt = nSpriteSortCnt;
5131          sub_5571C(0);
5132          sub_557C4(cX, cY, gInterpolate);
5133          renderDrawMasks();
5134          gView->pSprite->cstat = bakCstat;
5135          char bMirrorScreen = (videoGetRenderMode() == REND_CLASSIC) && MIRRORMODE; // mirror framebuffer for classic renderer
5136  
5137          if (nTilt || bDelirium)
5138          {
5139              if (videoGetRenderMode() == REND_CLASSIC)
5140              {
5141                  dassert(waloff[ TILTBUFFER ] != 0);
5142                  renderRestoreTarget();
5143                  int vrc = 64+4+2+1024;
5144                  if (bDelirium)
5145                  {
5146                      vrc = 64+32+4+2+1+1024;
5147                  }
5148                  int nAng = nTilt & (kAng90-1);
5149                  if (nAng > kAng45)
5150                  {
5151                      nAng = kAng90 - nAng;
5152                  }
5153                  int nScale = dmulscale32(Cos(nAng), 262144, Sin(nAng), 163840)>>tiltcs;
5154                  if (bMirrorScreen) // mirror tilt buffer
5155                  {
5156                      videoMirrorTile((uint8_t *)waloff[TILTBUFFER], tilesiz[TILTBUFFER].x, tilesiz[TILTBUFFER].y);
5157                      bMirrorScreen = 0;
5158                  }
5159                  rotatesprite(160<<16, 100<<16, nScale, nTilt+kAng90, TILTBUFFER, 0, 0, vrc, gViewX0, gViewY0, gViewX1, gViewY1);
5160              }
5161  #ifdef USE_OPENGL
5162              else
5163              {
5164                  if (videoGetRenderMode() == REND_POLYMOST && gDeliriumBlur)
5165                  {
5166                      if (!bDeliriumOld)
5167                      {
5168                          glAccum(GL_LOAD, 1.f);
5169                      }
5170                      else
5171                      {
5172                          const float fBlur = pow(1.f/3.f, 30.f/g_frameRate);
5173                          glAccum(GL_MULT, fBlur);
5174                          glAccum(GL_ACCUM, 1.f-fBlur);
5175                          glAccum(GL_RETURN, 1.f);
5176                      }
5177                  }
5178              }
5179  #endif
5180          }
5181          else if ((videoGetRenderMode() == REND_CLASSIC) && (gRenderScale > 1) && !bRenderScaleRefresh)
5182          {
5183              dassert(waloff[DOWNSCALEBUFFER] != 0);
5184              renderRestoreTarget();
5185              tileInvalidate(DOWNSCALEBUFFER, -1, -1);
5186              const int nScale = divscale16(fix16_from_int(320), fix16_from_int(tilesiz[DOWNSCALEBUFFER].y-1));
5187              unsigned int nStat = RS_NOMASK|RS_YFLIP|RS_AUTO|RS_STRETCH;
5188              if (bMirrorScreen) // mirror tilt buffer
5189              {
5190                  videoMirrorTile((uint8_t *)waloff[DOWNSCALEBUFFER], tilesiz[DOWNSCALEBUFFER].y, tilesiz[DOWNSCALEBUFFER].x);
5191                  bMirrorScreen = 0;
5192              }
5193              rotatesprite(fix16_from_int(320>>1), fix16_from_int(200>>1), nScale, kAng90, DOWNSCALEBUFFER, 0, 0, nStat, gViewX0, gViewY0, gViewX1, gViewY1);
5194          }
5195          else if (bRenderScaleRefresh)
5196              bRenderScaleRefresh = 0;
5197  
5198          if (bMirrorScreen)
5199              videoMirrorDrawing();
5200  
5201          bDeliriumOld = bDelirium && gDeliriumBlur;
5202  
5203          if (r_usenewaspect)
5204              newaspect_enable = 0;
5205          renderSetAspect(viewingRange, yxAspect);
5206  #if 0
5207          int nClipDist = gView->pSprite->clipdist<<2;
5208          int ve8, vec, vf0, vf4;
5209          GetZRange(gView->pSprite, &vf4, &vf0, &vec, &ve8, nClipDist, 0);
5210          int tmpSect = nSectnum;
5211          if ((vf0 & 0xc000) == 0x4000)
5212          {
5213              tmpSect = vf0 & (kMaxWalls-1);
5214          }
5215          int v8 = byte_1CE5C2 > 0 && (sector[tmpSect].ceilingstat&1);
5216          if (gWeather.at12d8 > 0 || v8)
5217          {
5218              gWeather.Draw(cX, cY, cZ, cA, q16horiz + defaultHoriz + deliriumPitch, gWeather.at12d8);
5219              if (v8)
5220              {
5221                  gWeather.at12d8 = ClipRange(delta*8+gWeather.at12d8, 0, 4095);
5222              }
5223              else
5224              {
5225                  gWeather.at12d8 = ClipRange(gWeather.at12d8-delta*64, 0, 4095);
5226              }
5227          }
5228  #endif
5229          if (gViewPos == VIEWPOS_0)
5230          {
5231              if (gAimReticle)
5232                  viewAimReticle(gView, defaultHoriz, q16slopehoriz, fFov);
5233              if (gProfile[gView->nPlayer].nWeaponHBobbing == 0) // disable weapon sway
5234                  v4c = 0;
5235              if (gWeaponInterpolate) // smooth motion
5236              {
5237                  cX = (v4c<<8)+(160<<16);
5238                  cY = (v48<<8)+(220<<16)+(zDelta<<9);
5239              }
5240              else // quantize like vanilla v1.21
5241              {
5242                  cX = ((v4c>>8)+160)<<16;
5243                  cY = ((v48>>8)+220+(zDelta>>7))<<16;
5244              }
5245              int nShade = sector[nSectnum].floorshade; int nPalette = 0;
5246              if (sector[gView->pSprite->sectnum].extra > 0) {
5247                  sectortype *pSector = &sector[gView->pSprite->sectnum];
5248                  XSECTOR *pXSector = &xsector[pSector->extra];
5249                  if (pXSector->color)
5250                      nPalette = pSector->floorpal;
5251              }
5252              if (gViewSize == 4 && !gHudCompetitiveMode) // with this view size, move up so it matches the same position as full hud
5253                  cY -= fix16_from_float(25.f/2.f);
5254              
5255              #ifdef NOONE_EXTENSIONS
5256                  if (gView->sceneQav < 0) WeaponDraw(gView, nShade, cX, cY, nPalette);
5257                  else if (gView->pXSprite->health > 0) playerQavSceneDraw(gView, nShade, cX, cY, nPalette);
5258                  else {
5259                      gView->sceneQav = gView->weaponQav = -1;
5260                      gView->weaponTimer = 0;
5261                      gView->curWeapon = kWeaponNone;
5262                  }
5263              #else
5264                  WeaponDraw(gView, nShade, cX, cY, nPalette);
5265              #endif
5266             
5267  
5268          }
5269          if (gViewPos == VIEWPOS_0 && gView->pXSprite->burnTime > 60)
5270          {
5271              viewBurnTime(gView->pXSprite->burnTime);
5272          }
5273          if (packItemActive(gView, kPackDivingSuit))
5274          {
5275              rotatesprite(0, 0, 65536, 0, 2344, 0, 0, 256+18, gViewX0, gViewY0, gViewX1, gViewY1);
5276              rotatesprite(320<<16, 0, 65536, 1024, 2344, 0, 0, 512+22, gViewX0, gViewY0, gViewX1, gViewY1);
5277              rotatesprite(0, 200<<16, 65536, 0, 2344, 0, 0, 256+22, gViewX0, gViewY0, gViewX1, gViewY1);
5278              rotatesprite(320<<16, 200<<16, 65536, 1024, 2344, 0, 0, 512+18, gViewX0, gViewY0, gViewX1, gViewY1);
5279              if (gDetail >= 4)
5280              {
5281                  rotatesprite(15<<16, 3<<16, 65536, 0, 2346, 32, 0, 256+19, gViewX0, gViewY0, gViewX1, gViewY1);
5282                  rotatesprite(212<<16, 77<<16, 65536, 0, 2347, 32, 0, 512+19, gViewX0, gViewY0, gViewX1, gViewY1);
5283              }
5284          }
5285          if (powerupCheck(gView, kPwUpAsbestArmor) > 0)
5286          {
5287              rotatesprite(0, 200<<16, 65536, 0, 2358, 0, 0, 256+22, gViewX0, gViewY0, gViewX1, gViewY1);
5288              rotatesprite(320<<16, 200<<16, 65536, 1024, 2358, 0, 0, 512+18, gViewX0, gViewY0, gViewX1, gViewY1);
5289          }
5290          if (bCrystalBall && waloff[CRYSTALBALLBUFFER])
5291          {
5292              DoLensEffect();
5293              viewingRange = viewingrange;
5294              yxAspect = yxaspect;
5295              renderSetAspect(65536, 54613);
5296              rotatesprite((280+xscalehud)<<16, 35<<16, 53248, kAng90, LENSBUFFER, 0, nPalCrystalBall, 512+6, gViewX0, gViewY0, gViewX1, gViewY1);
5297              rotatesprite((280+xscalehud)<<16, 35<<16, 53248, 0, 1683, 0, 0, 512+35, gViewX0, gViewY0, gViewX1, gViewY1);
5298              renderSetAspect(viewingRange, yxAspect);
5299          }
5300          
5301          if (powerupCheck(gView, kPwUpDeathMask) > 0) nPalette = 4;
5302          else if(powerupCheck(gView, kPwUpReflectShots) > 0) nPalette = 1;
5303          else if (gView->isUnderwater) {
5304              if (gView->nWaterPal) nPalette = gView->nWaterPal;
5305              else {
5306                  if (gView->pXSprite->medium == kMediumWater) nPalette = 1;
5307                  else if (gView->pXSprite->medium == kMediumGoo) nPalette = 3;
5308                  else nPalette = 2;
5309              }
5310          }
5311      }
5312      if (gViewMode == 4)
5313      {
5314          int cX = 0, cY = 0, nAng = 0;
5315          if (gViewMap.bFollowMode) // calculate get current player position for 2d map for follow mode
5316          {
5317              cX = gView->pSprite->x, cY = gView->pSprite->y, nAng = gView->pSprite->ang;
5318              if (!VanillaMode()) // interpolate angle for 2d map view
5319              {
5320                  fix16_t cA = gView->q16ang;
5321                  if (gViewInterpolate)
5322                  {
5323                      if (numplayers > 1 && gView == gMe && gPrediction && gMe->pXSprite->health > 0)
5324                      {
5325                          cX = interpolate(predictOld.at50, predict.at50, gInterpolate);
5326                          cY = interpolate(predictOld.at54, predict.at54, gInterpolate);
5327                          cA = interpolateangfix16(predictOld.at30, predict.at30, gInterpolate);
5328                      }
5329                      else
5330                      {
5331                          VIEW *pView = &gPrevView[gViewIndex];
5332                          cX = interpolate(pView->at50, cX, gInterpolate);
5333                          cY = interpolate(pView->at54, cY, gInterpolate);
5334                          cA = interpolateangfix16(pView->at30, cA, gInterpolate);
5335                      }
5336                  }
5337                  if (gView == gMe && (numplayers <= 1 || gPrediction) && gView->pXSprite->health != 0)
5338                      cA = gViewAngle;
5339                  nAng = fix16_to_int(cA);
5340              }
5341          }
5342          ClearGotSectorSectorFX();
5343          gViewMap.Process(cX, cY, nAng);
5344          UpdateGotSectorSectorFX();
5345      }
5346      viewDrawInterface(delta);
5347      if (IsPlayerSprite(gView->pSprite) && (gView->hand == 1))
5348      {
5349          int zn = ((gView->zWeapon-gView->zView-(12<<8))>>7)+220;
5350          gChoke.Draw(160, zn);
5351      }
5352      if (byte_1A76C6)
5353      {
5354          DrawStatSprite(2048, xdim-15, 20);
5355      }
5356      viewDisplayMessage();
5357      CalcFrameRate();
5358  #if 0
5359      if (gShowFrameRate)
5360      {
5361          int fX, fY;
5362          if (gViewMode == 3)
5363          {
5364              fX = gViewX1;
5365          }
5366          else
5367          {
5368              fX = xdim;
5369          }
5370          if (gViewMode == 3)
5371          {
5372              fY = gViewY0;
5373          }
5374          else
5375          {
5376              fY = 0;
5377          }
5378          if (gViewMode == 4)
5379          {
5380              fY += mulscale16(20, yscale);
5381          }
5382          sprintf(gTempStr, "%3i", gFrameRate);
5383          printext256(fX-12, fY, 31, -1, gTempStr, 1);
5384          fY += 8;
5385          sprintf(gTempStr, "pos=%d,%d,%d", gView->pSprite->x, gView->pSprite->y, gView->pSprite->z);
5386          printext256(fX-strlen(gTempStr)*4, fY, 31, -1, gTempStr, 1);
5387      }
5388  #endif
5389      viewDrawMapTitle();
5390      viewDrawAimedPlayerName();
5391      viewPrintFPS();
5392      if (gPaused)
5393      {
5394          viewDrawText(1, "PAUSED", 160, 10, 0, 0, 1, 0);
5395      }
5396      else if ((gView == gMe) && gDemo.bRecording && ((int)totalclock < (kTicRate*4)))
5397      {
5398          viewDrawText(0, "] DEMO STARTED [", 160, 10, -128, 7, 1, 1);
5399      }
5400      else if (gView != gMe)
5401      {
5402          sprintf(gTempStr, "] %s [", gProfile[gView->nPlayer].name);
5403          if (!VanillaMode()) // color player name
5404          {
5405              COLORSTR colorStr = {playerColorPalMessage(gPlayer[gView->nPlayer].teamId), 0, {2, 2+(int)strlen(gProfile[gView->nPlayer].name)}, {-1, -1}};
5406              viewDrawText(0, gTempStr, 160, 10, 0, 0, 1, 0, 0, 0, &colorStr);
5407          }
5408          else
5409          {
5410              viewDrawText(0, gTempStr, 160, 10, 0, 0, 1, 0);
5411          }
5412      }
5413      if (errMsg[0])
5414      {
5415          viewDrawText(0, errMsg, 160, 20, 0, 0, 1, 0);
5416      }
5417      if (gViewInterpolate)
5418      {
5419          RestoreInterpolations();
5420      }
5421      UpdateDacs(nPalette);
5422  }
5423  
5424  int nLoadingScreenTile;
5425  char pzLoadingScreenText1[256], pzLoadingScreenText2[256], pzLoadingScreenText3[256];
5426  
5427  void viewLoadingScreenWide(void)
5428  {
5429  #ifdef NOONE_EXTENSIONS
5430      if (gStatsPicnum != kLoadScreen)
5431      {
5432          if (gStatsColor < 255) videoClearScreen(gStatsColor);
5433          if (gStatsPicnum >= 0)
5434          {
5435              static ClockTicks oclock = gFrameClock; gFrameClock = totalclock;
5436              rotatesprite(160 << 16, 100 << 16, 65536, 0, gStatsPicnum, 0, 0, 0xa, 0, 0, xdim - 1, ydim - 1);
5437              gFrameClock = oclock;
5438          }
5439          return;
5440      }
5441  #endif
5442      
5443      videoClearScreen(0);
5444  #ifdef USE_OPENGL
5445      if ((blood_globalflags&BLOOD_FORCE_WIDELOADSCREEN) || (bLoadScreenCrcMatch && !(usehightile && h_xsize[kLoadScreen])))
5446  #else
5447      if ((blood_globalflags&BLOOD_FORCE_WIDELOADSCREEN) || bLoadScreenCrcMatch)
5448  #endif
5449      {
5450          if (yxaspect >= 65536)
5451          {
5452              rotatesprite(160<<16, 100<<16, 65536, 0, kLoadScreen, 0, 0, 1024+64+8+2, 0, 0, xdim-1, ydim-1);
5453          }
5454          else
5455          {
5456              int width = roundscale(xdim, 240, ydim);
5457              int nCount = (width+kLoadScreenWideBackWidth-1)/kLoadScreenWideBackWidth;
5458              for (int i = 0; i < nCount; i++)
5459              {
5460                  rotatesprite_fs((i*kLoadScreenWideBackWidth)<<16, 0, 65536, 0, kLoadScreenWideBack, 0, 0, 256+64+16+8+2);
5461              }
5462              rotatesprite_fs((kLoadScreenWideSideWidth>>1)<<16, 200<<15, 65536, 0, kLoadScreenWideLeft, 0, 0, 256+8+2);
5463              rotatesprite_fs((320-(kLoadScreenWideSideWidth>>1))<<16, 200<<15, 65536, 0, kLoadScreenWideRight, 0, 0, 512+8+2);
5464              rotatesprite_fs(320<<15, 200<<15, 65536, 0, kLoadScreenWideMiddle, 0, 0, 8+2);
5465          }
5466      }
5467      else
5468          rotatesprite(160<<16, 100<<16, 65536, 0, kLoadScreen, 0, 0, 64+8+2, 0, 0, xdim-1, ydim-1);
5469  }
5470  
5471  void viewLoadingScreenUpdate(const char *pzText4, int nPercent)
5472  {
5473      int vc;
5474      gMenuTextMgr.GetFontInfo(1, NULL, NULL, &vc);
5475      if (nLoadingScreenTile == kLoadScreen)
5476          viewLoadingScreenWide();
5477      else if (nLoadingScreenTile)
5478      {
5479          videoClearScreen(0);
5480          rotatesprite(160<<16, 100<<16, 65536, 0, nLoadingScreenTile, 0, 0, 0xa, 0, 0, xdim-1, ydim-1);
5481      }
5482      if (pzLoadingScreenText1[0])
5483      {
5484          rotatesprite(160<<16, 20<<16, 65536, 0, 2038, -128, 0, 78, 0, 0, xdim-1, ydim-1);
5485          viewDrawText(1, pzLoadingScreenText1, 160, 20-vc/2, -128, 0, 1, 1);
5486      }
5487      if (pzLoadingScreenText2[0])
5488      {
5489          viewDrawText(1, pzLoadingScreenText2, 160, 50, -128, 0, 1, 1);
5490      }
5491      if (pzLoadingScreenText3[0])
5492      {
5493          viewDrawText(1, pzLoadingScreenText3, 160, 70, -128, 0, 1, 1);
5494      }
5495      if (pzText4)
5496      {
5497          viewDrawText(3, pzText4, 160, 124, -128, 0, 1, 1);
5498      }
5499  
5500      if (nPercent != -1)
5501          TileHGauge(2260, 86, 110, nPercent, 100, 0, 131072);
5502  
5503      viewDrawText(3, "Please Wait", 160, 134, -128, 0, 1, 1);
5504  }
5505  
5506  void viewLoadingScreen(int nTile, const char *pText, const char *pText2, const char *pText3)
5507  {
5508      UpdateDacs(0, true);
5509      nLoadingScreenTile = nTile;
5510      if (pText)
5511          strncpy(pzLoadingScreenText1, pText, 256);
5512      else
5513          pzLoadingScreenText1[0] = 0;
5514      if (pText2)
5515          strncpy(pzLoadingScreenText2, pText2, 256);
5516      else
5517          pzLoadingScreenText2[0] = 0;
5518      if (pText3)
5519          strncpy(pzLoadingScreenText3, pText3, 256);
5520      else
5521          pzLoadingScreenText3[0] = 0;
5522      viewLoadingScreenUpdate(NULL, -1);
5523  }
5524  
5525  palette_t CrosshairColors = { 255, 255, 255, 0 };
5526  bool g_isAlterDefaultCrosshair = false;
5527  
5528  void viewSetCrosshairColor(int32_t r, int32_t g, int32_t b)
5529  {
5530      if (!g_isAlterDefaultCrosshair)
5531          return;
5532  
5533      CrosshairColors.r = r;
5534      CrosshairColors.g = g;
5535      CrosshairColors.b = b;
5536  
5537      tileLoad(kCrosshairTile);
5538  
5539      if (!waloff[kCrosshairTile])
5540          return;
5541  
5542      char *ptr = (char *)waloff[kCrosshairTile];
5543  
5544      int32_t ii = tilesiz[kCrosshairTile].x * tilesiz[kCrosshairTile].y;
5545  
5546      dassert(ii > 0);
5547  
5548      int32_t i = (videoGetRenderMode() == REND_CLASSIC)
5549          ? paletteGetClosestColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b)
5550          : paletteGetClosestColor(255, 255, 255);  // use white in GL so we can tint it to the right color
5551  
5552      do
5553      {
5554          if (*ptr != 255)
5555              *ptr = i;
5556          ptr++;
5557      } while (--ii);
5558  
5559      paletteMakeLookupTable(CROSSHAIR_PAL, NULL, CrosshairColors.r, CrosshairColors.g, CrosshairColors.b, 1);
5560  
5561  #ifdef USE_OPENGL
5562      // XXX: this makes us also load all hightile textures tinted with the crosshair color!
5563      polytint_t & crosshairtint = hictinting[CROSSHAIR_PAL];
5564      crosshairtint.r = CrosshairColors.r;
5565      crosshairtint.g = CrosshairColors.g;
5566      crosshairtint.b = CrosshairColors.b;
5567      crosshairtint.f = HICTINT_USEONART | HICTINT_GRAYSCALE;
5568  #endif
5569      tileInvalidate(kCrosshairTile, -1, -1);
5570  }
5571  
5572  void viewResetCrosshairToDefault(void)
5573  {
5574      paletteFreeLookupTable(CROSSHAIR_PAL);
5575      tileLoad(kCrosshairTile);
5576  }
5577  
5578  void viewSetRenderScale(char bShowRes)
5579  {
5580      const int kMaxDownScale = 480*2;
5581      if ((videoGetRenderMode() != REND_CLASSIC) || (gRenderScale <= 1))
5582      {
5583          if (bShowRes)
5584              OSD_Printf("Render resolution set to native res\n");
5585          tileDelete(DOWNSCALEBUFFER);
5586          return;
5587      }
5588  
5589      int nSizeX = ClipRange((gViewX1-gViewX0+1)/gRenderScale, 8, kMaxDownScale);
5590      int nSizeY = ClipRange((gViewY1-gViewY0+1)/gRenderScale, 8, kMaxDownScale);
5591  
5592      if (waloff[DOWNSCALEBUFFER]) // for some reason build has a problem when changing the render scale, so we need to skip a single frame before it'll work again
5593          bRenderScaleRefresh = 1;
5594      else
5595          tileAllocTile(DOWNSCALEBUFFER, kMaxDownScale, kMaxDownScale, 0, 0);
5596      tileSetSize(DOWNSCALEBUFFER, nSizeY, nSizeX);
5597  
5598      if (bShowRes)
5599          OSD_Printf("Render resolution set to %dx%d\n", nSizeX, nSizeY);
5600  }
5601  
5602  #define COLOR_RED redcol
5603  #define COLOR_WHITE whitecol
5604  
5605  #define LOW_FPS 60
5606  #define SLOW_FRAME_TIME 20
5607  
5608  #if defined GEKKO
5609  # define FPS_YOFFSET 16
5610  #else
5611  # define FPS_YOFFSET 0
5612  #endif
5613  
5614  #define FPS_COLOR(x) ((x) ? COLOR_RED : COLOR_WHITE)
5615  
5616  int32_t gShowFps, gFramePeriod;
5617  
5618  void viewPrintFPS(void)
5619  {
5620      char tempbuf[128];
5621      static int32_t frameCount;
5622      static double cumulativeFrameDelay;
5623      static double lastFrameTime;
5624      static float lastFPS, minFPS = std::numeric_limits<float>::max(), maxFPS;
5625      static double minGameUpdate = std::numeric_limits<double>::max(), maxGameUpdate;
5626  
5627      double frameTime = timerGetFractionalTicks();
5628      double frameDelay = frameTime - lastFrameTime;
5629      cumulativeFrameDelay += frameDelay;
5630  
5631      if (frameDelay >= 0)
5632      {
5633          int32_t x = (xdim <= 640);
5634  
5635          if (gShowFps)
5636          {
5637              int32_t chars = Bsprintf(tempbuf, "%.1f ms, %5.1f fps", frameDelay, lastFPS);
5638  
5639              printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5640              printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+1+FPS_YOFFSET,
5641                  FPS_COLOR(lastFPS < LOW_FPS), -1, tempbuf, x);
5642  
5643              if (gShowFps > 1)
5644              {
5645                  chars = Bsprintf(tempbuf, "max: %5.1f fps", maxFPS);
5646  
5647                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+10+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5648                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+10+FPS_YOFFSET,
5649                      FPS_COLOR(maxFPS < LOW_FPS), -1, tempbuf, x);
5650  
5651                  chars = Bsprintf(tempbuf, "min: %5.1f fps", minFPS);
5652  
5653                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+20+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5654                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+20+FPS_YOFFSET,
5655                      FPS_COLOR(minFPS < LOW_FPS), -1, tempbuf, x);
5656              }
5657              if (gShowFps > 2)
5658              {
5659                  if (g_gameUpdateTime > maxGameUpdate) maxGameUpdate = g_gameUpdateTime;
5660                  if (g_gameUpdateTime < minGameUpdate) minGameUpdate = g_gameUpdateTime;
5661  
5662                  chars = Bsprintf(tempbuf, "Game Update: %2.2f ms + draw: %2.2f ms", g_gameUpdateTime, g_gameUpdateAndDrawTime);
5663  
5664                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+30+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5665                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+30+FPS_YOFFSET,
5666                      FPS_COLOR(g_gameUpdateAndDrawTime >= SLOW_FRAME_TIME), -1, tempbuf, x);
5667  
5668                  chars = Bsprintf(tempbuf, "GU min/max/avg: %5.2f/%5.2f/%5.2f ms", minGameUpdate, maxGameUpdate, g_gameUpdateAvgTime);
5669  
5670                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+40+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5671                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+40+FPS_YOFFSET,
5672                      FPS_COLOR(maxGameUpdate >= SLOW_FRAME_TIME), -1, tempbuf, x);
5673                  
5674                  chars = Bsprintf(tempbuf, "bufferjitter: %i", gBufferJitter);
5675  
5676                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+50+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5677                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+50+FPS_YOFFSET,
5678                      COLOR_WHITE, -1, tempbuf, x);
5679  #if 0
5680                  chars = Bsprintf(tempbuf, "G_MoveActors(): %.3e ms", g_moveActorsTime);
5681  
5682                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+50+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5683                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+50+FPS_YOFFSET,
5684                      COLOR_WHITE, -1, tempbuf, x);
5685  
5686                  chars = Bsprintf(tempbuf, "G_MoveWorld(): %.3e ms", g_moveWorldTime);
5687  
5688                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+60+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5689                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+60+FPS_YOFFSET,
5690                      COLOR_WHITE, -1, tempbuf, x);
5691  #endif
5692              }
5693  #if 0
5694              // lag meter
5695              if (g_netClientPeer)
5696              {
5697                  chars = Bsprintf(tempbuf, "%d +- %d ms", (g_netClientPeer->lastRoundTripTime + g_netClientPeer->roundTripTime)/2,
5698                      (g_netClientPeer->lastRoundTripTimeVariance + g_netClientPeer->roundTripTimeVariance)/2);
5699  
5700                  printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+30+2+FPS_YOFFSET, 0, -1, tempbuf, x);
5701                  printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+30+1+FPS_YOFFSET, FPS_COLOR(g_netClientPeer->lastRoundTripTime > 200), -1, tempbuf, x);
5702              }
5703  #endif
5704          }
5705  
5706          if (cumulativeFrameDelay >= 1000.0)
5707          {
5708              lastFPS = 1000.f * frameCount / cumulativeFrameDelay;
5709              g_frameRate = Blrintf(lastFPS);
5710              frameCount = 0;
5711              cumulativeFrameDelay = 0.0;
5712  
5713              if (gShowFps > 1)
5714              {
5715                  if (lastFPS > maxFPS) maxFPS = lastFPS;
5716                  if (lastFPS < minFPS) minFPS = lastFPS;
5717  
5718                  static int secondCounter;
5719  
5720                  if (++secondCounter >= gFramePeriod)
5721                  {
5722                      maxFPS = (lastFPS + maxFPS) * .5f;
5723                      minFPS = (lastFPS + minFPS) * .5f;
5724                      maxGameUpdate = (g_gameUpdateTime + maxGameUpdate) * 0.5;
5725                      minGameUpdate = (g_gameUpdateTime + minGameUpdate) * 0.5;
5726                      secondCounter = 0;
5727                  }
5728              }
5729          }
5730          frameCount++;
5731      }
5732      lastFrameTime = frameTime;
5733  }
5734  
5735  #undef FPS_COLOR
5736  
5737  class ViewLoadSave : public LoadSave {
5738  public:
5739      void Load(void);
5740      void Save(void);
5741  };
5742  
5743  static ViewLoadSave *myLoadSave;
5744  
5745  static int messageTime;
5746  static char message[256];
5747  
5748  void ViewLoadSave::Load(void)
5749  {
5750      Read(&messageTime, sizeof(messageTime));
5751      Read(message, sizeof(message));
5752      Read(otherMirrorGotpic, sizeof(otherMirrorGotpic));
5753      Read(bakMirrorGotpic, sizeof(bakMirrorGotpic));
5754      Read(&gScreenTilt, sizeof(gScreenTilt));
5755      Read(&deliriumTilt, sizeof(deliriumTilt));
5756      Read(&deliriumTurn, sizeof(deliriumTurn));
5757      Read(&deliriumPitch, sizeof(deliriumPitch));
5758      gScreenTiltO = gScreenTilt;
5759      deliriumTurnO = deliriumTurn;
5760      deliriumPitchO = deliriumPitch;
5761  }
5762  
5763  void ViewLoadSave::Save(void)
5764  {
5765      Write(&messageTime, sizeof(messageTime));
5766      Write(message, sizeof(message));
5767      Write(otherMirrorGotpic, sizeof(otherMirrorGotpic));
5768      Write(bakMirrorGotpic, sizeof(bakMirrorGotpic));
5769      Write(&gScreenTilt, sizeof(gScreenTilt));
5770      Write(&deliriumTilt, sizeof(deliriumTilt));
5771      Write(&deliriumTurn, sizeof(deliriumTurn));
5772      Write(&deliriumPitch, sizeof(deliriumPitch));
5773  }
5774  
5775  void ViewLoadSaveConstruct(void)
5776  {
5777      myLoadSave = new ViewLoadSave();
5778  }