#include "stdafx.h"
#include "BaseSkillObject.h"
#include "Player_Common.h"
#include "ObjectManager.h"
#include "SkillManager.h"
#include "BaseObject.h"
#include "DamageCalc.h"
#include "Protocol.h"
#include "Player.h"
#include "Monster.h"
#include "gamesrv.h"
#include "Drop.h"



cBaseSkillObject::cBaseSkillObject()
{
	mUniqueIdx = 0;

	mAttacker.index = 0;
	mAttacker.index = eOBJECTTYPE_NONE;

	mCenterTarget.index = 0;
	mCenterTarget.index = eOBJECTTYPE_NONE;

	mIsProjectile = false;
	mActivityTime = 0;
	mSpeedFactor = 1.0f;
	mNoTargetNoDamage = false;
	mMapNumber = 0;
	mDetailPos = 0;
	mDetailEndTime = 0;
	mIsFieldTarget = false;
	mIsChgMonsSkill = false;
	mAttackerDie = 0;
	mCreateType = eSKILLCREATETYPE_NORMAL;
	mUseItemIdx = 0;
	mDistressPoint = 0;
	mDelTarget = false;
}


cBaseSkillObject::~cBaseSkillObject()
{
	SKILLMANAGER->DelSkillUniqueIdx( mUniqueIdx );
}



void cBaseSkillObject::Process( unsigned long elapsedTime, unsigned long accumTime )
{

	/// 공격자가 없는 경우 this오브젝트를 삭제 리스트에 넣음
	cBaseObject* pAttacker = OBJECTMANAGER->GetObject( mAttacker.type, mAttacker.index );
	if( pAttacker == NULL || mMapNumber != pAttacker->GetMapNumber() )
	{
		AttackerNullDamage();

		/// 소멸 프로세스 수행
		mSkillState = eSKILLPROCESS_DESTRUCTION;
	}


	/// 캐스팅
	if( mSkillState == eSKILLPROCESS_CASTING )
	{
		/// 캐스팅시 공격자 / 타겟 객체 체크
		if( CastingObjectCheck( pAttacker ) == false )
			mSkillState = eSKILLPROCESS_DESTRUCTION;

		/// 캐스팅 끝날때까지 루프
		/// ※ 위에서 죽었다라고 판정이나도 실제 발동 시간을 체크해서 발동 가능시간이면 발동
		if( mCastingTime <= accumTime - mCreationTime )	
		{
			/// 소모 항목 적용
			if( RequireValue( pAttacker ) == true )
				mSkillState = eSKILLPROCESS_ACTIVITY;		/// 발동
			else
				mSkillState = eSKILLPROCESS_DESTRUCTION;	/// 소멸
		}
	}


	/// 발동
	if( mSkillState == eSKILLPROCESS_ACTIVITY )
	{
		if( pAttacker->GetStateDie() == false )
		{
			/// 발동 끝날때까지 루프
			if( mActivityTime > ( (accumTime - ( mCreationTime + mCastingTime )) * mSpeedFactor  ) )
				return; 

			/// 발사체 유무 검사
			if( mIsProjectile == true )
				mSkillState = eSKILLPROCESS_PROJECTILE;		/// 발사체
			else
			{
				if( mCreateType == eSKILLCREATETYPE_NORMAL )
					SetCenterPos();

				mSkillState = eSKILLPROCESS_DAMAGE;			/// 데미지
			}
		}
		else
		{
			mSkillState = eSKILLPROCESS_DAMAGE;

			if( mActivityTime * 0.7 >
				( (accumTime - ( mCreationTime + mCastingTime )) * mSpeedFactor  ) )
				mAttackerDie = 1;
			else
				mAttackerDie = 2;
		}
	}


	/// 발사체 이동
	if( mSkillState == eSKILLPROCESS_PROJECTILE )
	{
		/// 발사체 목적지 설정
		///※ 발사체가 도달하기까지 목적지 좌표가 틀려질수 있으므로 목적지는 계속 체크
		if(	SetCenterPos() == false )
		{
			/// 타겟이 없으면 0데미지 발송
			mNoTargetNoDamage = true;
			mSkillState = eSKILLPROCESS_DAMAGE;	
		}
		else
		{
			/// 발사체 이동
			if( IsProjectileEndProcess( elapsedTime ) == true )
				mSkillState = eSKILLPROCESS_DAMAGE;			/// 데미지
		}
	}


	/// 데미지
	if( mSkillState == eSKILLPROCESS_DAMAGE )
	{
		/// 타겟 선정
		if( mCreateType == eSKILLCREATETYPE_NORMAL )
			TargetSelect( pAttacker );

		/// 데미지 적용
		ApplyDamage( pAttacker );

		mSkillState = eSKILLPROCESS_HP;		/// 소멸
	}


	/// HP적용
	if( mSkillState == eSKILLPROCESS_HP )
	{
		/// 시간 체크
		if( mDetailEndTime > accumTime )
			return;

		/// 더 적용 할것이 없으면 소멸
		if( ApplyHP( pAttacker ) == false )
			mSkillState = eSKILLPROCESS_DESTRUCTION;		/// 소멸
	}


	/// 소멸
	if( mSkillState == eSKILLPROCESS_DESTRUCTION )
	{
		SKILLMANAGER->InsertDeleteSkillObject( mAttacker, mUniqueIdx );
		return;
	}

}



bool cBaseSkillObject::IsProjectileEndProcess( unsigned long elapsedTime )
{
	float moveElapsedTime = (float)elapsedTime / SECOND_THOUSAND;

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

	/// 속도 * 경과시간 만큼의 스킬 효과위치 이동
	NiPoint2 estimatePos;
	estimatePos.x = mProjectilePos.x + (mProjectileSpeed*moveElapsedTime*dir1.x);
	estimatePos.y = mProjectilePos.y + (mProjectileSpeed*moveElapsedTime*dir1.y);

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

	/// 예상 이동위치가 목적지 좌표를 넘어가면 목적지 도달할때까지 이동
	if( dir1.Dot(dir2) > 0 )
	{
		mProjectilePos = estimatePos;

		/// 프로세스 계속 수행
		return false;
	}

	return true;
}


void cBaseSkillObject::AttackerNullDamage()
{

	cPlayer* pPlayer = NULL;

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

	/// 적용 메세지
	applyMsg = (MSG_SYN_SKILL_APPLY*)perIoContext->buffer;

	applyMsg->Category     = NM_SKILL;
	applyMsg->Protocol     = NM_SKILL_APPLY_SYN;
	applyMsg->mSkillIndex  = mUniqueIdx;
	applyMsg->mAttackerType = mAttacker.type;
	applyMsg->mSkillClassIndex = mSkillIdx;
	applyMsg->mDramaKey = mDramaKey;
	applyMsg->mIsChangeMonster = mIsChgMonsSkill;

	/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
	cBaseObject* pTarget = NULL;
	sObject target;
	unsigned long targetCnt = 0;
	for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
	{
		/// 타겟 객체 확인
		target = mTargetAry[i].mTarget;

		switch( target.type )
		{
		case eOBJECTTYPE_MONSTER:
			pTarget = GRIDMANAGER->GetMonster( target.index );
			break;
		case eOBJECTTYPE_PLAYER:	
			pTarget = GRIDMANAGER->GetPlayer( target.index ); 
			break;
		default:
			pTarget = NULL;
		}

		if( pTarget == NULL ) 
			continue;

		applyMsg->mApplyObject[targetCnt].mTarget = target;
		++targetCnt;

		/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
		pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
		while( pPlayer != NULL )
		{
			mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
			pPlayer = GRIDMANAGER->FindNextPlayer( true );
		}
	}

	applyMsg->mApplyCount = (unsigned char)targetCnt;

	cHashSet::cIterator begin = mSendTargetSet.Begin();
	cHashSet::cIterator end = mSendTargetSet.End();
	unsigned long connectionIdx;

	while( begin != end )
	{
		connectionIdx = *begin++;
		NETWORK2->SendToUser( connectionIdx, (char*)applyMsg, applyMsg->GetMsgLength() );
	}

	perIoContext->offset = applyMsg->GetMsgLength();

	char text[255];
	sprintf( text,
		"cBaseSkillObject::AttackerNullDamage:MSG_SYN_SKILL_APPLY(%d %d %d %d %d %d %d %d %d)",
		mAttacker.type,
		mAttacker.index,
		applyMsg->GetMsgLength(),
		applyMsg->mSkillIndex,
		applyMsg->mAttackerType,
		applyMsg->mSkillClassIndex,
		applyMsg->mDramaKey,
		applyMsg->mIsChangeMonster,
		applyMsg->mApplyCount );

	NETWORK2->ReleaseIoContext( perIoContext, text );
}


void cBaseSkillObject::SendDamage( cBaseObject* pAttacker, 
								  unsigned long shotType, eATTRIBUTETYPE attribType, eRANGETYPE rangeType,
								  unsigned short applyType1, unsigned short applyType2, unsigned long applyValue1, unsigned long applyValue2,
								  unsigned long influenceClassIdx, unsigned short accuracyValue, unsigned short criticalValue, bool applyHP )
{
	/// 공격자 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
	cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( pAttacker, true );
	while( pPlayer != NULL )
	{
		mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
		pPlayer = GRIDMANAGER->FindNextPlayer( true );
	}

	cBaseObject* pTarget = NULL;

	eDAMAGE_KIND damageKind = eDAMAGE_NONE;
	bool allDamageKindMiss = false;

	/// 풀에서 메모리 할당
	PerIoContext*         perIoContext = NETWORK2->GetIoContext( IOCP_REQUEST_WRITE );
	MSG_SYN_SKILL_DAMAGE* damageMsg = NULL;
	sTargetDamage*        targetDamage = NULL;

	/// 데미지 메세지
	damageMsg = (MSG_SYN_SKILL_DAMAGE*)perIoContext->buffer;

	damageMsg->Category     = NM_SKILL;
	if( applyHP == true )
		damageMsg->Protocol = NM_SKILL_DAMAGE_SYN;
	else
		damageMsg->Protocol = NM_SKILL_DAMAGEMP_SYN;
	damageMsg->mSkillIndex  = mUniqueIdx;
	damageMsg->mAttackerType = mAttacker.type;
	damageMsg->mSkillClassIndex = mSkillIdx;
	damageMsg->mDramaKey = mDramaKey;
	damageMsg->mIsChangeMonster = mIsChgMonsSkill;
	damageMsg->mTargetCount = 0;
	damageMsg->mAttackerDie = mAttackerDie == 1;
	damageMsg->SetMsgLength( sizeof(MSG_SYN_SKILL_DAMAGE) - sizeof(sTargetDamage) );
	targetDamage = damageMsg->mTargetDamage;


	/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
	sObject target;
	unsigned long targetCnt = 0;
	unsigned long damage = 0;			/// 계산된 데미지
	long attkAbsorbHPPer = pAttacker->GetStateOddity( eODDITYTYPE_HP_ABSORB );	/// 공격자 HP흡수 비율
	long attkAbsorbMPPer = pAttacker->GetStateOddity( eODDITYTYPE_MP_ABSORB ); /// 공격자 MP흡수 비율
	long attkAbsorbHP = 0;		/// 공격자 HP흡수
	long attkAbsorbMP = 0;		/// 공격자 MP흡수
	long absorbPer = 0;		/// 타겟이 데미지 흡수 비율
	long reflectionPer = 0;	/// 타겟이 데미지 반사 비율
	long reflection = 0;		/// 타겟이 데미지 반사
	bool noDamage = false;	/// 무적

	if( mAttackerDie != 1 )
	{
		for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
		{
			/// 타겟 객체 확인
			target = mTargetAry[i].mTarget;

			switch( target.type )
			{
			case eOBJECTTYPE_MONSTER:
				pTarget = GRIDMANAGER->GetMonster( target.index );
				break;
			case eOBJECTTYPE_PLAYER:	
				pTarget = GRIDMANAGER->GetPlayer( target.index ); 
				break;
			default:
				pTarget = NULL;
			}

			if( pTarget == NULL ) 
				continue;

			pTarget->AddTakeDamage( mAttacker, 0, 0, eTAKEDAMAGETYPE_DAMAGE );

			targetDamage->mTarget = target;

			/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
			pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
			while( pPlayer != NULL )
			{
				mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
				pPlayer = GRIDMANAGER->FindNextPlayer( true );
			}

			/// 스킬에서는 효과번호를 순간 데미지 효과에 관련된 항목만 사용한다(패시브 제외-패시브는 여기안들어옴)
			if( !( applyType1 == 0 || applyType1 / eSTATUSPLUS_INSTANTPERHIT == 1 ) )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("player AddInfluence - ApplyValue1 EROR");
			}
			if( !( applyType2 == 0 || applyType2 / eSTATUSPLUS_INSTANTPERHIT == 1 ) )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("player AddInfluence - ApplyValue2 EROR");
			}

			/// 순간 데미지 효과 계산
			pAttacker->CalcInstantSkill( (eSTATUS_PLUS)applyType1, applyValue1,	(eSTATUS_PLUS)applyType2, applyValue2 );


			/// 데미지 산출 
			damage = pAttacker->DamageCalc( target, mSkillIdx, attribType, rangeType );

			if( attribType == eATTRIBUTETYPE_MAGIC )
			{
				/// 마법 데미지 흡수
				absorbPer = pTarget->GetStateOddity( eODDITYTYPE_DAMAGE_ABSORB_MAG );
				/// 마법 무적
				if( pTarget->GetStateOddity( eODDITYTYPE_INVINCIBILITY_MAG ) != 0 )
					noDamage = true;
				/// 마법 데미지 반사
				reflectionPer = pTarget->GetStateOddity( eODDITYTYPE_DAMAGE_REFLECTION_MAG );
			}
			else
			{
				/// 물리 데미지 흡수
				absorbPer = pTarget->GetStateOddity( eODDITYTYPE_DAMAGE_ABSORB_PHY );
				/// 물리 무적
				if( pTarget->GetStateOddity( eODDITYTYPE_INVINCIBILITY_PHY ) != 0 )
					noDamage = true;
				/// 물리 데미지 반사
				reflectionPer = pTarget->GetStateOddity( eODDITYTYPE_DAMAGE_REFLECTION_PHY );
			}

			/// 데미지 흡수 계산
			if( absorbPer != 0 )
			{
				if( absorbPer < -100 )	absorbPer = -100;
				if( absorbPer > 100 )	absorbPer = 100;
				damage = FloatToInt( damage * ( 100 - absorbPer ) * 0.01f );
			}

			/// 무적 처리
			if( noDamage == true )
				damage = 0;

			/// 데미지 반사
			if( reflectionPer != 0 )
			{
				if( reflectionPer < -100 )	reflectionPer = -100;
				if( reflectionPer > 100 )	reflectionPer = 100;
				if( reflectionPer > 0 )
					reflection = FloatToInt(damage * reflectionPer * 0.01f);
				else
					reflection = -FloatToInt((damage * abs(reflectionPer) * 0.01f));
				damage = damage - reflection;
			}

			/// 공격자 데미지 hp흡수
			if( attkAbsorbHPPer != 0 )
			{
				if( attkAbsorbHPPer < -100 ) attkAbsorbHPPer = -100;
				if( attkAbsorbHPPer > 100 )	attkAbsorbHPPer = 100;
				attkAbsorbHP = FloatToInt( ( damage * abs(attkAbsorbHPPer) ) * 0.01f );
				if( attkAbsorbHPPer < 0 )
					attkAbsorbHP = attkAbsorbHP * -1;
			}

			/// 공격자 데미지 mp흡수
			if( attkAbsorbMPPer != 0 )
			{
				if( attkAbsorbMPPer < -100 ) attkAbsorbMPPer = -100;
				if( attkAbsorbMPPer > 100 )	attkAbsorbMPPer = 100;
				attkAbsorbMP = FloatToInt( ( damage * abs(attkAbsorbMPPer) ) * 0.01f );
				if( attkAbsorbMPPer < 0 )
					attkAbsorbMP = attkAbsorbMP * -1;
			}

			/// 대미지 분할 계산
			bool success = false;
			bool critical = false;
			damageKind = eDAMAGE_MISS;
			long divideDamage = 0;
			long divideReflection = 0;
			long divideAttkAbsorbHP = 0;
			long divideAttkAbsorbMP = 0;
			sDramaAction* damageThis = mpDetailBegin;
			if( mDetailCnt != 0 )
				allDamageKindMiss = true;
			for( unsigned long damageI = 0 ; damageI < mDetailCnt ; ++damageI )
			{
				if( pTarget->GetStateDie() != true )
				{
					/// 공격성공시 대미지 분할 계산
					success = pAttacker->AttackSuccess( target, mSkillIdx, attribType, accuracyValue );
					if( success == true && mAttackerDie != 2 )
					{
						damageKind = eDAMAGE_NORMAL;
						divideDamage = FloatToInt( damage * damageThis->percent ); 
						divideReflection = FloatToInt( reflection * damageThis->percent ); 
						divideAttkAbsorbHP = FloatToInt( attkAbsorbHP * damageThis->percent ); 
						divideAttkAbsorbMP = FloatToInt( attkAbsorbMP * damageThis->percent );
						allDamageKindMiss = false;

						/// 크리티컬시 대미지 증가
						critical = pAttacker->CriticalSuccess( target, mSkillIdx, attribType, criticalValue );
						if( critical == true )
						{
							damageKind = eDAMAGE_CRITICAL;
							divideDamage = FloatToInt(divideDamage * CRITICAL_RATE);
							divideReflection = FloatToInt(divideReflection * CRITICAL_RATE);
							divideAttkAbsorbHP = FloatToInt(divideAttkAbsorbHP * CRITICAL_RATE);
							divideAttkAbsorbMP = FloatToInt(divideAttkAbsorbMP * CRITICAL_RATE);
						}

						/// 총데미지가 1이라도 분할데미지 최소값은 1 - 무적은 0
						if( divideDamage < 1 )
						{
							if( attribType == eATTRIBUTETYPE_PHYSICAL && pTarget->GetStateOddity( eODDITYTYPE_INVINCIBILITY_PHY ) != 0 )
								divideDamage = 0;
							else if( attribType == eATTRIBUTETYPE_MAGIC && pTarget->GetStateOddity( eODDITYTYPE_INVINCIBILITY_MAG ) != 0 )
								divideDamage = 0;
							else
								divideDamage = 1;
						}							
					}
				}

				damageThis = (sDramaAction*)damageThis->pNext;

				targetDamage->mDetail[damageI].mDamage = divideDamage;
				targetDamage->mDetail[damageI].mDamageKind = (unsigned char)damageKind;
				targetDamage->mDetail[damageI].mDamageReflection = divideReflection;
				targetDamage->mDetail[damageI].mDamageAbsorbHP = divideAttkAbsorbHP;
				targetDamage->mDetail[damageI].mDamageAbsorbMP = divideAttkAbsorbMP;

				//! 검사용 코드 테스트후 삭제.
				if ( !(damageI < 10) )
					NETWORK2->PostServerEvent( "cBaseSkillObject::SendDamage:mDetailCnt(=%d), damageI(=%d).", mDetailCnt, damageI );

				mTargetAry[i].mDetailDamage[damageI] = divideDamage;
				mTargetAry[i].mDetailDamageKind[damageI] = (unsigned char)damageKind;
				mTargetAry[i].mDetailReflection[damageI] = divideReflection;
				mTargetAry[i].mDetailAttkAbsorbHP[damageI] = divideAttkAbsorbHP;
				mTargetAry[i].mDetailAttkAbsorbMP[damageI] = divideAttkAbsorbMP;
			}

			mTargetAry[targetCnt].mDetailCnt = damageI;
			targetDamage->mDetailCnt = damageI;

			/// 효과 생성조건 만족 확인
			if( influenceClassIdx != 0 && shotType != eSHOTTYPE_AURA && allDamageKindMiss == false )	
			{ 
				/// 효과 생성
				if( SKILLMANAGER->AddInfluence( mAttacker, target, influenceClassIdx, mSkillIdx, true ) == false )
					targetDamage->mInfluenceMiss = true;
			}

			damageMsg->SetMsgLength( damageMsg->GetMsgLength() + (unsigned short)targetDamage->GetLength( ) );
			targetDamage = (sTargetDamage*)((char*)targetDamage + (unsigned short)targetDamage->GetLength( ));

			++targetCnt;
		}
	}

	damageMsg->mTargetCount = (unsigned char)targetCnt;

	/// 메세지 발송명단 플레이어들에게 발송
	cHashSet::cIterator begin = mSendTargetSet.Begin();
	cHashSet::cIterator end = mSendTargetSet.End();
	unsigned long connectionIdx;

	while( begin != end )
	{
		connectionIdx = *begin++;
		NETWORK2->SendToUser( connectionIdx, (char*)damageMsg, damageMsg->GetMsgLength() );
	}

	perIoContext->offset = damageMsg->GetMsgLength();

	char text[255];
	sprintf( text,
		"cBaseSkillObject::SendDamage:MSG_SYN_SKILL_DAMAGE(%d %d %d %d %d %d %d %d %d)",
		mAttacker.type,
		mAttacker.index,
		damageMsg->mMsgSize,
		damageMsg->mSkillIndex,
		damageMsg->mAttackerType,
		damageMsg->mSkillClassIndex,
		damageMsg->mDramaKey,
		damageMsg->mIsChangeMonster,
		damageMsg->mTargetCount );

	NETWORK2->ReleaseIoContext( perIoContext, text );

	/// 분할 대미지 적용 시작 시점 계산
	mDetailStartTime = NETWORK2->GetAccumTime();
	if( mpDetailBegin != NULL )
		mDetailEndTime = mDetailStartTime + mpDetailBegin->time;
}


void cBaseSkillObject::SendHeal( cBaseObject* pAttacker, unsigned long shotType,
								unsigned short applyType1, unsigned short applyType2, unsigned long applyValue1, unsigned long applyValue2,
								unsigned long influenceClassIdx )
{

	/// 공격자 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
	cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( pAttacker, true );
	while( pPlayer != NULL )
	{
		mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
		pPlayer = GRIDMANAGER->FindNextPlayer( true );
	}

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

	/// 힐 메세지
	healMsg = (MSG_SYN_SKILL_HEAL*)perIoContext->buffer;

	healMsg->Category     = NM_SKILL;
	healMsg->Protocol     = NM_SKILL_HEAL_SYN;
	healMsg->mSkillIndex  = mUniqueIdx;
	healMsg->mAttackerType = mAttacker.type;
	healMsg->mSkillClassIndex = mSkillIdx;
	healMsg->mDramaKey = mDramaKey;
	healMsg->mAttackerDie = mAttackerDie == 1;
	healMsg->mIsChangeMonster = mIsChgMonsSkill;
	healMsg->mTargetCount = 0;

	/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
	cBaseObject* pTarget = NULL;
	sObject target;
	unsigned long heal = 0;
	unsigned long targetCnt = 0;
	if( mAttackerDie != 1 )
	{
		for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
		{
			/// 타겟 객체 확인
			target = mTargetAry[i].mTarget;

			switch( target.type )
			{
			case eOBJECTTYPE_MONSTER:
				pTarget = GRIDMANAGER->GetMonster( target.index );
				break;
			case eOBJECTTYPE_PLAYER:	
				pTarget = GRIDMANAGER->GetPlayer( target.index ); 
				break;
			default:
				pTarget = NULL;
			}

			if( pTarget == NULL ) 
				continue;

			//if ( target.type == eOBJECTTYPE_MONSTER )
			//	((cMonster*)pTarget)->AddTakeDamage( mAttacker.index, 0, 0, eTAKEDAMAGETYPE_HEAL );

			/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
			pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
			while( pPlayer != NULL )
			{
				mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
				pPlayer = GRIDMANAGER->FindNextPlayer( true );
			}

			if( applyType1 != eSTATUSPLUS_INSTANTPLUSFINAL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("eSKILLTYPE_HEAL ApplyValue1[%d] error", applyType1 );
				continue;
			}

			if( applyType2 != 0 )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("eSKILLTYPE_HEAL ApplyValue2[%d] error", applyType2 );
				continue;
			}

			/// 순간 힐 효과 계산
			pAttacker->CalcInstantSkill( (eSTATUS_PLUS)applyType1, applyValue1,	(eSTATUS_PLUS)applyType2, applyValue2 );

			if( mAttackerDie != 2 )
			{
				/// 힐 계산
				heal = DAMAGECALC->HealHPCalc( mAttacker, applyValue1 );
				pTarget->HPHeal( heal, false, mAttacker.index, mDistressPoint, eTAKEDAMAGETYPE_HEAL );

				/// 효과 생성조건 만족 확인
				if( influenceClassIdx != 0 && shotType != eSHOTTYPE_AURA )	
				{ 
					NiPoint2 pos( mCenterPos.x, mCenterPos.y );
					/// 효과 생성
					if( SKILLMANAGER->AddInfluence( mAttacker, target, influenceClassIdx, mSkillIdx, true ) == false )
						healMsg->mTargetHeal[targetCnt].mInfluenceMiss = true;
				}
			}

			healMsg->mTargetHeal[targetCnt].mTarget = target;
			healMsg->mTargetHeal[targetCnt].mHeal = heal;
			healMsg->mTargetHeal[targetCnt].mObjectHP = pTarget->GetHP();
			healMsg->mTargetHeal[targetCnt].mObjectMaxHP = pTarget->GetMaxHP();

			++targetCnt;
		}
	}

	healMsg->mTargetCount = (unsigned char)targetCnt;

	/// 메세지 발송명단 플레이어들에게 발송
	cHashSet::cIterator begin = mSendTargetSet.Begin();
	cHashSet::cIterator end = mSendTargetSet.End();
	unsigned long connectionIdx;

	while( begin != end )
	{
		connectionIdx = *begin++;
		NETWORK2->SendToUser( connectionIdx, (char*)healMsg, healMsg->GetMsgLength() );
	}

	perIoContext->offset = healMsg->GetMsgLength();

	char text[255];
	sprintf( text,
		"cBaseSkillObject::SendHeal:MSG_SYN_SKILL_HEAL(%d %d %d %d %d %d %d %d %d)",
		mAttacker.type,
		mAttacker.index,
		healMsg->GetMsgLength(),
		healMsg->mSkillIndex,
		healMsg->mAttackerType,
		healMsg->mSkillClassIndex,
		healMsg->mDramaKey,
		healMsg->mIsChangeMonster,
		healMsg->mTargetCount );

	NETWORK2->ReleaseIoContext( perIoContext, text );

	mDetailStartTime = NETWORK2->GetAccumTime();
	if( mpDetailBegin != NULL )
		mDetailEndTime = mDetailStartTime + mpDetailBegin->time;
}


void cBaseSkillObject::SendApply( cBaseObject* pAttacker, unsigned long shotType, 
								 unsigned short applyType1, unsigned short applyType2, 
								 unsigned long applyValue1, unsigned long applyValue2, unsigned long influenceClassIdx )
{
	/// 공격자 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
	cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( pAttacker, true );
	while( pPlayer != NULL )
	{
		mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
		pPlayer = GRIDMANAGER->FindNextPlayer( true );
	}

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

	/// 적용 메세지
	applyMsg = (MSG_SYN_SKILL_APPLY*)perIoContext->buffer;

	applyMsg->Category     = NM_SKILL;
	applyMsg->Protocol     = NM_SKILL_APPLY_SYN;
	applyMsg->mSkillIndex  = mUniqueIdx;
	applyMsg->mAttackerType = mAttacker.type;
	applyMsg->mSkillClassIndex = mSkillIdx;
	applyMsg->mDramaKey = mDramaKey;
	applyMsg->mAttackerDie = mAttackerDie == 1;
	applyMsg->mIsChangeMonster = mIsChgMonsSkill;

	/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
	cBaseObject* pTarget = NULL;
	sObject target;
	unsigned long targetCnt = 0;
	if( mAttackerDie != 1 )
	{
		for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
		{
			/// 타겟 객체 확인
			target = mTargetAry[i].mTarget;

			switch( target.type )
			{
			case eOBJECTTYPE_MONSTER:
				pTarget = GRIDMANAGER->GetMonster( target.index );
				break;
			case eOBJECTTYPE_PLAYER:	
				pTarget = GRIDMANAGER->GetPlayer( target.index ); 
				break;
			default:
				pTarget = NULL;
			}

			if( pTarget == NULL ) 
				continue;

			pTarget->AddTakeDamage( mAttacker, 0, mDistressPoint, eTAKEDAMAGETYPE_DAMAGE );

			applyMsg->mApplyObject[targetCnt].mTarget = target;

			/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
			pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
			while( pPlayer != NULL )
			{
				mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
				pPlayer = GRIDMANAGER->FindNextPlayer( true );
			}

			if( mAttackerDie != 2 )
			{
				switch( mCreateType )
				{
				case eSKILLCREATETYPE_NORMAL:
					{
						if( influenceClassIdx != 0 )
						{
							/// 오라 처리
							if( target.index == mAttacker.index && target.type == mAttacker.type && shotType == eSHOTTYPE_AURA )	
							{ 
								if( SKILLMANAGER->AddInfSwitch( mAttacker, target, influenceClassIdx, mSkillIdx, true ) == 0 )
									applyMsg->mApplyObject[targetCnt].mInfluenceMiss = true;
							}

							/// 오라외 효과 처리
							if( shotType != eSHOTTYPE_AURA )	
							{ 
								/// 효과 생성
								if( SKILLMANAGER->AddInfluence( mAttacker, target, influenceClassIdx, mSkillIdx, true ) == false )
									applyMsg->mApplyObject[targetCnt].mInfluenceMiss = true;
							}
						}
					}
					break;
				case eSKILLCREATETYPE_MAPCHANGE:
					{
						if( applyType1 == eSTATUSPLUS_MAPCHANGE && target.type == eOBJECTTYPE_PLAYER )
						{
							if( ((cPlayer*)pTarget)->ItemMapChange( applyValue1 ) == false )
							{
								NETWORK2->PostServerEvent("SendMapChange eSTATUSPLUS_MAPCHANGE MapChange[%d,%d,%d,%d] == false ", 
									target.index, ((cPlayer*)pTarget)->GetState(), ((cPlayer*)pTarget)->GetRequestRejection(), applyValue1 );
							}
						}

						if( applyType2 == eSTATUSPLUS_MAPCHANGE && target.type == eOBJECTTYPE_PLAYER )
						{
							if( ((cPlayer*)pTarget)->ItemMapChange( applyValue2 ) == false )
							{
								NETWORK2->PostServerEvent("SendMapChange eSTATUSPLUS_MAPCHANGE MapChange[%d,%d,%d,%d] == false ", 
									target.index, ((cPlayer*)pTarget)->GetState(), ((cPlayer*)pTarget)->GetRequestRejection(), applyType2 );
							}
						}
					}
					break;
				case eSKILLCREATETYPE_VEHICLE:
					{
						if( influenceClassIdx != 0 && target.index == mAttacker.index && target.type == mAttacker.type )
						{ 
							if( SKILLMANAGER->AddInfSwitch( mAttacker, target, influenceClassIdx, mSkillIdx, true ) == 0 )
								applyMsg->mApplyObject[targetCnt].mInfluenceMiss = true;
						}
					}
					break;
				}

			}

			++targetCnt;
		}
	}

	applyMsg->mApplyCount = (unsigned char)targetCnt;

	cHashSet::cIterator begin = mSendTargetSet.Begin();
	cHashSet::cIterator end = mSendTargetSet.End();
	unsigned long connectionIdx;

	while( begin != end )
	{
		connectionIdx = *begin++;
		NETWORK2->SendToUser( connectionIdx, (char*)applyMsg, applyMsg->GetMsgLength() );
	}

	perIoContext->offset = applyMsg->GetMsgLength();

	char text[255];
	sprintf( text,
		"cBaseSkillObject::SendApply:MSG_SYN_SKILL_APPLY(%d %d %d %d %d %d %d %d %d)",
		mAttacker.type,
		mAttacker.index,
		applyMsg->GetMsgLength(),
		applyMsg->mSkillIndex,
		applyMsg->mAttackerType,
		applyMsg->mSkillClassIndex,
		applyMsg->mDramaKey,
		applyMsg->mIsChangeMonster,
		applyMsg->mApplyCount );

	NETWORK2->ReleaseIoContext( perIoContext, text );

	mDetailStartTime = NETWORK2->GetAccumTime();
	if( mpDetailBegin != NULL )
		mDetailEndTime = mDetailStartTime + mpDetailBegin->time;
}

void cBaseSkillObject::SendDelTarget( cBaseObject* pAttacker, unsigned short accuracyValue )
{
	/// 공격자 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
	cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( pAttacker, true );
	while( pPlayer != NULL )
	{
		mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
		pPlayer = GRIDMANAGER->FindNextPlayer( true );
	}

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

	/// 적용 메세지
	applyMsg = (MSG_SYN_SKILL_DELTARGET*)perIoContext->buffer;
	applyMsg->Category     = NM_SKILL;
	applyMsg->Protocol     = NM_SKILL_DELTARGET_SYN;
	applyMsg->mSkillIndex  = mUniqueIdx;
	applyMsg->mSkillClassIndex = mSkillIdx;
	applyMsg->mAttackerType = mAttacker.type;
	applyMsg->mAttackerDie = mAttackerDie == 1;
	applyMsg->mDramaKey = mDramaKey;

	applyMsg->mDelTargetType = pAttacker->GetObjectType();
	applyMsg->mDelTargetIndex = pAttacker->GetObjectID();

	/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
	cBaseObject* pTarget = NULL;
	sObject target;
	unsigned long targetCnt = 0;
	if( mAttackerDie != 1 )
	{
		for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
		{
			/// 타겟 객체 확인
			target = mTargetAry[i].mTarget;

			switch( target.type )
			{
			case eOBJECTTYPE_MONSTER:
				pTarget = GRIDMANAGER->GetMonster( target.index );
				break;
			case eOBJECTTYPE_PLAYER:	
				pTarget = GRIDMANAGER->GetPlayer( target.index ); 
				break;
			default:
				pTarget = NULL;
			}

			if( pTarget == NULL ) 
				continue;

			pTarget->AddTakeDamage( mAttacker, 0, mDistressPoint, eTAKEDAMAGETYPE_NONETARGET );

			applyMsg->mApplyObject[targetCnt].mTarget = target;

			/// 확률 체크
			unsigned long activityRand = rand() % 100;
			applyMsg->mApplyObject[targetCnt].mInfluenceMiss = (accuracyValue <= activityRand);

			/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
			pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
			while( pPlayer != NULL )
			{
				mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
				pPlayer = GRIDMANAGER->FindNextPlayer( true );
			}
			++targetCnt;
		}
	}
	applyMsg->mApplyCount = (unsigned char)targetCnt;

	for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
	{
		/// apply target 
		target = mTargetAry[i].mTarget;

		///
		if( target.type == eOBJECTTYPE_PLAYER )
		{
			/// msg send;
			cPlayer* player = GRIDMANAGER->GetPlayer( target.index );
			if( player && applyMsg->mApplyObject[i].mInfluenceMiss == false )
			{
				NETWORK2->SendToUser( player->GetConnectionIdx(), (char*)applyMsg, applyMsg->GetMsgLength() );
				mSendTargetSet.Erase( player->GetConnectionIdx() );
			}
		}
		else
		{
			cMonster* monster = GRIDMANAGER->GetMonster( target.index );
			if( monster )
			{
				if( applyMsg->mApplyObject[i].mInfluenceMiss == false )
					monster->ChangeTargetDeleteSkill();
			}
		}
	}

	//applyMsg->mDelTargetType = 0;
	//applyMsg->mDelTargetIndex = 0;

	cHashSet::cIterator begin = mSendTargetSet.Begin();
	cHashSet::cIterator end = mSendTargetSet.End();
	unsigned long connectionIdx;

	while( begin != end )
	{
		connectionIdx = *begin++;
		NETWORK2->SendToUser( connectionIdx, (char*)applyMsg, applyMsg->GetMsgLength() );
	}

	perIoContext->offset = applyMsg->GetMsgLength();

	char text[255];
	sprintf( text,
		"cBaseSkillObject::SendDelTarget:MSG_SYN_SKILL_DELTARGET(%d %d %d %d %d %d %d %d)",
		mAttacker.type,
		mAttacker.index,
		applyMsg->GetMsgLength(),
		applyMsg->mSkillIndex,
		applyMsg->mAttackerType,
		applyMsg->mSkillClassIndex,
		applyMsg->mDramaKey,
		applyMsg->mApplyCount );

	NETWORK2->ReleaseIoContext( perIoContext, text );

	mDetailStartTime = NETWORK2->GetAccumTime();
	if( mpDetailBegin != NULL )
		mDetailEndTime = mDetailStartTime + mpDetailBegin->time;
}


bool cBaseSkillObject::ApplyHP( cBaseObject* pAttacker )
{
	mSendTargetSet.Clear();

	if( mpDetailBegin != NULL )
	{
		/// 공격자 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
		cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( pAttacker, true );

		while( pPlayer != NULL )
		{
			mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
			pPlayer = GRIDMANAGER->FindNextPlayer( true );
		}

		PerIoContext*            perIoContext = NETWORK2->GetIoContext( IOCP_REQUEST_WRITE );
		MSG_SYN_SKILL_DAMAGE_HP* damageMsg    = NULL;
		unsigned char            targetCnt    = 0;

		/// 데미지 메세지
		damageMsg = (MSG_SYN_SKILL_DAMAGE_HP*)perIoContext->buffer;

		damageMsg->Category     = NM_SKILL;
		damageMsg->Protocol     = NM_SKILL_DAMAGE_HP_SYN;
		damageMsg->mAttacker	= mAttacker;

		unsigned long applyDamage = 0;
		bool          die         = false;

		/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
		cBaseObject*  pTarget = NULL;
		sMultiTarget* pMultiTarget;
		sObject       target;

		for( unsigned long i = 0; i < mTargetAry.GetSize(); ++i )
		{
			pMultiTarget = &mTargetAry[i];
			if( pMultiTarget == NULL )
				continue;

			pTarget = NULL;
			target  = pMultiTarget->mTarget;
			pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
			if( pTarget == NULL || pTarget->GetStateDie() == true )
				continue;

			sTargetDamageHP* damageHP = (damageMsg->mTargetDamage + targetCnt);

			/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
			pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
			while( pPlayer != NULL )
			{
				mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
				pPlayer = GRIDMANAGER->FindNextPlayer( true );
			}

			unsigned long	mDetailDamage = pMultiTarget->mDetailDamage[mDetailPos];
			unsigned char	mDetailDamageKind = pMultiTarget->mDetailDamageKind[mDetailPos];

			if( pMultiTarget->mApplyHP == true )
			{
				/// 결투 데미지 체크시 결투 종료면 hpdamage 내부적으로 처리
				if( DUELMANAGER->DuelLastAttack( mAttacker, target, mDetailDamage, mDetailDamageKind == eDAMAGE_CRITICAL, &applyDamage ) == false )
					applyDamage = pTarget->HPDamage( mDetailDamage, &die, false, mDetailDamageKind == eDAMAGE_CRITICAL );
			}
			else
			{
				pTarget->MPDamage( mDetailDamage , false );
				applyDamage = 0;
			}

			damageHP->mTarget      = target;
			damageHP->mDamageKind  = mDetailDamageKind;
			damageHP->mObjectHP    = pTarget->GetHP();
			damageHP->mObjectMaxHP = pTarget->GetMaxHP();
			damageHP->mDie         = pTarget->GetStateDie();
			damageHP->mObjectMP    = pTarget->GetMP();
			damageHP->mObjectMaxMP = pTarget->GetMaxMP();

			++targetCnt;

			/// 받은 데미지 처리
			pTarget->AddTakeDamage( mAttacker, applyDamage, mDetailDamageKind == eDAMAGE_MISS ? 0 : mDistressPoint, eTAKEDAMAGETYPE_DAMAGE );
		}

		long damage = 0;
		long totalDamage = 0;
		long mp = 0;
		long totalMp = 0;

		/// 데미지 반사이거나 공격자 데미지 흡수 인경우
		if( pAttacker != NULL && pAttacker->GetStateDie() != true )
		{
			for( unsigned long i = 0 ; i < targetCnt ; ++i )
			{
				pMultiTarget = &mTargetAry[i];
				if( pMultiTarget != NULL )
				{
					target      = pMultiTarget->mTarget;
					damage      = pMultiTarget->mDetailReflection[mDetailPos] - pMultiTarget->mDetailAttkAbsorbHP[mDetailPos];
					totalDamage = totalDamage + damage;					 

					if( damage > 0 )
					{
						if( pMultiTarget->mApplyHP == true )
						{
							/// 결투 데미지 체크시 결투 종료면 hpdamage 내부적으로 처리
							if( DUELMANAGER->DuelLastAttack( target, mAttacker, damage, eDAMAGE_NORMAL, &applyDamage ) == false )
								applyDamage = pAttacker->HPDamage( damage, &die, false, eDAMAGE_NORMAL );
						}
						else
						{
							pAttacker->MPDamage( damage, false );
							applyDamage = 0;
						}

						/// 받은 데미지 처리
						pAttacker->AddTakeDamage( target, applyDamage, mDistressPoint, eTAKEDAMAGETYPE_DAMAGE );
					}
					else if( damage < 0 )
					{
						if( pMultiTarget->mApplyHP == true )
							pAttacker->HPHeal( -damage, false, 0, 0, eTAKEDAMAGETYPE_NONE );	/// 데미지 반사 인경우임
						else
							pAttacker->MPHeal( -damage, false );
					}

					mp      = pMultiTarget->mDetailAttkAbsorbMP[mDetailPos];
					totalMp = totalMp + mp;

					if( mp > 0 )
						pAttacker->MPHeal( mp, false );
					else if( mp < 0 )
						pAttacker->MPDamage( abs(mp), false );

				}
				else
					assert(NULL);
			}

			if( totalDamage != 0 || totalMp != 0 )
			{
				sTargetDamageHP* damageHP = (damageMsg->mTargetDamage + targetCnt);

				damageHP->mTarget      = mAttacker;
				damageHP->mDamageKind  = eDAMAGE_NORMAL;
				damageHP->mObjectHP    = pAttacker->GetHP();
				damageHP->mObjectMaxHP = pAttacker->GetMaxHP();
				damageHP->mObjectMP    = pAttacker->GetMP();
				damageHP->mObjectMaxMP = pAttacker->GetMaxMP();
				damageHP->mDie         = pAttacker->GetStateDie();

				++targetCnt;
			}
		}

		damageMsg->mCount = targetCnt;

		cHashSet::cIterator begin = mSendTargetSet.Begin();
		cHashSet::cIterator end   = mSendTargetSet.End();
		unsigned long connectionIdx;

		while( begin != end )
		{
			connectionIdx = *begin++;
			NETWORK2->SendToUser( connectionIdx, (char*)damageMsg, damageMsg->GetMsgLength() );
		}

		perIoContext->offset = damageMsg->GetMsgLength();
		NETWORK2->ReleaseIoContext( perIoContext, "cBaseSkillObject::ApplyHP:MSG_SYN_SKILL_DAMAGE_HP" );

		if( mpDetailBegin == 0 )
		{
			assert(0);
			return false;
		}

		mpDetailBegin = (sDramaAction*)mpDetailBegin->pNext;
		++mDetailPos;

		/// 적용항목 더 없음
		if( mpDetailBegin == NULL)
			return false;

		mDetailEndTime = mDetailStartTime + mpDetailBegin->time;
		return true;
	}

	return false;
}


void cBaseSkillObject::AddTotem( cBaseObject* pAttacker, unsigned long partyIdx, eAPPLYTYPE applyType, eATTRIBUTETYPE attributeType, unsigned long influenceClassIdx )
{

	NiPoint2 centerPos( mCenterPos.x, mCenterPos.y );
	cTotem* pTotem = OBJECTMANAGER->AddTotem( influenceClassIdx, partyIdx, centerPos, pAttacker->GetObject(), applyType, attributeType );
	if( pTotem != NULL )
		GRIDMANAGER->AddTotem( pTotem );

	/// 공격자 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
	cPlayer* pPlayer = GRIDMANAGER->FindFirstPlayer( pAttacker, true );
	while( pPlayer != NULL )
	{
		mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
		pPlayer = GRIDMANAGER->FindNextPlayer( true );
	}

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

	/// 적용 메세지
	applyMsg = (MSG_SYN_SKILL_APPLY*)perIoContext->buffer;

	applyMsg->Category     = NM_SKILL;
	applyMsg->Protocol     = NM_SKILL_APPLY_SYN;
	applyMsg->mSkillIndex  = mUniqueIdx;
	applyMsg->mAttackerType = mAttacker.type;
	applyMsg->mSkillClassIndex = mSkillIdx;
	applyMsg->mDramaKey = mDramaKey;
	applyMsg->mAttackerDie = mAttackerDie == 1;
	applyMsg->mIsChangeMonster = mIsChgMonsSkill;

	/// 타겟의 수만큼 데미지 계산 & 통합 메세지 발송
	cBaseObject* pTarget = NULL;
	sObject target;
	unsigned long targetCnt = 0;
	if( mAttackerDie != 1 )
	{
		for( unsigned long i = 0 ; i < mTargetAry.GetSize() ; ++i )
		{
			/// 타겟 객체 확인
			target = mTargetAry[i].mTarget;

			switch( target.type )
			{
			case eOBJECTTYPE_MONSTER:
				pTarget = GRIDMANAGER->GetMonster( target.index );
				break;
			case eOBJECTTYPE_PLAYER:	
				pTarget = GRIDMANAGER->GetPlayer( target.index ); 
				break;
			default:
				pTarget = NULL;
			}

			if( pTarget == NULL ) 
				continue;

			applyMsg->mApplyObject[targetCnt].mTarget = target;

			/// 타겟 그리드에 해당하는 플레이어를 모두 읽어 데미지 발송자 명단에 넣는다.
			pPlayer = GRIDMANAGER->FindFirstPlayer( pTarget, true );
			while( pPlayer != NULL )
			{
				mSendTargetSet.Insert( pPlayer->GetConnectionIdx() );
				pPlayer = GRIDMANAGER->FindNextPlayer( true );
			}

			++targetCnt;
		}
	}

	applyMsg->mApplyCount = (unsigned char)targetCnt;

	cHashSet::cIterator begin = mSendTargetSet.Begin();
	cHashSet::cIterator end = mSendTargetSet.End();
	unsigned long connectionIdx;

	while( begin != end )
	{
		connectionIdx = *begin++;
		NETWORK2->SendToUser( connectionIdx, (char*)applyMsg, applyMsg->GetMsgLength() );
	}

	perIoContext->offset = applyMsg->GetMsgLength();

	char text[255];
	sprintf( text,
		"cBaseSkillObject::SendApply:MSG_SYN_SKILL_APPLY(%d %d %d %d %d %d %d %d %d)",
		mAttacker.type,
		mAttacker.index,
		applyMsg->GetMsgLength(),
		applyMsg->mSkillIndex,
		applyMsg->mAttackerType,
		applyMsg->mSkillClassIndex,
		applyMsg->mDramaKey,
		applyMsg->mIsChangeMonster,
		applyMsg->mApplyCount );

	NETWORK2->ReleaseIoContext( perIoContext, text );

	mDetailStartTime = NETWORK2->GetAccumTime();
	if( mpDetailBegin != NULL )
		mDetailEndTime = mDetailStartTime + mpDetailBegin->time;
}


unsigned long cBaseSkillObject::SkillEndTime()
{
	unsigned long detailEndTime = 0;
	if( mpDetailEnd != NULL )
		detailEndTime = mpDetailEnd->time;

	return (mCreationTime + mCastingTime + mActivityTime) - NETWORK2->GetAccumTime() + detailEndTime;
}
