//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "iviewrender.h" #include "view.h" #include "studio.h" #include "bone_setup.h" #include "model_types.h" #include "beamdraw.h" #include "engine/ivdebugoverlay.h" #include "iviewrender_beams.h" #include "fx.h" #include "iefx.h" #include "dlight.h" #include "IEffects.h" #include "c_entitydissolveegon.h" #include "movevars_shared.h" #include "clienteffectprecachesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" CLIENTEFFECT_REGISTER_BEGIN(PrecacheEffectBuild2) CLIENTEFFECT_MATERIAL("effects/tesla_glow_noz") CLIENTEFFECT_MATERIAL("effects/spark") CLIENTEFFECT_MATERIAL("effects/combinemuzzle2") CLIENTEFFECT_REGISTER_END() //----------------------------------------------------------------------------- // Networking //----------------------------------------------------------------------------- IMPLEMENT_CLIENTCLASS_DT(C_EntityDissolveEgon, DT_EntityDissolveEgon, CEntityDissolveEgon) RecvPropTime(RECVINFO(m_flStartTime)), RecvPropFloat(RECVINFO(m_flFadeOutStart)), RecvPropFloat(RECVINFO(m_flFadeOutLength)), RecvPropFloat(RECVINFO(m_flFadeOutModelStart)), RecvPropFloat(RECVINFO(m_flFadeOutModelLength)), RecvPropFloat(RECVINFO(m_flFadeInStart)), RecvPropFloat(RECVINFO(m_flFadeInLength)), RecvPropInt(RECVINFO(m_nDissolveType)), RecvPropVector(RECVINFO(m_vDissolverOrigin)), RecvPropInt(RECVINFO(m_nMagnitude)), END_RECV_TABLE() extern PMaterialHandle g_Material_Spark; PMaterialHandle g_Material_AR2Glow = NULL; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_EntityDissolveEgon::C_EntityDissolveEgon(void) { m_bLinkedToServerEnt = true; m_pController = NULL; m_bCoreExplode = false; m_vEffectColor = Vector(255, 255, 255); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_EntityDissolveEgon::GetRenderBounds(Vector& theMins, Vector& theMaxs) { if (GetMoveParent()) { GetMoveParent()->GetRenderBounds(theMins, theMaxs); } else { theMins = GetAbsOrigin(); theMaxs = theMaxs; } } //----------------------------------------------------------------------------- // On data changed //----------------------------------------------------------------------------- void C_EntityDissolveEgon::OnDataChanged(DataUpdateType_t updateType) { BaseClass::OnDataChanged(updateType); if (updateType == DATA_UPDATE_CREATED) { m_flNextSparkTime = m_flStartTime; SetNextClientThink(CLIENT_THINK_ALWAYS); } } //----------------------------------------------------------------------------- // Cleanup //----------------------------------------------------------------------------- void C_EntityDissolveEgon::UpdateOnRemove(void) { if (m_pController) { physenv->DestroyMotionController(m_pController); m_pController = NULL; } BaseClass::UpdateOnRemove(); } //------------------------------------------------------------------------------ // Apply the forces to the entity //------------------------------------------------------------------------------ IMotionEvent::simresult_e C_EntityDissolveEgon::Simulate(IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular) { linear.Init(); angular.Init(); // Make it zero g linear.z -= -1.02 * GetCurrentGravity(); Vector vel; AngularImpulse angVel; pObject->GetVelocity(&vel, &angVel); vel += linear * deltaTime; // account for gravity scale Vector unitVel = vel; Vector unitAngVel = angVel; float speed = VectorNormalize(unitVel); // float angSpeed = VectorNormalize( unitAngVel ); // float speedScale = 0.0; // float angSpeedScale = 0.0; float flLinearLimit = 50; float flLinearLimitDelta = 40; if (speed > flLinearLimit) { float flDeltaVel = (flLinearLimit - speed) / deltaTime; if (flLinearLimitDelta != 0.0f) { float flMaxDeltaVel = -flLinearLimitDelta / deltaTime; if (flDeltaVel < flMaxDeltaVel) { flDeltaVel = flMaxDeltaVel; } } VectorMA(linear, flDeltaVel, unitVel, linear); } return SIM_GLOBAL_ACCELERATION; } //----------------------------------------------------------------------------- // Tesla effect //----------------------------------------------------------------------------- static void FX_BuildTesla(C_BaseEntity *pEntity, Vector &vecOrigin, Vector &vecEnd) { BeamInfo_t beamInfo; beamInfo.m_pStartEnt = pEntity; beamInfo.m_nStartAttachment = 0; beamInfo.m_pEndEnt = NULL; beamInfo.m_nEndAttachment = 0; beamInfo.m_nType = TE_BEAMTESLA; beamInfo.m_vecStart = vecOrigin; beamInfo.m_vecEnd = vecEnd; beamInfo.m_pszModelName = "sprites/lgtning.vmt"; beamInfo.m_flHaloScale = 0.0; beamInfo.m_flLife = random->RandomFloat(0.25f, 1.0f); beamInfo.m_flWidth = random->RandomFloat(8.0f, 14.0f); beamInfo.m_flEndWidth = 1.0f; beamInfo.m_flFadeLength = 0.5f; beamInfo.m_flAmplitude = 24; beamInfo.m_flBrightness = 255.0; beamInfo.m_flSpeed = 150.0f; beamInfo.m_nStartFrame = 0.0; beamInfo.m_flFrameRate = 30.0; beamInfo.m_flRed = 138.0; beamInfo.m_flGreen = 112.0; beamInfo.m_flBlue = 234.0; beamInfo.m_nSegments = 18; beamInfo.m_bRenderable = true; beamInfo.m_nFlags = 0; //FBEAM_ONLYNOISEONCE; beams->CreateBeamEntPoint(beamInfo); } //----------------------------------------------------------------------------- // Purpose: Tesla effect //----------------------------------------------------------------------------- void C_EntityDissolveEgon::BuildTeslaEffect(mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, bool bRandom, float flYawOffset) { Vector vecOrigin; QAngle vecAngles; MatrixGetColumn(hitboxToWorld, 3, vecOrigin); MatrixAngles(hitboxToWorld, vecAngles.Base()); C_BaseEntity *pEntity = GetMoveParent(); // Make a couple of tries at it int iTries = -1; Vector vecForward; trace_t tr; do { iTries++; // Some beams are deliberatly aimed around the point, the rest are random. if (!bRandom) { QAngle vecTemp = vecAngles; vecTemp[YAW] += flYawOffset; AngleVectors(vecTemp, &vecForward); // Randomly angle it up or down vecForward.z = RandomFloat(-1, 1); } else { vecForward = RandomVector(-1, 1); } UTIL_TraceLine(vecOrigin, vecOrigin + (vecForward * 192), MASK_SHOT, pEntity, COLLISION_GROUP_NONE, &tr); } while (tr.fraction >= 1.0 && iTries < 3); Vector vecEnd = tr.endpos - (vecForward * 8); // Only spark & glow if we hit something if (tr.fraction < 1.0) { if (!EffectOccluded(tr.endpos)) { // Move it towards the camera Vector vecFlash = tr.endpos; Vector vecForward; AngleVectors(MainViewAngles(), &vecForward); vecFlash -= (vecForward * 8); g_pEffects->EnergySplash(vecFlash, -vecForward, false); // End glow CSmartPtr pSimple = CSimpleEmitter::Create("dust"); pSimple->SetSortOrigin(vecFlash); SimpleParticle *pParticle; pParticle = (SimpleParticle *)pSimple->AddParticle(sizeof(SimpleParticle), pSimple->GetPMaterial("effects/tesla_glow_noz"), vecFlash); if (pParticle != NULL) { pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = RandomFloat(0.5, 1); pParticle->m_vecVelocity = vec3_origin; Vector color(1, 1, 1); float colorRamp = RandomFloat(0.75f, 1.25f); pParticle->m_uchColor[0] = MIN(1.0f, color[0] * colorRamp) * 255.0f; pParticle->m_uchColor[1] = MIN(1.0f, color[1] * colorRamp) * 255.0f; pParticle->m_uchColor[2] = MIN(1.0f, color[2] * colorRamp) * 255.0f; pParticle->m_uchStartSize = RandomFloat(6, 13); pParticle->m_uchEndSize = pParticle->m_uchStartSize - 2; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 10; pParticle->m_flRoll = RandomFloat(0, 360); pParticle->m_flRollDelta = 0; } } } // Build the tesla FX_BuildTesla(pEntity, vecOrigin, tr.endpos); } //----------------------------------------------------------------------------- // Sorts the components of a vector //----------------------------------------------------------------------------- static inline void SortAbsVectorComponents(const Vector& src, int* pVecIdx) { Vector absVec(fabs(src[0]), fabs(src[1]), fabs(src[2])); int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1; if (absVec[2] > absVec[maxIdx]) { maxIdx = 2; } // always choose something right-handed.... switch (maxIdx) { case 0: pVecIdx[0] = 1; pVecIdx[1] = 2; pVecIdx[2] = 0; break; case 1: pVecIdx[0] = 2; pVecIdx[1] = 0; pVecIdx[2] = 1; break; case 2: pVecIdx[0] = 0; pVecIdx[1] = 1; pVecIdx[2] = 2; break; } } //----------------------------------------------------------------------------- // Compute the bounding box's center, size, and basis //----------------------------------------------------------------------------- void C_EntityDissolveEgon::ComputeRenderInfo(mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, Vector *pVecAbsOrigin, Vector *pXVec, Vector *pYVec) { // Compute the center of the hitbox in worldspace Vector vecHitboxCenter; VectorAdd(pHitBox->bbmin, pHitBox->bbmax, vecHitboxCenter); vecHitboxCenter *= 0.5f; VectorTransform(vecHitboxCenter, hitboxToWorld, *pVecAbsOrigin); // Get the object's basis Vector vec[3]; MatrixGetColumn(hitboxToWorld, 0, vec[0]); MatrixGetColumn(hitboxToWorld, 1, vec[1]); MatrixGetColumn(hitboxToWorld, 2, vec[2]); // vec[1] *= -1.0f; Vector vecViewDir; VectorSubtract(CurrentViewOrigin(), *pVecAbsOrigin, vecViewDir); VectorNormalize(vecViewDir); // Project the shadow casting direction into the space of the hitbox Vector localViewDir; localViewDir[0] = DotProduct(vec[0], vecViewDir); localViewDir[1] = DotProduct(vec[1], vecViewDir); localViewDir[2] = DotProduct(vec[2], vecViewDir); // Figure out which vector has the largest component perpendicular // to the view direction... // Sort by how perpendicular it is int vecIdx[3]; SortAbsVectorComponents(localViewDir, vecIdx); // Here's our hitbox basis vectors; namely the ones that are // most perpendicular to the view direction *pXVec = vec[vecIdx[0]]; *pYVec = vec[vecIdx[1]]; // Project them into a plane perpendicular to the view direction *pXVec -= vecViewDir * DotProduct(vecViewDir, *pXVec); *pYVec -= vecViewDir * DotProduct(vecViewDir, *pYVec); VectorNormalize(*pXVec); VectorNormalize(*pYVec); // Compute the hitbox size Vector boxSize; VectorSubtract(pHitBox->bbmax, pHitBox->bbmin, boxSize); // We project the two longest sides into the vectors perpendicular // to the projection direction, then add in the projection of the perp direction Vector2D size(boxSize[vecIdx[0]], boxSize[vecIdx[1]]); size.x *= fabs(DotProduct(vec[vecIdx[0]], *pXVec)); size.y *= fabs(DotProduct(vec[vecIdx[1]], *pYVec)); // Add the third component into x and y size.x += boxSize[vecIdx[2]] * fabs(DotProduct(vec[vecIdx[2]], *pXVec)); size.y += boxSize[vecIdx[2]] * fabs(DotProduct(vec[vecIdx[2]], *pYVec)); // Bloat a bit, since the shadow wants to extend outside the model a bit size *= 2.0f; // Clamp the minimum size Vector2DMax(size, Vector2D(10.0f, 10.0f), size); // Factor the size into the xvec + yvec (*pXVec) *= size.x * 0.5f; (*pYVec) *= size.y * 0.5f; } //----------------------------------------------------------------------------- // Sparks! //----------------------------------------------------------------------------- void C_EntityDissolveEgon::DoSparks(mstudiohitboxset_t *set, matrix3x4_t *hitboxbones[MAXSTUDIOBONES]) { if (m_flNextSparkTime > gpGlobals->curtime) return; float dt = m_flStartTime + m_flFadeOutStart - gpGlobals->curtime; dt = clamp(dt, 0.0f, m_flFadeOutStart); float flNextTime; if (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) { flNextTime = SimpleSplineRemapVal(dt, 0.0f, m_flFadeOutStart, 2.0f * TICK_INTERVAL, 0.4f); } else { // m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT); flNextTime = SimpleSplineRemapVal(dt, 0.0f, m_flFadeOutStart, 0.3f, 1.0f); } m_flNextSparkTime = gpGlobals->curtime + flNextTime; // Send out beams around us int iNumBeamsAround = 2; int iNumRandomBeams = 1; int iTotalBeams = iNumBeamsAround + iNumRandomBeams; float flYawOffset = RandomFloat(0, 360); for (int i = 0; i < iTotalBeams; i++) { int nHitbox = random->RandomInt(0, set->numhitboxes - 1); mstudiobbox_t *pBox = set->pHitbox(nHitbox); float flActualYawOffset = 0; bool bRandom = (i >= iNumBeamsAround); if (!bRandom) { flActualYawOffset = anglemod(flYawOffset + ((360 / iTotalBeams) * i)); } BuildTeslaEffect(pBox, *hitboxbones[pBox->bone], bRandom, flActualYawOffset); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_EntityDissolveEgon::SetupEmitter(void) { if (!m_pEmitter) { m_pEmitter = CSimpleEmitter::Create("C_EntityDissolveEgon"); m_pEmitter->SetSortOrigin(GetAbsOrigin()); } } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float C_EntityDissolveEgon::GetFadeInPercentage(void) { float dt = gpGlobals->curtime - m_flStartTime; if (dt > m_flFadeOutStart) return 1.0f; if (dt < m_flFadeInStart) return 0.0f; if ((dt > m_flFadeInStart) && (dt < m_flFadeInStart + m_flFadeInLength)) { dt -= m_flFadeInStart; return (dt / m_flFadeInLength); } return 1.0f; } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float C_EntityDissolveEgon::GetFadeOutPercentage(void) { float dt = gpGlobals->curtime - m_flStartTime; if (dt < m_flFadeInStart) return 1.0f; if (dt > m_flFadeOutStart) { dt -= m_flFadeOutStart; if (dt > m_flFadeOutLength) return 0.0f; return 1.0f - (dt / m_flFadeOutLength); } return 1.0f; } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float C_EntityDissolveEgon::GetModelFadeOutPercentage(void) { float dt = gpGlobals->curtime - m_flStartTime; if (dt < m_flFadeOutModelStart) return 1.0f; if (dt > m_flFadeOutModelStart) { dt -= m_flFadeOutModelStart; if (dt > m_flFadeOutModelLength) return 0.0f; return 1.0f - (dt / m_flFadeOutModelLength); } return 1.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_EntityDissolveEgon::ClientThink(void) { C_BaseEntity *pEnt = GetMoveParent(); if (!pEnt) return; bool bIsRagdoll; #ifdef TF_CLIENT_DLL bIsRagdoll = true; #else C_BaseAnimating *pAnimating = GetMoveParent() ? GetMoveParent()->GetBaseAnimating() : NULL; if (!pAnimating) return; bIsRagdoll = pAnimating->IsRagdoll(); #endif // NOTE: IsRagdoll means *client-side* ragdoll. We shouldn't be trying to fight // the server ragdoll (or any server physics) on the client if ((!m_pController) && (m_nDissolveType == ENTITY_DISSOLVE_NORMAL) && bIsRagdoll) { IPhysicsObject *ppList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int nCount = pEnt->VPhysicsGetObjectList(ppList, ARRAYSIZE(ppList)); if (nCount > 0) { m_pController = physenv->CreateMotionController(this); for (int i = 0; i < nCount; ++i) { m_pController->AttachObject(ppList[i], true); } } } color32 color; color.r = (1.0f - GetFadeInPercentage()) * m_vEffectColor.x; color.g = (1.0f - GetFadeInPercentage()) * m_vEffectColor.y; color.b = (1.0f - GetFadeInPercentage()) * m_vEffectColor.z; color.a = GetModelFadeOutPercentage() * 255.0f; // Setup the entity fade pEnt->SetRenderMode(kRenderTransColor); pEnt->SetRenderColor(color.r, color.g, color.b, color.a); if (GetModelFadeOutPercentage() <= 0.2f) { m_bCoreExplode = true; } // If we're dead, fade out /*if (GetFadeOutPercentage() <= 0.0f) { // Do NOT remove from the client entity list. It'll confuse the local network backdoor, and the entity will never get destroyed // because when the server says to destroy it, the client won't be able to find it. // ClientEntityList().RemoveEntity( GetClientHandle() ); partition->Remove(PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle()); RemoveFromLeafSystem(); //FIXME: Ick! //Adrian: I'll assume we don't need the ragdoll either so I'll remove that too. if (m_bLinkedToServerEnt == false) { Release(); C_ClientRagdoll *pRagdoll = dynamic_cast (pEnt); if (pRagdoll) { pRagdoll->ReleaseRagdoll(); } #ifdef TF_CLIENT_DLL else { pEnt->Release(); } #endif } }*/ } //----------------------------------------------------------------------------- // Purpose: // Input : flags - // Output : int //----------------------------------------------------------------------------- int C_EntityDissolveEgon::DrawModel(int flags) { // See if we should draw if (gpGlobals->frametime == 0 || m_bReadyToDraw == false) return 0; C_BaseAnimating *pAnimating = GetMoveParent() ? GetMoveParent()->GetBaseAnimating() : NULL; if (pAnimating == NULL) return 0; matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; if (pAnimating->HitboxToWorldTransforms(hitboxbones) == false) return 0; studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel(pAnimating->GetModel()); if (pStudioHdr == NULL) return false; mstudiohitboxset_t *set = pStudioHdr->pHitboxSet(pAnimating->GetHitboxSet()); if (set == NULL) return false; QAngle Ang; // Make sure the emitter is setup properly SetupEmitter(); // Get fade percentages for the effect float fadeInPerc = GetFadeInPercentage(); float fadeOutPerc = GetFadeOutPercentage(); float fadePerc = (fadeInPerc >= 1.0f) ? fadeOutPerc : fadeInPerc; Vector vecSkew = vec3_origin; // Do extra effects under certain circumstances if ((fadePerc < 0.99f) && ((m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT))) { DoSparks(set, hitboxbones); } // Skew the particles in front or in back of their targets vecSkew = CurrentViewForward() * (8.0f - ((1.0f - fadePerc) * 32.0f)); float spriteScale = ((gpGlobals->curtime - m_flStartTime) / m_flFadeOutLength); spriteScale = clamp(spriteScale, 0.75f, 1.0f); // Cache off this material reference /*if (g_Material_Spark == NULL) { g_Material_Spark = ParticleMgr()->GetPMaterial("effects/spark"); } if (g_Material_AR2Glow == NULL) { g_Material_AR2Glow = ParticleMgr()->GetPMaterial("effects/combinemuzzle2"); } SimpleParticle *sParticle;*/ for (int i = 0; i < set->numhitboxes; ++i) { Vector vecAbsOrigin, xvec, yvec; mstudiobbox_t *pBox = set->pHitbox(i); ComputeRenderInfo(pBox, *hitboxbones[pBox->bone], &vecAbsOrigin, &xvec, &yvec); Vector offset; Vector xDir, yDir; xDir = xvec; //float xScale = VectorNormalize(xDir) * 0.75f; yDir = yvec; //float yScale = VectorNormalize(yDir) * 0.75f; int numParticles = clamp(3.0f * fadePerc, 0.f, 3.f); int iTempParts = 2; if (m_nDissolveType == ENTITY_DISSOLVE_CORE) { if (m_bCoreExplode == true) { numParticles = 15; iTempParts = 20; } } /*for (int j = 0; j < iTempParts; j++) { // Skew the origin offset = xDir * Helper_RandomFloat(-xScale*0.5f, xScale*0.5f) + yDir * Helper_RandomFloat(-yScale*0.5f, yScale*0.5f); offset += vecSkew; VectorAngles(vecAbsOrigin + offset, Ang); AddEffects(EF_EGONDISSOLVELIGHT); DispatchParticleEffect("dissolve_egon_warp", vecAbsOrigin + offset, Ang, NULL); } for (int j = 0; j < numParticles; j++) { offset = xDir * Helper_RandomFloat(-xScale*0.5f, xScale*0.5f) + yDir * Helper_RandomFloat(-yScale*0.5f, yScale*0.5f); offset += vecSkew; AddEffects(EF_EGONDISSOLVELIGHT); VectorAngles(vecAbsOrigin + offset, Ang); DispatchParticleEffect("dissolve_egon", vecAbsOrigin + offset, Ang, NULL); } dlight_t *el = effects->CL_AllocDlight(index);//( index ); el->origin = offset; el->color.r = 64; el->color.g = 128; el->color.b = 255; el->color.exponent = 5; el->radius = random->RandomInt(32, 128); el->decay = el->radius / 0.05f; el->die = gpGlobals->curtime + 0.05f;*/ } return 1; }