#include "gamesrv.h"

#include "stdafx.h"
#include "Monster.h"
#include "Totem.h"

#include "../Common/Protocol.h"
#include "Status_Common.h"

#include "ObjectManager.h"
#include "SkillManager.h"
#include "AIManager.h"
#include "MonsterScript.h"
#include "DamageCalc.h"
#include "Player.h"
#include "StatusCalc_Server.h"
#include "drop.h"
#include "PathFinder.h"
#include "mode.h"

cMonster::cMonster( void )
: cBaseObject( eOBJECTTYPE_MONSTER )
, mPathFinder( 0 )
, mPathCount( 0 )
, mPathIndex( 0 )
, mpAction( NULL )
, mMoveStopRange( 0 )
, mGroupRegen( false )
, mMoveOverTime( 0 )
, mAttackType( eMONSTERATTACK_MAX )
, mTalkDealyEndTime( 0 )
, mTalkState( eMONSTERTALK_MAX )
, mDestroyTime( 0 )
, mFollowImportance( 0 )
{
}



cMonster::~cMonster( void )
{
	mpAction = NULL;
}			



void* cMonster::operator new( size_t n )
{
	if( n != sizeof(cMonster) ) 
	{
		assert(0);
		return NULL;
	}

	return OBJECTMANAGER->AllocMonster();
}



void cMonster::operator delete( void* ptr, size_t n )
{
	/// NULL 포인터 검사
	if( ptr == 0 )
	{
		assert(0);
		return;
	}

	if( n != sizeof(cMonster) ) 
	{
		assert(0);
		return;
	}

	OBJECTMANAGER->FreeMonster( static_cast<cMonster*>(ptr) );

	return;
}



bool cMonster::Init( unsigned long uniqueidx, unsigned long classIdx, unsigned long regenIdx )
{
	/// 초기화
	memset( &mStatusPlus, 0 , sizeof(mStatusPlus) );
	memset( &mStatusPer, 0 , sizeof(mStatusPer) );
	memset( mODDITY, 0, sizeof(mODDITY) );
	mTakeDamageRoot.pool = NULL;
	mTakeDamageRoot.root = NULL;
	mIsParentLink = false;
	mParentMonsterIdx = 0;
	mNextTargetChangeTime = 0;
	mFollowEndMove = false;
	mFirstAttackCharacter = 0;
	mFirstAttackParty = 0;
	mIsFirstMove = true;
	mCriDie = false;
	mOutsideDestroy = false;
	mSkillWaitEndTime = 0;
	mPathCount = 0;
	mPathIndex = 0;
	SetTarget( eOBJECTTYPE_NONE, 0 );
	SetMoveStopRange( 0 );
	mPlayerFollowPos = FOLLOW_POS_MAX;
	mpRegenMonsterInfo = NULL;
	ActionChange( eACT_MOVE );
	mOutsideAddMonster = false;
	mDestroyTime = 0;
	/// 모드
	mIsCombat = false;
	mTotalTime = 0;
	mModeTime = 0;
	mModeAgent = MONSTERSCRIPT->GetMonsterModeAgent( classIdx );
	mCurrentMode = 0;

	/// 설정값 셋팅
	mObject.index = uniqueidx;

	/// 몬스터 스크립트
	sMonsterScript* pMonsterScript = MONSTERSCRIPT->GetMonsterListInfo( classIdx );
	if( !pMonsterScript )
	{
		NETWORK2->PostServerEvent("Monster::Init pMonsterScript[%d]", classIdx );
		return false;
	}
	mpMonsterInfo = pMonsterScript;
	mMoveSpeed = (float)mpMonsterInfo->mActionWalkSpd;
	mHP = mpMonsterInfo->mMaxHp;
	mMP = mpMonsterInfo->mMaxMp;
	mFollowImportance =  mpMonsterInfo->mImportance;

	if( mpMonsterInfo->mMonsterScale <= 0.0f )
	{
		NETWORK2->PostServerEvent("cMonsterScript MonsterScale ERROR - %d", mpMonsterInfo->mMonsterScale );
		return false;
	}
	mFixedObjectSize = mpMonsterInfo->mMonsterFixSize * mpMonsterInfo->mMonsterScale; 

	cArray* pMonsterSkillAry = (cArray*)SKILLSCRIPT->GetMonsterBaseSkillArr( mpMonsterInfo->mMonsterClassIdx );
	if( pMonsterSkillAry == NULL )
	{
		NETWORK2->PostServerEvent("Monster::Init pMonsterSkillAry[%d]", mpMonsterInfo->mMonsterClassIdx );
		return false;
	}

	sMonsterSkillScript* pMonsterSkill;
	for( unsigned long i = 0 ; i< pMonsterSkillAry->GetSize() ; ++i )
	{
		pMonsterSkill = (sMonsterSkillScript*)(*pMonsterSkillAry)[i];
		if( pMonsterSkill == NULL )
		{
			NETWORK2->PostServerEvent("Monster::Init pMonsterSkill == NULL" );
			continue;
		}

		mSkillCoolTimeMap.Insert( pMonsterSkill->mAttackType, 0 );
	}


	/// 리젠 정보 - 있는 경우만 셋팅 - 없는 경우 소환 몬스터
	sRegenScript* pRegenScript = MONSTERSCRIPT->GetRegenMonsterInfo( regenIdx );
	if( pRegenScript )
	{
		mpRegenMonsterInfo = pRegenScript;

		SetMapNumber( mpRegenMonsterInfo->mMapNumber );
		mRegenPos.x = mpRegenMonsterInfo->mRegenX;
		mRegenPos.y = mpRegenMonsterInfo->mRegenY;

		sRegenGroupScript* pRegenGroupScript = MONSTERSCRIPT->GetRegenGroupInfo( pRegenScript->mGroupNumber );
		if( pRegenGroupScript == NULL )
		{
			NETWORK2->PostServerEvent("cActionRegen::Action pRegenGroupScript == NULL");
			return false;
		}

		/// 범위안에서 랜덤 좌표의 리젠 위치 
		float regenX = mRegenPos.x + ( rand() % (int)pRegenGroupScript->mRegenRange ) - ( rand() % (int)pRegenGroupScript->mRegenRange );
		float regenY = mRegenPos.y + ( rand() % (int)pRegenGroupScript->mRegenRange ) - ( rand() % (int)pRegenGroupScript->mRegenRange );


		if( AIMANAGER->IsPassible( GetMapNumber(), regenX, regenY, GetObject() ) == false )
		{
///			NETWORK2->PostServerEvent("cMonster::Init  IsPassible(%d,%f,%f,%d) == false", GetMapNumber(), regenX, regenY, GetObject().index );
			return false;
		}

		/// 리젠좌표 기억
		mRegenPos.x = regenX;
		mRegenPos.y = regenY;
		/// 리젠 좌표 셋팅
		mObjectPos.x = regenX;
		mObjectPos.y = regenY;

		mDirection = pRegenScript->mDirection;		/// 방향값
		if( pRegenScript->mDirection > 999.0f )		/// 1000 인경우 랜덤 방향값
			mDirection = S_ToRadian( ( rand() % 358 ) - 179 );		/// -179 ~ +179 사이 값을 랜덤하게 결정
	}

	return true;
}



void cMonster::SetSummonInfo( cMonster* pParent, unsigned short mapNumber, float regenX, float regenY, unsigned long regenRange, unsigned long destroyTime )
{
	SetMapNumber( mapNumber );

	mRegenPos.x = regenX;
	mRegenPos.y = regenY;

	/// 처음 선정위치에 선택이 안된경우 범위를 반으로 줄여가며 위치 선정
	for( unsigned long j = 0 ; j < 3 ; ++j )
	{
		NiPoint2 pos;
		pos.x = regenX + ( rand() % regenRange ) - ( regenRange / 2 );
		pos.y = regenY + ( rand() % regenRange ) - ( regenRange / 2 );

		if( AIMANAGER->IsPassible( GetMapNumber(), mRegenPos.x, mRegenPos.y, GetObject() ) == false )
		{
			regenRange = (long)(regenRange * 0.5f);
			continue;
		}

		mRegenPos = pos;
		mObjectPos = mRegenPos;
		break;
	}

	mDirection = S_ToRadian( ( rand() % 358 ) - 179 );		/// 방향값 -179 ~ +179 사이 값을 랜덤하게 결정

	mOutsideAddMonster = true;

	mDestroyTime = destroyTime;

	/// 그리드 추가한다.
	if( GRIDMANAGER->AddMonster( this ) == false )
	{
		OBJECTMANAGER->RemoveMonster( this->GetObjectID() );
		NETWORK2->PostServerEvent("cAIManager::BossSummonMonRegen GRIDMANAGER->AddMonster( pMonster ) == false" );
		return;
	}

	if( pParent != NULL )
	{
		mIsParentLink = true;
		mParentMonsterIdx = pParent->GetObjectID();

		/// 보스를 공격하던 공격자 목록을 자식에 넣음
		TakeDamageRoot* pRoot = pParent->GetTakeDamageRoot();
		if( pRoot != NULL )
		{
			PerTakeDamage* per = (PerTakeDamage*)pRoot->pool;
			while( per )
			{
				sObject player = { eOBJECTTYPE_PLAYER, per->playerIdx };
				AddTakeDamage( player, 0, rand() % 10, eTAKEDAMAGETYPE_DAMAGE );
				per = (PerTakeDamage*)per->next;
			}
		}

		mFollowImportance = 0;
		ActionChange( eACT_FOLLOW );
	}
}



void cMonster::Release()
{
	if( mIsParentLink == true )
	{
		cMonster* pParent = OBJECTMANAGER->GetMonster( mParentMonsterIdx );
		if( pParent != NULL )
			pParent->DieSummonMonster( mObject.index );
	}

	SetTarget( eOBJECTTYPE_NONE, 0 );
	mSkillCoolTimeMap.Clear();
	ClearTakeDamage();

	ReleaseInfluenceSet();
}


cBaseObject* cMonster::GetTarget( )
{
	/// 타겟 종류에 따라
	switch( mTarget.type )
	{
	case eOBJECTTYPE_PLAYER:
		/// 플레이어 포인터 반환
		return (cBaseObject*)NearGridFindPlayer( mTarget.index );
	case eOBJECTTYPE_MONSTER:
		assert(NULL);
		NETWORK2->PostServerEvent("cMonster::GetTarget mTarget.type == eOBJECTTYPE_MONSTER");
		return NULL;

	default:
		/// 없으면 NULL포인터 반환
		return NULL;
	}
}


bool cMonster::SetTarget( unsigned char kind, unsigned long idx )
{
	cPlayer* pPlayer = NULL;

	/// 추적 퍼짐 위치 저장
	unsigned char oldPlayerFollowPos = mPlayerFollowPos;
	unsigned char newPlayerFollowPos = FOLLOW_POS_MAX;

	bool isNewFight = false;
	bool isFightEnd = false;

	if( kind == eOBJECTTYPE_PLAYER )
	{
		//pPlayer = NearGridFindPlayer( idx );
		/// ※ 객체가 있지만 그리드 범위 밖으로 나갔을 수 있으니 objectmanager에서 찾아온다.
		pPlayer = OBJECTMANAGER->GetPlayer( idx );
		if( pPlayer == NULL || pPlayer->GetStateDie() == true )
			return false;

		/// 새로운 타겟에 몬스터 비중 계산해 가능하다면 설정
		if( pPlayer->MonsterImportancePlus( mFollowImportance ) == false )
			return false;		/// 기존 타겟 유지

		/// 신규 타겟에 맞는 추적 퍼짐 위치 선정
		newPlayerFollowPos = pPlayer->SelectFollowPos( oldPlayerFollowPos );

		/// 추적 기준점  설정
		mFollowStartPos = mObjectPos;
		mFollowEndTime  = NETWORK2->GetAccumTime() + mpMonsterInfo->mFollowTime;

		/// 타겟이 없다가 생김
		if( GetTarget() == NULL )
			isNewFight = true;
	}

	if( mTarget.type == eOBJECTTYPE_PLAYER )
	{
		/// 기존타겟에 몬스터 비중을 감소
		/// ※ 객체가 있지만 그리드 범위 밖으로 나갔을 수 있으니 objectmanager에서 찾아온다.
		pPlayer = OBJECTMANAGER->GetPlayer( mTarget.index );
		if( pPlayer != NULL && pPlayer->GetStateDie() == false )
		{
			pPlayer->MonsterImportanceMinus( mFollowImportance );

			/// 기존 타겟의 추적 퍼짐 위치 반환
			pPlayer->UnSelectFollowPos( oldPlayerFollowPos );
			newPlayerFollowPos = FOLLOW_POS_MAX;
		}

		/// 타겟이 있는데 해제함
		if( kind == eOBJECTTYPE_NONE )
		{
			mAttackType = eMONSTERATTACK_MAX;
			/// 죽지 않은 경우만 추적 포기 대사 처리
			if( pPlayer != NULL && pPlayer->GetStateDie() == false )
				isFightEnd = true;
		}
	}

	mPlayerFollowPos = newPlayerFollowPos;

	/// 내가 죽는 경우를 제외하고는 추적을 포기하는 경우다
	/// ※타겟 해제하기전에 대사 발송
	if( isFightEnd == true && mHP != 0 )
		SendSpeech( eMONSTERTALK_GIVEUP );

	cBaseObject::SetTarget( kind, idx );

	/// 타겟 설정 후에 대사 발송
	if( isNewFight == true )
		SendSpeech( eMONSTERTALK_FIGHT );	

	return true;
}


eACTION_CHANGE cMonster::Update( unsigned long elapsedTime, unsigned long accumTime )
{
	/// 일정시간 공격상태로 가지 않는경우 제거처리
	if( mIsParentLink == true )
	{
		/// 부모가 없는경우
		cMonster* pParent = GRIDMANAGER->GetMonster( mParentMonsterIdx );
		if( pParent == 0 )
		{
			mIsParentLink = false;
			mParentMonsterIdx = 0;

			/// 삭제시간 설정
			mDestroyTime = 5000;
		}
		else
		{
			if( pParent->GetStateDie() == true || pParent->IsCombat() == false )
			{
				/// 부모와의 연결정보 초기화
				mIsParentLink = false;
				mParentMonsterIdx = 0;

				/// 삭제시간 설정
				mDestroyTime = 5000;
			}
		}
	}

	mTotalTime += elapsedTime;
	if( mIsCombat == false )
	{
		if( mDestroyTime != 0 )
		{
			if( mDestroyTime <= elapsedTime )
			{
				mDestroyTime = 0;

				OutsideDestroy();
			}
			else
			{
				mDestroyTime -= elapsedTime;
			}
		}
	}
	else
	{
		/// Mode Process
		if( mModeAgent != 0 && mCurrentMode != 0 )
		{
			/// 속도 체크후 - 단위시간당 체크를 넣어보자!!
			UpdateMode( elapsedTime, accumTime );
		}
	}

	///
	SpeechProcess();
	return mpAction->Action( this, elapsedTime, accumTime );
}

void cMonster::UpdateMode( unsigned long elapsedTime, unsigned long /*accumTime*/ )
{
	/// 모드 시간 증가
	mModeTime += elapsedTime;

	if( GetStateDie() == true )
		return;

	/// 모드 전환 시점 체크
	cModeInfoArr* conArr = mCurrentMode->GetConditionModeInfoArr();
	for( unsigned int i=0; i<conArr->GetSize(); i++ )
	{
		sModeInfoBase* p = (sModeInfoBase*)((*conArr)[i]);
		if( p->type != eMODEINFO_MODECHANGE )
		{
			/// error
			assert(0);
			continue;
		}

		sModeChange* pInfo = (sModeChange*)p;
		bool modeChange = true;
		for( unsigned int i=0; i<2; i++ )
		{
			if( ConditionValueCheck( pInfo->mCondition[i].conType, pInfo->mCondition[i].conValue ) == false )
			{
				modeChange = false;
				break;
			}
		}

		if( modeChange == true )
		{
			ChangeCurrentMode( pInfo->mModeIdx );
			return;
		}
	}

	/// 모드 지속처리 체크
	cModeInfoArr* processArr = mCurrentMode->GetProcessModeInfoArr();
	for( unsigned int i=0; i<processArr->GetSize(); i++ )
	{
		/// 보유 소환물에 대한 시간 처리 문제 체크
		sModeInfoBase* p = (sModeInfoBase*)((*processArr)[i]);
		switch( p->type )
		{
		case eMODEINFO_GENMONSTER:
			{
				sModeGenMonster* pMonMode = (sModeGenMonster*)p;
				if( pMonMode == NULL )
					return;

				/// 반복 여부 체크 - 반복 하는 것만 쿨타임이 들어 있음
				cHashMap::cIterator find = mGenMonCoolTimeMap.Find( mCurrentMode->GetModeIndex() );
				if( find != mGenMonCoolTimeMap.End() )
				{
					unsigned long cooltime = (*find).mSecond;
					if( cooltime < NETWORK2->GetAccumTime() )
					{
						/// 몬스터 소환 & 다음 리젠시간 기록
						AIMANAGER->BossSummonMonRegen( this, mMapNumber, mObjectPos.x, mObjectPos.y, pMonMode->mGenRange, pMonMode->mCount, pMonMode->mGenMonsterIdx );
						(*find).mSecond = NETWORK2->GetAccumTime() + pMonMode->mDelayTime;
					}
				}
			}
			break;
		case eMODEINFO_GENTOTEM:
			{
				sModeGenTotem* pTotemMode = (sModeGenTotem*)p;
				if( pTotemMode == NULL )
					return;

				cHashMap::cIterator find = mGenTotemCoolTimeMap.Find( mCurrentMode->GetModeIndex() );
				if( find != mGenTotemCoolTimeMap.End() )
				{
					unsigned long coolTime = (*find).mSecond;
					if( coolTime < NETWORK2->GetAccumTime() )
					{
						/// 트랩 설정 & 다음 설정 시간 셋팅
						for( unsigned char i = 0 ; i < pTotemMode->mCount ; ++i )
						{
							cTotem* pTotem = NULL;
							unsigned long genRange = pTotemMode->mGenRange;
							/// 처음 선정위치에 선택이 안된경우 범위를 반으로 줄여가며 위치 선정
							for( unsigned long j = 0 ; j < 3 ; ++j )
							{
								NiPoint2 pos;
								pos.x = mObjectPos.x;
								pos.y = mObjectPos.y;

								pos.x = pos.x + ( rand() % genRange ) - ( genRange / 2 );
								pos.y = pos.y + ( rand() % genRange ) - ( genRange / 2 );

								pTotem = OBJECTMANAGER->AddTotem( pTotemMode->mGenTotemIdx, 0, pos, mObject, eAPPLYTYPE_ENEMY, eATTRIBUTETYPE_PHYSICAL );
								if( pTotem != NULL )
								{
									GRIDMANAGER->AddTotem( pTotem );
									break;
								}

								genRange = (unsigned long)(genRange * 0.5f);
							}

							/// 최종적으로 안된경우 보스위치에 선정
							if( pTotem == NULL )
							{
								pTotem = OBJECTMANAGER->AddTotem( pTotemMode->mGenTotemIdx, 0, mObjectPos, mObject, eAPPLYTYPE_ENEMY, eATTRIBUTETYPE_PHYSICAL );
								if( pTotem != NULL )
									GRIDMANAGER->AddTotem( pTotem );
							}
						}						
						(*find).mSecond = NETWORK2->GetAccumTime() + pTotemMode->mDelayTime;
					}
				}
			}
			break;
		}
	}

	/// 모드 사용스킬 체크
	if( GetActionID() == eACT_FOLLOW || GetActionID() == eACT_ATTACK )
	{
		if( mModeSkillIdx == eMONSTERATTACK_MAX )
		{
			mTempSelectSkill.Clear();
			unsigned long totalRate = 0;

			cModeInfoArr* skillArr = mCurrentMode->GetCheckSkillModeInfoArr();
			for( unsigned int i=0; i<skillArr->GetSize(); i++ )
			{
				sModeInfoBase* p = (sModeInfoBase*)((*skillArr)[i]);
				if( p->type != eMODEINFO_USESKILL )
				{
					/// error
					assert(0);
					continue;
				}

				sModeSkill* pInfo = (sModeSkill*)p;

				//if( pInfo->mModeDelayTime < mModeTime )
				//	continue;

				bool useSkill = true;
				for( unsigned int i=0; i<2; i++ )
				{
					if( ConditionValueCheck( pInfo->mCondition[i].conType, pInfo->mCondition[i].conValue ) == false )
					{
						useSkill = false;
						break;
					}
				}

				if( useSkill == false )
					continue;

				cCoolTime::cIterator begin = mEliteCoolTimeMap.Find( pInfo->mSkillIdx );
				cCoolTime::cIterator end = mEliteCoolTimeMap.End();
		        
				if( begin != end )
				{
					if( (*begin).mSecond > NETWORK2->GetAccumTime() )
						continue;
				}

				cBaseObject* pTarget = GetTarget();
				if( pTarget == NULL )
					continue;

				long useRate = CalcSkillRate( pTarget, (eMONSTERATTACK_TYPE)pInfo->mSkillIdx, (long)pInfo->mActionPer );
				totalRate += useRate;

				/// 조건에 만족하는 공격타입을 담는다.
				mTempSelectSkill.Insert( (eMONSTERATTACK_TYPE)pInfo->mSkillIdx, useRate );
			}

			if( totalRate != 0 )
			{
				/// 선택된 공격타입의 비율 전체 합계중 한지점을 정한다.
				unsigned long randSelect = rand() % totalRate + 1;

				/// 선택된 한지점의 공격타입값을 찾아 리턴한다.
				cHashMap::cIterator sI = mTempSelectSkill.Begin();
				cHashMap::cIterator sE = mTempSelectSkill.End();
				for( ; sI != sE ; ++sI )
				{
					unsigned long attackType = (*sI).mFirst;
					unsigned long rate = (*sI).mSecond;

					if( rate >= randSelect )
					{
						mModeSkillIdx = (eMONSTERATTACK_TYPE)attackType;
						break;
					}

					randSelect = randSelect - rate;
				}
			}
		}
	}
}

unsigned long cMonster::DamageCalc( sObject target, unsigned long skillIdx, unsigned long aType, unsigned long rType )
{
	return DAMAGECALC->MonsterDamageCalc( mObject.index, target, (eMONSTERATTACK_TYPE)skillIdx, (eATTRIBUTETYPE)aType, (eRANGETYPE)rType );
}


bool cMonster::AttackSuccess( sObject target, unsigned long skillIdx, unsigned long aType, unsigned short /*accuracyValue*/ )
{
	return DAMAGECALC->MonsterAttackSuccess( mObject.index, target, (eMONSTERATTACK_TYPE)skillIdx, (eATTRIBUTETYPE)aType);
}


bool cMonster::CriticalSuccess( sObject target, unsigned long skillIdx, unsigned long aType, unsigned short /*criticalValue*/ )
{
	return DAMAGECALC->MonsterCritical( mObject.index, target, (eMONSTERATTACK_TYPE)skillIdx, (eATTRIBUTETYPE)aType );
}


void cMonster::SetEscape()
{
	switch( GetActionID() )
	{
	case eACT_ESCAPE:
	case eACT_DIE:
		return;
	}

	ActionChange( eACT_ESCAPE );
}


void cMonster::Comeback()
{
	/// 타겟 제거
	SetTarget( eOBJECTTYPE_NONE, 0 );

	/// 원래 지역으로 이동중 - 선공몬스터 공격 안하게 함
	SetFollowEndMove( true );

	/// 선공 드랍 유저 클리어
	mFirstAttackCharacter = 0;
	mFirstAttackParty = 0;

	/// 자유이동 범위 밖에 있는 경우만 RUN
	unsigned long freeRange = mpMonsterInfo->mFreeMoveRange;
	mRangeCheck.SetRadius( (float)freeRange );
	if( mRangeCheck.IsRange( mObjectPos, mRegenPos ) == false )
		SetMoveSpeed( mpMonsterInfo->mActionRunSpd, eMOVESPEED_RUN );	/// 몬스터 회귀 속도
	else
		SetMoveSpeed( mpMonsterInfo->mActionWalkSpd, eMOVESPEED_WALK );

	/// 범위안에서 랜덤 좌표의 리젠 위치 
	float targetPosX = mRegenPos.x + ( rand() % (int)freeRange ) - ( rand() % (int)freeRange );
	float targetPosY = mRegenPos.y + ( rand() % (int)freeRange ) - ( rand() % (int)freeRange );

	/// 갈수 있는지 역인지 체크
	if( AIMANAGER->IsPassible( mMapNumber, targetPosX, targetPosY, mObject ) == false )
		return;

	/// 이동 초기화
	MoveStop();
	SetMoveStopRange( 0 );

	/// 몬스터가 이동할 경로 셋팅
	SetPath( targetPosX, targetPosY );

	/// hp/mp 회복 설정 체크
	if( mpMonsterInfo->mIsRecovery == true )
	{
		if( mHP != GetMaxHP() )
			HPHeal( GetMaxHP(), true, 0, 0, eTAKEDAMAGETYPE_NONE );

		if( mMP != GetMaxMP() )
			MPHeal( GetMaxMP(), false );
	}

	/// 자유 이동으로 변경
	ActionChange( eACT_MOVE );

	SetCombatFlag( false );
}


bool cMonster::MoveUpdate( unsigned long deltaTime )
{

	/// 이동불가 상태 이상인 경우
	if( IsCantMove() == true )
		return false;

	if( mPathCount == 0 )
		return false;

	/// 경로가 설정되지 않았다
	if( mNextPos.x < 0.0f || mNextPos.y < 0.0f )
		return false;

	float moveDeltaTime = (float)(deltaTime) / SECOND_THOUSAND;

	/// 몬스터의 이동속도
	NiPoint2 pos = mObjectPos;
	float speed = static_cast<float>(GetMoveSpeed());

	/// 방향 설정
	NiPoint2 dir1;
	dir1.x = mNextPos.x - pos.x;
	dir1.y = mNextPos.y - pos.y;
	dir1.Unitize();

	const float limitOverTime = 0.01f;
	float plusTime;

	/// 목적지점 넘어서 저장된 시간을 이동시 +
	if( mMoveOverTime > limitOverTime )
		plusTime = limitOverTime;
	else
		plusTime = mMoveOverTime;

	mMoveOverTime = mMoveOverTime - plusTime;

	/// 속도 * 경과시간 만큼의 예상 이동 위치 계산
	pos.x += (speed * ( moveDeltaTime + plusTime ) * dir1.x);
	pos.y += (speed * ( moveDeltaTime + plusTime ) * dir1.y);

	/// 목적지 좌표와 예상 이동 위치를 비교
	NiPoint2 dir2;
	dir2.x = mNextPos.x - pos.x;
	dir2.y = mNextPos.y - pos.y;
	dir2.Unitize();

	bool keepMoving = true;

	/// 예상 이동위치가 목적지 좌표를 넘어가면 목적지 도달로 판정
	if( dir2.Dot(dir1) <= 0.0f )
	{
		NiPoint2 overPoint( pos.x, pos.y );

		/// 길찾기 경로 목적 지점을 넘어선 경우 넘은 거리만큼 시간 보관
		float overLength = ( overPoint - mNextPos ).Length();
		mMoveOverTime = mMoveOverTime + ( overLength / speed );

		//CONSOLE->WriteLog("OverLength, MoveOverTime %f %f", overLength, mMoveOverTime );

		pos.x = mNextPos.x;
		pos.y = mNextPos.y;
		if( mPathIndex < mPathCount-1 )
		{
			mNextPos = mPathArray[++mPathIndex];

			float viewPosX, viewPosY;
			viewPosX = mObjectPos.x - mNextPos.x;
			viewPosY = mObjectPos.y - mNextPos.y;

			mDirection = atan2( viewPosX, viewPosY );
		}
		else
		{
			mPathCount = 0;
			mPathIndex = 0;
			mMoveOverTime = 0.0f;
			keepMoving = false;
		}
	}

	/// 몬스터가 목적지에서 근처(일정범위)까지 도달하면 정지하는 모듈
	if( mMoveStopRange != 0.0f )
	{
		/// 타겟 오브젝트의 최종 목적지
		NiPoint3 targetPos;
		NiPoint3 monsterGuessPos( pos.x, pos.y, 0.0f );
		AIMANAGER->CalcHeight( mMapNumber, &monsterGuessPos.z, monsterGuessPos.x, monsterGuessPos.y );
		NiPoint3 monsterNowPos( GetXPos(), GetYPos(), Height() );

		/// 실제 타겟의 현재위치
		cBaseObject* pBaseObject = GetTarget();
		if( pBaseObject )
		{
			/// 타겟 오브젝트가 있는경우 오브젝트의 실제 위치를 가져옴
			targetPos.x = pBaseObject->GetXPos();
			targetPos.y = pBaseObject->GetYPos();
			targetPos.z = pBaseObject->Height();
		}
		else
		{
			/// 타겟 오브젝트가 없는경우 기록되있던 타겟 오브젝트의 위치를 가져옴
			targetPos.x = mLastPos.x;
			targetPos.y = mLastPos.y;
			AIMANAGER->CalcHeight( mMapNumber, &targetPos.z, targetPos.x, targetPos.y );
		}

		float moveStopRangeFix = OBJECTMANAGER->ObjectSizeRange( this, pBaseObject, mMoveStopRange );

		mRangeCheck.SetRadius( moveStopRangeFix );


		/// 이동 예상 위치와 실제 타겟 오브젝트의 충돌체크
		if( mRangeCheck.IsRange( monsterGuessPos, targetPos ) )
		{
			/// 좌표 보정
			NiPoint3 p;

			NiPoint3 d = monsterNowPos - targetPos;
			d.Unitize();

			p.x = targetPos.x + ((moveStopRangeFix-1.0f)*d.x);
			p.y = targetPos.y + ((moveStopRangeFix-1.0f)*d.y);

			SetPos( p.x, p.y );
			SetMoveStopRange( 0.0f );
			mLastPos.x = p.x;
			mLastPos.y = p.y;

			return false;
		}
	}

	SetPos( pos.x, pos.y );

	return keepMoving;
}



unsigned int cMonster::SetPath( float targetX, float targetY )
{
	/// 이동불가 상태 이상인 경우
	if( IsCantMove() == true )
		return 0;

	/// 타겟 좌표와 몬스터 좌표가 같으면 경로 설정안함
	if( GetXPos() == targetX && GetYPos() == targetY )
	{
		/// 경로 없음
		return 0;
	}

	/// 이동 할 최종 포인트를 기록해둠
	mLastPos.x = targetX;
	mLastPos.y = targetY;

	unsigned long timetick = GetTickCount();

	/// 길찾기 실행
	if( FindPath( NiPoint2( targetX, targetY ) ) == false )
	{
		return 0;
	}

	unsigned int pathCount = GetPathCount();
	assert( pathCount );

	unsigned long now = GetTickCount();

	if( now - timetick > 1000 )
	{
		NETWORK2->PostServerEvent("PathDelay[%d] cnt[%d] %.0f,%.0f -> %.0f,%.0f", now - timetick, pathCount, GetXPos(), GetYPos(), targetX, targetY );
		Verbose->WriteLog("PathDelay[%d] cnt[%d] %.0f,%.0f -> %.0f,%.0f", now - timetick, pathCount, GetXPos(), GetYPos(), targetX, targetY );
	}

	/// 이동 목적지(좌표)까지 이동하는 형태 
	SetMoveStopRange( 0 );

	PerIoContext* perIoContext = NETWORK2->GetIoContext( IOCP_REQUEST_WRITE );

	/// 이동 메세지 발송
	MSG_SYN_MOVE_MONSTER* msg = (MSG_SYN_MOVE_MONSTER*)perIoContext->buffer;

	msg->Category = NM_MONSTER;
	msg->Protocol = NM_MONSTER_MOVE_SYN;

	msg->mMonsterIdx = GetObjectID();

	for( unsigned int i = 0 ; i < pathCount ; ++i )
		msg->mPathArray[i] = GetPathArray()[i];

	msg->mMoveSpeed = GetMoveSpeed();
	msg->mMoveSpeedType = mMoveSpeedType;
	msg->mCount = pathCount;

	NETWORK2->QuickSend( this, (char*)msg, msg->GetMsgLength() );

	perIoContext->offset = msg->GetMsgLength();
	NETWORK2->ReleaseIoContext( perIoContext, "cMonster::SetPath:MSG_SYN_MOVE_MONSTER" );

	return pathCount;	
}



unsigned int cMonster::SetPath( float targetX, float targetY, float range, sObject target )
{
	/// 이동불가 상태 이상인 경우
	if( IsCantMove() == true )
		return 0;

	/// 타겟 좌표와 몬스터 좌표가 같으면 경로 설정안함
	if( GetXPos() == targetX && GetYPos() == targetY )
	{
		/// 경로 없음
		return 0;
	}

	/// 이동 할 최종 포인트를 기록해둠
	mLastPos.x = targetX;
	mLastPos.y = targetY;

	/// 길찾기 실행
	if( FindPath( NiPoint2(targetX, targetY) ) == false )
	{
		return 0;
	}

	unsigned int pathCount = GetPathCount();
	assert( pathCount );

	PerIoContext* perIoContext = NETWORK2->GetIoContext( IOCP_REQUEST_WRITE );

	/// 이동끝까지 가기전에 range범위 안에 들어오면 이동을 멈추는 이동형태
	SetMoveStopRange( range );

	/// 에이전트 -> 클라이언트에 전송
	MSG_SYN_ACTIONMOVE_MONSTER*	msg = (MSG_SYN_ACTIONMOVE_MONSTER*)perIoContext->buffer;

	msg->Category = NM_MONSTER;
	msg->Protocol = NM_MONSTER_ACTIONMOVE_SYN;

	for( unsigned int i = 0 ; i < pathCount ; ++i )
		msg->mPathArray[i] = GetPathArray()[i];

	msg->mMonsterIdx = GetObjectID();
	msg->mMoveSpeed = GetMoveSpeed();
	msg->mCount = pathCount;
	msg->mTarget = target;
	msg->mRange = range;

	NETWORK2->QuickSend( this, (char*)msg, msg->GetMsgLength() );

	perIoContext->offset = msg->GetMsgLength();
	NETWORK2->ReleaseIoContext( perIoContext, "cMonster::SetPath:MSG_SYN_ACTIONMOVE_MONSTER" );

	return pathCount;
}



void cMonster::ActionChange( eACTION_ID state )
{
	switch( state )
	{
	case eACT_MOVE:			SetAction( mActionMove ); SetCombatFlag( false ); break;
	case eACT_DIE:			SetAction( mActionDie ); SetCombatFlag( false ); break;
	case eACT_ATTACK:		SetAction( mActionAttack ); break;
	case eACT_ESCAPE:		SetAction( mActionEscape ); break;
	case eACT_FOLLOW:		SetAction( mActionFollow ); SetCombatFlag( true ); break;
	case eACT_IDLE:			SetAction( mActionIdle ); break;
	}
}



void cMonster::SetAction( cAction& pNewAtion )
{
	if( mpAction != NULL && mpAction->GetActionID() == eACT_DIE )
		return;

	/// 이미 사용중인 상태면 굳이 바꿀 필요는 없다.
	if( mpAction == &pNewAtion ) return;
	mpAction = NULL;
	mpAction = &pNewAtion;
	mpAction->ActionInit( this, NETWORK2->GetAccumTime() );
}


bool cMonster::AddInfluence( unsigned long uniqueIdx, unsigned long influenceClassIdx )
{ 
	if( mInfluenceSet.Insert( uniqueIdx ) == false )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("cMonster[%d]::AddSkillInfluence mInfluenceSet.Insert( %d, %d ) == false", mObject.index, uniqueIdx, influenceClassIdx );
		return false;
	}

	return true;
}


void cMonster::DeleteInfluence( unsigned long influenceIdx )
{
	if( mInfluenceSet.Erase( influenceIdx ) == false )
		NETWORK2->PostServerEvent("cMonster[%d]::EraseSkillInfluence[%d]", mObject.index, influenceIdx );
}



void cMonster::ReleaseInfluenceSet() 
{ 
	cSkillInfluenceSet::cIterator start = mInfluenceSet.Begin();
	cSkillInfluenceSet::cIterator end = mInfluenceSet.End();

	unsigned long infIdx = 0;
	while( start != end )
	{
		infIdx = *start++;
		SKILLMANAGER->DeleteInfluenceList( infIdx );
	}

	mInfluenceSet.Clear();
}


void cMonster::StatusCalc()
{ 
	STATUSCALC->CalcMonster( mObject.index ); 
}


bool cMonster::IsParentEqual( unsigned long parentUniqueIdx )
{
	cSkillInfluenceSet::cIterator beginAura = mInfluenceSet.Begin();
	cSkillInfluenceSet::cIterator endAura = mInfluenceSet.End();

	cInfluenceObject* pHaveInfluenceObject = NULL;
	while( beginAura != endAura )
	{
		/// 보유 효과 오브젝트
		pHaveInfluenceObject = SKILLMANAGER->GetInfluence( *beginAura++ );
		if( !pHaveInfluenceObject )	
		{ 
			assert(NULL); 
			NETWORK2->PostServerEvent("cSkillManager::IsParentEqual pHaveInfluenceObject==NULL Aura" );
			//-/-//InsertDeleteInfluenceObject( pHaveInfluenceObject );	/// 객체 삭제 대기열에 넣는다.
			continue; 
		}

		if( pHaveInfluenceObject->IsParentEqual( parentUniqueIdx ) == true )
			return true;
	}

	return false;
}

void cMonster::AddTakeDamage( sObject attacker, unsigned long damage, long distressPoint, eTAKEDAMAGE_TYPE type )
{
	unsigned long playerIdx = attacker.index;
	if( attacker.type == eOBJECTTYPE_TOTEM )
	{
		cTotem* totem = OBJECTMANAGER->GetTotem( attacker.index );
		if( totem == 0 )
			return;

		sObject o = totem->GetAttacker();
		if( o.type != eOBJECTTYPE_PLAYER )
			return;

		playerIdx = o.index;
	}

	mFollowEndTime = NETWORK2->GetAccumTime() + mpMonsterInfo->mFollowTime;

	/// 등록값 계산..
	long calcPoint = 0;
	unsigned long calcDamage = 0;
	switch( type )
	{
	case eTAKEDAMAGETYPE_DAMAGE:
		{
			/// 최초 공격자에 대한 정보 등록..
			if( mFirstAttackCharacter == 0 )
			{
				mFirstAttackCharacter = playerIdx;
				cPlayer* pPlayer = NearGridFindPlayer( playerIdx );
				if( pPlayer != NULL )
					mFirstAttackParty = pPlayer->GetPartyIndex();
			}

			calcPoint = (long)(damage * AGGRO_DAMAGE_RATE) + distressPoint;
			calcDamage = damage;
		}
		break;
	case eTAKEDAMAGETYPE_DAMAGE_SETTARGET:
		{
			calcPoint = (long)(damage * AGGRO_DAMAGE_RATE) + distressPoint;
			calcDamage = damage;
		}
		break;
	case eTAKEDAMAGETYPE_HEAL:
		{
			calcPoint = (long)(damage * AGGRO_HEAL_RATE) + distressPoint;
		}
		break;
	case eTAKEDAMAGETYPE_POTION:
		{
			calcPoint = (long)(damage * AGGRO_POTION_RATE) + distressPoint;
		}
		break;
	case eTAKEDAMAGETYPE_ADDPLAYER:
		break;
	default:
		return;
	}

	/// damage List에 등록 또는 갱신
	PerTakeDamage* perTakeDamage = TAKEDAMAGEPOOL->GetTakeDamage( &mTakeDamageRoot, playerIdx );
	if( perTakeDamage  == 0 )
	{
		perTakeDamage = TAKEDAMAGEPOOL->AddTakeDamage( &mTakeDamageRoot, playerIdx, calcDamage, calcPoint );
		if( perTakeDamage )
		{
			/// player에 등록한다.
			cPlayer* pPlayer = GRIDMANAGER->GetPlayer( playerIdx );
			if( pPlayer )
				pPlayer->AddTargetingMonster( GetObjectID() );
		}
		else
		{
			assert(0);
		}

		/// party 정보 등록
		cPlayer* pPlayer = GRIDMANAGER->GetPlayer( playerIdx );
		if( pPlayer != NULL )
		{
			unsigned long partyIdx = pPlayer->GetPartyIndex();
			cParty* partyInfo = PARTYMAN->GetParty( partyIdx );
			if( partyInfo )
			{
				unsigned long* userArr = partyInfo->GetUserArr();
				for( unsigned int i = 0; i < partyInfo->GetCount(); ++i, ++userArr )
				{
					if( playerIdx == (*userArr) )
						continue;

					cPlayer* pPartyPlayer = GRIDMANAGER->GetPlayer( (*userArr) );
					if( pPartyPlayer == 0 )
						continue;

					if( TAKEDAMAGEPOOL->GetTakeDamage( &mTakeDamageRoot, (*userArr) ) == 0 )
					{
						if( TAKEDAMAGEPOOL->AddTakeDamage( &mTakeDamageRoot, (*userArr), 0, 0 ) )
						{
							/// player에 등록한다.
							pPartyPlayer->AddTargetingMonster( GetObjectID() );
						}
						else
						{
							assert(0);
						}
					}
				}
			}
		}
	}
	else
	{
		perTakeDamage->takeDamage += calcDamage;

		if( calcPoint < 0 )
		{
			if( perTakeDamage->distressPoint < (unsigned long)(calcPoint*(-1)) )
				perTakeDamage->distressPoint = 0;
			else
				perTakeDamage->distressPoint += calcPoint;
		}
		else
			perTakeDamage->distressPoint += calcPoint;
	}

	if( GetStateDie() == true )
		return;

	if( type == eTAKEDAMAGETYPE_DAMAGE_SETTARGET )
	{
		if( SetTarget( eOBJECTTYPE_PLAYER, playerIdx ) == true )
			ActionChange( eACT_FOLLOW );

		return;
	}
	else if( type == eTAKEDAMAGETYPE_NONETARGET )
		return;

	/// 타겟 갱신..
	cBaseObject* pCurTarget = GetTarget();
	if( pCurTarget == 0 )
	{
		/// 신규 갱신..
		cPlayer* pNewTarget = 0;
		unsigned long maxPoint = 0;

		PerTakeDamage* per = (PerTakeDamage*)mTakeDamageRoot.pool;
		while( per )
		{
			cPlayer* perTarget = NearGridFindPlayer( per->playerIdx );
			if( perTarget )
			{
				if( maxPoint < per->distressPoint )
					pNewTarget = perTarget;
			}
			per = (PerTakeDamage*)per->next;
		}

		if( pNewTarget )
		{
			/// 타겟 갱신
			if( SetTarget( pNewTarget->GetObjectType(), pNewTarget->GetObjectID() ) == false )
				return;
		}
		else
		{
			/// 타겟을 새로 잡을 수 없는 경우
			if( SetTarget( eOBJECTTYPE_PLAYER, playerIdx ) == false )
				return;
		}
		ActionChange( eACT_FOLLOW );
	}
	else
	{
		PerTakeDamage* per = TAKEDAMAGEPOOL->GetTakeDamage( &mTakeDamageRoot, pCurTarget->GetObjectID() );
		if( per == 0 )
		{
			assert(0);
			/// 현재 공격자 타겟팅..
			if( SetTarget( eOBJECTTYPE_PLAYER, playerIdx ) == false )
				return;
		}

		unsigned long maxPoint = (unsigned long)((float)per->distressPoint * 1.1f);
		if( maxPoint <= perTakeDamage->distressPoint )
		{
			/// 타겟 변경
			if( SetTarget( eOBJECTTYPE_PLAYER, playerIdx ) == false )
				return;
		}
	}
}

void cMonster::DelTakeDamagePlayer( unsigned long playerIdx )
{
	TAKEDAMAGEPOOL->ReleaseTakeDamage( &mTakeDamageRoot, playerIdx );
}

void cMonster::ClearDistressPointPlayer( unsigned long playerIdx )
{
	PerTakeDamage* per = TAKEDAMAGEPOOL->GetTakeDamage( &mTakeDamageRoot, playerIdx );
	if( per )
		per->distressPoint = 0;
	else
		assert(0);

	if( mTarget.index == playerIdx )
		ChangeActionTarget();
}

void cMonster::ClearDistressPoint()
{
	/// 신규 갱신..
	PerTakeDamage* per = (PerTakeDamage*)mTakeDamageRoot.pool;
	while( per )
	{
		per->distressPoint = 0;

		per = (PerTakeDamage*)per->next;
	}
}

void cMonster::ClearTakeDamage()
{
	/// 신규 갱신..
	PerTakeDamage* per = (PerTakeDamage*)mTakeDamageRoot.pool;
	while( per )
	{
		unsigned long playerIdx = per->playerIdx;

		cPlayer* perPlayer = NearGridFindPlayer( per->playerIdx );
		if( perPlayer )
			perPlayer->DelTargetingMonster( GetObjectID() );
		else
		{
			perPlayer = GRIDMANAGER->GetPlayer( per->playerIdx );
			if( perPlayer )
				perPlayer->DelTargetingMonster( GetObjectID() );
			else
				assert(0);
		}

		per = (PerTakeDamage*)per->next;
		TAKEDAMAGEPOOL->ReleaseTakeDamage( &mTakeDamageRoot,  playerIdx );
	}

	TAKEDAMAGEPOOL->Shutdown();
}

//void cMonster::AddTakeDamage( unsigned long playerIdx, unsigned long takeDamage )
//{
//	/// 처음 공격자 우선권 설정
//	if( mFirstAttackCharacter == 0 )
//	{
//		mFirstAttackCharacter = playerIdx;
//		cPlayer* pPlayer = NearGridFindPlayer( playerIdx );
//		if( pPlayer != NULL )
//			mFirstAttackParty = pPlayer->GetPartyIndex();
//	}
//
//	/// 공격 성공이면 추적 시간 재설정
//	mFollowEndTime = NETWORK2->GetAccumTime() + mpMonsterInfo->mFollowTime;
//
//	cTakeDamageMap::cIterator find = mTakeDamageMap.Find( playerIdx );
//	cTakeDamageMap::cIterator end = mTakeDamageMap.End();
//
//	if( find == end )
//	{
//		if( mTakeDamageMap.Insert( playerIdx, takeDamage ) == NULL )
//			assert(NULL);		
//	}
//	else
//		(*find).mSecond = (*find).mSecond + takeDamage;
//
//	/// 추적이동 끝 - 원래 위로 복귀 이동인지 체크
//	if( mFollowEndMove == true )
//		SetFollowEndMove( false );	/// 복귀중이 아님으로 변경
//
//	/// 선정된 타겟이 없으면 타겟 변경
//	if( GetTarget() == NULL )
//	{
//		SetTarget( eOBJECTTYPE_PLAYER, playerIdx );
//	}
//
//	if( GetStateDie() != true )
//		SendSpeech( eMONSTERTALK_DAMAGE );
//}



unsigned long cMonster::GetTakeDamage( unsigned long playerIdx )
{
	PerTakeDamage* per = TAKEDAMAGEPOOL->GetTakeDamage( &mTakeDamageRoot, playerIdx );
	if( per )
		return per->takeDamage;

	return 0;
}


eMONSTERATTACK_TYPE cMonster::SelectSkill()
{
	///// 현재 선정돼있으면 리턴
	//if( mAttackType != eMONSTERATTACK_MAX )
	//	return mAttackType;

	/// 타겟 객체 확인
	cPlayer* pTarget = (cPlayer*)GetTarget();
	if( pTarget == NULL || pTarget->GetStateDie() == true )
	{
		mAttackType = eMONSTERATTACK_MAX;
		return mAttackType;
	}

	/// 모드스킬 선정된것이 있고 대상이 이동 상태가 아니면 모드스킬 사용
	if( mModeSkillIdx != eMONSTERATTACK_MAX && pTarget->GetState() != eOBJECT_STATE_MOVE )
	{
		mAttackType = mModeSkillIdx;
		return mAttackType;
	}

	cCoolTime::cIterator iter;
	
	mTempSelectSkill.Clear();

	if( mODDITY[eODDITYTYPE_SLEEP] != 0 )
	{
		mAttackType = eMONSTERATTACK_MAX;
		return mAttackType;
	}
	if( mODDITY[eODDITYTYPE_STUN] != 0 )
	{
		mAttackType = eMONSTERATTACK_MAX;
		return mAttackType;
	}

	unsigned long useTotalPer = 0;
	eMONSTERATTACK_TYPE attackType;
	sMonsterSkillScript* pMonsterSkillScript;
	for( iter = mSkillCoolTimeMap.Begin() ; iter != mSkillCoolTimeMap.End() ; ++iter )
	{
		attackType = (eMONSTERATTACK_TYPE)(*iter).mFirst;

		if( mODDITY[eODDITYTYPE_CANTSKILL] != 0 )
		{
			if( eMONSTERATTACK_SKILL1 <= attackType )
				continue;
		}

		/// 쿨타임 시간 안됨
		if( (*iter).mSecond > NETWORK2->GetAccumTime() )
		{
			continue;
		}

		pMonsterSkillScript = SKILLSCRIPT->GetMonsterSkillInfo( mpMonsterInfo->mMonsterClassIdx, attackType );
		if( pMonsterSkillScript == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("Monster::SelectSkill pMonsterSkillScript[%d,%d] == NULL", mpMonsterInfo->mMonsterClassIdx, attackType);
			continue;
		}

		switch( pMonsterSkillScript->mUseHPCheck )
		{
		case eMONSTERHPCHECK_NONE:
			break;
		case eMONSTERHPCHECK_LOWHP40:
			{
				if( mHP * 100 / GetMaxHP() >= SKILL_USE_HP40 )
				{
					continue;
				}
			}
			break;
		case eMONSTERHPCHECK_LOWHP80:
			{
				if( mHP * 100 / GetMaxHP() >= SKILL_USE_HP80 )
				{
					continue;
				}
			}
			break;
		}

		if( mMP < pMonsterSkillScript->mUseMP )
		{
			continue;
		}

		if( pMonsterSkillScript->mSkillUsePer == 0 )
			continue;

		/// 스킬 선정에 필요한 항목 비율 계산
		unsigned long skillRate = CalcSkillRate( pTarget, attackType, pMonsterSkillScript->mSkillUsePer );
		useTotalPer = useTotalPer + skillRate;
		
		/// 조건에 만족하는 공격타입을 담는다.
		mTempSelectSkill.Insert( attackType, skillRate );
	}

	if( useTotalPer == 0 )
		return eMONSTERATTACK_MAX;

	/// 선택된 공격타입의 비율 전체 합계중 한지점을 정한다.
	unsigned long randSelect = rand() % useTotalPer + 1;

	/// 선택된 한지점의 공격타입값을 찾아 리턴한다.
	cHashMap::cIterator sI = mTempSelectSkill.Begin();
	cHashMap::cIterator sE = mTempSelectSkill.End();
	for( ; sI != sE ; ++sI )
	{
		unsigned long attackType = (*sI).mFirst;
		unsigned long rate = (*sI).mSecond;

		if( rate >= randSelect )
		{
			mAttackType = (eMONSTERATTACK_TYPE)attackType;
			return mAttackType;
		}

		randSelect = randSelect - rate;
	}

	return eMONSTERATTACK_MAX;
}



void cMonster::UpdateSkillCoolTime( eMONSTERATTACK_TYPE attackType )
{
	if( eMONSTERATTACK_MAX > attackType )
	{
		cCoolTime::cIterator iter = mSkillCoolTimeMap.Find( attackType );
		cCoolTime::cIterator end = mSkillCoolTimeMap.End();

		sMonsterSkillScript* pSkill = SKILLSCRIPT->GetMonsterSkillInfo( mpMonsterInfo->mMonsterClassIdx, attackType );
		if( pSkill == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("monster coolTime error - script MonClassIdx[%d], attackType[%d]",mpMonsterInfo->mMonsterClassIdx, attackType );
			return;
		}

		if( iter != end )
		{
			(*iter).mSecond = NETWORK2->GetAccumTime() + pSkill->mCoolTime;
		}
	}
	else
	{
		cCoolTime::cIterator iter = mEliteCoolTimeMap.Find( attackType );
		cCoolTime::cIterator end = mEliteCoolTimeMap.End();

		sMonsterSkillScript* pSkill = SKILLSCRIPT->GetMonsterSkillInfo( mpMonsterInfo->mMonsterClassIdx, attackType );
		if( pSkill == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("monster coolTime error - script MonClassIdx[%d], attackType[%d]",mpMonsterInfo->mMonsterClassIdx, attackType );
			return;
		}

		if( iter != end )
		{
			(*iter).mSecond = NETWORK2->GetAccumTime() + pSkill->mCoolTime;
		}
		else
		{
			mEliteCoolTimeMap.Insert( attackType, NETWORK2->GetAccumTime() + pSkill->mCoolTime );
		}

	}
}


void cMonster::CalcSkillWaitTime( unsigned long skillEndTime )
{ 
	mSkillWaitEndTime = skillEndTime + SKILL_WAIT_TIME; 
}


bool cMonster::IsSkillWaitEnd()
{ 
	return mSkillWaitEndTime <= NETWORK2->GetAccumTime(); 
}


inline bool cMonster::GetStateDie() 
{
	switch( GetActionID() )
	{
	case eACT_DIE:
		return true;
	}		
	return false;
}


void cMonster::OutsideDestroy()
{
	mOutsideDestroy = true; 		
	ActionChange( eACT_DIE );

	MSG_SYN_MONSTER_DESTROY synMsg;
	synMsg.Category			= NM_MONSTER;
	synMsg.Protocol			= NM_MONSTER_DESTROY_SYN;
	synMsg.mMonsterIdx	    = mObject.index;
	NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_DESTROY) );
}


void cMonster::StateOddity( long* pOddity )
{

	for( int i = 0 ; i < eODDITYTYPE_MAX ; ++i )
	{
		if( mODDITY[i] != pOddity[i] )
		{
			if( pOddity[i] != 0 )
				SendOddity( (eODDITYTYPE)i, true );
			else
				SendOddity( (eODDITYTYPE)i, false );

			mODDITY[i] = pOddity[i];
		}
	}
}


void cMonster::SendOddity( eODDITYTYPE pos, bool apply )
{
	if( apply == false )
		return;

	/// 상태 이상중 클라이언트에 메세지를 발송 해줘야 하는 부분 처리
	switch(pos)
	{
	case eODDITYTYPE_CANTMOVE:
		{
			MoveStop();

			MSG_SYN_MONSTER_MOVESTOP synMsg;
			synMsg.Category     = NM_MONSTER;
			synMsg.Protocol     = NM_MONSTER_MOVESTOP_SYN;
			synMsg.mMonsterIdx  = mObject.index;
			synMsg.mMonsterPos.x = mObjectPos.x;
			synMsg.mMonsterPos.y = mObjectPos.y;
			NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(synMsg) );
		}
		break;
	case eODDITYTYPE_SLEEP:
		{
			MoveStop();

			MSG_SYN_MONSTER_MOVESTOP synMsg;
			synMsg.Category     = NM_MONSTER;
			synMsg.Protocol     = NM_MONSTER_MOVESTOP_SYN;
			synMsg.mMonsterIdx  = mObject.index;
			synMsg.mMonsterPos.x = mObjectPos.x;
			synMsg.mMonsterPos.y = mObjectPos.y;
			NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(synMsg) );
		}
		break;
	case eODDITYTYPE_STUN:
		{
			MoveStop();

			MSG_SYN_MONSTER_MOVESTOP synMsg;
			synMsg.Category     = NM_MONSTER;
			synMsg.Protocol     = NM_MONSTER_MOVESTOP_SYN;
			synMsg.mMonsterIdx  = mObject.index;
			synMsg.mMonsterPos.x = mObjectPos.x;
			synMsg.mMonsterPos.y = mObjectPos.y;
			NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(synMsg) );
		}
		break;
	}
}


bool cMonster::IsCantSkill()
{ 
	if( mODDITY[eODDITYTYPE_CANTSKILL] != 0 )
	{
		if( eMONSTERATTACK_SKILL1 <= mAttackType && mAttackType <= eMONSTERATTACK_SKILL3 )
			return true;
	}
	if( mODDITY[eODDITYTYPE_SLEEP] != 0 )
		return true; 
	if( mODDITY[eODDITYTYPE_STUN] != 0 )
		return true;

	return false;
}


void cMonster::TargetChange( sObject attacker, long distressPoint )
{
	/// 어그로 수치 증가
	AddTakeDamage( attacker, 0, distressPoint, eTAKEDAMAGETYPE_DAMAGE_SETTARGET );

	/// 추적 상태 설정
	if( GetStateDie() == false )
		ActionChange( eACT_FOLLOW );
}

long cMonster::CalcPhysicAttack( unsigned long physicAttack )
{
	long tempPhysicAttack = (long)( physicAttack
		+ ( physicAttack * mStatusPer.mPhysicAttack / 100 ) 
		+ mStatusPlus.mPhysicAttack ); 

	return tempPhysicAttack < 0 ? 0 : tempPhysicAttack; 
}


long cMonster::CalcPhysicRangeAttack( unsigned long physicRangeAttack )
{
	long tempPhysicRangeAttack = (long)( physicRangeAttack
		+ ( physicRangeAttack * mStatusPer.mPhysicRangeAttack / 100 ) 
		+ mStatusPlus.mPhysicRangeAttack ); 

	return tempPhysicRangeAttack < 0 ? 0 : tempPhysicRangeAttack; 
}


long cMonster::CalcMagicAttack( unsigned long magicAttack )
{
	long tempMagicAttack = (long)( magicAttack
		+ ( magicAttack * mStatusPer.mMagicAttack / 100 ) 
		+ mStatusPlus.mMagicAttack ); 

	return tempMagicAttack < 0 ? 0 : tempMagicAttack; 
}


long cMonster::GetPhysicDefense()
{
	long def = mpMonsterInfo->mDef;
	def = (long)( def
		+ ( def * mStatusPer.mPhysicDefense / 100 ) 
		+ mStatusPlus.mPhysicDefense ); 

	return def < 0 ? 0 : def; 
}


long cMonster::GetMagicDefense()
{
	long mDef = mpMonsterInfo->mMdef;
	mDef = (long)( mDef
		+ ( mDef * mStatusPer.mMagicDefense / 100 ) 
		+ mStatusPlus.mMagicDefense ); 

	return mDef < 0 ? 0 : mDef; 
}


long cMonster::GetAvoid()
{
	long avoid = mpMonsterInfo->mAvoid;
	avoid = (long)( avoid
		+ ( avoid * mStatusPer.mAvoid / 100 ) 
		+ mStatusPlus.mAvoid ); 

	return avoid < 0 ? 0 : avoid; 
}


long cMonster::CalcPhysicAttackRate( unsigned long physicAttackRate )
{
	long tempPhysicAttackRate = (long)( physicAttackRate
		+ ( physicAttackRate * mStatusPer.mPhysicAttackRate / 100 ) 
		+ mStatusPlus.mPhysicAttackRate ); 

	return tempPhysicAttackRate < 0 ? 0 : tempPhysicAttackRate; 
}


long cMonster::CalcMagicAttackRate( unsigned long magicAttackRate )
{
	long tempMagicAttackRate = (long)( magicAttackRate
		+ ( magicAttackRate * mStatusPer.mMagicAttackRate / 100 ) 
		+ mStatusPlus.mMagicAttackRate ); 

	return tempMagicAttackRate < 0 ? 0 : tempMagicAttackRate; 
}


long cMonster::CalcPhysicCritical( unsigned long physicCritical )
{
	long tempPhysicCritical = (long)( physicCritical
		+ ( physicCritical * mStatusPer.mPhysicCritical / 100 ) 
		+ mStatusPlus.mPhysicCritical ); 

	return tempPhysicCritical < 0 ? 0 : tempPhysicCritical; 
}


long cMonster::CalcMagicCritical( unsigned long magicCritical )
{
	long tempMagicCritical = (long)( magicCritical
		+ ( magicCritical * mStatusPer.mMagicCritical / 100 ) 
		+ mStatusPlus.mMagicCritical ); 

	return tempMagicCritical < 0 ? 0 : tempMagicCritical; 
}


float cMonster::GetAttackSpeed()
{
	if( mStatusPer.mAttackSpeed == 0.0f )
		return 1.0f;

	float attackSpeed = ( 100 + mStatusPer.mAttackSpeed ) / 100;

	if( attackSpeed < 0.1f )
		attackSpeed = 0.1f;

	if( attackSpeed > 2.0f )
		attackSpeed = 2.0f;

	return attackSpeed;
}


float cMonster::CalcStatusSkillRange( float skillRange, eRANGETYPE mRangeType )
{
	/// 스킬 사거리
	float range = skillRange;

	/// 효과 사거리
	float statusRangePlus = 0;
	float statusRangePer = 0;

	if( mRangeType == eRANGETYPE_CLOSE )
	{
		statusRangePlus = mStatusPlus.mInRange;
		statusRangePer = mStatusPer.mInRange;
	}
	else if( mRangeType == eRANGETYPE_LONG )
	{
		statusRangePlus = mStatusPlus.mOutRange;
		statusRangePer = mStatusPer.mOutRange;
	}
	else
	{
		assert(NULL);
		NETWORK2->PostServerEvent("cMonster::CalcStatusSkillRange mRangeType[%d] Err", mRangeType );
	}

	/// 사거리 계산
	range = range * ( ( 100 + statusRangePer ) / 100 ) + statusRangePlus;

	return range;
}


long cMonster::GetMoveSpeed()
{
	long moveSpeed = (long)( mStatusBase.mMoveSpeed
		+ ( mStatusBase.mMoveSpeed * mStatusPer.mMoveSpeed / 100 ) 
		+ mStatusPlus.mMoveSpeed ); 

	return moveSpeed < 0 ? 0 : moveSpeed; 
}


long cMonster::SetMoveSpeed( unsigned long moveSpeed, eMOVE_SPEED_TYPE moveSpeedType )
{ 
	mMoveSpeedType = moveSpeedType;
	mStatusBase.mMoveSpeed = (float)moveSpeed; 
	return GetMoveSpeed(); 
}


void cMonster::SendMoveSpeed()
{
	MSG_SYN_MONSTER_MOVESPEED synMsg;
	synMsg.Category     = NM_MONSTER;
	synMsg.Protocol     = NM_MONSTER_MOVESPEED_SYN;
	synMsg.mMonsterIdx  = mObject.index;
	synMsg.mMoveSpeed   = GetMoveSpeed();
	NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(synMsg) );
}


unsigned long cMonster::HPDamage( unsigned long damage, bool* die, bool msgSend, bool cri )
{
	/// 도망 조건 체크
	unsigned long beforeHPPer = mHP * 100 / GetMaxHP();	/// 이전 hp %

	/// 실제 적용된 데미지
	unsigned long realDamage = 0;
	*die = false;

	if( GetStateDie() == true )
	{  
		*die = true;
		return realDamage;
	}

	/// 현재 HP가 공격 받은 데미지 보다 작은경우
	if( mHP <= damage )
	{
		/// 몬스터HP 0으로 바꾸고 남아있던 HP만큼만 리턴
		realDamage = mHP;
		mHP = 0;
		*die = true;
		ActionChange( eACT_DIE );

		/// 죽을 때 대사 구분
		mCriDie = cri;
	}
	else 
	{ 
		/// 잠들어 있는 상태라면 풀어 준다.
		if( mODDITY[eODDITYTYPE_SLEEP] != 0 )
		{
			cSkillInfluenceSet::cIterator begin = mInfluenceSet.Begin();
			cSkillInfluenceSet::cIterator end = mInfluenceSet.End();

			cInfluenceObject* pInf = NULL;
			unsigned long influenceIdx = 0;
			while( begin != end )
			{
				influenceIdx = (*begin++);
				pInf = SKILLMANAGER->GetInfluence( influenceIdx );
				if( pInf == NULL )
					continue;

				if( pInf->IsSleep() == true )
					SKILLMANAGER->DeleteInfluenceList( influenceIdx );
			}
		}

		/// 데미지 만큼 감소시키고 데미지량 리턴
		realDamage = damage;
		mHP -= damage;	
	}

	/// 공격 성공이면 추적 시간 재설정
	mFollowEndTime = NETWORK2->GetAccumTime() + mpMonsterInfo->mFollowTime;

	/// 도망 조건 체크
	unsigned long afterHPPer = mHP * 100 / GetMaxHP();	/// 현재 hp %

	switch( mpMonsterInfo->mEscapeType )
	{
	case eESCAPETYPE_HP50LOW:
		{
			if( beforeHPPer > ESCAPE_HP50 && afterHPPer < ESCAPE_HP50 )
			{
				unsigned long randSelect = rand() % 100 + 1;
				if( mpMonsterInfo->mEscapePer >= randSelect )
					SetEscape();
			}
		}
		break;
	case eESCAPETYPE_HP30LOW:
		{
			if( beforeHPPer > ESCAPE_HP30 && afterHPPer < ESCAPE_HP30 )
			{
				unsigned long randSelect = rand() % 100 + 1;
				if( mpMonsterInfo->mEscapePer >= randSelect )
					SetEscape();
			}
		}
		break;
	}

	if( msgSend == true )
	{
		MSG_SYN_MONSTER_HP synMsg;
		synMsg.Category			= NM_MONSTER;
		synMsg.Protocol			= NM_MONSTER_HP_SYN;
		synMsg.mMonsterIdx	    = GetObjectID();
		synMsg.mHP				= mHP;
		synMsg.mMaxHP			= GetMaxHP();
		synMsg.mDie				= GetStateDie();

		NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_HP) );
	}

	return realDamage;
}



void cMonster::HPHeal( unsigned long hp, bool /*msgSend*/, unsigned long /*healerIdx*/, long /*distressPoint*/, eTAKEDAMAGE_TYPE /*type*/ )
{
	/// 힐을 받은 HP양이 최대치를 넘어 서는 경우
	if( GetMaxHP() < mHP + hp )
	{
		/// hp값을 최대치로 변경
		mHP = GetMaxHP();
	}
	else
	{ 
		/// 힐을 받은 양을 더해줌
		mHP += hp;
	}

	MSG_SYN_MONSTER_HP synMsg;
	synMsg.Category			= NM_MONSTER;
	synMsg.Protocol			= NM_MONSTER_HP_SYN;
	synMsg.mMonsterIdx	    = GetObjectID();
	synMsg.mHP				= mHP;
	synMsg.mMaxHP			= GetMaxHP();
	synMsg.mDie				= GetStateDie();

	NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_HP) );

}



void cMonster::MPDamage( unsigned long damage, bool msgSend )
{
	/// 현재 MP가 공격 받은 데미지 보다 작은경우
	if( mMP <= damage )
	{
		/// 몬스터MP 0으로 바꿈
		mMP = 0;
	}
	else 
	{ 
		/// 데미지 만큼 감소시키고 데미지량 리턴
		mMP -= damage;	
	}

	if( msgSend == true )
	{
		MSG_SYN_MONSTER_MP synMsg;
		synMsg.Category			= NM_MONSTER;
		synMsg.Protocol			= NM_MONSTER_MP_SYN;
		synMsg.mMonsterIdx	    = GetObjectID();
		synMsg.mMP				= mMP;
		synMsg.mMaxMP			= GetMaxMP();

		NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_MP) );
	}
}



void cMonster::MPHeal( unsigned long heal, bool /*msgSend*/ )
{
	/// 힐을 받은 MP양이 최대치를 넘어 서는 경우
	if( GetMaxMP() < mMP + heal )
	{
		/// mp값을 최대치로 변경
		mMP = GetMaxMP();
	}
	else
	{ 
		/// 힐을 받은 양을 더해줌
		mMP += heal;
	}

	//MSG_SYN_MONSTER_MP synMsg;
	//synMsg.Category			= NM_MONSTER;
	//synMsg.Protocol			= NM_MONSTER_MP_SYN;
	//synMsg.mMonsterIdx	    = GetObjectID();
	//synMsg.mMP				= mMP;
	//synMsg.mMaxMP			= GetMaxMP();

	//NETWORK2->QuickSendExcept( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_MP) );
}



void cMonster::MoveStop()
{
	mPathCount = 0;
	mPathIndex = 0;

	/// 이동중이던 클라이언트는 정지 시킨다.
	MSG_SYN_MONSTER_MOVESTOP synMsg;
	synMsg.Category     = NM_MONSTER;
	synMsg.Protocol     = NM_MONSTER_MOVESTOP_SYN;
	synMsg.mMonsterIdx  = mObject.index;
	synMsg.mMonsterPos.x = mObjectPos.x;
	synMsg.mMonsterPos.y = mObjectPos.y;

	NETWORK2->QuickSend( this, (char*)&synMsg, sizeof(synMsg) );
}


bool cMonster::FindPath( const NiPoint2& goal )
{
	if( mPathFinder == NULL )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("cMonster::FindPath == NULL");
		return false;
	}

	if( mPathFinder->FindPath( mPathArray, &mPathCount, MAX_PATH_COUNT, NiPoint2(mObjectPos.x, mObjectPos.y), goal ) == false )
		return false;

	mPathIndex = 0;
	mLastPos.x = goal.x;
	mLastPos.y = goal.y;
	mNextPos.x = mPathArray[0].x;
	mNextPos.y = mPathArray[0].y;

	float viewPosX, viewPosY;
	viewPosX = mObjectPos.x - mNextPos.x;
	viewPosY = mObjectPos.y - mNextPos.y;

	mDirection = atan2( viewPosX, viewPosY );

	return true;
}



void cMonster::SendSpeech( unsigned long talkState )
{
	if( talkState == eMONSTERTALK_MAX )
	{
		mTalkState = (unsigned char)talkState;
		return;
	}

	sMonsterTalk* pMonsterTalk = MONSTERSCRIPT->GetMonsterTalkInfo( mpMonsterInfo->mMonsterClassIdx, (unsigned char)talkState );
	if( pMonsterTalk == NULL )
		return;

	/// 프로세싱 설정
	switch( talkState )
	{
	case eMONSTERTALK_ATTACK1:	/// 발동하면 기존 대사 프로세싱 하던것을 종료		
	case eMONSTERTALK_ATTACK2:
	case eMONSTERTALK_ATTACK3:
	case eMONSTERTALK_SKILL1:
	case eMONSTERTALK_SKILL2:
	case eMONSTERTALK_GIVEUP:	
		mTalkState = eMONSTERTALK_MAX;
		break;
	case eMONSTERTALK_STAND:	/// 프로세싱 해야하는 항목
	case eMONSTERTALK_WORK:
	case eMONSTERTALK_RUN:
	case eMONSTERTALK_FOLLOW:
	case eMONSTERTALK_ESCAPE:
		{
			mTalkState = (unsigned char)talkState;
			/// 다음 체크 시간 설정
			mTalkProcessCheckTime = NETWORK2->GetAccumTime() + pMonsterTalk->mCheckTime;
		}
		break;
	}

	/// TALK_DELAY초간 다른 대화 발송 불가
	if( mTalkDealyEndTime > NETWORK2->GetAccumTime() )
		return;

	if( pMonsterTalk->mViewPercent == 0 )
		return;

	/// 대사 표시 확률 체크
	unsigned long randSelect = rand() % 100 + 1;
	if( pMonsterTalk->mViewPercent < randSelect )
		return;

	/// 선택된 대사 체크
	unsigned char randType = (unsigned char)( rand() % MONSTER_TALK_SIZE );
	unsigned long talkIdx = pMonsterTalk->mTalkIdx[randType];
	if( talkIdx == 0 )
		return;

	/// 메세지 발송
	MSG_SYN_MONSTER_SPEECH synMsg;
	synMsg.Category = NM_MONSTER;
	synMsg.Protocol = NM_MONSTER_SPEECH_SYN;
	synMsg.mMonsterIdx = GetObjectID();
	synMsg.mViewType = pMonsterTalk->mViewType;
	synMsg.mTalkIdx	= talkIdx;
	synMsg.mCharacterIdx = 0;

	/// 확인코드 - 이 경우 외의 타겟 정보는 타겟 선정 방법을 결정한 후에 넣자!
	switch( talkState )
	{
	case eMONSTERTALK_FIGHT:
	case eMONSTERTALK_ATTACK1:
	case eMONSTERTALK_ATTACK2:
	case eMONSTERTALK_ATTACK3:
	case eMONSTERTALK_SKILL1:
	case eMONSTERTALK_SKILL2:
	case eMONSTERTALK_DAMAGE:		/// 타겟과 공격자가 틀리더라도 타겟 정보로 발송 한다.
	case eMONSTERTALK_FOLLOW:
	case eMONSTERTALK_ESCAPE:
	case eMONSTERTALK_GIVEUP:
	case eMONSTERTALK_DIE1:
	case eMONSTERTALK_DIE2:
		synMsg.mCharacterIdx = mTarget.index;
		break;
	}

	/// 외치기 설정에 따른 발송 범위 선택
	if( pMonsterTalk->mViewType == eM_TALKVIEW_SHOUT )
		NETWORK2->QuickSendMap( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_SPEECH) );
	else
		NETWORK2->QuickSend( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_SPEECH) );

	/// TALK_DELAY 초간 발송 제약 시간 설정
	mTalkDealyEndTime = NETWORK2->GetAccumTime() + TALK_DELAY;
}


void cMonster::SpeechProcess()
{
	if( mTalkProcessCheckTime > NETWORK2->GetAccumTime() )
		return;

	SendSpeech( mTalkState );
}


void cMonster::AddSummonMonster( unsigned long monsterIdx )
{ 
	if( mSummonListSet.Insert( monsterIdx ) == false )
		NETWORK2->PostServerEvent("cMonster[%d]::AddSummonMonster mSummonListSet.Insert( %d ) == false", mObject.index, monsterIdx );
}

void cMonster::DieSummonMonster( unsigned long monsterIdx )
{ 
	mSummonListSet.Erase( monsterIdx ); 
}

void cMonster::ModeSkillEnd()
{
	if( mModeSkillIdx != eMONSTERATTACK_MAX && mModeSkillIdx == mAttackType )
	{
		mModeSkillIdx = eMONSTERATTACK_MAX;
	}
}


bool cMonster::SendSightIn(char category, char protocol, unsigned long connectionIdx)
{
	HANDLE            handle  = NULL;
	MSG_MONSTER_INFO* sendMsg = (MSG_MONSTER_INFO*)NETWORK2->GetMsgRoot( &handle, connectionIdx );
	if ( sendMsg != NULL )
	{
		sMonsterData*      monsterData       = &sendMsg->mMonsterInfo;
		sMonsterInfluence* pMonsterInfluence = sendMsg->mInfluence;

		sendMsg->Category = category;
		sendMsg->Protocol = protocol;

		monsterData->mMonsterIdx      = mObject.index;
		monsterData->mMonsterClassIdx = mpMonsterInfo->mMonsterClassIdx;
		monsterData->mPosX            = mObjectPos.x;
		monsterData->mPosY            = mObjectPos.y;
		monsterData->mDirection       = mDirection;
		monsterData->mHP			  = mHP;
		monsterData->mMaxHP			  = GetMaxHP();
		monsterData->mMP			  = mMP;
		monsterData->mMaxMP			  = GetMaxMP();

		sendMsg->mCount = 0;

		cSkillInfluenceSet::cIterator iter = mInfluenceSet.Begin();
		cSkillInfluenceSet::cIterator end = mInfluenceSet.End();

		cInfluenceObject* pInfluence = NULL;

		while( iter != end )
		{
			pInfluence = SKILLMANAGER->GetInfluence( *iter++ );
			if( pInfluence == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cMonster::SendSightIn pInfluence == NULL" );
				//-/-//SKILLMANAGER->InsertDeleteInfluenceObject( pInfluence );
				continue;
			}

			pMonsterInfluence->mUniqueIdx         = pInfluence->GetUniqueIdx();
			pMonsterInfluence->mInfluenceClassIdx = pInfluence->GetInfluenceClassIdx();
			++pMonsterInfluence;
			++sendMsg->mCount;
		}

		if ( NETWORK2->SendMsgRoot( handle, sendMsg->GetMsgLength() ) )
		{
			//-- TO 캐릭터 FROM 그리드(그리드 이동정보).
			return SendMonsterMove( connectionIdx );
		}
		else
			return false;
	}
	return false;
}

bool cMonster::SendSightOut(char category, char protocol, unsigned long connectionIdx)
{
	HANDLE                handle  = NULL;
	MSG_MONSTER_SIGHTOUT* sendMsg = (MSG_MONSTER_SIGHTOUT*)NETWORK2->GetMsgRoot( &handle, connectionIdx );
	if ( sendMsg != NULL )
	{
		//unsigned long* pInfluenceIdx = sendMsg->mInfluenceUniqueIdx;

		sendMsg->Category    = category;
		sendMsg->Protocol    = protocol;
		sendMsg->mMonsterIdx = mObject.index;
		//sendMsg->mCount      = 0;

		//cSkillInfluenceSet::cIterator iter = mInfluenceSet.Begin();
		//cSkillInfluenceSet::cIterator end = mInfluenceSet.End();

		//cInfluenceObject* pInfluence = NULL;

		//while( iter != end )
		//{
		//	pInfluence = SKILLMANAGER->GetInfluence( *iter++ );

		//	if( pInfluence == NULL )
		//	{
		//		assert(NULL);
		//		NETWORK2->PostServerEvent("cMonster::SendSightOut pInfluence == NULL" );
		//		//-/-//SKILLMANAGER->InsertDeleteInfluenceObject( pInfluence );
		//		continue;
		//	}

		//	(*pInfluenceIdx) = pInfluence->GetUniqueIdx();
		//	++pInfluenceIdx;
		//	++sendMsg->mCount;
		//}

		return NETWORK2->SendMsgRoot( handle, sizeof( MSG_MONSTER_SIGHTOUT ) );
	}
	return false;
}

bool cMonster::SendMonsterMove(unsigned long connectionIdx)
{
	// 061122 PKH 죽지 않은 몬스터만 보낸다.
	if ( GetStateDie() == true )
		return true;

	// 길찾기 경로 개수
	unsigned int pathCount = GetPathCount();

	// 길찾기가 만들어져 있지 않은 경우 다음 몬스터 검색
	if ( pathCount == 0 )
		return true;

	// 이동 경로가 최종목적지 포인트까지 이동이면
	if ( GetMoveStopRange() == 0 )
	{
		HANDLE                handle  = NULL;
		MSG_SYN_MOVE_MONSTER* msg = (MSG_SYN_MOVE_MONSTER*)NETWORK2->GetMsgRoot( &handle, connectionIdx );

		msg->Category    = NM_MONSTER;
		msg->Protocol    = NM_MONSTER_MOVE_SYN;
		msg->mMonsterIdx = GetObjectID();

		for( unsigned int i = 0 ; i < pathCount ; ++i )
			msg->mPathArray[i] = GetPathArray()[i];

		msg->mMoveSpeed = GetMoveSpeed();
		msg->mMoveSpeedType = mMoveSpeedType;
		msg->mCount = pathCount;

		return NETWORK2->SendMsgRoot( handle, msg->GetMsgLength() );
	}
	else	// 이동 경로가 최종목적지 포인트전 RANGE범위까지 인경우
	{
		cBaseObject* player = GetTarget();
		if ( player != NULL )
		{
			sObject target;

			target.index = player->GetObjectID();
			target.type  = player->GetObjectType();

			HANDLE                      handle  = NULL;
			MSG_SYN_ACTIONMOVE_MONSTER* msg = (MSG_SYN_ACTIONMOVE_MONSTER*)NETWORK2->GetMsgRoot( &handle, connectionIdx );

			msg->Category    = NM_MONSTER;
			msg->Protocol    = NM_MONSTER_ACTIONMOVE_SYN;
			msg->mMonsterIdx = GetObjectID();
			msg->mTarget     = target;
			msg->mRange      = GetMoveStopRange();

			for( unsigned int i = 0 ; i < pathCount ; ++i )
				msg->mPathArray[i] = GetPathArray()[i];

			msg->mMoveSpeed = GetMoveSpeed();
			msg->mCount = pathCount;

			return NETWORK2->SendMsgRoot( handle, msg->GetMsgLength() );
		}
	}

	return false;
}


cPlayer* cMonster::NearGridFindPlayer( unsigned long playerIdx )
{
	cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( this );
	/// 각각의 플레이어를 검색함
	while( pPlayer != NULL )
	{
		if( pPlayer->GetObjectID() == playerIdx )
			return pPlayer;

		pPlayer = GRIDMANAGER->FindNextPlayer();
	}

	return NULL;
}


long cMonster::CalcSkillRate( cBaseObject* pTarget, eMONSTERATTACK_TYPE attackType, long scriptRate )
{
	if( pTarget == NULL )
		return 0;

	sMonsterSkillScript* pScript = SKILLSCRIPT->GetMonsterSkillInfo( GetRaceGender(), attackType );
	if( pScript == NULL )
		return 0;

	if( pScript->mAttackSpd == 0 )
		return 0;

	/// 선택 확률 계산
	/// ( (공격력(소) + 공격력(대) ) / 2 ) / ( 공격속도/1000 ) - (절대값)( (대상과의 거리 - 사거리) / 10 ) + 사용확률
	float averageAttack = (float)( pScript->mMinAttackValue + pScript->mMaxAttackValue) / 2;
	float speedAttack = averageAttack / ( (float)pScript->mAttackSpd / 1000 );
	float range = (mObjectPos - pTarget->GetPos()).Length();
	float rangeRate = ( range - pScript->mTargetDist ) / 10;
	float rangeAttack = speedAttack - abs(rangeRate);
	float useRate = rangeAttack + scriptRate;

	if( useRate < 0.0f )
		return 0;

	return (unsigned long)useRate;    
}


void cMonster::ChangeActionTarget()
{
	/// Take Damage List를 검사한다.
	unsigned long maxPoint = 0;
	cPlayer* newTarget = 0;
	PerTakeDamage* per = (PerTakeDamage*)mTakeDamageRoot.pool;
	while( per )
	{
		cPlayer* player = NearGridFindPlayer( per->playerIdx );
		if( player && player != GetTarget() && player->GetStateDie() == false )
		{
			if( maxPoint <= per->distressPoint )
			{
				maxPoint = per->distressPoint;
				newTarget = player;
			}
		}
		per = (PerTakeDamage*)per->next;
	}

	if( newTarget )
	{
		/// 타겟 설정
		if( SetTarget( newTarget->GetObjectType(), newTarget->GetObjectID() ) == false )
			return;

		/// 추적 상태 설정
		ActionChange( eACT_FOLLOW );
	}
	else
	{
		/// 회기 상태 설정
		ClearTakeDamage();
		Comeback();
	}
}


void cMonster::ChangeTargetDeleteSkill()
{
	if( mTakeDamageRoot.count == 1 )
		return;

	ChangeActionTarget();
}

void cMonster::SetCombatFlag( bool set )
{
	if( mIsCombat == set )
		return;

	mIsCombat = set;

	if( mIsCombat == true )
		CombatStart();
	else
		CombatEnd();
}

void cMonster::CombatStart()
{
	/// 전투 시작 시점
	mTotalTime = 0;

	ChangeCurrentMode( 0 );
}

void cMonster::CombatEnd()
{
	/// 전투 종료 시점
	mTotalTime = 0;

	ChangeCurrentMode( -1 );
}

void cMonster::ChangeCurrentMode( long modeIdx )
{
	if( mModeAgent == 0 )
		return;

	/// 모드 변수 처리
	if( modeIdx < 0 )
	{
		mCurrentMode = NULL;
	}
	else
	{
		/// 모드 변경전 기존 모드 설정 항목 제거
		if( mCurrentMode )
		{
			cModeInfoArr* arr = mCurrentMode->GetInitModeInfoArr();
			for( unsigned int i=0; i<arr->GetSize(); i++ )
			{
				sModeInfoBase* p = (sModeInfoBase*)((*arr)[i]);
				switch( p->type )
				{
					/// 버프 제거
				case eMODEINFO_APPLYBUFF:
					{
						sModeBuff* pBuffMode = (sModeBuff*)p;
						if( pBuffMode == NULL )
							return;

						if( pBuffMode->mModeDel == true && pBuffMode->mApplyBuffIdx != 0 )
							SKILLMANAGER->DeleteInfluenceClassIdx( mObject, pBuffMode->mApplyBuffIdx );
					}
				}
			}
		}


		cMode* mode = mModeAgent->GetMode( modeIdx );
		if( mCurrentMode == mode )
		{
			assert(0);
			return;
		}
		mCurrentMode = mode;
	}

	mModeTime = 0;
	mModeSkillIdx = eMONSTERATTACK_MAX;

	/// Net Msg처리
	MSG_SYN_MONSTER_MODECHANGE synMsg;
	synMsg.Category = NM_MONSTER;
	synMsg.Protocol = NM_MONSTER_MODECHANGE_SYN;
	synMsg.mMonsterIdx = mObject.index;
	synMsg.mModeIdx = modeIdx;
	NETWORK2->QuickSend( this, (char*)&synMsg, sizeof( synMsg ) );

	/// 모드 시작 설정 처리
	if( mCurrentMode )
	{
		cModeInfoArr* arr = mCurrentMode->GetInitModeInfoArr();
		for( unsigned int i=0; i<arr->GetSize(); i++ )
		{
			sModeInfoBase* p = (sModeInfoBase*)((*arr)[i]);
			switch( p->type )
			{
			case eMODEINFO_APPLYBUFF:
				{
					sModeBuff* pBuffMode = (sModeBuff*)p;
					if( pBuffMode == NULL )
						return;

					if( pBuffMode->mApplyBuffIdx != 0 )
						SKILLMANAGER->AddInfluence( mObject, mObject, pBuffMode->mApplyBuffIdx, 0, true ); 
				}
				break;
			case eMODEINFO_GENMONSTER:
				{
					sModeGenMonster* pMonMode = (sModeGenMonster*)p;
					if( pMonMode == NULL )
						return;

					/// 소환 몬스터 추가
					AIMANAGER->BossSummonMonRegen( this, mMapNumber, mObjectPos.x, mObjectPos.y, pMonMode->mGenRange, pMonMode->mCount, pMonMode->mGenMonsterIdx );

					/// 루프를 도는 것만 쿨타임 정보를 기록해서 몬스터process시 추가 리젠한다.
					if( pMonMode->mLoop == true )
					{
						unsigned long nextTime = NETWORK2->GetAccumTime() + pMonMode->mDelayTime;

						/// 등록된 정보를 찾아 추가/변경
						cHashMap::cIterator find = mGenMonCoolTimeMap.Find( modeIdx );
						if( find == mGenMonCoolTimeMap.End() )
							mGenMonCoolTimeMap.Insert( modeIdx, nextTime );							
						else
							(*find).mSecond = nextTime;
					}							
				}
				break;
			case eMODEINFO_GENTOTEM:
				{
					//bool			mLoop;
					//unsigned long	mDelayTime;

					sModeGenTotem* pTotemMode = (sModeGenTotem*)p;
					if( pTotemMode == NULL )
						return;

					for( unsigned char i = 0 ; i < pTotemMode->mCount ; ++i )
					{
						NiPoint2 pos;
						pos.x = mObjectPos.x;
						pos.y = mObjectPos.y;

						pos.x = pos.x + ( rand() % pTotemMode->mGenRange ) - ( pTotemMode->mGenRange / 2 );
						pos.y = pos.y + ( rand() % pTotemMode->mGenRange ) - ( pTotemMode->mGenRange / 2 );


						cTotem* pTotem = OBJECTMANAGER->AddTotem( pTotemMode->mGenTotemIdx, 0, pos, mObject, eAPPLYTYPE_ENEMY, eATTRIBUTETYPE_PHYSICAL );
						if( pTotem != NULL )
							GRIDMANAGER->AddTotem( pTotem );
					}

					/// 루프를 도는 것만 쿨타임 정보를 기록해서 몬스터process시 추가 리젠한다.
					if( pTotemMode->mLoop == true )
					{
						unsigned long nextTime = NETWORK2->GetAccumTime() + pTotemMode->mDelayTime;

						/// 등록된 정보를 찾아 추가/변경
						cHashMap::cIterator find = mGenTotemCoolTimeMap.Find( modeIdx );
						if( find == mGenTotemCoolTimeMap.End() )
							mGenTotemCoolTimeMap.Insert( modeIdx, nextTime );							
						else
							(*find).mSecond = nextTime;
					}	
				}
				break;
			case eMODEINFO_SPEECH:
				{
					sModeSpeech* pSpeechMode = (sModeSpeech*)p;
					if( pSpeechMode == NULL )
						return;

					/// 메세지 발송
					MSG_SYN_MOSTER_MODESPEECH synMsg;
					synMsg.Category = NM_MONSTER;
					synMsg.Protocol = NM_MONSTER_MODESPEECH_SYN;
					synMsg.mMonsterIdx = GetObjectID();
					synMsg.mTalkIdx	= pSpeechMode->mSpeechIdx;
					synMsg.mCharacterIdx = mTarget.index;

					NETWORK2->QuickSendMap( this, (char*)&synMsg, sizeof(MSG_SYN_MONSTER_SPEECH) );
				}
				break;
			case eMODEINFO_MODESTATE:
				{
					sModeState* pIdleMode = (sModeState*)p;
					if( pIdleMode == NULL )
						return;

					mIdleEndTime = NETWORK2->GetAccumTime() + pIdleMode->mMaintainTime;
					ActionChange( eACT_IDLE );
				}
				break;
			case eMODEINFO_AGGRO:
				{
					/// 자신의 어그로 초기화
					ClearDistressPoint();
				}
				break;
			case eMODEINFO_MODEINITSKILL:
				{
					/// 모드 진입시 조건가능하면 무조건 선정
					cHashMap successSkill;
					unsigned long sumRate = 0;

					cBaseObject* pTarget = GetTarget();
					if( pTarget == NULL )
						break;

					/// 선택 가능 스킬목록 작성
					cModeInfoArr* skillArr = mCurrentMode->GetCheckSkillModeInfoArr();
					for( unsigned int i=0; i<skillArr->GetSize(); i++ )
					{
						sModeInfoBase* p = (sModeInfoBase*)((*skillArr)[i]);
						if( p->type != eMODEINFO_USESKILL )
						{
							/// error
							assert(0);
							continue;
						}

						sModeSkill* pInfo = (sModeSkill*)p;

						bool useSkill = true;
						for( unsigned int i=0; i<2; i++ )
						{
							if( ConditionValueCheck( pInfo->mCondition[i].conType, pInfo->mCondition[i].conValue ) == false )
							{
								useSkill = false;
								break;
							}
						}

						if( useSkill == false )
							continue;

						/// 쿨타임 체크
						cCoolTime::cIterator begin = mEliteCoolTimeMap.Find( pInfo->mSkillIdx );
						cCoolTime::cIterator end = mEliteCoolTimeMap.End();

						if( begin != end )
						{
							if( (*begin).mSecond > NETWORK2->GetAccumTime() )
								continue;
						}

						/// 선택 비율값 계산
						long useRate = CalcSkillRate( pTarget, (eMONSTERATTACK_TYPE)pInfo->mSkillIdx, (long)pInfo->mActionPer );

						/// 가능한 스킬 목록에 저장
						successSkill.Insert( pInfo->mSkillIdx, useRate );
						sumRate = sumRate + useRate;
					}

					/// 가능한 스킬이 없으면 통과
                    if( sumRate == 0 )
						break;

					/// 가능한 스킬중 선택
					unsigned long selectRand = rand() % sumRate;
					cHashMap::cIterator sI = successSkill.Begin();                                      
					cHashMap::cIterator sE = successSkill.End();
					for( ; sI != sE ; ++sI )
					{
                        unsigned long rate = (*sI).mSecond;
						if( selectRand < rate )
						{
							mModeSkillIdx = (eMONSTERATTACK_TYPE)(*sI).mFirst;
							break;
						}
                        
						selectRand = selectRand - rate;
					}
				}
				break;				
			default:
				assert(0);
				break;
			}
		}
	}
}

bool cMonster::ConditionValueCheck( unsigned char type, unsigned long value )
{
	switch( type )
	{
	case 0: 
		return true;
	case 1: // total time
		{
			if( value <= mTotalTime )
				return true;
		}
		break;
	case 2: // mode time
		{
			if( value <= mModeTime )
				return true;
		}
		break;
	case 3: // monster hp
		{
			unsigned long per = GetHP() * 100 / GetMaxHP();
			if( value >= per )
				return true;
		}
		break;
	default:
		assert(0);
		break;
	}
	return false;
}
