bool CLIENTFX_INSTANCE::IsFinished() { if (IsDone()) return true; CLinkListNode<FX_LINK> *pActiveNode = m_collActiveFX.GetHead(); CBaseFX *pFX = LTNULL; while (pActiveNode) { pFX = pActiveNode->m_Data.m_pFX; // Check for expiration if( pFX ) { //determine if this effect has expired bool bExpired = ((pFX->GetElapsed() >= pFX->GetEndTime()) || pFX->IsShuttingDown()) && (pFX->IsFinishedShuttingDown() || !pActiveNode->m_Data.m_pRef->m_bSmoothShutdown); if (!bExpired) return false; } pActiveNode = pActiveNode->m_pNext; } return true; }
//------------------------------------------------------------------ // // FUNCTION : UpdateInstanceInterval() // // PURPOSE : Given an instance and a time interval, this will appropriately update // all effects contained within that interval // //------------------------------------------------------------------ void CClientFXMgr::UpdateInstanceInterval(CLIENTFX_INSTANCE* pInst, float fStartInterval, float fEndInterval) { //here are the possible scenarios: // Inactive // Inactive -> Active -> Shutting down // Inactive -> Active -> Shutting down -> Inactive // Inactive -> Active // Shutting Down // Shutting Down -> Inactive // Shutting Down -> Active -> Shutting Down // Shutting Down -> Active -> Shutting Down -> Inactive // Shutting Down -> Active // Active //A note about intervals: The interval used is inclusive of both start and ends. This does //mean that a boundary such as a key beginning or end can be hit twice, but because of the //way the state flow works, this will not cause any issues, since when it is inactive //it will look for active and vice a versa. Therefore since the beginning and end cannot //lie on the same position this should not introduce any issues, but ensures that the //full track length is handled when updating //alright, run through all active effects and determine what to do CLinkListNode<FX_LINK> *pActiveNode; CLinkListNode<FX_LINK> *pNextActiveNode; //determine if this is a looping instance bool bLoopInstance = pInst->m_bLoop; pActiveNode = pInst->m_collActiveFX.GetHead(); while( pActiveNode ) { //cache the next node in case we delete this key pNextActiveNode = pActiveNode->m_pNext; //alright, first off look at the state of the key and see what to do CBaseFX *pFX = pActiveNode->m_Data.m_pFX; const FX_KEY* pKey = pActiveNode->m_Data.m_pRef; //this is our time slice beginning float fCurrStart = fStartInterval; //skip dead space on inactive effects if(!pFX->IsActive()) { //sanity check, the shutting down flag should never be set without active assert(!pFX->IsShuttingDown()); //This check is to verify that we aren't shutting down the instance and have inactive effects. //If we are shutting down, inactive effects are immediately pruned, and then active effects //should be removed as they are completed assert(!pInst->m_bShutdown); //figure out the start time of this effect float fKeyStart = pKey->m_tmStart; //this effect is not active. See if it should be (loop keys always should be) if(pKey->m_bContinualLoop || ((fKeyStart >= fCurrStart) && (fKeyStart <= fEndInterval))) { //our effect has just become active, we need to reset its elapsed time to 0, //and switch it over to an initial frame pFX->SetElapsed(0.0f); pFX->SetState(FS_ACTIVE | FS_INITIALFRAME); pFX->ClearState(FS_SHUTTINGDOWN); pFX->SetVisible(true); //handle applying a fake effect offset ApplyEffectStartingOffset(pFX, pKey); //move our timeslice forward to the beginning of the key fCurrStart = fKeyStart; } } if(pFX->IsActive()) { //we are an active effect, which means we are either in the time block or are //shutting down. //see if we are currently shutting down, and if we are going to transition into //becoming active if(pFX->IsShuttingDown()) { float fKeyStart = pKey->m_tmStart; //however, we can only bring it into the active state from here if the instance //will allow us bool bCanActivate = pInst->m_bLoop && !pInst->m_bShutdown; //this effect is not active. See if it should be if(bCanActivate && (fKeyStart >= fCurrStart) && (fKeyStart <= fEndInterval)) { //it will become active in this range, so move up to there and change state pFX->Update(fKeyStart - fCurrStart); pFX->SetElapsed(0.0f); pFX->SetState(FS_INITIALFRAME | FS_ACTIVE); pFX->ClearState(FS_SHUTTINGDOWN); //handle applying a fake effect offset ApplyEffectStartingOffset(pFX, pKey); //move our time position up to the key start fCurrStart = fKeyStart; } } //alright, we are now in a chunk of time where either the block is active //or shutting down. If it is active, we need to update until the end of the slice //or the end of the effect if(!pFX->IsShuttingDown()) { //we aren't shutting down, so we can update like normal float fTimeBlockEnd = fEndInterval; bool bCompleteKey = ((pKey->m_tmEnd <= fTimeBlockEnd) && (pKey->m_tmEnd >= fCurrStart)); //see if we hit the end of the key if(bCompleteKey) { fTimeBlockEnd = pKey->m_tmEnd; } //update based upon the interval length pFX->Update(fTimeBlockEnd - fCurrStart); //the initial update state should be cleared now pFX->ClearState(FS_INITIALFRAME); //see if we completed the key if(bCompleteKey) { //we did, so now switch to a shutting down state if it isn't continually looping, //otherwise we need to reset the elapsed time to 0 if(pKey->m_bContinualLoop && bLoopInstance) { //we have looped our key, so reset the elapsed amount. However, if we are a //continually looping effect that started with an offset, we don't want //to lose that offset and should threfore just wrap based upon the lifespan if(pKey->m_fMaxStartOffset >= 0.001f) { pFX->SetElapsed((float)fmod(pFX->GetElapsed(), pFX->GetLifespan())); } else { //no starting offset, so just to make sure that everything syncs //up correctly and no error gets introduced, reset it to 0 pFX->SetElapsed(0.0f); } } else { //we're past our key, so start shutting down pFX->SetState(FS_SHUTTINGDOWN); } } //update our time start fCurrStart = fTimeBlockEnd; } //alright, now handle shutting down, in which case we just want to update however //much time we have left in this interval, and see if the effect is completed if(pFX->IsShuttingDown()) { //allow it to update pFX->Update(fEndInterval - fCurrStart); //can this effect do a smooth shutdown? bool bSmoothShutdown = pKey->m_bSmoothShutdown && pInst->m_bSmoothShutdown; //see if this effect is done shutting down if(pFX->IsFinishedShuttingDown() || !bSmoothShutdown) { //notify of an effect that has finished shutting down HandleShutdownEffect(pInst, pActiveNode); //move onto the next node and keep processing pActiveNode = pNextActiveNode; continue; } } } //and onto the next node pActiveNode = pNextActiveNode; } }
void CClientFXMgr::ShutdownClientFX(CLIENTFX_INSTANCE *pFxGroup) { CLinkListNode<CLIENTFX_INSTANCE *> *pActiveNode = m_collActiveGroupFX.GetHead(); //setup the callback in case any create effects as they are destroyed SetupCreateEffectCallback(); while (pActiveNode) { CLIENTFX_INSTANCE* pInst = pActiveNode->m_Data; if (pInst == pFxGroup) { // Shut it down !! pInst->m_bShutdown = true; HOBJECT hReplaceParent = NULL; //see if this effect performs a smooth shutdown, if so, we need to create a dummy //object to place everything under a dummy object if (pInst->m_bSmoothShutdown) { LTVector vPos; LTRotation rRot; if (pFxGroup->m_hParent) { m_pClientDE->GetObjectPos(pFxGroup->m_hParent, &vPos); m_pClientDE->GetObjectRotation(pFxGroup->m_hParent, &rRot); } else { vPos = pFxGroup->m_vPos; rRot = pFxGroup->m_rRot; } // Create a temporary parent object.... ObjectCreateStruct ocs; INIT_OBJECTCREATESTRUCT(ocs); ocs.m_ObjectType = OT_NORMAL; ocs.m_Pos = vPos; ocs.m_Rotation = rRot; ocs.m_Flags = 0; hReplaceParent = m_pClientDE->CreateObject(&ocs); pFxGroup->m_hAlternateParent = hReplaceParent; pFxGroup->m_hParent = hReplaceParent; } if (!hReplaceParent) { //we couldn't create or didn't need a replacement object, so just remove all fx pInst->m_hAlternateParent = NULL; pInst->m_hParent = NULL; pInst->RemoveAllEffects(); } else { // We have a parent to set, but in addition we need to notify all effects that //we are shutting down, and remove any effects that don't need a smooth shutdown (this //way it can be polled instantly after to determine if it is done) CLinkListNode<FX_LINK> *pActiveFxNode = pInst->m_collActiveFX.GetHead(); CLinkListNode<FX_LINK> *pNextActiveFxNode = NULL; while( pActiveFxNode ) { //cache the next in case this node is deleted pNextActiveFxNode = pActiveFxNode->m_pNext; CBaseFX *pFX = pActiveFxNode->m_Data.m_pFX; //we want to remove any inactive nodes if(!pFX->IsActive()) { pInst->DeleteFX(pActiveFxNode); } else { const FX_KEY* pKey = pActiveFxNode->m_Data.m_pRef; //reassign the parent of this object pFX->SetParent(hReplaceParent); //we have an active effect, set it to shutting down pFX->SetState(FS_SHUTTINGDOWN); //now that it is shutting down, see if it is complete if(pFX->IsFinishedShuttingDown() || !pInst->m_bSmoothShutdown || !pKey->m_bSmoothShutdown) { //we can just remove the effect pInst->DeleteFX(pActiveFxNode); } } pActiveFxNode = pNextActiveFxNode; } } break; } pActiveNode = pActiveNode->m_pNext; } }