void CReplayManager::RestoreSnapshot() { float watchDuration = gpGlobals->curtime - m_flReplayStartTime; if (watchDuration > m_flRunDuration) { if (m_nReplayRunIndex < m_nMaxReplayRuns - 1) { m_nReplayRunIndex += 1; m_bIsReplayStart = true; } else if (m_bIsHighlightReplay) { m_nReplayIndex = FindNextHighlightReplayIndex(m_nReplayIndex + 1, SDKGameRules()->State_Get()); if (m_nReplayIndex == -1) { StopReplay(); return; } m_nReplayRunIndex = 0; m_bIsReplayStart = true; m_bIsHighlightStart = true; } else { StopReplay(); return; } } MatchEvent *pMatchEvent = m_MatchEvents[m_nReplayIndex]; if (m_bIsReplayStart) { m_bIsReplayStart = false; m_bAtMinGoalPos = pMatchEvent->atMinGoalPos; m_bIsReplaying = true; m_bReplayIsPending = false; CalcMaxReplayRunsAndDuration(pMatchEvent, gpGlobals->curtime); watchDuration = 0; if (m_bIsHighlightReplay && m_bIsHighlightStart) { m_bIsHighlightStart = false; if (pMatchEvent->matchEventType == MATCH_EVENT_GOAL) { IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_goal"); if (pEvent) { pEvent->SetInt("second", pMatchEvent->second); pEvent->SetInt("match_period", pMatchEvent->matchPeriod); pEvent->SetInt("scoring_team", pMatchEvent->team); pEvent->SetString("scorer", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : ""); pEvent->SetString("first_assister", pMatchEvent->pPlayer2Data ? pMatchEvent->pPlayer2Data->m_szName : ""); pEvent->SetString("second_assister", pMatchEvent->pPlayer3Data ? pMatchEvent->pPlayer3Data->m_szName : ""); gameeventmanager->FireEvent(pEvent); } } else if (pMatchEvent->matchEventType == MATCH_EVENT_OWNGOAL) { IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_owngoal"); if (pEvent) { pEvent->SetInt("second", pMatchEvent->second); pEvent->SetInt("match_period", pMatchEvent->matchPeriod); pEvent->SetInt("scoring_team", pMatchEvent->team); pEvent->SetString("scorer", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : ""); gameeventmanager->FireEvent(pEvent); } } else if (pMatchEvent->matchEventType == MATCH_EVENT_MISS) { IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_miss"); if (pEvent) { pEvent->SetInt("second", pMatchEvent->second); pEvent->SetInt("match_period", pMatchEvent->matchPeriod); pEvent->SetInt("finishing_team", pMatchEvent->team); pEvent->SetString("finisher", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : ""); pEvent->SetString("first_assister", pMatchEvent->pPlayer2Data ? pMatchEvent->pPlayer2Data->m_szName : ""); pEvent->SetString("second_assister", pMatchEvent->pPlayer3Data ? pMatchEvent->pPlayer3Data->m_szName : ""); gameeventmanager->FireEvent(pEvent); } } else if (pMatchEvent->matchEventType == MATCH_EVENT_KEEPERSAVE) { IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_keepersave"); if (pEvent) { pEvent->SetInt("second", pMatchEvent->second); pEvent->SetInt("match_period", pMatchEvent->matchPeriod); pEvent->SetInt("keeper_team", pMatchEvent->team); pEvent->SetString("keeper", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : ""); pEvent->SetString("finisher", pMatchEvent->pPlayer2Data ? pMatchEvent->pPlayer2Data->m_szName : ""); gameeventmanager->FireEvent(pEvent); } } else if (pMatchEvent->matchEventType == MATCH_EVENT_REDCARD) { IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_redcard"); if (pEvent) { pEvent->SetInt("second", pMatchEvent->second); pEvent->SetInt("match_period", pMatchEvent->matchPeriod); pEvent->SetInt("fouling_team", pMatchEvent->team); pEvent->SetString("fouling_player", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : ""); gameeventmanager->FireEvent(pEvent); } } } } CBall *pRealBall = GetMatchBall(); if (pRealBall && !(pRealBall->GetEffects() & EF_NODRAW)) { pRealBall->AddEffects(EF_NODRAW); pRealBall->AddSolidFlags(FSOLID_NOT_SOLID); } for (int i = 1; i <= gpGlobals->maxClients; i++) { CSDKPlayer *pRealPl = ToSDKPlayer(UTIL_PlayerByIndex(i)); if (!CSDKPlayer::IsOnField(pRealPl)) continue; if (!(pRealPl->GetEffects() & EF_NODRAW)) { pRealPl->AddEffects(EF_NODRAW); pRealPl->DoServerAnimationEvent(PLAYERANIMEVENT_CANCEL); pRealPl->AddSolidFlags(FSOLID_NOT_SOLID); pRealPl->SetMoveType(MOVETYPE_NONE); } if (pRealPl->GetPlayerBall() && !(pRealPl->GetPlayerBall()->GetEffects() & EF_NODRAW)) { pRealPl->GetPlayerBall()->AddEffects(EF_NODRAW); pRealPl->GetPlayerBall()->AddSolidFlags(FSOLID_NOT_SOLID); } } float normalRunDuration = m_flRunDuration - m_flSlowMoDuration; if (watchDuration > normalRunDuration) watchDuration = normalRunDuration + m_flSlowMoCoeff * (watchDuration - normalRunDuration); // To find the correct snapshot calculate the time passed since we started watching the replay and match it to the right snapshot in our recording list Snapshot *pNextSnap = NULL; Snapshot *pSnap = NULL; float snapDuration = 0; // Traverse backwards looking for a recorded snapshot matching the time since replay start for (int i = pMatchEvent->snapshots.Count() - 1; i >= 0; i--) { // Save the snapshot of the previous iteration, so we have a snapshot to interpolate to when we found our target snapshot pNextSnap = pSnap; // Save the current snapshot pSnap = pMatchEvent->snapshots[i]; // Snapshots have absolute match times, so calculate the relative time span between the first recorded snapshot and the current snapshot snapDuration = pSnap->snaptime - pMatchEvent->snapshots[0]->snaptime - m_flReplayStartTimeOffset; // We usually only play the last x seconds of a replay instead of the whole thing, so subtract the start time offset from the time since the first snapshot. // The first snapshot which time span is equal or shorter than the duration since replay start is the one which should be shown next. if (snapDuration <= watchDuration) break; } // No snapshots in the list if (!pSnap) return; float nextSnapDuration; if (pNextSnap) nextSnapDuration = pNextSnap->snaptime - pMatchEvent->snapshots[0]->snaptime - m_flReplayStartTimeOffset; else nextSnapDuration = 0; BallSnapshot *pBallSnap = pSnap->pBallSnapshot; if (pBallSnap) { if (!m_pBall) { m_pBall = (CReplayBall *)CreateEntityByName("replayball"); m_pBall->Spawn(); } if (Q_strcmp(m_pBall->m_szSkinName, GetMatchBall()->GetSkinName())) Q_strncpy(m_pBall->m_szSkinName.GetForModify(), GetMatchBall()->GetSkinName(), MAX_KITNAME_LENGTH); m_pBall->VPhysicsGetObject()->SetPosition(pBallSnap->pos, pBallSnap->ang, false); m_pBall->VPhysicsGetObject()->SetVelocity(&pBallSnap->vel, &pBallSnap->rot); } else { UTIL_Remove(m_pBall); m_pBall = NULL; } float frac; if (pNextSnap) { // Calc fraction between both snapshots frac = clamp((watchDuration - snapDuration) / (nextSnapDuration - snapDuration), 0.0f, 1.0f); } else { // Exact snapshot time matched or no next snapshot to interpolate to frac = 0.0f; } for (int i = 0; i < 2; i++) { for (int j = 0; j < 11; j++) { PlayerSnapshot *pPlSnap = pSnap->pPlayerSnapshot[i][j]; if (!pPlSnap) { UTIL_Remove(m_pPlayers[i][j]); m_pPlayers[i][j] = NULL; continue; } if (!m_pPlayers[i][j]) { m_pPlayers[i][j] = (CReplayPlayer *)CreateEntityByName("replayplayer"); m_pPlayers[i][j]->Spawn(); m_pPlayers[i][j]->SetNumAnimOverlays(NUM_LAYERS_WANTED); } CReplayPlayer *pPl = m_pPlayers[i][j]; pPl->m_nTeamNumber = pPlSnap->teamNumber; pPl->m_nTeamPosIndex = pPlSnap->teamPosIndex; pPl->m_bIsKeeper = pPlSnap->isKeeper; pPl->m_nShirtNumber = pPlSnap->shirtNumber; if (Q_strcmp(pPl->m_szPlayerName, pPlSnap->pPlayerData->m_szName)) Q_strncpy(pPl->m_szPlayerName.GetForModify(), pPlSnap->pPlayerData->m_szName, MAX_PLAYER_NAME_LENGTH); if (Q_strcmp(pPl->m_szShirtName, pPlSnap->pPlayerData->m_szShirtName)) Q_strncpy(pPl->m_szShirtName.GetForModify(), pPlSnap->pPlayerData->m_szShirtName, MAX_PLAYER_NAME_LENGTH); pPl->m_nSkinIndex = pPlSnap->skinIndex; pPl->m_nBody = pPlSnap->body; PlayerSnapshot *pNextPlSnap = NULL; if (pNextSnap) pNextPlSnap = pNextSnap->pPlayerSnapshot[i][j]; if (frac > 0.0f && pNextPlSnap) { pPl->SetLocalOrigin(Lerp( frac, pPlSnap->pos, pNextPlSnap->pos )); //pPl->SetLocalVelocity(Lerp( frac, pPlSnap->vel, pNextPlSnap->vel )); pPl->SetLocalAngles(Lerp( frac, pPlSnap->ang, pNextPlSnap->ang )); } else { pPl->SetLocalOrigin(pPlSnap->pos); //pPl->SetLocalVelocity(pPlSnap->vel); pPl->SetLocalAngles(pPlSnap->ang); } bool interpolationAllowed; if (frac > 0.0f && pNextPlSnap && pPlSnap->masterSequence == pNextPlSnap->masterSequence) { // If the master state changes, all layers will be invalid too, so don't interp (ya know, interp barely ever happens anyway) interpolationAllowed = true; } else interpolationAllowed = false; // First do the master settings if (interpolationAllowed) { pPl->SetSequence( Lerp( frac, pPlSnap->masterSequence, pNextPlSnap->masterSequence ) ); pPl->SetCycle( Lerp( frac, pPlSnap->masterCycle, pNextPlSnap->masterCycle ) ); if( pPlSnap->masterCycle > pNextPlSnap->masterCycle ) { // the older record is higher in frame than the newer, it must have wrapped around from 1 back to 0 // add one to the newer so it is lerping from .9 to 1.1 instead of .9 to .1, for example. float newCycle = Lerp( frac, pPlSnap->masterCycle, pNextPlSnap->masterCycle + 1 ); pPl->SetCycle(newCycle < 1 ? newCycle : newCycle - 1 );// and make sure .9 to 1.2 does not end up 1.05 } else { pPl->SetCycle( Lerp( frac, pPlSnap->masterCycle, pNextPlSnap->masterCycle ) ); } pPl->SetPoseParameter(pPl->GetModelPtr(), 4, Lerp(frac, pPlSnap->moveX, pNextPlSnap->moveX)); pPl->SetPoseParameter(pPl->GetModelPtr(), 3, Lerp(frac, pPlSnap->moveY, pNextPlSnap->moveY)); } else { pPl->SetSequence(pPlSnap->masterSequence); pPl->SetCycle(pPlSnap->masterCycle); pPl->SetPoseParameter(pPl->GetModelPtr(), 4, pPlSnap->moveX); pPl->SetPoseParameter(pPl->GetModelPtr(), 3, pPlSnap->moveY); } // Now do all the layers for (int layerIndex = 0; layerIndex < NUM_LAYERS_WANTED; layerIndex++) { CAnimationLayer *pTargetLayer = pPl->GetAnimOverlay(layerIndex); if(!pTargetLayer) continue; LayerRecord *pSourceLayer = &pPlSnap->layerRecords[layerIndex]; pTargetLayer->m_nOrder = pSourceLayer->order; pTargetLayer->m_nSequence = pSourceLayer->sequence; pTargetLayer->m_flPlaybackRate = pSourceLayer->playbackRate; pTargetLayer->m_fFlags = pSourceLayer->flags; pTargetLayer->m_bLooping = pSourceLayer->looping; pTargetLayer->m_bSequenceFinished = pSourceLayer->sequenceFinished; pTargetLayer->m_nPriority = pSourceLayer->priority; pTargetLayer->m_nActivity = pSourceLayer->activity; pTargetLayer->m_flLastAccess = pSourceLayer->lastAccess; pTargetLayer->m_flLastEventCheck = pSourceLayer->lastEventCheck; if (interpolationAllowed) { LayerRecord *pNextSourceLayer = &pNextPlSnap->layerRecords[layerIndex]; if(pSourceLayer->order == pNextSourceLayer->order && pSourceLayer->sequence == pNextSourceLayer->sequence) { // We can't interpolate across a sequence or order change if( pSourceLayer->cycle > pNextSourceLayer->cycle ) { // the older record is higher in frame than the newer, it must have wrapped around from 1 back to 0 // add one to the newer so it is lerping from .9 to 1.1 instead of .9 to .1, for example. float newCycle = Lerp( frac, pSourceLayer->cycle, pNextSourceLayer->cycle + 1 ); pTargetLayer->m_flCycle = newCycle < 1 ? newCycle : newCycle - 1;// and make sure .9 to 1.2 does not end up 1.05 } else { pTargetLayer->m_flCycle = Lerp(frac, pSourceLayer->cycle, pNextSourceLayer->cycle); } pTargetLayer->m_flWeight = Lerp(frac, pSourceLayer->weight, pNextSourceLayer->weight); pTargetLayer->m_flLayerAnimtime = Lerp(frac, pSourceLayer->layerAnimtime, pNextSourceLayer->layerAnimtime); pTargetLayer->m_flBlendIn = Lerp(frac, pSourceLayer->blendIn, pNextSourceLayer->blendIn); pTargetLayer->m_flBlendOut = Lerp(frac, pSourceLayer->blendOut, pNextSourceLayer->blendOut); pTargetLayer->m_flPrevCycle = Lerp(frac, pSourceLayer->prevCycle, pNextSourceLayer->prevCycle); pTargetLayer->m_flKillDelay = Lerp(frac, pSourceLayer->killDelay, pNextSourceLayer->killDelay); pTargetLayer->m_flKillRate = Lerp(frac, pSourceLayer->killRate, pNextSourceLayer->killRate); pTargetLayer->m_flLayerFadeOuttime = Lerp(frac, pSourceLayer->layerFadeOuttime, pNextSourceLayer->layerFadeOuttime); } } else { //Either no interp, or interp failed. Just use record. pTargetLayer->m_flCycle = pSourceLayer->cycle; pTargetLayer->m_flWeight = pSourceLayer->weight; pTargetLayer->m_flLayerAnimtime = pSourceLayer->layerAnimtime; pTargetLayer->m_flBlendIn = pSourceLayer->blendIn; pTargetLayer->m_flBlendOut = pSourceLayer->blendOut; pTargetLayer->m_flPrevCycle = pSourceLayer->prevCycle; pTargetLayer->m_flKillDelay = pSourceLayer->killDelay; pTargetLayer->m_flKillRate = pSourceLayer->killRate; pTargetLayer->m_flLayerFadeOuttime = pSourceLayer->layerFadeOuttime; } } } } }