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 = §or[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 = §or[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 = §or[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 = §or[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 = §or[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 = §or[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 = §or[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 = §or[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 }