/*============================================================================= Name : Blobs.c Purpose : Code to create and maintain lists of 'blobs' which are spheres encapsulating objects in the game world. Created 2/9/1998 by lmoloney Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #include #include #include #include "debug.h" #include "memory.h" #include "spaceobj.h" #include "universe.h" #include "fastmath.h" #include "sensors.h" #include "netcheck.h" #include "battle.h" #include "soundevent.h" #include "GravWellGenerator.h" #include "blobs.h" #include "Alliance.h" #include "TimeoutTimer.h" #include "singleplayer.h" #ifdef BOB_STATS BobStats bobStats = { 0,0,0,0,0,0,0,0,0,0,0 }; void bobListUpdateStatsInitFunc(bool subBlobs) { memset(&bobStats,0,sizeof(struct BobStats)); bobStats.subBlobs = subBlobs; GetRawTime(&bobStats.timeStart); } void bobListUpdateStatsCloseFunc() { GetRawTime(&bobStats.timeStop); bobStats.timeDuration = bobStats.timeStop - bobStats.timeStart; bobStats.statsValid = TRUE; } #define bobListUpdateStatsInit(x) bobListUpdateStatsInitFunc(x) #define bobListUpdateStatsClose() bobListUpdateStatsCloseFunc() #define bobStatsPass() bobStats.numPasses++ #define bobStatsWalk() bobStats.numWalks++; #define bobStatsCheck() bobStats.numChecks++; #define bobStatsTrivialReject() bobStats.trivialRejects++; #define bobStatsInitialBlobs(x) bobStats.initialBlobs = (x) #define bobStatsFinalBlobs(x) bobStats.finalBlobs = (x) #define bobStatsCombine() bobStats.combine++; #else #define bobListUpdateStatsInit(x) #define bobListUpdateStatsClose() #define bobStatsPass() #define bobStatsWalk() #define bobStatsCheck() #define bobStatsTrivialReject() #define bobStatsInitialBlobs(x) #define bobStatsFinalBlobs(x) #define bobStatsCombine() #endif /*============================================================================= Data: =============================================================================*/ static BlobProperties *BlobPropertiesPtr; real32 bobUpdateRadiusThreshold = BOB_UpdateRadiusThreshold; real32 bobUpdateCentreThreshold = BOB_UpdateCentreThreshold; //vector bobOriginVector = {0.0f, 0.0f, 0.0f}; /*============================================================================= Functions: =============================================================================*/ #define BLOBOBJ_BATCH 10 void blobFree(blob *thisBlob); void blobFreeContents(blob *thisBlob); extern real32 SINGLEPLAYER_BOBBIGGESTRADIUS_LEVEL6; extern BlobProperties collBlobProperties; /*----------------------------------------------------------------------------- Name : bobResetProperties Description : resets blob properties to their defaults Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void bobResetProperties() { collBlobProperties.bobBiggestRadius = collBlobProperties.bobBiggestRadiusDefault; } /*----------------------------------------------------------------------------- Name : bobInitProperties Description : initializes blob properties to allow for special case properties for certain single player levels Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void bobInitProperties() { bobResetProperties(); if (singlePlayerGame) { if (singlePlayerGameInfo.currentMission == 6) { collBlobProperties.bobBiggestRadius = SINGLEPLAYER_BOBBIGGESTRADIUS_LEVEL6; } } } /*----------------------------------------------------------------------------- Name : bobFindNearestBlobToObject Description : finds the nearest blob to the obj Inputs : list of blobs to search, obj Outputs : Return : returns nearest blob to the obj ----------------------------------------------------------------------------*/ blob *bobFindNearestBlobToObject(LinkedList *list,SpaceObj *obj,real32 *returndistsqr) { vector distvec; real32 distsqr; Node *blobnode = list->head; blob *thisBlob; real32 mindistsqr = REALlyBig; blob *closestBlob = NULL; while (blobnode != NULL) { thisBlob = (blob *)listGetStructOfNode(blobnode); vecSub(distvec,obj->posinfo.position,thisBlob->centre); distsqr = vecMagnitudeSquared(distvec); if (distsqr < mindistsqr) { closestBlob = thisBlob; mindistsqr = distsqr; } blobnode = blobnode->next; } *returndistsqr = mindistsqr; return closestBlob; } /*----------------------------------------------------------------------------- Name : bobListSortCallback Description : Compare two Inputs : Outputs : Return : TRUE if firststruct > secondstruct (ascending order) ----------------------------------------------------------------------------*/ bool bobListSortCallback(void *firststruct,void *secondstruct) { if (((blob *)(firststruct))->sortDistance > ((blob *)(secondstruct))->sortDistance) { return(TRUE); } return(FALSE); } /*----------------------------------------------------------------------------- Name : CompareSpaceObjCollOptimizeDistCB Description : Callback function for comparing collOptimzeDist of two spaceobj's Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ int CompareSpaceObjCollOptimizeDistCB(const void *arg1,const void *arg2) { SpaceObj *sobj1 = *((SpaceObj **)arg1); SpaceObj *sobj2 = *((SpaceObj **)arg2); if (sobj1->collOptimizeDist > sobj2->collOptimizeDist) { return 1; } else { return -1; } } #define AssignIfBigger(var,val) \ if ((val) > (var)) \ { \ (var) = (val); \ } /*----------------------------------------------------------------------------- Name : FillInCollisionBlobSpecificLists Description : Fills in collision blob specific lists (Ships, Targets, etc.) based on blobObjects list Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void FillInCollisionBlobSpecificLists(blob *thisBlob) { sdword numShips = 0, numResources = 0, numDerelicts = 0, numBullets = 0; sdword numMissileMissiles = 0, numMissileMines = 0, numSmallTargets = 0, numBigTargets = 0; sdword numBigShips = 0, numSmallShips = 0; sdword i; SpaceObjSelection *blobObjects = thisBlob->blobObjects; SpaceObj *spaceobj; thisBlob->blobMaxShipCollSphereSize = 0.0f; thisBlob->blobMaxBigShipCollSphereSize = 0.0f; thisBlob->blobMaxSmallShipCollSphereSize = 0.0f; thisBlob->blobMaxSTargetCollSphereSize = 0.0f; thisBlob->blobMaxBTargetCollSphereSize = 0.0f; thisBlob->blobMaxResourceCollSphereSize = 0.0f; thisBlob->blobMaxDerelictCollSphereSize = 0.0f; for (i=0;inumSpaceObjs;i++) { spaceobj = blobObjects->SpaceObjPtr[i]; if (spaceobj->flags & SOF_Targetable) { if(spaceobj->objtype != OBJ_MissileType) { real32 originalcollspheresize = ((TargetPtr)spaceobj)->staticinfo->staticheader.staticCollInfo.originalcollspheresize; if (originalcollspheresize >= BIG_TARGET_SIZE) { thisBlob->blobBigTargets->TargetPtr[numBigTargets++] = (TargetPtr)spaceobj; AssignIfBigger(thisBlob->blobMaxBTargetCollSphereSize,originalcollspheresize) } else { //don't add missiles because they're bitches thisBlob->blobSmallTargets->TargetPtr[numSmallTargets++] = (TargetPtr)spaceobj; AssignIfBigger(thisBlob->blobMaxSTargetCollSphereSize,originalcollspheresize) } } } switch (spaceobj->objtype) { case OBJ_ShipType: { real32 originalcollspheresize = ((ShipPtr)spaceobj)->staticinfo->staticheader.staticCollInfo.originalcollspheresize; if (originalcollspheresize >= BIG_SHIP_SIZE) { thisBlob->blobBigShips->ShipPtr[numBigShips++] = (ShipPtr)spaceobj; AssignIfBigger(thisBlob->blobMaxBigShipCollSphereSize,originalcollspheresize) } else { thisBlob->blobSmallShips->ShipPtr[numSmallShips++] = (ShipPtr)spaceobj; AssignIfBigger(thisBlob->blobMaxSmallShipCollSphereSize,originalcollspheresize) } thisBlob->blobShips->ShipPtr[numShips++] = (ShipPtr)spaceobj; AssignIfBigger(thisBlob->blobMaxShipCollSphereSize,((ShipPtr)spaceobj)->staticinfo->staticheader.staticCollInfo.collspheresize) } break; case OBJ_BulletType: thisBlob->blobBullets->BulletPtr[numBullets++] = (BulletPtr)spaceobj; break; case OBJ_AsteroidType: case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: thisBlob->blobResources->ResourcePtr[numResources++] = (ResourcePtr)spaceobj; AssignIfBigger(thisBlob->blobMaxResourceCollSphereSize,((ResourcePtr)spaceobj)->staticinfo->staticheader.staticCollInfo.collspheresize) break; case OBJ_DerelictType: thisBlob->blobDerelicts->DerelictPtr[numDerelicts++] = (DerelictPtr)spaceobj; AssignIfBigger(thisBlob->blobMaxDerelictCollSphereSize,((DerelictPtr)spaceobj)->staticinfo->staticheader.staticCollInfo.collspheresize) break; case OBJ_MissileType: if (((MissilePtr)spaceobj)->missileType == MISSILE_Mine) { thisBlob->blobMissileMines->MissilePtr[numMissileMines++] = (MissilePtr)spaceobj; } else { thisBlob->blobMissileMissiles->MissilePtr[numMissileMissiles++] = (MissilePtr)spaceobj; } break; default: dbgAssert(FALSE); break; } } dbgAssert(thisBlob->blobShips->numShips == numShips); dbgAssert(thisBlob->blobBigShips->numShips == numBigShips); dbgAssert(thisBlob->blobSmallShips->numShips == numSmallShips); dbgAssert(thisBlob->blobBigTargets->numTargets == numBigTargets); dbgAssert(thisBlob->blobSmallTargets->numTargets == numSmallTargets); dbgAssert(thisBlob->blobResources->numResources == numResources); dbgAssert(thisBlob->blobDerelicts->numDerelicts == numDerelicts); dbgAssert(thisBlob->blobBullets->numBullets == numBullets); dbgAssert(thisBlob->blobMissileMissiles->numMissiles == numMissileMissiles); dbgAssert(thisBlob->blobMissileMines->numMissiles == numMissileMines); } /*----------------------------------------------------------------------------- Name : bobUpdateObjsInBlobCollInfo Description : updates "objects in blob" collision info Inputs : thisBlob Outputs : Return : ----------------------------------------------------------------------------*/ void bobUpdateObjsInBlobCollInfo(blob *thisBlob) { SpaceObjSelection *blobObjects = thisBlob->blobObjects; sdword i; SpaceObj *spaceobj; vector distvec; // calculate collOptimizeDist for each spaceobj relative to blob centre thisBlob->flags &= ~(BTM_PieceOfTheAction); //clear out the "action flags" for (i=0;inumSpaceObjs;i++) { spaceobj = blobObjects->SpaceObjPtr[i]; dbgAssert((spaceobj->flags & SOF_Dead) == 0); #if BOB_ERROR_CHECKING if (spaceobj->objtype == OBJ_AsteroidType) { dbgAssert(((Asteroid *)spaceobj)->asteroidtype != Asteroid0); // don't put Asteroid0 in for speed } else #endif if (spaceobj->objtype == OBJ_ShipType) { //keep the "action flags" up-to-date if (((Ship *)spaceobj)->recentlyAttacked != 0) { bitSet(thisBlob->flags, BTF_GettingAttacked); } if (((Ship *)spaceobj)->shipisattacking) { bitSet(thisBlob->flags, BTF_Attacking); } } vecSub(distvec,spaceobj->posinfo.position,thisBlob->centre); spaceobj->collOptimizeDist = fsqrt(vecMagnitudeSquared(distvec)); } qsort(&blobObjects->SpaceObjPtr[0],blobObjects->numSpaceObjs,sizeof(SpaceObj *),CompareSpaceObjCollOptimizeDistCB); FillInCollisionBlobSpecificLists(thisBlob); blobAnalVerify(thisBlob); } /*----------------------------------------------------------------------------- Name : bobUpdateExtraCollBobInfo Description : updates extra collision blob info for collision blobs only Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void bobUpdateExtraCollBobInfo(LinkedList *list) { SpaceObj *spaceobj; vector distvec; sdword i; Node *blobnode = list->head; blob *thisBlob; sdword numShips,numResources,numDerelicts,numBullets,numMissileMissiles,numMissileMines,numSmallTargets,numBigTargets; sdword numBigShips,numSmallShips; SpaceObjSelection *blobObjects; // for each blob, while (blobnode != NULL) { thisBlob = (blob *)listGetStructOfNode(blobnode); blobnode = blobnode->next; blobObjects = thisBlob->blobObjects; // calculate collOptimizeDist for each spaceobj relative to blob centre for (i=0;inumSpaceObjs;i++) { spaceobj = blobObjects->SpaceObjPtr[i]; vecSub(distvec,spaceobj->posinfo.position,thisBlob->centre); spaceobj->collOptimizeDist = fsqrt(vecMagnitudeSquared(distvec)); spaceobj->collMyBlob = thisBlob; } // sort blob spaceobjs based on distance from centre of blob (collOptimizeDist) qsort(&blobObjects->SpaceObjPtr[0],blobObjects->numSpaceObjs,sizeof(SpaceObj *),CompareSpaceObjCollOptimizeDistCB); if (bitTest(thisBlob->flags, BTF_DontUpdate)) { //don't re-process this blob if it's good enough bitClear(thisBlob->flags, BTF_DontUpdate); continue; } // create blob ship, resource, derelict, bullet, missile, and target lists for efficiency // 1st pass: figure out # of ship, resource, derelict, bullet, missile, and targets numShips = 0, numResources = 0, numDerelicts = 0, numBullets = 0; numMissileMissiles = 0, numMissileMines = 0, numSmallTargets = 0, numBigTargets = 0; numBigShips = 0, numSmallShips = 0; for (i=0;inumSpaceObjs;i++) { spaceobj = blobObjects->SpaceObjPtr[i]; if (spaceobj->flags & SOF_Targetable) { if(spaceobj->objtype != OBJ_MissileType) { if (((TargetPtr)spaceobj)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_TARGET_SIZE) { numBigTargets++; } else { numSmallTargets++; } } } switch (spaceobj->objtype) { case OBJ_ShipType: if (((ShipPtr)spaceobj)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_SHIP_SIZE) { numBigShips++; } else { numSmallShips++; } numShips++; break; case OBJ_BulletType: numBullets++; break; case OBJ_AsteroidType: case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: numResources++; break; case OBJ_DerelictType: numDerelicts++; break; case OBJ_MissileType: if (((MissilePtr)spaceobj)->missileType == MISSILE_Mine) { numMissileMines++; } else { numMissileMissiles++; } break; default: dbgAssert(FALSE); break; } } // 2nd pass: construct ship, resource, derelict, bullet, missile, and target lists thisBlob->maxNumShips = numShips + BLOBOBJ_BATCH; thisBlob->blobShips = memAlloc(sizeofSelectCommand(thisBlob->maxNumShips),"bs(blobships)",Pyrophoric); thisBlob->blobShips->numShips = numShips; thisBlob->maxNumBigShips = numBigShips + BLOBOBJ_BATCH; thisBlob->blobBigShips = memAlloc(sizeofSelectCommand(thisBlob->maxNumBigShips),"bsb(blobbships)",Pyrophoric); thisBlob->blobBigShips->numShips = numBigShips; thisBlob->maxNumSmallShips = numSmallShips + BLOBOBJ_BATCH; thisBlob->blobSmallShips = memAlloc(sizeofSelectCommand(thisBlob->maxNumSmallShips),"bss(blobsships)",Pyrophoric); thisBlob->blobSmallShips->numShips = numSmallShips; thisBlob->maxNumSmallTargets = numSmallTargets + BLOBOBJ_BATCH; thisBlob->blobSmallTargets = memAlloc(sizeofSelectCommand(thisBlob->maxNumSmallTargets),"bt(blobstargets)",Pyrophoric); thisBlob->blobSmallTargets->numTargets = numSmallTargets; thisBlob->maxNumBigTargets = numBigTargets + BLOBOBJ_BATCH; thisBlob->blobBigTargets = memAlloc(sizeofSelectCommand(thisBlob->maxNumBigTargets),"bt(blobbtargets)",Pyrophoric); thisBlob->blobBigTargets->numTargets = numBigTargets; thisBlob->maxNumResources = numResources + BLOBOBJ_BATCH; thisBlob->blobResources = memAlloc(sizeofSelectCommand(thisBlob->maxNumResources),"br(blobresources)",Pyrophoric); thisBlob->blobResources->numResources = numResources; thisBlob->maxNumDerelicts = numDerelicts + BLOBOBJ_BATCH; thisBlob->blobDerelicts = memAlloc(sizeofSelectCommand(thisBlob->maxNumDerelicts),"bd(blobderelicts)",Pyrophoric); thisBlob->blobDerelicts->numDerelicts = numDerelicts; thisBlob->maxNumBullets = numBullets + BLOBOBJ_BATCH; thisBlob->blobBullets = memAlloc(sizeofSelectCommand(thisBlob->maxNumBullets),"bb(blobbullets)",Pyrophoric); thisBlob->blobBullets->numBullets = numBullets; thisBlob->maxNumMissileMissiles = numMissileMissiles + BLOBOBJ_BATCH; thisBlob->blobMissileMissiles = memAlloc(sizeofSelectCommand(thisBlob->maxNumMissileMissiles),"bm(blobmissiles)",Pyrophoric); thisBlob->blobMissileMissiles->numMissiles = numMissileMissiles; thisBlob->maxNumMissileMines = numMissileMines + BLOBOBJ_BATCH; thisBlob->blobMissileMines = memAlloc(sizeofSelectCommand(thisBlob->maxNumMissileMines),"bm(blobmines)",Pyrophoric); thisBlob->blobMissileMines->numMissiles = numMissileMines; thisBlob->maxNumObjects = blobObjects->numSpaceObjs; FillInCollisionBlobSpecificLists(thisBlob); blobAnalVerify(thisBlob); } } /*----------------------------------------------------------------------------- Name : bobSubBlobListCreate Description : Create a 'sub-blob' list inside a bigger blob. Inputs : blobProperties - same as for bobListCreate list - same as for bobListCreate superBlob - the blob which contains a list of objects to use criteria - callback function to see if an object is suitable for the mini-blob Outputs : Return : ----------------------------------------------------------------------------*/ void bobSubBlobListCreate(BlobProperties *blobProperties, LinkedList *list, blob *superBlob, subblobcallback criteria) { blob *preallocBlobs; SpaceObjSelection *preallocObjSelection; SpaceObjSelection *newObjSelection; udword usePreallocBlob = 0; SpaceObj *spaceobj; blob *newBlob; blob *tblob; Node *node, *nextnode; sdword index; real32 mass; vector distance; dbgAssert(blobProperties); BlobPropertiesPtr = blobProperties; listInit(list); //init the linked list if (superBlob->blobObjects->numSpaceObjs == 0) { //if there's nothing in the blob return; //all done } //first pass: create a sphere structure for each ship preallocBlobs = memAlloc(bobBlobSize(1)*superBlob->blobObjects->numSpaceObjs,"MassAllocBlobs",0); preallocObjSelection = memAlloc(sizeof(SelectCommand)*superBlob->blobObjects->numSpaceObjs,"MassAllocSel",0); for (index = 0; index < superBlob->blobObjects->numSpaceObjs; index++) { //for all blobs in this big fat blob spaceobj = superBlob->blobObjects->SpaceObjPtr[index]; if (criteria(superBlob, spaceobj)) { //if this object satisfies some criteria switch (spaceobj->objtype) { case OBJ_ShipType: if (((Ship *)spaceobj)->shiptype == Drone) { continue; } mass = ((Ship *)spaceobj)->staticinfo->staticheader.mass; break; case OBJ_BulletType: continue; //no sphere for you! case OBJ_AsteroidType: case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: mass = ((Resource *)spaceobj)->staticinfo->staticheader.mass; break; case OBJ_DerelictType: if (((Derelict *)spaceobj)->staticinfo->worldRender) { //don't include worlds in the blobs. They wouldn't fit anyhow. continue; } mass = ((Derelict *)spaceobj)->staticinfo->staticheader.mass; break; case OBJ_EffectType: case OBJ_MissileType: continue; //no sphere for you! } newBlob = &preallocBlobs[usePreallocBlob]; newBlob->flags = 0; bitClear(newBlob->flags, BTF_FreeThisBlob); bitClear(newBlob->flags, BTF_FreeBlobObjects); newBlob->centre = spaceobj->posinfo.position; newBlob->radius = BlobPropertiesPtr->bobStartSphereSize; newBlob->volume = sphereVolume(newBlob->radius); newBlob->oneOverVolume = 1.0f / newBlob->volume; newBlob->totalMass = mass; newBlob->RUs = 0; #ifndef HW_Release // newBlob->debugFlag = 0; #endif newBlob->blobShips = NULL; newBlob->blobBigShips = NULL; newBlob->blobSmallShips = NULL; newBlob->blobSmallTargets = NULL; newBlob->blobBigTargets = NULL; newBlob->blobResources = NULL; newBlob->blobDerelicts = NULL; newBlob->blobBullets = NULL; newBlob->blobMissileMissiles = NULL; newBlob->blobMissileMines = NULL; newBlob->blobObjects = &preallocObjSelection[usePreallocBlob++]; newBlob->blobObjects->numSpaceObjs = 1; newBlob->blobObjects->SpaceObjPtr[0] = spaceobj; newBlob->subBlobs.num = BIT31; //flag the sub-blob list not yet created vecSub(distance, superBlob->centre, spaceobj->posinfo.position); newBlob->sortDistance = vecMagnitudeSquared(distance); newBlob->sqrtSortDistance = 0.0f; listAddNode(list, &newBlob->node, newBlob); newBlob->subBlobs.num = BIT31; //flag the sub-blob list not yet created } } //second pass: sort the spheres based on distance from the mothership listMergeSortGeneral(list, bobListSortCallback); //final pass: combine the spheres into a neat, maageable list bobListUpdateStatsInit(TRUE); bobListUpdate(list); bobListUpdateStatsClose(); // at this point we have a list of all the blobs we want. But they may be in the preallocBlobs chunk. // Reallocate any that are in the preallocBlobs chunk. // Same goes for blobObjects which may be in the preallocSelectCommands chunk. node = list->head; while (node != NULL) { tblob = (blob *)listGetStructOfNode(node); nextnode = node->next; if (!bitTest(tblob->flags, BTF_FreeBlobObjects)) { if (tblob->blobObjects != NULL) { dbgAssert(tblob->blobObjects->numSpaceObjs == 1); newObjSelection = memAlloc(sizeofSelectCommand(1),"nbo(newblobobj)",Pyrophoric); memcpy(newObjSelection,tblob->blobObjects,sizeofSelectCommand(1)); tblob->blobObjects = newObjSelection; } bitSet(tblob->flags, BTF_FreeBlobObjects); } if (!bitTest(tblob->flags, BTF_FreeThisBlob)) { newBlob = memAlloc(sizeof(blob),"nb(newblob)",Pyrophoric); memcpy(newBlob,tblob,sizeof(blob)); bitSet(newBlob->flags, BTF_FreeThisBlob); listAddNodeAfter(node,&newBlob->node,newBlob); listRemoveNode(node); } node = nextnode; } memFree(preallocBlobs); memFree(preallocObjSelection); } /*----------------------------------------------------------------------------- Name : bobAllObjectsSatisfyBlob Description : Checks to see if the position of all objects is still within the bounds of the blob. Inputs : thisBlob - blob to examine. Outputs : Return : TRUE if all blob objects are still within the blob ----------------------------------------------------------------------------*/ bool bobAllObjectsSatisfyBlob(blob *thisBlob) { vector difference, blobPosition; real32 blobRadius, blobRadiusSq = thisBlob->radius * thisBlob->radius; real32 differenceSq; if (bitTest(thisBlob->flags, BTF_RecentDeath)) { //if something in this blob just died return(FALSE); //update this blob } bobObjectListMedian(&blobPosition, &blobRadius, thisBlob->blobObjects->numSpaceObjs, thisBlob->blobObjects->SpaceObjPtr); if (blobRadius > thisBlob->radius) { //if the blob has grown if (blobRadius / thisBlob->radius > bobUpdateRadiusThreshold) { //limit how much it can grow return(FALSE); } } vecSub(difference, thisBlob->centre, blobPosition); //inter-centre distance differenceSq = difference.x * difference.x + difference.y * difference.y + difference.z * difference.z; if (differenceSq / blobRadiusSq > bobUpdateCentreThreshold) { //if the centre has moved too much return(FALSE); } return(TRUE); //all objects inside the blob cool like ice cubes baby } /*----------------------------------------------------------------------------- Name : bobAllObjectsFlagDontUpdate Description : Flag all objects in the blob as SOF_DontCreateBlob and the blob as BTF_DontUpdate and BTF_ClearObjectFlags Inputs : thisBlob - blob to flag Outputs : Return : ----------------------------------------------------------------------------*/ void bobAllObjectsFlagDontUpdate(blob *thisBlob) { sdword count; SpaceObj **spaceobjPointer; spaceobjPointer = thisBlob->blobObjects->SpaceObjPtr; for (count = thisBlob->blobObjects->numSpaceObjs; count > 0; count--, spaceobjPointer++) { //flag all objects if ((*spaceobjPointer)->objtype == OBJ_BulletType) //remove all bullets and missile missiles (not mines) from the list { *spaceobjPointer = thisBlob->blobObjects->SpaceObjPtr[thisBlob->blobObjects->numSpaceObjs - 1]; spaceobjPointer--; thisBlob->blobObjects->numSpaceObjs--; continue; } else if ((((*spaceobjPointer)->objtype == OBJ_MissileType)) && (((Missile*)(*spaceobjPointer))->missileType != MISSILE_Mine)) { //else it's a missile missile // missile could be in small target list and also in missile missile list if (thisBlob->blobSmallTargets != NULL) { RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobSmallTargets,(*spaceobjPointer)); } *spaceobjPointer = thisBlob->blobObjects->SpaceObjPtr[thisBlob->blobObjects->numSpaceObjs - 1]; spaceobjPointer--; thisBlob->blobObjects->numSpaceObjs--; continue; } dbgAssert(!bitTest((*spaceobjPointer)->flags, SOF_DontCreateBlob)); bitSet((*spaceobjPointer)->flags, SOF_DontCreateBlob); } thisBlob->sqrtSortDistance = 0.0f; thisBlob->blobBullets->numBullets = 0; //clear out the bullet and missile lists thisBlob->blobMissileMissiles->numMissiles = 0; //they'll soon be re-created anyhow thisBlob->flags |= BTF_DontUpdate | BTF_ClearObjectFlags;//flag the blob } /*----------------------------------------------------------------------------- Name : bobAllObjectsUnflagDontUpdate Description : Clears all objects in the blob of SOF_DontCreateBlob and the blob of BTF_DontUpdate and BTF_ClearObjectFlags Inputs : thisBlob - blob to flag Outputs : Return : ----------------------------------------------------------------------------*/ void bobAllObjectsUnflagDontUpdate(blob *thisBlob) { sdword count; SpaceObj **spaceobjPointer; spaceobjPointer = thisBlob->blobObjects->SpaceObjPtr; for (count = thisBlob->blobObjects->numSpaceObjs; count > 0; count--, spaceobjPointer++) { //flag all objects bitClear((*spaceobjPointer)->flags, SOF_DontCreateBlob); } bitClear(thisBlob->flags, (BTF_ClearObjectFlags));//flag the blob } /*----------------------------------------------------------------------------- Name : bobAllBlobsFlagObjects Description : Flag all objects in specified blobs as satisfying the current state of the blob. If not satisfying blob state, delete the blob. Inputs : list - list of blobs Outputs : Return : ----------------------------------------------------------------------------*/ void bobAllBlobsFlagObjects(LinkedList *list) { Node *node, *nextNode; blob *thisBlob; for (node = list->head; node != NULL; node = nextNode) { nextNode = node->next; thisBlob = (blob *)listGetStructOfNode(node); if (bobAllObjectsSatisfyBlob(thisBlob)) { //if this blob works in it's present state bobAllObjectsFlagDontUpdate(thisBlob); } else { //this blob is no good, proceed to update it as usual listRemoveNode(node); if (thisBlob->subBlobs.num != BIT31) { //if there is a sub-blob list bobListDelete(&thisBlob->subBlobs); } blobFree(thisBlob); } } } /*----------------------------------------------------------------------------- Name : bobListCreate Description : Create a list of blobs for the current state of the universe. Typically called at the startup of the sensors manager. Inputs : playerIndex - index of current player. Outputs : Return : The head of a list of blobs. ----------------------------------------------------------------------------*/ void bobListCreate(BlobProperties *blobProperties, LinkedList *list, udword playerIndex) { SpaceObj *spaceobj; Node *node; blob *newBlob; real32 mass; blob *preallocBlobs; SpaceObjSelection *preallocObjSelection; SpaceObjSelection *newObjSelection; udword usePreallocBlob = 0; blob *tblob; Node *nextnode; TargetPtr target; dbgAssert(blobProperties); BlobPropertiesPtr = blobProperties; //listInit(list); //init the linked list if (list->num > 0) { //if there are already blobs in the blob list bobAllBlobsFlagObjects(list); } if (universe.SpaceObjList.num == 0) { return; // no SpaceObj's, therefore no blobs } //first pass: create a sphere structure for each ship // mass preallocate everything preallocBlobs = memAlloc(bobBlobSize(1)*universe.SpaceObjList.num,"MAB(MassAllocBlobs)",Pyrophoric); preallocObjSelection = memAlloc(sizeof(SelectCommand)*universe.SpaceObjList.num,"MAS(MassAllocSel)",Pyrophoric); node = universe.SpaceObjList.head; //get first node in universe list while (node != NULL) { spaceobj = (SpaceObj *)listGetStructOfNode(node); node = node->next; if (spaceobj->flags & (SOF_Dead | SOF_DontCreateBlob)) { //don't process dead or satisfactory objects continue; } switch (spaceobj->objtype) { case OBJ_ShipType: mass = ((Ship *)spaceobj)->staticinfo->staticheader.mass; break; case OBJ_BulletType: spaceobj->collMyBlob = NULL; continue; case OBJ_AsteroidType: if (((Asteroid *)spaceobj)->asteroidtype == Asteroid0) { spaceobj->collMyBlob = NULL; // don't add Asteroid0 for speed continue; } // deliberately fall through case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: mass = ((Resource *)spaceobj)->staticinfo->staticheader.mass; break; case OBJ_DerelictType: if (((Derelict *)spaceobj)->staticinfo->worldRender) { //don't include worlds in the blobs. They wouldn't fit anyhow. continue; } mass = ((Derelict *)spaceobj)->staticinfo->staticheader.mass; break; case OBJ_EffectType: continue; case OBJ_MissileType: if (((MissilePtr)spaceobj)->missileType == MISSILE_Mine) { mass = 0.1f; break; } else { spaceobj->collMyBlob = NULL; continue; } } newBlob = &preallocBlobs[usePreallocBlob]; newBlob->flags = 0; bitClear(newBlob->flags, BTF_FreeThisBlob); bitClear(newBlob->flags, BTF_FreeBlobObjects); newBlob->centre = spaceobj->posinfo.position; newBlob->radius = BlobPropertiesPtr->bobStartSphereSize; newBlob->volume = sphereVolume(newBlob->radius); newBlob->oneOverVolume = 1.0f / newBlob->volume; newBlob->totalMass = mass; newBlob->RUs = 0; #ifndef HW_Release // newBlob->debugFlag = 0; #endif newBlob->blobShips = NULL; newBlob->blobBigShips = NULL; newBlob->blobSmallShips = NULL; newBlob->blobBigTargets = NULL; newBlob->blobSmallTargets = NULL; newBlob->blobResources = NULL; newBlob->blobDerelicts = NULL; newBlob->blobBullets = NULL; newBlob->blobMissileMissiles = NULL; newBlob->blobMissileMines = NULL; newBlob->blobObjects = &preallocObjSelection[usePreallocBlob++]; newBlob->blobObjects->numSpaceObjs = 1; newBlob->blobObjects->SpaceObjPtr[0] = spaceobj; newBlob->subBlobs.num = BIT31; //flag the sub-blob list not yet created newBlob->sortDistance = vecMagnitudeSquared(spaceobj->posinfo.position); newBlob->sqrtSortDistance = 0.0f; listAddNode(list, &newBlob->node, newBlob); } //second pass: sort the spheres based on distance from the mothership listMergeSortGeneral(list, bobListSortCallback); //final pass: combine the spheres into a neat, maageable list bobListUpdateStatsInit(FALSE); bobListUpdate(list); bobListUpdateStatsClose(); // at this point we have a list of all the blobs we want. But they may be in the preallocBlobs chunk. // Reallocate any that are in the preallocBlobs chunk. // Same goes for blobObjects which may be in the preallocSelectCommands chunk. node = list->head; while (node != NULL) { tblob = (blob *)listGetStructOfNode(node); nextnode = node->next; if (!bitTest(tblob->flags, BTF_FreeBlobObjects)) { if (tblob->blobObjects != NULL) { dbgAssert(tblob->blobObjects->numSpaceObjs == 1); newObjSelection = memAlloc(sizeofSelectCommand(1),"nbo(newblobobj)",Pyrophoric); memcpy(newObjSelection,tblob->blobObjects,sizeofSelectCommand(1)); tblob->blobObjects = newObjSelection; } bitSet(tblob->flags, BTF_FreeBlobObjects); } if (!bitTest(tblob->flags, BTF_FreeThisBlob)) { newBlob = memAlloc(sizeof(blob),"nb(newblob)",Pyrophoric); memcpy(newBlob,tblob,sizeof(blob)); bitSet(newBlob->flags, BTF_FreeThisBlob); listAddNodeAfter(node,&newBlob->node,newBlob); listRemoveNode(node); } node = nextnode; } memFree(preallocBlobs); memFree(preallocObjSelection); //next pass: do blob itemizing needed only for collisions bobUpdateExtraCollBobInfo(list); //add bullets and missiles to the collision blobs of their targets // put in bullets/missiles to nearest blobs node = universe.BulletList.head; //get first node in bullet list while (node != NULL) { spaceobj = (SpaceObj *)listGetStructOfNode(node); node = node->next; target = ((Bullet *)spaceobj)->target; if ((target != NULL) && (target->collMyBlob != NULL)) { bobAddObjToSpecificBlob(target->collMyBlob,spaceobj); } else { bobAddObjToNearestBlob(list,spaceobj); } } node = universe.MissileList.head; //get first node in missile list while (node != NULL) { spaceobj = (SpaceObj *)listGetStructOfNode(node); node = node->next; if (spaceobj->flags & SOF_Dead) { continue; } if (((MissilePtr)spaceobj)->missileType != MISSILE_Mine) { target = ((Missile *)spaceobj)->target; if ((target != NULL) && (target->collMyBlob != NULL)) { bobAddObjToSpecificBlob(target->collMyBlob,spaceobj); } else { bobAddObjToNearestBlob(list,spaceobj); } } } } /*----------------------------------------------------------------------------- Name : bobObjetListMedian Description : Compute the median of a set of objects (average of greates and lowest). Inputs : nObjects - number of objects objects - list of objects Outputs : dest - where to store the mean destRadius - where to store the newly computed radius Return : ----------------------------------------------------------------------------*/ void bobObjectListMedian(vector *dest, real32 *destRadius, sdword nObjects, SpaceObj **objects) { sdword index; vector minV = {REALlyBig, REALlyBig, REALlyBig}; vector maxV = {REALlyNegative, REALlyNegative, REALlyNegative}; real32 radius, val; for (index = 0; index < nObjects; index++, objects++) { if (bitTest((*objects)->flags, SOF_Impactable)) { //if there's a collision sphere radius = (*objects)->staticinfo->staticheader.staticCollInfo.collspheresize; val = (*objects)->posinfo.position.x - radius; minV.x = min(minV.x, val); val = (*objects)->posinfo.position.y - radius; minV.y = min(minV.y, val); val = (*objects)->posinfo.position.z - radius; minV.z = min(minV.z, val); val = (*objects)->posinfo.position.x + radius; maxV.x = max(maxV.x, val); val = (*objects)->posinfo.position.y + radius; maxV.y = max(maxV.y, val); val = (*objects)->posinfo.position.z + radius; maxV.z = max(maxV.z, val); } else { //else there's no collision sphere minV.x = min(minV.x, (*objects)->posinfo.position.x); minV.y = min(minV.y, (*objects)->posinfo.position.y); minV.z = min(minV.z, (*objects)->posinfo.position.z); maxV.x = max(maxV.x, (*objects)->posinfo.position.x); maxV.y = max(maxV.y, (*objects)->posinfo.position.y); maxV.z = max(maxV.z, (*objects)->posinfo.position.z); } } dest->x = (minV.x + maxV.x) * 0.5f; dest->y = (minV.y + maxV.y) * 0.5f; dest->z = (minV.z + maxV.z) * 0.5f; *destRadius = max(max(maxV.x - minV.x, maxV.y - minV.y), maxV.z - minV.z) * 0.5f + BlobPropertiesPtr->bobRadiusCombineMargin; } /*----------------------------------------------------------------------------- Name : bobBlobCombine Description : Combine two blobs determined to be overlapping. Inputs : thisBlob - the blob to be retained otherBlob - the blob to be absorbed newRadius - the new combined radius already computed newVolume - the new combined volume already computed Outputs : Return : the next node for continued walking through the linked list ----------------------------------------------------------------------------*/ Node *bobBlobCombine(blob *thisBlob, blob *otherBlob, real32 newRadius, real32 newVolume) { blob tempBlob; Node *nextNode; SpaceObjSelection *newselection; sdword totalNumObjects; tempBlob = *thisBlob; // structure copy of thisBlob, before merging takes place totalNumObjects = tempBlob.blobObjects->numSpaceObjs + otherBlob->blobObjects->numSpaceObjs; newselection = memAlloc(sizeofSelectCommand(totalNumObjects),"nbO(newblobObjects)",Pyrophoric); newselection->numSpaceObjs = totalNumObjects; thisBlob->blobObjects = newselection; #ifndef HW_Release // thisBlob->debugFlag = 0; #endif bitClear(thisBlob->flags, BTF_DontUpdate); //make sure this blob gets itemized thisBlob->flags |= otherBlob->flags & BTF_ClearObjectFlags;//make sure objects get their SOF_DontCreateBlob bit cleared if (bitTest(tempBlob.flags, BTF_FreeThisBlob)) { bitSet(thisBlob->flags, BTF_FreeThisBlob); } else { bitClear(thisBlob->flags, BTF_FreeThisBlob); } bitSet(thisBlob->flags, BTF_FreeBlobObjects); thisBlob->blobShips = NULL; thisBlob->blobBigShips = NULL; thisBlob->blobSmallShips = NULL; thisBlob->blobSmallTargets = NULL; thisBlob->blobBigTargets = NULL; thisBlob->blobResources = NULL; thisBlob->blobDerelicts = NULL; thisBlob->blobBullets = NULL; thisBlob->blobMissileMissiles = NULL; thisBlob->blobMissileMines = NULL; //join the object lists together memcpy(newselection->SpaceObjPtr,tempBlob.blobObjects->SpaceObjPtr,tempBlob.blobObjects->numSpaceObjs * sizeof(SpaceObj *)); memcpy(&newselection->SpaceObjPtr[tempBlob.blobObjects->numSpaceObjs], otherBlob->blobObjects->SpaceObjPtr, otherBlob->blobObjects->numSpaceObjs * sizeof(SpaceObj *)); bobObjectListMedian(&thisBlob->centre, &newRadius, newselection->numSpaceObjs, newselection->SpaceObjPtr); thisBlob->radius = newRadius; thisBlob->volume = newVolume; thisBlob->oneOverVolume = 1.0f / newVolume; thisBlob->totalMass = tempBlob.totalMass + otherBlob->totalMass; thisBlob->sortDistance = vecMagnitudeSquared(thisBlob->centre); thisBlob->sqrtSortDistance = 0.0f; thisBlob->RUs = tempBlob.RUs + otherBlob->RUs; //delete otherBlob, and thisBlob becomes newBlob nextNode = otherBlob->node.next; blobFreeContents(&tempBlob); listRemoveNode(&otherBlob->node); blobFree(otherBlob); return(nextNode); } /*----------------------------------------------------------------------------- Name : bobBlobItemize Description : Perform analiysis of a blob for current sensors level. This includes counting capital and non-capital ships, number of RU's and determining if the blob is visable. Inputs : thisBlob - blob to scan through sensorsLevel - current sensors level for player. Outputs : Return : void ----------------------------------------------------------------------------*/ char bobCapitalClass[] = {CLASS_Frigate, CLASS_Destroyer, CLASS_Carrier, CLASS_HeavyCruiser, CLASS_Mothership, 0}; char bobAttackClass[] = {CLASS_Fighter, CLASS_Corvette, 0}; void bobBlobItemize(blob *thisBlob, sdword sensorsLevel) { sdword index; SpaceObj *object; ubyte shipClass; SpaceObjSelection *blobObjects = thisBlob->blobObjects; bool bGravWellDetected = FALSE, bProbeDetected = FALSE; Ship *gravWell, *probe; thisBlob->RUs = 0; thisBlob->nRockRUs = thisBlob->nDustRUs = 0; thisBlob->nDustClouds = thisBlob->nRocks = 0; thisBlob->flags &= ~(BTM_ItemizeFlags); thisBlob->nCapitalShips = thisBlob->nAttackShips = thisBlob->nNonCombat = 0; thisBlob->nFriendlyShips = 0; thisBlob->shipMass = 0.0f; if (thisBlob->radius < BlobPropertiesPtr->bobSmallestRadius) { //cap the radius to a minimum thisBlob->radius = BlobPropertiesPtr->bobSmallestRadius; } for (index = 0; index < blobObjects->numSpaceObjs; index++) { //for every object in this blob object = blobObjects->SpaceObjPtr[index]; switch (object->objtype) { //see what type of ship it is case OBJ_ShipType: if (!allianceIsShipAlly((Ship *)object, universe.curPlayerPtr)) { //only count enemy units shipClass = (ubyte)((ShipStaticInfo *)((Ship *)object)->staticinfo)->shipclass; if (smStrchr(bobCapitalClass, shipClass) != NULL) { thisBlob->nCapitalShips++; } else if (smStrchr(bobAttackClass, shipClass) != NULL) { thisBlob->nAttackShips++; } else { thisBlob->nNonCombat++; } if (!bitTest(object->flags,SOF_Cloaked)) { //if there is an uncloaked enemy ship here bitSet(thisBlob->flags, BTF_UncloakedEnemies); } } else { thisBlob->nFriendlyShips++; } thisBlob->shipMass += ((Ship *)object)->staticinfo->staticheader.mass; //!!! bryce: change this definition #define probeActive(ship) TRUE if (((Ship *)object)->playerowner == universe.curPlayerPtr && !(object->flags & SOF_Disabled)) //if shipis players and isn't disabled! { //if this is one of the player's ships bitSet(thisBlob->flags, BTF_Explored); if (((Ship *)object)->shiptype == Probe && probeActive((Ship *)object)) { //if player has an active probe in the blob bitSet(thisBlob->flags, BTF_ProbeDroid); } else if (((Ship *)object)->shiptype == Carrier) { bitSet(thisBlob->flags, BTF_Carrier); } else if (((Ship *)object)->shiptype == Mothership) { bitSet(thisBlob->flags, BTF_Mothership); } } else if (allianceIsShipAlly((Ship *)object,universe.curPlayerPtr) && !(object->flags & SOF_Disabled)) { bitSet(thisBlob->flags, BTF_Explored); } else { //not friendly or allied if (((Ship *)object)->shiptype == GravWellGenerator) { //enemy gravwell if (((GravWellGeneratorSpec *)(((Ship *)object)->ShipSpecifics))->GravFieldOn) { //if it's active bGravWellDetected = TRUE; gravWell = (Ship *)object; } } else if (((Ship *)object)->shiptype == Probe) { //enemy probe bProbeDetected = TRUE; probe = (Ship *)object; } } if (((Ship *)object)->recentlyAttacked != 0) { bitSet(thisBlob->flags, BTF_GettingAttacked); } if (((Ship *)object)->shipisattacking) { bitSet(thisBlob->flags, BTF_Attacking); } break; case OBJ_AsteroidType: bitSet(thisBlob->flags, BTF_Asteroid); thisBlob->nRockRUs += ((Resource *)object)->staticinfo->resourcevalue; thisBlob->nRocks++; goto resourceCommon; case OBJ_GasType: goto resourceCommon; case OBJ_DustType: bitSet(thisBlob->flags, BTF_DustCloud); thisBlob->nDustRUs += ((Resource *)object)->staticinfo->resourcevalue; thisBlob->nDustClouds++; goto resourceCommon; case OBJ_NebulaType: bitSet(thisBlob->flags, BTF_Nebula); thisBlob->nDustRUs += ((Resource *)object)->staticinfo->resourcevalue; resourceCommon: thisBlob->RUs += ((Resource *)object)->staticinfo->resourcevalue; break; } } if (bitTest(thisBlob->flags, BTF_Explored)) { if (bProbeDetected) { battleChatterFleetAttempt(SOUND_EVENT_DEFAULT, BCE_ProbeDetected, SOUND_EVENT_DEFAULT, &probe->posinfo.position); } if (bGravWellDetected) { battleChatterFleetAttempt(SOUND_EVENT_DEFAULT, BCE_GravwellDetected, SOUND_EVENT_DEFAULT, &gravWell->posinfo.position); } } //set maximum sensors levels the same as having a probe in every blob if we are in ghost mode. if (smGhostMode) { bitSet(thisBlob->flags, BTF_ProbeDroid); } //explicitly say a player ship is in this blob if the correct sensors level } /*----------------------------------------------------------------------------- Name : bobListUpdate Description : Updates the current blob list by combining spheres (as needed) and splitting spheres (as needed). Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void bobListUpdate(LinkedList *list) { Node *thisNode, *otherNode; blob *thisBlob, *otherBlob; sdword nHits; real32 distance, radius, checkradius, newRadius, newVolume, newDensity; vector difference; real32 thisblobSortDistance; real32 otherblobSortDistance; real32 bobOverlapFactor = BlobPropertiesPtr->bobOverlapFactor; real32 bobSqrtOverlapFactor = BlobPropertiesPtr->bobSqrtOverlapFactor; real32 bobDensityHigh = BlobPropertiesPtr->bobDensityHigh; real32 bobBiggestRadius = BlobPropertiesPtr->bobBiggestRadius; real32 checkdist; bobStatsInitialBlobs(list->num); //first pass: go through list repeatedly until there are no more spheres to combine do { nHits = 0; thisNode = list->head; bobStatsPass(); while (thisNode != NULL) { thisBlob = (blob *)listGetStructOfNode(thisNode); if (thisBlob->sqrtSortDistance == 0.0f) { thisBlob->sqrtSortDistance = fsqrt(thisBlob->sortDistance); } thisblobSortDistance = thisBlob->sqrtSortDistance; //walk forward through list combining any spheres which could combine with this one otherNode = thisNode->next; while (otherNode != NULL) { //get distance bettween blobs and compare to sum of radii bobStatsWalk(); otherBlob = (blob *)listGetStructOfNode(otherNode);//get distance bettween nodes if (otherBlob->sqrtSortDistance == 0.0f) { otherBlob->sqrtSortDistance = fsqrt(otherBlob->sortDistance); } otherblobSortDistance = otherBlob->sqrtSortDistance; checkdist = otherblobSortDistance - thisblobSortDistance; //a test for early aborting of this comparison process if (checkdist > (thisBlob->radius + bobBiggestRadius)) { bobStatsTrivialReject(); break; } radius = thisBlob->radius + otherBlob->radius; checkradius = radius * bobSqrtOverlapFactor; if (checkdist > checkradius) { goto nextnode; } bobStatsCheck(); vecSub(difference, thisBlob->centre, otherBlob->centre); if (!isBetweenInclusive(difference.x,-checkradius,checkradius)) { goto nextnode; } if (!isBetweenInclusive(difference.y,-checkradius,checkradius)) { goto nextnode; } if (!isBetweenInclusive(difference.z,-checkradius,checkradius)) { goto nextnode; } distance = vecMagnitudeSquared(difference); radius *= radius; //combined radii^2 if (distance <= (radius*bobOverlapFactor)) { //if these blobs are sufficiently overlapping //determine if the new sphere would match density criteria bobStatsCombine(); newRadius = (((real32)fsqrt(distance)) + thisBlob->radius + otherBlob->radius) * 0.5f; newVolume = sphereVolume(newRadius); newDensity = (thisBlob->totalMass + otherBlob->totalMass) / newVolume; if (newDensity > max(thisBlob->totalMass * thisBlob->oneOverVolume, otherBlob->totalMass * otherBlob->oneOverVolume)) { //if new sphere is denser than the greater of the two spheres otherNode = bobBlobCombine(thisBlob, otherBlob, newRadius, newVolume); nHits++; } else if (newDensity > bobDensityHigh && newRadius < bobBiggestRadius) { //if combination will increase density otherNode = bobBlobCombine(thisBlob, otherBlob, newRadius, newVolume); nHits++; } else { //else we're not to combine them otherNode = otherNode->next; } } else { nextnode: otherNode = otherNode->next; } } thisNode = thisNode->next; } } while (nHits > 0); bobStatsFinalBlobs(list->num); //do a pass over all the final nodes to determine how to render them thisNode = list->head; while (thisNode != NULL) { thisBlob = listGetStructOfNode(thisNode); thisNode = thisNode->next; if (bitTest(thisBlob->flags, BTF_ClearObjectFlags)) { bobAllObjectsUnflagDontUpdate(thisBlob); //remove the update flag } if (!bitTest(thisBlob->flags, BTF_DontUpdate)) { bobBlobItemize(thisBlob, universe.curPlayerPtr->sensorLevel); } } } /*----------------------------------------------------------------------------- Name : blobFreeContents Description : frees the blob's contents Inputs : thisBlob Outputs : Return : ----------------------------------------------------------------------------*/ void blobFreeContents(blob *thisBlob) { #ifndef HW_Release // dbgAssert(thisBlob->debugFlag == 0); #endif if (thisBlob->blobShips != NULL) memFree(thisBlob->blobShips); if (thisBlob->blobBigShips != NULL) memFree(thisBlob->blobBigShips); if (thisBlob->blobSmallShips != NULL) memFree(thisBlob->blobSmallShips); if (thisBlob->blobSmallTargets != NULL) memFree(thisBlob->blobSmallTargets); if (thisBlob->blobBigTargets != NULL) memFree(thisBlob->blobBigTargets); if (thisBlob->blobResources != NULL) memFree(thisBlob->blobResources); if (thisBlob->blobDerelicts != NULL) memFree(thisBlob->blobDerelicts); if (thisBlob->blobBullets != NULL) memFree(thisBlob->blobBullets); if (thisBlob->blobMissileMissiles != NULL) memFree(thisBlob->blobMissileMissiles); if (thisBlob->blobMissileMines != NULL) memFree(thisBlob->blobMissileMines); if (bitTest(thisBlob->flags, BTF_FreeBlobObjects)) { if (thisBlob->blobObjects != NULL) memFree(thisBlob->blobObjects); } } /*----------------------------------------------------------------------------- Name : blobFree Description : frees a blob Inputs : thisBlob Outputs : Return : ----------------------------------------------------------------------------*/ void blobFree(blob *thisBlob) { blobFreeContents(thisBlob); if (bitTest(thisBlob->flags, BTF_FreeThisBlob)) { memFree(thisBlob); } } /*----------------------------------------------------------------------------- Name : bobListDelete Description : Delete a list of blobs, along with any sub-blobs they might be hiding. Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void bobListDelete(LinkedList *list) { Node *blobnode = list->head; Node *nextnode; blob *tblob; while (blobnode != NULL) { tblob = (blob *)listGetStructOfNode(blobnode); nextnode = blobnode->next; listRemoveNode(blobnode); if (tblob->subBlobs.num != BIT31) { //if there is a sub-blob list bobListDelete(&tblob->subBlobs); } blobFree(tblob); blobnode = nextnode; } dbgAssert(list->num == 0); } #if 0 /*----------------------------------------------------------------------------- Name : bobGetChecksum Description : Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ real32 bobGetChecksum(LinkedList *list,sdword *numBlobsInChecksum) { Node *blobnode = list->head; blob *thisBlob; real32 x=0.0f; real32 y=0.0f; real32 z=0.0f; sdword countBlobs = 0; while (blobnode != NULL) { thisBlob = (blob *)listGetStructOfNode(blobnode); x += thisBlob->centre.x; y += thisBlob->centre.y; z += thisBlob->centre.z; if ((netlogfile) && (logEnable == LOG_VERBOSE)) { #if BINNETLOG binnetBlobInfo bi; bi.header = makenetcheckHeader('B','1','0','B'); bi.collBlobSortDist = thisBlob->sortDistance; bi.x = thisBlob->centre.x; bi.y = thisBlob->centre.y; bi.z = thisBlob->centre.z; bi.r = thisBlob->radius; bi.numSpaceObjs = thisBlob->blobObjects->numSpaceObjs; fwrite(&bi, sizeof(bi), 1 ,netlogfile); #else fprintf(netlogfile," Blob:%f %f %f %d\n",thisBlob->centre.x,thisBlob->centre.y,thisBlob->centre.z,thisBlob->blobObjects->numSpaceObjs); #endif } countBlobs++; blobnode = blobnode->next; } *numBlobsInChecksum = countBlobs; return (x+y+z); } #endif /*----------------------------------------------------------------------------- Name : bobRemoveMineFromSpecificBlob Description : removes a mine from thisBlob Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void bobRemoveMineFromSpecificBlob(blob *thisBlob,Missile *mine) { RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobObjects,(SpaceObj *)mine); RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobMissileMines,(SpaceObj *)mine); mine->collMyBlob = NULL; } /*----------------------------------------------------------------------------- Name : bobObjectDied Description : Deletes a spaceobj out of the blobs Inputs : object, list Outputs : Return : ----------------------------------------------------------------------------*/ void bobObjectDied(SpaceObj *object,LinkedList *list) { //remove from any blob lists it may be in Node *node = list->head; blob *thisBlob; sdword index; SpaceObjSelection *blobObjects; while (node != NULL) { //search every blob thisBlob = (blob *)listGetStructOfNode(node); if (thisBlob->subBlobs.num != BIT31) { //if there is a sub-blob list in this blob bobObjectDied(object, &thisBlob->subBlobs); } blobObjects = thisBlob->blobObjects; for (index = 0; index < blobObjects->numSpaceObjs; index++) { //search every object in blob if ((SpaceObj *)object == blobObjects->SpaceObjPtr[index]) { //if this is the blob for (; index < blobObjects->numSpaceObjs - 1; index++) { //for the rest of the list blobObjects->SpaceObjPtr[index] = blobObjects->SpaceObjPtr[index + 1]; } blobObjects->numSpaceObjs--; //mode one notch back in the array if (object->flags & SOF_Targetable) { if(object->objtype != OBJ_MissileType) { if (((TargetPtr)object)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_TARGET_SIZE) { if (thisBlob->blobBigTargets != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobBigTargets,object); dbgAssert(result); } } else { //don't do for mines...they won't be in the list if (thisBlob->blobSmallTargets != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobSmallTargets,object); dbgAssert(result); } } } } switch (object->objtype) { case OBJ_ShipType: if (((ShipPtr)object)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_SHIP_SIZE) { if (thisBlob->blobBigShips != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobBigShips,object); dbgAssert(result); } } else { if (thisBlob->blobSmallShips != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobSmallShips,object); dbgAssert(result); } } if (thisBlob->blobShips != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobShips,object); dbgAssert(result); } bitSet(thisBlob->flags, BTF_RecentDeath); break; case OBJ_BulletType: if (thisBlob->blobBullets != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobBullets,object); dbgAssert(result); } break; case OBJ_AsteroidType: case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: if (thisBlob->blobResources != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobResources,object); dbgAssert(result); } bitSet(thisBlob->flags, BTF_RecentDeath); break; case OBJ_DerelictType: if (thisBlob->blobDerelicts != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobDerelicts,object); dbgAssert(result); } break; case OBJ_MissileType: if (((MissilePtr)object)->missileType == MISSILE_Mine) { if (thisBlob->blobMissileMines != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobMissileMines,object); dbgAssert(result); } } else { if (thisBlob->blobMissileMissiles != NULL) { bool result = RemoveSpaceObjFromSelectionPreserveOrder((SpaceObjSelection *)thisBlob->blobMissileMissiles,object); dbgAssert(result); } } break; default: dbgAssert(FALSE); break; } #if 0 // don't do verify here - objects may be deleted from blob before being removed from lists resulting in inconsistancies #if BOB_ANAL_CHECKING if (thisBlob->subBlobs.num == BIT31) { blobAnalVerify(thisBlob); } #endif #endif goto foundInBlobs; } } node = node->next; } foundInBlobs:; } /*----------------------------------------------------------------------------- Name : AddSpaceObjToSelectionPreserveOrder Description : Adds obj to selection, while preserving sort order (based on collOptimizeDist) Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void AddSpaceObjToSelectionPreserveOrder(SpaceObjSelection *selection,SpaceObj *obj) { sdword bottom,top; sdword numObjects = selection->numSpaceObjs; sdword foundindex; if (numObjects == 0) { // just add new object: selection->SpaceObjPtr[0] = obj; selection->numSpaceObjs = 1; return; } #if BOB_ANAL_CHECKING if (ShipInSelection((SelectCommand *)selection,(Ship *)obj)) { dbgFatalf(DBG_Loc,"obj %d already in selection!",obj->objtype); } #endif bottom = 0; top = numObjects-1; while (top >= bottom) { foundindex = (bottom+top)>>1; if (obj->collOptimizeDist > selection->SpaceObjPtr[foundindex]->collOptimizeDist) { // foundindex is too small bottom = foundindex+1; } else { top = foundindex-1; } } dbgAssert(foundindex >= 0); dbgAssert(foundindex < numObjects); if (obj->collOptimizeDist < selection->SpaceObjPtr[foundindex]->collOptimizeDist) { AddSpaceObjToSelectionBeforeIndex(obj,selection,foundindex); } else { AddSpaceObjToSelectionAfterIndex(obj,selection,foundindex); } } #if 0 void *myMemRealloc(void *currentPointer, sdword newSize, char *name, udword flags) { ubyte *newptr; newptr = memAlloc(newSize,name,flags); memcpy(newptr,currentPointer,newSize-4); memFree(currentPointer); return newptr; } #endif /*----------------------------------------------------------------------------- Name : AddObjToObjectListsOfBlob Description : Add an object to a specific blob Inputs : obj - object to add to blob putInBlob - blob to put obj in Outputs : Return : ----------------------------------------------------------------------------*/ void AddObjToObjectListsOfBlob(SpaceObj *obj,blob *putInBlob) { if (obj->flags & SOF_Targetable) { if(obj->objtype != OBJ_MissileType) { //don't add missiles to big/small target lists. if (((TargetPtr)obj)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_TARGET_SIZE) { if ((putInBlob->blobBigTargets->numTargets+1) > putInBlob->maxNumBigTargets) { putInBlob->maxNumBigTargets += BLOBOBJ_BATCH; putInBlob->blobBigTargets = memRealloc(putInBlob->blobBigTargets,sizeofSelectCommand(putInBlob->maxNumBigTargets),"nbbt(nblobbtargets)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobBigTargets,obj); } else { if ((putInBlob->blobSmallTargets->numTargets+1) > putInBlob->maxNumSmallTargets) { putInBlob->maxNumSmallTargets += BLOBOBJ_BATCH; putInBlob->blobSmallTargets = memRealloc(putInBlob->blobSmallTargets,sizeofSelectCommand(putInBlob->maxNumSmallTargets),"nbst(nblobstargets)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobSmallTargets,obj); } } } switch (obj->objtype) { case OBJ_ShipType: if (((ShipPtr)obj)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_SHIP_SIZE) { if ((putInBlob->blobBigShips->numShips+1) > putInBlob->maxNumBigShips) { putInBlob->maxNumBigShips += BLOBOBJ_BATCH; putInBlob->blobBigShips = memRealloc(putInBlob->blobBigShips,sizeofSelectCommand(putInBlob->maxNumBigShips),"nbbs(nblobbships)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobBigShips,obj); } else { if ((putInBlob->blobSmallShips->numShips+1) > putInBlob->maxNumSmallShips) { putInBlob->maxNumSmallShips += BLOBOBJ_BATCH; putInBlob->blobSmallShips = memRealloc(putInBlob->blobSmallShips,sizeofSelectCommand(putInBlob->maxNumSmallShips),"nbss(nblobsships)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobSmallShips,obj); } if ((putInBlob->blobShips->numShips+1) > putInBlob->maxNumShips) { putInBlob->maxNumShips += BLOBOBJ_BATCH; putInBlob->blobShips = memRealloc(putInBlob->blobShips,sizeofSelectCommand(putInBlob->maxNumShips),"nbs(nblobships)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobShips,obj); break; case OBJ_BulletType: if ((putInBlob->blobBullets->numBullets+1) > putInBlob->maxNumBullets) { putInBlob->maxNumBullets += BLOBOBJ_BATCH; putInBlob->blobBullets = memRealloc(putInBlob->blobBullets,sizeofSelectCommand(putInBlob->maxNumBullets),"nbb(nblobbullets)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobBullets,obj); break; case OBJ_AsteroidType: case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: if ((putInBlob->blobResources->numResources+1) > putInBlob->maxNumResources) { putInBlob->maxNumResources += BLOBOBJ_BATCH; putInBlob->blobResources = memRealloc(putInBlob->blobResources,sizeofSelectCommand(putInBlob->maxNumResources),"nbr(nblobresources)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobResources,obj); break; case OBJ_DerelictType: if ((putInBlob->blobDerelicts->numDerelicts+1) > putInBlob->maxNumDerelicts) { putInBlob->maxNumDerelicts += BLOBOBJ_BATCH; putInBlob->blobDerelicts = memRealloc(putInBlob->blobDerelicts,sizeofSelectCommand(putInBlob->maxNumDerelicts),"nbd(nblobderelicts)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobDerelicts,obj); break; case OBJ_MissileType: if (((MissilePtr)obj)->missileType == MISSILE_Mine) { if ((putInBlob->blobMissileMines->numMissiles+1) > putInBlob->maxNumMissileMines) { putInBlob->maxNumMissileMines += BLOBOBJ_BATCH; putInBlob->blobMissileMines = memRealloc(putInBlob->blobMissileMines,sizeofSelectCommand(putInBlob->maxNumMissileMines),"nbm(nblobmines)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobMissileMines,obj); } else { if ((putInBlob->blobMissileMissiles->numMissiles+1) > putInBlob->maxNumMissileMissiles) { putInBlob->maxNumMissileMissiles += BLOBOBJ_BATCH; putInBlob->blobMissileMissiles = memRealloc(putInBlob->blobMissileMissiles,sizeofSelectCommand(putInBlob->maxNumMissileMissiles),"nbm(nblobmissiles)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder((SpaceObjSelection *)putInBlob->blobMissileMissiles,obj); } break; default: dbgAssert(FALSE); break; } if ((putInBlob->blobObjects->numSpaceObjs+1) > putInBlob->maxNumObjects) { dbgAssert(bitTest(putInBlob->flags, BTF_FreeBlobObjects)); putInBlob->maxNumObjects += BLOBOBJ_BATCH; putInBlob->blobObjects = memRealloc(putInBlob->blobObjects,sizeofSelectCommand(putInBlob->maxNumObjects),"nbo(nblobobjects)",Pyrophoric); } AddSpaceObjToSelectionPreserveOrder(putInBlob->blobObjects,obj); //blobAnalVerify(putInBlob); INVALID TO PUT HERE: Missiles are added to blobs before the missile list, will fail! } /*----------------------------------------------------------------------------- Name : bobAddObjToSpecificBlob Description : Adds obj to putInBlob, while preserving sort order Inputs : putInBlob, obj Outputs : Return : Note : Must be collision blobs, which is sorted ----------------------------------------------------------------------------*/ void bobAddObjToSpecificBlob(blob *putInBlob,SpaceObj *obj) { vector distvec; dbgAssert((obj->flags & SOF_Dead) == 0); if (obj->objtype == OBJ_AsteroidType) { dbgAssert(((Asteroid *)obj)->asteroidtype != Asteroid0); // don't put Asteroid0 in for speed } dbgAssert(putInBlob); #ifndef HW_Release // dbgAssert(putInBlob->debugFlag == 0); #endif vecSub(distvec,obj->posinfo.position,putInBlob->centre); obj->collOptimizeDist = fsqrt(vecMagnitudeSquared(distvec)); obj->collMyBlob = putInBlob; AddObjToObjectListsOfBlob(obj,putInBlob); } /*----------------------------------------------------------------------------- Name : bobAddObjToNearestBlob Description : Adds obj to nearest blob in list, while preserving sort order Inputs : list, obj Outputs : Return : Note : Must be collision blobs, which is sorted ----------------------------------------------------------------------------*/ void bobAddObjToNearestBlob(LinkedList *list,SpaceObj *obj) { real32 distsqr; blob *putInBlob; dbgAssert((obj->flags & SOF_Dead) == 0); if (obj->objtype == OBJ_AsteroidType) { dbgAssert(((Asteroid *)obj)->asteroidtype != Asteroid0); // don't put Asteroid0 in for speed } putInBlob = bobFindNearestBlobToObject(list,obj,&distsqr); if (putInBlob == NULL) { return; } #ifndef HW_Release // dbgAssert(putInBlob->debugFlag == 0); #endif obj->collOptimizeDist = fsqrt(distsqr); obj->collMyBlob = putInBlob; AddObjToObjectListsOfBlob(obj,putInBlob); } #if BOB_ANAL_CHECKING /*----------------------------------------------------------------------------- Name : bobAnalShipInSelection Description : returns TRUE if ship is in selection Inputs : selection, ship Outputs : Return : number of times object is in selection ----------------------------------------------------------------------------*/ sdword bobAnalShipInSelection(SelectCommand *selection,Ship *ship) { sdword i, nFound = 0; for (i=0;inumShips;i++) { if (selection->ShipPtr[i] == ship) { nFound++; } } return(nFound); } /*----------------------------------------------------------------------------- Name : blobAnalVerifyFn Description : Anally check all lists in the specified blob. Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void blobAnalVerifyFn(blob *thisBlob) { sdword index; SpaceObjSelection *blobObjects; SpaceObj *object; bool result; sdword numShips, numSmallShips, numBigShips, numSmallTargets, numBigTargets, numResources, numDerelicts, numBullets, numMissileMissiles, numMissileMines, numObjects; //verify the main object list blobObjects = thisBlob->blobObjects; numObjects = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { //search every object in blob object = blobObjects->SpaceObjPtr[index]; dbgAssert(object->collMyBlob == thisBlob); dbgAssert(1 == bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects,(Ship *)object)); if (object->flags & SOF_Targetable) { if(obj->objtype != OBJ_MissileType) { if (((TargetPtr)object)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_TARGET_SIZE) { dbgAssert(thisBlob->blobBigTargets != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobBigTargets, (Ship *)object); dbgAssert(result == 1); } else { dbgAssert(thisBlob->blobSmallTargets != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobSmallTargets, (Ship *)object); dbgAssert(result == 1); } } } switch (object->objtype) { case OBJ_ShipType: if (((ShipPtr)object)->staticinfo->staticheader.staticCollInfo.originalcollspheresize >= BIG_SHIP_SIZE) { dbgAssert(thisBlob->blobBigShips != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobBigShips, (Ship *)object); dbgAssert(result == 1); } else { dbgAssert(thisBlob->blobSmallShips != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobSmallShips, (Ship *)object); dbgAssert(result == 1); } dbgAssert(thisBlob->blobShips != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobShips, (Ship *)object); dbgAssert(result == 1); break; case OBJ_BulletType: dbgAssert(thisBlob->blobBullets != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobBullets, (Ship *)object); dbgAssert(result == 1); break; case OBJ_AsteroidType: case OBJ_NebulaType: case OBJ_GasType: case OBJ_DustType: dbgAssert(thisBlob->blobResources != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobResources, (Ship *)object); dbgAssert(result == 1); break; case OBJ_DerelictType: dbgAssert(thisBlob->blobDerelicts != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobDerelicts, (Ship *)object); dbgAssert(result == 1); break; case OBJ_MissileType: if (((MissilePtr)object)->missileType == MISSILE_Mine) { dbgAssert(thisBlob->blobMissileMines != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobMissileMines, (Ship *)object); dbgAssert(result == 1); } else { dbgAssert(thisBlob->blobMissileMissiles != NULL); result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobMissileMissiles, (Ship *)object); dbgAssert(result == 1); } break; default: dbgAssert(FALSE); break; } } //now verify the individually itemized lists //verify ship list blobObjects = (SpaceObjSelection *)thisBlob->blobShips; dbgAssert(blobObjects->numSpaceObjs <= universe.ShipList.num); //dbgAssert(thisBlob->blobBigShips->numShips + thisBlob->blobSmallShips->numShips == thisBlob->blobShips->numShips); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumShips); numShips = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->collMyBlob == thisBlob); dbgAssert(object->objtype == OBJ_ShipType); } //verify small ship list blobObjects = (SpaceObjSelection *)thisBlob->blobSmallShips; dbgAssert(blobObjects->numSpaceObjs <= universe.ShipList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumSmallShips); numSmallShips = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->collMyBlob == thisBlob); dbgAssert(object->objtype == OBJ_ShipType); } //verify big ship list blobObjects = (SpaceObjSelection *)thisBlob->blobBigShips; dbgAssert(blobObjects->numSpaceObjs <= universe.ShipList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumBigShips); numBigShips = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->collMyBlob == thisBlob); dbgAssert(object->objtype == OBJ_ShipType); } //verify target lists blobObjects = (SpaceObjSelection *)thisBlob->blobSmallTargets; dbgAssert(blobObjects->numSpaceObjs <= universe.ImpactableList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumSmallTargets); numSmallTargets = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(bitTest(object->flags, SOF_Targetable)); } blobObjects = (SpaceObjSelection *)thisBlob->blobBigTargets; dbgAssert(blobObjects->numSpaceObjs <= universe.ImpactableList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumBigTargets); numBigTargets = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(bitTest(object->flags, SOF_Targetable)); } //verify resource list blobObjects = (SpaceObjSelection *)thisBlob->blobResources; dbgAssert(blobObjects->numSpaceObjs <= universe.ResourceList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumResources); numResources = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->objtype == OBJ_AsteroidType || object->objtype == OBJ_NebulaType || object->objtype == OBJ_DustType || object->objtype == OBJ_GasType); } //verify derelict list blobObjects = (SpaceObjSelection *)thisBlob->blobDerelicts; dbgAssert(blobObjects->numSpaceObjs <= universe.DerelictList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumDerelicts); numDerelicts = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->objtype == OBJ_DerelictType); } //verify bullet list blobObjects = (SpaceObjSelection *)thisBlob->blobBullets; dbgAssert(blobObjects->numSpaceObjs <= universe.BulletList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumBullets); numBullets = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->objtype == OBJ_BulletType); } //verify missile list blobObjects = (SpaceObjSelection *)thisBlob->blobMissileMissiles; dbgAssert(blobObjects->numSpaceObjs <= universe.MissileList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumMissileMissiles); numMissileMissiles = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->objtype == OBJ_MissileType); dbgAssert(((MissilePtr)object)->missileType == MISSILE_Regular); } //verify mine list blobObjects = (SpaceObjSelection *)thisBlob->blobMissileMines; dbgAssert(blobObjects->numSpaceObjs <= universe.MissileList.num); dbgAssert(blobObjects->numSpaceObjs <= thisBlob->maxNumMissileMines); numMissileMines = blobObjects->numSpaceObjs; for (index = 0; index < blobObjects->numSpaceObjs; index++) { object = blobObjects->SpaceObjPtr[index]; result = bobAnalShipInSelection((SelectCommand *)thisBlob->blobObjects, (Ship *)object); dbgAssert(result == 1); dbgAssert(object->objtype == OBJ_MissileType); dbgAssert(((MissilePtr)object)->missileType == MISSILE_Mine); } //numeric verifications dbgAssert(numSmallShips + numBigShips == numShips); dbgAssert(numMissileMines + numMissileMissiles + numBullets + numDerelicts + numResources + numShips == numObjects); } #endif