callback.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 24 #include "build.h" 25 #include "common_game.h" 26 27 #include "actor.h" 28 #include "ai.h" 29 #include "blood.h" 30 #include "callback.h" 31 #include "config.h" 32 #include "db.h" 33 #include "dude.h" 34 #include "endgame.h" 35 #include "eventq.h" 36 #include "fx.h" 37 #include "gameutil.h" 38 #include "globals.h" 39 #include "levels.h" 40 #include "player.h" 41 #include "replace.h" 42 #include "seq.h" 43 #include "sfx.h" 44 #include "sound.h" 45 #include "trig.h" 46 #include "triggers.h" 47 #include "view.h" 48 #ifdef NOONE_EXTENSIONS 49 #include "nnexts.h" 50 #endif 51 52 void fxFlameLick(int nSprite) // 0 53 { 54 spritetype *pSprite = &sprite[nSprite]; 55 int nXSprite = pSprite->extra; 56 XSPRITE *pXSprite = &xsprite[nXSprite]; 57 int top, bottom; 58 GetSpriteExtents(pSprite, &top, &bottom); 59 for (int i = 0; i < 3; i++) 60 { 61 int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>3; 62 int nAngle = Random(2048); 63 int dx = mulscale30(nDist, Cos(nAngle)); 64 int dy = mulscale30(nDist, Sin(nAngle)); 65 int x = pSprite->x + dx; 66 int y = pSprite->y + dy; 67 int z = bottom-Random(bottom-top); 68 spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, z); 69 if (pFX) 70 { 71 xvel[pFX->index] = xvel[nSprite] + Random2(-dx); 72 yvel[pFX->index] = yvel[nSprite] + Random2(-dy); 73 zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa); 74 } 75 } 76 if (pXSprite->burnTime > 0) 77 evPost(nSprite, 3, 5, kCallbackFXFlameLick); 78 } 79 80 void Remove(int nSprite) // 1 81 { 82 spritetype *pSprite = &sprite[nSprite]; 83 evKill(nSprite, 3); 84 if (pSprite->extra > 0) 85 seqKill(3, pSprite->extra); 86 sfxKill3DSound(pSprite); 87 88 if (pSprite->statnum < kStatFree) 89 DeleteSprite(nSprite); 90 } 91 92 void FlareBurst(int nSprite) // 2 93 { 94 dassert(nSprite >= 0 && nSprite < kMaxSprites); 95 spritetype *pSprite = &sprite[nSprite]; 96 int nAngle = getangle(xvel[nSprite], yvel[nSprite]); 97 int nRadius = 0x55555; 98 for (int i = 0; i < 8; i++) 99 { 100 spritetype *pSpawn = actSpawnSprite(pSprite, 5); 101 pSpawn->picnum = 2424; 102 pSpawn->shade = -128; 103 pSpawn->xrepeat = pSpawn->yrepeat = 32; 104 pSpawn->type = kMissileFlareAlt; 105 pSpawn->clipdist = 2; 106 pSpawn->owner = pSprite->owner; 107 int nAngle2 = (i<<11)/8; 108 int dx = 0; 109 int dy = mulscale30r(nRadius, Sin(nAngle2)); 110 int dz = mulscale30r(nRadius, -Cos(nAngle2)); 111 if (i&1) 112 { 113 dy >>= 1; 114 dz >>= 1; 115 } 116 RotateVector(&dx, &dy, nAngle); 117 xvel[pSpawn->index] += dx; 118 yvel[pSpawn->index] += dy; 119 zvel[pSpawn->index] += dz; 120 evPost(pSpawn->index, 3, 960, kCallbackRemove); 121 } 122 evPost(nSprite, 3, 0, kCallbackRemove); 123 } 124 125 void fxFlareSpark(int nSprite) // 3 126 { 127 spritetype *pSprite = &sprite[nSprite]; 128 spritetype *pFX = gFX.fxSpawn(FX_28, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 129 if (pFX) 130 { 131 xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa); 132 yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa); 133 zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa); 134 } 135 evPost(nSprite, 3, 4, kCallbackFXFlareSpark); 136 } 137 138 void fxFlareSparkLite(int nSprite) // 4 139 { 140 spritetype *pSprite = &sprite[nSprite]; 141 spritetype *pFX = gFX.fxSpawn(FX_28, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 142 if (pFX) 143 { 144 xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa); 145 yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa); 146 zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa); 147 } 148 evPost(nSprite, 3, 12, kCallbackFXFlareSparkLite); 149 } 150 151 void fxZombieBloodSpurt(int nSprite) // 5 152 { 153 dassert(nSprite >= 0 && nSprite < kMaxSprites); 154 spritetype *pSprite = &sprite[nSprite]; 155 int nXSprite = pSprite->extra; 156 dassert(nXSprite > 0 && nXSprite < kMaxXSprites); 157 XSPRITE *pXSprite = &xsprite[nXSprite]; 158 int top, bottom; 159 GetSpriteExtents(pSprite, &top, &bottom); 160 spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, top); 161 if (pFX) 162 { 163 xvel[pFX->index] = xvel[nSprite] + Random2(0x11111); 164 yvel[pFX->index] = yvel[nSprite] + Random2(0x11111); 165 zvel[pFX->index] = zvel[nSprite] - 0x6aaaa; 166 } 167 if (pXSprite->data1 > 0) 168 { 169 evPost(nSprite, 3, 4, kCallbackFXZombieSpurt); 170 pXSprite->data1 -= kTicsPerFrame; 171 } 172 else if (pXSprite->data2 > 0) 173 { 174 evPost(nSprite, 3, 60, kCallbackFXZombieSpurt); 175 pXSprite->data1 = 40; 176 pXSprite->data2--; 177 } 178 } 179 180 void fxBloodSpurt(int nSprite) // 6 181 { 182 spritetype *pSprite = &sprite[nSprite]; 183 spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 184 if (pFX) 185 { 186 pFX->ang = 0; 187 xvel[pFX->index] = xvel[nSprite]>>8; 188 yvel[pFX->index] = yvel[nSprite]>>8; 189 zvel[pFX->index] = zvel[nSprite]>>8; 190 } 191 evPost(nSprite, 3, 6, kCallbackFXBloodSpurt); 192 } 193 194 195 void fxArcSpark(int nSprite) // 7 196 { 197 spritetype* pSprite = &sprite[nSprite]; 198 spritetype* pFX = gFX.fxSpawn(FX_15, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 199 if (pFX) 200 { 201 xvel[pFX->index] = xvel[nSprite] + Random2(0x10000); 202 yvel[pFX->index] = yvel[nSprite] + Random2(0x10000); 203 zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa); 204 } 205 evPost(nSprite, 3, 3, kCallbackFXArcSpark); 206 } 207 208 209 void fxDynPuff(int nSprite) // 8 210 { 211 int nCallbackTicks = 12; 212 spritetype *pSprite = &sprite[nSprite]; 213 if (zvel[nSprite]) 214 { 215 const bool bSmokeTrail3D = gSmokeTrail3D && !VanillaMode() && (gGameOptions.nGameType == kGameTypeSinglePlayer) && actSpriteOwnerIsDude(pSprite) && ((pSprite->type == kThingArmedTNTStick) || (pSprite->type == kThingArmedTNTBundle) || (pSprite->type == kThingArmedSpray)); 216 if (bSmokeTrail3D) // feature is single-player only (causes desync) 217 { 218 const int nTile = 3436; 219 const int frames = picanm[nTile].num; 220 const int frameoffset = qanimateoffs(nTile, 32768+nSprite); 221 const int angleOffset = frameoffset * (kAngMask / (frames+1)); 222 int angle = (pSprite->ang+((-angleOffset)-kAng90))&kAngMask; 223 if (actSpriteOwnerIsPlayer(pSprite)) // chances are if an enemy is throwing tnt at the player, the angle will be inverted - so rotate by 180 degrees 224 angle = (angle+kAng180)&kAngMask; 225 int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum+frameoffset].x/2))>>3; 226 nDist += nDist>>2; 227 int x = pSprite->x + mulscale30(nDist, Cos(angle)); 228 int y = pSprite->y + mulscale30(nDist, Sin(angle)); 229 int z = pSprite->z + mulscale30(nDist<<3, Cos(angle)); 230 spritetype *pFX = gFX.fxSpawn(FX_7, pSprite->sectnum, x, y, z); 231 if (pFX) 232 { 233 xvel[pFX->index] = xvel[nSprite] + mulscale30(nDist<<10, Cos(angle)); 234 yvel[pFX->index] = yvel[nSprite] + mulscale30(nDist<<10, Sin(angle)); 235 zvel[pFX->index] = zvel[nSprite]; 236 nCallbackTicks = 10; 237 } 238 } 239 else // original 240 { 241 int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2; 242 int x = pSprite->x + mulscale30(nDist, Cos(pSprite->ang-kAng90)); 243 int y = pSprite->y + mulscale30(nDist, Sin(pSprite->ang-kAng90)); 244 int z = pSprite->z; 245 spritetype *pFX = gFX.fxSpawn(FX_7, pSprite->sectnum, x, y, z); 246 if (pFX) 247 { 248 xvel[pFX->index] = xvel[nSprite]; 249 yvel[pFX->index] = yvel[nSprite]; 250 zvel[pFX->index] = zvel[nSprite]; 251 } 252 } 253 } 254 evPost(nSprite, 3, nCallbackTicks, kCallbackFXDynPuff); 255 } 256 257 void Respawn(int nSprite) // 9 258 { 259 spritetype *pSprite = &sprite[nSprite]; 260 dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites); 261 XSPRITE *pXSprite = &xsprite[pSprite->extra]; 262 263 if (pSprite->statnum != kStatRespawn && pSprite->statnum != kStatThing) { 264 OSD_Printf("Sprite #%d is not on Respawn or Thing list\n", nSprite); 265 return; 266 } else if (!(pSprite->flags & kHitagRespawn)) { 267 OSD_Printf("Sprite #%d does not have the respawn attribute\n", nSprite); 268 return; 269 } 270 271 switch (pXSprite->respawnPending) { 272 case 1: { 273 int nTime = mulscale16(actGetRespawnTime(pSprite), 0x4000); 274 pXSprite->respawnPending = 2; 275 evPost(nSprite, 3, nTime, kCallbackRespawn); 276 break; 277 } 278 case 2: { 279 int nTime = mulscale16(actGetRespawnTime(pSprite), 0x2000); 280 pXSprite->respawnPending = 3; 281 evPost(nSprite, 3, nTime, kCallbackRespawn); 282 break; 283 } 284 case 3: { 285 dassert(pSprite->owner != kStatRespawn); 286 dassert(pSprite->owner >= 0 && pSprite->owner < kMaxStatus); 287 ChangeSpriteStat(nSprite, pSprite->owner); 288 pSprite->type = pSprite->inittype; 289 pSprite->owner = -1; 290 pSprite->flags &= ~kHitagRespawn; 291 xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0; 292 pXSprite->respawnPending = 0; 293 pXSprite->burnTime = 0; 294 pXSprite->isTriggered = 0; 295 if (IsDudeSprite(pSprite)) { 296 if (!VanillaMode()) // remove a kill 297 gKillMgr.RemoveKill(pSprite); 298 int nType = pSprite->type-kDudeBase; 299 pSprite->x = baseSprite[nSprite].x; 300 pSprite->y = baseSprite[nSprite].y; 301 pSprite->z = baseSprite[nSprite].z; 302 pSprite->cstat |= 0x1101; 303 #ifdef NOONE_EXTENSIONS 304 305 if (gModernMap) 306 { 307 pXSprite->health = nnExtDudeStartHealth(pSprite, pXSprite->sysData2); 308 if (pXSprite->dudeFlag4) // return dude to the patrol state 309 { 310 pXSprite->data3 = 0; 311 pXSprite->target = -1; 312 } 313 } 314 else pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4; 315 316 switch (pSprite->type) { 317 default: 318 pSprite->clipdist = getDudeInfo(nType + kDudeBase)->clipdist; 319 if (gSysRes.Lookup(getDudeInfo(nType + kDudeBase)->seqStartID, "SEQ")) 320 seqSpawn(getDudeInfo(nType + kDudeBase)->seqStartID, 3, pSprite->extra, -1); 321 break; 322 case kDudeModernCustom: 323 break; 324 } 325 326 #else 327 pSprite->clipdist = getDudeInfo(nType + kDudeBase)->clipdist; 328 pXSprite->health = getDudeInfo(nType + kDudeBase)->startHealth << 4; 329 if (gSysRes.Lookup(getDudeInfo(nType + kDudeBase)->seqStartID, "SEQ")) 330 seqSpawn(getDudeInfo(nType + kDudeBase)->seqStartID, 3, pSprite->extra, -1); 331 #endif 332 aiInitSprite(pSprite); 333 pXSprite->key = 0; 334 } else if (pSprite->type == kThingTNTBarrel) { 335 pSprite->cstat |= CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN; 336 pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE; 337 } 338 339 gFX.fxSpawn(FX_29, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 340 sfxPlay3DSound(pSprite, 350, -1, 0); 341 break; 342 } 343 } 344 } 345 346 void PlayerBubble(int nSprite) // 10 347 { 348 spritetype *pSprite = &sprite[nSprite]; 349 if (IsPlayerSprite(pSprite)) 350 { 351 PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1]; 352 dassert(pPlayer != NULL); 353 if (!pPlayer->bubbleTime) 354 return; 355 int top, bottom; 356 GetSpriteExtents(pSprite, &top, &bottom); 357 for (int i = 0; i < (pPlayer->bubbleTime>>6); i++) 358 { 359 int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2; 360 int nAngle = Random(2048); 361 int x = pSprite->x + mulscale30(nDist, Cos(nAngle)); 362 int y = pSprite->y + mulscale30(nDist, Sin(nAngle)); 363 int z = bottom-Random(bottom-top); 364 spritetype *pFX = gFX.fxSpawn((FX_ID)(FX_23+Random(3)), pSprite->sectnum, x, y, z); 365 if (pFX) 366 { 367 xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa); 368 yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa); 369 zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa); 370 } 371 } 372 evPost(nSprite, 3, 4, kCallbackPlayerBubble); 373 } 374 } 375 376 void EnemyBubble(int nSprite) // 11 377 { 378 spritetype *pSprite = &sprite[nSprite]; 379 int top, bottom; 380 GetSpriteExtents(pSprite, &top, &bottom); 381 for (int i = 0; i < (klabs(zvel[nSprite])>>18); i++) 382 { 383 int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2; 384 int nAngle = Random(2048); 385 int x = pSprite->x + mulscale30(nDist, Cos(nAngle)); 386 int y = pSprite->y + mulscale30(nDist, Sin(nAngle)); 387 int z = bottom-Random(bottom-top); 388 spritetype *pFX = gFX.fxSpawn((FX_ID)(FX_23+Random(3)), pSprite->sectnum, x, y, z); 389 if (pFX) 390 { 391 xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa); 392 yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa); 393 zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa); 394 } 395 } 396 evPost(nSprite, 3, 4, kCallbackEnemeyBubble); 397 } 398 399 void CounterCheck(int nSector) // 12 400 { 401 dassert(nSector >= 0 && nSector < kMaxSectors); 402 if (sector[nSector].type != kSectorCounter) return; 403 if (sector[nSector].extra <= 0) return; 404 405 XSECTOR *pXSector = &xsector[sector[nSector].extra]; 406 int nReq = pXSector->waitTimeA; int nType = pXSector->data; int nCount = 0; 407 if (!nType || !nReq) return; 408 409 for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { 410 if (sprite[nSprite].type == nType) nCount++; 411 } 412 413 if (nCount < nReq) { 414 evPost(nSector, 6, 5, kCallbackCounterCheck); 415 return; 416 } else { 417 //pXSector->waitTimeA = 0; //do not reset necessary objects counter to zero 418 trTriggerSector(nSector, pXSector, kCmdOn, kCauserGame); 419 pXSector->locked = 1; //lock sector, so it can be opened again later 420 } 421 } 422 423 424 void FinishHim(int nSprite) // 13 425 { 426 spritetype* pSprite = &sprite[nSprite]; 427 int nXSprite = pSprite->extra; 428 XSPRITE* pXSprite = &xsprite[nXSprite]; 429 if (gGameOptions.nGameType <= kGameTypeCoop) 430 return; 431 if (IsPlayerSprite(pSprite) && playerSeqPlaying(&gPlayer[pSprite->type - kDudePlayer1], 16) && pXSprite->target == gMe->nSprite) 432 sndStartSample(3313, -1, 1, 0); 433 } 434 435 void fxBloodBits(int nSprite) // 14 436 { 437 spritetype *pSprite = &sprite[nSprite]; 438 int ceilZ, ceilHit, floorZ, floorHit; 439 GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0); 440 int top, bottom; 441 GetSpriteExtents(pSprite, &top, &bottom); 442 pSprite->z += floorZ-bottom; 443 int nAngle = Random(2048); 444 int nDist = Random(16)<<4; 445 int x = pSprite->x+mulscale28(nDist, Cos(nAngle)); 446 int y = pSprite->y+mulscale28(nDist, Sin(nAngle)); 447 int nSector = pSprite->sectnum; 448 if (gGameOptions.bSectorBehavior && !VanillaMode()) // check sector when creating splatter in random directions 449 { 450 if (!FindSector(x, y, pSprite->z, &nSector)) // could not find valid sector, delete fx 451 { 452 gFX.fxFree(nSprite); 453 return; 454 } 455 if ((sector[nSector].floorpicnum >= 4080) && (sector[nSector].floorpicnum <= 4095)) // if sector is open air, delete fx 456 { 457 gFX.fxFree(nSprite); 458 return; 459 } 460 GetZRangeAtXYZ(x, y, pSprite->z, nSector, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0); // get new floor position of changed sector 461 } 462 gFX.fxSpawn(FX_48, nSector, x, y, pSprite->z); 463 if (pSprite->ang == 1024) 464 { 465 const int nChannel = 28+(pSprite->index&2); 466 const int nFlags = !VanillaMode() ? 1|4 : 1; // don't cut off if channel is already occupied 467 dassert(nChannel < 32); 468 sfxPlay3DSound(pSprite, 385, nChannel, nFlags); 469 } 470 if (Chance(0x5000)) 471 { 472 if (!gGameOptions.bSectorBehavior || VanillaMode()) // restore sector (resolves optimization bug) 473 nSector = pSprite->sectnum; 474 spritetype *pFX = gFX.fxSpawn(FX_36, nSector, x, y, floorZ-64); 475 if (pFX) 476 { 477 pFX->ang = nAngle; 478 if (gGameOptions.bSectorBehavior && !VanillaMode()) 479 { 480 floorZ = getflorzofslope(pFX->sectnum, pFX->x, pFX->y); // get more accurate z position of sector 481 if (klabs(pFX->z-floorZ) <= (32<<8)) // if sprite is less than one meter from floor, set to sector height and test for slope 482 { 483 pFX->z = floorZ; 484 if ((sector[pFX->sectnum].floorstat&2) && (sector[pFX->sectnum].floorheinum != 0)) // align sprite to slope 485 { 486 walltype *pWall1 = &wall[sector[pFX->sectnum].wallptr]; 487 walltype *pWall2 = &wall[pWall1->point2]; 488 spriteSetSlope(pFX->index, sector[pFX->sectnum].floorheinum); 489 pFX->ang = getangle(pWall2->x-pWall1->x, pWall2->y-pWall1->y)+kAng270; 490 } 491 } 492 } 493 } 494 } 495 gFX.fxFree(nSprite); 496 } 497 498 499 void fxTeslaAlt(int nSprite) // 15 500 { 501 spritetype* pSprite = &sprite[nSprite]; 502 spritetype* pFX = gFX.fxSpawn(FX_49, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 503 if (pFX) 504 { 505 xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa); 506 yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa); 507 zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa); 508 } 509 evPost(nSprite, 3, 3, kCallbackFXTeslaAlt); 510 } 511 512 513 int tommySleeveSnd[] = { 608, 609, 611 }; // unused? 514 int sawedOffSleeveSnd[] = { 610, 612 }; 515 516 void fxBouncingSleeve(int nSprite) // 16 517 { 518 if ((sprite[nSprite].cstat&kPhysFalling) && !VanillaMode() && EnemiesNotBlood()) // cultist spawned shell, remove when it hits the floor 519 return gFX.fxFree(nSprite); 520 spritetype* pSprite = &sprite[nSprite]; int ceilZ, ceilHit, floorZ, floorHit; 521 GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0); 522 int top, bottom; GetSpriteExtents(pSprite, &top, &bottom); 523 pSprite->z += floorZ - bottom; 524 525 int zv = zvel[nSprite] - velFloor[pSprite->sectnum]; 526 527 if (zvel[nSprite] == 0) sleeveStopBouncing(pSprite); 528 else if (zv > 0) { 529 actFloorBounceVector((int*)& xvel[nSprite], (int*)& yvel[nSprite], &zv, pSprite->sectnum, 0x9000); 530 zvel[nSprite] = zv; 531 if (velFloor[pSprite->sectnum] == 0 && klabs(zvel[nSprite]) < 0x20000) { 532 sleeveStopBouncing(pSprite); 533 return; 534 } 535 536 int nChannel = 28 + (pSprite->index & 2); 537 dassert(nChannel < 32); 538 539 // tommy sleeve 540 if (pSprite->type >= FX_37 && pSprite->type <= FX_39) { 541 Random(3); 542 sfxPlay3DSound(pSprite, 608 + Random(2), nChannel, 1); 543 544 // sawed-off sleeve 545 } else { 546 sfxPlay3DSound(pSprite, sawedOffSleeveSnd[Random(2)], nChannel, 1); 547 } 548 } 549 550 } 551 552 553 void sleeveStopBouncing(spritetype* pSprite) { 554 xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0; 555 if (pSprite->extra > 0) seqKill(3, pSprite->extra); 556 sfxKill3DSound(pSprite, -1, -1); 557 558 switch (pSprite->type) { 559 case FX_37: 560 case FX_38: 561 case FX_39: 562 pSprite->picnum = 2465; 563 break; 564 case FX_40: 565 case FX_41: 566 case FX_42: 567 pSprite->picnum = 2464; 568 break; 569 } 570 571 pSprite->type = FX_51; // static spent casing 572 pSprite->xrepeat = pSprite->yrepeat = 10; 573 if (gGameOptions.bSectorBehavior && !VanillaMode()) // offset into ground so casings can be dragged across sectors 574 { 575 pSprite->z = 0; 576 int top, bottom; 577 GetSpriteExtents(pSprite, &top, &bottom); 578 pSprite->z = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y) - bottom; 579 } 580 } 581 582 583 void returnFlagToBase(int nSprite) // 17 584 { 585 spritetype* pSprite = &sprite[nSprite]; 586 if (pSprite->owner >= 0 && pSprite->owner < kMaxSprites) 587 { 588 spritetype* pOwner = &sprite[pSprite->owner]; 589 XSPRITE* pXOwner = &xsprite[pOwner->extra]; 590 switch (pSprite->type) { 591 case kItemFlagA: 592 trTriggerSprite(pOwner->index, pXOwner, kCmdOn, pOwner->index); 593 sndStartSample(8003, 255, 2, 0); 594 gBlueFlagDropped = false; 595 viewSetMessage("Blue Flag returned to base."); 596 break; 597 case kItemFlagB: 598 trTriggerSprite(pOwner->index, pXOwner, kCmdOn, pOwner->index); 599 sndStartSample(8002, 255, 2, 0); 600 gRedFlagDropped = false; 601 viewSetMessage("Red Flag returned to base."); 602 break; 603 } 604 } 605 evPost(pSprite->index, 3, 0, kCallbackRemove); 606 } 607 608 void fxPodBloodSpray(int nSprite) // 18 609 { 610 spritetype* pSprite = &sprite[nSprite]; 611 spritetype* pFX; 612 if (pSprite->type == 53) 613 pFX = gFX.fxSpawn(FX_53, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 614 else 615 pFX = gFX.fxSpawn(FX_54, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 616 if (pFX) 617 { 618 pFX->ang = 0; 619 xvel[pFX->index] = xvel[nSprite] >> 8; 620 yvel[pFX->index] = yvel[nSprite] >> 8; 621 zvel[pFX->index] = zvel[nSprite] >> 8; 622 } 623 evPost(nSprite, 3, 6, kCallbackFXPodBloodSpray); 624 } 625 626 void fxPodBloodSplat(int nSprite) // 19 627 { 628 spritetype *pSprite = &sprite[nSprite]; 629 int ceilZ, ceilHit, floorZ, floorHit; 630 GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0); 631 int top, bottom; 632 GetSpriteExtents(pSprite, &top, &bottom); 633 pSprite->z += floorZ-bottom; 634 int nAngle = Random(2048); 635 int nDist = Random(16)<<4; 636 int x = pSprite->x+mulscale28(nDist, Cos(nAngle)); 637 int y = pSprite->y+mulscale28(nDist, Sin(nAngle)); 638 if (pSprite->ang == 1024 && pSprite->type == 53) 639 { 640 int nChannel = 28+(pSprite->index&2); 641 dassert(nChannel < 32); 642 sfxPlay3DSound(pSprite, 385, nChannel, 1); 643 } 644 spritetype *pFX = NULL; 645 if (pSprite->type == 53 || pSprite->type == kThingPodGreenBall) 646 { 647 if (Chance(0x500) || pSprite->type == kThingPodGreenBall) 648 pFX = gFX.fxSpawn(FX_55, pSprite->sectnum, x, y, floorZ-64); 649 if (pFX) 650 pFX->ang = nAngle; 651 } 652 else 653 { 654 pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, floorZ-64); 655 if (pFX) 656 pFX->ang = nAngle; 657 } 658 gFX.fxFree(nSprite); 659 } 660 661 662 663 void LeechStateTimer(int nSprite) // 20 664 { 665 spritetype *pSprite = &sprite[nSprite]; 666 if (pSprite->statnum == kStatThing && !(pSprite->flags & 32)) { 667 switch (pSprite->type) { 668 case kThingDroppedLifeLeech: 669 #ifdef NOONE_EXTENSIONS 670 case kModernThingEnemyLifeLeech: 671 #endif 672 xsprite[pSprite->extra].stateTimer = 0; 673 break; 674 } 675 } 676 } 677 678 void sub_76A08(spritetype *pSprite, spritetype *pSprite2, PLAYER *pPlayer) // ??? 679 { 680 int top, bottom; 681 int nSprite = pSprite->index; 682 GetSpriteExtents(pSprite, &top, &bottom); 683 pSprite->x = pSprite2->x; 684 pSprite->y = pSprite2->y; 685 pSprite->z = sector[pSprite2->sectnum].floorz-(bottom-pSprite->z); 686 pSprite->ang = pSprite2->ang; 687 ChangeSpriteSect(nSprite, pSprite2->sectnum); 688 sfxPlay3DSound(pSprite2, 201, -1, 0); 689 xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0; 690 viewBackupSpriteLoc(nSprite, pSprite); 691 if (pPlayer) 692 { 693 playerResetInertia(pPlayer); 694 pPlayer->zViewVel = pPlayer->zWeaponVel = 0; 695 } 696 } 697 698 void DropVoodoo(int nSprite) // unused 699 { 700 spritetype *pSprite = &sprite[nSprite]; 701 int nOwner = actSpriteOwnerToSpriteId(pSprite); 702 if (nOwner < 0 || nOwner >= kMaxSprites) 703 { 704 evPost(nSprite, 3, 0, kCallbackRemove); 705 return; 706 } 707 spritetype *pOwner = &sprite[nOwner]; 708 PLAYER *pPlayer; 709 if (IsPlayerSprite(pOwner)) 710 pPlayer = &gPlayer[pOwner->type-kDudePlayer1]; 711 else 712 pPlayer = NULL; 713 if (!pPlayer) 714 { 715 evPost(nSprite, 3, 0, kCallbackRemove); 716 return; 717 } 718 pSprite->ang = getangle(pOwner->x-pSprite->x, pOwner->y-pSprite->y); 719 int nXSprite = pSprite->extra; 720 if (nXSprite > 0) 721 { 722 XSPRITE *pXSprite = &xsprite[nXSprite]; 723 if (pXSprite->data1 == 0) 724 { 725 evPost(nSprite, 3, 0, kCallbackRemove); 726 return; 727 } 728 int nSprite2, nNextSprite; 729 for (nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nNextSprite) 730 { 731 nNextSprite = nextspritestat[nSprite2]; 732 if (nOwner == nSprite2) 733 continue; 734 spritetype *pSprite2 = &sprite[nSprite2]; 735 int nXSprite2 = pSprite2->extra; 736 if (nXSprite2 > 0 && nXSprite2 < kMaxXSprites) 737 { 738 XSPRITE *pXSprite2 = &xsprite[nXSprite2]; 739 PLAYER *pPlayer2; 740 if (IsPlayerSprite(pSprite2)) 741 pPlayer2 = &gPlayer[pSprite2->type-kDudePlayer1]; 742 else 743 pPlayer2 = NULL; 744 if (pXSprite2->health > 0 && (pPlayer2 || pXSprite2->key == 0)) 745 { 746 if (pPlayer2) 747 { 748 if (gGameOptions.nGameType == kGameTypeCoop) 749 continue; 750 if (gGameOptions.nGameType == kGameTypeTeams && pPlayer->teamId == pPlayer2->teamId) 751 continue; 752 int t = 0x8000/ClipLow(gNetPlayers-1, 1); 753 if (!powerupCheck(pPlayer2, kPwUpDeathMask)) 754 t += ((3200-pPlayer2->armor[2])<<15)/3200; 755 if (Chance(t) || nNextSprite < 0) 756 { 757 int nDmg = actDamageSprite(nOwner, pSprite2, kDamageSpirit, pXSprite->data1<<4); 758 pXSprite->data1 = ClipLow(pXSprite->data1-nDmg, 0); 759 sub_76A08(pSprite2, pSprite, pPlayer2); 760 evPost(nSprite, 3, 0, kCallbackRemove); 761 return; 762 } 763 } 764 else 765 { 766 int vd = 0x2666; 767 switch (pSprite2->type) 768 { 769 case kDudeBoneEel: 770 case kDudeBat: 771 case kDudeRat: 772 case kDudeTinyCaleb: 773 case kDudeBeast: 774 vd = 0x147; 775 break; 776 case kDudeZombieAxeBuried: 777 case kDudePodGreen: 778 case kDudeTentacleGreen: 779 case kDudePodFire: 780 case kDudeTentacleFire: 781 case kDudePodMother: 782 case kDudeTentacleMother: 783 case kDudeCerberusTwoHead: 784 case kDudeCerberusOneHead: 785 case kDudeTchernobog: 786 case kDudeBurningInnocent: 787 case kDudeBurningCultist: 788 case kDudeBurningZombieAxe: 789 case kDudeBurningZombieButcher: 790 case kDudeCultistReserved: 791 case kDudeZombieAxeLaying: 792 case kDudeInnocent: 793 case kDudeBurningTinyCaleb: 794 case kDudeBurningBeast: 795 vd = 0; 796 break; 797 } 798 if (vd && (Chance(vd) || nNextSprite < 0)) 799 { 800 sub_76A08(pSprite2, pSprite, NULL); 801 evPost(nSprite, 3, 0, kCallbackRemove); 802 return; 803 } 804 } 805 } 806 } 807 } 808 pXSprite->data1 = ClipLow(pXSprite->data1-1, 0); 809 evPost(nSprite, 3, 0, kCallbackRemove); 810 } 811 } 812 813 #ifdef NOONE_EXTENSIONS 814 void fxPodGreenBloodSpray(int nSprite) // 24 815 { 816 spritetype* pSprite = &sprite[nSprite]; 817 spritetype* pFX; 818 819 pFX = gFX.fxSpawn(FX_53, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z); 820 if (pFX) 821 { 822 pFX->ang = 0; 823 xvel[pFX->index] = xvel[nSprite] >> 8; 824 yvel[pFX->index] = yvel[nSprite] >> 8; 825 zvel[pFX->index] = zvel[nSprite] >> 8; 826 } 827 828 evPost(nSprite, 3, 6, kCallbackFxPodGreenBloodSpray); 829 } 830 #endif 831 832 void(*gCallback[kCallbackMax])(int) = 833 { 834 fxFlameLick, 835 Remove, 836 FlareBurst, 837 fxFlareSpark, 838 fxFlareSparkLite, 839 fxZombieBloodSpurt, 840 fxBloodSpurt, 841 fxArcSpark, 842 fxDynPuff, 843 Respawn, 844 PlayerBubble, 845 EnemyBubble, 846 CounterCheck, 847 FinishHim, 848 fxBloodBits, 849 fxTeslaAlt, 850 fxBouncingSleeve, 851 returnFlagToBase, 852 fxPodBloodSpray, 853 fxPodBloodSplat, 854 LeechStateTimer, 855 DropVoodoo, // unused 856 #ifdef NOONE_EXTENSIONS 857 callbackUniMissileBurst, // the code is in nnexts.cpp 858 callbackMakeMissileBlocking, // the code is in nnexts.cpp 859 fxPodGreenBloodSpray, 860 #endif 861 };