/ source / blood / src / callback.cpp
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  };