DETOUR_DECL_MEMBER(void, CTFTankBoss_TankBossThink) { static CountdownTimer ctNodes; if (ctNodes.IsElapsed()) { ctNodes.Start(0.5f); ForEachEntityByRTTI<CPathTrack>([](CPathTrack *node){ NDebugOverlay::Box(node->GetAbsOrigin(), Vector(-10.0f, -10.0f, -10.0f), Vector(10.0f, 10.0f, 10.0f), 0xff, 0xff, 0xff, 0x80, 0.5f); NDebugOverlay::EntityTextAtPosition(node->GetAbsOrigin(), 0, CFmtStrN<16>("#%d", i), 0.5f, 0xff, 0xff, 0xff, 0xff); CPathTrack *next = node->GetNext(); if (next != nullptr) { NDebugOverlay::HorzArrow(node->GetAbsOrigin(), next->GetAbsOrigin(), 3.0f, 0xff, 0xff, 0xff, 0xff, true, 0.5f); } }); } auto tank = reinterpret_cast<CTFTankBoss *>(this); NDebugOverlay::EntityText(ENTINDEX(tank), 0, CFmtStrN<256>("%.1f%%", GetTankProgress(tank) * 100.0f), gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff); // NDebugOverlay::EntityText(ENTINDEX(tank), 0, CFmtStrN<256>("m_hCurrentNode: #%d", ENTINDEX(*m_hCurrentNode)), // gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff); // NDebugOverlay::EntityText(ENTINDEX(tank), 1, CFmtStrN<256>("m_iCurrentNode: %d", *m_iCurrentNode), // gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff); // NDebugOverlay::EntityText(ENTINDEX(tank), 2, CFmtStrN<256>("m_flTotalDistance: %.0f", *m_flTotalDistance), // gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff); // NDebugOverlay::EntityText(ENTINDEX(tank), 3, CFmtStrN<256>("m_NodeDists[i]: %.0f", (*m_NodeDists)[*m_iCurrentNode]), // gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff); DETOUR_MEMBER_CALL(CTFTankBoss_TankBossThink)(); }
//----------------------------------------------------------------------------- void C_Fish::ClientThink() { if (FishDebug.GetBool()) { debugoverlay->AddLineOverlay( m_pos, m_actualPos, 255, 0, 0, true, 0.1f ); switch( m_localLifeState ) { case LIFE_DYING: debugoverlay->AddTextOverlay( m_pos, 0.1f, "DYING" ); break; case LIFE_DEAD: debugoverlay->AddTextOverlay( m_pos, 0.1f, "DEAD" ); break; } } float deltaT = gpGlobals->frametime; // check if we just died if (m_localLifeState == LIFE_ALIVE && m_lifeState != LIFE_ALIVE) { // we have died m_localLifeState = LIFE_DYING; m_deathDepth = m_pos.z; // determine surface float angle m_deathAngle = RandomFloat( 87.0f, 93.0f ) * ((RandomInt( 0, 100 ) < 50) ? 1.0f : -1.0f); } switch( m_localLifeState ) { case LIFE_DYING: { // depth parameter float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth); t *= t; // roll onto side m_angles.z = m_deathAngle * t; // float to surface const float fudge = 2.0f; if (m_pos.z < m_waterLevel - fudge) { m_vel.z += (1.0f - t) * m_buoyancy * deltaT; } else { m_localLifeState = LIFE_DEAD; } break; } case LIFE_DEAD: { // depth parameter float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth); t *= t; // roll onto side m_angles.z = m_deathAngle * t; // keep near water surface const float sub = 0.5f; m_vel.z += 10.0f * (m_waterLevel - m_pos.z - sub) * deltaT; // bob on surface const float rollAmp = 5.0f; const float rollFreq = 2.33f; m_angles.z += rollAmp * sin( rollFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; const float rollAmp2 = 7.0f; const float rollFreq2 = 4.0f; m_angles.x += rollAmp2 * sin( rollFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; const float bobAmp = 0.75f; const float bobFreq = 4.0f; m_vel.z += bobAmp * sin( bobFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; const float bobAmp2 = 0.75f; const float bobFreq2 = 3.333f; m_vel.z += bobAmp2 * sin( bobFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; // decay movement speed to zero const float drag = 1.0f; m_vel.z -= drag * m_vel.z * deltaT; break; } case LIFE_ALIVE: { // use server-side Z coordinate directly m_pos.z = m_actualPos.z; // use server-side angles m_angles = m_actualAngles; // fishy wiggle based on movement if (!m_wiggleTimer.IsElapsed()) { float swimPower = 1.0f - (m_wiggleTimer.GetElapsedTime() / m_wiggleTimer.GetCountdownDuration()); const float amp = 6.0f * swimPower; float wiggle = amp * sin( m_wigglePhase ); m_wigglePhase += m_wiggleRate * deltaT; // wiggle decay const float wiggleDecay = 5.0f; m_wiggleRate -= wiggleDecay * deltaT; m_angles.y += wiggle; } break; } } // compute error between our local position and actual server position Vector error = m_actualPos - m_pos; error.z = 0.0f; float errorLen = error.Length(); if (m_localLifeState == LIFE_ALIVE) { // if error is far above average, start swimming const float wiggleThreshold = 2.0f; if (errorLen - m_averageError > wiggleThreshold) { // if error is large, we must have started swimming const float swimTime = 5.0f; m_wiggleTimer.Start( swimTime ); m_wiggleRate = 2.0f * errorLen; const float maxWiggleRate = 30.0f; if (m_wiggleRate > maxWiggleRate) { m_wiggleRate = maxWiggleRate; } } // update average error m_errorHistory[ m_errorHistoryIndex++ ] = errorLen; if (m_errorHistoryIndex >= MAX_ERROR_HISTORY) { m_errorHistoryIndex = 0; m_errorHistoryCount = MAX_ERROR_HISTORY; } else if (m_errorHistoryCount < MAX_ERROR_HISTORY) { ++m_errorHistoryCount; } m_averageError = 0.0f; if (m_errorHistoryCount) { for( int r=0; r<m_errorHistoryCount; ++r ) { m_averageError += m_errorHistory[r]; } m_averageError /= (float)m_errorHistoryCount; } } // keep fish motion smooth by correcting towards actual server position // NOTE: This only tracks XY motion const float maxError = 20.0f; float errorT = errorLen / maxError; if (errorT > 1.0f) { errorT = 1.0f; } // we want a nonlinear spring force for tracking errorT *= errorT; // as fish move faster, their error increases - use a stiffer spring when fast, and a weak one when slow const float trackRate = 0.0f + errorT * 115.0f; m_vel.x += trackRate * error.x * deltaT; m_vel.y += trackRate * error.y * deltaT; const float trackDrag = 2.0f + errorT * 6.0f; m_vel.x -= trackDrag * m_vel.x * deltaT; m_vel.y -= trackDrag * m_vel.y * deltaT; // euler integration m_pos += m_vel * deltaT; SetNetworkOrigin( m_pos ); SetAbsOrigin( m_pos ); SetNetworkAngles( m_angles ); SetAbsAngles( m_angles ); }