#include #include #include "engine.h" #include "db.h" #include "globals.h" #include "eventq.h" #include "triggers.h" #include "debug4g.h" #include "sectorfx.h" #include "misc.h" #include "gameutil.h" #include "trig.h" #include "screen.h" #include "actor.h" #include "seq.h" #include "error.h" #include "names.h" #include "levels.h" #include "view.h" #include "sfx.h" #include "sound.h" // can be removed once sfx complete #define kRemoteDelay 12 // 0.1 second #define kProxDelay 30 // 0.25 second static struct { long x, y; } kwall[kMaxWalls]; static struct { long x, y, z; } ksprite[kMaxSprites]; // trigger effects enum { kSeqClearGlass = 0x80, kSeqStainedGlass, kSeqWeb, kSeqBeam, kSeqJar, kSeqWaterOff, kSeqWaterOn, kSeqSpike, kSeqMetalGrate1, kSeqFireTrap1, kSeqFireTrap2, kSeqPadlock, kSeqMGunOpen, kSeqMGunFire, kSeqMGunClose, kSeqMGunFlare, kSeqTriggerMax, }; /******************************************************************************* DOOR STATES OFF means closed ON means open Doors should be created in map edit in the open position, since it is easier to computationally determine closed than open. The trInit function should set the initial positions of the doors based on their state flags. *******************************************************************************/ /******************************************************************************* Busy System Constants *******************************************************************************/ enum { kBusyOk = 0, kBusyRestore, kBusyReverse, kBusyComplete, }; typedef int (*BUSYPROC)( int nIndex, int nBusy ); struct BUSY { int nIndex; int nDelta; int nBusy; BUSYPROC busyProc; }; #define kMaxBusyArray 128 int gBusyCount = 0; BUSY gBusy[kMaxBusyArray]; /******************************************************************************* LOCAL FUNCTION PROTOTYPES *******************************************************************************/ static void InitGenerator( int nSprite ); static void ActivateGenerator( int nSprite ); static void FireballTrapCallback( int /* type */, int nXIndex ); static void MGunFireCallback( int /* type */, int nXIndex ); static void MGunOpenCallback( int /* type */, int nXIndex ); /******************************************************************************* LOCAL HELPER FUNCTIONS *******************************************************************************/ /******************************************************************************* FUNCTION: Lin2Sin() DESCRIPTION: Convert a linear busy ramp to a sinusoidal ramp *******************************************************************************/ inline int Lin2Sin( int nBusy ) { return (1 << 15) - (Cos(nBusy * kAngle180 / kMaxBusyValue) >> 15); } /******************************************************************************* FUNCTION: SetSpriteState() DESCRIPTION: Change the state of an xsprite, and broadcast message if necessary. *******************************************************************************/ static void SetSpriteState( int nSprite, XSPRITE *pXSprite, int state ) { if ( (pXSprite->busy & kFluxMask) == 0 && pXSprite->state == state ) return; pXSprite->busy = state << 16; pXSprite->state = state; if ( state != pXSprite->restState && pXSprite->waitTime > 0 ) evPost(nSprite, SS_SPRITE, pXSprite->waitTime * kTimerRate / 10, pXSprite->restState ? kCommandOn : kCommandOff); if (pXSprite->command == kCommandLink) return; if ( pXSprite->txID != 0 ) { if (pXSprite->triggerOn && pXSprite->state == 1) { dprintf( "evSend(%i, SS_SPRITE, %i, %i)\n", nSprite, pXSprite->txID, pXSprite->command ); evSend(nSprite, SS_SPRITE, pXSprite->txID, pXSprite->command); } if (pXSprite->triggerOff && pXSprite->state == 0) { dprintf( "evSend(%i, SS_SPRITE, %i, %i)\n", nSprite, pXSprite->txID, pXSprite->command ); evSend(nSprite, SS_SPRITE, pXSprite->txID, pXSprite->command); } } } /******************************************************************************* FUNCTION: SetWallState() DESCRIPTION: Change the state of an xwall, and broadcast message if necessary. *******************************************************************************/ static void SetWallState( int nWall, XWALL *pXWall, int state ) { if ( (pXWall->busy & kFluxMask) == 0 && pXWall->state == state ) return; pXWall->busy = state << 16; pXWall->state = state; if ( state != pXWall->restState && pXWall->waitTime > 0 ) evPost(nWall, SS_WALL, pXWall->waitTime * kTimerRate / 10, pXWall->restState ? kCommandOn : kCommandOff); if (pXWall->command == kCommandLink) return; if ( pXWall->txID != 0 ) { if (pXWall->triggerOn && pXWall->state == 1) { dprintf( "evSend(%i,SS_WALL, %i, %i)\n", nWall, pXWall->txID, pXWall->command ); evSend(nWall, SS_WALL, pXWall->txID, pXWall->command); } if (pXWall->triggerOff && pXWall->state == 0) { dprintf( "evSend(%i,SS_WALL, %i, %i)\n", nWall, pXWall->txID, pXWall->command ); evSend(nWall, SS_WALL, pXWall->txID, pXWall->command); } } } /******************************************************************************* FUNCTION: SetSectorState() DESCRIPTION: Change the state of an xsector, and broadcast message if necessary. *******************************************************************************/ static void SetSectorState( int nSector, XSECTOR *pXSector, int state ) { if ( (pXSector->busy & kFluxMask) == 0 && pXSector->state == state ) return; pXSector->busy = state << 16; pXSector->state = state; if ( state != pXSector->restState && pXSector->waitTime > 0 ) evPost(nSector, SS_SECTOR, pXSector->waitTime * kTimerRate / 10, pXSector->restState ? kCommandOn : kCommandOff); if (pXSector->command == kCommandLink) return; if ( pXSector->txID != 0 ) { if (pXSector->triggerOn && pXSector->state == 1) { dprintf( "evSend(%i, SS_SECTOR, %i, %i)\n", nSector, pXSector->txID, pXSector->command ); evSend(nSector, SS_SECTOR, pXSector->txID, pXSector->command); } if (pXSector->triggerOff && pXSector->state == 0) { dprintf( "evSend(%i, SS_SECTOR, %i, %i)\n", nSector, pXSector->txID, pXSector->command ); evSend(nSector, SS_SECTOR, pXSector->txID, pXSector->command); } } } /******************************************************************************* FUNCTION: AddBusy() DESCRIPTION: Add a busy process. The callback procedure will be called once per frame with the updated busy value. PARAMETERS: nIndex is used to identify the xobject to the callback function. NOTES: if nDelta is < 0, the busy value will count down from kMaxBusyValue. *******************************************************************************/ static void AddBusy( int nIndex, BUSYPROC busyProc, int nDelta ) { dassert(nDelta != 0); dassert(busyProc != NULL); // find an existing nIndex busy, or an unused busy slot for (int i = 0; i < gBusyCount; i++) { if ((nIndex == gBusy[i].nIndex) && (busyProc == gBusy[i].busyProc)) break; } // adding a new busy? if (i == gBusyCount) { if ( gBusyCount == kMaxBusyArray ) { dprintf("OVERFLOW: AddBusy() ignored\n"); return; }; gBusy[i].nIndex = nIndex; gBusy[i].busyProc = busyProc; gBusy[i].nBusy = (nDelta > 0) ? 0 : kMaxBusyValue; gBusyCount++; } gBusy[i].nDelta = nDelta; } /******************************************************************************* FUNCTION: ReverseBusy() DESCRIPTION: Reverse the delta of a busy process. This is used to change the direction of a process from outside the callback. *******************************************************************************/ void ReverseBusy( int nIndex, BUSYPROC busyProc ) { dassert(busyProc != NULL); // find an existing nIndex busy, or an unused busy slot for (int i = 0; i < gBusyCount; i++) { if ( nIndex == gBusy[i].nIndex && busyProc == gBusy[i].busyProc ) { gBusy[i].nDelta = -gBusy[i].nDelta; return; } } dprintf("ReverseBusy: matching busy not found!\n"); } /******************************************************************************* FUNCTION: GetSourceBusy() DESCRIPTION: Get the busy value of the object that originated the event. This is used for kCommandLink messages. *******************************************************************************/ static unsigned GetSourceBusy( EVENT event ) { int nXIndex; switch ( event.type ) { case SS_SECTOR: nXIndex = sector[event.index].extra; dassert(nXIndex > 0 && nXIndex < kMaxXSectors); return xsector[nXIndex].busy; case SS_WALL: nXIndex = wall[event.index].extra; dassert(nXIndex > 0 && nXIndex < kMaxXWalls); return xwall[nXIndex].busy; case SS_SPRITE: nXIndex = sprite[event.index].extra; dassert(nXIndex > 0 && nXIndex < kMaxXSprites); return xsprite[nXIndex].busy; } // shouldn't reach this point return 0; } /******************************************************************************* FUNCTION: OperateSprite() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void OperateSprite( int nSprite, XSPRITE *pXSprite, EVENT event ) { SPRITE *pSprite = &sprite[nSprite]; // handle respawns first, to avoid toggling the sprite state if ( event.command == kCommandRespawn) { actRespawnSprite( (short)nSprite ); switch (pSprite->type) { case kThingTNTBarrel: pSprite->cstat |= kSpriteBlocking | kSpriteHitscan; break; } return; } // special handling for lock/unlock commands switch ( event.command ) { case kCommandLock: pXSprite->locked = 1; return; case kCommandUnlock: pXSprite->locked = 0; return; case kCommandToggleLock: pXSprite->locked ^= 1; return; } switch (pSprite->type) { case kMissileStarburstFlare: { if ( sprite[nSprite].statnum != kStatMissile) // don't trigger if already exploded break; pSprite->type = kMissileExplodingFlare; int nVel = (M2X(2.0) << 4) / kTimerRate; // meters per second int nAngle = getangle(pSprite->xvel, pSprite->yvel); for (int i = 0; i < 16; i++) { int nBurst = actCloneSprite( pSprite ); dbInsertXSprite( nBurst ); long dxVel = 0; long dyVel = mulscale30r(nVel, Cos(i * kAngle360 / 16)); long dzVel = mulscale30r(nVel, Sin(i * kAngle360 / 16)); if (i & 1) { dyVel >>= 1; dzVel >>= 1; } RotateVector( &dxVel, &dyVel, nAngle ); SPRITE *pBurst = &sprite[nBurst]; pBurst->xvel += dxVel; pBurst->yvel += dyVel; pBurst->zvel += dzVel; } break; } // case kMissileExplodingFlare: case kMissileFlare: actPostSprite( nSprite, kStatFree ); break; case kThingMachineGun: if (pXSprite->health > 0) { if ( event.command == kCommandOn ) { if (pXSprite->state == 0) { SetSpriteState(nSprite, pXSprite, 1); seqSpawn(kSeqMGunOpen, SS_SPRITE, pSprite->extra, MGunOpenCallback); //sfxStart3DSound(pSprite->extra, kSfxSwitch2); // data1 = max ammo (defaults to infinite) // data2 = dynamic ammo if data1 > 0 if (pXSprite->data1 > 0) pXSprite->data2 = pXSprite->data1; } } else if ( event.command == kCommandOff ) { if (pXSprite->state) { SetSpriteState(nSprite, pXSprite, 0); seqSpawn(kSeqMGunClose, SS_SPRITE, pSprite->extra); //sfxStart3DSound(pSprite->extra, kSfxSwitch2); } } } break; case kThingWallCrack: SetSpriteState(nSprite, pXSprite, 0); actPostSprite( nSprite, kStatFree ); break; case kThingCrateFace: SetSpriteState(nSprite, pXSprite, 0); actPostSprite( nSprite, kStatFree ); break; case kTrapPoweredZap: switch( event.command ) { case kCommandOff: dprintf("zap off\n"); pXSprite->state = 0; pSprite->cstat |= kSpriteInvisible; pSprite->cstat &= ~kSpriteBlocking; break; case kCommandOn: dprintf("zap on\n"); pXSprite->state = 1; pSprite->cstat &= ~kSpriteInvisible; pSprite->cstat |= kSpriteBlocking; break; case kCommandToggle: dprintf("zap toggle\n"); pXSprite->state ^= 1; pSprite->cstat ^= kSpriteInvisible; pSprite->cstat ^= kSpriteBlocking; break; } break; case kSwitchPadlock: switch( event.command ) { case kCommandOff: SetSpriteState(nSprite, pXSprite, 0); break; case kCommandOn: SetSpriteState(nSprite, pXSprite, 1); seqSpawn(kSeqPadlock, SS_SPRITE, pSprite->extra); sfxStart3DSound(pSprite->extra, kSfxChains); break; default: SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1); if (pXSprite->state) { seqSpawn(kSeqPadlock, SS_SPRITE, pSprite->extra); sfxStart3DSound(pSprite->extra, kSfxChains); } break; } break; case kSwitchMomentary: switch( event.command ) { case kCommandOff: SetSpriteState(nSprite, pXSprite, 0); break; case kCommandOn: SetSpriteState(nSprite, pXSprite, 1); break; default: SetSpriteState(nSprite, pXSprite, pXSprite->restState ^ 1); break; } sfxStart3DSound(pSprite->extra, kSfxSwitch2); break; case kSwitchCombination: switch( event.command ) { case kCommandOff: if (--pXSprite->data1 < 0) // underflow? pXSprite->data1 += pXSprite->data3; break; default: if (++pXSprite->data1 >= pXSprite->data3) // overflow? pXSprite->data1 -= pXSprite->data3; break; } // handle master switches if ( pXSprite->command == kCommandLink && pXSprite->txID != 0 ) evSend(nSprite, SS_SPRITE, pXSprite->txID, kCommandLink); // at right combination? if (pXSprite->data1 == pXSprite->data2) SetSpriteState(nSprite, pXSprite, 1); else SetSpriteState(nSprite, pXSprite, 0); break; case kThingTNTStick: case kThingTNTBundle: case kThingTNTBarrel: if ( sprite[nSprite].statnum != kStatRespawn ) // don't trigger if waiting to be respawned! actExplodeSprite((short)nSprite); break; case kThingTNTRemArmed: if ( sprite[nSprite].statnum == kStatRespawn ) break; switch ( event.command ) { case kCommandOn: sfxStart3DSound(pSprite->extra, kSfxTNTDetRemote); evPost(nSprite, SS_SPRITE, kRemoteDelay); break; default: actExplodeSprite((short)nSprite); break; } break; case kThingTNTProxArmed: if ( sprite[nSprite].statnum == kStatRespawn ) break; switch ( event.command ) { case kCommandSpriteProximity: if ( pXSprite->state ) // don't trigger it if already triggered break; sfxStart3DSound(pSprite->extra, kSfxTNTDetProx); evPost(nSprite, SS_SPRITE, kProxDelay, kCommandOff); pXSprite->state = 1; break; case kCommandCallback: sfxStart3DSound(pSprite->extra, kSfxTNTArmProx); actPostSprite(nSprite, kStatProximity); // activate it by changing list break; default: actExplodeSprite((short)nSprite); break; } break; case kGenTrigger: case kGenWaterDrip: case kGenBloodDrip: case kGenFireball: case kGenEctoSkull: case kGenDart: case kGenSound: switch( event.command ) { case kCommandCallback: pXSprite->data3 = FALSE; // prevent callback reentrancy if ( pXSprite->state ) { if ( pSprite->type != kGenTrigger ) ActivateGenerator( nSprite ); if ( pXSprite->txID != 0 ) evSend(nSprite, SS_SPRITE, pXSprite->txID, pXSprite->command); if ( pXSprite->busyTime > 0 ) { pXSprite->data3 = TRUE; evPost(nSprite, SS_SPRITE, (pXSprite->busyTime + BiRandom(pXSprite->data1)) * kTimerRate / 10); } } break; case kCommandOff: SetSpriteState(nSprite, pXSprite, 0); break; default: if ( event.command == kCommandOn ) SetSpriteState(nSprite, pXSprite, 1); else SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1); if ( pXSprite->state && pXSprite->data3 == FALSE ) { pXSprite->data3 = TRUE; evPost(nSprite, SS_SPRITE, 0); // trigger immediately } break; } break; default: switch( event.command ) { case kCommandOff: SetSpriteState(nSprite, pXSprite, 0); break; case kCommandOn: SetSpriteState(nSprite, pXSprite, 1); break; default: SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1); break; } break; } } /******************************************************************************* FUNCTION: OperateWall() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void OperateWall( int nWall, XWALL *pXWall, EVENT event ) { WALL *pWall = &wall[nWall]; // special handling for lock/unlock commands switch ( event.command ) { case kCommandLock: pXWall->locked = 1; return; case kCommandUnlock: pXWall->locked = 0; return; case kCommandToggleLock: pXWall->locked ^= 1; return; } switch (pWall->type) { case kWallClearGlass: if ( pXWall->state == 0 ) { SetWallState(nWall, pXWall, 1); seqSpawn(kSeqClearGlass, SS_MASKED, wall[nWall].extra); sndStartSample( "glasshit2", 64 ); } break; case kWallStainedGlass: if ( pXWall->state == 0 ) { SetWallState(nWall, pXWall, 1); seqSpawn(kSeqStainedGlass, SS_MASKED, wall[nWall].extra); sndStartSample( "glasshit2", 64 ); } break; case kWallWoodBeams: if ( pXWall->state == 0 ) { SetWallState(nWall, pXWall, 1); seqSpawn(kSeqBeam, SS_MASKED, wall[nWall].extra); } break; case kWallWeb: if ( pXWall->state == 0 ) { SetWallState(nWall, pXWall, 1); seqSpawn(kSeqWeb, SS_MASKED, wall[nWall].extra); } break; case kWallMetalGrate1: if ( pXWall->state == 0 ) { SetWallState(nWall, pXWall, 1); seqSpawn(kSeqMetalGrate1, SS_MASKED, wall[nWall].extra); } break; default: switch( event.command ) { case kCommandOff: SetWallState(nWall, pXWall, 0); break; case kCommandOn: SetWallState(nWall, pXWall, 1); break; default: SetWallState(nWall, pXWall, pXWall->state ^ 1); break; } break; } } void TranslateSector( int nSector, int i0, int i1, int x0, int y0, int a0, int x1, int y1, int a1, BOOL fAllWalls ) { XSECTOR *pXSector = &xsector[sector[nSector].extra]; int vX = x1 - x0; int vY = y1 - y0; int vA = a1 - a0; int Xi0 = mulscale16(i0, vX); int Xi1 = mulscale16(i1, vX); int dX = Xi1 - Xi0; int Yi0 = mulscale16(i0, vY); int Yi1 = mulscale16(i1, vY); int dY = Yi1 - Yi0; int Ai0 = mulscale16(i0, vA); int Ai1 = mulscale16(i1, vA); int dA = Ai1 - Ai0; long x, y; short nWall = sector[nSector].wallptr; if ( fAllWalls ) { for (int i = 0; i < sector[nSector].wallnum; nWall++, i++) { x = kwall[nWall].x; y = kwall[nWall].y; if ( Ai1 ) RotatePoint(&x, &y, Ai1, x0, y0); // move vertex dragpoint(nWall, x + Xi1, y + Yi1); } } else { for (int i = 0; i < sector[nSector].wallnum; nWall++, i++) { short nWall2 = wall[nWall].point2; x = kwall[nWall].x; y = kwall[nWall].y; if ( wall[nWall].cstat & kWallMoveForward ) { if ( Ai1 ) RotatePoint(&x, &y, Ai1, x0, y0); dragpoint(nWall, x + Xi1, y + Yi1); // move next vertex if not explicitly tagged if ( !(wall[nWall2].cstat & kWallMoveMask) ) { x = kwall[nWall2].x; y = kwall[nWall2].y; if ( Ai1 ) RotatePoint(&x, &y, Ai1, x0, y0); dragpoint(nWall2, x + Xi1, y + Yi1); } } else if ( wall[nWall].cstat & kWallMoveBackward ) { if ( Ai1 ) RotatePoint(&x, &y, -Ai1, x0, y0); dragpoint(nWall, x - Xi1, y - Yi1); // move next vertex if not explicitly tagged if ( !(wall[nWall2].cstat & kWallMoveMask) ) { x = kwall[nWall2].x; y = kwall[nWall2].y; if ( Ai1 ) RotatePoint(&x, &y, -Ai1, x0, y0); dragpoint(nWall2, x - Xi1, y - Yi1); } } } } for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; // don't move markers if ( pSprite->statnum == kStatMarker ) continue; x = ksprite[nSprite].x; y = ksprite[nSprite].y; if ( sprite[nSprite].cstat & kSpriteMoveForward ) { if ( Ai1 ) RotatePoint(&x, &y, Ai1, x0, y0); pSprite->ang = (short)((pSprite->ang + dA) & kAngleMask); pSprite->x = x + Xi1; pSprite->y = y + Yi1; } else if ( sprite[nSprite].cstat & kSpriteMoveReverse ) { if ( Ai1 ) RotatePoint(&x, &y, -Ai1, x0, y0); pSprite->ang = (short)((pSprite->ang - dA) & kAngleMask); pSprite->x = x - Xi1; pSprite->y = y - Yi1; } else if ( pXSector->drag ) { int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); if ( !(pSprite->cstat & kSpriteRMask) && zBot >= sector[nSector].floorz ) { // translate relatively (degenerative) if ( dA ) RotatePoint(&pSprite->x, &pSprite->y, dA, x0 + Xi0, y0 + Yi0); pSprite->ang = (short)((pSprite->ang + dA) & kAngleMask); pSprite->x += dX; pSprite->y += dY; } } } } /******************************************************************************* FUNCTION: GetHighestSprite() DESCRIPTION: Helper function to return the highest sprite with the specified statnum equal to nStatus in the sector specified by nSector. PARAMETERS: nSector = the sector to search nStatus = the status number to match, or kMaxStatus = ALL RETURNS: The sprite number with the highest Z, or -1 = none NOTES: *******************************************************************************/ int GetHighestSprite( int nSector, int nStatus, int *hiz ) { *hiz = sector[nSector].floorz; int retSprite = -1; for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { if ( sprite[nSprite].statnum == nStatus || nStatus == kMaxStatus) { SPRITE *pSprite = &sprite[nSprite]; int zUp, zDown; GetSpriteExtents(pSprite, &zUp, &zDown); if ( pSprite->z - zUp < *hiz ) { *hiz = pSprite->z - zUp; retSprite = nSprite; } } } return retSprite; } /******************************************************************************* FUNCTION: VCrushBusy() DESCRIPTION: Called by trProcessBusy : calculates the new floorz and ceilingz height based on the nBusy value PARAMETERS: RETURNS: NOTES: This busy proc works for lifts, Top doors, Bottom doors, and HSplit doors *******************************************************************************/ int VCrushBusy( int nSector, int nBusy ) { dassert( nSector >= 0 && nSector < numsectors); int nXSector = sector[nSector]. extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; int newCeilZ, newFloorZ; int dCeilZ = pXSector->onCeilZ - pXSector->offCeilZ; if ( dCeilZ ) newCeilZ = pXSector->offCeilZ + mulscale16(dCeilZ, Lin2Sin(nBusy)); int dFloorZ = pXSector->onFloorZ - pXSector->offFloorZ; if ( dFloorZ ) newFloorZ = pXSector->offFloorZ + mulscale16(dFloorZ, Lin2Sin(nBusy)); int z; int nSprite = GetHighestSprite( nSector, kStatDude, &z ); if ( nSprite >= 0 && newCeilZ >= z ) return kBusyRestore; if (dCeilZ) sector[nSector].ceilingz = newCeilZ; if (dFloorZ) sector[nSector].floorz = newFloorZ; pXSector->busy = nBusy; if ( pXSector->command == kCommandLink && pXSector->txID != 0 ) evSend(nSector, SS_SECTOR, pXSector->txID, kCommandLink); if ( (nBusy & kFluxMask) == 0 ) { SetSectorState(nSector, pXSector, nBusy >> 16); return kBusyComplete; } return kBusyOk; } /******************************************************************************* FUNCTION: VSpriteBusy() DESCRIPTION: Called by trProcessBusy : calculates the new sprite z based on the nBusy value PARAMETERS: RETURNS: NOTES: This busy proc works for z motion sprites only. The sector floor and ceiling heights are unaffected. *******************************************************************************/ int VSpriteBusy( int nSector, int nBusy ) { dassert( nSector >= 0 && nSector < numsectors); int nXSector = sector[nSector]. extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; int floorZRange = pXSector->onFloorZ - pXSector->offFloorZ; if ( floorZRange != 0 ) { // adjust the z for any floor relative sprites or face sprites in the floor for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->cstat & kSpriteMoveFloor ) pSprite->z = ksprite[nSprite].z + mulscale16(floorZRange, Lin2Sin(nBusy)); } } int ceilZRange = pXSector->onCeilZ - pXSector->offCeilZ; if ( ceilZRange != 0 ) { // adjust the z for any ceiling relative sprites or face sprites in the ceiling for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->cstat & kSpriteMoveCeiling ) pSprite->z = ksprite[nSprite].z + mulscale16(ceilZRange, Lin2Sin(nBusy)); } } pXSector->busy = nBusy; if ( pXSector->command == kCommandLink && pXSector->txID != 0 ) evSend(nSector, SS_SECTOR, pXSector->txID, kCommandLink); if ( (nBusy & kFluxMask) == 0 ) { SetSectorState(nSector, pXSector, nBusy >> 16); return kBusyComplete; } return kBusyOk; } /******************************************************************************* FUNCTION: VDoorBusy() DESCRIPTION: Called by trProcessBusy : calculates the new floorz and ceilingz height based on the nBusy value PARAMETERS: RETURNS: NOTES: This busy proc works for lifts, Top doors, Bottom doors, and HSplit doors *******************************************************************************/ int VDoorBusy( int nSector, int nBusy ) { dassert( nSector >= 0 && nSector < numsectors); int nXSector = sector[nSector]. extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; int floorZRange = pXSector->onFloorZ - pXSector->offFloorZ; if ( floorZRange != 0 ) { int oldFloorZ = sector[nSector].floorz; sector[nSector].floorz = pXSector->offFloorZ + mulscale16(floorZRange, Lin2Sin(nBusy)); // adjust the z for any floor relative sprites or face sprites in the floor for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); if ( pSprite->cstat & kSpriteMoveFloor || ( !(pSprite->cstat & kSpriteRMask) && zBot >= oldFloorZ) ) pSprite->z += sector[nSector].floorz - oldFloorZ; } } int ceilZRange = pXSector->onCeilZ - pXSector->offCeilZ; if ( ceilZRange != 0 ) { int oldCeilZ = sector[nSector].ceilingz; sector[nSector].ceilingz = pXSector->offCeilZ + mulscale16(ceilZRange, Lin2Sin(nBusy)); // adjust the z for any ceiling relative sprites or face sprites in the ceiling for (short nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); if ( pSprite->cstat & kSpriteMoveCeiling || ( !(pSprite->cstat & kSpriteRMask) && zTop <= oldCeilZ) ) pSprite->z += sector[nSector].ceilingz - oldCeilZ; } } pXSector->busy = nBusy; if ( pXSector->command == kCommandLink && pXSector->txID != 0 ) evSend(nSector, SS_SECTOR, pXSector->txID, kCommandLink); if ( (nBusy & kFluxMask) == 0 ) { SetSectorState(nSector, pXSector, nBusy >> 16); return kBusyComplete; } return kBusyOk; } /******************************************************************************* FUNCTION: HDoorBusy() DESCRIPTION: Called by trProcessBusy : calculates the new wall x,y based on the nBusy value RETURNS: NOTES: This busy proc works for slide doors *******************************************************************************/ int HDoorBusy( int nSector, int nBusy ) { dassert( nSector >= 0 && nSector < numsectors); SECTOR *pSector = §or[nSector]; int nXSector = pSector->extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; SPRITE *pMark0 = &sprite[pXSector->marker0]; SPRITE *pMark1 = &sprite[pXSector->marker1]; TranslateSector(nSector, Lin2Sin(pXSector->busy), Lin2Sin(nBusy), pMark0->x, pMark0->y, pMark0->ang, pMark1->x, pMark1->y, pMark1->ang, pSector->type == kSectorSlide); // SlideSector(nSector, mulscale16(dx, nAmount), mulscale16(dy, nAmount)); pXSector->busy = nBusy; if ( pXSector->command == kCommandLink && pXSector->txID != 0 ) evSend(nSector, SS_SECTOR, pXSector->txID, kCommandLink); if ( (nBusy & kFluxMask) == 0 ) { SetSectorState(nSector, pXSector, nBusy >> 16); return kBusyComplete; } return kBusyOk; } /******************************************************************************* FUNCTION: RDoorBusy() DESCRIPTION: Called by trProcessBusy : calculates the new rotating door wall angles based on the nBusy value. PARAMETERS: RETURNS: NOTES: *******************************************************************************/ int RDoorBusy( int nSector, int nBusy ) { dassert( nSector >= 0 && nSector < numsectors); SECTOR *pSector = §or[nSector]; int nXSector = pSector->extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; SPRITE *pMark = &sprite[pXSector->marker0]; TranslateSector(nSector, Lin2Sin(pXSector->busy), Lin2Sin(nBusy), pMark->x, pMark->y, 0, pMark->x, pMark->y, pMark->ang, pSector->type == kSectorRotate); pXSector->busy = nBusy; if ( pXSector->command == kCommandLink && pXSector->txID != 0 ) evSend(nSector, SS_SECTOR, pXSector->txID, kCommandLink); if ( (nBusy & kFluxMask) == 0 ) { SetSectorState(nSector, pXSector, nBusy >> 16); return kBusyComplete; } return kBusyOk; } /******************************************************************************* FUNCTION: OperateDoor() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void OperateDoor( int nSector, EVENT event, BUSYPROC busyProc ) { dassert( nSector >= 0 && nSector < numsectors); int nXSector = sector[nSector].extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; int nDelta = kMaxBusyValue / ClipLow(pXSector->busyTime * kTimerRate / 10, 1); switch (event.command) { case kCommandOff: if ( pXSector->busy != 0x0000 ) AddBusy(nSector, busyProc, -nDelta); break; case kCommandOn: if ( pXSector->busy != 0x10000 ) AddBusy(nSector, busyProc, nDelta); break; default: if ( (pXSector->busy & kFluxMask) && pXSector->interruptable ) ReverseBusy(nSector, busyProc); else AddBusy(nSector, busyProc, pXSector->state ? -nDelta : nDelta); break; } } /******************************************************************************* FUNCTION: OperateTeleport() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void OperateTeleport( int nSector, EVENT event ) { dassert( nSector >= 0 && nSector < numsectors ); int nXSector = sector[nSector].extra; dassert( nXSector > 0 && nXSector < kMaxXSectors ); XSECTOR *pXSector = &xsector[nXSector]; BOOL bChanged = FALSE; switch (event.command) { case kCommandOff: bChanged = (pXSector->state == 1); SetSectorState(nSector, pXSector, 0); break; case kCommandOn: bChanged = (pXSector->state == 0); SetSectorState(nSector, pXSector, 1); break; default: bChanged = (pXSector->state == pXSector->restState); SetSectorState(nSector, pXSector, pXSector->restState ^ 1); break; } if ( bChanged && (pXSector->state != pXSector->restState) ) { int nDest = pXSector->marker0; dassert( nDest < kMaxSprites ); SPRITE *pDest = &sprite[nDest]; dassert( pDest->statnum == kStatMarker); dassert( pDest->type == kMarkerWarpDest ); for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->statnum == kStatDude ) { pSprite->x = pDest->x; pSprite->y = pDest->y; pSprite->z += sector[pDest->sectnum].floorz - sector[nSector].floorz; pSprite->ang = pDest->ang; changespritesect( (short)nSprite, pDest->sectnum ); if (pSprite->type >= kDudePlayer1 && pSprite->type <= kDudePlayer8) { // fix backup position for interpolation to work properly viewBackupPlayerLoc(pSprite->type - kDudePlayer1); } } } } } /******************************************************************************* FUNCTION: OperateSector() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void OperateSector( int nSector, XSECTOR *pXSector, EVENT event ) { SECTOR *pSector = §or[nSector]; // special handling for lock/unlock commands switch ( event.command ) { case kCommandLock: pXSector->locked = 1; return; case kCommandUnlock: pXSector->locked = 0; return; case kCommandToggleLock: pXSector->locked ^= 1; return; } switch ( pSector->type) { case kSectorZSprite: OperateDoor(nSector, event, VSpriteBusy); break; case kSectorZMotion: OperateDoor(nSector, event, VDoorBusy); break; case kSectorZCrusher: OperateDoor(nSector, event, VCrushBusy); break; case kSectorSlideMarked: case kSectorSlide: case kSectorSlideCrush: OperateDoor(nSector, event, HDoorBusy); break; case kSectorRotateMarked: case kSectorRotate: case kSectorRotateCrush: OperateDoor(nSector, event, RDoorBusy); break; case kSectorTeleport: OperateTeleport( nSector, event ); break; default: switch( event.command ) { case kCommandOff: SetSectorState(nSector, pXSector, 0); break; case kCommandOn: SetSectorState(nSector, pXSector, 1); break; case kCommandToggle: case kCommandSectorPush: case kCommandSectorImpact: case kCommandSectorEnter: case kCommandSectorExit: SetSectorState(nSector, pXSector, pXSector->state ^ 1); break; } break; } } /******************************************************************************* FUNCTION: LinkSector() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void LinkSector( int nSector, XSECTOR *pXSector, EVENT event ) { SECTOR *pSector = §or[nSector]; int nBusy = GetSourceBusy(event); switch ( pSector->type ) { case kSectorZSprite: VSpriteBusy( nSector, nBusy ); break; case kSectorZMotion: VDoorBusy( nSector, nBusy ); break; case kSectorZCrusher: VCrushBusy( nSector, nBusy ); break; case kSectorSlide: case kSectorSlideMarked: case kSectorSlideCrush: HDoorBusy( nSector, nBusy ); break; case kSectorRotate: case kSectorRotateMarked: case kSectorRotateCrush: RDoorBusy( nSector, nBusy ); break; default: pXSector->busy = nBusy; if ( (nBusy & kFluxMask) == 0 ) SetSectorState(nSector, pXSector, nBusy >> 16); break; } } /******************************************************************************* FUNCTION: LinkSprite() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void LinkSprite( int nSprite, XSPRITE *pXSprite, EVENT event ) { SPRITE *pSprite = &sprite[nSprite]; int nBusy = GetSourceBusy(event); switch ( pSprite->type ) { case kSwitchCombination: // should only be linked to a master switch if (event.type == SS_SPRITE) { int nXSprite2 = sprite[event.index].extra; dassert(nXSprite2 > 0 && nXSprite2 < kMaxXSprites); // get master switch selection pXSprite->data1 = xsprite[nXSprite2].data1; // at right combination? if (pXSprite->data1 == pXSprite->data2) SetSpriteState(nSprite, pXSprite, 1); else SetSpriteState(nSprite, pXSprite, 0); } break; default: pXSprite->busy = nBusy; if ( (nBusy & kFluxMask) == 0 ) SetSpriteState(nSprite, pXSprite, nBusy >> 16); break; } } /******************************************************************************* FUNCTION: LinkWall() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void LinkWall( int nWall, XWALL *pXWall, EVENT event ) { WALL *pWall = &wall[nWall]; int nBusy = GetSourceBusy(event); switch ( pWall->type ) { default: pXWall->busy = nBusy; if ( (nBusy & kFluxMask) == 0 ) SetWallState(nWall, pXWall, nBusy >> 16); break; } } /******************************************************************************* EXPORTED FUNCTIONS *******************************************************************************/ /******************************************************************************* FUNCTION: trTriggerSector() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trTriggerSector( unsigned nSector, XSECTOR *pXSector, int command ) { dprintf("TriggerSector: nSector=%i, command=%i\n", nSector, command); // bypass locked XObjects if ( pXSector->locked ) return; // bypass triggered one-shots if ( pXSector->isTriggered ) return; if ( pXSector->triggerOnce ) pXSector->isTriggered = 1; if ( pXSector->decoupled ) { if ( pXSector->txID != 0 ) evSend(nSector, SS_SECTOR, pXSector->txID, pXSector->command); } else { EVENT event; event.command = command; // operate the sector OperateSector( nSector, pXSector, event ); } } /******************************************************************************* FUNCTION: trMessageSector() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trMessageSector( unsigned nSector, EVENT event ) { dassert(sector[nSector].extra > 0 && sector[nSector].extra < kMaxXSectors); XSECTOR *pXSector = &xsector[sector[nSector].extra]; // don't send it to the originator /* if (event.type == SS_SECTOR && event.index == nSector && !pXSector->decoupled && event.command != kCommandCallback ) return; */ // operate the sector if ( event.command == kCommandLink ) LinkSector( nSector, pXSector, event ); else OperateSector( nSector, pXSector, event ); } /******************************************************************************* FUNCTION: trTriggerWall() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trTriggerWall( unsigned nWall, XWALL *pXWall, int command ) { dprintf("TriggerWall: nWall=%i, command=%i\n", nWall, command); // bypass locked XObjects if ( pXWall->locked ) return; // bypass triggered one-shots if ( pXWall->isTriggered ) return; if ( pXWall->triggerOnce ) pXWall->isTriggered = 1; if ( pXWall->decoupled ) { if ( pXWall->txID != 0 ) evSend(nWall, SS_WALL, pXWall->txID, pXWall->command); } else { EVENT event; event.command = command; // operate the wall OperateWall(nWall, pXWall, event); } } /******************************************************************************* FUNCTION: trMessageWall() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trMessageWall( unsigned nWall, EVENT event ) { dassert(wall[nWall].extra > 0 && wall[nWall].extra < kMaxXWalls); XWALL *pXWall = &xwall[wall[nWall].extra]; // don't send it to the originator /* if (event.type == SS_WALL && event.index == nWall && !pXWall->decoupled && event.command != kCommandCallback ) return; */ // operate the wall if ( event.command == kCommandLink ) LinkWall( nWall, pXWall, event ); else OperateWall( nWall, pXWall, event ); } /******************************************************************************* FUNCTION: trTriggerSprite() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trTriggerSprite( unsigned nSprite, XSPRITE *pXSprite, int command ) { dprintf("TriggerSprite: nSprite=%i, type= %i command=%i\n", nSprite, sprite[nSprite].type, command); // bypass locked XObjects if ( pXSprite->locked ) return; // bypass triggered one-shots if ( pXSprite->isTriggered ) return; if ( pXSprite->triggerOnce ) pXSprite->isTriggered = 1; if ( pXSprite->decoupled ) { if ( pXSprite->txID != 0 ) evSend(nSprite, SS_SPRITE, pXSprite->txID, pXSprite->command); } else { EVENT event; event.command = command; // operate the sprite OperateSprite(nSprite, pXSprite, event); } } /******************************************************************************* FUNCTION: trMessageSprite() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trMessageSprite( unsigned nSprite, EVENT event ) { // return immediately if sprite has been deleted if ( sprite[nSprite].statnum == kMaxStatus ) return; dassert(sprite[nSprite].extra > 0 && sprite[nSprite].extra < kMaxXSprites); XSPRITE *pXSprite = &xsprite[sprite[nSprite].extra]; // don't send it to the originator /* if (event.type == SS_SPRITE && event.index == nSprite && !pXSprite->decoupled && event.command != kCommandCallback ) return; */ // operate the sprite if ( event.command == kCommandLink ) LinkSprite( nSprite, pXSprite, event ); else OperateSprite( nSprite, pXSprite, event ); } /******************************************************************************* FUNCTION: trProcessBusy() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trProcessBusy( void ) { int i; for ( i = gBusyCount - 1; i >= 0; i-- ) { // temporarily store nBusy before normalization int tempBusy = gBusy[i].nBusy; gBusy[i].nBusy = ClipRange(gBusy[i].nBusy + gBusy[i].nDelta * kFrameTicks, 0, kMaxBusyValue); int rcode = gBusy[i].busyProc( gBusy[i].nIndex, gBusy[i].nBusy ); switch (rcode) { case kBusyRestore: gBusy[i].nBusy = tempBusy; // restore previous nBusy break; case kBusyReverse: gBusy[i].nBusy = tempBusy; // restore previous nBusy gBusy[i].nDelta = -gBusy[i].nDelta; // reverse delta break; case kBusyComplete: gBusyCount--; gBusy[i] = gBusy[gBusyCount]; break; } } } /******************************************************************************* FUNCTION: trInit() DESCRIPTION: Initialize the trigger system. PARAMETERS: RETURNS: NOTES: Called in prepareboard, AFTER call to dbLoadMap *******************************************************************************/ void trInit( void ) { int i; int nSector, nWall, nSprite; gBusyCount = 0; // get wall vertice positions for (nWall = 0; nWall < numwalls; nWall++) { kwall[nWall].x = wall[nWall].x; kwall[nWall].y = wall[nWall].y; } // get sprite positions for ( nSprite = 0; nSprite < kMaxSprites; nSprite++ ) { if ( sprite[nSprite].statnum < kMaxStatus ) { ksprite[nSprite].x = sprite[nSprite].x; ksprite[nSprite].y = sprite[nSprite].y; ksprite[nSprite].z = sprite[nSprite].z; } } // init wall trigger masks (must be done first) for (nWall = 0; nWall < numwalls; nWall++) { if ( wall[nWall].extra > 0 ) { int nXWall = wall[nWall].extra; dassert(nXWall < kMaxXWalls); XWALL *pXWall = &xwall[nXWall]; if ( pXWall->state ) pXWall->busy = kMaxBusyValue; switch( wall[nWall].type ) { default: break; } } } // init sector trigger masks dassert((numsectors >= 0) && (numsectors < kMaxSectors)); for ( nSector = 0; nSector < numsectors; nSector++ ) { SECTOR *pSector = §or[nSector]; int nXSector = pSector->extra; if ( nXSector > 0 ) { dassert(nXSector < kMaxXSectors); XSECTOR *pXSector = &xsector[nXSector]; if ( pXSector->state ) pXSector->busy = kMaxBusyValue; int oldFloorZ, oldCeilZ; SPRITE *pMark0 = NULL, *pMark1 = NULL; switch( pSector->type ) { case kSectorTeleport: pXSector->data = -1; break; case kSectorZSprite: // it's probabyl okay to start this one with the on or off z case kSectorZMotion: case kSectorZCrusher: oldFloorZ = pSector->floorz; oldCeilZ = pSector->ceilingz; // open or close door as necessary for initial state if ( pXSector->state == 0 ) { pSector->ceilingz = pXSector->offCeilZ; pSector->floorz = pXSector->offFloorZ; } else { pSector->ceilingz = pXSector->onCeilZ; pSector->floorz = pXSector->onFloorZ; } // adjust the z for any floor relative sprites or face sprites in the floor for (nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->cstat & kSpriteMoveFloor || ( !(pSprite->cstat & kSpriteRMask) && pSprite->z >= oldFloorZ) ) pSprite->z += pSector->floorz - oldFloorZ; } // adjust the z for any ceiling relative sprites or face sprites in the ceiling for (nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->cstat & kSpriteMoveCeiling || ( !(pSprite->cstat & kSpriteRMask) && pSprite->z <= oldCeilZ) ) pSprite->z += pSector->ceilingz - oldCeilZ; } break; case kSectorSlideMarked: case kSectorSlide: case kSectorSlideCrush: pMark0 = &sprite[pXSector->marker0]; pMark1 = &sprite[pXSector->marker1]; // move door to off position by reversing markers TranslateSector(nSector, 0, 0x10000, pMark1->x, pMark1->y, pMark1->ang, pMark0->x, pMark0->y, pMark0->ang, pSector->type == kSectorSlide); // grab updated positions of walls for (i = 0; i < pSector->wallnum; i++) { int nWall = pSector->wallptr + i; kwall[nWall].x = wall[nWall].x; kwall[nWall].y = wall[nWall].y; } // grab updated positions of sprites for (nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { ksprite[nSprite].x = sprite[nSprite].x; ksprite[nSprite].y = sprite[nSprite].y; ksprite[nSprite].z = sprite[nSprite].z; } // open door if necessary TranslateSector(nSector, 0, pXSector->busy, pMark0->x, pMark0->y, pMark0->ang, pMark1->x, pMark1->y, pMark1->ang, pSector->type == kSectorSlide); break; case kSectorRotateMarked: case kSectorRotate: case kSectorRotateCrush: pMark0 = &sprite[pXSector->marker0]; // move door to off position TranslateSector(nSector, 0, -0x10000, pMark0->x, pMark0->y, 0, pMark0->x, pMark0->y, pMark0->ang, pSector->type == kSectorRotate); // grab updated positions of walls for (i = 0; i < pSector->wallnum; i++) { int nWall = pSector->wallptr + i; kwall[nWall].x = wall[nWall].x; kwall[nWall].y = wall[nWall].y; } // grab updated positions of sprites for (nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { ksprite[nSprite].x = sprite[nSprite].x; ksprite[nSprite].y = sprite[nSprite].y; ksprite[nSprite].z = sprite[nSprite].z; } // open door if necessary TranslateSector(nSector, 0, pXSector->busy, pMark0->x, pMark0->y, 0, pMark0->x, pMark0->y, pMark0->ang, pSector->type == kSectorRotate); break; default: break; } } } // init sprite trigger masks for ( nSprite = 0; nSprite < kMaxSprites; nSprite++ ) { int nXSprite = sprite[nSprite].extra; if ( sprite[nSprite].statnum < kMaxStatus && nXSprite > 0 ) { dassert(nXSprite < kMaxXSprites); XSPRITE *pXSprite = &xsprite[nXSprite]; if ( pXSprite->state ) pXSprite->busy = kMaxBusyValue; // special initialization for implicit trigger types switch ( sprite[nSprite].type ) { case kSwitchPadlock: pXSprite->triggerOnce = 1; // force trigger once break; case kGenTrigger: case kGenWaterDrip: case kGenBloodDrip: case kGenFireball: case kGenEctoSkull: case kGenDart: case kGenSound: InitGenerator( nSprite ); break; case kThingTNTProxArmed: pXSprite->triggerProximity = 1; break; } if ( pXSprite->triggerPush || pXSprite->triggerImpact ) sprite[nSprite].cstat |= kSpriteHitscan; /******************************************************************************* Proximity sensors are being put on a separate list here, but this presents a problem, since this means they can't be on any other list, such as the Thing list. This prevents the proximity bombs from being initialized properly. How do we make them affected by explosions? *******************************************************************************/ if ( pXSprite->triggerProximity ) actPostSprite( nSprite, kStatProximity ); // changespritestat((short)nSprite, kStatProximity); switch( sprite[nSprite].type ) { default: break; } } } evSend( 0, 0, kChannelTriggerStart, kCommandOn ); if (gNetMode == kNetModeCoop) evSend( 0, 0, kChannelTriggerCoop, kCommandOn ); else if (gNetMode == kNetModeBloodBath || gNetMode == kNetModeTeams) evSend( 0, 0, kChannelTriggerMatch, kCommandOn ); } /******************************************************************************* FUNCTION: trTextOver() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trTextOver( int textId ) { if ( gLevelMessage[textId] != NULL ) viewSetMessage(gLevelMessage[textId]); } /******************************************************************************* FUNCTION: trLightning() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ void trLightning( int nForkID ) { dassert( nForkID >= 0 && nForkID < kMaxLightning ); dprintf( "trLightning: nForkID = %i\n", nForkID ); int nSlot = gLightningInfo[ nForkID ].slot; dprintf( "trLightning: nSlot = %i\n", nSlot ); if ( nSlot != -1 ) { dassert( nSlot >= 0 && nSlot < kMaxSkyTiles ); dprintf( "trLightning: gLightningInfo[ %i ].offset = %i\n", nForkID, gLightningInfo[ nForkID ].offset ); pskyoff[ nSlot ] = (short)gLightningInfo[ nForkID ].offset; } } /******************************************************************************* FUNCTION: InitGenerator() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ static void InitGenerator( int nSprite ) { dassert(nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; dassert(pSprite->statnum != kMaxStatus); int nXSprite = pSprite->extra; dassert(nXSprite > 0); XSPRITE *pXSprite = &xsprite[nXSprite]; dassert(pXSprite->busyTime > 0); // should be > 0 for timer events switch ( sprite[nSprite].type ) { case kGenTrigger: pSprite->cstat &= ~kSpriteBlocking; pSprite->cstat |= kSpriteInvisible; break; default: break; //return; } pXSprite->data3 = FALSE; if ( pXSprite->state != pXSprite->restState && pXSprite->busyTime > 0 ) { pXSprite->data3 = TRUE; evPost(nSprite, SS_SPRITE, (pXSprite->busyTime + BiRandom(pXSprite->data1)) * kTimerRate / 10); } } /******************************************************************************* FUNCTION: ActivateGenerator() DESCRIPTION: PARAMETERS: RETURNS: NOTES: *******************************************************************************/ static void ActivateGenerator( int nSprite ) { dassert(nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; dassert(pSprite->statnum != kMaxStatus); int nXSprite = pSprite->extra; dassert(nXSprite > 0); XSPRITE *pXSprite = &xsprite[nXSprite]; int zTop, zBot; switch ( pSprite->type ) { case kGenWaterDrip: GetSpriteExtents(pSprite, &zTop, &zBot); actSpawnThing( pSprite->sectnum, pSprite->x, pSprite->y, zBot, kThingWaterDrip); break; case kGenBloodDrip: GetSpriteExtents(pSprite, &zTop, &zBot); actSpawnThing( pSprite->sectnum, pSprite->x, pSprite->y, zBot, kThingBloodDrip); break; case kGenSound: sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, pXSprite->soundId); break; case kGenFireball: switch(pXSprite->data2) { case 0: FireballTrapCallback( SS_SPRITE, nXSprite ); break; case 1: seqSpawn(kSeqFireTrap1, SS_SPRITE, nXSprite, FireballTrapCallback); break; case 2: seqSpawn(kSeqFireTrap2, SS_SPRITE, nXSprite, FireballTrapCallback); break; } break; case kGenEctoSkull: case kGenDart: // spawn a missile that moves and damages accordingly break; default: break; } } static void FireballTrapCallback( int /* type */, int nXIndex ) { XSPRITE *pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference; SPRITE *pSprite = &sprite[nSprite]; int dx, dy, dz; if ( pSprite->cstat & kSpriteFloor ) // floor sprite { dx = dy = 0; if ( pSprite->cstat & kSpriteFlipY ) // face down floor sprite dz = 1 << 14; else // face up floor sprite dz = -1 << 14; } else // wall sprite or face sprite { dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = 0; } actFireMissile( nSprite, pSprite->z, dx, dy, dz, kMissileFireball); } static void MGunFireCallback( int /* type */, int nXIndex ) { int nSprite = xsprite[nXIndex].reference; SPRITE *pSprite = &sprite[nSprite]; XSPRITE *pXSprite = &xsprite[nXIndex]; // if dynamic ammo left or infinite ammo if (pXSprite->data2 > 0 || pXSprite->data1 == 0) { if (pXSprite->data2 > 0) { pXSprite->data2--; // subtract ammo if (pXSprite->data2 == 0) evPost(nSprite, SS_SPRITE, 1, kCommandOff); // empty! turn it off } int dx = (Cos(pSprite->ang) >> 16) + BiRandom(1000); int dy = (Sin(pSprite->ang) >> 16) + BiRandom(1000); int dz = BiRandom(1000); actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorBullet); sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxTomFire); } } static void MGunOpenCallback( int /* type */, int nXIndex ) { seqSpawn(kSeqMGunFire, SS_SPRITE, nXIndex, MGunFireCallback); }