/*********************************************************************//**
*	BaseMonster
*	Abstraktni trida definujici zaklad vsech nepratel.
*	
*	author: Michal Jirous
*	date: 23.04.2009
*	file: ent_basemonster.cpp
**********************************************************************/

#include "ent_basemonster.h"
#include "game.h"
using namespace modelLib;

CBaseMonster::CBaseMonster()
{
	m_iTargetAnimation = -1;
	m_fTargetAngle = 0;
	m_iStatus = IDLE;
	m_fVisibilityRadius = BASIC_VISIBILITY_RADIUS;
	m_fAnglesPerPoint = ANGLES_PER_POINT;
	m_fAttackRadius = DEFAULT_ATTACK_RADIUS;

	anim_finnish_func = NULL;
	anim_finnish_monster = NULL;
	m_fSpeed = 0.0f; m_fMaxSpeed = 1.0f;
	m_iCurrentAnimation = -1;
	m_bDeathAnim = false;
	m_bStore_StartDeath = false;

	m_iProperties |= ENT_PARM_MONSTER;
	m_pSequence = NULL;
	m_bStore_WaitForSequence = false;
	m_bWaitForSequence = false;
	m_bNoticedPlayer = false;
	m_StepDelayTime = DEFAULT_STEP_DELAY_TIME;
	m_uiStepSoundStartTime = 0;

	game.m_uiTotalGameTargets++;
	m_iDefaultDeathSequence = 0;
}


void CBaseMonster::die()
{
	Creatures::die();
	game.m_uiTargetsDoneCount++;
}

void CBaseMonster::passFlags( int flags )
{
	if( flags & WAIT_FOR_SEQUENCE )
		m_bStore_WaitForSequence = true;
	if( flags & START_DEATH )
		m_bStore_StartDeath = true;


	m_bWaitForSequence = m_bStore_WaitForSequence;
}


void CBaseMonster::render(RenderData &renderData )
{
	glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT );
	glPushMatrix();
	glEnable( GL_TEXTURE_2D );
	glTranslatef( origin.x, origin.y, m_Model.m_bottomCenterPoint.z );
	glRotatef( angles.z, 0, 0, 1 );

	if( !m_bDeathAnim )
		m_pModel->update( renderData.frameTimes.elapsed_seconds );
	
	glColor3fv( (float*)&rendercolor );
	m_pModel->draw();

	glPopMatrix();
	glPopAttrib();
}


void CBaseMonster::processStartDeath()
{
	if( m_bStore_StartDeath )
	{
		//die no storage
		game.m_uiTargetsDoneCount++;
		m_bIsDeath = true;

		m_bDeathAnim = true;	//zadna animace

		//ale
		m_pModel->playSequence( m_iDefaultDeathSequence );
		m_pModel->setFrame( -1.0f );
		m_iCurrentAnimation = -1;
		m_iStatus = DYING;
	}
}

void CBaseMonster::onTarget( int target_type )
{
	if( m_bIsDeath  )	//klasicky kdyz vyvolam umrti vzdalene
	{
		if( m_bStore_StartDeath && m_iHealth > 0 )
		{
			m_bIsDeath = false;
			m_bDeathAnim = false;
			m_iStatus = INTERRUPTLESS_ANIM;
			
			game.m_uiTargetsDoneCount--;

			playAnimation( DefaultRaisingSequence, NULL, NULL );

			m_BackTimeData.push_front( BackTimeData() );
			m_BackTimeData.front().event_time = game.getGameTime();
			m_BackTimeData.front().type = EVENT_RAISE_FROM_DEATH;
		}
	}
	else if( !m_bIsDeath )
	{
		m_iHealth = 0;
		game.m_uiTargetsDoneCount++;
		m_bIsDeath = true;
	}
}


void CBaseMonster::compile()
{
	

	m_pModel = modelLib::modelLibrary.applyHalfLifeMDL( m_sModelName );

	m_pModel->getBoundingBox( (float*)m_RenderBounds.m_fBounds, (float*)&m_RenderBounds.m_fBounds[MAX_X] );
	m_RenderBounds.translate( origin );
	m_Target = origin;
	m_fSpeed = 1.0f; 

	Creatures::compile();

	processStartDeath();
	
}


bool CBaseMonster::renderCullTest( RenderData &renderData )
{
	return renderData.frustum->isBoundsInsideFrustum( m_RenderBounds );
}

void CBaseMonster::teleport( const alg::Vector &v, bool interpolation )
{
	CBasePhysics::teleport( v, interpolation );
	m_RenderBounds.translate( v );
}
#include "ctrl_gamevars.h"
#include "sys_controller.h"
void CBaseMonster::prepareForCollision()
{
	if( !m_bIsInAir && m_iStatus == REACH_TARGET )
		m_Velocity = m_vecDirection * m_fSpeed;
	else
		m_Velocity.clear();

	m_Velocity += Vector( 0.0f, 0.0f, -gamevarsLibrary::getfData( g_Gravity ) ) * systemController::RUNTIME_PERIOD_S * systemController::RUNTIME_PERIOD_S * POINTS_PER_METER;	//rychlost v m/s
	
	m_Model.m_vecMovement = m_Velocity;
}


void CBaseMonster::setTarget( ScriptedSequence *pSequence )
{
	m_pSequence = pSequence;
	m_bWaitForSequence = true;
}



void CBaseMonster::calculateDistanceAndAngle( float &dist, float &angle, Point &target )
{
	Vector playerTargetDirection = target - origin;
	playerTargetDirection.z = 0;
	dist = playerTargetDirection.absolute();

	
	angle = m_vecDirection.scalarMultiply( playerTargetDirection );

	Vector axis = m_vecDirection * playerTargetDirection;
	if( axis.z > 0 )
		angle = -angle;

}


void CBaseMonster::update( float seconds )
{
	Creatures::update( seconds );

	m_pModel->getBoundingBox( (float*)m_RenderBounds.m_fBounds, (float*)&m_RenderBounds.m_fBounds[MAX_X] );
	m_RenderBounds.translate( Point(origin.x, origin.y, origin.z - (m_Model.m_topCenterPoint.z - m_Model.m_bottomCenterPoint.z) ) );

	for( std::list<callBack>::iterator iter = m_CallBacks.begin(); iter != m_CallBacks.end(); ++iter )
	{
		if( (*iter).time < game.getGameTime() )
		{
			if( (*iter).anim_finnish_func && (*iter).anim_finnish_monster )
			{
				((*iter).anim_finnish_monster->*(*iter).anim_finnish_func )();
				iter = m_CallBacks.erase( iter );
				if( iter == m_CallBacks.end() )
					break;
			}
		}
	}


	if( m_iCurrentAnimation != -1 && !m_pModel->isPlaying() )
	{
		if( anim_finnish_monster && anim_finnish_func )
			(anim_finnish_monster->*anim_finnish_func)();
		m_iCurrentAnimation = -1;
		anim_finnish_func = NULL;
		anim_finnish_monster = NULL;

		if( m_iStatus == INTERRUPTLESS_ANIM )
			m_iStatus = IDLE;

		if( m_bIsDeath )
		{
			m_bDeathAnim = true;
			m_pModel->setFrame( -1.0f );
		}
	}
	
	m_MovementSound.setPosition( origin.x, origin.y, origin.z );


		if( !m_bIsInAir )
			m_StepSoundEnergy = std::min(m_StepSoundEnergy+30u,600u);
		else
			m_StepSoundEnergy -= std::min( m_StepSoundEnergy, 10u );

		if( !m_bIsInAir && m_StepSoundEnergy > m_StepDelayTime && m_Velocity.absolute() > 0.0f )
		{
			if( m_uiStepSoundStartTime + m_StepDelayTime < game.getGameTime() )
			{
				generalSounds.playStepSound( m_GroundMaterial, m_MovementSound );			
				m_uiStepSoundStartTime = game.getGameTime();
			}
		}




	if( m_iStatus == DYING )
		return;
	
	m_vecDirection.createFromAngle( angles.z ); 

	float dist, angle;
	calculateDistanceAndAngle( dist, angle, game.m_pPlayer->origin );
	
	
	bool m_bSeePlayer = false;

	if( dist < m_fVisibilityRadius )
	{	
		if( fabs(angle) < 130.0f )	//see player
		{
			m_bSeePlayer = true;
			m_bNoticedPlayer = true;
		}
	}
	else
		m_bNoticedPlayer = false;

	m_bSeePlayer |= m_bNoticedPlayer;

	if( m_bSeePlayer && !m_bWaitForSequence )
	{
		m_Target = game.m_pPlayer->origin;
		m_fTargetAngle = angle;
	}
	else
	{
		m_bSeePlayer = false;
		m_Target = origin;
		m_fTargetAngle = 0;
		dist = 0;
	}
	
	if( m_pSequence && m_bWaitForSequence )
	{
		if( compareFloats( m_pSequence->origin.x, origin.x, FLOAT_COMPARE_ACCURACY_LOW ) == 0 &&
			compareFloats( m_pSequence->origin.y, origin.y, FLOAT_COMPARE_ACCURACY_LOW ) == 0 )
		{
			if( m_pSequence->m_iAnimation != -1 )
				m_iStatus = CUSTOM;
			else
			{
				onSequenceEnd();
				m_bWaitForSequence = false;
				return;
			}
		}



		if( m_pSequence->m_bMoveHere )
		{
			calculateDistanceAndAngle( dist, angle, m_pSequence->origin );
			m_fTargetAngle = angle;
		}
	}

	m_fSpeed = min( m_fMaxSpeed, dist );

	if( m_bIsDeath )
		m_iStatus = DYING;

	

	if( m_iStatus <= REACH_TARGET  )
	{
		if( dist < m_fAttackRadius && m_bSeePlayer && fabs(m_fTargetAngle) < 45.0f && !m_bWaitForSequence )
		{
			onAttack();
			m_iStatus = ATTACK;
		}
		else if( fabs(m_fTargetAngle) > ROTATION_ANGLE_THRESHOLD || dist * ANGLES_PER_POINT < fabs(m_fTargetAngle) )
		{
			if( m_iStatus != ATTACK )
			{
				m_iStatus = REACH_ANGLE;
				onReachingAngle();
			}
		}
		else if( dist > m_fAttackRadius && ( m_bSeePlayer ) || m_bWaitForSequence && dist > 0.0f )
		{
			if( m_iStatus != ATTACK )
			{
				m_iStatus = REACH_TARGET;
				onReachingTarget();
			}
		}
		else
			m_iStatus = IDLE;
	}	
	
	if( m_iStatus == ATTACK )
	{
		onAttack();
		m_iStatus = ATTACK;
	}
	else if( m_iStatus == DYING )
		onDying();
	else if( m_iStatus == CUSTOM )
		custom();
	else if( m_iStatus == IDLE )
		onIdle();
}

void CBaseMonster::onAttack( const AttackInfo &attackInfo )
{
	Creatures::onAttack( attackInfo );
	if( attackInfo.m_pInitiator == game.m_pPlayer )
		m_bNoticedPlayer = true;
}



void CBaseMonster::custom()
{
	if( m_pSequence )
		playAnimation( m_pSequence->m_iAnimation, &CBaseMonster::onSequenceEnd, this );
}

void CBaseMonster::goBackInTime()
{
	bool death = m_bIsDeath;
	Creatures::goBackInTime();

	for( std::list<BackTimeData>::reverse_iterator riter = m_BackTimeData.rbegin(); riter != m_BackTimeData.rend(); ++riter  )
	{
		//jakmile nalezneme border, tak koncime pruchod
		if( (*riter).type == EVENT_BORDER )
			break;

		if( (*riter).type == EVENT_SEQUENCE )
		{
			m_bWaitForSequence = true;
			m_pSequence = (ScriptedSequence*)((*riter).values.pointer);
			break;
		}
	}

	m_bDeathAnim = m_bIsDeath;
	if( !m_bIsDeath )
		m_iStatus = IDLE;

	if( death && !m_bIsDeath )	//ozivl
	{
		game.m_uiTargetsDoneCount--;

	}
	else if( !death && m_bIsDeath )	//nejsem mrtev ale byl sem mrtev
	{
		processStartDeath();
	}
}


void CBaseMonster::onSequenceEnd()
{
	m_bWaitForSequence = false;
	
	//ulozime ukonceni sekvence
	m_BackTimeData.push_front( BackTimeData() );
	m_BackTimeData.front().event_time = game.getGameTime();
	m_BackTimeData.front().type = EVENT_SEQUENCE;
	m_BackTimeData.front().values.pointer = m_pSequence;


	if( m_pSequence )
	{
		m_pSequence->onEnd();
	}
	m_iStatus = IDLE;
	m_pSequence = NULL;
}

void CBaseMonster::decompile()
{
	Creatures::decompile();
	modelLib::modelLibrary.unloadModel( m_sModelName );
	modelLib::ModelElement *pModel = m_pModel;
	modelLib::modelLibrary.freeModelElement( pModel );
}

#include "level_collision.h"
bool CBaseMonster::basic_combat( int damage, AttackInfo &target, float distance, int damage_type, float dispersion)
{
	target.m_fDistance = distance;
	target.m_iDamage = damage;
	target.m_iDamageType = damage_type;
	target.m_Start = Point(origin.x, origin.y, origin.z - 16.0f);
	target.m_vecDirection = Vector(game.m_pPlayer->origin - origin).normalize();
	target.m_pInitiator = this;
	return collisionSystem.processShooting( target );
}

void CBaseMonster::restart()
{
	if( m_bIsDeath )
		game.m_uiTargetsDoneCount--;

	Creatures::restart();
	m_iStatus = IDLE;
	m_bDeathAnim = false;
	m_iCurrentAnimation = -1;
	m_pSequence = NULL;
	m_bWaitForSequence = m_bStore_WaitForSequence;
	m_uiStepSoundStartTime = 0;

	processStartDeath();
}

void CBaseMonster::insertCallBack( Uint32 delay, void (CBaseMonster::*func)(void ), CBaseMonster *monster )
{
	m_CallBacks.push_back( callBack() );
	m_CallBacks.back().time = game.getGameTime() + delay;
	m_CallBacks.back().anim_finnish_monster = monster;
	m_CallBacks.back().anim_finnish_func = func;
}


void CBaseMonster::onReachingAngle()
{
	if( m_fTargetAngle > 0.0f )	//kladny odecitame
	{
		angles.z -= min( m_fTargetAngle, 2.0f );
	}
	else
	{
		angles.z -= max( m_fTargetAngle, -2.0f );
	}
}

void CBaseMonster::onReachingTarget()
{
	if( m_fTargetAngle > 0.0f )	//kladny odecitame
	{
		angles.z -= min( m_fTargetAngle, 2.0f );
	}
	else
	{
		angles.z -= max( m_fTargetAngle, -2.0f );
	}
}

void CBaseMonster::onAttackEnd()
{
	m_iStatus = IDLE;
}


bool CBaseMonster::playAnimation( int sequence, void (CBaseMonster::*func)( void ), CBaseMonster *monster )
{
	if( m_pModel->isPlaying() && m_iStatus == ATTACK )
		return false;

	if( sequence == m_iCurrentAnimation )
		return false;

	m_CallBacks.clear();

	m_pModel->playSequence( sequence );
	anim_finnish_func = func;
	m_iCurrentAnimation = sequence;
	anim_finnish_monster = monster;
	return true;
}




void CBaseMonster::onAngleReachAnimEnd()
{
	

	m_iStatus = IDLE;
}
