// ---------------------------------------------------------------
// Zap Rift Entity
//
// AUTHOR
//        Tyler Lund <halflife@bubblemod.org>
//
// LICENSE
//
//        Permission is granted to anyone to use this  software  for
//        any purpose on any computer system, and to redistribute it
//        in any way, subject to the following restrictions:
//
//        1. The author is not responsible for the  consequences  of
//           use of this software, no matter how awful, even if they
//           arise from defects in it.
//        2. The origin of this software must not be misrepresented,
//           either by explicit claim or by omission.
//        3. Altered  versions  must  be plainly marked as such, and
//           must  not  be  misrepresented  (by  explicit  claim  or
//           omission) as being the original software.
//        3a. It would be nice if I got  a  copy  of  your  improved
//            version  sent to halflife@bubblemod.org.
//        4. This notice must not be removed or altered.
//
// ---------------------------------------------------------------

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"
#include "gamerules.h"
#include "effects.h"
#include "BMOD_zapgunrift.h"
#include "shake.h"

#define RIFTSPR "sprites/cnt1.spr"
#define RIFTSPR2 "sprites/cnt1.spr"

LINK_ENTITY_TO_CLASS( zaprift, CZapRift );

void CZapRift::Precache( void )
{
        PRECACHE_SOUND("debris/zap4.wav");
        PRECACHE_SOUND("weapons/electro4.wav");
        PRECACHE_SOUND("hassault/hw_shoot1.wav");
        PRECACHE_MODEL( RIFTSPR );
        PRECACHE_MODEL( RIFTSPR2 );
}

void CZapRift::Spawn( void )
{
        pev->classname = MAKE_STRING( "zapgun" );

        pev->movetype = MOVETYPE_FLY;
        pev->solid = SOLID_NOT;
 
		pev->rendermode = kRenderGlow;
        pev->renderamt = 240;
        pev->renderfx = kRenderFxNoDissipation;

		UTIL_MakeAimVectors( pev->angles );
		//pev->origin = pev->origin + gpGlobals->v_forward * 8;

        SET_MODEL(ENT(pev), RIFTSPR);
        pev->frame = 0;
        pev->scale = 2;
        m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1;

        // Create Inner Animated Sprite
        m_pSprite = CSprite::SpriteCreate( RIFTSPR2, pev->origin, TRUE );
        m_pSprite->Animate( 1 );
        m_pSprite->pev->scale = 1;
        m_pSprite->SetTransparency( kRenderGlow, 255, 255, 255, 180, kRenderFxNoDissipation );
        m_pSprite->pev->spawnflags |= SF_SPRITE_TEMPORARY;

        SetThink ( Animate );
        pev->nextthink = gpGlobals->time + 0.1;
        m_fLifeSpan = gpGlobals->time + 3;
        m_fNextElectrify = gpGlobals->time + 0.1;

        EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0,100 );
}

void CZapRift::Animate( void )
{
        pev->nextthink = gpGlobals->time + 0.1;



        if ( pev->frame++ )
        {
                if ( pev->frame > m_maxFrame )
                {
                        pev->frame = 0;
                }
        }
                
        m_pSprite->pev->scale = RANDOM_FLOAT(.5, 3);                

        if ( m_fLifeSpan < gpGlobals->time)
        {
			pev->effects |= EF_NODRAW;
            m_pSprite->Expand( 10, 100 );        
            SetThink(SUB_Remove);
			pev->nextthink = gpGlobals->time + 5;
        }

        if ( m_fNextElectrify < gpGlobals->time )
        {
                // Send out electric streamers and do area effect damage

				for (int i = 0; i < 3; i++)
				{
					Vector vecTemp;
					Vector vecEnd = Vector (0,0,0);
					
					// Hopefully find a long beam
					for (int j = 0; j < 5; j++)
					{
						vecTemp = FindBeam();
						if ( vecTemp.Length() > vecEnd.Length() )
							vecEnd = vecTemp;
					}

					// Draw the streamer
					MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
						WRITE_BYTE( TE_BEAMPOINTS );
						WRITE_COORD(pev->origin.x);
						WRITE_COORD(pev->origin.y);
						WRITE_COORD(pev->origin.z);
						WRITE_COORD( vecEnd.x );
						WRITE_COORD( vecEnd.y );
						WRITE_COORD( vecEnd.z );
						WRITE_SHORT( g_sModelIndexLightning );
						WRITE_BYTE( 0 ); // Starting frame
						WRITE_BYTE( 0  ); // framerate * 0.1
						WRITE_BYTE( 20 ); // life * 0.1
						WRITE_BYTE( 80 ); // width
						WRITE_BYTE( 50 ); // noise
						WRITE_BYTE( 180 ); // color r,g,b
						WRITE_BYTE( 255 ); // color r,g,b
						WRITE_BYTE( 96 ); // color r,g,b
						WRITE_BYTE( 255 ); // brightness
						WRITE_BYTE( 0 ); // scroll speed
					MESSAGE_END();

				}

				// Deal damage. 
				CBaseEntity *pEntity= NULL;
				TraceResult tr;
				while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, 512 )) != NULL)
				{
					// Only look for players
					if ( pEntity->pev->takedamage != DAMAGE_NO )
					{
						// blast's don't tavel  out of water,
						// so ignore players that lie on the other side.
						if (pEntity->pev->waterlevel == 0)
							continue;

						// Trace to the target
						UTIL_TraceLine( pev->origin, pEntity->BodyTarget( pev->origin ) , dont_ignore_monsters, ENT(pev), &tr);
						if (tr.flFraction == 1.0 || tr.pHit == pEntity->edict())
						{
							ClearMultiDamage( );
							pEntity->TraceAttack( VARS( pev->owner ), 30, Vector(0,0,1), &tr, DMG_SHOCK | DMG_ALWAYSGIB );
							ApplyMultiDamage( pev, VARS( pev->owner ) );
							UTIL_ScreenFade( pEntity, Vector(180,255,96), 2, 0.5, 128, FFADE_IN ); 
						}
					}
				}
                m_fNextElectrify = gpGlobals->time + 1; 
        }

}

Vector CZapRift::FindBeam( void )
{
	TraceResult tr;

	// Direction is within the forward hemisphere.
	UTIL_MakeAimVectors( pev->angles );
	Vector vecDir = gpGlobals->v_right   * RANDOM_FLOAT( -1,  1) 
				  + gpGlobals->v_up      * RANDOM_FLOAT( -1,  1)
				  + gpGlobals->v_forward * RANDOM_FLOAT(  0,  1);
	vecDir = vecDir.Normalize();

	// Maximum distance
	float fDist = 1024;

	// End point of the streamer
	Vector vecEnd = pev->origin + vecDir * fDist;

	// Trace to the end of the streamer to see if 
	// there is architecture in the way.
	UTIL_TraceLine(pev->origin, vecEnd, ignore_monsters, ENT(pev), &tr);
	fDist = (tr.vecEndPos - pev->origin).Length();

	// Now we need to find the distance to the water transition. 
	// (if there is one)
	if (UTIL_PointContents(tr.vecEndPos) != CONTENTS_WATER)
	{
		Vector vecMin = pev->origin;
		Vector vecMax = pev->origin + vecDir * fDist;
		Vector vecMid;
		float diff = fDist;

		while (diff > 1.0)
		{
			vecMid = vecMin + (vecMax - vecMin).Normalize() * diff / 2;
			if 	(UTIL_PointContents(vecMid) == CONTENTS_WATER)	
			{
				vecMin = vecMid;
			}
			else
			{
				vecMax = vecMid;
			}
			diff = (vecMax - vecMin).Length();
		}
		
		fDist = (vecMid - pev->origin).Length();
	}

	// Return new endpoint. 
	return pev->origin + vecDir * fDist;
}

LINK_ENTITY_TO_CLASS( zapbounce, CZapBounce );

void CZapBounce::Precache( void )
{
        PRECACHE_SOUND("debris/zap4.wav");
        PRECACHE_SOUND("weapons/electro4.wav");
        PRECACHE_SOUND("hassault/hw_shoot1.wav");
        PRECACHE_MODEL( RIFTSPR );
        PRECACHE_MODEL( RIFTSPR2 );
}

void CZapBounce::Spawn( void )
{
        pev->classname = MAKE_STRING( "multizapper" );

        pev->movetype = MOVETYPE_FLY;
        pev->solid = SOLID_NOT;
 
		//UTIL_MakeAimVectors( pev->angles );
		m_vecStart = pev->origin;
		m_vecDir = pev->angles;
		m_fDamage = 90;
		m_iBounce = 5;
		m_bFirstZap = TRUE;

        SetThink ( BounceThink );
        pev->nextthink = gpGlobals->time + 0.2;       

        EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0,100 );
}

void CZapBounce::BounceThink( void )
{
		TraceResult tr;
        CBaseEntity *pEntity;

        pev->nextthink = gpGlobals->time + 0.05;       
		
		// Zap Forward with some randomness.
		if (!m_bFirstZap) 
		{
			m_vecDir = m_vecDir * 5 + Vector(RANDOM_FLOAT( -1,  1),
											   RANDOM_FLOAT( -1,  1),
											   RANDOM_FLOAT( -1,  1)
											   );
		}
		m_vecDir = m_vecDir.Normalize();

		// Maximum distance for this segment.
		float fDist = 128;

		// End point of the streamer
		Vector vecEnd = m_vecStart + m_vecDir * fDist;

		// Trace to the end of the streamer 
		UTIL_TraceLine(m_vecStart, vecEnd, dont_ignore_monsters, pentIgnore, &tr);
		fDist = (tr.vecEndPos - m_vecStart).Length();
		
		// draw lightning
		for (int i = 0; i < 2; i++)
		{
			MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
					WRITE_BYTE( TE_BEAMPOINTS );
					WRITE_COORD(m_vecStart.x);
					WRITE_COORD(m_vecStart.y);
					WRITE_COORD(m_vecStart.z);
					WRITE_COORD( tr.vecEndPos.x );
					WRITE_COORD( tr.vecEndPos.y );
					WRITE_COORD( tr.vecEndPos.z );
					WRITE_SHORT( g_sModelIndexLightning );
					WRITE_BYTE( 0 ); // Starting frame
					WRITE_BYTE( 0  ); // framerate * 0.1
					WRITE_BYTE( 10 ); // life * 0.1
					WRITE_BYTE( m_iBounce * 10 ); // width
					WRITE_BYTE( 10 * (6 - m_iBounce )); // noise
					WRITE_BYTE( 96 ); // color r,g,b
					WRITE_BYTE( 180 ); // color r,g,b
					WRITE_BYTE( 255 ); // color r,g,b
					WRITE_BYTE( m_iBounce *  50); // brightness
					WRITE_BYTE( 0 ); // scroll speed
			MESSAGE_END();
		}

		m_vecStart = tr.vecEndPos;

		// Did we hit an entity? Then do damage.
        pEntity = CBaseEntity::Instance(tr.pHit);
		if (pEntity) {
			if (pEntity->pev->takedamage)
			{
				ClearMultiDamage( );
				pEntity->TraceAttack( VARS(pev->owner), m_fDamage, m_vecDir, &tr, DMG_SHOCK | DMG_ALWAYSGIB );
				ApplyMultiDamage( VARS(pev->owner), VARS(pev->owner) );
				UTIL_ScreenFade( pEntity, Vector(96,180,255), 2, 0.5, 128, FFADE_IN ); 
			}
			// Did we hit a wall? Then reflect.
			else 
			{
				float n;
				n = -DotProduct(tr.vecPlaneNormal, m_vecDir);

				Vector r;	
				r = 2.0 * tr.vecPlaneNormal * n + m_vecDir;
				m_vecDir = r;
			}
			pentIgnore = ENT(pEntity->pev);
		}

		// We can hurt ourselves after the first beam.
		m_bFirstZap = FALSE;

		// bounce once
		m_iBounce--;
		if (m_iBounce < 1)
		{
			// UTIL_ClientPrintAll( HUD_PRINTTALK, "<SERVER> Zap bounce point destroyed.\n");
            SetThink(SUB_Remove);
			pev->nextthink = gpGlobals->time + .1;
			return;
		}

		// split beam?
		if (RANDOM_LONG(0,100) < 80) 
		{
			m_fDamage *= .75;

			CZapBounce* pRift = (CZapBounce*)CBaseEntity::Create( "zapbounce", 
				m_vecStart, 
				m_vecDir, 
				ENT(pev->owner) );
			pRift->m_fDamage = m_fDamage;
			pRift->m_iBounce = m_iBounce;
			pRift->m_bFirstZap = FALSE;
			pRift->pev->nextthink = gpGlobals->time + RANDOM_LONG(.2,.3);
		}
}

