text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: Dissolve entity to be attached to target entity. Serves two purposes:
  //
  //          1) An entity that can be placed by a level designer and triggered
  //             to ignite a target entity.
  //
  //          2) An entity that can be created at runtime to ignite a target entity.
  //
  //=============================================================================//
  
  #include "cbase.h"
  #include "EntityDissolve.h"
  #include "baseanimating.h"
  #include "physics_prop_ragdoll.h"
  #include "ai_basenpc.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  static const char *s_pElectroThinkContext = "ElectroThinkContext";
  
  
  //-----------------------------------------------------------------------------
  // Lifetime
  //-----------------------------------------------------------------------------
  #define DISSOLVE_FADE_IN_START_TIME         0.0f
  #define DISSOLVE_FADE_IN_END_TIME           1.0f
  #define DISSOLVE_FADE_OUT_MODEL_START_TIME  1.9f
  #define DISSOLVE_FADE_OUT_MODEL_END_TIME    2.0f
  #define DISSOLVE_FADE_OUT_START_TIME        2.0f
  #define DISSOLVE_FADE_OUT_END_TIME          2.0f
  
  //-----------------------------------------------------------------------------
  // Model
  //-----------------------------------------------------------------------------
  #define DISSOLVE_SPRITE_NAME    "sprites/blueglow1.vmt"
  
  //-----------------------------------------------------------------------------
  // Save/load
  //-----------------------------------------------------------------------------
  BEGIN_DATADESC( CEntityDissolve )
  
      DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
      DEFINE_FIELD( m_flFadeInStart, FIELD_FLOAT ),
      DEFINE_FIELD( m_flFadeInLength, FIELD_FLOAT ),
      DEFINE_FIELD( m_flFadeOutModelStart, FIELD_FLOAT ),
      DEFINE_FIELD( m_flFadeOutModelLength, FIELD_FLOAT ),
      DEFINE_FIELD( m_flFadeOutStart, FIELD_FLOAT ),
      DEFINE_FIELD( m_flFadeOutLength, FIELD_FLOAT ),
      DEFINE_KEYFIELD( m_nDissolveType, FIELD_INTEGER, "dissolvetype" ),
      DEFINE_FIELD( m_vDissolverOrigin, FIELD_VECTOR ),
      DEFINE_KEYFIELD( m_nMagnitude, FIELD_INTEGER, "magnitude" ),
  
      DEFINE_FUNCTION( DissolveThink ),
      DEFINE_FUNCTION( ElectrocuteThink ),
  
      DEFINE_INPUTFUNC( FIELD_STRING, "Dissolve", InputDissolve ),
  
  END_DATADESC()
  
  
  //-----------------------------------------------------------------------------
  // Networking
  //-----------------------------------------------------------------------------
  IMPLEMENT_SERVERCLASS_ST( CEntityDissolve, DT_EntityDissolve )
      SendPropTime( SENDINFO( m_flStartTime ) ),
      SendPropFloat( SENDINFO( m_flFadeInStart ), 0, SPROP_NOSCALE ),
      SendPropFloat( SENDINFO( m_flFadeInLength ), 0, SPROP_NOSCALE ),
      SendPropFloat( SENDINFO( m_flFadeOutModelStart ), 0, SPROP_NOSCALE ),
      SendPropFloat( SENDINFO( m_flFadeOutModelLength ), 0, SPROP_NOSCALE ),
      SendPropFloat( SENDINFO( m_flFadeOutStart ), 0, SPROP_NOSCALE ),
      SendPropFloat( SENDINFO( m_flFadeOutLength ), 0, SPROP_NOSCALE ),
      SendPropInt( SENDINFO( m_nDissolveType ), ENTITY_DISSOLVE_BITS, SPROP_UNSIGNED ),
      SendPropVector  (SENDINFO(m_vDissolverOrigin), 0, SPROP_NOSCALE ),
      SendPropInt( SENDINFO( m_nMagnitude ), 8, SPROP_UNSIGNED ),
  END_SEND_TABLE()
  
  LINK_ENTITY_TO_CLASS( env_entity_dissolver, CEntityDissolve );
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  CEntityDissolve::CEntityDissolve( void )
  {
      m_flStartTime   = 0.0f;
      m_nMagnitude = 250;
  }
  
  CEntityDissolve::~CEntityDissolve( void )
  {
  }
  
  
  //-----------------------------------------------------------------------------
  // Precache
  //-----------------------------------------------------------------------------
  void CEntityDissolve::Precache()
  {
      if ( NULL_STRING == GetModelName() )
      {
          PrecacheModel( DISSOLVE_SPRITE_NAME );
      }
      else
      {
          PrecacheModel( STRING( GetModelName() ) );
      }
  }
  
  
  //-----------------------------------------------------------------------------
  // Spawn
  //-----------------------------------------------------------------------------
  void CEntityDissolve::Spawn()
  {
      BaseClass::Spawn();
      Precache();
      UTIL_SetModel( this, STRING( GetModelName() ) );
  
      if ( (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) )
      {
          if ( dynamic_cast< CRagdollProp* >( GetMoveParent() ) )
          {
              SetContextThink( &CEntityDissolve::ElectrocuteThink, gpGlobals->curtime + 0.01f, s_pElectroThinkContext );
          }
      }
  
      // Setup our times
      m_flFadeInStart = DISSOLVE_FADE_IN_START_TIME;
      m_flFadeInLength = DISSOLVE_FADE_IN_END_TIME - DISSOLVE_FADE_IN_START_TIME;
  
      m_flFadeOutModelStart = DISSOLVE_FADE_OUT_MODEL_START_TIME;
      m_flFadeOutModelLength = DISSOLVE_FADE_OUT_MODEL_END_TIME - DISSOLVE_FADE_OUT_MODEL_START_TIME;
  
      m_flFadeOutStart = DISSOLVE_FADE_OUT_START_TIME;
      m_flFadeOutLength = DISSOLVE_FADE_OUT_END_TIME - DISSOLVE_FADE_OUT_START_TIME;
  
      if ( m_nDissolveType == ENTITY_DISSOLVE_CORE )
      {
          m_flFadeInStart = 0.0f;
          m_flFadeOutStart = CORE_DISSOLVE_FADE_START;
          m_flFadeOutModelStart = CORE_DISSOLVE_MODEL_FADE_START;
          m_flFadeOutModelLength = CORE_DISSOLVE_MODEL_FADE_LENGTH;
          m_flFadeInLength = CORE_DISSOLVE_FADEIN_LENGTH;
      }
  
      m_nRenderMode = kRenderTransColor;
      SetRenderColor( 255, 255, 255, 255 );
      m_nRenderFX = kRenderFxNone;
  
      SetThink( &CEntityDissolve::DissolveThink );
      if ( gpGlobals->curtime > m_flStartTime )
      {
          // Necessary for server-side ragdolls
          DissolveThink();
      }
      else
      {
          SetNextThink( gpGlobals->curtime + 0.01f );
      }
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  // Input  : inputdata -
  //-----------------------------------------------------------------------------
  void CEntityDissolve::InputDissolve( inputdata_t &inputdata )
  {
      string_t strTarget = inputdata.value.StringID();
  
      if (strTarget == NULL_STRING)
      {
          strTarget = m_target;
      }
  
      CBaseEntity *pTarget = NULL;
      while ((pTarget = gEntList.FindEntityGeneric(pTarget, STRING(strTarget), this, inputdata.pActivator)) != NULL)
      {
          CBaseAnimating *pBaseAnim = pTarget->GetBaseAnimating();
          if (pBaseAnim)
          {
              pBaseAnim->Dissolve( NULL, gpGlobals->curtime, false, m_nDissolveType, GetAbsOrigin(), m_nMagnitude );
          }
      }
  }
  
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Creates a flame and attaches it to a target entity.
  // Input  : pTarget -
  //-----------------------------------------------------------------------------
  CEntityDissolve *CEntityDissolve::Create( CBaseEntity *pTarget, const char *pMaterialName,
      float flStartTime, int nDissolveType, bool *pRagdollCreated )
  {
      if ( pRagdollCreated )
      {
          *pRagdollCreated = false;
      }
  
      if ( !pMaterialName )
      {
          pMaterialName = DISSOLVE_SPRITE_NAME;
      }
  
      if ( pTarget->IsPlayer() )
      {
          // Simply immediately kill the player.
          CBasePlayer *pPlayer = assert_cast< CBasePlayer* >( pTarget );
          pPlayer->SetArmorValue( 0 );
          CTakeDamageInfo info( pPlayer, pPlayer, pPlayer->GetHealth(), DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE );
          pPlayer->TakeDamage( info );
          return NULL;
      }
  
      CEntityDissolve *pDissolve = (CEntityDissolve *) CreateEntityByName( "env_entity_dissolver" );
  
      if ( pDissolve == NULL )
          return NULL;
  
      pDissolve->m_nDissolveType = nDissolveType;
      if ( (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) )
      {
          if ( pTarget->IsNPC() && pTarget->MyNPCPointer()->CanBecomeRagdoll() )
          {
              CTakeDamageInfo info;
              CBaseEntity *pRagdoll = CreateServerRagdoll( pTarget->MyNPCPointer(), 0, info, COLLISION_GROUP_DEBRIS, true );
              pRagdoll->SetCollisionBounds( pTarget->CollisionProp()->OBBMins(), pTarget->CollisionProp()->OBBMaxs() );
  
              // Necessary to cause it to do the appropriate death cleanup
              if ( pTarget->m_lifeState == LIFE_ALIVE )
              {
                  CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
                  CTakeDamageInfo ragdollInfo( pPlayer, pPlayer, 10000.0, DMG_SHOCK | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE );
                  pTarget->TakeDamage( ragdollInfo );
              }
  
              if ( pRagdollCreated )
              {
                  *pRagdollCreated = true;
              }
  
              UTIL_Remove( pTarget );
              pTarget = pRagdoll;
          }
      }
  
      pDissolve->SetModelName( AllocPooledString(pMaterialName) );
      pDissolve->AttachToEntity( pTarget );
      pDissolve->SetStartTime( flStartTime );
      pDissolve->Spawn();
  
      // Send to the client even though we don't have a model
      pDissolve->AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
  
      // Play any appropriate noises when we start to dissolve
      if ( (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) )
      {
          pTarget->DispatchResponse( "TLK_ELECTROCUTESCREAM" );
      }
      else
      {
          pTarget->DispatchResponse( "TLK_DISSOLVESCREAM" );
      }
      return pDissolve;
  }
  
  
  //-----------------------------------------------------------------------------
  // What type of dissolve?
  //-----------------------------------------------------------------------------
  CEntityDissolve *CEntityDissolve::Create( CBaseEntity *pTarget, CBaseEntity *pSource )
  {
      // Look for other boogies on the ragdoll + kill them
      for ( CBaseEntity *pChild = pSource->FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() )
      {
          CEntityDissolve *pDissolve = dynamic_cast<CEntityDissolve*>(pChild);
          if ( !pDissolve )
              continue;
  
          return Create( pTarget, STRING( pDissolve->GetModelName() ), pDissolve->m_flStartTime, pDissolve->m_nDissolveType );
      }
  
      return NULL;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Attaches the flame to an entity and moves with it
  // Input  : pTarget - target entity to attach to
  //-----------------------------------------------------------------------------
  void CEntityDissolve::AttachToEntity( CBaseEntity *pTarget )
  {
      // So our dissolver follows the entity around on the server.
      SetParent( pTarget );
      SetLocalOrigin( vec3_origin );
      SetLocalAngles( vec3_angle );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  // Input  : lifetime -
  //-----------------------------------------------------------------------------
  void CEntityDissolve::SetStartTime( float flStartTime )
  {
      m_flStartTime = flStartTime;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Burn targets around us
  //-----------------------------------------------------------------------------
  void CEntityDissolve::DissolveThink( void )
  {
      CBaseAnimating *pTarget = ( GetMoveParent() ) ? GetMoveParent()->GetBaseAnimating() : NULL;
  
      if ( GetModelName() == NULL_STRING && pTarget == NULL )
           return;
  
      if ( pTarget == NULL )
      {
          UTIL_Remove( this );
          return;
      }
  
      // Turn them into debris
      pTarget->SetCollisionGroup( COLLISION_GROUP_DISSOLVING );
  
      if ( pTarget && pTarget->GetFlags() & FL_TRANSRAGDOLL )
      {
          SetRenderColorA( 0 );
      }
  
      float dt = gpGlobals->curtime - m_flStartTime;
  
      if ( dt < m_flFadeInStart )
      {
          SetNextThink( m_flStartTime + m_flFadeInStart );
          return;
      }
  
      // If we're done fading, then kill our target entity and us
      if ( dt >= m_flFadeOutStart + m_flFadeOutLength )
      {
          // Necessary to cause it to do the appropriate death cleanup
          // Yeah, the player may have nothing to do with it, but
          // passing NULL to TakeDamage causes bad things to happen
          CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
          int iNoPhysicsDamage = g_pGameRules->Damage_GetNoPhysicsForce();
          CTakeDamageInfo info( pPlayer, pPlayer, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL | iNoPhysicsDamage );
          pTarget->TakeDamage( info );
  
          if ( pTarget != pPlayer )
          {
              UTIL_Remove( pTarget );
          }
  
          UTIL_Remove( this );
  
          return;
      }
  
      SetNextThink( gpGlobals->curtime + TICK_INTERVAL );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Burn targets around us
  //-----------------------------------------------------------------------------
  void CEntityDissolve::ElectrocuteThink( void )
  {
      CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( GetMoveParent() );
      if ( !pRagdoll )
          return;
  
      ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( );
      for ( int j = 0; j < pRagdollPhys->listCount; ++j )
      {
          Vector vecForce;
          vecForce = RandomVector( -2400.0f, 2400.0f );
          pRagdollPhys->list[j].pObject->ApplyForceCenter( vecForce );
      }
  
      SetContextThink( &CEntityDissolve::ElectrocuteThink, gpGlobals->curtime + random->RandomFloat( 0.1, 0.2f ),
          s_pElectroThinkContext );
  }
  