#include "gamesrv.h"

#include "stdafx.h"

#include "SkillManager.h"
#include "ObjectManager.h"

#include "BasicString.h"

#include "Skill_Common.h"
#include "Npc_Common.h"
#include "GameFile.h"
#include "BaseObject.h"
#include "Protocol.h"
#include "Player.h"
#include "Monster.h"
#include "Totem.h"
#include "StatusCalc_Server.h"
#include "SkillScript.h"
#include "CommunityScript.h"
#include "MakeSkillScript.h"
#include "RangeCheck.h"
#include "AppTimer.h"
#include "InfluenceObject.h"
#include "DuelManager.h"
#include "StatusCalc_Server.h"


cSkillManager* cSkillManager::mpSkillManager = NULL;


cSkillManager::cSkillManager()
{
	if(mpSkillManager)
	{
		MessageBox(NULL,"cSkillManager : 이 클래스는 객체가 유일한객체(singleton)이어야 합니다.\
			이전 객체를 사용하던지 이전 객체를 지우고 생성하십시오.","경고",MB_OK);
	}
	else
	{
		mpSkillManager = this;
	}
}



cSkillManager::~cSkillManager()
{
	ReleasePlayerHaveSkill();
	ReleaseInfluence();
	Release();
}



bool cSkillManager::Init()
{
	mPlayerSkillObjectPool.Reserve(1000,100);
	mMonsterSkillObjectPool.Reserve(2000,100);
	mPlayerMonSkillObjectPool.Reserve(1000,100);
	mInfluenceObjectPool.Reserve(5000,100);
	mPlayerHaveSkillPool.Reserve(40000,100);

	mpSkillScript = new cSkillScript;
	if( !mpSkillScript->Init() )
	{
		return false;
	}

	mpCommuScript = new cCommunityScript;
	if( !mpCommuScript->Init() )
	{
		return false;
	}

	if( !mDramaturgyManager.Init() )
	{
		return false;
	}

	mpMakeSkillScript = new cMakeSkillScript;
	if( !mpMakeSkillScript->Init() )
	{
		return false;
	}

	return true;
}



void cSkillManager::Release()
{
	SAFE_DELETE( mpMakeSkillScript );
	SAFE_DELETE( mpCommuScript );
	SAFE_RELEASE_DELETE( mpSkillScript );	

	cPointerHashMap::cIterator iter;

	cBaseSkillObject* pPlayerSkillObj = NULL;
	cMonsterSkillObject* pMonsterSkillObj = NULL;

	cArray* pSkillArray = NULL;
	for(iter = mPlayerSkillMap.Begin(); iter != mPlayerSkillMap.End(); ++iter)
	{
		pSkillArray = (cArray*)(*iter).mSecond;

		if( pSkillArray == NULL )
			continue;

		for( unsigned int i = 0 ; pSkillArray->GetSize() > i ; ++i )
		{
			pPlayerSkillObj = (cBaseSkillObject*)(*pSkillArray)[i];
			SAFE_DELETE( pPlayerSkillObj );
		}
		pSkillArray->Clear();

		SAFE_DELETE( pSkillArray );
	}
	mPlayerSkillMap.Clear();

	for(iter = mMonsterSkillMap.Begin(); iter != mMonsterSkillMap.End(); ++iter)
	{
		pSkillArray = (cArray*)(*iter).mSecond;

		if( pSkillArray == NULL )
			continue;

		for( unsigned int i = 0 ; pSkillArray->GetSize() > i ; ++i )
		{
			pMonsterSkillObj = (cMonsterSkillObject*)(*pSkillArray)[i];
			SAFE_DELETE( pMonsterSkillObj );
		}
		pSkillArray->Clear();

		SAFE_DELETE( pSkillArray );
	}
	mMonsterSkillMap.Clear();
}


void cSkillManager::Process( unsigned long elapsedTime, unsigned long accumTime )
{
	cBaseSkillObject*    pPlayerSkillObj  = NULL;
	cMonsterSkillObject* pMonsterSkillObj = NULL;
	cInfluenceObject*    pInfluenceObject = NULL;

	cPointerHashMap::cIterator begin;
	cPointerHashMap::cIterator end;

try {

	/// 플레이어 액티브 스킬 프로세스
	end = mPlayerSkillMap.End();
	for( begin = mPlayerSkillMap.Begin() ; begin != end; ++begin )
	{
		cArray* pSkillArray = (cArray*)(*begin).mSecond;

		if( pSkillArray != NULL )
		{
			unsigned int arraySize = pSkillArray->GetSize();
			for( unsigned int i = arraySize ; i > 0 ; --i )
			{
				pPlayerSkillObj = (cBaseSkillObject*)(*pSkillArray)[i-1];
				if( pPlayerSkillObj != NULL )
					pPlayerSkillObj->Process( elapsedTime, accumTime );
				else
				{				
					NETWORK2->PostServerEvent( "cSkillManager::Process() pPlayerSkillObj == NULL" );
					pSkillArray->PopAt( i-1 );
				}
			}
		}
		else if ( end != mPlayerSkillMap.End( ) )
		{
			NETWORK2->PostServerEvent( "In if ( end != mPlayerSkillMap.End( ) ) { ... }. Throwing 'cSkillManager::Process' exception." );
		}
		else
			assert(NULL);
	}

} catch ( ... ) {
	NETWORK2->PostServerEvent( "In for( mPlayerSkillMap ) { (0x%x)->Process( %u, %u ); }. Throwing 'cSkillManager::Process' exception.", pPlayerSkillObj, elapsedTime, accumTime );
	throw;
}

try {

	/// 몬스터 액티브 스킬 프로세스
	end = mMonsterSkillMap.End();
	for( begin = mMonsterSkillMap.Begin() ; begin != end; ++begin )
	{
		cArray* pSkillArray = (cArray*)(*begin).mSecond;
		if( pSkillArray != NULL )
		{
			unsigned int arraySize = pSkillArray->GetSize( );
			for( unsigned int i = arraySize ; i > 0 ; --i )
			{
				pMonsterSkillObj = (cMonsterSkillObject*)(*pSkillArray)[i-1];
				if( pMonsterSkillObj != NULL )
					pMonsterSkillObj->Process( elapsedTime, accumTime );
				else
				{
					NETWORK2->PostServerEvent("cSkillManager::Process() pMonsterSkillObj == NULL");
					pSkillArray->PopAt( i-1 );
				}
			}
		}
		else if ( end != mMonsterSkillMap.End( ) )
		{
			NETWORK2->PostServerEvent( "In if ( end != mMonsterSkillMap.End( ) ) { ... }. Throwing 'cSkillManager::Process' exception." );
		}
		else
			assert(NULL);
	}

} catch ( ... ) {
	NETWORK2->PostServerEvent( "In for( mMonsterSkillMap ) { (0x%x)->Process( %u, %u ) }. Throwing 'cSkillManager::Process' exception.", pMonsterSkillObj, elapsedTime, accumTime );
	throw;
}


try {

	/// 효과 리스트 프로세스
	end = mInfluenceMap.End();
	for( begin = mInfluenceMap.Begin() ; begin != end; ++begin )
	{
		pInfluenceObject = (cInfluenceObject*)((*begin).mSecond);
		if( pInfluenceObject != NULL )
		{
			if( pInfluenceObject->Process( elapsedTime, accumTime ) == false )
                mDeleteInfluenceAry.PushBack( pInfluenceObject->GetUniqueIdx() );
		}
		else if ( end != mInfluenceMap.End( ) )
			NETWORK2->PostServerEvent( "In if ( end != mInfluenceMap.End( ) ) { ... }. Throwing 'cSkillManager::Process' exception." );
		else
			assert(NULL);
	}

	for( unsigned long i = 0 ; mAura.GetSize() > i ; ++i )
	{
		AddInfChildAura( mAura[i].attacker, mAura[i].target, mAura[i].influenceClassIdx, 0, mAura[i].parentUniqueIdx, true );
	}
	mAura.Clear();

} catch ( ... ) {
	NETWORK2->PostServerEvent( "In for( mInfluenceMap ) { (0x%x)->Process( %u, %u ) }. Throwing 'cSkillManager::Process' exception.", pInfluenceObject, elapsedTime, accumTime );
	throw;
}

try {

	/// 플레이어, 몬스터 액티브 스킬 삭제
	unsigned int DelSkillArySize = mDeleteSkillAry.GetSize( );

	sDelSkillInfo* info;
	for( unsigned int i = 0 ; i < DelSkillArySize ; ++i )
	{
		info = &mDeleteSkillAry[i];
		if( info != NULL )
		{
			if( DeleteSkillObject( info->skillUser.type, info->skillUser.index, info->mSkillIdx ) == false )
				NETWORK2->PostServerEvent( "cSkillManager::Process DeleteSkillObject[%d,%d,%d]", info->skillUser.type, info->skillUser.index, info->mSkillIdx );
		}                		
	}
	mDeleteSkillAry.Clear();

} catch ( ... ) {
	NETWORK2->PostServerEvent( "In for( mDeleteSkillAry ) { ... }. Throwing 'cSkillManager::Process' exception." );
	throw;
}

try {

	/// 효과 삭제 목록 처리
	unsigned int mDelSize = mDeleteInfluenceAry.GetSize( );
	for( unsigned int i = 0; i < mDelSize; ++i )
	{
		unsigned long uniqueIdx = mDeleteInfluenceAry[i];
		cInfluenceObject* pInf = GetInfluence( uniqueIdx );
		if( pInf != NULL )
			DeleteInfluence( pInf->GetTarget(), uniqueIdx );
	}
	mDeleteInfluenceAry.Clear();

} catch ( ... ) {
	NETWORK2->PostServerEvent( "In for( mDeleteInfluenceAry ) { ... }. Throwing 'cSkillManager::Process' exception." );
	throw;
}

}



bool cSkillManager::IsUsingSkill( unsigned char type, unsigned long objectIdx ) 
{
	switch( type )
	{
	case eOBJECTTYPE_MONSTER:
		{
			cArray* pSkillArray = (cArray*)mMonsterSkillMap.GetAt( objectIdx );
			if( pSkillArray == NULL )
			{
				return false;
			}

			cMonsterSkillObject* pSkillObject = NULL;
			for( unsigned int i = 0 ; i < pSkillArray->GetSize() ; ++i )
			{
				pSkillObject = (cMonsterSkillObject*)(*pSkillArray)[i];
				if( pSkillObject == NULL )
					continue;

				/// 데미지 적용까지 가기전 단계이면 스킬 사용 중
				if( pSkillObject->GetSkillState() < eSKILLPROCESS_PROJECTILE )
					return true;
			}
		}
		break;
	case eOBJECTTYPE_HERO:
	case eOBJECTTYPE_PLAYER:
		{
			cArray* pSkillArray = (cArray*)mPlayerSkillMap.GetAt( objectIdx );
			if( pSkillArray == NULL )
			{
				return false;
			}

			cBaseSkillObject* pSkillObject = NULL;
			for( unsigned int i = 0 ; i < pSkillArray->GetSize() ; ++i )
			{
				pSkillObject = (cBaseSkillObject*)(*pSkillArray)[i];
				if( pSkillObject == NULL )
					continue;

				if( pSkillObject->IsProjectile() == false )
				{
					/// 데미지 적용까지 가기전 단계이면 스킬 사용 중
					if( pSkillObject->GetSkillState() < eSKILLPROCESS_HP || pSkillObject->IsDetailEnd() == false )
						return true;
				}
				else
				{
					/// 데미지 적용까지 가기전 단계이면 스킬 사용 중
					if( pSkillObject->GetSkillState() < eSKILLPROCESS_PROJECTILE )
						return true;
				}
			}
		}
		break;		
	default:
		assert(NULL);
		NETWORK2->PostServerEvent("IsUsingSkill type error[%d]", type );
		return false;
	}

	return false;
}


unsigned long cSkillManager::SkillLeftEndTime( unsigned char type, unsigned long objectIdx ) 
{
	switch( type )
	{
	case eOBJECTTYPE_MONSTER:
		{
			cArray* pSkillArray = (cArray*)mMonsterSkillMap.GetAt( objectIdx );
			if( pSkillArray == NULL )
			{
				return 0;
			}

			cMonsterSkillObject* pSkillObject = NULL;
			for( unsigned int i = 0 ; i < pSkillArray->GetSize() ; ++i )
			{
				pSkillObject = (cMonsterSkillObject*)(*pSkillArray)[i];
				if( pSkillObject == NULL )
					continue;

				/// 데미지 적용까지 가기전 단계이면 스킬 사용 중
				if( pSkillObject->GetSkillState() < eSKILLPROCESS_PROJECTILE )
					return pSkillObject->SkillEndTime();
			}
		}
		break;
	case eOBJECTTYPE_HERO:
	case eOBJECTTYPE_PLAYER:
		{
			cArray* pSkillArray = (cArray*)mPlayerSkillMap.GetAt( objectIdx );
			if( pSkillArray == NULL )
			{
				return 0;
			}

			cBaseSkillObject* pSkillObject = NULL;
			for( unsigned int i = 0 ; i < pSkillArray->GetSize() ; ++i )
			{
				pSkillObject = (cBaseSkillObject*)(*pSkillArray)[i];
				if( pSkillObject == NULL )
					continue;

				if( pSkillObject->IsProjectile() == false )
				{
					/// 데미지 적용까지 가기전 단계이면 스킬 사용 중
					if( pSkillObject->GetSkillState() < eSKILLPROCESS_HP || pSkillObject->IsDetailEnd() == false )
						return pSkillObject->SkillEndTime();
				}
				else
				{
					/// 데미지 적용까지 가기전 단계이면 스킬 사용 중
					if( pSkillObject->GetSkillState() < eSKILLPROCESS_PROJECTILE )
						return pSkillObject->SkillEndTime();
				}
			}
		}
		break;		
	default:
		assert(NULL);
		NETWORK2->PostServerEvent("IsUsingSkill type error[%d]", type );
		return 0;
	}

	return 0;
}


void cSkillManager::CastSkillCancel( sObject attacker )
{
	cPlayer* pAttacker = OBJECTMANAGER->GetPlayer(attacker.index);
	if( pAttacker == NULL )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("CastSkillCancel - attacker.index[%d]", attacker.index );
		return;
	}

	unsigned long skillIndex = 0;

	cArray* pSkillArray = (cArray*)mPlayerSkillMap.GetAt( attacker.index );
	if( pSkillArray != NULL )
	{
		cBaseSkillObject* pSkillObject = NULL;
		for( unsigned int i = 0 ; i < pSkillArray->GetSize() ; ++i )
		{
			pSkillObject = (cBaseSkillObject*)(*pSkillArray)[i];
			if( pSkillObject == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("CastSkillCancel - mPlayerSkillMap.GetAt( %d )", attacker.index );
				continue;
			}

			/// 취소하려는 스킬 객체가 캐스팅중이 아니면 캐스팅 취소 성공메세지를 보내지 않는다.
			if( pSkillObject->GetSkillState() != eSKILLPROCESS_CASTING )
			{
				Verbose->WriteLog("State() != eSKILLPROCESS_CASTING");
				return;
			}
			else
			{
				skillIndex = pSkillObject->GetUniqueIdx();
				/// 캐스팅 취소처리
				DeleteSkillObject( attacker.type, attacker.index, skillIndex );
				if( pAttacker->ChangeState( eOBJECT_STATE_IDLE ) == false )
				{
					assert(NULL);
					NETWORK2->PostServerEvent("cSkillManager::CastSkillCancel pAttacker[%d,%d,%d,%d]->ChangeState( eOBJECT_STATE_IDLE ) == false",
						attacker.index, pAttacker->GetState(), pAttacker->GetStateStop(), pAttacker->GetRequestRejection() );
				}

				break;
			}
		}
	}
	else
	{
		/// 이미 스킬 객체가 없기때문에 캐스팅 취소 성공메세지는 보낸다
		NETWORK2->PostServerEvent("CastSkillCancel - none skillobject");
		assert(NULL);
	}

	/// 캐스팅 취소 응답 메세지는 취소 성공시에만 발송된다.
	HANDLE                     handle  = NULL;
	MSG_RES_SKILL_CAST_CANCEL* pResMsg = (MSG_RES_SKILL_CAST_CANCEL*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

	if ( pResMsg != NULL )
	{
		pResMsg->Category = NM_SKILL;
		pResMsg->Protocol = NM_SKILL_CAST_CANCEL_RES;
		pResMsg->mSkillIndex = skillIndex;
		NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_CAST_CANCEL) );
	}

	MSG_SYN_SKILL_CAST_CANCEL pSynMsg;
	pSynMsg.Category = NM_SKILL;
	pSynMsg.Protocol = NM_SKILL_CAST_CANCEL_SYN;
	pSynMsg.mSkillIndex = skillIndex;
	NETWORK2->QuickSendExcept( pAttacker, (char*)&pSynMsg, sizeof(pSynMsg) );
}


void cSkillManager::PlayerNormalAttackRequest( unsigned long characterIdx, MSG_REQ_SKILL_USED* pMsg )
{

	/// 이함수를 통해 들어오는 유저의 타입은 플레이어 뿐이다.
	sObject attacker = { eOBJECTTYPE_PLAYER, characterIdx };

	sObject target = pMsg->mTarget;
	unsigned long skillIdx = pMsg->mSkillClassIndex;
	unsigned long clientIdx = pMsg->mManagedKey;

	/// 서버에는 hero가 없다
	if( target.type == eOBJECTTYPE_HERO )
		target.type = eOBJECTTYPE_PLAYER;

	/// 공격자 객체 체크
	cPlayer* pAttacker = (cPlayer*)OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL )	
	{ 
		NETWORK2->PostServerEvent("PlayerNormalAttackRequest - GetObject( %d, %d )", attacker.type, attacker.index );
		assert(NULL);
		return; 
	}

	pAttacker->MoveStop();

	try
	{
		/// 기본 공격 스킬 인덱스가 아님
		if( skillIdx > NORMAL_ATTACK_SKILL_MAX )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerNormalAttackRequest - skillIdx[%d] > NORMAL_ATTACK_SKILL_MAX", skillIdx );
			throw SKILL_USED_ERR_SKILLTYPE;
		}


		/// 타겟 객체 체크
		cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
		if( pTarget == NULL )	
			throw SKILL_USED_ERR_TARGETOBJECT;


		if( pTarget->GetStateDie() == true )	
			throw SKILL_USED_ERR_TARGETOBJECT;


		if( pAttacker->GetChgMonsterIdx() != 0 )
			throw SKILL_USED_ERR_CHG_MONSTER;


		/// 공격자에게 타겟을 설정한다
		pAttacker->SetTarget( target.type, target.index );


		/// 플레이어가 목적지에 도착하기전에 공격메세지가 들어오는 경우 허용범위 계산
		mRangeCheck.SetRadius( SYNC_MOVE_RANGE );
		NiPoint2 clientPos( pMsg->mClientPosX, pMsg->mClientPosY );
		NiPoint2 serverPos = pAttacker->GetPos();

		if( mRangeCheck.IsRange( serverPos, clientPos ) == false )
		{
			/// 이동 중이던 상태 정지
			pAttacker->SetMoveTargetPos( serverPos.x, serverPos.y );

			Verbose->WriteLog("sPos[%.2f,%.2f],cPos[%.2f,%.2f] = [%.2f]", serverPos.x, serverPos.y, clientPos.x, clientPos.y, (serverPos - clientPos).Length() );

			HANDLE             handle = NULL;
			MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

			if ( posMsg != NULL )
			{
				posMsg->Category      = NM_SKILL;
				posMsg->Protocol      = NM_SKILL_USED_POS_ERROR_RES;
				posMsg->mAttacker     = attacker;
				posMsg->mAttackerPosX = pAttacker->GetXPos( );
				posMsg->mAttackerPosY = pAttacker->GetYPos( );
				posMsg->mTarget       = target;
				posMsg->mTargetPosX   = pTarget->GetXPos( );
				posMsg->mTargetPosY   = pTarget->GetYPos( );

				NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
			}

			throw SKILL_USED_ERR_ATTAKCERPOS;
		}


		/// 이미 스킬 사용중인지 체크
		if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
			throw SKILL_USED_ERR_USING;


		/// 스킬index 체크
		sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
		if( pSkillInfo == NULL )	
		{ 
			/// 스킬 스크립트 데이터 획득 오류
			assert(NULL); 
			NETWORK2->PostServerEvent("playerNormalAttackRequest skillScript[%d]", skillIdx );
			throw SKILL_ADD_ERR_SCRIPT;
		}


		/// 액티브 스킬이 아닌 경우
		if( pSkillInfo->mType == eSKILLTYPE_PASSIVE )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("playerNormalAttackRequest eSKILLTYPE_PASSIVE[%d,%d]", skillIdx, pSkillInfo->mType );
			throw SKILL_USED_ERR_PASSIVE;
		}


		/// 기본 공격도 쿨타임 체크를 해야 하기 때문에 보유스킬 리스트에 대표번호(1)로 넣는다.
		cHaveSkillObject* pPlayerHaveSkill = GetPlayerHaveSkill( attacker.index, NORMAL_ATTACK_SKILL );
		if( !pPlayerHaveSkill )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent("playerNormalAttackRequest GetPlayerHaveSkill[%d,%d]", attacker.index, NORMAL_ATTACK_SKILL );
			throw SKILL_USED_ERR_DONTHAVE;
		}


		/// 스킬 스크립트 스텝정보 읽어옴
		unsigned int stepIdx = pPlayerHaveSkill->GetSkillStep();
		sPlayerSkillStepInfo* pStepInfo = &pSkillInfo->mpSetpInfoArray[stepIdx];

		/// 내 장비가 맞는지 체크
		eWEAPON_STATE weaponState = pAttacker->GetWeaponState();
		bool IsEquipment = true;
		if( pSkillInfo->mUseEquipment )
		{
			/// 착용 장비 체크
			switch( pSkillInfo->mUseEquipment )
			{
			case eEQUIPTYPE_SWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD )
					IsEquipment = false;
				break;
			case eEQUIPTYPE_LONGSWORD:
				if( weaponState != eWEAPON_STATE_LONGSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHORTSWORD:
				if( weaponState != eWEAPON_STATE_SHORTSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_GUN:
				if( weaponState != eWEAPON_STATE_GUN )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_STAFF:
				if( weaponState != eWEAPON_STATE_STAFF )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHIELD:
				if( weaponState != eWEAPON_STATE_SHEILD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_LONGSWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_LONGSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_LONGSWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_LONGSWORD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_LONGSWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_LONGSWORD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHORTSWORD_GUN:
				if( weaponState != eWEAPON_STATE_SHORTSWORD &&
					weaponState != eWEAPON_STATE_GUN )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_CLOTH:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_ROBES )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_LIGHTARMOR:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_LIGHT_ARMOR )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_HEAVYARMOR:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_HEAVY_ARMOR )
					IsEquipment =  false;
				break;
			}
		}

		if( IsEquipment == false )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("player[%d]NormalAttack[%d]Request IsEquipment[%d,%d]== false", 
				attacker.index, weaponState,pAttacker->GetArmorSets(), skillIdx );
			throw SKILL_USED_ERR_EQUIPMENT;
		}

		/// 타겟 조건 체크
		if( target.type != eOBJECTTYPE_MONSTER )
		{
			if( DUELMANAGER->IsAttack( pAttacker->GetDuelIdx(), attacker.index, target.index ) == false )
				throw SKILL_USED_ERR_TARGETSELECT;
		}

		/// 스킬+아이템+효과 사거리
		float range = pAttacker->CalcStatusSkillRange( pStepInfo->mTargetDist, pSkillInfo->mRangeType, true );

		range = OBJECTMANAGER->ObjectSizeRange( pAttacker, pTarget, range );

		/// 스텟에서 계산된 공격속도 ( 아이템, 버프 포함 )
		float normalAttackSpeed = pAttacker->GetStatus2()->mAttackSpeed;

		if( normalAttackSpeed <= 0.0f )
			normalAttackSpeed = 0.1f;

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

		/// 클라,서버 도착 시간 차이만큼의 거리 허용치
		cRangeCheck skillRangeCheck( range + SYNC_MOVE_RANGE );

		NiPoint3 attackerPos( pAttacker->GetXPos(), pAttacker->GetYPos(), pAttacker->Height() );
		NiPoint3 targetPos( pTarget->GetXPos(), pTarget->GetYPos(), pTarget->Height() );

		/// 스킬 사거리 밖에 타겟이 있다.
		if( !skillRangeCheck.IsRange( attackerPos, targetPos ) )	
		{ 
			Verbose->WriteLog("aPos[%.2f,%.2f,%.2f],tPos[%.2f,%.2f,%.2f] = [%.2f]", attackerPos.x, attackerPos.y, attackerPos.z,
				targetPos.x, targetPos.y, targetPos.z, (attackerPos - targetPos).Length() );

			HANDLE             handle  = NULL;
			MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

			if ( posMsg != NULL )
			{
				posMsg->Category      = NM_SKILL;
				posMsg->Protocol      = NM_SKILL_USED_POS_ERROR_RES;
				posMsg->mAttacker     = attacker;
				posMsg->mAttackerPosX = pAttacker->GetXPos( );
				posMsg->mAttackerPosY = pAttacker->GetYPos( );
				posMsg->mTarget       = target;
				posMsg->mTargetPosX   = pTarget->GetXPos( );
				posMsg->mTargetPosY   = pTarget->GetYPos( );

				NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
			}

			throw SKILL_USED_ERR_TARGETDIST;
		}


		unsigned long currentTime = NETWORK2->GetAccumTime();

		/// 쿨타임
		if( pStepInfo->mCoolTime != 0 && pPlayerHaveSkill->GetEndCoolTime() > currentTime )	
		{ 
			NETWORK2->PostServerEvent( "스킬:%d 재사용 오차 [%d - %d = %d]", skillIdx, pPlayerHaveSkill->GetEndCoolTime(), currentTime, pPlayerHaveSkill->GetEndCoolTime() - currentTime );
			Verbose->WriteLog( "스킬:%d 재사용 오차 [%d - %d = %d]", skillIdx, pPlayerHaveSkill->GetEndCoolTime(), currentTime, pPlayerHaveSkill->GetEndCoolTime() - currentTime );

			throw SKILL_USED_ERR_COOLTIME;
		}

		/// 고유키 생성
		unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
		if( skillUniqueIdx == 0 )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent( "playerNormalAttackRequest GeneratIdx[%d]", skillUniqueIdx );
			throw SKILL_USED_ERR_UNIQUEIDX;
		}

		/// 스킬 객체 생성
		cPlayerSkillObject* pSkillObject = new cPlayerSkillObject;
		if( pSkillObject->InitPlayerNormalAttack( characterIdx, target, skillIdx, skillUniqueIdx, 
			normalAttackSpeed, pAttacker->GetMapNumber() ) != 0 )
		{ 
			NETWORK2->PostServerEvent( "playerNormalAttackRequest new SkillObject == false" );
			SAFE_DELETE( pSkillObject );
			throw SKILL_USED_ERR_OBJECTINIT;
		}

		/// 플레이어의 상태 체크한다.
		if( pAttacker->ChangeState( eOBJECT_STATE_ATTACK ) == false )
		{
			SAFE_DELETE( pSkillObject );
			/// 공격자의 상태가 공격 불가 상태
			throw SKILL_USED_ERR_ATTACKERSTATE;
		}

		/// 스킬을 리스트에 등록
		InsertPlayerSkillMap( characterIdx, pSkillObject );

		/// 스킬 생성 성공 메세지를 해당 히어로에게 전달
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL, NM_SKILL_USED_RES );

		if ( resMsg != NULL )
		{
			resMsg->ErrorCode        = 0;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;
			resMsg->mSkillIndex      = skillUniqueIdx;
			resMsg->mObjectMP        = pAttacker->GetMP();
			resMsg->mMaxMP           = pAttacker->GetMaxMP();

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}

		/// 스킬 생성 메세지를 다른 클라이언트에게 전달
		MSG_SYN_SKILL_USED synMsg;
		synMsg.Category = NM_SKILL;
		synMsg.Protocol = NM_SKILL_USED_SYN;
		synMsg.mSkillClassIndex = skillIdx;
		synMsg.mSkillIndex = skillUniqueIdx;
		synMsg.mAttacker = attacker;
		synMsg.mAttackerPosX = pAttacker->GetXPos();
		synMsg.mAttackerPosY = pAttacker->GetYPos();
		synMsg.mTarget = target;
		synMsg.mTargetPosX = pTarget->GetXPos();
		synMsg.mTargetPosY = pTarget->GetYPos();
		synMsg.mObjectMP = pAttacker->GetMP();
		synMsg.mObjectMaxMP = pAttacker->GetMaxMP();
		NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );

		return;

	}
	catch ( int error )
	{
		/// 스킬 실패 메세지
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL, NM_SKILL_USED_RES );

		if ( resMsg != NULL )
		{
			resMsg->ErrorCode        = error;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}
	}
}


void cSkillManager::PlayerObjectSkillRequest( unsigned long characterIdx, MSG_REQ_SKILL_USED* pMsg, bool casting )
{

	/// 이함수를 통해 들어오는 유저의 타입은 플레이어 뿐이다.
	sObject attacker = { eOBJECTTYPE_PLAYER, characterIdx };

	sObject target = pMsg->mTarget;
	unsigned long skillIdx = pMsg->mSkillClassIndex;
	unsigned long clientIdx = pMsg->mManagedKey;

	/// 서버에는 hero가 없다
	if( target.type == eOBJECTTYPE_HERO )
		target.type = eOBJECTTYPE_PLAYER;


	/// 공격자 객체 체크
	cPlayer* pAttacker = (cPlayer*)OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL )	
	{ 
		NETWORK2->PostServerEvent("PlayerObjectSkillRequest - GetObject( %d, %d )", attacker.type, attacker.index );
		assert(NULL);
		return; 
	}

	pAttacker->MoveStop();

	try
	{
		/// 기본 공격 스킬 인덱스인경우 오류
		if( skillIdx <= NORMAL_ATTACK_SKILL_MAX )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest - skillIdx[%d] > NORMAL_ATTACK_SKILL_MAX", skillIdx );
			throw SKILL_USED_ERR_SKILLTYPE;
		}


		/// 스킬 사용 불가
		if( pAttacker->IsCantSkill() == true )
			throw SKILL_USED_ERR_USEVALUE;


		if( pAttacker->GetChgMonsterIdx() != 0 )
			throw SKILL_USED_ERR_CHG_MONSTER;


		/// 스킬 사용중인지 체크
		if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
			throw SKILL_USED_ERR_USING;


		/// 스킬index 체크
		sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
		if( pSkillInfo == NULL )	
		{ 
			/// 스킬 스크립트 데이터 획득 오류
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest skillScript[%d]", skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}


		if( pSkillInfo->mShotType == eSHOTTYPE_CASTING && casting != true )
		{
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest[%d] ShotType != eSHOTTYPE_CASTING[%d]", characterIdx, skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}


		if( pSkillInfo->mShotType != eSHOTTYPE_CASTING && casting == true )
		{
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest[%d] ShotType == eSHOTTYPE_CASTING[%d]", characterIdx, skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}


		/// 액티브 스킬이 아닌 경우
		if( pSkillInfo->mType == eSKILLTYPE_PASSIVE )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest eSKILLTYPE_PASSIVE[%d,%d]", skillIdx, pSkillInfo->mType );
			throw SKILL_USED_ERR_PASSIVE;
		}


		/// 공격자의 보유스킬인지 체크
		cHaveSkillObject* pPlayerHaveSkill = GetPlayerHaveSkill( attacker.index, skillIdx );
		if( !pPlayerHaveSkill )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest GetPlayerHaveSkill[%d,%d]", attacker.index, NORMAL_ATTACK_SKILL );
			throw SKILL_USED_ERR_DONTHAVE;
		}


		/// 스킬 스크립트 스텝정보 읽어옴
		unsigned char stepIdx = pPlayerHaveSkill->GetSkillStep();
		sPlayerSkillStepInfo* pStepInfo = &pSkillInfo->mpSetpInfoArray[stepIdx];

		/// 내 장비가 맞는지 체크
		bool IsEquipment = true;
		if( pSkillInfo->mUseEquipment )
		{
			eWEAPON_STATE weaponState = pAttacker->GetWeaponState();

			/// 착용 장비 체크
			switch( pSkillInfo->mUseEquipment )
			{
			case eEQUIPTYPE_SWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD )
					IsEquipment = false;
				break;
			case eEQUIPTYPE_LONGSWORD:
				if( weaponState != eWEAPON_STATE_LONGSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHORTSWORD:
				if( weaponState != eWEAPON_STATE_SHORTSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_GUN:
				if( weaponState != eWEAPON_STATE_GUN )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_STAFF:
				if( weaponState != eWEAPON_STATE_STAFF )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHIELD:
				if( weaponState != eWEAPON_STATE_SHEILD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_LONGSWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_LONGSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_LONGSWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_LONGSWORD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_LONGSWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_LONGSWORD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHORTSWORD_GUN:
				if( weaponState != eWEAPON_STATE_SHORTSWORD &&
					weaponState != eWEAPON_STATE_GUN )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_CLOTH:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_ROBES )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_LIGHTARMOR:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_LIGHT_ARMOR )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_HEAVYARMOR:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_HEAVY_ARMOR )
					IsEquipment =  false;
				break;
			}
		}

		if( IsEquipment == false )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerObjectSkillRequest IsEquipment == false" );
			throw SKILL_USED_ERR_EQUIPMENT;
		}

		/// 캐릭터 상태
		if( pSkillInfo->mUseState != 0 ) // 모두
		{
			switch( pSkillInfo->mUseState )
			{
			case 1:	// HPUP
				if( pStepInfo->mUseStateValue > ( pAttacker->GetHP() * PERCENT_HUNDRED / pAttacker->GetMaxHP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			case 2: // HPDONW:	
				if( pStepInfo->mUseStateValue < ( pAttacker->GetHP() * PERCENT_HUNDRED / pAttacker->GetMaxHP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			case 3: // MPUP:		
				if( pStepInfo->mUseStateValue > ( pAttacker->GetMP() * PERCENT_HUNDRED / pAttacker->GetMaxMP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			case 4: // MPDOWN:	
				if( pStepInfo->mUseStateValue < ( pAttacker->GetMP() * PERCENT_HUNDRED / pAttacker->GetMaxMP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			default:
				assert(NULL);
				NETWORK2->PostServerEvent("PlayerObjectSkillRequest pSkillInfo->mUseState[%d]", pSkillInfo->mUseState );
				throw SKILL_USED_ERR_SCRIPT;
			}
		}


		/// 소모 MP 수치 체크
		if( pStepInfo->mUseMP > pAttacker->GetMP() )	
			throw SKILL_USED_ERR_USEVALUE;


		/// 소모 HP 수치 체크
		if( pStepInfo->mUseHP >= pAttacker->GetHP() )	
			throw SKILL_USED_ERR_USEVALUE;


		/// 소모아이템 IDX
		if( pStepInfo->mUseItemCount != 0 )
		{
			if( pAttacker->IsItemUse( pSkillInfo->mUseItem, pStepInfo->mUseItemCount ) == false )
				throw SKILL_USED_ERR_USEVALUE;
		}

		cBaseObject* pTarget = NULL;
		NiPoint3 targetPos = NiPoint3::ZERO;

		/// 타겟 사거리 체크
		switch( pSkillInfo->mBoundType )
		{
		case eBOUNDTYPE_SELF_NOTARGET:			/// 자신이 범위 시작위치여서 거리체크 안함	
			{
				/// 자신을 타겟으로 선정
				target = attacker;
				targetPos.x = pAttacker->GetXPos();
				targetPos.y = pAttacker->GetYPos();
				targetPos.z = pAttacker->Height();
				pTarget = pAttacker;
			}
			break;
		case eBOUNDTYPE_NONE:					/// 범위 없음
		case eBOUNDTYPE_TARGET:					/// 타겟 기준범위
		case eBOUNDTYPE_SELF_TARGET:			/// 시전자(자신) 기준범위 - 발동시 타겟 존재
			{

				/// 자신 일경우 거리체크 안함
				if( pSkillInfo->mApplyType == eAPPLYTYPE_SELF ) 
				{
					/// 자신을 타겟으로 선정
					target = attacker;
					targetPos.x = pAttacker->GetXPos();
					targetPos.y = pAttacker->GetYPos();
					targetPos.z = pAttacker->Height();
					pTarget = pAttacker;
					break;
				}

				/// 타겟이 있는 스킬의 사거리는 0이 될수 없다. - 스크립트 작성 오류
				if( pStepInfo->mTargetDist == 0 )
				{
					assert(NULL);
					NETWORK2->PostServerEvent( "PlayerObjectSkillRequest[%d] mTargetDist == 0", skillIdx );
					throw SKILL_USED_ERR_SCRIPT;
				}

				pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
				/// 타겟 객체 확인
				if( pTarget == NULL )	
					throw SKILL_USED_ERR_TARGETOBJECT;

				targetPos.x = pTarget->GetXPos();
				targetPos.y = pTarget->GetYPos();
				targetPos.z = pTarget->Height();

				/// 부활 스킬인지 체크
				bool isResurrection = false;

				if( pSkillInfo->mApplyType == eAPPLYTYPE_DIEBUDDY )
					isResurrection = true;

				/// 타겟 생존 유무 체크
				if( pTarget->GetStateDie() == true && isResurrection == false )
					throw SKILL_USED_ERR_TARGETOBJECT;
				else if( pTarget->GetStateDie() == false && isResurrection == true )
					throw SKILL_USED_ERR_TARGETOBJECT;

				/// 타겟 타입 체크
				switch( pSkillInfo->mApplyType ) 
				{
				case eAPPLYTYPE_SELF:
					break;
				case eAPPLYTYPE_ENEMY:
					{
						/// 타겟 조건 체크
						if( target.type != eOBJECTTYPE_MONSTER )
						{
							if( DUELMANAGER->IsAttack( pAttacker->GetDuelIdx(), attacker.index, target.index ) == false )
								throw SKILL_USED_ERR_TARGETSELECT;
						}
					}
					break;
				case eAPPLYTYPE_BUDDY:
					{
						/// pvp유저 빼해야함
						if( target.type != eOBJECTTYPE_PLAYER )
							throw SKILL_USED_ERR_TARGETSELECT;
					}
					break;
				case eAPPLYTYPE_PARTY:
					{
						/// 자신은 무조건 가능
						if( target.index == pAttacker->GetObjectID() && target.type == pAttacker->GetObjectType() )
							break;

						bool isParty = false;
						/// 타겟이 파티원인지 체크
						cParty* pParty = PARTYMAN->GetParty( ((cPlayer*)pAttacker)->GetPartyIndex() );
						if( pParty != NULL )
						{
							unsigned long* userArr = pParty->GetUserArr();
							unsigned int partyCount = pParty->GetCount();

							for( unsigned int i = 0 ; i < partyCount ; ++i )
							{
								/// 타겟이 파티원인지 확인
								if( target.index == userArr[i] )
								{
									isParty = true;
									break;
								}
							}
						}

						/// 타겟이 파티원이 아닌 경우
						if( isParty == false )
							throw SKILL_USED_ERR_TARGETSELECT;
					}
					break;
				}

				/// 스킬+아이템+효과 사거리
				float range = pAttacker->CalcStatusSkillRange( pStepInfo->mTargetDist, pSkillInfo->mRangeType, false );

				range = OBJECTMANAGER->ObjectSizeRange( pAttacker, pTarget, range );

				/// 스킬 사거리 밖에 타겟이 있다.
				cRangeCheck skillRangeCheck( range + SYNC_MOVE_RANGE );
				NiPoint3 attackerPos( pAttacker->GetXPos(), pAttacker->GetYPos(), pAttacker->Height() );

				if( !skillRangeCheck.IsRange( attackerPos, targetPos ) )	
				{ 
					HANDLE             handle = NULL;
					MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

					if ( posMsg != NULL )
					{
						posMsg->Category      = NM_SKILL;
						posMsg->Protocol      = NM_SKILL_USED_POS_ERROR_RES;
						posMsg->mAttacker     = attacker;
						posMsg->mAttackerPosX = pAttacker->GetXPos( );
						posMsg->mAttackerPosY = pAttacker->GetYPos( );
						posMsg->mTarget       = target;
						posMsg->mTargetPosX   = targetPos.x;
						posMsg->mTargetPosY   = targetPos.y;

						NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
					}

					throw SKILL_USED_ERR_TARGETDIST;
				}

				/// 공격자에게 타겟을 설정한다
				pAttacker->SetTarget( target.type, target.index );

			}break;
			/// 필드인경우는 이함수에 들어올수 없음
		case eBOUNDTYPE_FIELD:
		default:
			{
				/// 스크립트 내용이 잘못됐다.
				assert(NULL);
				NETWORK2->PostServerEvent("PlayerObjectSkillRequest mBoundType[%d,%d]", skillIdx, pSkillInfo->mBoundType );
				throw SKILL_USED_ERR_SCRIPT;
			}
		}	

		/// 플레이어가 목적지에 도착하기전에 공격메세지가 들어오는 경우 허용범위 계산
		mRangeCheck.SetRadius( SYNC_MOVE_RANGE );
		NiPoint2 clientPos( pMsg->mClientPosX, pMsg->mClientPosY );
		NiPoint2 serverPos = pAttacker->GetPos();

		if( mRangeCheck.IsRange( serverPos, clientPos ) == false )
		{
			/// 이동 중이던 상태 정지
			pAttacker->SetMoveTargetPos( serverPos.x, serverPos.y );

			HANDLE             handle = NULL;
			MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

			if ( posMsg != NULL )
			{
				posMsg->Category = NM_SKILL;
				posMsg->Protocol = NM_SKILL_USED_POS_ERROR_RES;
				posMsg->mAttacker = attacker;
				posMsg->mAttackerPosX = pAttacker->GetXPos( );
				posMsg->mAttackerPosY = pAttacker->GetYPos( );
				posMsg->mTarget = target;
				posMsg->mTargetPosX = targetPos.x;
				posMsg->mTargetPosY = targetPos.y;

				NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
			}

			throw SKILL_USED_ERR_ATTAKCERPOS;
		}

		unsigned long currentTime = NETWORK2->GetAccumTime();

		/// 쿨타임
		if( pStepInfo->mCoolTime != 0 && pPlayerHaveSkill->GetEndCoolTime() > currentTime )	
		{ 
			NETWORK2->PostServerEvent( "스킬:%d 재사용 오차 [%d - %d = %d]", skillIdx, pPlayerHaveSkill->GetEndCoolTime(), currentTime, pPlayerHaveSkill->GetEndCoolTime() - currentTime );
			Verbose->WriteLog( "스킬:%d 재사용 오차 [%d - %d = %d]", skillIdx, pPlayerHaveSkill->GetEndCoolTime(), currentTime, pPlayerHaveSkill->GetEndCoolTime() - currentTime );

			throw SKILL_USED_ERR_COOLTIME;
		}

		/// 고유키 생성
		unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
		if( skillUniqueIdx == 0 )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent( "PlayerObjectSkillRequest GeneratIdx[%d]", skillUniqueIdx );
			throw SKILL_USED_ERR_UNIQUEIDX;
		}

		/// 캐스팅을 하지 않는 스킬이면 스킬사용시 소모항목 감소
		if( casting == false )
		{
			/// MP를 소모 하는 스킬
			if( pStepInfo->mUseMP != 0 )
			{
				pAttacker->MPDamage( pStepInfo->mUseMP, false );
			}

			/// HP를 소모 하는 스킬
			if( pStepInfo->mUseHP != 0 )
			{
				/// HP 소모 & 메세지 발송
				bool die;
				pAttacker->HPDamage( pStepInfo->mUseHP, &die, true );
			}

			/// 소모아이템 IDX
			if( pStepInfo->mUseItemCount != 0 )
			{
				if( pAttacker->ItemUse( pSkillInfo->mUseItem, pStepInfo->mUseItemCount ) == false )
				{
					assert(NULL);
					NETWORK2->PostServerEvent("ERROR - item use skill itemidx[%d], itemcnd[%d]", pSkillInfo->mUseItem, pStepInfo->mUseItemCount );
				}
			}
		}


		/// 스킬 객체 생성
		cPlayerSkillObject* pSkillObject = new cPlayerSkillObject;
		bool init = pSkillObject->InitPlayerSkill( skillIdx, stepIdx, skillUniqueIdx, pAttacker, pTarget,  pStepInfo->mCastingTime );
		if( init == false )
		{ 
			/// 스킬 객체 생성 실패
			assert(NULL);
			SAFE_DELETE( pSkillObject );
			NETWORK2->PostServerEvent( "PlayerObjectSkillRequest new SkillObject" );		
			throw SKILL_USED_ERR_OBJECTINIT;			
		}

		/// 플레이어의 상태 체크한다.
		if( pAttacker->ChangeState( eOBJECT_STATE_ATTACK ) == false )
		{
			SAFE_DELETE( pSkillObject );
			/// 공격자의 상태가 공격 불가 상태
			throw SKILL_USED_ERR_ATTACKERSTATE;
		}

		/// 스킬을 리스트에 등록
		InsertPlayerSkillMap( characterIdx, pSkillObject );

		/// 스킬 생성 성공 메세지를 해당 히어로에게 전달
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			/// 캐스팅 여부에 따른 메세지 내용 정의
			if( casting == true )
				resMsg->Protocol = NM_SKILL_CAST_USED_RES;
			else
				resMsg->Protocol = NM_SKILL_USED_RES;

			resMsg->ErrorCode        = 0;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;
			resMsg->mSkillIndex      = skillUniqueIdx;
			resMsg->mObjectMP        = pAttacker->GetMP();
			resMsg->mMaxMP           = pAttacker->GetMaxMP();

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}

		/// 스킬 생성 메세지를 다른 클라이언트에게 전달
		MSG_SYN_SKILL_USED synMsg;
		synMsg.Category = NM_SKILL;
		/// 캐스팅 여부에 따른 메세지 내용 정의
		if( casting == true )
			synMsg.Protocol = NM_SKILL_CAST_USED_SYN;
		else
			synMsg.Protocol = NM_SKILL_USED_SYN;
		synMsg.mSkillClassIndex = skillIdx;
		synMsg.mSkillIndex = skillUniqueIdx;
		synMsg.mAttacker = attacker;
		synMsg.mAttackerPosX = pAttacker->GetXPos();
		synMsg.mAttackerPosY = pAttacker->GetYPos();
		synMsg.mTarget = target;
		synMsg.mTargetPosX = targetPos.x;
		synMsg.mTargetPosY = targetPos.y;
		synMsg.mObjectMP = pAttacker->GetMP();
		synMsg.mObjectMaxMP = pAttacker->GetMaxMP();
		NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );
	}
	catch ( int error )
	{
		/// 스킬 실패 메세지
		HANDLE handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			if( casting == true )
				resMsg->Protocol = NM_SKILL_CAST_USED_RES;
			else
				resMsg->Protocol = NM_SKILL_USED_RES;

			resMsg->ErrorCode        = error;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}
	}
}



void cSkillManager::PlayerPositionSkillRequest( unsigned long characterIdx, MSG_REQ_POS_USED* pMsg, bool casting )
{

	/// 이함수를 통해 들어오는 공격자의 타입은 플레이어 뿐이다.
	sObject attacker = { eOBJECTTYPE_PLAYER, characterIdx };

	float targetXPos = pMsg->mTargetPosX;
	float targetYPos = pMsg->mTargetPosY;
	unsigned long skillIdx = pMsg->mSkillClassIndex;
	unsigned long clientIdx = pMsg->mManagedKey;


	/// 공격자 객체 체크
	cPlayer* pAttacker = (cPlayer*)OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL )	
	{ 
		NETWORK2->PostServerEvent("PlayerPositionSkillRequest - GetObject( %d, %d )", attacker.type, attacker.index );
		assert(NULL);
		return; 
	}

	pAttacker->MoveStop();
	pAttacker->SetBlink( targetXPos, targetYPos );

	try
	{
		/// 기본 공격 스킬 인덱스인경우 오류
		if( skillIdx <= NORMAL_ATTACK_SKILL_MAX )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest - skillIdx[%d] > NORMAL_ATTACK_SKILL_MAX", skillIdx );
			throw SKILL_USED_ERR_SKILLTYPE;
		}


		/// 스킬 사용 불가
		if( pAttacker->IsCantSkill() == true )
			throw SKILL_USED_ERR_USEVALUE;


		if( pAttacker->GetChgMonsterIdx() != 0 )
			throw SKILL_USED_ERR_CHG_MONSTER;


		/// 이미 스킬 사용중인지 체크
		if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
			throw SKILL_USED_ERR_USING;


		/// 공격자에게 타겟을 설정한다
		pAttacker->SetTarget( eOBJECTTYPE_NONE, 0 );


		/// 플레이어가 목적지에 도착하기전에 공격메세지가 들어오는 경우 허용범위 계산
		mRangeCheck.SetRadius( SYNC_MOVE_RANGE );
		NiPoint2 clientPos( pMsg->mClientPosX, pMsg->mClientPosY );
		NiPoint2 serverPos( pAttacker->GetXPos(), pAttacker->GetYPos() );

		if( mRangeCheck.IsRange( serverPos, clientPos ) == false )
		{
			/// 이동 중이던 상태 정지
			pAttacker->SetMoveTargetPos( serverPos.x, serverPos.y );

			Verbose->WriteLog("sPos[%.2f,%.2f],cPos[%.2f,%.2f] = [%.2f]", serverPos.x, serverPos.y,
				clientPos.x, clientPos.y, (serverPos - clientPos).Length() );

			HANDLE             handle = NULL;
			MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

			if ( posMsg != NULL )
			{
				posMsg->Category      = NM_SKILL;
				posMsg->Protocol      = NM_SKILL_USED_POS_ERROR_RES;
				posMsg->mAttacker     = attacker;
				posMsg->mAttackerPosX = pAttacker->GetXPos( );
				posMsg->mAttackerPosY = pAttacker->GetYPos( );
				posMsg->mTarget.type  = eOBJECTTYPE_NONE;
				posMsg->mTargetPosX   = 0.0f;
				posMsg->mTargetPosY   = 0.0f;

				NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
			}

			throw SKILL_USED_ERR_ATTAKCERPOS;
		}


		/// 스킬index 체크
		sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
		if( pSkillInfo == NULL )	
		{ 
			/// 스킬 스크립트 데이터 획득 오류
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest skillScript[%d]", skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}


		if( pSkillInfo->mShotType == eSHOTTYPE_CASTING && casting != true )
		{
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest[%d] ShotType != eSHOTTYPE_CASTING[%d]", characterIdx, skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}


		if( pSkillInfo->mShotType != eSHOTTYPE_CASTING && casting == true )
		{
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest[%d] ShotType == eSHOTTYPE_CASTING[%d]", characterIdx, skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}


		/// 액티브 스킬이 아닌 경우
		if( pSkillInfo->mType == eSKILLTYPE_PASSIVE )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest eSKILLTYPE_PASSIVE[%d,%d]", skillIdx, pSkillInfo->mType );
			throw SKILL_USED_ERR_PASSIVE;
		}


		/// 공격자의 보유스킬인지 체크
		cHaveSkillObject* pPlayerHaveSkill = GetPlayerHaveSkill( attacker.index, skillIdx );
		if( !pPlayerHaveSkill )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest GetPlayerHaveSkill[%d,%d]", attacker.index, NORMAL_ATTACK_SKILL );
			throw SKILL_USED_ERR_DONTHAVE;
		}


		/// 스킬 스크립트 스텝정보 읽어옴
		unsigned int stepIdx = pPlayerHaveSkill->GetSkillStep();
		sPlayerSkillStepInfo* pStepInfo = &pSkillInfo->mpSetpInfoArray[stepIdx];

		/// 내 장비가 맞는지 체크
		bool IsEquipment = true;
		if( pSkillInfo->mUseEquipment )
		{
			eWEAPON_STATE weaponState = pAttacker->GetWeaponState();

			/// 착용 장비 체크
			switch( pSkillInfo->mUseEquipment )
			{
			case eEQUIPTYPE_SWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD )
					IsEquipment = false;
				break;
			case eEQUIPTYPE_LONGSWORD:
				if( weaponState != eWEAPON_STATE_LONGSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHORTSWORD:
				if( weaponState != eWEAPON_STATE_SHORTSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_GUN:
				if( weaponState != eWEAPON_STATE_GUN )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_STAFF:
				if( weaponState != eWEAPON_STATE_STAFF )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHIELD:
				if( weaponState != eWEAPON_STATE_SHEILD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_LONGSWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_LONGSWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_LONGSWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_LONGSWORD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SWORD_LONGSWORD_DOUBLESWORD:
				if( weaponState != eWEAPON_STATE_SWORD &&
					weaponState != eWEAPON_STATE_SWORD_SHEILD &&
					weaponState != eWEAPON_STATE_LONGSWORD &&
					weaponState != eWEAPON_STATE_DOUBLESWORD )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_SHORTSWORD_GUN:
				if( weaponState != eWEAPON_STATE_SHORTSWORD &&
					weaponState != eWEAPON_STATE_GUN )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_CLOTH:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_ROBES )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_LIGHTARMOR:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_LIGHT_ARMOR )
					IsEquipment =  false;
				break;
			case eEQUIPTYPE_HEAVYARMOR:
				if( pAttacker->GetArmorSets() != ARMOR_SETS_HEAVY_ARMOR )
					IsEquipment =  false;
				break;
			}
		}

		if( IsEquipment == false )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerPositionSkillRequest IsEquipment == false" );
			throw SKILL_USED_ERR_EQUIPMENT;
		}

		/// 캐릭터 상태
		if( pSkillInfo->mUseState != 0 ) // 모두
		{
			switch( pSkillInfo->mUseState )
			{
			case 1:	// HPUP
				if( pStepInfo->mUseStateValue > ( pAttacker->GetHP() * PERCENT_HUNDRED / pAttacker->GetMaxHP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			case 2: // HPDONW:	
				if( pStepInfo->mUseStateValue < ( pAttacker->GetHP() * PERCENT_HUNDRED / pAttacker->GetMaxHP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			case 3: // MPUP:		
				if( pStepInfo->mUseStateValue > ( pAttacker->GetMP() * PERCENT_HUNDRED / pAttacker->GetMaxMP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			case 4: // MPDOWN:	
				if( pStepInfo->mUseStateValue < ( pAttacker->GetMP() * PERCENT_HUNDRED / pAttacker->GetMaxMP() ) )	
				{ 
					throw SKILL_USED_ERR_USEVALUE;
				}break;
			default:
				assert(NULL);
				NETWORK2->PostServerEvent("PlayerPositionSkillRequest pSkillInfo->mUseState[%d]", pSkillInfo->mUseState );
				throw SKILL_USED_ERR_SCRIPT;
			}
		}


		/// 소모 MP
		if( pStepInfo->mUseMP > pAttacker->GetMP() )	
			throw SKILL_USED_ERR_USEVALUE;


		/// 소모 HP
		if( pStepInfo->mUseHP >= pAttacker->GetHP() )	
			throw SKILL_USED_ERR_USEVALUE;


		/// 소모아이템 IDX
		if( pStepInfo->mUseItemCount != 0 )
		{
			if( pAttacker->IsItemUse( pSkillInfo->mUseItem, pStepInfo->mUseItemCount ) == false )
				throw SKILL_USED_ERR_USEVALUE;
		}


		/// 타겟 사거리 체크
		switch( pSkillInfo->mBoundType )
		{
			/// 필드인경우만 이함수에 들어옴
		case eBOUNDTYPE_FIELD:
			{
				NiPoint3 targetPos( targetXPos, targetYPos, 0.0f );
				AIMANAGER->CalcHeight( pAttacker->GetMapNumber(), &targetPos.z, targetXPos, targetYPos );

				NiPoint3 attackerPos( pAttacker->GetXPos(), pAttacker->GetYPos(), pAttacker->Height() );

				/// 스킬+아이템+효과 사거리
				float range = pAttacker->CalcStatusSkillRange( pStepInfo->mTargetDist, pSkillInfo->mRangeType, false );

				range = OBJECTMANAGER->ObjectSizeRange( pAttacker, NULL, range );

				/// 스킬 사거리 밖에 타겟이 있다.
				cRangeCheck skillRangeCheck( range + SYNC_MOVE_RANGE );
				if( !skillRangeCheck.IsRange( attackerPos, targetPos ) )
				{ 
					HANDLE             handle = NULL;
					MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

					if ( posMsg != NULL )
					{
						posMsg->Category      = NM_SKILL;
						posMsg->Protocol      = NM_SKILL_USED_POS_ERROR_RES;
						posMsg->mAttacker     = attacker;
						posMsg->mAttackerPosX = pAttacker->GetXPos();
						posMsg->mAttackerPosY = pAttacker->GetYPos();
						posMsg->mTarget.type  = eOBJECTTYPE_NONE;
						posMsg->mTargetPosX   = 0.0f;
						posMsg->mTargetPosY   = 0.0f;

						NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
					}

					throw SKILL_USED_ERR_TARGETDIST;
				}

			}break;
		default:
			{
				/// 스크립트 내용이 잘못됐다.
				assert(NULL);
				NETWORK2->PostServerEvent("PlayerPositionSkillRequest pSkillInfo->mBoundType[%d]", pSkillInfo->mBoundType );
				throw SKILL_USED_ERR_SCRIPT;
			}
		}	


		unsigned long currentTime = NETWORK2->GetAccumTime();

		/// 쿨타임
		if( pStepInfo->mCoolTime != 0 && pPlayerHaveSkill->GetEndCoolTime() > currentTime )	
		{ 
			NETWORK2->PostServerEvent( "스킬:%d 재사용 오차 [%d - %d = %d]", skillIdx, pPlayerHaveSkill->GetEndCoolTime(), currentTime, pPlayerHaveSkill->GetEndCoolTime() - currentTime );
			Verbose->WriteLog( "스킬:%d 재사용 오차 [%d - %d = %d]", skillIdx, pPlayerHaveSkill->GetEndCoolTime(), currentTime, pPlayerHaveSkill->GetEndCoolTime() - currentTime );

			throw SKILL_USED_ERR_COOLTIME;
		}


		/// 고유키 생성
		unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
		if( skillUniqueIdx == 0 )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent( "PlayerPositionSkillRequest GeneratIdx[%d]", skillUniqueIdx );
			throw SKILL_USED_ERR_UNIQUEIDX;
		}

		/// 캐스팅을 하지 않는 스킬이면 스킬사용시 소모항목 감소
		if( casting == false )
		{
			/// MP를 소모 하는 스킬
			if( pStepInfo->mUseMP != 0 )
				pAttacker->MPDamage( pStepInfo->mUseMP, false );

			/// HP를 소모 하는 스킬
			if( pStepInfo->mUseHP != 0 )
			{
				/// HP 소모 & 메세지 발송
				bool die;
				pAttacker->HPDamage( pStepInfo->mUseHP, &die, true );
			}


			/// 소모아이템 IDX
			if( pStepInfo->mUseItemCount != 0 )
			{
				if( pAttacker->ItemUse( pSkillInfo->mUseItem, pStepInfo->mUseItemCount ) == false )
				{
					assert(NULL);
					NETWORK2->PostServerEvent("ERROR - item use skill itemidx[%d], itemcnd[%d]", pSkillInfo->mUseItem, pStepInfo->mUseItemCount );
				}
			}
		}


		/// 스킬 객체 생성
		cPlayerSkillObject* pSkillObject = new cPlayerSkillObject;
		bool init = pSkillObject->InitPlayerSkillPosition( characterIdx, targetXPos, targetYPos, skillIdx,
			skillUniqueIdx, casting, pAttacker->GetMapNumber() );
		if( init == false )
		{ 
			/// 스킬 객체 생성 실패
			assert(NULL);
			SAFE_DELETE( pSkillObject );
			NETWORK2->PostServerEvent( "PlayerPositionSkillRequest new SkillObject" );
			throw SKILL_USED_ERR_OBJECTINIT;
		}

		/// 플레이어의 상태 체크한다.
		if( pAttacker->ChangeState( eOBJECT_STATE_ATTACK ) == false )
		{
			/// 공격자의 상태가 공격 불가 상태
			assert(NULL);
			NETWORK2->PostServerEvent( "PlayerPositionSkillRequest ChangeState ERROR" );
			throw SKILL_USED_ERR_ATTACKERSTATE;
		}


		/// 스킬 리스트에 등록
		InsertPlayerSkillMap( characterIdx, pSkillObject );


		/// 스킬 생성 성공 메세지를 해당 히어로에게 전달
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ), NM_SKILL );

		if ( resMsg != NULL )
		{
			/// 캐스팅 여부에 따른 메세지 내용 정의
			if( casting == true )
				resMsg->Protocol = NM_SKILL_POS_CAST_USED_RES;
			else
				resMsg->Protocol = NM_SKILL_POS_USED_RES;

			resMsg->ErrorCode        = 0;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;
			resMsg->mSkillIndex      = skillUniqueIdx;
			resMsg->mObjectMP        = pAttacker->GetMP();
			resMsg->mMaxMP           = pAttacker->GetMaxMP();

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}

		/// 스킬 생성 메세지를 다른 클라이언트에게 전달
		MSG_SYN_POS_USED synMsg;
		synMsg.Category = NM_SKILL;
		/// 캐스팅 여부에 따른 메세지 내용 정의
		if( casting == true )
			synMsg.Protocol = NM_SKILL_POS_CAST_USED_SYN;
		else
			synMsg.Protocol = NM_SKILL_POS_USED_SYN;
		synMsg.mSkillClassIndex = skillIdx;
		synMsg.mSkillIndex = skillUniqueIdx;
		synMsg.mAttacker = attacker;
		synMsg.mAttackerPosX = pAttacker->GetXPos();
		synMsg.mAttackerPosY = pAttacker->GetYPos();
		synMsg.mTargetPosX = targetXPos;
		synMsg.mTargetPosY = targetYPos;
		synMsg.mObjectMP = pAttacker->GetMP();
		synMsg.mObjectMaxMP = pAttacker->GetMaxMP();
		NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );
	}
	catch( int error )
	{
		/// 스킬 실패 메세지
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ), NM_SKILL );
		if ( resMsg != NULL )
		{
			if( casting == true )
				resMsg->Protocol = NM_SKILL_CAST_USED_RES;
			else
				resMsg->Protocol = NM_SKILL_USED_RES;

			resMsg->ErrorCode        = error;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}
	}
}


void cSkillManager::PlayerMonSkillRequest( unsigned long characterIdx, MSG_REQ_SKILL_USED* pMsg, bool casting )
{
	eMONSTERATTACK_TYPE attackType = (eMONSTERATTACK_TYPE)pMsg->mSkillClassIndex;

	/// 이함수를 통해 들어오는 유저의 타입은 플레이어 뿐이다.
	sObject attacker = { eOBJECTTYPE_PLAYER, characterIdx };

	sObject target = pMsg->mTarget;
	unsigned long clientIdx = pMsg->mManagedKey;

	/// 서버에는 hero가 없다
	if( target.type == eOBJECTTYPE_HERO )
		target.type = eOBJECTTYPE_PLAYER;

	/// 공격자 객체 체크
	cPlayer* pAttacker = (cPlayer*)OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL )	
	{ 
		NETWORK2->PostServerEvent("PlayerMonSkillRequest - GetObject( %d, %d )", attacker.type, attacker.index );
		assert(NULL);
		return; 
	}

	unsigned long monsterClassIdx = pAttacker->GetChgMonsterIdx();

	pAttacker->MoveStop();

	try
	{
		/// 기본 공격 스킬 인덱스인경우 오류
		if( attackType >= eMONSTERATTACK_MAX )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("PlayerMonSkillRequest - attackType[%d] > eMONSTERATTACK_MAX", attackType );
			throw SKILL_USED_ERR_SKILLTYPE;
		}


		/// 스킬 사용 불가
		if( pAttacker->IsCantSkill() == true )
			throw SKILL_USED_ERR_USEVALUE;


		if( pAttacker->GetChgMonsterIdx() == 0 )
			throw SKILL_USED_ERR_CHG_MONSTER;


		/// 스킬 사용중인지 체크
		if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
			throw SKILL_USED_ERR_USING;


		/// 스킬index 체크
		sMonsterSkillScript* pSkillInfo = SKILLSCRIPT->GetMonsterSkillInfo( monsterClassIdx, attackType );
		if( pSkillInfo == NULL )	
		{ 
			/// 스킬 스크립트 데이터 획득 오류
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerMonSkillRequest skillScript[%d,%d]", monsterClassIdx, attackType );
			throw SKILL_USED_ERR_SCRIPT;
		}


		if( pSkillInfo->mShotType == eSHOTTYPE_CASTING && casting != true )
		{
			NETWORK2->PostServerEvent("PlayerMonSkillRequest[%d,%d] ShotType != eSHOTTYPE_CASTING[%d]", characterIdx, monsterClassIdx, attackType );
			throw SKILL_USED_ERR_SCRIPT;
		}


		if( pSkillInfo->mShotType != eSHOTTYPE_CASTING && casting == true )
		{
			assert(NULL); 
			NETWORK2->PostServerEvent("PlayerMonSkillRequest[%d,%d] ShotType == eSHOTTYPE_CASTING[%d]", characterIdx, monsterClassIdx, attackType );
			throw SKILL_USED_ERR_SCRIPT;
		}


		/// 소모 MP 수치 체크
		if( pSkillInfo->mUseMP > pAttacker->GetMP() )	
			throw SKILL_USED_ERR_USEVALUE;


		cBaseObject* pTarget = NULL;
		NiPoint3 targetPos = NiPoint3::ZERO;


		/// 타겟 사거리 체크
		switch( pSkillInfo->mBoundType )
		{
		case eBOUNDTYPE_SELF_NOTARGET:			/// 자신이 범위 시작위치여서 거리체크 안함	
			break;
		case eBOUNDTYPE_NONE:			/// 범위 없음
		case eBOUNDTYPE_TARGET:			/// 타겟 기준범위
		case eBOUNDTYPE_SELF_TARGET:	/// 시전자(자신) 기준범위 - 발동시 타겟 존재
			{

				/// 자신 일경우 거리체크 안함
				if( pSkillInfo->mApplyType == eAPPLYTYPE_SELF ) 
				{
					/// 자신을 타겟으로 선정
					target = attacker;
					targetPos.x = pAttacker->GetXPos();
					targetPos.y = pAttacker->GetYPos();
					targetPos.z = pAttacker->Height();
					pTarget = pAttacker;
					break;
				}

				/// 타겟이 있는 스킬의 사거리는 0이 될수 없다. - 스크립트 작성 오류
				if( pSkillInfo->mTargetDist == 0 )
				{
					assert(NULL);
					NETWORK2->PostServerEvent( "PlayerMonSkillRequest pStepInfo[%d,%d]->mTargetDist == 0", monsterClassIdx, attackType );
					throw SKILL_USED_ERR_SCRIPT;
				}

				pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
				/// 타겟 객체 확인
				if( pTarget == NULL )	
					throw SKILL_USED_ERR_TARGETOBJECT;

				targetPos.x = pTarget->GetXPos();
				targetPos.y = pTarget->GetYPos();
				targetPos.z = pTarget->Height();

				/// 타겟 생존 유무 체크
				if( pTarget->GetStateDie() == true  )
					throw SKILL_USED_ERR_TARGETOBJECT;

				/// 타겟 타입 체크
				switch( pSkillInfo->mApplyType ) 
				{
				case eAPPLYTYPE_SELF:
					break;
				case eAPPLYTYPE_ENEMY:
					{
						/// 타겟 조건 체크
						if( target.type != eOBJECTTYPE_MONSTER )
						{
							if( DUELMANAGER->IsAttack( pAttacker->GetDuelIdx(), attacker.index, target.index ) == false )
								throw SKILL_USED_ERR_TARGETSELECT;
						}
					}
					break;
				case eAPPLYTYPE_BUDDY:
					{
						/// pvp유저 빼해야함
						if( target.type != eOBJECTTYPE_PLAYER )
							throw SKILL_USED_ERR_TARGETSELECT;
					}
					break;
				}

				/// 스킬+아이템+효과 사거리
				float range = pAttacker->CalcStatusSkillRange( pSkillInfo->mTargetDist, pSkillInfo->mRangeType, false );

				range = OBJECTMANAGER->ObjectSizeRange( pAttacker, pTarget, range );

				/// 스킬 사거리 밖에 타겟이 있다.
				cRangeCheck skillRangeCheck( range + SYNC_MOVE_RANGE );
				NiPoint3 attackerPos( pAttacker->GetXPos(), pAttacker->GetYPos(), pAttacker->Height() );

				if( !skillRangeCheck.IsRange( attackerPos, targetPos ) )	
				{ 
					HANDLE             handle = NULL;
					MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

					if ( posMsg != NULL )
					{
						posMsg->Category      = NM_SKILL;
						posMsg->Protocol      = NM_SKILL_USED_POS_ERROR_RES;
						posMsg->mAttacker     = attacker;
						posMsg->mAttackerPosX = pAttacker->GetXPos( );
						posMsg->mAttackerPosY = pAttacker->GetYPos( );
						posMsg->mTarget       = target;
						posMsg->mTargetPosX   = targetPos.x;
						posMsg->mTargetPosY   = targetPos.y;

						NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
					}

					throw SKILL_USED_ERR_TARGETDIST;
				}

				/// 공격자에게 타겟을 설정한다
				pAttacker->SetTarget( target.type, target.index );

			}break;
			/// 필드인경우는 이함수에 들어올수 없음
		case eBOUNDTYPE_FIELD:
		default:
			{
				/// 스크립트 내용이 잘못됐다.
				assert(NULL);
				NETWORK2->PostServerEvent("PlayerMonSkillRequest mBoundType[%d,%d]", monsterClassIdx, pSkillInfo->mBoundType );
				throw SKILL_USED_ERR_SCRIPT;
			}
		}	


		/// 플레이어가 목적지에 도착하기전에 공격메세지가 들어오는 경우 허용범위 계산
		mRangeCheck.SetRadius( SYNC_MOVE_RANGE );
		NiPoint2 clientPos( pMsg->mClientPosX, pMsg->mClientPosY );
		NiPoint2 serverPos = pAttacker->GetPos();


		if( mRangeCheck.IsRange( serverPos, clientPos ) == false )
		{
			/// 이동 중이던 상태 정지
			pAttacker->SetMoveTargetPos( serverPos.x, serverPos.y );

			HANDLE             handle = NULL;
			MSG_RES_SKILL_POS* posMsg = (MSG_RES_SKILL_POS*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx( ) );

			if ( posMsg != NULL )
			{
				posMsg->Category = NM_SKILL;
				posMsg->Protocol = NM_SKILL_USED_POS_ERROR_RES;
				posMsg->mAttacker = attacker;
				posMsg->mAttackerPosX = pAttacker->GetXPos( );
				posMsg->mAttackerPosY = pAttacker->GetYPos( );
				posMsg->mTarget = target;
				posMsg->mTargetPosX = targetPos.x;
				posMsg->mTargetPosY = targetPos.y;

				NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_POS) );
			}

			throw SKILL_USED_ERR_ATTAKCERPOS;
		}

		if( pAttacker->IsChgMonCoolTimeEnd( attackType ) == false )
			throw SKILL_USED_ERR_COOLTIME;


		/// 고유키 생성
		unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
		if( skillUniqueIdx == 0 )	
		{ 
			assert(NULL);
			NETWORK2->PostServerEvent( "PlayerPositionSkillRequest GeneratIdx[%d]", skillUniqueIdx );
			throw SKILL_USED_ERR_UNIQUEIDX;
		}


		/// 캐스팅을 하지 않는 스킬이면 스킬사용시 소모항목 감소
		if( casting == false )
		{
			/// MP를 소모 하는 스킬
			if( pSkillInfo->mUseMP != 0 )
			{
				pAttacker->MPDamage( pSkillInfo->mUseMP, false );
			}
		}


		/// 스킬 객체 생성
		cPlayerMonSkillObject* pSkillObject = new cPlayerMonSkillObject;
		bool init = pSkillObject->InitPlayerMonAttack( characterIdx, pAttacker->GetChgMonsterIdx(), 
			attackType, target, skillUniqueIdx, casting, 
			pAttacker->GetStatus2()->mAttackSpeed,	pAttacker->GetMapNumber() );
		if( init == false )
		{ 
			/// 스킬 객체 생성 실패
			assert(NULL);
			SAFE_DELETE( pSkillObject );
			NETWORK2->PostServerEvent( "PlayerObjectSkillRequest new SkillObject" );		
			throw SKILL_USED_ERR_OBJECTINIT;			
		}


		/// 플레이어의 상태 체크한다.
		if( pAttacker->ChangeState( eOBJECT_STATE_ATTACK ) == false )
		{
			SAFE_DELETE( pSkillObject );
			/// 공격자의 상태가 공격 불가 상태
			throw SKILL_USED_ERR_ATTACKERSTATE;
		}


		/// 스킬을 리스트에 등록
		InsertPlayerSkillMap( characterIdx, pSkillObject );


		/// 스킬 생성 성공 메세지를 해당 히어로에게 전달
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			/// 캐스팅 여부에 따른 메세지 내용 정의
			if( casting == true )
				resMsg->Protocol = NM_SKILL_CAST_USED_MON_RES;
			else
				resMsg->Protocol = NM_SKILL_USED_MON_RES;

			resMsg->ErrorCode        = 0;
			resMsg->mSkillClassIndex = pMsg->mSkillClassIndex;
			resMsg->mManagedKey      = pMsg->mManagedKey;
			resMsg->mSkillIndex      = skillUniqueIdx;
			resMsg->mObjectMP        = pAttacker->GetMP();
			resMsg->mMaxMP           = pAttacker->GetMaxMP();

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}


		/// 스킬 생성 메세지를 다른 클라이언트에게 전달
		MSG_SYN_SKILL_USED synMsg;
		synMsg.Category = NM_SKILL;
		/// 캐스팅 여부에 따른 메세지 내용 정의
		if( casting == true )
			synMsg.Protocol = NM_SKILL_CAST_USED_SYN;
		else
			synMsg.Protocol = NM_SKILL_USED_SYN;
		synMsg.mSkillClassIndex = attackType;
		synMsg.mSkillIndex = skillUniqueIdx;
		synMsg.mAttacker = attacker;
		synMsg.mAttackerPosX = pAttacker->GetXPos();
		synMsg.mAttackerPosY = pAttacker->GetYPos();
		synMsg.mTarget = target;
		synMsg.mTargetPosX = targetPos.x;
		synMsg.mTargetPosY = targetPos.y;
		synMsg.mObjectMP = pAttacker->GetMP();
		synMsg.mObjectMaxMP = pAttacker->GetMaxMP();
		NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );
	}
	catch ( int error )
	{
		/// 스킬 실패 메세지
		HANDLE handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			if( casting == true )
				resMsg->Protocol = NM_SKILL_CAST_USED_MON_RES;
			else
				resMsg->Protocol = NM_SKILL_USED_MON_RES;

			resMsg->ErrorCode        = error;
			resMsg->mSkillClassIndex = attackType;
			resMsg->mManagedKey      = clientIdx;

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}
	}
}


void cSkillManager::MapChangeSkillRequest( unsigned long characterIdx, MSG_REQ_SKILL_ITEMMAPCHANGE* pMsg )
{
	/// 이함수를 통해 들어오는 유저의 타입은 플레이어 뿐이다.
	sObject attacker = { eOBJECTTYPE_PLAYER, characterIdx };

	sObject target = pMsg->mTarget;
	unsigned long skillIdx = pMsg->mSkillClassIndex;
	unsigned long clientIdx = pMsg->mManagedKey;

	/// 서버에는 hero가 없다
	if( target.type == eOBJECTTYPE_HERO )
		target.type = eOBJECTTYPE_PLAYER;

	/// 공격자 객체 체크
	cPlayer* pAttacker = (cPlayer*)OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::MapChangeSkillRequest - GetObject( %d, %d )", attacker.type, attacker.index );
		return; 
	}

	pAttacker->MoveStop();

	try
	{
		/// 스킬 사용중인지 체크
		if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
			throw SKILL_USED_ERR_USING;

		/// 상태 체크
		if( pAttacker->IsChangeState( eOBJECT_STATE_ATTACK ) == false )
			throw SKILL_USED_ERR_ATTACKERSTATE;

		/// 스킬index 체크
		sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
		if( pSkillInfo == NULL )	
		{ 
			/// 스킬 스크립트 데이터 획득 오류
			NETWORK2->PostServerEvent("cSkillManager::MapChangeSkillRequest skillScript[%d]", skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}
		sPlayerSkillStepInfo* pStepInfo = &pSkillInfo->mpSetpInfoArray[0];

		/// inventory 검증..
		long useItemDBIdx = pAttacker->IsItemMapChange( pMsg->mSlotNumber, pSkillInfo->mUseItem, skillIdx );
		if( useItemDBIdx == 0 )
			throw SKILL_USED_ERR_USEVALUE;

		/// 맵이동 가능 체크
		if( pStepInfo->mApplyValueType1 == eSTATUSPLUS_MAPCHANGE )
		{
			if( pAttacker->IsItemMapChange( pStepInfo->mApplyValue1 ) == false )
				throw SKILL_USED_ERR_ATTACKERSTATE;
		}
		else if( pStepInfo->mApplyValueType2 == eSTATUSPLUS_MAPCHANGE )
		{
			if( pAttacker->IsItemMapChange( pStepInfo->mApplyValue2 ) == false )
				throw SKILL_USED_ERR_ATTACKERSTATE;
		}
		else
			throw SKILL_USED_ERR_ATTACKERSTATE;

		/// 고유키 생성
		unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
		if( skillUniqueIdx == 0 )	
		{ 
			NETWORK2->PostServerEvent( "cSkillManager::MapChangeSkillRequest GeneratIdx[%d]", skillUniqueIdx );
			throw SKILL_USED_ERR_UNIQUEIDX;
		}

		/// 스킬 객체 생성
		cPlayerSkillObject* pSkillObject = new cPlayerSkillObject;
		bool init = pSkillObject->InitPlayerMapChange( skillIdx, skillUniqueIdx, pAttacker, useItemDBIdx, pStepInfo->mCastingTime );
		if( init == false )
		{ 
			/// 스킬 객체 생성 실패
			SAFE_DELETE( pSkillObject );
			NETWORK2->PostServerEvent( "cSkillManager::MapChangeSkillRequest new SkillObject" );		
			throw SKILL_USED_ERR_OBJECTINIT;			
		}

		/// 플레이어의 상태 변경
		pAttacker->ChangeState( eOBJECT_STATE_ATTACK );

		/// 스킬을 리스트에 등록
		InsertPlayerSkillMap( characterIdx, pSkillObject );

		/// 스킬 생성 성공 메세지를 해당 히어로에게 전달
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			/// 캐스팅 여부에 따른 메세지 내용 정의
			resMsg->Protocol = NM_SKILL_ITEMMAPCHANGE_RES;
			resMsg->ErrorCode        = 0;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;
			resMsg->mSkillIndex      = skillUniqueIdx;
			resMsg->mObjectMP        = pAttacker->GetMP();
			resMsg->mMaxMP           = pAttacker->GetMaxMP();

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}

		/// 스킬 생성 메세지를 다른 클라이언트에게 전달
		MSG_SYN_SKILL_USED synMsg;
		synMsg.Category = NM_SKILL;
		synMsg.Protocol = NM_SKILL_ITEMMAPCHANGE_SYN;
		synMsg.mSkillClassIndex = skillIdx;
		synMsg.mSkillIndex = skillUniqueIdx;
		synMsg.mAttacker = attacker;
		synMsg.mAttackerPosX = pAttacker->GetXPos();
		synMsg.mAttackerPosY = pAttacker->GetYPos();
		synMsg.mTarget = target;
		synMsg.mTargetPosX = pAttacker->GetXPos();
		synMsg.mTargetPosY = pAttacker->GetYPos();
		synMsg.mObjectMP = pAttacker->GetMP();
		synMsg.mObjectMaxMP = pAttacker->GetMaxMP();
		NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );
	}
	catch ( int error )
	{
		/// 스킬 실패 메세지
		HANDLE handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			resMsg->Protocol = NM_SKILL;
			resMsg->Protocol = NM_SKILL_ITEMMAPCHANGE_RES;
			resMsg->ErrorCode        = error;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}
	}
}


void cSkillManager::VehiCleSkillRequest( unsigned long characterIdx, MSG_REQ_SKILL_VEHICLE* pMsg )
{
	/// 이함수를 통해 들어오는 유저의 타입은 플레이어 뿐이다.
	sObject attacker = { eOBJECTTYPE_PLAYER, characterIdx };

	sObject target = pMsg->mTarget;
	unsigned long skillIdx = pMsg->mSkillClassIndex;
	unsigned long clientIdx = pMsg->mManagedKey;

	/// 서버에는 hero가 없다
	if( target.type == eOBJECTTYPE_HERO )
		target.type = eOBJECTTYPE_PLAYER;

	/// 공격자 객체 체크
	cPlayer* pAttacker = (cPlayer*)OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::VehicleSkillRequest - GetObject( %d, %d )", attacker.type, attacker.index );
		return; 
	}

	pAttacker->MoveStop();

	try
	{
		/// 스킬 사용중인지 체크
		if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
			throw SKILL_USED_ERR_USING;

		/// 상태 체크
		if( pAttacker->IsChangeState( eOBJECT_STATE_ATTACK ) == false )
			throw SKILL_USED_ERR_ATTACKERSTATE;

		/// 스킬index 체크
		sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
		if( pSkillInfo == NULL )	
		{ 
			/// 스킬 스크립트 데이터 획득 오류
			NETWORK2->PostServerEvent("cSkillManager::VehicleSkillRequest skillScript[%d]", skillIdx );
			throw SKILL_USED_ERR_SCRIPT;
		}
		sPlayerSkillStepInfo* pStepInfo = &pSkillInfo->mpSetpInfoArray[0];

		/// inventory 검증..
		long useItemDBIdx = pAttacker->IsItemVehicle( pMsg->mSlotNumber, pSkillInfo->mUseItem, skillIdx );
		if( useItemDBIdx == 0 )
			throw SKILL_USED_ERR_USEVALUE;

		/// 몬스터 변신 상태이면 사용 불가
		if( pAttacker->GetChgMonsterIdx() != 0 )
			throw SKILL_USED_ERR_ATTACKERSTATE;

		/// 고유키 생성
		unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
		if( skillUniqueIdx == 0 )	
		{ 
			NETWORK2->PostServerEvent( "cSkillManager::VehicleSkillRequest GeneratIdx[%d]", skillUniqueIdx );
			throw SKILL_USED_ERR_UNIQUEIDX;
		}

		/// 스킬 객체 생성
		cPlayerSkillObject* pSkillObject = new cPlayerSkillObject;
		bool init = pSkillObject->InitPlayerVehicle( skillIdx, skillUniqueIdx, pAttacker, pSkillInfo->mUseItem, pStepInfo->mCastingTime );
		if( init == false )
		{ 
			/// 스킬 객체 생성 실패
			SAFE_DELETE( pSkillObject );
			NETWORK2->PostServerEvent( "cSkillManager::VehicleSkillRequest new SkillObject" );		
			throw SKILL_USED_ERR_OBJECTINIT;			
		}

		/// 플레이어의 상태 변경
		pAttacker->ChangeState( eOBJECT_STATE_ATTACK );

		/// 스킬을 리스트에 등록
		InsertPlayerSkillMap( characterIdx, pSkillObject );

		/// 스킬 생성 성공 메세지를 해당 히어로에게 전달
		HANDLE              handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			/// 캐스팅 여부에 따른 메세지 내용 정의
			resMsg->Protocol = NM_SKILL_ITEMVEHICLE_RES;
			resMsg->ErrorCode        = SKILL_USED_ERR_SUCCESS;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;
			resMsg->mSkillIndex      = skillUniqueIdx;
			resMsg->mObjectMP        = pAttacker->GetMP();
			resMsg->mMaxMP           = pAttacker->GetMaxMP();

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}

		/// 스킬 생성 메세지를 다른 클라이언트에게 전달
		MSG_SYN_SKILL_USED synMsg;
		synMsg.Category = NM_SKILL;
		synMsg.Protocol = NM_SKILL_ITEMVEHICLE_SYN;
		synMsg.mSkillClassIndex = skillIdx;
		synMsg.mSkillIndex = skillUniqueIdx;
		synMsg.mAttacker = attacker;
		synMsg.mAttackerPosX = pAttacker->GetXPos();
		synMsg.mAttackerPosY = pAttacker->GetYPos();
		synMsg.mTarget = target;
		synMsg.mTargetPosX = pAttacker->GetXPos();
		synMsg.mTargetPosY = pAttacker->GetYPos();
		synMsg.mObjectMP = pAttacker->GetMP();
		synMsg.mObjectMaxMP = pAttacker->GetMaxMP();
		NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );
	}
	catch ( int error )
	{
		/// 스킬 실패 메세지
		HANDLE handle = NULL;
		MSG_RES_SKILL_USED* resMsg = (MSG_RES_SKILL_USED*)NETWORK2->GetMsgRoot( &handle, pAttacker->GetConnectionIdx(), NM_SKILL );

		if ( resMsg != NULL )
		{
			resMsg->Protocol = NM_SKILL;
			resMsg->Protocol = NM_SKILL_ITEMVEHICLE_RES;
			resMsg->ErrorCode        = error;
			resMsg->mSkillClassIndex = skillIdx;
			resMsg->mManagedKey      = clientIdx;

			NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_USED) );
		}
	}
}


bool cSkillManager::MonsterAttack( unsigned long monsterIdx, eMONSTERATTACK_TYPE attackType, sObject target )
{

	if( attackType == eMONSTERATTACK_MAX )
		return false;

	/// 이함수를 통해 들어오는 유저의 타입은 몬스터 뿐이다.
	sObject attacker = { eOBJECTTYPE_MONSTER, monsterIdx };


	/// 공격자 객체 체크
	cMonster* pAttacker = OBJECTMANAGER->GetMonster( attacker.index );
	if( pAttacker == NULL )	
	{ 
		assert(NULL);
		NETWORK2->PostServerEvent("MonsterAttack - attacker.index[%d]", attacker.index );
		return false; 
	}


	/// 이미 스킬 사용중인지 체크
	if( IsUsingSkill( pAttacker->GetObjectType(), pAttacker->GetObjectID() ) == true )	
		return false; 


	/// 타겟 객체 체크
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( pTarget == NULL || pTarget->GetStateDie() == true )	
		return false; 


	/// 스킬index 체크
	sMonsterSkillScript* pSkillInfo = SKILLSCRIPT->GetMonsterSkillInfo( pAttacker->GetRaceGender(), attackType );
	if( pSkillInfo == NULL )	
	{ 
		/// 스킬 스크립트 데이터 획득 오류
		assert(NULL); 
		NETWORK2->PostServerEvent("MonsterAttack - GetMonsterSkillInfo[%d,%d]", pAttacker->GetRaceGender(), attackType );
		return false; 
	}


	/// 타겟 설정 체크 
	if( target.type != eOBJECTTYPE_PLAYER )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("MonsterAttack - target.type[%d]", target.type );
		return false;
	}

	/// 소모 MP 수치 체크
	if( pSkillInfo->mUseMP > pAttacker->GetMP() )	
		return false;

	float range = pAttacker->CalcStatusSkillRange( pSkillInfo->mTargetDist, pSkillInfo->mRangeType );

	range = OBJECTMANAGER->ObjectSizeRange( pAttacker, pTarget, range );

	/// 스킬 사거리 밖에 타겟이 있다.
	cRangeCheck skillRangeCheck( range );

	NiPoint3 attackerPos( pAttacker->GetXPos(), pAttacker->GetYPos(), pAttacker->Height() );
	NiPoint3 targetPos( pTarget->GetXPos(), pTarget->GetYPos(), pTarget->Height() );

	if( !skillRangeCheck.IsRange( attackerPos, targetPos ) )	
		return false; 


	bool casting = false;
	if( pSkillInfo->mShotType == eMONSTERSHOTTYPE_CASTING )
		casting = true;


	/// 스킬 고유번호 생성
	unsigned long skillUniqueIdx = mSkillGen.GeneratIdx();
	if( skillUniqueIdx == 0 )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent( "MonsterAttack skillUniqueIdx[%d]", skillUniqueIdx );
		return false; 
	}

	float attackSpeed = 1.0f;

	/// 일반 공격만 공속값 변경
	if( eMONSTERATTACK_NORMAL1 == attackType || eMONSTERATTACK_NORMAL2 == attackType )
	{
		attackSpeed = pAttacker->GetAttackSpeed();
	}

	/// 스킬 객체 생성
	cMonsterSkillObject* pSkillObject = new cMonsterSkillObject;
	bool init = pSkillObject->InitMonsterAttack( monsterIdx, pAttacker->GetRaceGender(), attackType, 
		target, skillUniqueIdx, casting, attackSpeed, pAttacker->GetMapNumber() );
	if( init == false )
	{ 
		assert(NULL);
		NETWORK2->PostServerEvent( "MonsterAttack new SkillObject" );
		SAFE_DELETE( pSkillObject );
		return false;
	}

	/// 캐스팅을 하지 않는 스킬이면 스킬사용시 소모항목 감소
	if( casting == false )
	{
		/// MP를 소모 하는 스킬
		if( pSkillInfo->mUseMP != 0 )
			pAttacker->MPDamage( pSkillInfo->mUseMP, false );
	}

	/// 스킬을 리스트에 등록
	InsertMonsterSkillMap( monsterIdx, pSkillObject );

	pAttacker->SetAttackType( attackType );
	pAttacker->ModeSkillEnd();

	pAttacker->UpdateSkillCoolTime( attackType );

	/// 스킬 사용시 기본 공격만 대사를 한다.
	/// ※ 모드 스킬은 따로 출력하지 않고 모드 전환시 모드에 해당하는 대사만 한다.
	if( attackType < eMONSTERATTACK_MAX )
		pAttacker->SendSpeech( attackType + eMONSTERTALK_ATTACKSTART );

	/// 스킬 생성 메세지를 다른 클라이언트에게 전달
	MSG_SYN_MONSTER_SKILL_USED synMsg;
	synMsg.Category = NM_SKILL;
	if( casting == true )
		synMsg.Protocol = NM_SKILL_MONSTER_CAST_USED_SYN;
	else
		synMsg.Protocol = NM_SKILL_MONSTER_USED_SYN;
	synMsg.mSkillIndex = skillUniqueIdx;
	synMsg.mAttacker.type = eOBJECTTYPE_MONSTER;
	synMsg.mAttackType = (unsigned char)attackType;
	synMsg.mAttacker.index = monsterIdx;
	synMsg.mAttackerPosX = pAttacker->GetXPos();
	synMsg.mAttackerPosY = pAttacker->GetYPos();
	synMsg.mAttackSpeedFactor = attackSpeed;
	synMsg.mTarget = target;
	NETWORK2->QuickSendExcept( pAttacker, (char*)&synMsg, sizeof(synMsg) );

	pAttacker->MoveStop();

	return true;
}


bool cSkillManager::DeleteSkillObject( unsigned char type, unsigned long objectIdx, unsigned long skillUniqueIdx )
{
	switch( type )
	{
	case eOBJECTTYPE_MONSTER:
		{
			cMonsterSkillObject* pSkillObject = NULL;

			cArray* pArray = (cArray*)mMonsterSkillMap.GetAt( objectIdx );
			if( pArray == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::DeleteSkillObject(%d,%d,%d) pArray == NULL", type, objectIdx, skillUniqueIdx );
				return false;
			}

			for( unsigned int i = pArray->GetSize() ; i > 0 ; --i )
			{
				pSkillObject = (cMonsterSkillObject*)(*pArray)[i-1];
				if( pSkillObject == NULL )
				{
					assert(NULL);
					NETWORK2->PostServerEvent("cSkillManager::DeleteSkillObject(%d,%d,%d) pSkillObject == NULL", type, objectIdx, skillUniqueIdx );
					pArray->PopAt( i-1 );
					continue;
				}

				if( pSkillObject->GetUniqueIdx() == skillUniqueIdx )
				{
					SAFE_DELETE( pSkillObject );
					pArray->PopAt( i-1 );
					return true;
				}
			}
		}
		break;
	case eOBJECTTYPE_HERO:
	case eOBJECTTYPE_PLAYER:
		{
			cBaseSkillObject* pSkillObject = NULL;

			cArray* pArray = (cArray*)mPlayerSkillMap.GetAt( objectIdx );
			if( pArray == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::DeleteSkillObject(%d,%d,%d) pArray == NULL", type, objectIdx, skillUniqueIdx );
				return false;
			}

			for( unsigned int i = pArray->GetSize() ; i > 0 ; --i )
			{
				pSkillObject = (cBaseSkillObject*)(*pArray)[i-1];
				if( pSkillObject == NULL )
				{
					assert(NULL);
					NETWORK2->PostServerEvent("cSkillManager::DeleteSkillObject(%d,%d,%d) pSkillObject == NULL", type, objectIdx, skillUniqueIdx );
					pArray->PopAt( i-1 );
					continue;
				}

				if( pSkillObject->GetUniqueIdx() == skillUniqueIdx )
				{
					SAFE_DELETE( pSkillObject );
					pArray->PopAt( i-1 );
					return true;
				}
			}
		}
		break;
	default:
		return false;
	}

	return false;
}


void cSkillManager::InsertDeleteSkillObject( sObject skillUser, unsigned long skillUniqueIdx ) 
{ 
	sDelSkillInfo info;
	info.skillUser = skillUser;
	info.mSkillIdx = skillUniqueIdx;

	mDeleteSkillAry.PushBack( info ); 
}


unsigned char cSkillManager::AddPlayerHaveSkill( unsigned long playerIdx, unsigned long skillIdx, unsigned char skillStep, unsigned long cooltime )
{

	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( !pPlayer )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("AddPlayerHaveSkill - playerIdx[%d]", playerIdx );
		return SKILL_ADD_ERR_PLAYER; 
	}

	/// 스킬index 체크
	sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
	if( !pSkillInfo )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("AddPlayerHaveSkill - skillIdx[%d]", skillIdx );
		return SKILL_ADD_ERR_SCRIPT; 
	}

	/// 스킬 단계 값 체크
	if( skillStep < 0 || pSkillInfo->mStepCount <= skillStep )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("AddPlayerHaveSkill - skillStep[%d]", skillStep );
		return SKILL_ADD_ERR_SKILLSTEP; 
	}


	/*--------------- skilllist_pc ---------------*/

	/// 전직업 사용 SP량
	if( pSkillInfo->mJobStep != 0 && pSkillInfo->mJobStep != 9 )
	{
		unsigned long point  = pPlayer->GetJobUsedSkillPoint( pSkillInfo->mJobStep-1 );

		if( point < pSkillInfo->mBeforeReqSP )
			return SKILL_ADD_ERR_JOBSP;
	}

	/// 종족 구분 있는경우
	if( pSkillInfo->mRace != 0 ) /// 0 = ALL
	{
		/// 종족 체크
		switch( pPlayer->GetRace() )
		{
		case eRACE_HUMAN:
			if( !( pSkillInfo->mRace == eSKILLRACE_HUMAN || 
				pSkillInfo->mRace == eSKILLRACE_HUMAN_BEAST || 
				pSkillInfo->mRace == eSKILLRACE_HUMAN_ELF ) )	
			{
				return SKILL_ADD_ERR_RACE; 
			}break;
		case eRACE_BEAST:
			if( !( pSkillInfo->mRace == eSKILLRACE_BEAST || 
				pSkillInfo->mRace == eSKILLRACE_HUMAN_BEAST || 
				pSkillInfo->mRace == eSKILLRACE_BEAST_ELF ) )
			{ 
				return SKILL_ADD_ERR_RACE; 
			}break;
		case eRACE_ELF:
			if( !( pSkillInfo->mRace == eSKILLRACE_ELF || 
				pSkillInfo->mRace == eSKILLRACE_HUMAN_ELF || 
				pSkillInfo->mRace == eSKILLRACE_BEAST_ELF ) )	
			{ 
				return SKILL_ADD_ERR_RACE; 
			}break;
		default:
			assert(NULL); 
			return SKILL_ADD_ERR_SCRIPT; 	
		}
	}

	/// 선행 스킬1 필요
	if( pSkillInfo->mLearnSkillIdx1 != 0 )
	{
		/// 선행 보유 스킬 체크
		cHaveSkillObject* pPlayerHaveSkill = GetPlayerHaveSkill( playerIdx, pSkillInfo->mLearnSkillIdx1 );
		if( !pPlayerHaveSkill )	
			return SKILL_ADD_ERR_LEARNSKILL; 

		/// 선행 보유 스킬의 스텝
		if( pSkillInfo->mLearnSkillStep1 > pPlayerHaveSkill->GetSkillStep() + 1 )	
			return SKILL_ADD_ERR_LEARNSTEP; 
	}

	/// 선행 스킬2 필요
	if( pSkillInfo->mLearnSkillIdx2 != 0 )
	{
		/// 선행 보유 스킬 체크
		cHaveSkillObject* pPlayerHaveSkill = GetPlayerHaveSkill( playerIdx, pSkillInfo->mLearnSkillIdx2 );
		if( !pPlayerHaveSkill )	
			return SKILL_ADD_ERR_LEARNSKILL; 

		/// 선행 보유 스킬의 스텟
		if( pSkillInfo->mLearnSkillStep2 > pPlayerHaveSkill->GetSkillStep() + 1 )	
			return SKILL_ADD_ERR_LEARNSTEP; 
	}


	///*--------------- skilllist_sub ---------------*/

	/// 직업구분 있는 경우
	if( pSkillInfo->mpSetpInfoArray->mJobType != 0 )
	{
		/// 직업을 체크
		if( !SKILLSCRIPT->IsPlayerSkillJob( pSkillInfo->mpSetpInfoArray->mJobType, static_cast<unsigned long>(pPlayer->GetJob()) ) )
			return SKILL_ADD_ERR_JOB; 
	}

	/// 캐릭터 필요 level
	if( pSkillInfo->mpSetpInfoArray->mPlayerLevel > pPlayer->GetLevel() )	
		return SKILL_ADD_ERR_LEVEL; 

	///*---------------------------------------------*/


	/// 추가할 스킬이 보유리스트에 있는지 읽어옴
	cHaveSkillObject* pPlayerHaveSkill = GetPlayerHaveSkill( playerIdx, skillIdx );

	/// 새로 배우는 스킬인지 체크
	if( skillStep == SKILL_START_STEP )
	{
		/// 이미 스킬이 있으면 오류
		if( pPlayerHaveSkill )	
		{ 
			assert(NULL); 
			NETWORK2->PostServerEvent("AddPlayerHaveSkill -alreay have skill skillStep[%d]", skillStep );
			return SKILL_ADD_ERR_SKILLSTEP; 
		}

		/// playeridx로 플레이어가 보유한 스킬리스트 포인터를 얻어옴
		cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( playerIdx );
		/// 보유한 스킬리스트가 없으면
		if ( !pPlayerHaveSkillMap )
		{
			/// 새로운 스킬리스트를 만듬
			pPlayerHaveSkillMap = new cPointerHashMap;

			/// 새로 만든 스킬리스트를 기록
			if( !( mPlayerHaveSkillMap.Insert( playerIdx, pPlayerHaveSkillMap ) ) )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("AddPlayerHaveSkill[%d,%d,%d] - mPlayerHaveSkillMap.Insert error", playerIdx, skillIdx, skillStep );
				SAFE_DELETE( pPlayerHaveSkillMap );
				return SKILL_ADD_ERR_MAPINSERT;
			}
		}

		/// 보유스킬리스트 정보를 기록
		cHaveSkillObject* pPlayerHaveSkill = new cHaveSkillObject;
		pPlayerHaveSkill->Init( skillIdx, 0, cooltime );

		if( !(pPlayerHaveSkillMap->Insert( skillIdx, pPlayerHaveSkill ) ) )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("AddPlayerHaveSkill[%d,%d,%d] - mPlayerHaveSkillMap.Insert error", playerIdx, skillIdx, skillStep  );
			SAFE_DELETE( pPlayerHaveSkill );
			return SKILL_ADD_ERR_MAPINSERT;
		}
	}
	else	/// 단계를 업시키는 경우
	{
		/// 보유스킬이 없으면 오류
		if( pPlayerHaveSkill == 0 )	
		{ 
			NETWORK2->PostServerEvent("AddPlayerHaveSkill[%d,%d,%d] - update error not have", playerIdx, skillIdx, skillStep );
			return SKILL_ADD_ERR_SKILLSTEP; 
		}

		/// 단계 업조건이 만족하지 않으면 오류
		if( pPlayerHaveSkill->StepUp( skillStep, cooltime ) == false )	
		{ 
			NETWORK2->PostServerEvent("AddPlayerHaveSkill[%d,%d,%d] - update error", playerIdx, skillIdx, skillStep );
			return SKILL_ADD_ERR_SKILLSTEP; 
		}
	}

	/// 패시브 일경우 스텟 재계산
	if( pSkillInfo->mType == eSKILLTYPE_PASSIVE )
	{
		/// 기본항목을 제외한 확장(+,%) 항목만 계산
		STATUSCALC->CalcPlayerExtensionGlobal( pPlayer->GetObject() );
	}

	/// 오라스킬이면 기존 스킬 끔
	if( pSkillInfo->mShotType == eSHOTTYPE_AURA )
		AuraInfluenceOff( playerIdx, skillIdx );

	return SKILL_ADD_ERR_SUCCESS;
}



void cSkillManager::InsertPlayerSkillMap( unsigned long characterIdx, cBaseSkillObject* pSkillObject )
{
	cArray* pSkillArray = (cArray*)mPlayerSkillMap.GetAt( characterIdx );
	if( pSkillArray == NULL )
	{
		pSkillArray = new cArray;
		mPlayerSkillMap.Insert( characterIdx, pSkillArray );
	}

	pSkillArray->PushBack( pSkillObject );
}



void cSkillManager::InsertMonsterSkillMap( unsigned long monsterIdx, cMonsterSkillObject* pSkillObject )
{
	cArray* pSkillArray = (cArray*)mMonsterSkillMap.GetAt( monsterIdx );
	if( pSkillArray == NULL )
	{
		pSkillArray = new cArray;
		mMonsterSkillMap.Insert( monsterIdx, pSkillArray );
	}

	pSkillArray->PushBack( pSkillObject );
}



cHaveSkillObject* cSkillManager::GetPlayerHaveSkill( unsigned long playerIdx, unsigned long skillIdx )
{
	cPointerHashMap* cPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( playerIdx );
	if ( !cPlayerHaveSkillMap )
	{
		return NULL;
	}

	/// 기본 공격인 경우 LBS추가
	if( skillIdx < NORMAL_ATTACK_SKILL_MAX )
		return (cHaveSkillObject*)cPlayerHaveSkillMap->GetAt( NORMAL_ATTACK_SKILL );

	return (cHaveSkillObject*)cPlayerHaveSkillMap->GetAt( skillIdx );
}



void* cSkillManager::GetPlayerHaveSkill( unsigned long playerIdx )
{
	return mPlayerHaveSkillMap.GetAt( playerIdx );
}



bool cSkillManager::DelPlayerHaveSkill( unsigned long playerIdx, unsigned long skillIdx )
{
	/// playeridx로 플레이어가 보유한 스킬리스트 포인터를 얻어옴
	cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( playerIdx );
	/// 보유한 스킬리스트가 없으면
	if ( !pPlayerHaveSkillMap )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("cSkillManager::DelPlayerHaveSkill pPlayer[%d]HaveSkillMap == NULL", playerIdx );
		return false;
	}

	if( !(pPlayerHaveSkillMap->Erase( skillIdx ) ) )
	{
		return false;
	}

	return true;
}


void cSkillManager::RollBackPlayerHaveSkill( unsigned long playerIdx, unsigned long skillIdx )
{
	cHaveSkillObject* pHaveSkillObject = GetPlayerHaveSkill( playerIdx, skillIdx );
	if( !pHaveSkillObject ) 
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::RollBackPlayerHaveSkill pHaveSkillObject[%d,%d] == NULL", playerIdx, skillIdx );
		return; 
	}

	if( pHaveSkillObject->GetSkillStep() == SKILL_START_STEP )
	{
		cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( playerIdx );
		if( !pPlayerHaveSkillMap )	
		{ 
			assert(NULL); 
			NETWORK2->PostServerEvent("cSkillManager::RollBackPlayerHaveSkill pPlayerHaveSkillMap[%d] == NULL", playerIdx );
			return; 
		}

		SAFE_DELETE( pHaveSkillObject );
		pPlayerHaveSkillMap->Erase( skillIdx );
	}
	else
	{
		/// 스텝 한단계 감소
		pHaveSkillObject->StepDown();
	}
}


void cSkillManager::ReleasePlayerHaveSkill()
{
	cPointerHashMap::cIterator startPlayer = mPlayerHaveSkillMap.Begin();
	cPointerHashMap::cIterator endPlayer = mPlayerHaveSkillMap.End();

	for( ; startPlayer != endPlayer ; ++startPlayer )
	{
		cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)(*startPlayer).mSecond;
		if ( !pPlayerHaveSkillMap )		{ continue; }

		cPointerHashMap::cIterator startSkill = pPlayerHaveSkillMap->Begin();
		cPointerHashMap::cIterator endSkill = pPlayerHaveSkillMap->End();

		cHaveSkillObject* p;
		for( ; startSkill != endSkill ; ++startSkill )
		{
			p = (cHaveSkillObject*)(*startSkill).mSecond;
			SAFE_DELETE( p );
		}
		SAFE_DELETE( pPlayerHaveSkillMap );
	}

	mPlayerHaveSkillMap.Clear();
}



void cSkillManager::ReleaseInfluence()
{
	cPointerHashMap::cIterator startInfluence = mInfluenceMap.Begin();
	cPointerHashMap::cIterator endInfluence = mInfluenceMap.End();

	cInfluenceObject* pInfluenceObject;
	for( ; startInfluence != endInfluence ; ++startInfluence )
	{
		pInfluenceObject = (cInfluenceObject*)(*startInfluence).mSecond;
		SAFE_DELETE( pInfluenceObject );
	}

	mInfluenceMap.Clear();
}


bool cSkillManager::SavePlayerInfluence( unsigned long playerIdx, SKILL_INFLUENCE_INSERT* pInfluenceInsert )
{

	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( !pPlayer )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SavePlayerInfluence pPlayer[%d] == NULL", playerIdx );
		return false; 
	}

	tHashSet<unsigned long>* pInfluenceAry = pPlayer->GetInfluenceSet();
	if( !pInfluenceAry )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SavePlayerInfluence pInfluenceAry == NULL" );
		return false; 
	}

	cInfluenceObject* pInfluenceObject = NULL;

	tHashSet<unsigned long>::cIterator start = pInfluenceAry->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceAry->End();

	// 08/03/11 수정
	TB_CHARACTER_INFLUENCE* table = pInfluenceInsert->mTable;	// long i = 0;
	for( ; start != end ; ++start )
	{
		pInfluenceObject = GetInfluence( (*start) );
		if( !pInfluenceObject ) 
		{ 
			assert(NULL); 
			NETWORK2->PostServerEvent("cSkillManager::SavePlayerInfluence pInfluenceObject[%d] == NULL", (*start) );
			//-/-//InsertDeleteInfluenceObject( pInfluenceObject );	/// 객체 삭제 대기열에 넣는다.
			continue;
		}

		if( PVP_DM_LEADER_INFLUENCE == pInfluenceObject->GetInfluenceClassIdx() )
			continue;			
		if( PVP_DM_LEADER_INFLUENCE2 == pInfluenceObject->GetInfluenceClassIdx() )
			continue;			

		/// 오라효과, 오라에 복사된 효과는 저장하지 않는다.
		if( pInfluenceObject->IsSwitchType() == true )
			continue;

		// 08/03/11 수정
		pInfluenceObject->SavePlayerInfluence( table );		// pInfluenceObject->SavePlayerInfluence( &pInfluenceInsert->mTable[i] );
		table++;											// i++;

		pInfluenceInsert->mRowCount++;
	}
	// 08/03/11 수정
	// pInfluenceInsert->mRowCount = i;
	pInfluenceInsert->mCharacterIdx = playerIdx;
	return true;
}



void cSkillManager::RestorePlayerInfluence( unsigned long playerIdx, SKILL_INFLUENCE_SELECT* pInfluenceSelect, unsigned long rowCnt )
{

	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( !pPlayer ) 
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::RestorePlayerInfluence pPlayer[%d] == NULL", playerIdx );
		return; 
	}

	const int colNum = 3;

	unsigned long uniqueIdx = 0;
	cInfluenceObject* pInf = NULL;
	for( unsigned long i = 0 ; i < rowCnt ; ++i )
	{
		sInfluence* pInfluence = &pInfluenceSelect->mTable[i];
		if( pInfluence == NULL )
			continue;

		sInfluenceScript* pScript = SKILLSCRIPT->GetInfluenceInfo( pInfluence->mInfluenceClassIdx );
		if( pScript == NULL )
			continue;

		/// 신규 생성
		pInf = new cInfluenceObject;
		uniqueIdx = mInfluenceGen.GeneratIdx();
		if( pInf->RestorePlayerInfluence( playerIdx, uniqueIdx, pInfluence ) == false )
		{
			assert(NULL);
			SAFE_DELETE( pInf );
			continue;
		}

		if( !( mInfluenceMap.Insert( uniqueIdx, pInf ) ) )
		{
			assert(NULL);
			SAFE_DELETE( pInf );
			NETWORK2->PostServerEvent( "cSkillManager::RestorePlayerInfluence mInfluenceMap.Insert Duplicate" );
			continue;
		}

		if( pPlayer->AddInfluence( uniqueIdx, pInf->GetInfluenceClassIdx() ) == false )
		{
			assert(NULL);
			NETWORK2->PostServerEvent( "cSkillManager::RestorePlayer[%d]Influence[%d] pPlayer->AddSkillInfluence[%d] == false", playerIdx, uniqueIdx, pInf->GetInfluenceClassIdx() );
			NETWORK2->PostServerEvent( "cSkillManager::RestorePlayer[%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d]",
			pInfluence->mUniqueIdx,
			pInfluence->mDbIdx,
			pInfluence->mInfluenceClassIdx,
			pInfluence->mAttacker.type,
			pInfluence->mAttacker.index,
			pInfluence->mIsRealTime,
			pInfluence->mRestTime,
			pInfluence->mTime1,
			pInfluence->mTime2,
			pInfluence->mInfluenceValue1,
			pInfluence->mInfluenceValue2,
			pInfluence->mAttriButeType );
			continue;
		}

		long type[colNum] = { pScript->mStatusPlusIdx1, pScript->mStatusPlusIdx2, pScript->mStatusPlusIdx3 };
		long value[colNum] = { pScript->mValue1, pScript->mValue2, pScript->mValue3 };

		for( int i = 0 ; i < colNum ; ++i )
			ApplyInfluence( pPlayer, pPlayer, uniqueIdx, pInfluence->mInfluenceClassIdx, type[i], value[i] );
	}
}


void cSkillManager::ReleasePlayerHaveSkill( unsigned long playerIdx )
{
	/// playeridx로 플레이어가 보유한 스킬리스트 포인터를 얻어옴
	cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( playerIdx );
	/// 보유한 스킬리스트가 없으면
	if ( !pPlayerHaveSkillMap )	{ return; }

	cPointerHashMap::cIterator startSkill = pPlayerHaveSkillMap->Begin();
	cPointerHashMap::cIterator endSkill = pPlayerHaveSkillMap->End();

	cHaveSkillObject* p = NULL;
	for( ; startSkill != endSkill ; ++startSkill )
	{
		p = (cHaveSkillObject*)(*startSkill).mSecond;
		SAFE_DELETE( p );
	}
	SAFE_DELETE( pPlayerHaveSkillMap );

	mPlayerHaveSkillMap.Erase( playerIdx );
}


bool cSkillManager::AddInfluence( sObject attacker, sObject target, unsigned long infClassIdx, unsigned long skillClassIdx, bool msgSend )
{
	//////////////////////////////////////////////////////////////////////////
	/// 참조 데이타 로드
	
	/// 신규효과 스크립트
	sInfluenceScript* pNewScript = SKILLSCRIPT->GetInfluenceInfo( infClassIdx );
	if( pNewScript == NULL ) 	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewScript[%d]==NULL", infClassIdx );
		return false; 
	}
	const int colNum = 3;
	long type[colNum] = { pNewScript->mStatusPlusIdx1, pNewScript->mStatusPlusIdx2, pNewScript->mStatusPlusIdx3 };
	long value[colNum] = { pNewScript->mValue1, pNewScript->mValue2, pNewScript->mValue3 };

	/// 공격자, 타겟 포인터
	cBaseObject* pAttacker = OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL || pAttacker->GetStateDie() == true )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pAttacker[%d,%d]==NULL", attacker.type, attacker.index );
		return false; 
	}
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( !pTarget )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pTarget[%d,%d]==NULL", target.type, target.index );
		return false; 
	}


	//////////////////////////////////////////////////////////////////////////
	/// 발동 확률 체크부
	unsigned long skillRate = pNewScript->mActivityPer;
	if( skillRate != 0 )
	{
		/// 확률 값
		unsigned long checkPer = skillRate;

		/// 공격자와 타겟간의 상태이상 공식을 적용 해야 하는지 유무
		bool isBadInfluence = false;
		for( int i = 0 ; i < colNum ; ++i )
		{
			if( eSTATUSPLUS_DISPEL <= type[i] && type[i] <= eSTATUSPLUS_CONDITIONINDISPOSITION_MAX )
			{
				isBadInfluence = true;
				break;
			}
		}

		/// 상태 이상 확률값 산출
		if( isBadInfluence == true )
		{
			/// 공격자가 플레이어인 경우 
			if( attacker.type == eOBJECTTYPE_PLAYER )
				checkPer = DAMAGECALC->PlayerConditionOdd( target, attacker, skillRate, skillClassIdx );
			else if( attacker.type == eOBJECTTYPE_MONSTER )
				checkPer = DAMAGECALC->MonsterConditionOdd( target, attacker, skillRate, skillClassIdx );
		}

		/// 확률 체크
		unsigned long activityRand = rand() % 100;
		if( checkPer <= activityRand )	
			return false; 
	}


	bool EndVehicle = false;
	//////////////////////////////////////////////////////////////////////////
	/// 효과 객체 생성 체크부
	bool createInfluence = false;
	for( int i = 0 ; i < colNum ; ++i )
	{
		if( eSTATUSPLUS_STR_PLUS <= type[i] && type[i] <= eSTATUSPLUS_BASE_MAX ||
			eSTATUSPLUS_ATK_PLUS <= type[i] && type[i] <= eSTATUSPLUS_BUFF_ETC_MAX ||
			eSTATUSPLUS_DOT_HPCHANGE_PLUS <= type[i] && type[i] <= eSTATUSPLUS_DOT_MAX ||
			eSTATUSPLUS_STOPMOVE <= type[i] && type[i] <= eSTATUSPLUS_CONDITIONINDISPOSITION_MAX ||
			eSTATUSPLUS_CHAR_SIZE == type[i] )
		{
			createInfluence = true;
			break;
		}
		/// 스킬에서만 사용 - 기입 에러
		else if( eSTATUSPLUS_INSTANTPERHIT <= type[i] && type[i] <= eSTATUSPLUS_DELETE_TARGETING )
		{
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence type[%d,%d] error", infClassIdx, type[i] );
			continue;
		}
		/// 탈것 탄상태에선 몬스터 변신 불가
		else if( type[i] == eSTATUSPLUS_CHANGE_MONSTER )
		{
			if( pTarget->GetObjectType() != eOBJECTTYPE_PLAYER )
				return false;

			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( pPlayer->IsVehicle() == true )
			{
				EndVehicle = true;
			}
		}
	}


	//////////////////////////////////////////////////////////////////////////
	/// 효과 객체 생성부
	unsigned long uniqueIdx = 0;
	if( createInfluence == true )
	{
		cHashSet* pInfluenceSet = pTarget->GetInfluenceSet();

		/// 중복/그룹 체크
		tHashSet<unsigned long>::cIterator begin = pInfluenceSet->Begin();
		tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();
	
		while( begin != end )
		{
			/// 보유 효과 오브젝트
			cInfluenceObject* pHaveInf = GetInfluence( *begin++ );
			if( !pHaveInf )	
			{ 
				NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceObject==NULL" );
				continue; 
			}
	
			/// 보유 효과의 스크립트 정보
			sInfluenceScript* pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveInf->GetInfluenceClassIdx() );
			if( !pHaveInf )	
			{ 
				NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceScript==NULL" );
				continue; 
			}
	
			/// 보유하고 있는 효과와 신규 효과의 중복체크
			if( pNewScript->mNotOverlapGroupIdx != 0 &&
				pHaveScript->mNotOverlapGroupIdx == pNewScript->mNotOverlapGroupIdx )
			{
				/// 공격자도 같으면 효과 제거
				if( pHaveInf->GetAttacker().index == attacker.index &&
					pHaveInf->GetAttacker().type == attacker.type )
				{
					DeleteInfluence( target, pHaveInf->GetUniqueIdx() );
					break;
				}
			}
	
			/// 보유하고 있는 효과와 신규 효과의 효력그룹 비교
			if( pNewScript->mGroupIdx == pHaveScript->mGroupIdx )
			{
				/// 신규 가 더 높거나 같으면 신규 생성(기존삭제)
				if( pNewScript->mGroupOrder >= pHaveScript->mGroupOrder )
				{
					DeleteInfluence( target, pHaveInf->GetUniqueIdx() );
					break;
				}
				else
					return false;
			}
		}

		/// 생성
		unsigned long plusTime = 0;
		cInfluenceObject* pNewObject = new cInfluenceObject;
		uniqueIdx = mInfluenceGen.GeneratIdx();
		if( !pNewObject->NormalInit( attacker, target, uniqueIdx, infClassIdx, skillClassIdx, &plusTime ) )
		{
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewInfluenceObject->Init err");
			SAFE_DELETE( pNewObject );
			return false;
		}

		if( !( mInfluenceMap.Insert( uniqueIdx, pNewObject ) ) )
		{
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence mInfluenceMap duplicate");
			SAFE_DELETE( pNewObject );
			return false;
		}

		/// 타겟 효과 리스트에 추가
		if( pTarget->AddInfluence( uniqueIdx, pNewObject->GetInfluenceClassIdx() ) == false )
		{
			NETWORK2->PostServerEvent( "cSkillManager::AddInfluence pTarget[%d,%d]->AddSkillInfluence[%d,%d] == false", 
				target.type, target.index, uniqueIdx, infClassIdx );
			return false;
		}

		if( msgSend == true )
		{
			MSG_SYN_INFLUENCE_CREATE msg;
			msg.Category = NM_SKILL;
			msg.Protocol = NM_SKILL_INFLUENCE_CREATE_SYN;
			msg.mTarget = target;
			msg.mAttacker = attacker;
			msg.mUniqueIdx = uniqueIdx;
			msg.mInfluenceClassIdx = infClassIdx;
			msg.mAuraSkillClassIdx = 0;
			msg.mDieDelete = pNewObject->GetDieDelete();
			msg.mPlusTime = plusTime;

			NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
		}
	}


	//////////////////////////////////////////////////////////////////////////
	/// 적용부
	bool calcStatus = false;
	for( int i = 0 ; i < colNum ; ++i )
	{
		if( ApplyInfluence( pAttacker, pTarget, uniqueIdx, infClassIdx, type[i], value[i] ) == true )
			calcStatus = true;
	}


	//////////////////////////////////////////////////////////////////////////
	/// 스텟 계산
	if( calcStatus == true )
	{
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
			STATUSCALC->CalcPlayerExtensionGlobal( pTarget->GetObject() );
		else if( pTarget->GetObjectType() == eOBJECTTYPE_MONSTER )
			STATUSCALC->CalcMonster( target.index );
		else
			assert(NULL);
	}

	if( EndVehicle == true )
	{
		((cPlayer*)pTarget)->EndVehicleClient( false );
	}

	return true;
}


unsigned long cSkillManager::AddInfSwitch( sObject attacker, sObject target, unsigned long infClassIdx, unsigned long skillClassIdx, bool msgSend )
{
	//////////////////////////////////////////////////////////////////////////
	/// 참조 데이타 로드

	/// 신규효과 스크립트
	sInfluenceScript* pNewScript = SKILLSCRIPT->GetInfluenceInfo( infClassIdx );
	if( pNewScript == NULL ) 	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewScript[%d]==NULL", infClassIdx );
		return 0; 
	}
	const int colNum = 3;
	long type[colNum] = { pNewScript->mStatusPlusIdx1, pNewScript->mStatusPlusIdx2, pNewScript->mStatusPlusIdx3 };
	long value[colNum] = { pNewScript->mValue1, pNewScript->mValue2, pNewScript->mValue3 };

	/// 공격자, 타겟 포인터
	cBaseObject* pAttacker = OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL || pAttacker->GetStateDie() == true )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pAttacker[%d,%d]==NULL", attacker.type, attacker.index );
		return 0; 
	}
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( !pTarget )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pTarget[%d,%d]==NULL", target.type, target.index );
		return 0; 
	}


	//////////////////////////////////////////////////////////////////////////
	/// 효과 객체 생성부

	cHashSet* pInfluenceSet = pTarget->GetInfluenceSet();

	tHashSet<unsigned long>::cIterator begin = pInfluenceSet->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();

	/// 자신이 켜둔 오라 먼저 체크
	while( begin != end )
	{
		/// 보유 효과 오브젝트
		cInfluenceObject* pHaveInf = GetInfluence( *begin++ );
		if( !pHaveInf )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceObject==NULL Aura" );
			continue; 
		}

		/// 보유 효과의 스크립트 정보
		sInfluenceScript* pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveInf->GetInfluenceClassIdx() );
		if( !pHaveScript )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceScript==NULL Aura" );
			continue; 
		}

		/// 보유하고 있는 효과와 신규 효과의 중복체크
		if( pHaveScript->mNotOverlapGroupIdx != 0 &&
			pHaveScript->mNotOverlapGroupIdx == pNewScript->mNotOverlapGroupIdx )
		{				
			/// 공격자도 같으면 효과 제거
			if( pHaveInf->GetAttacker().index == attacker.index &&
				pHaveInf->GetAttacker().type == attacker.type )
			{
				DeleteInfluence( target, pHaveInf->GetUniqueIdx() );
				break;
			}
		}
	}

	begin = pInfluenceSet->Begin();
	end = pInfluenceSet->End();

	while( begin != end )
	{
		/// 보유 효과 오브젝트
		cInfluenceObject* pHaveInf = GetInfluence( *begin++ );
		if( !pHaveInf )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceObject==NULL" );
			continue; 
		}

		/// 보유 효과의 스크립트 정보
		sInfluenceScript* pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveInf->GetInfluenceClassIdx() );
		if( !pHaveScript )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceScript==NULL" );
			continue; 
		}

		/// 보유하고 있는 효과와 신규 효과의 효력그룹 비교
		if( pNewScript->mGroupIdx == pHaveScript->mGroupIdx )
		{
			/// 신규 가 더 높거나 같으면 신규 생성(기존삭제)
			if( pNewScript->mGroupOrder >= pHaveScript->mGroupOrder )
			{
				DeleteInfluence( target, pHaveInf->GetUniqueIdx() );
				break;
			}
			else
				return 0;
		}
	}

	cInfluenceObject* pNewObject = new cInfluenceObject;
	unsigned long uniqueIdx = mInfluenceGen.GeneratIdx();
	if( !pNewObject->SwitchParentInit( attacker, target, uniqueIdx, infClassIdx, skillClassIdx ) )
	{
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewInfluenceObject->Init err");
		SAFE_DELETE( pNewObject );
		return 0;
	}

	if( !( mInfluenceMap.Insert( uniqueIdx, pNewObject ) ) )
	{
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence mInfluenceMap duplicate");
		SAFE_DELETE( pNewObject );
		return 0;
	}

	/// 타겟 효과 리스트에 추가
	if( pTarget->AddInfluence( uniqueIdx, pNewObject->GetInfluenceClassIdx() ) == false )
	{
		NETWORK2->PostServerEvent( "cSkillManager::AddInfluence pTarget[%d,%d]->AddSkillInfluence[%d,%d] == false", 
			target.type, target.index, uniqueIdx, infClassIdx );
		return 0;
	}

	if( msgSend == true )
	{
		MSG_SYN_INFLUENCE_CREATE msg;
		msg.Category = NM_SKILL;
		msg.Protocol = NM_SKILL_INFLUENCE_CREATE_SYN;
		msg.mTarget = target;
		msg.mAttacker = attacker;
		msg.mUniqueIdx = uniqueIdx;
		msg.mInfluenceClassIdx = infClassIdx;
		msg.mAuraSkillClassIdx = skillClassIdx;
		msg.mDieDelete = pNewObject->GetDieDelete();
		msg.mPlusTime = 0;

		NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
	}


	//////////////////////////////////////////////////////////////////////////
	/// 적용부
	bool calcStatus = false;
	for( int i = 0 ; i < colNum ; ++i )
	{
		if( ApplyInfluence( pAttacker, pTarget, uniqueIdx, infClassIdx, type[i], value[i] ) == true )
			calcStatus = true;
	}


	//////////////////////////////////////////////////////////////////////////
	/// 스텟 계산
	if( calcStatus == true )
	{
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
			STATUSCALC->CalcPlayerExtensionGlobal( pTarget->GetObject() );
		else if( pTarget->GetObjectType() == eOBJECTTYPE_MONSTER )
			STATUSCALC->CalcMonster( target.index );
		else
			assert(NULL);
	}

	return uniqueIdx;
}


bool cSkillManager::AddInfChildAura( sObject attacker, sObject target, unsigned long infClassIdx, unsigned long skillClassIdx, unsigned long parentIdx, bool msgSend )
{
	//////////////////////////////////////////////////////////////////////////
	/// 참조 데이타 로드

	/// 신규효과 스크립트
	sInfluenceScript* pNewScript = SKILLSCRIPT->GetInfluenceInfo( infClassIdx );
	if( pNewScript == NULL ) 	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewScript[%d]==NULL", infClassIdx );
		return false; 
	}
	const int colNum = 3;
	long type[colNum] = { pNewScript->mStatusPlusIdx1, pNewScript->mStatusPlusIdx2, pNewScript->mStatusPlusIdx3 };
	long value[colNum] = { pNewScript->mValue1, pNewScript->mValue2, pNewScript->mValue3 };

	/// 공격자, 타겟 포인터
	cBaseObject* pAttacker = OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL || pAttacker->GetStateDie() == true )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pAttacker[%d,%d]==NULL", attacker.type, attacker.index );
		return false; 
	}
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( !pTarget )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pTarget[%d,%d]==NULL", target.type, target.index );
		return false; 
	}


	//////////////////////////////////////////////////////////////////////////
	/// 효과 객체 생성부
	cHashSet* pInfluenceSet = pTarget->GetInfluenceSet();

	tHashSet<unsigned long>::cIterator begin = pInfluenceSet->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();
	while( begin != end )
	{
		/// 보유 효과 오브젝트
		cInfluenceObject* pHaveInf = GetInfluence( *begin++ );
		if( !pHaveInf )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceObject==NULL" );
			continue; 
		}

		/// 보유 효과의 스크립트 정보
		sInfluenceScript* pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveInf->GetInfluenceClassIdx() );
		if( !pHaveScript )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceScript==NULL" );
			continue; 
		}

		/// 보유하고 있는 효과와 신규 효과의 효력그룹 비교
		if( pNewScript->mGroupIdx == pHaveScript->mGroupIdx )
		{
			/// 신규 가 더 높으면 신규 생성(기존삭제)
			if( pNewScript->mGroupOrder > pHaveScript->mGroupOrder )
			{
				DeleteInfluence( target, pHaveInf->GetUniqueIdx() );
				break;
			}
			else
				return false;
		}
	}

	cInfluenceObject* pNewObject = new cInfluenceObject;
	unsigned long uniqueIdx = mInfluenceGen.GeneratIdx();
	if( !pNewObject->SwitchChildAuraInit( attacker, target, uniqueIdx, infClassIdx, skillClassIdx, parentIdx ) )
	{
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewInfluenceObject->Init err");
		SAFE_DELETE( pNewObject );
		return false;
	}

	if( !( mInfluenceMap.Insert( uniqueIdx, pNewObject ) ) )
	{
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence mInfluenceMap duplicate");
		SAFE_DELETE( pNewObject );
		return false;
	}

	/// 타겟 효과 리스트에 추가
	if( pTarget->AddInfluence( uniqueIdx, pNewObject->GetInfluenceClassIdx() ) == false )
	{
		NETWORK2->PostServerEvent( "cSkillManager::AddInfluence pTarget[%d,%d]->AddSkillInfluence[%d,%d] == false", 
			target.type, target.index, uniqueIdx, infClassIdx );
		return false;
	}

	if( msgSend == true )
	{
		MSG_SYN_INFLUENCE_CREATE msg;
		msg.Category = NM_SKILL;
		msg.Protocol = NM_SKILL_INFLUENCE_CREATE_SYN;
		msg.mTarget = target;
		msg.mAttacker = attacker;
		msg.mUniqueIdx = uniqueIdx;
		msg.mInfluenceClassIdx = infClassIdx;
		msg.mAuraSkillClassIdx = 0;
		msg.mDieDelete = pNewObject->GetDieDelete();
		msg.mPlusTime = 0;

		NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
	}


	//////////////////////////////////////////////////////////////////////////
	/// 적용부
	bool calcStatus = false;
	for( int i = 0 ; i < colNum ; ++i )
	{
		if( ApplyInfluence( pAttacker, pTarget, uniqueIdx, infClassIdx, type[i], value[i] ) == true )
			calcStatus = true;
	}


	//////////////////////////////////////////////////////////////////////////
	/// 스텟 계산
	if( calcStatus == true )
	{
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
			STATUSCALC->CalcPlayerExtensionGlobal( pTarget->GetObject() );
		else if( pTarget->GetObjectType() == eOBJECTTYPE_MONSTER )
			STATUSCALC->CalcMonster( target.index );
		else
			assert(NULL);
	}

	return true;
}


bool cSkillManager::AddInfChildTotem( sObject attacker, sObject target, unsigned long infClassIdx, unsigned long skillClassIdx, unsigned long parentIdx, bool msgSend )
{
	//////////////////////////////////////////////////////////////////////////
	/// 참조 데이타 로드

	sInfluenceScript* pNewScript = SKILLSCRIPT->GetInfluenceInfo( infClassIdx );
	if( pNewScript == NULL ) 	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewScript[%d]==NULL", infClassIdx );
		return false; 
	}
	const int colNum = 3;
	long type[colNum] = { pNewScript->mStatusPlusIdx1, pNewScript->mStatusPlusIdx2, pNewScript->mStatusPlusIdx3 };
	long value[colNum] = { pNewScript->mValue1, pNewScript->mValue2, pNewScript->mValue3 };

	/// 공격자, 타겟 포인터
	cBaseObject* pAttacker = OBJECTMANAGER->GetObject( attacker.type, attacker.index );
	if( pAttacker == NULL || pAttacker->GetStateDie() == true )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pAttacker[%d,%d]==NULL", attacker.type, attacker.index );
		return false; 
	}
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( !pTarget )	
	{ 
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pTarget[%d,%d]==NULL", target.type, target.index );
		return false; 
	}


	//////////////////////////////////////////////////////////////////////////
	/// 효과 객체 생성부
	cHashSet* pInfluenceSet = pTarget->GetInfluenceSet();

	tHashSet<unsigned long>::cIterator begin = pInfluenceSet->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();
	while( begin != end )
	{
		/// 보유 효과 오브젝트
		cInfluenceObject* pHaveInf = GetInfluence( *begin++ );
		if( !pHaveInf )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceObject==NULL" );
			continue; 
		}

		/// 보유 효과의 스크립트 정보
		sInfluenceScript* pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveInf->GetInfluenceClassIdx() );
		if( !pHaveScript )	
		{ 
			NETWORK2->PostServerEvent("cSkillManager::AddInfluence pHaveInfluenceScript==NULL" );
			continue; 
		}

		/// 보유하고 있는 효과와 신규 효과의 효력그룹 비교
		if( pNewScript->mGroupIdx == pHaveScript->mGroupIdx )
		{
			/// 신규 가 더 높으면 신규 생성(기존삭제)
			if( pNewScript->mGroupOrder > pHaveScript->mGroupOrder )
			{
				DeleteInfluence( target, pHaveInf->GetUniqueIdx() );
				break;
			}
			else
				return false;
		}
	}

	cInfluenceObject* pNewObject = new cInfluenceObject;
	unsigned long uniqueIdx = mInfluenceGen.GeneratIdx();
	if( !pNewObject->SwitchChildTotemInit( attacker, target, uniqueIdx, infClassIdx, skillClassIdx, parentIdx ) )
	{
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence pNewInfluenceObject->Init err");
		SAFE_DELETE( pNewObject );
		return false;
	}

	if( !( mInfluenceMap.Insert( uniqueIdx, pNewObject ) ) )
	{
		NETWORK2->PostServerEvent("cSkillManager::AddInfluence mInfluenceMap duplicate");
		SAFE_DELETE( pNewObject );
		return false;
	}

	/// 타겟 효과 리스트에 추가
	if( pTarget->AddInfluence( uniqueIdx, pNewObject->GetInfluenceClassIdx() ) == false )
	{
		NETWORK2->PostServerEvent( "cSkillManager::AddInfluence pTarget[%d,%d]->AddSkillInfluence[%d,%d] == false", 
			target.type, target.index, uniqueIdx, infClassIdx );
		return false;
	}

	if( msgSend == true )
	{
		MSG_SYN_INFLUENCE_CREATE msg;
		msg.Category = NM_SKILL;
		msg.Protocol = NM_SKILL_INFLUENCE_CREATE_SYN;
		msg.mTarget = target;
		msg.mAttacker = attacker;
		msg.mUniqueIdx = uniqueIdx;
		msg.mInfluenceClassIdx = infClassIdx;
		msg.mAuraSkillClassIdx = 0;
		msg.mDieDelete = pNewObject->GetDieDelete();
		msg.mPlusTime = 0;

		NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
	}


	//////////////////////////////////////////////////////////////////////////
	/// 적용부
	bool calcStatus = false;
	for( int i = 0 ; i < colNum ; ++i )
	{
		if( ApplyInfluence( pAttacker, pTarget, uniqueIdx, infClassIdx, type[i], value[i] ) == true )
			calcStatus = true;
	}


	//////////////////////////////////////////////////////////////////////////
	/// 스텟 계산
	if( calcStatus == true )
	{
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
			STATUSCALC->CalcPlayerExtensionGlobal( pTarget->GetObject() );
		else if( pTarget->GetObjectType() == eOBJECTTYPE_MONSTER )
			STATUSCALC->CalcMonster( target.index );
		else
			assert(NULL);
	}

	return true;
}


bool cSkillManager::ItemCheckAddInf( cBaseObject* pAttacker, cBaseObject* pTarget, unsigned long infClassIdx )
{

	if( pAttacker == NULL )
		return false;

	if( pTarget == NULL )
		return false;

	if( pAttacker->GetStateDie() == true )
		return false;

	const int colNum = 3;
	sInfluenceScript* pScript = SKILLSCRIPT->GetInfluenceInfo( infClassIdx );
	if( pScript == NULL )
		return false;

	long type[colNum] = { pScript->mStatusPlusIdx1, pScript->mStatusPlusIdx2, pScript->mStatusPlusIdx3 };
	long value[colNum] = { pScript->mValue1, pScript->mValue2, pScript->mValue3 };

	for( int i = 0 ; i < colNum ; ++i )
	{
		/// 죽었을때만 가능한 효과 체크
		if( pTarget->GetStateDie() == true )
		{
			switch( type[i] )
			{
			case eSTATUSPLUS_RESURRECTION_HP_PLUS:
			case eSTATUSPLUS_RESURRECTION_HP_PER:
			case eSTATUSPLUS_RESURRECTION_MP_PLUS:
			case eSTATUSPLUS_RESURRECTION_MP_PER:
				if( pTarget->GetObjectType() != eOBJECTTYPE_PLAYER || value[i] <= 0 )
					return false;
			default:
				return false;
			}
		}

		/// 정상적인 상태일때 효과 체크
		switch( type[i] )
		{
		case eSTATUSPLUS_BLINK:
			{
				if( pTarget->GetObjectType() != eOBJECTTYPE_PLAYER )
					return false;
					
				cPlayer* pPlayer = (cPlayer*)pTarget;
				if( pPlayer->GetState() != eOBJECT_STATE_ATTACK )
					return false;
			}
			break;
		case eSTATUSPLUS_CHANGE_MONSTER:
			if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
			{
				cPlayer* pPlayer = (cPlayer*)pTarget;
				if( !(pPlayer->GetState() == eOBJECT_STATE_IDLE || pPlayer->GetState() == ePLAYER_STATE_ITEMPICK ) )
					return false;

				/// 
				//if( pPlayer->IsVehicle() == true )
				//{
				//	return false;
				//}
			}
			break;
		}
	}

	return true;
}


bool cSkillManager::ApplyInfluence( cBaseObject* pAttacker, cBaseObject* pTarget, unsigned long infObjectIdx, unsigned long infClassIdx, 
								   unsigned long type, long value )
{

	bool statusCalc = false;

	if( pAttacker == NULL )
		return statusCalc;

	if( pTarget == NULL )
		return statusCalc;

	if( pAttacker->GetStateDie() == true )
		return statusCalc;

	/// 스텟 계산을 하는지만 체크하는 부분
	if( ( eSTATUSPLUS_STR_PLUS <= type && type <= eSTATUSPLUS_BASE_MAX ) ||
		( eSTATUSPLUS_ATK_PLUS <= type && type <= eSTATUSPLUS_BUFF_ETC_MAX ) || 
		eSTATUSPLUS_CHAR_SIZE == type || eSTATUSPLUS_CHANGE_MONSTER == type )
	{
		statusCalc = true;
	}

	switch( type )
	{
	case eSTATUSPLUS_DISPEL:
		SKILLMANAGER->DispelInfluence( pAttacker->GetObject(), value );
		break;
	case eSTATUSPLUS_RESURRECTION_HP_PLUS:
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )	/// 플레이어만 부활
		{
			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( pPlayer->GetStateDie() == true && value > 0 )
			{
				/// 부활시 hp회복량 설정
				pPlayer->SkillResurrectionHP( value );

				/// 클라이언트에 부활 요청 메세지 발송
				HANDLE handle = NULL;
				MSG_RES_SKILL_RESURRECTION* sendMsg = (MSG_RES_SKILL_RESURRECTION*)NETWORK2->GetMsgRoot( &handle, pPlayer->GetConnectionIdx( ) );
				if ( sendMsg != NULL )
				{
					sendMsg->Category    = NM_SKILL;
					sendMsg->Protocol    = NM_SKILL_RESURRECTION_RES;
					sendMsg->mAttackerIdx = pAttacker->GetObjectID();
					NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_RESURRECTION) );
				}
			}
		}
		break;
	case eSTATUSPLUS_RESURRECTION_HP_PER:
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )	/// 플레이어인지 체크
		{
			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( pPlayer->GetStateDie() == true && value > 0 )
			{
				long resurrectionValue = pPlayer->GetMaxHP() * value / 100;

				/// 부활시 hp회복량 설정
				pPlayer->SkillResurrectionHP( resurrectionValue );

				/// 클라이언트에 부활 요청 메세지 발송
				HANDLE handle = NULL;
				MSG_RES_SKILL_RESURRECTION* sendMsg = (MSG_RES_SKILL_RESURRECTION*)NETWORK2->GetMsgRoot( &handle, pPlayer->GetConnectionIdx( ) );
				if ( sendMsg != NULL )
				{
					sendMsg->Category    = NM_SKILL;
					sendMsg->Protocol    = NM_SKILL_RESURRECTION_RES;
					sendMsg->mAttackerIdx = pAttacker->GetObjectID();
					NETWORK2->SendMsgRoot( handle, sizeof(MSG_RES_SKILL_RESURRECTION) );
				}
			}
		}
		break;
	case eSTATUSPLUS_RESURRECTION_MP_PLUS:
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )	/// 플레이어인지 체크
		{
			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( pPlayer->GetStateDie() == true )
			{
				/// 부활시 mp회복량 설정
				/// ※ SkillResurrectionHP 가 설정된경우만 셋팅됨
				pPlayer->SkillResurrectionMP( value );
			}
		}
		break;
	case eSTATUSPLUS_RESURRECTION_MP_PER:
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )	/// 플레이어인지 체크
		{
			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( pPlayer->GetStateDie() == true )
			{
				long resurrectionValue = pPlayer->GetMaxMP() * value / 100;

				/// 부활시 mp회복량 설정
				/// ※ SkillResurrectionHP 가 설정된경우만 셋팅됨
				pPlayer->SkillResurrectionMP( resurrectionValue );
			}
		}
		break;
	case eSTATUSPLUS_BLINK:
		if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
		{
			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( pPlayer->GetState() != eOBJECT_STATE_ATTACK )
				return statusCalc;

			pPlayer->Blink();	
		}
		break;
	case eSTATUSPLUS_DISPEL_ALL:
		SKILLMANAGER->DispelInfluenceAll( pTarget->GetObject() );
		break;
	case eSTATUSPLUS_HP_ABSORB_INS:
		break;
	case eSTATUSPLUS_MP_ABSORB_INS:
		break;
	case eSTATUSPLUS_MONSTER_ESCAPE:
		if( pTarget->GetObjectType() == eOBJECTTYPE_MONSTER )
			((cMonster*)pTarget)->SetEscape();
		break;

	case eSTATUSPLUS_NONSKILL_HPCHANGE_PLUS:
		{
			bool die = false;

			if( value > 0 )
				pTarget->HPHeal( value, false, pAttacker->GetObjectID(), 0, eTAKEDAMAGETYPE_POTION );
			else
			{
				/// 잘못된 스크립트
				NETWORK2->PostServerEvent("eSTATUSPLUS_NONSKILL_HPCHANGE_PLUS influenceValue <= 0" );
				pTarget->HPDamage( abs(value), &die, false );
			}

			MSG_SYN_SKILL_NONSKILL_HPCHANGE msg; 
			msg.Category = NM_SKILL;
			msg.Protocol = NM_SKILL_NONSKILL_HPCHANGE_SYN;
			msg.mInfluenceClassIdx = infClassIdx;
			msg.mTarget = pTarget->GetObject();
			msg.mChangeValue = value;
			msg.mObjectHP = pTarget->GetHP();
			msg.mObjectMaxHP = pTarget->GetMaxHP();

			NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
		}
		break;
	case eSTATUSPLUS_NONSKILL_HPCHANGE_PER:
		break;
	case eSTATUSPLUS_NONSKILL_MPCHANGE_PLUS:
		{
			if( value > 0 )
			{
				/// MP회복
				pTarget->MPHeal( value, false );
			}
			else
			{
				/// 잘못된 스크립트
				assert(NULL);
				NETWORK2->PostServerEvent("eSTATUSPLUS_NONSKILL_MPCHANGE_PLUS influenceValue <= 0" );
				pTarget->MPHeal( abs(value), false );
			}

			MSG_SYN_SKILL_NONSKILL_MPCHANGE msg; 
			msg.Category = NM_SKILL;
			msg.Protocol = NM_SKILL_NONSKILL_MPCHANGE_SYN;
			msg.mInfluenceClassIdx = infClassIdx;
			msg.mTarget = pTarget->GetObject();
			msg.mChangeValue = value;
			msg.mObjectMP = pTarget->GetMP();
			msg.mObjectMaxMP = pTarget->GetMaxMP();

			NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
		}
		break;
	case eSTATUSPLUS_NONSKILL_MPCHANGE_PER:
		break;
	case eSTATUSPLUS_CHAR_SIZE:
		break;
	case eSTATUSPLUS_CHANGE_MONSTER:
		{
			if( pTarget->GetObjectType() != eOBJECTTYPE_PLAYER )
				return false;

			cPlayer* pPlayer = (cPlayer*)pTarget;
			if( !(pPlayer->GetState() == eOBJECT_STATE_IDLE || pPlayer->GetState() == ePLAYER_STATE_ITEMPICK ) )
				return statusCalc;

			pPlayer->SetChgMonster( infObjectIdx, value );
		}
		break;
	case eSTATUSPLUS_VEHICLE:
		{
			if( pTarget->GetObjectType() != eOBJECTTYPE_PLAYER )
				return statusCalc;

			cPlayer* pPlayer = (cPlayer*)pTarget;
			pPlayer->SetVehicle( infObjectIdx, value );
		}
		break;
	}

	return statusCalc;
}

void cSkillManager::DispelInfluence( sObject target, unsigned long influenceTypeDetail )
{
	/// 타겟 포인터 확인
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( pTarget == NULL )
		return;

	/// 플레이어의 보유 효과 해쉬셋을 읽어옴
	tHashSet<unsigned long>* pHashSet = pTarget->GetInfluenceSet();

	tHashSet<unsigned long>::cIterator begin = pHashSet->Begin();
	tHashSet<unsigned long>::cIterator end = pHashSet->End();

	cInfluenceObject* pHaveObejct = NULL;
	sInfluenceScript* pHaveScript = NULL;
	unsigned long uniqueIdx;

	/// 전체 검색
	while( begin != end )
	{
		uniqueIdx = *begin++;
		pHaveObejct = SKILLMANAGER->GetInfluence( uniqueIdx );

		if( pHaveObejct == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("pHaveInfluenceObejct err - unique[%d]", uniqueIdx );
			//-/-//InsertDeleteInfluenceObject( pHaveObejct );	/// 객체 삭제 대기열에 넣는다.
			continue;
		}

		/// 보유한 효과의 스크립트 정보를 읽어옴
		pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveObejct->GetInfluenceClassIdx() );
		if( pHaveScript == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("DispelInfluence InfluenceClassIdx err - [%d]", pHaveObejct->GetInfluenceClassIdx() );
			continue;
		}

		/// 입력한 디스펠 정보와 보유 효과 정보가 같으면 효과 제거
		if( pHaveScript->mTypeDetail == influenceTypeDetail )
		{
			DeleteInfluence( target, uniqueIdx );
			break;
		}
	}
}


void cSkillManager::DispelInfluenceAll( sObject target )
{
	/// 타겟 포인터 확인
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( pTarget == NULL )
		return;

	/// 타겟 객체의 보유 효과 해시셋을 읽어옴
	tHashSet<unsigned long>* pHashSet = pTarget->GetInfluenceSet();

	tHashSet<unsigned long>::cIterator begin = pHashSet->Begin();
	tHashSet<unsigned long>::cIterator end = pHashSet->End();

	cInfluenceObject* pHaveObejct = NULL;
	sInfluenceScript* pHaveScript = NULL;
	unsigned long uniqueIdx;

	/// 전체 검색
	while( begin != end )
	{
		uniqueIdx = *begin++;
		pHaveObejct = SKILLMANAGER->GetInfluence( uniqueIdx );

		if( pHaveObejct == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("pHaveInfluenceObejct err - unique[%d]", uniqueIdx );
			//-/-//InsertDeleteInfluenceObject( pHaveObejct );	/// 객체 삭제 대기열에 넣는다.
			continue;
		}

		/// 보유한 효과의 스크립트 정보를 읽어옴
		pHaveScript = SKILLSCRIPT->GetInfluenceInfo( pHaveObejct->GetInfluenceClassIdx() );
		if( pHaveScript == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("DispelInfluence InfluenceClassIdx err - [%d]", pHaveObejct->GetInfluenceClassIdx() );
			continue;
		}

		/// 디스펠 전체는 디버프만 제거한다.
		if( pHaveScript->mType != eINFLUENCETYPE_DEBUF )
			continue;

		SKILLMANAGER->DeleteInfluenceList( uniqueIdx );
	}
}


bool cSkillManager::ClientAuraInfluenceOff( unsigned long playerIdx, unsigned long uniqueIdx, unsigned long skillClassIdx )
{
	cInfluenceObject* pInfluenceObject = (cInfluenceObject*)mInfluenceMap.GetAt( uniqueIdx );
	if( pInfluenceObject == NULL )
		return false;

	/// 스킬 번호가 같은지 확인
	if( pInfluenceObject->GetCopySkillClassIdx() != skillClassIdx )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("ClientAuraInfluenceOff skillClassIdx ERROR [%d,%d,%d]", playerIdx, uniqueIdx, skillClassIdx );
		return false;;
	}

	/// 부모 효과인지 확인
	if( pInfluenceObject->GetSwitchType() != eSWITCHTYPE_PARENT )
		return false;

	/// 효과 주인 확인
	if( pInfluenceObject->GetTarget().index != playerIdx ||
		pInfluenceObject->GetTarget().type != eOBJECTTYPE_PLAYER )
	{
		/// 조작된 메세지
		assert(NULL);
		NETWORK2->PostServerEvent("ClientAuraInfluenceOff Hack? player[%d,%d,%d]", playerIdx, uniqueIdx, skillClassIdx );
		return false;
	}

	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( pPlayer == NULL )
		return false;

	SKILLMANAGER->DeleteInfluence( pPlayer->GetObject(), uniqueIdx );

	return true;
}


void cSkillManager::AuraInfluenceOff( unsigned long playerIdx, unsigned long skillClassIdx )
{
	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( pPlayer != NULL )
	{
		cHashSet* pInfSet = pPlayer->GetInfluenceSet();
		cHashSet::cIterator begin = pInfSet->Begin();
		cHashSet::cIterator end = pInfSet->End();

		unsigned long influenceIdx;
		while( begin != end )
		{
			influenceIdx = *begin++;

			cInfluenceObject* pInf = GetInfluence( influenceIdx );
			if( pInf == NULL )
			{
				NETWORK2->PostServerEvent("cSkillManager::AuraInfluenceOff pInf[%d] == NULL", playerIdx, skillClassIdx );
				//-/-//InsertDeleteInfluenceObject( pInf );	/// 객체 삭제 대기열에 넣는다.
				continue;
			}

			if( pInf->GetCopySkillClassIdx() != skillClassIdx )
				continue;

			if( pInf->GetSwitchType() != eSWITCHTYPE_PARENT )
				continue;

			if( pInf->GetTarget().index != playerIdx ||
				pInf->GetTarget().type != eOBJECTTYPE_PLAYER )
				continue;

			SKILLMANAGER->DeleteInfluence( pPlayer->GetObject(), influenceIdx );
			return;
		}
	}
}

void cSkillManager::InfluenceOff( unsigned long playerIdx, unsigned long influenceClassIdx )
{
	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( pPlayer != NULL )
	{
		cHashSet* pInfSet = pPlayer->GetInfluenceSet();
		cHashSet::cIterator i = pInfSet->Begin();
		cHashSet::cIterator end = pInfSet->End();

		for( ; i != end; ++i )
		{
			unsigned long uniqueIndex = (unsigned long)(*i);
			cInfluenceObject* pInf = GetInfluence( uniqueIndex );
			if( pInf == NULL )
			{
				NETWORK2->PostServerEvent("cSkillManager::InfluenceOff pInf[%d, %d, %d] == NULL", playerIdx, uniqueIndex, influenceClassIdx );
				continue;
			}

			if( pInf->GetInfluenceClassIdx() != influenceClassIdx )
				continue;

			if( pInf->GetTarget().index != playerIdx ||
				pInf->GetTarget().type != eOBJECTTYPE_PLAYER )
				continue;

			SKILLMANAGER->DeleteInfluence( pPlayer->GetObject(), uniqueIndex );
			return;
		}
	}
}


void cSkillManager::AddAuraChild( auraTemp auraChild )
{
	mAura.PushBack( auraChild );
}


void cSkillManager::DeleteInfluence( sObject target, unsigned long uniqueIdx )
{
	/// 타겟 객체 목록에 들어있는 효과 번호를 지운다.
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( pTarget != NULL )
	{
		pTarget->DeleteInfluence( uniqueIdx );
		pTarget->StatusCalc();
	}

	/// 효과 객체를 지운다.
	cInfluenceObject* pInf = GetInfluence( uniqueIdx );
	if( pInf != NULL )
	{
		/// 토템에 의해 만들어진 효과면 삭제시 부모토템에서 목록에서 뺌
		if( pInf->IsTotemInf() == true )
			pInf->TotemSetEraseTarget();

		if( pTarget )
		{
			///
			const int maximum = 3;
			sInfluenceScript* pScript = SKILLSCRIPT->GetInfluenceInfo( pInf->GetInfluenceClassIdx() );
			if( pScript != NULL )
			{
				unsigned short idx[maximum] = { pScript->mStatusPlusIdx1, pScript->mStatusPlusIdx2, pScript->mStatusPlusIdx3 };

				for( int i = 0 ; i < maximum ; ++i )
				{
					switch( idx[i] )
					{
					case eSTATUSPLUS_CHANGE_MONSTER:
						{
							if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
								((cPlayer*)pTarget)->EndChgMonster(); 
						}
						break;
					case eSTATUSPLUS_VEHICLE:
						{
							if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
								((cPlayer*)pTarget)->SendEndVehicle(); 
						}
						break;
					}
				}
			}
			else
				NETWORK2->PostServerEvent("cPlayer[%d,%d]::EraseSkillInfluence SKILLSCRIPT->GetInfluenceInfo(%d) == NULL", 
				target.type, target.index, pInf->GetInfluenceClassIdx() );

			///
			if( pInf->IsSendDelMsg() == true )
			{
				MSG_SYN_INFLUENCE_DELETE msg;
				msg.Category = NM_SKILL;
				msg.Protocol = NM_SKILL_INFLUENCE_DELETE_SYN;
				msg.mUniqueIdx = uniqueIdx;

				NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
			}
		}

		/// 제거
		SAFE_DELETE( pInf );
		mInfluenceMap.Erase( uniqueIdx );
	}

	if( pTarget != NULL )
		pTarget->StatusCalc();
}


void cSkillManager::DeleteInfluenceClassIdx( sObject target, unsigned long classIdx )
{
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( pTarget == NULL )
		return;

	cHashSet* pInfSet = pTarget->GetInfluenceSet();
	if( pInfSet == NULL )
		return;

	cHashSet::cIterator start = pInfSet->Begin();
	cHashSet::cIterator end = pInfSet->End();

	unsigned long infIdx = 0;
	cInfluenceObject* pInfluence = NULL;
	while( start != end )
	{
		infIdx = *start++;
		pInfluence = SKILLMANAGER->GetInfluence( infIdx );
		if( pInfluence == NULL )
		{
			NETWORK2->PostServerEvent("cSkillManager::DeleteInfluenceClassIdx  pInfluence[%d,%d] == NULL", target.type, target.index );
			continue;
		}

		if( pInfluence->GetInfluenceClassIdx() != classIdx )
			continue;        

		SKILLMANAGER->DeleteInfluenceList( infIdx );
	}
}


void cSkillManager::VehicleDeleteInfluence( sObject target, unsigned long uniqueIdx, bool sendSync )
{
	/// 타겟 객체 목록에 들어있는 효과 번호를 지운다.
	cBaseObject* pTarget = OBJECTMANAGER->GetObject( target.type, target.index );
	if( pTarget != NULL )
	{
		pTarget->DeleteInfluence( uniqueIdx );
		pTarget->StatusCalc();
	}

	/// 효과 객체를 지운다.
	cInfluenceObject* pInf = GetInfluence( uniqueIdx );
	if( pInf != NULL )
	{
		if( pTarget )
		{
			///
			const int maximum = 3;
			sInfluenceScript* pScript = SKILLSCRIPT->GetInfluenceInfo( pInf->GetInfluenceClassIdx() );
			if( pScript != NULL )
			{
				unsigned short idx[maximum] = { pScript->mStatusPlusIdx1, pScript->mStatusPlusIdx2, pScript->mStatusPlusIdx3 };

				for( int i = 0 ; i < maximum ; ++i )
				{
					switch( idx[i] )
					{
					case eSTATUSPLUS_CHANGE_MONSTER:
						{
							if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
								((cPlayer*)pTarget)->EndChgMonster(); 
						}
						break;
					case eSTATUSPLUS_VEHICLE:
						{
							if( sendSync )
							{
								if( pTarget->GetObjectType() == eOBJECTTYPE_PLAYER )
									((cPlayer*)pTarget)->SendEndVehicleExcept(); 
							}
						}
						break;
					}
				}
			}
			else
				NETWORK2->PostServerEvent("cPlayer[%d,%d]::EraseSkillInfluence SKILLSCRIPT->GetInfluenceInfo(%d) == NULL", 
				target.type, target.index, pInf->GetInfluenceClassIdx() );

			///
			if( pInf->IsSendDelMsg() == true )
			{
				MSG_SYN_INFLUENCE_DELETE msg;
				msg.Category = NM_SKILL;
				msg.Protocol = NM_SKILL_INFLUENCE_DELETE_SYN;
				msg.mUniqueIdx = uniqueIdx;

				NETWORK2->QuickSend( pTarget, (char*)&msg, sizeof(msg) );
			}
		}

		/// 제거
		SAFE_DELETE( pInf );
		mInfluenceMap.Erase( uniqueIdx );
	}
}


void cSkillManager::DeleteInfluenceList( unsigned long uniqueIdx )
{
	cInfluenceObject* pInf = GetInfluence( uniqueIdx );
	if( pInf != NULL )
		pInf->DelReady(); 

	mDeleteInfluenceAry.PushBack( uniqueIdx );
}


void cSkillManager::ClientDeleteInfluenceObject( unsigned long playerIdx, unsigned long uniqueIdx )
{

	cInfluenceObject* pInfluenceObject = (cInfluenceObject*)mInfluenceMap.GetAt( uniqueIdx );
	if( pInfluenceObject == NULL )
		return;	/// 메세지 지연으로 나올수 있음

	/// 조작된 메세지
	if( pInfluenceObject->GetTarget().index != playerIdx ||
		pInfluenceObject->GetTarget().type != eOBJECTTYPE_PLAYER )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("ClientDeleteInfluenceObject Hack");
		return;
	}

	sInfluenceScript* pInflueneScript = SKILLSCRIPT->GetInfluenceInfo( pInfluenceObject->GetInfluenceClassIdx() );
	if( pInflueneScript == NULL )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("ClientDeleteInfluenceObject GetInfluenceInfo[%d]", pInfluenceObject->GetInfluenceClassIdx() );
		return;
	}

	/// 디버프 삭제 요청 불가
	if( pInflueneScript->mType == eINFLUENCETYPE_DEBUF )
		return;

	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( playerIdx );
	if( pPlayer == NULL )
		return;
	
	DeleteInfluence( pPlayer->GetObject(), uniqueIdx );
	return;
}


bool cSkillManager::NeedAddPlayerHaveSkill( unsigned long skillIdx, unsigned char skillStep, unsigned long* money, unsigned short* sp )
{
	/// 플레이어 스킬스크립트 참조
	sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
	if( !pSkillInfo )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::NeedAddPlayerHaveSkill pSkillInfo==NULL" );
		return false; 
	}

	/// 플레이어 스킬스텝 스크립트 참조
	if( skillStep < 0 || pSkillInfo->mStepCount <= skillStep )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::NeedAddPlayerHaveSkill pSkillInfo->mStepCount ERR" );
		return false; 
	}

	sPlayerSkillStepInfo* pSkillStep = &pSkillInfo->mpSetpInfoArray[skillStep];
	if( pSkillStep == NULL )
	{
		assert(NULL);
		NETWORK2->PostServerEvent("cSkillManager::NeedAddPlayerHaveSkill pSkillStep==NULL" );
		return false;
	}

	*money = pSkillStep->mUseMoney;
	*sp = pSkillStep->mRequireSP;

	return true;

}



void cSkillManager::UpdateSkillCoolTime( sObject attacker, unsigned long skillIdx, unsigned char skillStep, unsigned long /*accumTime*/, float speedFactor )
{
	/// 플레이어만 사용 몬스터는 따로 가지고 있다
	switch( attacker.type )
	{
	case eOBJECTTYPE_HERO:
	case eOBJECTTYPE_PLAYER:
		{
			/// 플레이어 스킬스크립트 참조
			sPlayerSkillBaseInfo* pSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( skillIdx );
			if( !pSkillInfo )	
			{ 
				assert(NULL); 
				NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime pSkillInfo[%d]==NULL", skillIdx );
				return; 
			}

			/// 플레이어 스킬스텝 스크립트 참조
			if( skillStep < 0 || pSkillInfo->mStepCount <= skillStep )	
			{ 
				assert(NULL); 
				NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime pSkillInfo->mStepCount[%d] ERR", pSkillInfo->mStepCount );
				return; 
			}
			sPlayerSkillStepInfo* pSkillStep = &pSkillInfo->mpSetpInfoArray[skillStep];

			/// 플레이어 보유스킬 포인트 획득
			cPointerHashMap* pHaveSkillMap = (cPointerHashMap*)GetPlayerHaveSkill( attacker.index );
			if( !pHaveSkillMap )	
			{ 
				assert(NULL); 
				NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime pHaveSkillMap[%d]==NULL", attacker.index );
				return; 
			}

			/// 쿨타임, 글로벌 쿨타임 없는 경우 셋팅없이 정상 종료
			if( pSkillInfo->mIsGlobalCoolTime == false && pSkillStep->mCoolTime == 0 )	
			{ 
				/// 쿨타임이 없는데 그룹이 있는 경우는 스크립트오류
				if( pSkillInfo->mGroupCoolTimeIdx != 0 )	
				{ 
					assert(NULL); 
					NETWORK2->PostServerEvent("pSkillInfo->mIsGlobalCoolTime == false && pSkillInfo->mGroupCoolTimeIdx != 0" );
				}
				return; 
			}

			/// 허용치를 포함한 쿨타임
			unsigned long fixCoolTime = (unsigned long)( pSkillStep->mCoolTime * ( 2.0f - speedFactor ) );
			/// 공격 허용 시간
			if( fixCoolTime - SYNC_NORMALATTACK > 0 )
				fixCoolTime = fixCoolTime - SYNC_NORMALATTACK;
			else
			{
				fixCoolTime = 0; 
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime[%d] fixCoolTime == 0", skillIdx );
			}					

			/// 보유스킬 포인터
			cHaveSkillObject* pHaveSkill = NULL;
			/// 보유스킬의 스킬 기본정보
			sPlayerSkillBaseInfo* pHaveSkillInfo = NULL;

			/// 그룹이 없는 경우 자신만 쿨타임 증가
			if( pSkillInfo->mGroupCoolTimeIdx == 0 )
			{
				pHaveSkill = GetPlayerHaveSkill( attacker.index, skillIdx );
				if( pHaveSkill == NULL ) 
				{ 
					assert(NULL); 
					NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime GetPlayerHaveSkill[%d,%d]", attacker.index, skillIdx );
					return; 
				}

				pHaveSkill->UpdateCoolTime( fixCoolTime );
			}
			else
			{
				/// 보유 스킬들의 쿨타임 관련 항목 업데이트
				cPointerHashMap::cIterator begin = pHaveSkillMap->Begin();
				cPointerHashMap::cIterator end = pHaveSkillMap->End();

				for( ; begin != end ; ++begin )
				{

					pHaveSkill = (cHaveSkillObject*)(*begin).mSecond;
					if( pHaveSkill == NULL )
					{
						assert(NULL);
						NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime pHaveSkill==NULL" );
						continue;
					}             

					/// 그룹 쿨타임이 없는스킬은 통과
					if( pSkillInfo->mGroupCoolTimeIdx == 0 )
						continue;

					/// 보유스킬과 사용 스킬이 같은 그룹인지 체크
					pHaveSkillInfo = SKILLSCRIPT->GetPlayerSkillInfo( pHaveSkill->GetSkillIdx() );
					if( !pHaveSkillInfo ) 
					{ 
						assert(NULL); 
						NETWORK2->PostServerEvent("cSkillManager::UpdateSkillCoolTime pHaveSkillInfo==NULL" );
						continue; 
					}

					if( pSkillInfo->mGroupCoolTimeIdx == pHaveSkillInfo->mGroupCoolTimeIdx )
					{
						/// 같은 그룹인경우 사용 스킬의 쿨타임으로 갱신
						pHaveSkill->UpdateCoolTime( fixCoolTime );
					}
				}
			}
		}break;
	}
}


void cSkillManager::AddSkillReset( unsigned long playerIdx, unsigned long skillIdx, unsigned char skillStep )
{
	/// playeridx로 플레이어가 보유한 스킬리스트 포인터를 얻어옴
	cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( playerIdx );
	/// 보유한 스킬리스트가 없으면
	if ( !pPlayerHaveSkillMap )
	{
		/// 새로운 스킬리스트를 만듬
		pPlayerHaveSkillMap = new cPointerHashMap;

		/// 새로 만든 스킬리스트를 기록
		if( !( mPlayerHaveSkillMap.Insert( playerIdx, pPlayerHaveSkillMap ) ) )
		{
			NETWORK2->PostServerEvent("AddSkillReset[%d,%d,%d] - mPlayerHaveSkillMap.Insert error", playerIdx, skillIdx, skillStep );
			SAFE_DELETE( pPlayerHaveSkillMap );
			return;
		}
	}

	/// 보유스킬리스트 정보를 기록
	cHaveSkillObject* pPlayerHaveSkill = new cHaveSkillObject;
	pPlayerHaveSkill->Init( skillIdx, skillStep, 0 );

	if( !(pPlayerHaveSkillMap->Insert( skillIdx, pPlayerHaveSkill ) ) )
	{
		NETWORK2->PostServerEvent("AddSkillReset[%d,%d,%d] - mPlayerHaveSkillMap.Insert error", playerIdx, skillIdx, skillStep  );
		SAFE_DELETE( pPlayerHaveSkill );
		return;
	}
}


bool cSkillManager::SaveSkillCoolTime( unsigned long playerIdx, SKILL_COOLTIME* skillCoolTime )
{

	skillCoolTime->mCharacterIdx = playerIdx;
	skillCoolTime->mRetvalue = 0;

	/// 플레이어 보유스킬 포인트 획득
	cPointerHashMap* pHaveSkillMap = (cPointerHashMap*)GetPlayerHaveSkill( playerIdx );
	if( !pHaveSkillMap )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SaveSkillCoolTime pHaveSkillMap==NULL" );
		return false; 
	}

	cPointerHashMap::cIterator begin = pHaveSkillMap->Begin();
	cPointerHashMap::cIterator end = pHaveSkillMap->End();

	/// 보유스킬 포인터
	cHaveSkillObject* pHaveSkill = NULL;
	long restTime = 0;
	unsigned long row = 0;

	for( ; begin != end ; ++begin )
	{
		pHaveSkill = (cHaveSkillObject*)((*begin).mSecond);
		if( pHaveSkill == NULL )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("cSkillManager::SaveSkillCoolTime pHaveSkill==NULL" );
			continue;
		}

		/// 기본 공격 통과
		if( pHaveSkill->GetSkillIdx() <= NORMAL_ATTACK_SKILL_MAX )
			continue;

		/// 남은 쿨타임이 없는 경우 통과
		if( pHaveSkill->DbSaveDataCoolTime() <= NETWORK2->GetAccumTime() )
			continue;

		/// 재사용 가능시간이 DB_SAVE_COOLTIME ms초 이상인 것들만 저장
		restTime = pHaveSkill->DbSaveDataCoolTime() - NETWORK2->GetAccumTime();
		if( restTime > DB_SAVE_COOLTIME && pHaveSkill->GetSkillIdx() != 0 )
		{
			skillCoolTime->mTable[row].mSkillIdx = pHaveSkill->GetSkillIdx();
			skillCoolTime->mTable[row].mSkillStep = pHaveSkill->GetSkillStep();
			skillCoolTime->mTable[row].mEndCoolTime = restTime;
			++row;
		}
	}

	skillCoolTime->mRowCount = row;

	return true;

}


bool cSkillManager::RestorePlayerSkill( SKILL_SELECT* skillSelect )
{
	/// 모유목록 전체 제거
	if( skillSelect->mRowCount >= 1 )
		ReleasePlayerHaveSkill( skillSelect->mCharacterIdx );

	/// 기본공격(idx:0) 넣어 줘야함
	SKILLMANAGER->AddPlayerHaveSkill( skillSelect->mCharacterIdx, NORMAL_ATTACK_SKILL, SKILL_START_STEP, 0 );

	long restTime;
	for ( long i = 0; i < skillSelect->mRowCount; i++ )
	{
		restTime = skillSelect->mTable[i].mEndCoolTime;

		/// 사용 가능 시간이 된경우
		if( restTime < 0 )	{ restTime = 0; }

		/// playeridx로 플레이어가 보유한 스킬리스트 포인터를 얻어옴
		cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( skillSelect->mCharacterIdx );
		/// 보유한 스킬리스트가 없으면
		if ( !pPlayerHaveSkillMap )
		{
			/// 새로운 스킬리스트를 만듬
			pPlayerHaveSkillMap = new cPointerHashMap;

			/// 새로 만든 스킬리스트를 기록
			if( !( mPlayerHaveSkillMap.Insert( skillSelect->mCharacterIdx, pPlayerHaveSkillMap ) ) )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::RestorePlayerSkill mPlayerHaveSkillMap.Insert duplicate" );
				SAFE_DELETE( pPlayerHaveSkillMap );
				return false;
			}
		}

		/// 보유스킬리스트 정보를 기록
		cHaveSkillObject* pPlayerHaveSkill = new cHaveSkillObject;
		pPlayerHaveSkill->RestoreInit( skillSelect->mTable[i].mSkillIdx, skillSelect->mTable[i].mSkillStep, restTime );

		if( !(pPlayerHaveSkillMap->Insert( skillSelect->mTable[i].mSkillIdx, pPlayerHaveSkill ) ) )
		{
			assert(NULL);
			NETWORK2->PostServerEvent("cSkillManager::RestorePlayerSkill pPlayerHaveSkillMap.Insert duplicate" );
			SAFE_DELETE( pPlayerHaveSkill );
			return false;
		}
	}

	return true;
}

void cSkillManager::SendMsgSkillHaveToPlayer( unsigned long connectionIdx, unsigned long characterIdx )
{
	HANDLE              handle  = NULL;
	MSG_RES_SKILL_LIST* sendMsg = (MSG_RES_SKILL_LIST*)NETWORK2->GetMsgRoot( &handle, connectionIdx, NM_SKILL, NM_SKILL_LIST_RES );

	if ( sendMsg != NULL )
	{
		cPointerHashMap* pPlayerHaveSkillMap = (cPointerHashMap*)mPlayerHaveSkillMap.GetAt( characterIdx );

		if( pPlayerHaveSkillMap )
		{
			cHaveSkillObject* pPlayerHaveSkill;

			cPointerHashMap::cIterator start = pPlayerHaveSkillMap->Begin();
			cPointerHashMap::cIterator end = pPlayerHaveSkillMap->End();

			unsigned char& row = (sendMsg->mRowCount = 0);
			for( ; start != end ; ++start )
			{
				pPlayerHaveSkill = (cHaveSkillObject*)(*start).mSecond;

				if( pPlayerHaveSkill == NULL )
				{
					assert(NULL);
					NETWORK2->PostServerEvent("cSkillManager::SendMsgSkillHaveToPlayer pPlayerHaveSkill==NULL" );
					continue;
				}

				if( pPlayerHaveSkill->GetSkillIdx() == 0 )	
					continue; 

				sendMsg->mTable[row].mSkillIdx     = pPlayerHaveSkill->GetSkillIdx();
				sendMsg->mTable[row].mSkillStep    = pPlayerHaveSkill->GetSkillStep();
				sendMsg->mTable[row].mRestCoolTime = pPlayerHaveSkill->GetRestTime();
				++row;
			}
		}

		NETWORK2->SendMsgRoot( handle, sendMsg->GetMsgLength( ) );
	}

	cPlayer* player = OBJECTMANAGER->GetPlayer( characterIdx );
	if( player )
		player->SendSkillPoint();
	else
		assert(0);
}



void cSkillManager::SendMsgInfluenceToPlayer( unsigned long connectionIdx, unsigned long characterIdx )
{
	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( characterIdx );
	if( !pPlayer )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SendMsgInfluenceToPlayer pPlayer[%d]==NULL", characterIdx);
		return; 
	}

	tHashSet<unsigned long>* pInfluenceSet = pPlayer->GetInfluenceSet();
	if( !pInfluenceSet )	
	{ 
		assert(NULL);
		NETWORK2->PostServerEvent("cSkillManager::SendMsgInfluenceToPlayer pInfluenceSet==NULL" );
		return; 
	}

	cInfluenceObject* pInfluenceObject = NULL;

	tHashSet<unsigned long>::cIterator start = pInfluenceSet->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();

	HANDLE                  handle  = NULL;
	MSG_RES_INFLUENCE_LIST* sendMsg = (MSG_RES_INFLUENCE_LIST*)NETWORK2->GetMsgRoot( &handle, connectionIdx, NM_SKILL, NM_SKILL_INFLUENCE_LIST_RES );

	if ( sendMsg != NULL )
	{
		unsigned char& row = (sendMsg->mRowCount = 0);
		for( ; start != end ; ++start )
		{
			pInfluenceObject = GetInfluence( (*start) );
			if( pInfluenceObject == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::SendMsgInfluenceToPlayer pInfluenceObject[%d]==NULL", (*start) );
				//-/-//InsertDeleteInfluenceObject( pInfluenceObject );	/// 객체 삭제 대기열에 넣는다.
				continue;
			}

			sendMsg->mTable[row].mUniqueIdx         = pInfluenceObject->GetUniqueIdx();
			sendMsg->mTable[row].mInfluenceClassIdx = pInfluenceObject->GetInfluenceClassIdx();
			sendMsg->mTable[row].mAuraSkillClassIdx = pInfluenceObject->GetCopySkillClassIdx();
			sendMsg->mTable[row].mRestTime          = pInfluenceObject->GetInfluenceRestTime();	
			sendMsg->mTable[row].mDieDelete         = pInfluenceObject->GetDieDelete();
			++row;
		}

		NETWORK2->SendMsgRoot( handle, sendMsg->GetMsgLength( ) );
	}
}



void cSkillManager::SendAddSynMsgInfluenceToPlayer( unsigned long connectionIdx, unsigned long characterIdx )
{
	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( characterIdx );
	if( !pPlayer )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SendAddSynMsgInfluenceToPlayer pPlayer[%d]==NULL", characterIdx );
		return; 
	}

	tHashSet<unsigned long>* pInfluenceSet = pPlayer->GetInfluenceSet();
	if( !pInfluenceSet )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SendAddSynMsgInfluenceToPlayer pInfluenceSet==NULL" );
		return; 
	}

	cInfluenceObject* pInfluenceObject = NULL;

	tHashSet<unsigned long>::cIterator start = pInfluenceSet->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();

	HANDLE                     handle  = NULL;
	MSG_SYN_INFLUENCE_ADDLIST* sendMsg = (MSG_SYN_INFLUENCE_ADDLIST*)NETWORK2->GetMsgRoot( &handle, connectionIdx, NM_SKILL, NM_SKILL_INFLUENCE_ADDLIST_SYN );

	if ( sendMsg != NULL )
	{
		sendMsg->mObject.type  = eOBJECTTYPE_PLAYER;
		sendMsg->mObject.index = characterIdx;

		unsigned char& row = (sendMsg->mRowCount = 0);
		for( ; start != end ; ++start )
		{
			pInfluenceObject = GetInfluence( (*start) );

			if( pInfluenceObject == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::SendAddSynMsgInfluenceToPlayer pInfluenceObject[%d]==NULL", (*start) );
				//-/-//InsertDeleteInfluenceObject( pInfluenceObject );	/// 객체 삭제 대기열에 넣는다.
				continue;
			}

			sendMsg->mTable[row].mUniqueIdx         = pInfluenceObject->GetUniqueIdx();
			sendMsg->mTable[row].mInfluenceClassIdx = pInfluenceObject->GetInfluenceClassIdx();
			sendMsg->mTable[row].mDieDelete			= pInfluenceObject->GetDieDelete();
			sendMsg->mTable[row].mRestTime          = pInfluenceObject->GetInfluenceRestTime();
			++row;
		}

		NETWORK2->SendMsgRoot( handle, sendMsg->GetMsgLength( ) );
	}
}



void cSkillManager::SendDelSynMsgInfluenceToPlayer( unsigned long connectionIdx, unsigned long characterIdx )
{
	cPlayer* pPlayer = OBJECTMANAGER->GetPlayer( characterIdx );
	if( !pPlayer )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SendAddSynMsgInfluenceToPlayer pPlayer[%d]==NULL", characterIdx );
		return; 
	}

	tHashSet<unsigned long>* pInfluenceSet = pPlayer->GetInfluenceSet();
	if( !pInfluenceSet )	
	{ 
		assert(NULL); 
		NETWORK2->PostServerEvent("cSkillManager::SendAddSynMsgInfluenceToPlayer pInfluenceSet==NULL" );
		return; 
	}

	cInfluenceObject* pInfluenceObject = NULL;

	tHashSet<unsigned long>::cIterator start = pInfluenceSet->Begin();
	tHashSet<unsigned long>::cIterator end = pInfluenceSet->End();

	HANDLE                     handle  = NULL;
	MSG_SYN_INFLUENCE_DELLIST* sendMsg = (MSG_SYN_INFLUENCE_DELLIST*)NETWORK2->GetMsgRoot( &handle, connectionIdx, NM_SKILL, NM_SKILL_INFLUENCE_DELLIST_SYN );

	if ( sendMsg != NULL )
	{
		sendMsg->mObject.type  = eOBJECTTYPE_PLAYER;
		sendMsg->mObject.index = characterIdx;

		unsigned char& row = (sendMsg->mRowCount = 0);
		for( ; start != end ; ++start )
		{
			pInfluenceObject = GetInfluence( (*start) );

			if( pInfluenceObject == NULL )
			{
				assert(NULL);
				NETWORK2->PostServerEvent("cSkillManager::SendDelSynMsgInfluenceToPlayer pInfluenceObject[%d]==NULL", (*start) );
				//-/-//InsertDeleteInfluenceObject( pInfluenceObject );	/// 객체 삭제 대기열에 넣는다.
				continue;
			}

			sendMsg->mUniqueIdx[row] = pInfluenceObject->GetUniqueIdx();
			++row;
		}

		NETWORK2->SendMsgRoot( handle, sendMsg->GetMsgLength( ) );
	}
}
