#include #include "engine.h" #include "debug4g.h" #include "error.h" #include "eventq.h" #include "db.h" #include "pqueue.h" #include "triggers.h" #include "globals.h" #include "levels.h" #define kMaxChannels 4096 #define kMaxID 1024 static PriorityQueue eventQ; /******************************************************************************* Each channel bucket is a contiguous range in a rxBucket[] array. The bucketHead[] array points to the start index for each channel id. Creating the buckets is not particularly fast, but using them is very fast and storage efficient. *******************************************************************************/ struct RXBUCKET { unsigned index : 13; // object array index (sprite[], sector[], wall[]) unsigned type : 3; // 0=sprite, 1=sector, 2=wall } rxBucket[kMaxChannels]; ushort bucketHead[kMaxID + 1]; static int GetBucketChannel( RXBUCKET *pBucket ) { int nXIndex; switch (pBucket->type) { case SS_SECTOR: nXIndex = sector[pBucket->index].extra; dassert(nXIndex > 0); return xsector[nXIndex].rxID; case SS_WALL: nXIndex = wall[pBucket->index].extra; dassert(nXIndex > 0); return xwall[nXIndex].rxID; case SS_SPRITE: nXIndex = sprite[pBucket->index].extra; dassert(nXIndex > 0); return xsprite[nXIndex].rxID; } ThrowError("Unexpected rxBucket type", ES_ERROR); return 0; } int CompareChannels( const void *ref1, const void *ref2 ) { return GetBucketChannel((RXBUCKET *)ref1) - GetBucketChannel((RXBUCKET *)ref2); } /******************************************************************************* FUNCTION: evInit() DESCRIPTION: Initialize the event queue NOTES: The map should be loaded so that all the rxIDs can be scanned *******************************************************************************/ void evInit( void ) { int i, j; int nCount = 0; eventQ.Flush(); // add all the tags to the bucket array for (i = 0; i < kMaxSectors; i++) { int nXSector = sector[i].extra; if (nXSector > 0 && xsector[nXSector].rxID > 0) { dassert(nCount < kMaxChannels); rxBucket[nCount].type = SS_SECTOR; rxBucket[nCount].index = i; nCount++; } } for (i = 0; i < kMaxWalls; i++) { int nXWall = wall[i].extra; if (nXWall > 0 && xwall[nXWall].rxID > 0) { dassert(nCount < kMaxChannels); rxBucket[nCount].type = SS_WALL; rxBucket[nCount].index = i; nCount++; } } for (i = 0; i < kMaxSprites; i++) { if (sprite[i].statnum < kMaxStatus) { int nXSprite = sprite[i].extra; if (nXSprite > 0 && xsprite[nXSprite].rxID > 0) { dassert(nCount < kMaxChannels); rxBucket[nCount].type = SS_SPRITE; rxBucket[nCount].index = i; nCount++; } } } // sort the array on rx tags qsort(rxBucket, nCount, sizeof(RXBUCKET), &CompareChannels); // create the list of header indices j = 0; for (i = 0; i < kMaxID; i++) { bucketHead[i] = (short)j; while ( j < nCount && GetBucketChannel(&rxBucket[j]) == i) j++; } bucketHead[i] = (short)j; } /*********************************************************************** * evSourceState() * * Return the operational state of the event's source. * **********************************************************************/ static BOOL evGetSourceState( int type, int nIndex ) { int nXIndex; switch ( type ) { case SS_SECTOR: nXIndex = sector[nIndex].extra; dassert(nXIndex > 0 && nXIndex < kMaxXSectors); return (BOOL)xsector[nXIndex].state; case SS_WALL: nXIndex = wall[nIndex].extra; dassert(nXIndex > 0 && nXIndex < kMaxXWalls); return (BOOL)xwall[nXIndex].state; case SS_SPRITE: nXIndex = sprite[nIndex].extra; dassert(nXIndex > 0 && nXIndex < kMaxXSprites); return (BOOL)xsprite[nXIndex].state; } // shouldn't reach this point return FALSE; } /*********************************************************************** * evSend() * **********************************************************************/ void evSend( int index, int type, int to, int command ) { if ( command == kCommandState ) command = evGetSourceState(type, index) ? kCommandOn : kCommandOff; else if ( command == kCommandNotState ) command = evGetSourceState(type, index) ? kCommandOff : kCommandOn; EVENT event; event.index = index; event.type = type; event.to = to; event.command = command; // handle transmit-only system triggers if (to > kChannelNull) { switch (to) { // case kChannelNull: // ThrowError("Event with null tag encountered", ES_ERROR); case kChannelEndLevelA: gEndLevelFlag = gEndingA; // Hooray. You finished the level. return; case kChannelEndLevelB: gEndLevelFlag = gEndingB; // Hooray. You finished the level via the secret ending. return; case kChannelTextOver: if (command < kCommandNumbered) ThrowError("Non-numbered command triggered for TextOver", ES_ERROR); trTextOver(command - kCommandNumbered); return; case kChannelLightning: if (command < kCommandNumbered) ThrowError("Non-numbered command triggered for Lightning", ES_ERROR); trLightning(command - kCommandNumbered); return; case kChannelTriggerStart: dprintf("Trigger start broadcast\n"); break; case kChannelTriggerMatch: dprintf("BloodBath start broadcast\n"); break; case kChannelTriggerCoop: dprintf("Coop start broadcast\n"); break; case kChannelRemoteFire1: case kChannelRemoteFire2: case kChannelRemoteFire3: case kChannelRemoteFire4: case kChannelRemoteFire5: case kChannelRemoteFire6: case kChannelRemoteFire7: case kChannelRemoteFire8: // these can't use the rx buckets since they are dynamically created for (short nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite] ) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->extra > 0 ) { XSPRITE *pXSprite = &xsprite[pSprite->extra]; if ( pXSprite->rxID == to ) trMessageSprite( nSprite, event ); } } return; } } // the event is a broadcast message for (int i = bucketHead[event.to]; i < bucketHead[event.to + 1]; i++) { // don't send it to the originator if (rxBucket[i].type == event.type && rxBucket[i].index == event.index) continue; switch ( rxBucket[i].type ) { case SS_SECTOR: trMessageSector( rxBucket[i].index, event ); break; case SS_WALL: trMessageWall( rxBucket[i].index, event ); break; case SS_SPRITE: trMessageSprite( rxBucket[i].index, event ); break; default: break; } } } /*********************************************************************** * evPost() * **********************************************************************/ void evPost( int index, int type, ulong time, int command ) { if ( command == kCommandState ) command = evGetSourceState(type, index) ? kCommandOn : kCommandOff; else if ( command == kCommandNotState ) command = evGetSourceState(type, index) ? kCommandOff : kCommandOn; EVENT event; event.index = index; event.type = type; event.command = command; eventQ.Insert(gFrameClock + time, (void *&)event); } /*********************************************************************** * evProcess() * * check for and process broadcast commands to objects * **********************************************************************/ void evProcess( ulong time ) { // while there are events to be processed while ( eventQ.Check(time) ) { void *p = eventQ.Remove(); EVENT event = (EVENT &)p; // is it a special callback event? // if (event.command != kCommandCallback) // ThrowError("Non-callback in event queue", ES_ERROR); dprintf("Dispatching callback event "); switch ( event.type ) { case SS_SECTOR: dprintf("to sector %d\n", event.index); trMessageSector( event.index, event ); break; case SS_WALL: dprintf("to wall %d\n", event.index); trMessageWall( event.index, event ); break; case SS_SPRITE: dprintf("to sprite %d\n", event.index); trMessageSprite( event.index, event ); break; } } }