/ source / blood / src / triggers.cpp
triggers.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 <random>
  24  #include <iostream>
  25  
  26  #include "build.h"
  27  #include "compat.h"
  28  #include "mmulti.h"
  29  #include "common_game.h"
  30  
  31  #include "ai.h"
  32  #include "actor.h"
  33  #include "blood.h"
  34  #include "db.h"
  35  #include "endgame.h"
  36  #include "eventq.h"
  37  #include "fx.h"
  38  #include "gameutil.h"
  39  #include "gib.h"
  40  #include "globals.h"
  41  #include "levels.h"
  42  #include "loadsave.h"
  43  #include "player.h"
  44  #include "seq.h"
  45  #include "qav.h"
  46  #include "sfx.h"
  47  #include "sound.h"
  48  #include "triggers.h"
  49  #include "trig.h"
  50  #include "view.h"
  51  #include "messages.h"
  52  #include "weapon.h"
  53  #ifdef NOONE_EXTENSIONS
  54  #include "nnexts.h"
  55  #endif
  56  
  57  int basePath[kMaxSectors];
  58  
  59  void FireballTrapSeqCallback(int, int);
  60  void MGunFireSeqCallback(int, int);
  61  void MGunOpenSeqCallback(int, int);
  62  
  63  int nFireballTrapClient = seqRegisterClient(FireballTrapSeqCallback);
  64  int nMGunFireClient = seqRegisterClient(MGunFireSeqCallback);
  65  int nMGunOpenClient = seqRegisterClient(MGunOpenSeqCallback);
  66  
  67  
  68  
  69  unsigned int GetWaveValue(unsigned int nPhase, int nType)
  70  {
  71      switch (nType)
  72      {
  73      case 0:
  74          return fix16_from_float(0.5)-(Cos((nPhase<<10)>>16)>>15);
  75      case 1:
  76          return nPhase;
  77      case 2:
  78          return fix16_from_float(1)-(Cos((nPhase<<9)>>16)>>14);
  79      case 3:
  80          return Sin((nPhase<<9)>>16)>>14;
  81      }
  82      return nPhase;
  83  }
  84  
  85  char SetSpriteState(int nSprite, XSPRITE* pXSprite, int nState, int causerID)
  86  {
  87      if ((pXSprite->busy & 0xffff) == 0 && pXSprite->state == nState)
  88          return 0;
  89      pXSprite->busy = nState << 16;
  90      pXSprite->state = nState;
  91      evKill(nSprite, 3, causerID);
  92      if ((sprite[nSprite].flags & kHitagRespawn) != 0 && sprite[nSprite].inittype >= kDudeBase && sprite[nSprite].inittype < kDudeMax)
  93      {
  94          pXSprite->respawnPending = 3;
  95          evPost(nSprite, 3, gGameOptions.nMonsterRespawnTime, kCallbackRespawn);
  96          return 1;
  97      }
  98      if (pXSprite->restState != nState && pXSprite->waitTime > 0)
  99          evPost(nSprite, 3, (pXSprite->waitTime * 120) / 10, pXSprite->restState ? kCmdOn : kCmdOff, causerID);
 100      if (pXSprite->txID)
 101      {
 102          if (pXSprite->command != kCmdLink && pXSprite->triggerOn && pXSprite->state)
 103              evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command, causerID);
 104          if (pXSprite->command != kCmdLink && pXSprite->triggerOff && !pXSprite->state)
 105              evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command, causerID);
 106      }
 107      return 1;
 108  }
 109  
 110  
 111  
 112  char SetWallState(int nWall, XWALL *pXWall, int nState, int causerID)
 113  {
 114      if ((pXWall->busy&0xffff) == 0 && pXWall->state == nState)
 115          return 0;
 116      pXWall->busy = nState<<16;
 117      pXWall->state = nState;
 118      evKill(nWall, 0, causerID);
 119      if (pXWall->restState != nState && pXWall->waitTime > 0)
 120          evPost(nWall, 0, (pXWall->waitTime*120) / 10, pXWall->restState ? kCmdOn : kCmdOff, causerID);
 121      if (pXWall->txID)
 122      {
 123          if (pXWall->command != kCmdLink && pXWall->triggerOn && pXWall->state)
 124              evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command, causerID);
 125          if (pXWall->command != kCmdLink && pXWall->triggerOff && !pXWall->state)
 126              evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command, causerID);
 127      }
 128      return 1;
 129  }
 130  
 131  char SetSectorState(int nSector, XSECTOR *pXSector, int nState, int causerID)
 132  {
 133      if ((pXSector->busy&0xffff) == 0 && pXSector->state == nState)
 134          return 0;
 135      pXSector->busy = nState<<16;
 136      pXSector->state = nState;
 137      evKill(nSector, 6, causerID);
 138      if (nState == 1)
 139      {
 140          if (pXSector->command != kCmdLink && pXSector->triggerOn && pXSector->txID)
 141              evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command, causerID);
 142          if (pXSector->stopOn)
 143          {
 144              pXSector->stopOn = 0;
 145              pXSector->stopOff = 0;
 146          }
 147          else if (pXSector->reTriggerA)
 148              evPost(nSector, 6, (pXSector->waitTimeA * 120) / 10, kCmdOff, causerID);
 149      }
 150      else
 151      {
 152          if (pXSector->command != kCmdLink && pXSector->triggerOff && pXSector->txID)
 153              evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command, causerID);
 154          if (pXSector->stopOff)
 155          {
 156              pXSector->stopOn = 0;
 157              pXSector->stopOff = 0;
 158          }
 159          else if (pXSector->reTriggerB)
 160              evPost(nSector, 6, (pXSector->waitTimeB * 120) / 10, kCmdOn, causerID);
 161      }
 162      return 1;
 163  }
 164  
 165  int gBusyCount = 0;
 166  BUSY gBusy[kMaxBusyCount];
 167  
 168  void AddBusy(int a1, BUSYID a2, int nDelta)
 169  {
 170      dassert(nDelta != 0);
 171      
 172      int i;
 173      for (i = 0; i < gBusyCount; i++)
 174      {
 175          if (gBusy[i].at0 == a1 && gBusy[i].atc == a2)
 176              break;
 177      }
 178  
 179      if (i == gBusyCount)
 180      {
 181      #ifdef NOONE_EXTENSIONS
 182          if ((!gModernMap && gBusyCount >= kMaxBusyCountVanilla) || gBusyCount >= kMaxBusyCount)
 183      #else
 184          if (gBusyCount >= kMaxBusyCountVanilla)
 185      #endif
 186          {
 187              consoleSysMsg("Failed to AddBusy for #%d! Max busy reached (%d)", a1, gBusyCount);
 188              return;
 189          }
 190  
 191          gBusy[i].at0 = a1;
 192          gBusy[i].atc = a2;
 193          gBusy[i].at8 = nDelta > 0 ? 0 : 65536;
 194          gBusyCount++;
 195      }
 196  
 197      gBusy[i].at4 = nDelta;
 198  }
 199  
 200  void ReverseBusy(int a1, BUSYID a2)
 201  {
 202      int i;
 203      for (i = 0; i < gBusyCount; i++)
 204      {
 205          if (gBusy[i].at0 == a1 && gBusy[i].atc == a2)
 206          {
 207              gBusy[i].at4 = -gBusy[i].at4;
 208              break;
 209          }
 210      }
 211  }
 212  
 213  unsigned int GetSourceBusy(const EVENT &a1)
 214  {
 215      int nIndex = a1.index;
 216      switch (a1.type)
 217      {
 218      case 6:
 219      {
 220          int nXIndex = sector[nIndex].extra;
 221          dassert(nXIndex > 0 && nXIndex < kMaxXSectors);
 222          return xsector[nXIndex].busy;
 223      }
 224      case 0:
 225      {
 226          int nXIndex = wall[nIndex].extra;
 227          dassert(nXIndex > 0 && nXIndex < kMaxXWalls);
 228          return xwall[nXIndex].busy;
 229      }
 230      case 3:
 231      {
 232          int nXIndex = sprite[nIndex].extra;
 233          dassert(nXIndex > 0 && nXIndex < kMaxXSprites);
 234          return xsprite[nXIndex].busy;
 235      }
 236      }
 237      return 0;
 238  }
 239  
 240  void LifeLeechOperate(spritetype *pSprite, XSPRITE *pXSprite, const EVENT &event)
 241  {
 242      switch (event.cmd) {
 243      case kCmdSpritePush:
 244      {
 245          int nPlayer = pXSprite->data4;
 246          if (nPlayer >= 0 && nPlayer < gNetPlayers)
 247          {
 248              PLAYER *pPlayer = &gPlayer[nPlayer];
 249              if (pPlayer->pXSprite->health > 0)
 250              {
 251                  evKill(pSprite->index, 3);
 252                  pPlayer->ammoCount[8] = ClipHigh(pPlayer->ammoCount[8]+pXSprite->data3, gAmmoInfo[8].max);
 253                  pPlayer->hasWeapon[kWeaponLifeLeech] = 1;
 254                  if (pPlayer->curWeapon != kWeaponLifeLeech)
 255                  {
 256                      if (!VanillaMode() && checkLitSprayOrTNT(pPlayer)) // if tnt/spray is actively used, do not switch weapon
 257                          break;
 258                      pPlayer->weaponState = 0;
 259                      pPlayer->nextWeapon = kWeaponLifeLeech;
 260                  }
 261              }
 262          }
 263          break;
 264      }
 265      case kCmdSpriteProximity:
 266      {
 267          int nTarget = pXSprite->target;
 268          if (nTarget >= 0 && nTarget < kMaxSprites)
 269          {
 270              if (!pXSprite->stateTimer)
 271              {
 272                  spritetype *pTarget = &sprite[nTarget];
 273                  if (pTarget->statnum == kStatDude && !(pTarget->flags&32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites)
 274                  {
 275                      int top, bottom;
 276                      GetSpriteExtents(pSprite, &top, &bottom);
 277                      int nType = pTarget->type-kDudeBase;
 278                      DUDEINFO *pDudeInfo = getDudeInfo(nType+kDudeBase);
 279                      int z1 = (top-pSprite->z)-256;
 280                      int x = pTarget->x;
 281                      int y = pTarget->y;
 282                      int z = pTarget->z;
 283                      int nDist = approxDist(x - pSprite->x, y - pSprite->y);
 284                      if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum))
 285                      {
 286                          int t = divscale12(nDist, 0x1aaaaa);
 287                          x += (xvel[nTarget]*t)>>12;
 288                          y += (yvel[nTarget]*t)>>12;
 289                          int angBak = pSprite->ang;
 290                          pSprite->ang = getangle(x-pSprite->x, y-pSprite->y);
 291                          int dx = Cos(pSprite->ang)>>16;
 292                          int dy = Sin(pSprite->ang)>>16;
 293                          int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
 294                          int dz = divscale10(tz - top - 256, nDist);
 295                          int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
 296                          if (gLifeleechRnd && !VanillaMode()) // if random projectiles for lifeleech flag is on
 297                              nMissileType = kMissileBase + Random(kMissileLifeLeechAltSmall-kMissileBase);
 298                          int t2;
 299                          if (!pXSprite->data3)
 300                              t2 = 120 / 10.0;
 301                          else
 302                              t2 = (3*120) / 10.0;
 303                          spritetype *pMissile = actFireMissile(pSprite, 0, z1, dx, dy, dz, nMissileType);
 304                          if (pMissile)
 305                          {
 306                              pMissile->owner = pSprite->owner;
 307                              pXSprite->stateTimer = 1;
 308                              if (WeaponsNotBlood() && !VanillaMode()) // reduce the firing rate of the lifeleech
 309                                  pXSprite->stateTimer = 3;
 310                              evPost(pSprite->index, 3, t2, kCallbackLeechStateTimer);
 311                              if (!(gInfiniteAmmo && !VanillaMode())) // forever let lifeleech fire
 312                                  pXSprite->data3 = ClipLow(pXSprite->data3-1, 0);
 313                              if ((ProjectilesRaze() || ProjectilesNotBlood()) && !VanillaMode()) // disable collisions so lifeleech doesn't do that weird bobbing
 314                                  pMissile->cstat &= ~(CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);
 315                          }
 316                          pSprite->ang = angBak;
 317                      }
 318                  }
 319              }
 320          }
 321          return;
 322      }
 323      }
 324      actPostSprite(pSprite->index, kStatFree);
 325  }
 326  
 327  void ActivateGenerator(int);
 328  
 329  void OperateSprite(int nSprite, XSPRITE *pXSprite, const EVENT &event)
 330  {
 331      int causerID = event.causer;
 332      spritetype *pSprite = &sprite[nSprite];
 333      
 334      #ifdef NOONE_EXTENSIONS
 335          if (gModernMap && modernTypeOperateSprite(nSprite, pSprite, pXSprite, event))
 336              return;
 337      #endif
 338      
 339      switch (event.cmd) {
 340          case kCmdLock:
 341              pXSprite->locked = 1;
 342              return;
 343          case kCmdUnlock:
 344              pXSprite->locked = 0;
 345              return;
 346          case kCmdToggleLock:
 347              pXSprite->locked = pXSprite->locked ^ 1;
 348              return;
 349      }
 350  
 351      if (pSprite->statnum == kStatDude && pSprite->type >= kDudeBase && pSprite->type < kDudeMax) {
 352          
 353          switch (event.cmd) {
 354              case kCmdOff:
 355                  SetSpriteState(nSprite, pXSprite, 0, causerID);
 356                  break;
 357              case kCmdSpriteProximity:
 358                  if (pXSprite->state) break;
 359                  fallthrough__;
 360              case kCmdOn:
 361              case kCmdSpritePush:
 362              case kCmdSpriteTouch:
 363                  if (!pXSprite->state) SetSpriteState(nSprite, pXSprite, 1, causerID);
 364                  aiActivateDude(pSprite, pXSprite);
 365                  break;
 366          }
 367  
 368          return;
 369      }
 370  
 371  
 372      switch (pSprite->type) {
 373      case kTrapMachinegun:
 374          if (pXSprite->health <= 0) break; 
 375          switch (event.cmd) {
 376              case kCmdOff:
 377                  if (!SetSpriteState(nSprite, pXSprite, 0, causerID)) break;
 378                  seqSpawn(40, 3, pSprite->extra, -1);
 379                  break;
 380              case kCmdOn:
 381                  if (!SetSpriteState(nSprite, pXSprite, 1, causerID)) break;
 382                  seqSpawn(38, 3, pSprite->extra, nMGunOpenClient);
 383                  if (pXSprite->data1 > 0)
 384                      pXSprite->data2 = pXSprite->data1;
 385                  break;
 386          }
 387          break;
 388      case kThingFallingRock:
 389          if (SetSpriteState(nSprite, pXSprite, 1, causerID))
 390              pSprite->flags |= 7;
 391          break;
 392      case kThingWallCrack:
 393          if (SetSpriteState(nSprite, pXSprite, 0, causerID))
 394              actPostSprite(nSprite, kStatFree);
 395          break;
 396      case kThingCrateFace:
 397          if (SetSpriteState(nSprite, pXSprite, 0, causerID))
 398              actPostSprite(nSprite, kStatFree);
 399          break;
 400      case kTrapZapSwitchable:
 401          switch (event.cmd) {
 402              case kCmdOff:
 403                  pXSprite->state = 0;
 404                  pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
 405                  pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
 406                  break;
 407              case kCmdOn:
 408                  pXSprite->state = 1;
 409                  pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE;
 410                  pSprite->cstat |= CSTAT_SPRITE_BLOCK;
 411                  break;
 412              case kCmdToggle:
 413                  pXSprite->state ^= 1;
 414                  pSprite->cstat ^= CSTAT_SPRITE_INVISIBLE;
 415                  pSprite->cstat ^= CSTAT_SPRITE_BLOCK;
 416                  break;
 417          }
 418          break;
 419      case kTrapFlame:
 420          switch (event.cmd) {
 421              case kCmdOff:
 422                  if (!SetSpriteState(nSprite, pXSprite, 0, causerID)) break;
 423                  seqSpawn(40, 3, pSprite->extra, -1);
 424                  sfxKill3DSound(pSprite, 0, -1);
 425                  break;
 426              case kCmdOn:
 427                  if (!SetSpriteState(nSprite, pXSprite, 1, causerID)) break;
 428                  seqSpawn(38, 3, pSprite->extra, -1);
 429                  sfxPlay3DSound(pSprite, 441, 0, 0);
 430                  break;
 431          }
 432          break;
 433      case kSwitchPadlock:
 434          switch (event.cmd) {
 435              case kCmdOff:
 436                  SetSpriteState(nSprite, pXSprite, 0, causerID);
 437                  break;
 438              case kCmdOn:
 439                  if (!SetSpriteState(nSprite, pXSprite, 1, causerID)) break;
 440                  seqSpawn(37, 3, pSprite->extra, -1);
 441                  break;
 442              default:
 443                  SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1, causerID);
 444                  if (pXSprite->state) seqSpawn(37, 3, pSprite->extra, -1);
 445                  break;
 446          }
 447          break;
 448      case kSwitchToggle:
 449          switch (event.cmd) {
 450              case kCmdOff:
 451                  if (!SetSpriteState(nSprite, pXSprite, 0, causerID)) break;
 452                  sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
 453                  break;
 454              case kCmdOn:
 455                  if (!SetSpriteState(nSprite, pXSprite, 1, causerID)) break;
 456                  sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
 457                  break;
 458              default:
 459                  if (!SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1, causerID)) break;
 460                  if (pXSprite->state) sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
 461                  else sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
 462                  break;
 463          }
 464          break;
 465      case kSwitchOneWay:
 466          switch (event.cmd) {
 467              case kCmdOff:
 468                  if (!SetSpriteState(nSprite, pXSprite, 0, causerID)) break;
 469                  sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
 470                  break;
 471              case kCmdOn:
 472                  if (!SetSpriteState(nSprite, pXSprite, 1, causerID)) break;
 473                  sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
 474                  break;
 475              default:
 476                  if (!SetSpriteState(nSprite, pXSprite, pXSprite->restState ^ 1, causerID)) break;
 477                  if (pXSprite->state) sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
 478                  else sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
 479                  break;
 480          }
 481          break;
 482      case kSwitchCombo:
 483          switch (event.cmd) {
 484              case kCmdOff:
 485                  pXSprite->data1--;
 486                  if (pXSprite->data1 < 0)
 487                      pXSprite->data1 += pXSprite->data3;
 488                  break;
 489              default:
 490                  pXSprite->data1++;
 491                  if (pXSprite->data1 >= pXSprite->data3)
 492                      pXSprite->data1 -= pXSprite->data3;
 493                  break;
 494          }
 495          
 496          sfxPlay3DSound(pSprite, pXSprite->data4, -1, 0);
 497          
 498          if (pXSprite->command == kCmdLink && pXSprite->txID > 0)
 499              evSend(nSprite, 3, pXSprite->txID, kCmdLink, causerID);
 500  
 501          if (pXSprite->data1 == pXSprite->data2) 
 502              SetSpriteState(nSprite, pXSprite, 1, causerID);
 503          else 
 504              SetSpriteState(nSprite, pXSprite, 0, causerID);
 505  
 506          break;
 507      case kMarkerDudeSpawn:
 508          if (gGameOptions.nMonsterSettings && pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeMax) {
 509              spritetype* pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0);
 510              if (pSpawn) {
 511                  XSPRITE *pXSpawn = &xsprite[pSpawn->extra];
 512                  if (gGameOptions.nRandomizerMode && !VanillaMode()) { // randomize spawned enemy
 513                      dbRandomizerMode(pSpawn);
 514                      if (pXSprite && (gGameOptions.nRandomizerMode & 1)) // if randomizer is set to enemies or enemies+weapons mode, randomly scale enemies
 515                          dbRandomizerModeScale(pSpawn, pXSpawn);
 516                  }
 517                  if (dbIsBannedSprite(pSpawn, pXSpawn)) { // if spawned sprite is banned, remove sprite
 518                      evKill(pSpawn->index, 3);
 519                      if (sprite[pSpawn->index].extra > 0)
 520                          seqKill(3, sprite[pSpawn->index].extra);
 521                      DeleteSprite(pSpawn->index);
 522                      break;
 523                  }
 524                  gKillMgr.AddCount(pSpawn);
 525                  switch (pXSprite->data1) {
 526                      case kDudeBurningInnocent:
 527                      case kDudeBurningCultist:
 528                      case kDudeBurningZombieAxe:
 529                      case kDudeBurningZombieButcher:
 530                      case kDudeBurningTinyCaleb:
 531                      case kDudeBurningBeast: {
 532                          pXSpawn->health = getDudeInfo(pXSprite->data1)->startHealth << 4;
 533                          pXSpawn->burnTime = 10;
 534                          pXSpawn->target = -1;
 535                          aiActivateDude(pSpawn, pXSpawn);
 536                          break;
 537                      default:
 538                          break;
 539                      }
 540                  }
 541              }
 542          }
 543          break;
 544      case kMarkerEarthQuake:
 545          pXSprite->triggerOn = 0;
 546          pXSprite->isTriggered = 1;
 547          SetSpriteState(nSprite, pXSprite, 1, causerID);
 548          for (int p = connecthead; p >= 0; p = connectpoint2[p]) {
 549              spritetype *pPlayerSprite = gPlayer[p].pSprite;
 550              int dx = (pSprite->x - pPlayerSprite->x)>>4;
 551              int dy = (pSprite->y - pPlayerSprite->y)>>4;
 552              int dz = (pSprite->z - pPlayerSprite->z)>>8;
 553              int nDist = dx*dx+dy*dy+dz*dz+0x40000;
 554              gPlayer[p].quakeEffect = divscale16(pXSprite->data1, nDist);
 555          }
 556          break;
 557      case kThingTNTBarrel:
 558          if (pSprite->flags & kHitagRespawn) return;
 559          fallthrough__;
 560      case kThingArmedTNTStick:
 561      case kThingArmedTNTBundle:
 562      case kThingArmedSpray:
 563          actExplodeSprite(pSprite);
 564          break;
 565      case kTrapExploder:
 566          switch (event.cmd) {
 567              case kCmdOn:
 568                  SetSpriteState(nSprite, pXSprite, 1, causerID);
 569                  break;
 570              default:
 571                  pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE;
 572                  actExplodeSprite(pSprite);
 573                  break;
 574          }
 575          break;
 576      case kThingArmedRemoteBomb:
 577          if (pSprite->statnum != kStatRespawn) {
 578              if (event.cmd != kCmdOn) actExplodeSprite(pSprite);
 579              else {
 580                  sfxPlay3DSound(pSprite, 454, 0, 0);
 581                  evPost(nSprite, 3, 18, kCmdOff, causerID);
 582              }
 583          }
 584          break;
 585      case kThingArmedProxBomb:
 586          if (pSprite->statnum != kStatRespawn) {
 587              switch (event.cmd) {
 588                  case kCmdSpriteProximity:
 589                      if (pXSprite->state) break;
 590                      sfxPlay3DSound(pSprite, 452, 0, 0);
 591                      evPost(nSprite, 3, 30, kCmdOff, causerID);
 592                      pXSprite->state = 1;
 593                      fallthrough__;
 594                  case kCmdOn:
 595                      sfxPlay3DSound(pSprite, 451, 0, 0);
 596                      pXSprite->Proximity = 1;
 597                      break;
 598                  default:
 599                      actExplodeSprite(pSprite);
 600                      break;
 601              }
 602          }
 603          break;
 604      case kThingDroppedLifeLeech:
 605          LifeLeechOperate(pSprite, pXSprite, event);
 606          break;
 607      case kGenTrigger:
 608      case kGenDripWater:
 609      case kGenDripBlood:
 610      case kGenMissileFireball:
 611      case kGenMissileEctoSkull:
 612      case kGenDart:
 613      case kGenBubble:
 614      case kGenBubbleMulti:
 615      case kGenSound:
 616          switch (event.cmd) {
 617              case kCmdOff:
 618                  SetSpriteState(nSprite, pXSprite, 0, causerID);
 619                  break;
 620              case kCmdRepeat:
 621                  if (pSprite->type != kGenTrigger) ActivateGenerator(nSprite);
 622                  if (pXSprite->txID) evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command, causerID);
 623                  if (pXSprite->busyTime > 0) {
 624                      int nRand = Random2(pXSprite->data1);
 625                      evPost(nSprite, 3, 120*(nRand+pXSprite->busyTime) / 10, kCmdRepeat, causerID);
 626                  }
 627                  break;
 628              default:
 629                  if (!pXSprite->state) {
 630                      SetSpriteState(nSprite, pXSprite, 1, causerID);
 631                      evPost(nSprite, 3, 0, kCmdRepeat, causerID);
 632                  }
 633                  break;
 634          }
 635          break;
 636      case kSoundPlayer:
 637          if (gGameOptions.nGameType == kGameTypeSinglePlayer)
 638          {
 639              if (gMe->pXSprite->health <= 0)
 640                  break;
 641              gMe->restTime = 0;
 642          }
 643          sndStartSample(pXSprite->data1, -1, 1, 0);
 644          break;
 645      case kThingObjectGib:
 646      case kThingObjectExplode:
 647      case kThingBloodBits:
 648      case kThingBloodChunks:
 649      case kThingZombieHead:
 650          switch (event.cmd) {
 651              case kCmdOff:
 652                  if (!SetSpriteState(nSprite, pXSprite, 0, causerID)) break;
 653                  actActivateGibObject(pSprite, pXSprite);
 654                  break;
 655              case kCmdOn:
 656                  if (!SetSpriteState(nSprite, pXSprite, 1, causerID)) break;
 657                  actActivateGibObject(pSprite, pXSprite);
 658                  break;
 659              default:
 660                  if (!SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1, causerID)) break;
 661                  actActivateGibObject(pSprite, pXSprite);
 662                  break;
 663          }
 664          break;
 665      default:
 666          switch (event.cmd) {
 667              case kCmdOff:
 668                  SetSpriteState(nSprite, pXSprite, 0, causerID);
 669                  break;
 670              case kCmdOn:
 671                  SetSpriteState(nSprite, pXSprite, 1, causerID);
 672                  break;
 673              default:
 674                  SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1, causerID);
 675                  break;
 676          }
 677          break;
 678      }
 679  }
 680  
 681  void SetupGibWallState(walltype *pWall, XWALL *pXWall)
 682  {
 683      walltype *pWall2 = NULL;
 684      if (pWall->nextwall >= 0)
 685          pWall2 = &wall[pWall->nextwall];
 686      if (pXWall->state)
 687      {
 688          pWall->cstat &= ~65;
 689          if (pWall2)
 690          {
 691              pWall2->cstat &= ~65;
 692              pWall->cstat &= ~16;
 693              pWall2->cstat &= ~16;
 694          }
 695          return;
 696      }
 697      char bVector = pXWall->triggerVector != 0;
 698      pWall->cstat |= 1;
 699      if (bVector)
 700          pWall->cstat |= 64;
 701      if (pWall2)
 702      {
 703          pWall2->cstat &= ~1;
 704          if (bVector)
 705              pWall2->cstat |= 64;
 706          pWall->cstat |= 16;
 707          pWall2->cstat |= 16;
 708      }
 709  }
 710  
 711  void OperateWall(int nWall, XWALL *pXWall, const EVENT &event) {
 712      
 713      int causerID = event.causer;
 714      walltype *pWall = &wall[nWall];
 715      
 716      switch (event.cmd) {
 717          case kCmdLock:
 718              pXWall->locked = 1;
 719              return;
 720          case kCmdUnlock:
 721              pXWall->locked = 0;
 722              return;
 723          case kCmdToggleLock:
 724              pXWall->locked ^= 1;
 725              return;
 726      }
 727      
 728      #ifdef NOONE_EXTENSIONS
 729      if (gModernMap && modernTypeOperateWall(nWall, pWall, pXWall, event))
 730          return;
 731      #endif
 732  
 733      switch (pWall->type) {
 734          case kWallGib:
 735              if (GetWallType(nWall) != pWall->type) break;
 736              char bStatus;
 737              switch (event.cmd) {
 738                  case kCmdOn:
 739                  case kCmdWallImpact:
 740                      bStatus = SetWallState(nWall, pXWall, 1, causerID);
 741                      break;
 742                  case kCmdOff:
 743                      bStatus = SetWallState(nWall, pXWall, 0, causerID);
 744                      break;
 745                  default:
 746                      bStatus = SetWallState(nWall, pXWall, pXWall->state ^ 1, causerID);
 747                      break;
 748              }
 749  
 750              if (bStatus) {
 751                  SetupGibWallState(pWall, pXWall);
 752                  if (pXWall->state) {
 753                      CGibVelocity vel(100, 100, 250);
 754                      int nType = ClipRange(pXWall->data, 0, 31);
 755                      if (nType > 0)
 756                          GibWall(nWall, (GIBTYPE)nType, &vel);
 757                  }
 758              }
 759              return;
 760          default:
 761              switch (event.cmd) {
 762                  case kCmdOff:
 763                      SetWallState(nWall, pXWall, 0, causerID);
 764                      break;
 765                  case kCmdOn:
 766                      SetWallState(nWall, pXWall, 1, causerID);
 767                      break;
 768                  default:
 769                      SetWallState(nWall, pXWall, pXWall->state ^ 1, causerID);
 770                      break;
 771              }
 772              return;
 773      }
 774  
 775  
 776  }
 777  
 778  void SectorStartSound(int nSector, int nState)
 779  {
 780      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
 781      {
 782          spritetype *pSprite = &sprite[nSprite];
 783          if (pSprite->statnum == kStatDecoration && pSprite->type == kSoundSector)
 784          {
 785              int nXSprite = pSprite->extra;
 786              dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
 787              XSPRITE *pXSprite = &xsprite[nXSprite];
 788              if (nState)
 789              {
 790                  if (pXSprite->data3)
 791                      sfxPlay3DSound(pSprite, pXSprite->data3, 0, 0);
 792              }
 793              else
 794              {
 795                  if (pXSprite->data1)
 796                      sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
 797              }
 798          }
 799      }
 800  }
 801  
 802  void SectorEndSound(int nSector, int nState)
 803  {
 804      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
 805      {
 806          spritetype *pSprite = &sprite[nSprite];
 807          if (pSprite->statnum == kStatDecoration && pSprite->type == kSoundSector)
 808          {
 809              int nXSprite = pSprite->extra;
 810              dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
 811              XSPRITE *pXSprite = &xsprite[nXSprite];
 812              if (nState)
 813              {
 814                  if (pXSprite->data2)
 815                      sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
 816              }
 817              else
 818              {
 819                  if (pXSprite->data4)
 820                      sfxPlay3DSound(pSprite, pXSprite->data4, 0, 0);
 821              }
 822          }
 823      }
 824  }
 825  
 826  void PathSound(int nSector, int nSound)
 827  {
 828      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
 829      {
 830          spritetype *pSprite = &sprite[nSprite];
 831          if (pSprite->statnum == kStatDecoration && pSprite->type == kSoundSector)
 832              sfxPlay3DSound(pSprite, nSound, 0, 0);
 833      }
 834  }
 835  
 836  void DragPoint(int nWall, int x, int y)
 837  {
 838      viewInterpolateWall(nWall, &wall[nWall]);
 839      wall[nWall].x = x;
 840      wall[nWall].y = y;
 841  
 842      int vsi = numwalls;
 843      int vb = nWall;
 844      do
 845      {
 846          if (wall[vb].nextwall >= 0)
 847          {
 848              vb = wall[wall[vb].nextwall].point2;
 849              viewInterpolateWall(vb, &wall[vb]);
 850              wall[vb].x = x;
 851              wall[vb].y = y;
 852          }
 853          else
 854          {
 855              vb = nWall;
 856              do
 857              {
 858                  if (wall[lastwall(vb)].nextwall >= 0)
 859                  {
 860                      vb = wall[lastwall(vb)].nextwall;
 861                      viewInterpolateWall(vb, &wall[vb]);
 862                      wall[vb].x = x;
 863                      wall[vb].y = y;
 864                  }
 865                  else
 866                      break;
 867                  vsi--;
 868              } while (vb != nWall && vsi > 0);
 869              break;
 870          }
 871          vsi--;
 872      } while (vb != nWall && vsi > 0);
 873  }
 874  
 875  void TranslateSector(int nSector, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, char bAllWalls)
 876  {
 877      int x, y;
 878      int nXSector = sector[nSector].extra;
 879      XSECTOR *pXSector = &xsector[nXSector];
 880      int v20 = interpolate(a6, a9, a2, 1);
 881      int vc = interpolate(a6, a9, a3, 1);
 882      int v28 = vc - v20;
 883      int v24 = interpolate(a7, a10, a2, 1);
 884      int v8 = interpolate(a7, a10, a3, 1);
 885      int v2c = v8 - v24;
 886      int v44 = interpolate(a8, a11, a2, 1);
 887      int vbp = interpolate(a8, a11, a3, 1);
 888      int v14 = vbp - v44;
 889      int nWall = sector[nSector].wallptr;
 890      char bIsolatedSector = 1; // used to check if sector translation is likely for a moving shadow sector
 891      
 892      #ifdef NOONE_EXTENSIONS
 893      // fix Y arg in RotatePoint for reverse (green) moving sprites?
 894      int sprDy = (gModernMap) ? a5 : a4;
 895      #else
 896      int sprDy = a4;
 897      #endif
 898  
 899      if (bAllWalls)
 900      {
 901          for (int i = 0; i < sector[nSector].wallnum; nWall++, i++)
 902          {
 903              x = baseWall[nWall].x;
 904              y = baseWall[nWall].y;
 905              if (vbp)
 906                  RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
 907              DragPoint(nWall, x+vc-a4, y+v8-a5);
 908  
 909              if (bIsolatedSector && sectRangeIsFine(wall[nWall].nextsector))
 910              {
 911                  const int nNextSector = wall[nWall].nextsector;
 912                  if (sector[nSector].floorpicnum == sector[nNextSector].floorpicnum)
 913                  {
 914                      if (sector[nSector].floorz == sector[nNextSector].floorz) // if floor height is the same for both sectors, and share the same picnum
 915                          bIsolatedSector = 0;
 916                  }
 917              }
 918          }
 919      }
 920      else
 921      {
 922          for (int i = 0; i < sector[nSector].wallnum; nWall++, i++)
 923          {
 924              int v10 = wall[nWall].point2;
 925              x = baseWall[nWall].x;
 926              y = baseWall[nWall].y;
 927              if (wall[nWall].cstat&16384)
 928              {
 929                  if (vbp)
 930                      RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
 931                  DragPoint(nWall, x+vc-a4, y+v8-a5);
 932                  if ((wall[v10].cstat&49152) == 0)
 933                  {
 934                      x = baseWall[v10].x;
 935                      y = baseWall[v10].y;
 936                      if (vbp)
 937                          RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
 938                      DragPoint(v10, x+vc-a4, y+v8-a5);
 939                  }
 940                  continue;
 941              }
 942              if (wall[nWall].cstat&32768)
 943              {
 944                  if (vbp)
 945                      RotatePoint((int*)&x, (int*)&y, -vbp, a4, a5);
 946                  DragPoint(nWall, x-(vc-a4), y-(v8-a5));
 947                  if ((wall[v10].cstat&49152) == 0)
 948                  {
 949                      x = baseWall[v10].x;
 950                      y = baseWall[v10].y;
 951                      if (vbp)
 952                          RotatePoint((int*)&x, (int*)&y, -vbp, a4, a5);
 953                      DragPoint(v10, x-(vc-a4), y-(v8-a5));
 954                  }
 955                  continue;
 956              }
 957          }
 958      }
 959      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
 960      {
 961          spritetype *pSprite = &sprite[nSprite];
 962          char bSpriteMoved = 0;
 963          // allow to move markers by sector movements in game if flags 1 is added in editor.
 964          switch (pSprite->statnum) {
 965              case kStatMarker:
 966              case kStatPathMarker:
 967                  #ifdef NOONE_EXTENSIONS
 968                      if (!gModernMap || !(pSprite->flags & 0x1)) continue;
 969                  #else
 970                      continue;
 971                  #endif
 972                  break;
 973          }
 974  
 975          x = baseSprite[nSprite].x;
 976          y = baseSprite[nSprite].y;
 977          if (sprite[nSprite].cstat&8192)
 978          {
 979              if (vbp)
 980                  RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
 981              viewBackupSpriteLoc(nSprite, pSprite);
 982              pSprite->ang = (pSprite->ang+v14)&2047;
 983              pSprite->x = x+vc-a4;
 984              pSprite->y = y+v8-a5;
 985              bSpriteMoved = 1;
 986          }
 987          else if (sprite[nSprite].cstat&16384)
 988          {
 989              if (vbp)
 990                  RotatePoint((int*)& x, (int*)& y, -vbp, a4, sprDy);
 991              viewBackupSpriteLoc(nSprite, pSprite);
 992              pSprite->ang = (pSprite->ang-v14)&2047;
 993              pSprite->x = x-(vc-a4);
 994              pSprite->y = y-(v8-a5);
 995              bSpriteMoved = 1;
 996          }
 997          else if (pXSector->Drag)
 998          {
 999              int top, bottom;
1000              GetSpriteExtents(pSprite, &top, &bottom);
1001              int floorZ = getflorzofslope(nSector, pSprite->x, pSprite->y);
1002              if (!(pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK) && floorZ <= bottom)
1003              {
1004                  if (!VanillaMode()) viewBackupSpriteLoc(nSprite, pSprite);
1005                  if (v14)
1006                      RotatePoint((int*)&pSprite->x, (int*)&pSprite->y, v14, v20, v24);
1007                  if (VanillaMode()) viewBackupSpriteLoc(nSprite, pSprite);
1008                  pSprite->ang = (pSprite->ang+v14)&2047;
1009                  pSprite->x += v28;
1010                  pSprite->y += v2c;
1011                  bSpriteMoved = 1;
1012              }
1013          }
1014          if (!bSpriteMoved && (bAllWalls || pXSector->Drag) && gGameOptions.bSectorBehavior && !VanillaMode()) // add sector movement support for bullet casing/blood splat fx sprites
1015          {
1016              int floorZ;
1017              const int16_t nType = pSprite->type;
1018              if ((nType == FX_51) && bIsolatedSector) // bullet casing
1019              {
1020                  int top, bottom;
1021                  GetSpriteExtents(pSprite, &top, &bottom);
1022                  floorZ = getflorzofslope(nSector, pSprite->x, pSprite->y);
1023                  bSpriteMoved = !(pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK) && (floorZ <= bottom);
1024              }
1025              else if ((nType == FX_36) && bIsolatedSector) // floor blood splat
1026              {
1027                  floorZ = getflorzofslope(nSector, pSprite->x, pSprite->y);
1028                  bSpriteMoved = ((pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_FLOOR) != 0) && (pSprite->z == floorZ);
1029              }
1030              else if ((nType == FX_34) || (nType == FX_35) || (nType == FX_43)) // wall blood splat/bullet hole
1031              {
1032                  bSpriteMoved = (pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_WALL) != 0;
1033                  if (bSpriteMoved) // check if wall aligned sprite is connected to a wall
1034                  {
1035                      const int nStartWall = sector[nSector].wallptr, nEndWall = nStartWall + sector[nSector].wallnum;
1036                      int nFoundWall = nStartWall, nDist = INT_MAX;
1037                      for (nWall = nStartWall; nWall < nEndWall; nWall++) // check each wall distance of sector to sprite
1038                      {
1039                          const vec2_t pos = {pSprite->x, pSprite->y};
1040                          const int nDistCurWall = getwalldist(pos, nWall); // find closest wall to sprite
1041                          if (nDistCurWall < nDist)
1042                          {
1043                              nDist = nDistCurWall;
1044                              nFoundWall = nWall;
1045                          }
1046                      }
1047                      bSpriteMoved = wall[nFoundWall].nextsector == -1; // if nearest wall is not linked to a sector, drag sprite
1048                  }
1049              }
1050              if (bSpriteMoved)
1051              {
1052                  viewBackupSpriteLoc(nSprite, pSprite);
1053                  if (v14)
1054                      RotatePoint((int*)&pSprite->x, (int*)&pSprite->y, v14, v20, v24);
1055                  pSprite->ang = (pSprite->ang+v14)&2047;
1056                  pSprite->x += v28;
1057                  pSprite->y += v2c;
1058              }
1059          }
1060      }
1061  
1062      #ifdef NOONE_EXTENSIONS
1063      // translate sprites near outside walls
1064      ////////////////////////////////////////////////////////////
1065      
1066      int nSprite, *ptr;
1067      if (gModernMap && (ptr = gSprNSect.GetSprPtr(nSector)) != NULL)
1068      {
1069          while (*ptr >= 0)
1070          {
1071              spritetype* pSprite = &sprite[*ptr++];
1072              if (pSprite->statnum >= kMaxStatus)
1073                  continue;
1074  
1075              nSprite = pSprite->index;
1076              x = baseSprite[nSprite].x;
1077              y = baseSprite[nSprite].y;
1078              if (pSprite->cstat & 8192)
1079              {
1080                  if (vbp)
1081                      RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
1082                  viewBackupSpriteLoc(nSprite, pSprite);
1083                  pSprite->ang = (pSprite->ang + v14) & 2047;
1084                  pSprite->x = x + vc - a4;
1085                  pSprite->y = y + v8 - a5;
1086              }
1087              else if (pSprite->cstat & 16384)
1088              {
1089                  if (vbp)
1090                      RotatePoint((int*)&x, (int*)&y, -vbp, a4, sprDy);
1091                  viewBackupSpriteLoc(nSprite, pSprite);
1092                  pSprite->ang = (pSprite->ang - v14) & 2047;
1093                  pSprite->x = x - (vc - a4);
1094                  pSprite->y = y - (v8 - a5);
1095              }
1096          }
1097      }
1098      /////////////////////
1099      #endif
1100  
1101  }
1102  
1103  inline bool isBloodOrBullethole(spritetype *pSprite)
1104  {
1105      if (!pSprite)
1106          return false;
1107      const int16_t nType = pSprite->type;
1108      const uint16_t nAlignMask = pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK;
1109      if (nType == FX_34) // wall blood splat (large)
1110          return (nAlignMask&CSTAT_SPRITE_ALIGNMENT_WALL) != 0;
1111      else if (nType == FX_35) // wall blood splat (small)
1112          return (nAlignMask&CSTAT_SPRITE_ALIGNMENT_WALL) != 0;
1113      else if (nType == FX_36) // floor blood splat
1114          return (nAlignMask&CSTAT_SPRITE_ALIGNMENT_FLOOR) != 0;
1115      else if (nType == FX_43) // wall bullet hole
1116          return (nAlignMask&CSTAT_SPRITE_ALIGNMENT_WALL) != 0;
1117      return false;
1118  }
1119  
1120  void ZTranslateSector(int nSector, XSECTOR *pXSector, int a3, int a4)
1121  {
1122      sectortype *pSector = &sector[nSector];
1123      viewInterpolateSector(nSector, pSector);
1124  
1125      int *ptr1 = NULL, *ptr2;
1126      int dfz = pXSector->onFloorZ - pXSector->offFloorZ;
1127      int dcz = pXSector->onCeilZ - pXSector->offCeilZ;
1128      
1129      #ifdef NOONE_EXTENSIONS
1130      // get pointer to sprites near outside walls before translation
1131      ///////////////////////////////////////////////////////////////
1132      if (gModernMap && (dfz || dcz))
1133          ptr1 = gSprNSect.GetSprPtr(nSector);
1134      #endif
1135  
1136      if (dfz != 0)
1137      {
1138          int oldZ = pSector->floorz;
1139          baseFloor[nSector] = pSector->floorz = pXSector->offFloorZ + mulscale16(dfz, GetWaveValue(a3, a4));
1140          velFloor[nSector] += (pSector->floorz-oldZ)<<8;
1141          for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1142          {
1143              spritetype *pSprite = &sprite[nSprite];
1144              if (pSprite->statnum == kStatMarker || pSprite->statnum == kStatPathMarker)
1145                  continue;
1146              int top, bottom;
1147              GetSpriteExtents(pSprite, &top, &bottom);
1148              if (pSprite->cstat&8192)
1149              {
1150                  viewBackupSpriteLoc(nSprite, pSprite);
1151                  pSprite->z += pSector->floorz-oldZ;
1152              }
1153              else if (pSprite->flags&2)
1154                  pSprite->flags |= 4;
1155              else if (oldZ <= bottom && !(pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK))
1156              {
1157                  viewBackupSpriteLoc(nSprite, pSprite);
1158                  pSprite->z += pSector->floorz-oldZ;
1159              }
1160              else if (isBloodOrBullethole(pSprite) && gGameOptions.bSectorBehavior && !VanillaMode()) // drag bullethole/blood with sector
1161              {
1162                  if (pSprite->type == FX_36) // if floor blood splat sprite
1163                  {
1164                      if (pSprite->z != oldZ) // if sprite isn't sitting on floor, don't move
1165                          continue;
1166                  }
1167                  if ((pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_WALL) // if wall aligned sprite, check if sector is an elevator
1168                  {
1169                      const int nDelta = klabs((pXSector->onFloorZ-pXSector->offFloorZ)-(pXSector->onCeilZ-pXSector->offCeilZ))>>12; // get floor/ceiling delta differences (elevators should equal 0 - this check fixes CP04's secret elevator)
1170                      const char bElevator = (sector[nSector].wallnum > 0) && (pXSector->onCeilZ != pXSector->offCeilZ) && !(sector[nSector].ceilingstat&kSecCParallax) && (nDelta == 0);
1171                      if (!bElevator) // if ceiling is not moving (not an elevator), don't move wall aligned sprite
1172                          continue;
1173                      const int nStartWall = sector[nSector].wallptr, nEndWall = nStartWall + sector[nSector].wallnum;
1174                      int nFoundWall = nStartWall, nDist = INT_MAX;
1175                      for (int nWall = nStartWall; nWall < nEndWall; nWall++) // because elevators have an entrance, we need to only move sprites aligned to the elevator interior walls
1176                      {
1177                          const vec2_t pos = {pSprite->x, pSprite->y};
1178                          const int nDistCurWall = getwalldist(pos, nWall); // find closest wall to sprite
1179                          if (nDistCurWall < nDist)
1180                          {
1181                              nDist = nDistCurWall;
1182                              nFoundWall = nWall;
1183                          }
1184                      }
1185                      if (wall[nFoundWall].nextsector != -1) // if nearest wall is linked to a sector, do not drag (wall is not part of elevator interior)
1186                          continue;
1187                  }
1188                  viewBackupSpriteLoc(nSprite, pSprite);
1189                  pSprite->z += pSector->floorz-oldZ;
1190              }
1191          }
1192          
1193          #ifdef NOONE_EXTENSIONS
1194          // translate sprites near outside walls (floor)
1195          ////////////////////////////////////////////////////////////
1196          if (ptr1)
1197          {
1198              ptr2 = ptr1;
1199              while (*ptr2 >= 0)
1200              {
1201                  spritetype* pSprite = &sprite[*ptr2++];
1202                  if (pSprite->statnum < kMaxStatus && (pSprite->cstat & 8192))
1203                  {
1204                      viewBackupSpriteLoc(pSprite->index, pSprite);
1205                      pSprite->z += pSector->floorz - oldZ;
1206                  }
1207              }
1208          }
1209          /////////////////////
1210          #endif
1211      }
1212  
1213      if (dcz != 0)
1214      {
1215          int oldZ = pSector->ceilingz;
1216          baseCeil[nSector] = pSector->ceilingz = pXSector->offCeilZ + mulscale16(dcz, GetWaveValue(a3, a4));
1217          velCeil[nSector] += pSector->ceilingz-oldZ;
1218          for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1219          {
1220              spritetype *pSprite = &sprite[nSprite];
1221              if (pSprite->statnum == kStatMarker || pSprite->statnum == kStatPathMarker)
1222                  continue;
1223              if (pSprite->cstat&16384)
1224              {
1225                  viewBackupSpriteLoc(nSprite, pSprite);
1226                  pSprite->z += pSector->ceilingz-oldZ;
1227              }
1228          }
1229          
1230          #ifdef NOONE_EXTENSIONS
1231          // translate sprites near outside walls (ceil)
1232          ////////////////////////////////////////////////////////////
1233          if (ptr1)
1234          {
1235              ptr2 = ptr1;
1236              while (*ptr2 >= 0)
1237              {
1238                  spritetype* pSprite = &sprite[*ptr2++];
1239                  if (pSprite->statnum < kMaxStatus && (pSprite->cstat & 16384))
1240                  {
1241                      viewBackupSpriteLoc(pSprite->index, pSprite);
1242                      pSprite->z += pSector->ceilingz - oldZ;
1243                  }
1244              }
1245          }
1246          /////////////////////
1247          #endif
1248      }
1249  }
1250  
1251  int GetHighestSprite(int nSector, int nStatus, int *a3)
1252  {
1253      *a3 = sector[nSector].floorz;
1254      int v8 = -1;
1255      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1256      {
1257          if (sprite[nSprite].statnum == nStatus || nStatus == kStatFree)
1258          {
1259              spritetype *pSprite = &sprite[nSprite];
1260              int top, bottom;
1261              GetSpriteExtents(pSprite, &top, &bottom);
1262              if (pSprite->z-top < *a3)
1263              {
1264                  *a3 = pSprite->z-top;
1265                  v8 = nSprite;
1266              }
1267          }
1268      }
1269      return v8;
1270  }
1271  
1272  int GetCrushedSpriteExtents(unsigned int nSector, int *pzTop, int *pzBot)
1273  {
1274      dassert(pzTop != NULL && pzBot != NULL);
1275      dassert(nSector < (unsigned int)numsectors);
1276      int vc = -1;
1277      sectortype *pSector = &sector[nSector];
1278      int vbp = pSector->ceilingz;
1279      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1280      {
1281          spritetype *pSprite = &sprite[nSprite];
1282          if (pSprite->statnum == kStatDude || pSprite->statnum == kStatThing)
1283          {
1284              int top, bottom;
1285              GetSpriteExtents(pSprite, &top, &bottom);
1286              if (vbp > top)
1287              {
1288                  vbp = top;
1289                  *pzTop = top;
1290                  *pzBot = bottom;
1291                  vc = nSprite;
1292              }
1293          }
1294      }
1295      return vc;
1296  }
1297  
1298  int VCrushBusy(unsigned int nSector, unsigned int a2, int causerID)
1299  {
1300      dassert(nSector < (unsigned int)numsectors);
1301      int nXSector = sector[nSector].extra;
1302      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1303      XSECTOR *pXSector = &xsector[nXSector];
1304      int nWave;
1305      if (pXSector->busy < a2)
1306          nWave = pXSector->busyWaveA;
1307      else
1308          nWave = pXSector->busyWaveB;
1309      int dz1 = pXSector->onCeilZ - pXSector->offCeilZ;
1310      int vc = pXSector->offCeilZ;
1311      if (dz1 != 0)
1312          vc += mulscale16(dz1, GetWaveValue(a2, nWave));
1313      int dz2 = pXSector->onFloorZ - pXSector->offFloorZ;
1314      int v10 = pXSector->offFloorZ;
1315      if (dz2 != 0)
1316          v10 += mulscale16(dz2, GetWaveValue(a2, nWave));
1317      int v18;
1318      if (GetHighestSprite(nSector, 6, &v18) >= 0 && vc >= v18)
1319          return 1;
1320      viewInterpolateSector(nSector, &sector[nSector]);
1321      if (dz1 != 0)
1322          sector[nSector].ceilingz = vc;
1323      if (dz2 != 0)
1324          sector[nSector].floorz = v10;
1325      pXSector->busy = a2;
1326      if (pXSector->command == kCmdLink && pXSector->txID)
1327          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1328      if ((a2&0xffff) == 0)
1329      {
1330          SetSectorState(nSector, pXSector, a2>>16, causerID);
1331          SectorEndSound(nSector, a2>>16);
1332          return 3;
1333      }
1334      return 0;
1335  }
1336  
1337  int VSpriteBusy(unsigned int nSector, unsigned int a2, int causerID)
1338  {
1339      dassert(nSector < (unsigned int)numsectors);
1340      int nXSector = sector[nSector].extra;
1341      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1342      XSECTOR *pXSector = &xsector[nXSector];
1343      int nWave;
1344      if (pXSector->busy < a2)
1345          nWave = pXSector->busyWaveA;
1346      else
1347          nWave = pXSector->busyWaveB;
1348      int dz1 = pXSector->onFloorZ - pXSector->offFloorZ;
1349      if (dz1 != 0)
1350      {
1351          for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1352          {
1353              spritetype *pSprite = &sprite[nSprite];
1354              if (pSprite->cstat&8192)
1355              {
1356                  viewBackupSpriteLoc(nSprite, pSprite);
1357                  pSprite->z = baseSprite[nSprite].z+mulscale16(dz1, GetWaveValue(a2, nWave));
1358              }
1359          }
1360      }
1361      int dz2 = pXSector->onCeilZ - pXSector->offCeilZ;
1362      if (dz2 != 0)
1363      {
1364          for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1365          {
1366              spritetype *pSprite = &sprite[nSprite];
1367              if (pSprite->cstat&16384)
1368              {
1369                  viewBackupSpriteLoc(nSprite, pSprite);
1370                  pSprite->z = baseSprite[nSprite].z+mulscale16(dz2, GetWaveValue(a2, nWave));
1371              }
1372          }
1373      }
1374      pXSector->busy = a2;
1375      if (pXSector->command == kCmdLink && pXSector->txID)
1376          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1377      if ((a2&0xffff) == 0)
1378      {
1379          SetSectorState(nSector, pXSector, a2>>16, causerID);
1380          SectorEndSound(nSector, a2>>16);
1381          return 3;
1382      }
1383      return 0;
1384  }
1385  
1386  int VDoorBusy(unsigned int nSector, unsigned int a2, int causerID)
1387  {
1388      dassert(nSector < (unsigned int)numsectors);
1389      int nXSector = sector[nSector].extra;
1390      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1391      XSECTOR *pXSector = &xsector[nXSector];
1392      int vbp;
1393      if (pXSector->state)
1394          vbp = 65536/ClipLow((120*pXSector->busyTimeA)/10, 1);
1395      else
1396          vbp = -65536/ClipLow((120*pXSector->busyTimeB)/10, 1);
1397      int top, bottom;
1398      int nSprite = GetCrushedSpriteExtents(nSector,&top,&bottom);
1399      if (nSprite >= 0 && a2 > pXSector->busy)
1400      {
1401          spritetype *pSprite = &sprite[nSprite];
1402          dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
1403          XSPRITE *pXSprite = &xsprite[pSprite->extra];
1404          if (pXSector->onCeilZ > pXSector->offCeilZ || pXSector->onFloorZ < pXSector->offFloorZ)
1405          {
1406              if (pXSector->interruptable)
1407              {
1408                  if (pXSector->Crush)
1409                  {
1410                      if (pXSprite->health <= 0)
1411                          return 2;
1412                      int nDamage;
1413                      if (pXSector->data == 0)
1414                          nDamage = 500;
1415                      else
1416                          nDamage = pXSector->data;
1417                      actDamageSprite(nSprite, &sprite[nSprite], kDamageFall, nDamage<<4);
1418                  }
1419                  a2 = ClipRange(a2-(vbp/2)*4, 0, 65536);
1420              }
1421              else if (pXSector->Crush && pXSprite->health > 0)
1422              {
1423                  int nDamage;
1424                  if (pXSector->data == 0)
1425                      nDamage = 500;
1426                  else
1427                      nDamage = pXSector->data;
1428                  actDamageSprite(nSprite, &sprite[nSprite], kDamageFall, nDamage<<4);
1429                  a2 = ClipRange(a2-(vbp/2)*4, 0, 65536);
1430              }
1431          }
1432      }
1433      else if (nSprite >= 0 && a2 < pXSector->busy)
1434      {
1435          spritetype *pSprite = &sprite[nSprite];
1436          dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
1437          XSPRITE *pXSprite = &xsprite[pSprite->extra];
1438          if (pXSector->offCeilZ > pXSector->onCeilZ || pXSector->offFloorZ < pXSector->onFloorZ)
1439          {
1440              if (pXSector->interruptable)
1441              {
1442                  if (pXSector->Crush)
1443                  {
1444                      if (pXSprite->health <= 0)
1445                          return 2;
1446                      int nDamage;
1447                      if (pXSector->data == 0)
1448                          nDamage = 500;
1449                      else
1450                          nDamage = pXSector->data;
1451                      actDamageSprite(nSprite, &sprite[nSprite], kDamageFall, nDamage<<4);
1452                  }
1453                  a2 = ClipRange(a2+(vbp/2)*4, 0, 65536);
1454              }
1455              else if (pXSector->Crush && pXSprite->health > 0)
1456              {
1457                  int nDamage;
1458                  if (pXSector->data == 0)
1459                      nDamage = 500;
1460                  else
1461                      nDamage = pXSector->data;
1462                  actDamageSprite(nSprite, &sprite[nSprite], kDamageFall, nDamage<<4);
1463                  a2 = ClipRange(a2+(vbp/2)*4, 0, 65536);
1464              }
1465          }
1466      }
1467      int nWave;
1468      if (pXSector->busy < a2)
1469          nWave = pXSector->busyWaveA;
1470      else
1471          nWave = pXSector->busyWaveB;
1472      if ((nWave == 3) && gGameOptions.bSectorBehavior && !VanillaMode()) // use better wave type for elevator (from raze)
1473          nWave = 0;
1474      ZTranslateSector(nSector, pXSector, a2, nWave);
1475      pXSector->busy = a2;
1476      if (pXSector->command == kCmdLink && pXSector->txID)
1477          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1478      if ((a2&0xffff) == 0)
1479      {
1480          SetSectorState(nSector, pXSector, a2>>16, causerID);
1481          SectorEndSound(nSector, a2>>16);
1482          return 3;
1483      }
1484      return 0;
1485  }
1486  
1487  int HDoorBusy(unsigned int nSector, unsigned int a2, int causerID)
1488  {
1489      dassert(nSector < (unsigned int)numsectors);
1490      sectortype *pSector = &sector[nSector];
1491      int nXSector = pSector->extra;
1492      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1493      XSECTOR *pXSector = &xsector[nXSector];
1494      int nWave;
1495      if (pXSector->busy < a2)
1496          nWave = pXSector->busyWaveA;
1497      else
1498          nWave = pXSector->busyWaveB;
1499      spritetype *pSprite1 = &sprite[pXSector->marker0];
1500      spritetype *pSprite2 = &sprite[pXSector->marker1];
1501      TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->type == kSectorSlide);
1502      ZTranslateSector(nSector, pXSector, a2, nWave);
1503      pXSector->busy = a2;
1504      if (pXSector->command == kCmdLink && pXSector->txID)
1505          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1506      if ((a2&0xffff) == 0)
1507      {
1508          SetSectorState(nSector, pXSector, a2>>16, causerID);
1509          SectorEndSound(nSector, a2>>16);
1510          return 3;
1511      }
1512      return 0;
1513  }
1514  
1515  int RDoorBusy(unsigned int nSector, unsigned int a2, int causerID)
1516  {
1517      dassert(nSector < (unsigned int)numsectors);
1518      sectortype *pSector = &sector[nSector];
1519      int nXSector = pSector->extra;
1520      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1521      XSECTOR *pXSector = &xsector[nXSector];
1522      int nWave;
1523      if (pXSector->busy < a2)
1524          nWave = pXSector->busyWaveA;
1525      else
1526          nWave = pXSector->busyWaveB;
1527      spritetype *pSprite = &sprite[pXSector->marker0];
1528      TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, 0, pSprite->x, pSprite->y, pSprite->ang, pSector->type == kSectorRotate);
1529      ZTranslateSector(nSector, pXSector, a2, nWave);
1530      pXSector->busy = a2;
1531      if (pXSector->command == kCmdLink && pXSector->txID)
1532          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1533      if ((a2&0xffff) == 0)
1534      {
1535          SetSectorState(nSector, pXSector, a2>>16, causerID);
1536          SectorEndSound(nSector, a2>>16);
1537          return 3;
1538      }
1539      return 0;
1540  }
1541  
1542  int StepRotateBusy(unsigned int nSector, unsigned int a2, int causerID)
1543  {
1544      dassert(nSector < (unsigned int)numsectors);
1545      sectortype *pSector = &sector[nSector];
1546      int nXSector = pSector->extra;
1547      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1548      XSECTOR *pXSector = &xsector[nXSector];
1549      spritetype *pSprite = &sprite[pXSector->marker0];
1550      int vbp;
1551      if (pXSector->busy < a2)
1552      {
1553          vbp = pXSector->data+pSprite->ang;
1554          int nWave = pXSector->busyWaveA;
1555          TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, pXSector->data, pSprite->x, pSprite->y, vbp, 1);
1556      }
1557      else
1558      {
1559          vbp = pXSector->data-pSprite->ang;
1560          int nWave = pXSector->busyWaveB;
1561          TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, vbp, pSprite->x, pSprite->y, pXSector->data, 1);
1562      }
1563      pXSector->busy = a2;
1564      if (pXSector->command == kCmdLink && pXSector->txID)
1565          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1566      if ((a2&0xffff) == 0)
1567      {
1568          SetSectorState(nSector, pXSector, a2>>16, causerID);
1569          SectorEndSound(nSector, a2>>16);
1570          pXSector->data = vbp&2047;
1571          return 3;
1572      }
1573      return 0;
1574  }
1575  
1576  int GenSectorBusy(unsigned int nSector, unsigned int a2, int causerID)
1577  {
1578      dassert(nSector < (unsigned int)numsectors);
1579      sectortype *pSector = &sector[nSector];
1580      int nXSector = pSector->extra;
1581      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1582      XSECTOR *pXSector = &xsector[nXSector];
1583      pXSector->busy = a2;
1584      if (pXSector->command == kCmdLink && pXSector->txID)
1585          evSend(nSector, 6, pXSector->txID, kCmdLink, causerID);
1586      if ((a2&0xffff) == 0)
1587      {
1588          SetSectorState(nSector, pXSector, a2>>16, causerID);
1589          SectorEndSound(nSector, a2>>16);
1590          return 3;
1591      }
1592      return 0;
1593  }
1594  
1595  int PathBusy(unsigned int nSector, unsigned int a2, int causerID)
1596  {
1597      dassert(nSector < (unsigned int)numsectors);
1598      sectortype *pSector = &sector[nSector];
1599      int nXSector = pSector->extra;
1600      dassert(nXSector > 0 && nXSector < kMaxXSectors);
1601      XSECTOR *pXSector = &xsector[nXSector];
1602      spritetype *pSprite = &sprite[basePath[nSector]];
1603      spritetype *pSprite1 = &sprite[pXSector->marker0];
1604      XSPRITE *pXSprite1 = &xsprite[pSprite1->extra];
1605      spritetype *pSprite2 = &sprite[pXSector->marker1];
1606      XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
1607      int nWave = pXSprite1->wave;
1608      TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, 1);
1609      ZTranslateSector(nSector, pXSector, a2, nWave);
1610      pXSector->busy = a2;
1611      if ((a2&0xffff) == 0)
1612      {
1613          evPost(nSector, 6, (120*pXSprite2->waitTime)/10, kCmdOn, causerID);
1614          pXSector->state = 0;
1615          pXSector->busy = 0;
1616          if (pXSprite1->data4)
1617              PathSound(nSector, pXSprite1->data4);
1618          pXSector->marker0 = pXSector->marker1;
1619          pXSector->data = pXSprite2->data1;
1620          return 3;
1621      }
1622      return 0;
1623  }
1624  
1625  void OperateDoor(unsigned int nSector, XSECTOR *pXSector, const EVENT &event, BUSYID busyWave) 
1626  {
1627      switch (event.cmd) {
1628          case kCmdOff:
1629              if (!pXSector->busy) break;
1630              AddBusy(nSector, busyWave, -65536/ClipLow((pXSector->busyTimeB*120)/10, 1));
1631              SectorStartSound(nSector, 1);
1632              break;
1633          case kCmdOn:
1634              if (pXSector->busy == 0x10000) break;
1635              AddBusy(nSector, busyWave, 65536/ClipLow((pXSector->busyTimeA*120)/10, 1));
1636              SectorStartSound(nSector, 0);
1637              break;
1638          default:
1639              if (pXSector->busy & 0xffff)  {
1640                  if (pXSector->interruptable) {
1641                      ReverseBusy(nSector, busyWave);
1642                      pXSector->state = !pXSector->state;
1643                  }
1644              } else {
1645                  char t = !pXSector->state; int nDelta;
1646              
1647                  if (t) nDelta = 65536/ClipLow((pXSector->busyTimeA*120)/10, 1);
1648                  else nDelta = -65536/ClipLow((pXSector->busyTimeB*120)/10, 1);
1649              
1650                  AddBusy(nSector, busyWave, nDelta);
1651                  SectorStartSound(nSector, pXSector->state);
1652              }
1653              break;
1654      }
1655  }
1656  
1657  char SectorContainsDudes(int nSector)
1658  {
1659      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1660      {
1661          if (sprite[nSprite].statnum == kStatDude)
1662              return 1;
1663      }
1664      return 0;
1665  }
1666  
1667  void TeleFrag(int nKiller, int nSector)
1668  {
1669      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1670      {
1671          spritetype *pSprite = &sprite[nSprite];
1672          if (pSprite->statnum == kStatDude)
1673              actDamageSprite(nKiller, pSprite, kDamageExplode, 4000);
1674          else if (pSprite->statnum == kStatThing)
1675              actDamageSprite(nKiller, pSprite, kDamageExplode, 8000);
1676      }
1677  }
1678  
1679  void OperateTeleport(unsigned int nSector, XSECTOR *pXSector)
1680  {
1681      dassert(nSector < (unsigned int)numsectors);
1682      int nDest = pXSector->marker0;
1683      dassert(nDest < kMaxSprites);
1684      spritetype *pDest = &sprite[nDest];
1685      dassert(pDest->statnum == kStatMarker);
1686      dassert(pDest->type == kMarkerWarpDest);
1687      dassert(pDest->sectnum >= 0 && pDest->sectnum < kMaxSectors);
1688      for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
1689      {
1690          spritetype *pSprite = &sprite[nSprite];
1691          if (pSprite->statnum == kStatDude)
1692          {
1693              PLAYER *pPlayer;
1694              char bPlayer = IsPlayerSprite(pSprite);
1695              if (bPlayer)
1696                  pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
1697              else
1698                  pPlayer = NULL;
1699              if (bPlayer || !SectorContainsDudes(pDest->sectnum))
1700              {
1701                  if (!(gGameOptions.uNetGameFlags&kNetGameFlagNoTeleFrag))
1702                      TeleFrag(pXSector->data, pDest->sectnum);
1703                  pSprite->x = pDest->x;
1704                  pSprite->y = pDest->y;
1705                  pSprite->z += sector[pDest->sectnum].floorz-sector[nSector].floorz;
1706                  pSprite->ang = pDest->ang;
1707                  ChangeSpriteSect(nSprite, pDest->sectnum);
1708                  sfxPlay3DSound(pDest, 201, -1, 0);
1709                  xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
1710                  ClearBitString(gInterpolateSprite, nSprite);
1711                  viewBackupSpriteLoc(nSprite, pSprite);
1712                  if (pPlayer)
1713                  {
1714                      playerResetInertia(pPlayer);
1715                      pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
1716                      if (!VanillaMode()) // if player teleported, clear old angles
1717                      {
1718                          pPlayer->angold = pSprite->ang;
1719                          pPlayer->q16ang = fix16_from_int(pSprite->ang);
1720                          if (pPlayer == gMe) // if player is listener, update ear position/reset ear velocity so audio pitch of surrounding sfx does not freak out when teleporting player
1721                              sfxResetListener();
1722                      }
1723                  }
1724                  if (!VanillaMode())
1725                      sfxUpdateSpritePos(pSprite); // update any assigned sfx to new position
1726              }
1727          }
1728      }
1729  }
1730  
1731  void OperatePath(unsigned int nSector, XSECTOR *pXSector, const EVENT &event)
1732  {
1733      int nSprite;
1734      spritetype *pSprite = NULL;
1735      XSPRITE *pXSprite;
1736      dassert(nSector < (unsigned int)numsectors);
1737      spritetype *pSprite2 = &sprite[pXSector->marker0];
1738      XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
1739      int nId = pXSprite2->data2;
1740      for (nSprite = headspritestat[kStatPathMarker]; nSprite >= 0; nSprite = nextspritestat[nSprite])
1741      {
1742          pSprite = &sprite[nSprite];
1743          if (pSprite->type == kMarkerPath)
1744          {
1745              pXSprite = &xsprite[pSprite->extra];
1746              if (pXSprite->data1 == nId)
1747                  break;
1748          }
1749      }
1750  
1751      // trigger marker after it gets reached
1752      #ifdef NOONE_EXTENSIONS
1753          if (gModernMap && pXSprite2->state != 1)
1754              trTriggerSprite(pSprite2->index, pXSprite2, kCmdOn, event.causer);
1755      #endif
1756  
1757      if (nSprite < 0) {
1758          viewSetSystemMessage("Unable to find path marker with id #%d for path sector #%d", nId, nSector);
1759          pXSector->state = 0;
1760          pXSector->busy = 0;
1761          return;
1762      }
1763          
1764      pXSector->marker1 = nSprite;
1765      pXSector->offFloorZ = pSprite2->z;
1766      pXSector->onFloorZ = pSprite->z;
1767      switch (event.cmd) {
1768          case kCmdOn:
1769              pXSector->state = 0;
1770              pXSector->busy = 0;
1771              AddBusy(nSector, BUSYID_7, 65536/ClipLow((120*pXSprite2->busyTime)/10,1));
1772              if (pXSprite2->data3) PathSound(nSector, pXSprite2->data3);
1773              break;
1774      }
1775  }
1776  
1777  void OperateSector(unsigned int nSector, XSECTOR *pXSector, const EVENT &event)
1778  {
1779      dassert(nSector < (unsigned int)numsectors);
1780      sectortype *pSector = &sector[nSector];
1781      
1782      #ifdef NOONE_EXTENSIONS
1783      if (gModernMap && modernTypeOperateSector(nSector, pSector, pXSector, event))
1784          return;
1785      #endif
1786  
1787      switch (event.cmd) {
1788          case kCmdLock:
1789              pXSector->locked = 1;
1790              break;
1791          case kCmdUnlock:
1792              pXSector->locked = 0;
1793              break;
1794          case kCmdToggleLock:
1795              pXSector->locked ^= 1;
1796              break;
1797          case kCmdStopOff:
1798              pXSector->stopOn = 0;
1799              pXSector->stopOff = 1;
1800              break;
1801          case kCmdStopOn:
1802              pXSector->stopOn = 1;
1803              pXSector->stopOff = 0;
1804              break;
1805          case kCmdStopNext:
1806              pXSector->stopOn = 1;
1807              pXSector->stopOff = 1;
1808              break;
1809          default:
1810          #ifdef NOONE_EXTENSIONS
1811          if (gModernMap && pXSector->unused1) break;
1812          #endif
1813              switch (pSector->type) {
1814                  case kSectorZMotionSprite:
1815                      OperateDoor(nSector, pXSector, event, BUSYID_1);
1816                      break;
1817                  case kSectorZMotion:
1818                      OperateDoor(nSector, pXSector, event, BUSYID_2);
1819                      break;
1820                  case kSectorSlideMarked:
1821                  case kSectorSlide:
1822                      OperateDoor(nSector, pXSector, event, BUSYID_3);
1823                      break;
1824                  case kSectorRotateMarked:
1825                  case kSectorRotate:
1826                      OperateDoor(nSector, pXSector, event, BUSYID_4);
1827                      break;
1828                  case kSectorRotateStep:
1829                      switch (event.cmd) {
1830                          case kCmdOn:
1831                              pXSector->state = 0;
1832                              pXSector->busy = 0;
1833                              AddBusy(nSector, BUSYID_5, 65536/ClipLow((120*pXSector->busyTimeA)/10, 1));
1834                              SectorStartSound(nSector, 0);
1835                              break;
1836                          case kCmdOff:
1837                              pXSector->state = 1;
1838                              pXSector->busy = 65536;
1839                              AddBusy(nSector, BUSYID_5, -65536/ClipLow((120*pXSector->busyTimeB)/10, 1));
1840                              SectorStartSound(nSector, 1);
1841                              break;
1842                      }
1843                      break;
1844                  case kSectorTeleport:
1845                      OperateTeleport(nSector, pXSector);
1846                      break;
1847                  case kSectorPath:
1848                      OperatePath(nSector, pXSector, event);
1849                      break;
1850                  default:
1851                      if (!pXSector->busyTimeA && !pXSector->busyTimeB) {
1852                          
1853                          switch (event.cmd) {
1854                              case kCmdOff:
1855                                  SetSectorState(nSector, pXSector, 0, event.causer);
1856                                  break;
1857                              case kCmdOn:
1858                                  SetSectorState(nSector, pXSector, 1, event.causer);
1859                                  break;
1860                              default:
1861                                  SetSectorState(nSector, pXSector, pXSector->state ^ 1, event.causer);
1862                                  break;
1863                          }
1864  
1865                      } else {
1866                          
1867                          OperateDoor(nSector, pXSector, event, BUSYID_6);
1868  
1869                      }
1870  
1871                      break;
1872              }
1873              break;
1874      }
1875  }
1876  
1877  void InitPath(unsigned int nSector, XSECTOR *pXSector)
1878  {
1879      int nSprite;
1880      spritetype *pSprite;
1881      XSPRITE *pXSprite;
1882      dassert(nSector < (unsigned int)numsectors);
1883      int nId = pXSector->data;
1884      for (nSprite = headspritestat[kStatPathMarker]; nSprite >= 0; nSprite = nextspritestat[nSprite])
1885      {
1886          pSprite = &sprite[nSprite];
1887          if (pSprite->type == kMarkerPath)
1888          {
1889              pXSprite = &xsprite[pSprite->extra];
1890              if (pXSprite->data1 == nId)
1891                  break;
1892          }
1893      }
1894      
1895      if (nSprite < 0) {
1896          //ThrowError("Unable to find path marker with id #%d", nId);
1897          viewSetSystemMessage("Unable to find path marker with id #%d for path sector #%d", nId, nSector);
1898          return;
1899          
1900      }
1901  
1902      pXSector->marker0 = nSprite;
1903      basePath[nSector] = nSprite;
1904      if (pXSector->state)
1905          evPost(nSector, 6, 0, kCmdOn, kCauserGame);
1906  }
1907  
1908  void LinkSector(int nSector, XSECTOR *pXSector, const EVENT &event)
1909  {
1910      sectortype *pSector = &sector[nSector];
1911      int nBusy = GetSourceBusy(event);
1912      switch (pSector->type) {
1913          case kSectorZMotionSprite:
1914              VSpriteBusy(nSector, nBusy, event.causer);
1915              break;
1916          case kSectorZMotion:
1917              VDoorBusy(nSector, nBusy, event.causer);
1918              break;
1919          case kSectorSlideMarked:
1920          case kSectorSlide:
1921              HDoorBusy(nSector, nBusy, event.causer);
1922              break;
1923          case kSectorRotateMarked:
1924          case kSectorRotate:
1925              RDoorBusy(nSector, nBusy, event.causer);
1926              break;
1927          default:
1928              pXSector->busy = nBusy;
1929              if ((pXSector->busy&0xffff) == 0)
1930                  SetSectorState(nSector, pXSector, nBusy>>16, event.causer);
1931              break;
1932      }
1933  }
1934  
1935  void LinkSprite(int nSprite, XSPRITE *pXSprite, const EVENT &event) {
1936      spritetype *pSprite = &sprite[nSprite];
1937      int nBusy = GetSourceBusy(event);
1938  
1939      switch (pSprite->type)  {
1940          case kSwitchCombo:
1941          {
1942              if (event.type == 3)
1943              {
1944                  int nSprite2 = event.index;
1945                  int nXSprite2 = sprite[nSprite2].extra;
1946                  dassert(nXSprite2 > 0 && nXSprite2 < kMaxXSprites);
1947                  pXSprite->data1 = xsprite[nXSprite2].data1;
1948                  if (pXSprite->data1 == pXSprite->data2)
1949                      SetSpriteState(nSprite, pXSprite, 1, event.causer);
1950                  else
1951                      SetSpriteState(nSprite, pXSprite, 0, event.causer);
1952              }
1953          }
1954          break;
1955          default:
1956          {
1957              pXSprite->busy = nBusy;
1958              if ((pXSprite->busy & 0xffff) == 0)
1959                  SetSpriteState(nSprite, pXSprite, nBusy >> 16, event.causer);
1960          }
1961          break;
1962      }
1963  }
1964  
1965  void LinkWall(int nWall, XWALL *pXWall, const EVENT &event)
1966  {
1967      int nBusy = GetSourceBusy(event);
1968      pXWall->busy = nBusy;
1969      if ((pXWall->busy & 0xffff) == 0)
1970          SetWallState(nWall, pXWall, nBusy>>16, event.causer);
1971  }
1972  
1973  void trTriggerSector(unsigned int nSector, XSECTOR *pXSector, int command, int causerID) {
1974      dassert(nSector < (unsigned int)numsectors);
1975      if (!pXSector->locked && !pXSector->isTriggered) {
1976          
1977          if (pXSector->triggerOnce) 
1978              pXSector->isTriggered = 1;
1979          
1980          if (pXSector->decoupled && pXSector->txID > 0)
1981              evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command, causerID);
1982          
1983          else {
1984              EVENT event;
1985              event.cmd = command;
1986              #ifdef NOONE_EXTENSIONS
1987                  event.causer = (gModernMap) ? causerID : kCauserGame;
1988              #else
1989                  event.causer = kCauserGame;
1990              #endif
1991              OperateSector(nSector, pXSector, event);
1992          }
1993  
1994      }
1995  }
1996  
1997  void trTriggerWall(unsigned int nWall, XWALL *pXWall, int command, int causerID) {
1998      dassert(nWall < (unsigned int)numwalls);
1999      if (!pXWall->locked && !pXWall->isTriggered) {
2000          
2001          if (pXWall->triggerOnce)
2002              pXWall->isTriggered = 1;
2003          
2004          if (pXWall->decoupled && pXWall->txID > 0)
2005              evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command, causerID);
2006  
2007          else {
2008              EVENT event;
2009              event.cmd = command;
2010              #ifdef NOONE_EXTENSIONS
2011                  event.causer = (gModernMap) ? causerID : kCauserGame;
2012              #else
2013                  event.causer = kCauserGame;
2014              #endif
2015              OperateWall(nWall, pXWall, event);
2016          }
2017  
2018      }
2019  }
2020  
2021  void trTriggerSprite(unsigned int nSprite, XSPRITE *pXSprite, int command, int causerID) {
2022      if (!pXSprite->locked && !pXSprite->isTriggered) {
2023          
2024          if (pXSprite->triggerOnce)
2025              pXSprite->isTriggered = 1;
2026  
2027          if (pXSprite->Decoupled && pXSprite->txID > 0)
2028             evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command, causerID);
2029          
2030          else {
2031              EVENT event;
2032              event.cmd = command;
2033              #ifdef NOONE_EXTENSIONS
2034                  event.causer = (gModernMap) ? causerID : kCauserGame;
2035              #else
2036                  event.causer = kCauserGame;
2037              #endif
2038              OperateSprite(nSprite, pXSprite, event);
2039          }
2040  
2041      }
2042  }
2043  
2044  
2045  void trMessageSector(unsigned int nSector, const EVENT &event) {
2046      dassert(nSector < (unsigned int)numsectors);
2047      dassert(sector[nSector].extra > 0 && sector[nSector].extra < kMaxXSectors);
2048      XSECTOR *pXSector = &xsector[sector[nSector].extra];
2049      if (!pXSector->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock) {
2050          switch (event.cmd) {
2051              case kCmdLink:
2052                  LinkSector(nSector, pXSector, event);
2053                  break;
2054              #ifdef NOONE_EXTENSIONS
2055              case kCmdModernUse:
2056                  modernTypeTrigger(6, nSector, event);
2057                  break;
2058              #endif
2059              default:
2060                  OperateSector(nSector, pXSector, event);
2061                  break;
2062          }
2063      }
2064  }
2065  
2066  void trMessageWall(unsigned int nWall, const EVENT &event) {
2067      dassert(nWall < (unsigned int)numwalls);
2068      dassert(wall[nWall].extra > 0 && wall[nWall].extra < kMaxXWalls);
2069      
2070      XWALL *pXWall = &xwall[wall[nWall].extra];
2071      if (!pXWall->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock) {
2072          switch (event.cmd) {
2073              case kCmdLink:
2074                  LinkWall(nWall, pXWall, event);
2075                  break;
2076              #ifdef NOONE_EXTENSIONS
2077              case kCmdModernUse:
2078                  modernTypeTrigger(0, nWall, event);
2079                  break;
2080              #endif
2081              default:
2082                  OperateWall(nWall, pXWall, event);
2083                  break;
2084          }
2085      }
2086  }
2087  
2088  void trMessageSprite(unsigned int nSprite, const EVENT &event) {
2089      if (sprite[nSprite].statnum == kStatFree)
2090          return;
2091      spritetype *pSprite = &sprite[nSprite];
2092      if (pSprite->extra < 0 || pSprite->extra >= kMaxXSprites)
2093          return;
2094      XSPRITE* pXSprite = &xsprite[sprite[nSprite].extra];
2095      if (!pXSprite->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock) {
2096          switch (event.cmd) {
2097              case kCmdLink:
2098                  LinkSprite(nSprite, pXSprite, event);
2099                  break;
2100              #ifdef NOONE_EXTENSIONS
2101              case kCmdModernUse:
2102                  modernTypeTrigger(3, nSprite, event);
2103                  break;
2104              #endif
2105              default:
2106                  OperateSprite(nSprite, pXSprite, event);
2107                  break;
2108          }
2109      }
2110  }
2111  
2112  
2113  
2114  void ProcessMotion(void)
2115  {
2116      sectortype *pSector;
2117      int nSector;
2118      for (pSector = sector, nSector = 0; nSector < numsectors; nSector++, pSector++)
2119      {
2120          int nXSector = pSector->extra;
2121          if (nXSector <= 0)
2122              continue;
2123          XSECTOR *pXSector = &xsector[nXSector];
2124          if (pXSector->bobSpeed != 0)
2125          {
2126              if (pXSector->bobAlways)
2127                  pXSector->bobTheta += pXSector->bobSpeed;
2128              else if (pXSector->busy == 0)
2129                  continue;
2130              else
2131                  pXSector->bobTheta += mulscale16(pXSector->bobSpeed, pXSector->busy);
2132              int vdi = mulscale30(Sin(pXSector->bobTheta), pXSector->bobZRange<<8);
2133              for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
2134              {
2135                  spritetype *pSprite = &sprite[nSprite];
2136                  if (pSprite->cstat&24576)
2137                  {
2138                      viewBackupSpriteLoc(nSprite, pSprite);
2139                      pSprite->z += vdi;
2140                  }
2141              }
2142              if (pXSector->bobFloor)
2143              {
2144                  int floorZ = pSector->floorz;
2145                  viewInterpolateSector(nSector, pSector);
2146                  pSector->floorz = baseFloor[nSector]+vdi;
2147                  for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
2148                  {
2149                      spritetype *pSprite = &sprite[nSprite];
2150                      if (pSprite->flags&2)
2151                          pSprite->flags |= 4;
2152                      else
2153                      {
2154                          int top, bottom;
2155                          GetSpriteExtents(pSprite, &top, &bottom);
2156                          if (bottom >= floorZ && (pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK) == 0)
2157                          {
2158                              viewBackupSpriteLoc(nSprite, pSprite);
2159                              pSprite->z += vdi;
2160                          }
2161                      }
2162                  }
2163              }
2164              if (pXSector->bobCeiling)
2165              {
2166                  int ceilZ = pSector->ceilingz;
2167                  viewInterpolateSector(nSector, pSector);
2168                  pSector->ceilingz = baseCeil[nSector]+vdi;
2169                  for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
2170                  {
2171                      spritetype *pSprite = &sprite[nSprite];
2172                      int top, bottom;
2173                      GetSpriteExtents(pSprite, &top, &bottom);
2174                      if (top <= ceilZ && (pSprite->cstat&CSTAT_SPRITE_ALIGNMENT_MASK) == 0)
2175                      {
2176                          viewBackupSpriteLoc(nSprite, pSprite);
2177                          pSprite->z += vdi;
2178                      }
2179                  }
2180              }
2181          }
2182      }
2183  }
2184  
2185  void AlignSlopes(void)
2186  {
2187      sectortype *pSector;
2188      int nSector;
2189      for (pSector = sector, nSector = 0; nSector < numsectors; nSector++, pSector++)
2190      {
2191          if (qsector_filler[nSector])
2192          {
2193              walltype *pWall = &wall[pSector->wallptr+qsector_filler[nSector]];
2194              walltype *pWall2 = &wall[pWall->point2];
2195              int nNextSector = pWall->nextsector;
2196              if (nNextSector >= 0)
2197              {
2198                  int x = (pWall->x+pWall2->x)/2;
2199                  int y = (pWall->y+pWall2->y)/2;
2200                  viewInterpolateSector(nSector, pSector);
2201                  alignflorslope(nSector, x, y, getflorzofslope(nNextSector, x, y));
2202                  alignceilslope(nSector, x, y, getceilzofslope(nNextSector, x, y));
2203              }
2204          }
2205      }
2206  }
2207  
2208  int(*gBusyProc[])(unsigned int, unsigned int, int) =
2209  {
2210      VCrushBusy,
2211      VSpriteBusy,
2212      VDoorBusy,
2213      HDoorBusy,
2214      RDoorBusy,
2215      StepRotateBusy,
2216      GenSectorBusy,
2217      PathBusy,
2218  #ifdef NOONE_EXTENSIONS
2219      pathSpriteBusy,
2220  #endif
2221  };
2222  
2223  void trProcessBusy(void)
2224  {
2225      memset(velFloor, 0, sizeof(velFloor));
2226      memset(velCeil, 0, sizeof(velCeil));
2227      for (int i = gBusyCount-1; i >= 0; i--)
2228      {
2229          int nStatus;
2230          int oldBusy = gBusy[i].at8;
2231          gBusy[i].at8 = ClipRange(oldBusy+gBusy[i].at4*4, 0, 65536);
2232          #ifdef NOONE_EXTENSIONS
2233              if (!gModernMap || !xsector[sector[gBusy[i].at0].extra].unused1) nStatus = gBusyProc[gBusy[i].atc](gBusy[i].at0, gBusy[i].at8, -1);
2234              else nStatus = 3; // allow to pause/continue motion for sectors any time by sending special command
2235          #else
2236              nStatus = gBusyProc[gBusy[i].atc](gBusy[i].at0, gBusy[i].at8, -1);
2237          #endif
2238          switch (nStatus) {
2239              case 1:
2240                  gBusy[i].at8 = oldBusy;
2241                  break;
2242              case 2:
2243                  gBusy[i].at8 = oldBusy;
2244                  gBusy[i].at4 = -gBusy[i].at4;
2245                  break;
2246              case 3:
2247                  gBusy[i] = gBusy[--gBusyCount];
2248                  break;
2249          }
2250      }
2251      ProcessMotion();
2252      AlignSlopes();
2253  }
2254  
2255  void InitGenerator(int);
2256  
2257  void setBasePoint(int objType, int objIdx)
2258  {
2259      switch (objType) {
2260          case 1:
2261              baseSprite[objIdx].x = sprite[objIdx].x;
2262              baseSprite[objIdx].y = sprite[objIdx].y;
2263              baseSprite[objIdx].z = sprite[objIdx].z;
2264              break;
2265          case 2:
2266              baseWall[objIdx].x = wall[objIdx].x;
2267              baseWall[objIdx].y = wall[objIdx].y;
2268              break;
2269          case 3:
2270              baseFloor[objIdx] = sector[objIdx].floorz;
2271              baseCeil[objIdx] = sector[objIdx].ceilingz;
2272              break;
2273      }
2274  }
2275  
2276  void setBaseWallSect(int nSect)
2277  {
2278      int swal, ewal;
2279      swal = sector[nSect].wallptr;
2280      ewal = swal + sector[nSect].wallnum - 1;
2281      while(swal <= ewal)
2282          setBasePoint(2, swal++);
2283  }
2284  
2285  void setBaseSpriteSect(int nSect)
2286  {
2287      int i;
2288      i = headspritesect[nSect];
2289      while (i >= 0)
2290      {
2291          setBasePoint(1, i);
2292          i = nextspritesect[i];
2293      }
2294  }
2295  
2296  void trInit(void)
2297  {
2298      gBusyCount = 0;
2299      spritetype* pMark1, * pMark2;
2300      int i, * ptr;
2301  
2302      dassert((numsectors >= 0) && (numsectors < kMaxSectors));
2303  
2304  #ifdef NOONE_EXTENSIONS
2305      if (gModernMap)
2306          nnExtTrInit(); // init modern triggers stuff
2307  #endif
2308      
2309      for (i = 0; i < numwalls; i++)
2310      {
2311          setBasePoint(2, i);
2312          if (wall[i].extra <= 0)
2313              continue;
2314  
2315          dassert(wall[i].extra < kMaxXWalls);
2316          XWALL* pXWall = &xwall[wall[i].extra];
2317          if (pXWall->state)
2318              pXWall->busy = 65536;
2319      }
2320  
2321      for (i = 0; i < kMaxSprites; i++)
2322      {
2323          sprite[i].inittype = -1;
2324          if (sprite[i].statnum >= kStatFree)
2325              continue;
2326  
2327          setBasePoint(1, i);
2328          sprite[i].inittype = sprite[i].type;
2329      }
2330  
2331  
2332      for (i = 0; i < numsectors; i++)
2333      {
2334          pMark2 = NULL;
2335          sectortype* pSector = &sector[i];
2336          setBasePoint(3, i);
2337          int nXSector = pSector->extra;
2338          if (nXSector > 0)
2339          {
2340              dassert(nXSector < kMaxXSectors);
2341              XSECTOR* pXSector = &xsector[nXSector];
2342              if (pXSector->state)
2343                  pXSector->busy = 65536;
2344              switch (pSector->type) {
2345              case kSectorCounter:
2346                  #ifdef NOONE_EXTENSIONS
2347                      if (gModernMap) pXSector->triggerOff = false;
2348                      else pXSector->triggerOnce = 1;
2349                  #else
2350                      pXSector->triggerOnce = 1;
2351                  #endif
2352                  evPost(i, 6, 0, kCallbackCounterCheck);
2353                  break;
2354              case kSectorSlideMarked:
2355              case kSectorSlide:
2356                  // grab second marker
2357                  pMark2 = &sprite[pXSector->marker1];
2358                  fallthrough__;
2359              case kSectorRotateMarked:
2360              case kSectorRotate:
2361                  // grab first marker
2362                  pMark1 = &sprite[pXSector->marker0];
2363                  if (pMark2) TranslateSector(i, 0, -65536, pMark1->x, pMark1->y, pMark1->x, pMark1->y, pMark1->ang, pMark2->x, pMark2->y, pMark2->ang, pSector->type == kSectorSlide);
2364                  else TranslateSector(i, 0, -65536, pMark1->x, pMark1->y, pMark1->x, pMark1->y, 0, pMark1->x, pMark1->y, pMark1->ang, pSector->type == kSectorRotate);
2365                  
2366                  #ifdef NOONE_EXTENSIONS
2367                  // must set basepoint for outside sprites as well
2368                  if (gModernMap && (ptr = gSprNSect.GetSprPtr(i)) != NULL)
2369                  {
2370                      while (*ptr >= 0)
2371                          setBasePoint(1, *ptr++);
2372                  }
2373                  #endif
2374  
2375                  setBaseWallSect(i); setBaseSpriteSect(i);
2376  
2377                  if (pMark2) TranslateSector(i, 0, pXSector->busy, pMark1->x, pMark1->y, pMark1->x, pMark1->y, pMark1->ang, pMark2->x, pMark2->y, pMark2->ang, pSector->type == kSectorSlide);
2378                  else TranslateSector(i, 0, pXSector->busy, pMark1->x, pMark1->y, pMark1->x, pMark1->y, 0, pMark1->x, pMark1->y, pMark1->ang, pSector->type == kSectorRotate);
2379                  fallthrough__;
2380              case kSectorZMotionSprite:
2381              case kSectorZMotion:
2382                  ZTranslateSector(i, pXSector, pXSector->busy, 1);
2383                  break;
2384              case kSectorPath:
2385                  InitPath(i, pXSector);
2386                  break;
2387              default:
2388                  break;
2389              }
2390          }
2391      }
2392      for (int i = 0; i < kMaxSprites; i++)
2393      {
2394          int nXSprite = sprite[i].extra;
2395          if (sprite[i].statnum < kStatFree && nXSprite > 0)
2396          {
2397              dassert(nXSprite < kMaxXSprites);
2398              XSPRITE *pXSprite = &xsprite[nXSprite];
2399              if (pXSprite->state)
2400                  pXSprite->busy = 65536;
2401              switch (sprite[i].type) {
2402              case kSwitchPadlock:
2403                  pXSprite->triggerOnce = 1;
2404                  break;
2405              #ifdef NOONE_EXTENSIONS
2406              case kModernRandom:
2407              case kModernRandom2:
2408                  if (!gModernMap || pXSprite->state == pXSprite->restState) break;
2409                  evPost(i, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat, i);
2410                  if (pXSprite->waitTime > 0)
2411                      evPost(i, 3, (pXSprite->waitTime * 120) / 10, pXSprite->restState ? kCmdOn : kCmdOff, i);
2412                  break;
2413              case kModernSeqSpawner:
2414              case kModernObjDataAccumulator:
2415              case kModernDudeTargetChanger:
2416              case kModernEffectSpawner:
2417              case kModernWindGenerator:
2418                  if (pXSprite->state == pXSprite->restState) break;
2419                  evPost(i, 3, 0, kCmdRepeat, i);
2420                  if (pXSprite->waitTime > 0)
2421                      evPost(i, 3, (pXSprite->waitTime * 120) / 10, pXSprite->restState ? kCmdOn : kCmdOff, i);
2422                  break;
2423              #endif
2424              case kGenTrigger:
2425              case kGenDripWater:
2426              case kGenDripBlood:
2427              case kGenMissileFireball:
2428              case kGenDart:
2429              case kGenBubble:
2430              case kGenBubbleMulti:
2431              case kGenMissileEctoSkull:
2432              case kGenSound:
2433                  InitGenerator(i);
2434                  break;
2435              case kThingArmedProxBomb:
2436                  pXSprite->Proximity = 1;
2437                  break;
2438              case kThingFallingRock:
2439                  if (pXSprite->state) sprite[i].flags |= 7;
2440                  else sprite[i].flags &= ~7;
2441                  break;
2442              }
2443              if (pXSprite->Vector) sprite[i].cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
2444              if (pXSprite->Push) sprite[i].cstat |= 4096;
2445          }
2446      }
2447      
2448      evSend(0, 0, kChannelLevelStart, kCmdOn, kCauserGame);
2449      #ifdef NOONE_EXTENSIONS
2450          if (gModernMap)
2451          {
2452              #ifdef TRIGGER_START_CHANNEL_NBLOOD
2453                  evSend(0, 0, TRIGGER_START_CHANNEL_NBLOOD, kCmdOn, kCauserGame);
2454              #endif
2455  
2456              #ifdef TRIGGER_START_CHANNEL_RAZE
2457                   evSend(0, 0, TRIGGER_START_CHANNEL_RAZE, kCmdOn, kCauserGame);
2458              #endif
2459          }
2460      #endif
2461  
2462      switch (gGameOptions.nGameType) {
2463          case 1:
2464              evSend(0, 0, kChannelLevelStartCoop, kCmdOn, kCauserGame);
2465              break;
2466          case 2:
2467              evSend(0, 0, kChannelLevelStartMatch, kCmdOn, kCauserGame);
2468              break;
2469          case 3:
2470              evSend(0, 0, kChannelLevelStartMatch, kCmdOn, kCauserGame);
2471              evSend(0, 0, kChannelLevelStartTeamsOnly, kCmdOn, kCauserGame);
2472              break;
2473      }
2474  }
2475  
2476  void trTextOver(int nId)
2477  {
2478      char *pzMessage = levelGetMessage(nId);
2479      if (pzMessage)
2480          viewSetMessage(pzMessage, VanillaMode() ? 0 : 8, MESSAGE_PRIORITY_INI); // 8: gold
2481  }
2482  
2483  void InitGenerator(int nSprite)
2484  {
2485      dassert(nSprite < kMaxSprites);
2486      spritetype *pSprite = &sprite[nSprite];
2487      dassert(pSprite->statnum != kMaxStatus);
2488      int nXSprite = pSprite->extra;
2489      dassert(nXSprite > 0);
2490      XSPRITE *pXSprite = &xsprite[nXSprite];
2491      switch (sprite[nSprite].type) {
2492          case kGenTrigger:
2493              pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
2494              pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
2495              break;
2496      }
2497      if (pXSprite->state != pXSprite->restState && pXSprite->busyTime > 0)
2498          evPost(nSprite, 3, (120*(pXSprite->busyTime+Random2(pXSprite->data1)))/10, kCmdRepeat, nSprite);
2499  }
2500  
2501  void ActivateGenerator(int nSprite)
2502  {
2503      dassert(nSprite < kMaxSprites);
2504      spritetype *pSprite = &sprite[nSprite];
2505      dassert(pSprite->statnum != kMaxStatus);
2506      int nXSprite = pSprite->extra;
2507      dassert(nXSprite > 0);
2508      XSPRITE *pXSprite = &xsprite[nXSprite];
2509      switch (pSprite->type) {
2510          case kGenDripWater:
2511          case kGenDripBlood: {
2512              int top, bottom;
2513              GetSpriteExtents(pSprite, &top, &bottom);
2514              actSpawnThing(pSprite->sectnum, pSprite->x, pSprite->y, bottom, (pSprite->type == kGenDripWater) ? kThingDripWater : kThingDripBlood);
2515              break;
2516          }
2517          case kGenSound:
2518              sfxPlay3DSound(pSprite, pXSprite->data2, -1, 0);
2519              break;
2520          case kGenMissileFireball:
2521              switch (pXSprite->data2) {
2522                  case 0:
2523                      FireballTrapSeqCallback(3, nXSprite);
2524                      break;
2525                  case 1:
2526                      seqSpawn(35, 3, nXSprite, nFireballTrapClient);
2527                      break;
2528                  case 2:
2529                      seqSpawn(36, 3, nXSprite, nFireballTrapClient);
2530                      break;
2531              }
2532              break;
2533          case kGenMissileEctoSkull:
2534              break;
2535          case kGenBubble:
2536          case kGenBubbleMulti: {
2537              int top, bottom;
2538              GetSpriteExtents(pSprite, &top, &bottom);
2539              gFX.fxSpawn((pSprite->type == kGenBubble) ? FX_23 : FX_26, pSprite->sectnum, pSprite->x, pSprite->y, top);
2540              break;
2541          }
2542      }
2543  }
2544  
2545  void FireballTrapSeqCallback(int, int nXSprite)
2546  {
2547      XSPRITE *pXSprite = &xsprite[nXSprite];
2548      int nSprite = pXSprite->reference;
2549      spritetype *pSprite = &sprite[nSprite];
2550      if (pSprite->cstat&32)
2551          actFireMissile(pSprite, 0, 0, 0, 0, (pSprite->cstat&8) ? 0x4000 : -0x4000, kMissileFireball);
2552      else
2553          actFireMissile(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, kMissileFireball);
2554  }
2555  
2556  
2557  void MGunFireSeqCallback(int, int nXSprite)
2558  {
2559      int nSprite = xsprite[nXSprite].reference;
2560      spritetype *pSprite = &sprite[nSprite];
2561      XSPRITE *pXSprite = &xsprite[nXSprite];
2562      if (pXSprite->data2 > 0 || pXSprite->data1 == 0)
2563      {
2564          if (pXSprite->data2 > 0)
2565          {
2566              pXSprite->data2--;
2567              if (pXSprite->data2 == 0)
2568                  evPost(nSprite, 3, 1, kCmdOff, nSprite);
2569          }
2570          int dx = (Cos(pSprite->ang)>>16)+Random2(1000);
2571          int dy = (Sin(pSprite->ang)>>16)+Random2(1000);
2572          int dz = Random2(1000);
2573          if (gGameOptions.nHitscanProjectiles && !VanillaMode()) // if enemy hitscan projectiles are enabled, spawn bullet projectile
2574          {
2575              const int bakX = pSprite->x, bakY = pSprite->y;
2576              pSprite->x += mulscale14(missileInfo[kMissileBullet-kMissileBase].velocity, dx)>>14; // move origin away from attached wall so it doesn't collide on first tick (this happens with BB6 - twin fortress)
2577              pSprite->y += mulscale14(missileInfo[kMissileBullet-kMissileBase].velocity, dy)>>14;
2578              actFireMissile(pSprite, 0, 0, dx, dy, dz, kMissileBullet);
2579              pSprite->x = bakX, pSprite->y = bakY;
2580          }
2581          else
2582              actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorBullet);
2583          sfxPlay3DSound(pSprite, 359, -1, 0);
2584      }
2585  }
2586  
2587  void MGunOpenSeqCallback(int, int nXSprite)
2588  {
2589      seqSpawn(39, 3, nXSprite, nMGunFireClient);
2590  }
2591  
2592  class TriggersLoadSave : public LoadSave
2593  {
2594  public:
2595      virtual void Load();
2596      virtual void Save();
2597  };
2598  
2599  void TriggersLoadSave::Load()
2600  {
2601      Read(&gBusyCount, sizeof(gBusyCount));
2602      Read(gBusy, sizeof(gBusy));
2603      Read(basePath, sizeof(basePath));
2604  }
2605  
2606  void TriggersLoadSave::Save()
2607  {
2608      Write(&gBusyCount, sizeof(gBusyCount));
2609      Write(gBusy, sizeof(gBusy));
2610      Write(basePath, sizeof(basePath));
2611  }
2612  
2613  static TriggersLoadSave *myLoadSave;
2614  
2615  void TriggersLoadSaveConstruct(void)
2616  {
2617      myLoadSave = new TriggersLoadSave();
2618  }