Jump to content

TheEnd

Member
  • Posts

    185
  • Joined

  • Last visited

  • Feedback

    0%

Posts posted by TheEnd

  1. hey..

     i I can not see description  Sash  and any item By time

    syserr:

    1027 15:18:50958 ::   File "ui.py", line 1921, in OnOverInItem
    
    1027 15:18:50959 ::   File "ui.py", line 88, in __call__
    
    1027 15:18:50959 ::   File "ui.py", line 79, in __call__
    
    1027 15:18:50959 ::   File "uisystemgems.py", line 291, in OverInItem
    
    1027 15:18:50959 ::   File "uiToolTip.py", line 1312, in AddItemData
    
    1027 15:18:50959 :: TypeError
    1027 15:18:50959 :: : 
    1027 15:18:50959 :: 'int' object has no attribute '__getitem__'
    1027 15:18:50959 :: 
    
    1027 15:18:52410 :: Traceback (most recent call last):
    
    1027 15:18:52410 ::   File "ui.py", line 1921, in OnOverInItem
    
    1027 15:18:52410 ::   File "ui.py", line 88, in __call__
    
    1027 15:18:52411 ::   File "ui.py", line 79, in __call__
    
    1027 15:18:52411 ::   File "uisystemgems.py", line 291, in OverInItem
    
    1027 15:18:52411 ::   File "uiToolTip.py", line 1455, in AddItemData
    
    1027 15:18:52411 :: TypeError
    1027 15:18:52411 :: : 
    1027 15:18:52411 :: 'int' object has no attribute '__getitem__'
    1027 15:18:52411 :: 

     

  2. #include "stdafx.h"
    #include <sstream>
    
    #include "utils.h"
    #include "config.h"
    #include "vector.h"
    #include "char.h"
    #include "char_manager.h"
    #include "battle.h"
    #include "desc.h"
    #include "desc_manager.h"
    #include "packet.h"
    #include "affect.h"
    #include "item.h"
    #include "sectree_manager.h"
    #include "mob_manager.h"
    #include "start_position.h"
    #include "party.h"
    #include "buffer_manager.h"
    #include "guild.h"
    #include "log.h"
    #include "unique_item.h"
    #include "questmanager.h"
    
    #define ENABLE_BLOCK_SKILL_OXEVENT
    
    extern int test_server;
    
    static const DWORD s_adwSubSkillVnums[] =
    {
    	SKILL_LEADERSHIP,
    	SKILL_COMBO,
    	SKILL_MINING,
    	SKILL_LANGUAGE1,
    	SKILL_LANGUAGE2,
    	SKILL_LANGUAGE3,
    	SKILL_POLYMORPH,
    	SKILL_HORSE,
    	SKILL_HORSE_SUMMON,
    	SKILL_HORSE_WILDATTACK,
    	SKILL_HORSE_CHARGE,
    	SKILL_HORSE_ESCAPE,
    	SKILL_HORSE_WILDATTACK_RANGE,
    	SKILL_ADD_HP,
    	SKILL_RESIST_PENETRATE
    };
    
    struct FPartyPIDCollector{
    		std::vector <DWORD> vecPIDs;
    		FPartyPIDCollector()
    {
    	}
    	void operator () (LPCHARACTER ch)
    	{
    		vecPIDs.push_back(ch->GetPlayerID());
    	}
    };
    
    time_t CHARACTER::GetSkillNextReadTime(DWORD dwVnum) const
    {
    	if (dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("vnum overflow (vnum: %u)", dwVnum);
    		return 0;
    	}
    
    	return m_pSkillLevels ? m_pSkillLevels[dwVnum].tNextRead : 0;
    }
    
    void CHARACTER::SetSkillNextReadTime(DWORD dwVnum, time_t time)
    {
    	if (m_pSkillLevels && dwVnum < SKILL_MAX_NUM)
    		m_pSkillLevels[dwVnum].tNextRead = time;
    }
    
    bool TSkillUseInfo::HitOnce(DWORD dwVnum)
    {
    	// 쓰지도않았으면 때리지도 못한다.
    	if (!bUsed)
    		return false;
    
    	sys_log(1, "__HitOnce NextUse %u current %u count %d scount %d", dwNextSkillUsableTime, get_dword_time(), iHitCount, iSplashCount);
    
    	if (dwNextSkillUsableTime && dwNextSkillUsableTime<get_dword_time() && dwVnum != SKILL_MUYEONG && dwVnum != SKILL_HORSE_WILDATTACK)
    	{
    		sys_log(1, "__HitOnce can't hit");
    
    		return false;
    	}
    
    	if (iHitCount == -1)
    	{
    		sys_log(1, "__HitOnce OK %d %d %d", dwNextSkillUsableTime, get_dword_time(), iHitCount);
    		return true;
    	}
    
    	if (iHitCount)
    	{
    		sys_log(1, "__HitOnce OK %d %d %d", dwNextSkillUsableTime, get_dword_time(), iHitCount);
    		iHitCount--;
    		return true;
    	}
    	return false;
    }
    
    
    
    bool TSkillUseInfo::UseSkill(bool isGrandMaster, DWORD vid, DWORD dwCooltime, int splashcount, int hitcount, int range)
    {
    	this->isGrandMaster = isGrandMaster;
    	DWORD dwCur = get_dword_time();
    
    	// 아직 쿨타임이 끝나지 않았다.
    	if (bUsed && dwNextSkillUsableTime > dwCur)
    	{
    		sys_log(0, "cooltime is not over delta %u", dwNextSkillUsableTime - dwCur);
    		iHitCount = 0;
    		return false;
    	}
    
    	bUsed = true;
    
    	if (dwCooltime)
    		dwNextSkillUsableTime = dwCur + dwCooltime;
    	else
    		dwNextSkillUsableTime = 0;
    
    	iRange = range;
    	iMaxHitCount = iHitCount = hitcount;
    
    	if (test_server)
    		sys_log(0, "UseSkill NextUse %u  current %u cooltime %d hitcount %d/%d", dwNextSkillUsableTime, dwCur, dwCooltime, iHitCount, iMaxHitCount);
    
    	dwVID = vid;
    	iSplashCount = splashcount;
    	return true;
    }
    
    int CHARACTER::GetChainLightningMaxCount() const
    { 
    	return aiChainLightningCountBySkillLevel[MIN(SKILL_MAX_LEVEL, GetSkillLevel(SKILL_CHAIN))];
    }
    
    void CHARACTER::SetAffectedEunhyung() 
    { 
    	m_dwAffectedEunhyungLevel = GetSkillPower(SKILL_EUNHYUNG); 
    }
    
    void CHARACTER::SetSkillGroup(BYTE bSkillGroup)
    {
    	if (bSkillGroup > 2) 
    		return;
    
    	if (GetLevel() < 5)
    		return;
    
    	m_points.skill_group = bSkillGroup; 
    
    	TPacketGCChangeSkillGroup p;
    	p.header = HEADER_GC_SKILL_GROUP;
    	p.skill_group = m_points.skill_group;
    
    	GetDesc()->Packet(&p, sizeof(TPacketGCChangeSkillGroup));
    }
    
    int CHARACTER::ComputeCooltime(int time)
    {
    	return CalculateDuration(GetPoint(POINT_CASTING_SPEED), time);
    }
    
    void CHARACTER::SkillLevelPacket()
    {
    	if (!GetDesc())
    		return;
    
    	TPacketGCSkillLevel pack;
    
    	pack.bHeader = HEADER_GC_SKILL_LEVEL;
    	thecore_memcpy(&pack.skills, m_pSkillLevels, sizeof(TPlayerSkill) * SKILL_MAX_NUM);
    	GetDesc()->Packet(&pack, sizeof(TPacketGCSkillLevel));
    }
    
    void CHARACTER::SetSkillLevel(DWORD dwVnum, BYTE bLev)
    {
    	if (NULL == m_pSkillLevels)
    		return;
    
    	if (dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("vnum overflow (vnum %u)", dwVnum);
    		return;
    	}
    
    #ifdef __7AND8TH_SKILLS__
    	if (dwVnum >= SKILL_ANTI_PALBANG && dwVnum <= SKILL_HELP_SALPOONG)
    	{
    		if (!SkillCanUp(dwVnum) && bLev != 0)
    			return;
    	}
    #endif
    
    	m_pSkillLevels[dwVnum].bLevel = MIN(40, bLev);
    
    	if (bLev >= 40)
    		m_pSkillLevels[dwVnum].bMasterType = SKILL_PERFECT_MASTER;
    	else if (bLev >= 30)
    		m_pSkillLevels[dwVnum].bMasterType = SKILL_GRAND_MASTER;
    	else if (bLev >= 20)
    		m_pSkillLevels[dwVnum].bMasterType = SKILL_MASTER;
    	else
    		m_pSkillLevels[dwVnum].bMasterType = SKILL_NORMAL;
    }
    
    bool CHARACTER::IsLearnableSkill(DWORD dwSkillVnum) const
    {
    	const CSkillProto * pkSkill = CSkillManager::instance().Get(dwSkillVnum);
    	if (!pkSkill)
    		return false;
    
    	if (GetSkillLevel(dwSkillVnum) >= SKILL_MAX_LEVEL)
    		return false;
    
    	if (pkSkill->dwType == 0)
    	{
    		if (GetSkillLevel(dwSkillVnum) >= pkSkill->bMaxLevel)
    			return false;
    
    		return true;
    	}
    
    	if (pkSkill->dwType == 5)
    	{
    		if (dwSkillVnum == SKILL_HORSE_WILDATTACK_RANGE && GetJob() != JOB_ASSASSIN)
    			return false; 
    
    		return true;
    	}
    
    	if (GetSkillGroup() == 0)
    		return false;
    
    	if (pkSkill->dwType - 1 == GetJob())
    		return true;
    
    	if (7 == pkSkill->dwType && JOB_WOLFMAN == GetJob())
    		return true;
    
    	if (pkSkill->dwType == 5)
    	{
    		if (SKILL_7_A_ANTI_TANHWAN <= dwSkillVnum && dwSkillVnum <= SKILL_7_D_ANTI_YONGBI)
    		{
    			for (int i = 0 ; i < 4 ; i++)
    			{
    				if (unsigned(SKILL_7_A_ANTI_TANHWAN + i) != dwSkillVnum)
    				{
    					if (0 != GetSkillLevel(SKILL_7_A_ANTI_TANHWAN + i))
    					{
    						return false;
    					}
    				}
    			}
    
    			return true;
    		}
    
    		if (SKILL_8_A_ANTI_GIGONGCHAM <= dwSkillVnum && dwSkillVnum <= SKILL_8_D_ANTI_BYEURAK)
    		{
    			for (int i = 0 ; i < 4 ; i++)
    			{
    				if (unsigned(SKILL_8_A_ANTI_GIGONGCHAM + i) != dwSkillVnum)
    				{
    					if (0 != GetSkillLevel(SKILL_8_A_ANTI_GIGONGCHAM + i))
    						return false;
    				}
    			}
    			
    			return true;
    		}
    	}
    
    	if (pkSkill->dwType == 6)
    	{
    		if (dwSkillVnum >= SKILL_CHAYEOL && dwSkillVnum <= SKILL_CHEONGRANG)
    		{
    			return true;
    		}
    	}
    
    #ifdef __7AND8TH_SKILLS__
    	if (pkSkill->dwType == 6)
    	{
    		if (dwSkillVnum >= SKILL_ANTI_PALBANG && dwSkillVnum <= SKILL_HELP_SALPOONG)
    		{
    			if (GetSkillLevel(dwSkillVnum) != 0)
    				return true;
    		}
    	}
    #endif
    
    	return false;
    }
    
    // ADD_GRANDMASTER_SKILL
    bool CHARACTER::LearnGrandMasterSkill(DWORD dwSkillVnum)
    {
    	CSkillProto * pkSk = CSkillManager::instance().Get(dwSkillVnum);
    
    	if (!pkSk)
    		return false;
    
    	if (!IsLearnableSkill(dwSkillVnum))
    	{
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("수련할 수 없는 스킬입니다."));
    		return false;
    	}
    
    	sys_log(0, "learn grand master skill[%d] cur %d, next %d", dwSkillVnum, get_global_time(), GetSkillNextReadTime(dwSkillVnum));
    
    	/*
    	   if (get_global_time() < GetSkillNextReadTime(dwSkillVnum))
    	   {
    	   if (!(test_server && quest::CQuestManager::instance().GetEventFlag("no_read_delay")))
    	   {
    	   if (FindAffect(AFFECT_SKILL_NO_BOOK_DELAY))
    	   {
    	// 주안술서 사용중에는 시간 제한 무시
    	RemoveAffect(AFFECT_SKILL_NO_BOOK_DELAY);
    	ChatPacket(CHAT_TYPE_INFO, LC_TEXT("주안술서를 통해 주화입마에서 빠져나왔습니다."));
    	}
    	else 	    
    	{
    	SkillLearnWaitMoreTimeMessage(GetSkillNextReadTime(dwSkillVnum) - get_global_time());
    	return false;
    	}
    	}
    	}
    	 */
    
    	// bType이 0이면 처음부터 책으로 수련 가능
    	if (pkSk->dwType == 0)
    	{
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("그랜드 마스터 수련을 할 수 없는 스킬입니다."));
    		return false;
    	}
    
    	if (GetSkillMasterType(dwSkillVnum) != SKILL_GRAND_MASTER)
    	{
    		if (GetSkillMasterType(dwSkillVnum) > SKILL_GRAND_MASTER)
    			ChatPacket(CHAT_TYPE_INFO, LC_TEXT("퍼펙트 마스터된 스킬입니다. 더 이상 수련 할 수 없습니다."));
    		else
    			ChatPacket(CHAT_TYPE_INFO, LC_TEXT("이 스킬은 아직 그랜드 마스터 수련을 할 경지에 이르지 않았습니다."));
    		return false;
    	}
    
    	std::string strTrainSkill;
    	{
    		std::ostringstream os;
    		os << "training_grandmaster_skill.skill" << dwSkillVnum;
    		strTrainSkill = os.str();
    	}
    
    	// 여기서 확률을 계산합니다.
    	BYTE bLastLevel = GetSkillLevel(dwSkillVnum);
    
    	int idx = MIN(9, GetSkillLevel(dwSkillVnum) - 30);
    
    	sys_log(0, "LearnGrandMasterSkill %s table idx %d value %d", GetName(), idx, aiGrandMasterSkillBookCountForLevelUp[idx]);
    
    	int iTotalReadCount = GetQuestFlag(strTrainSkill) + 1;
    	SetQuestFlag(strTrainSkill, iTotalReadCount);
    
    	int iMinReadCount = aiGrandMasterSkillBookMinCount[idx];
    	int iMaxReadCount = aiGrandMasterSkillBookMaxCount[idx];
    
    	int iBookCount = aiGrandMasterSkillBookCountForLevelUp[idx];
    
    	if ( LC_IsYMIR() == true || LC_IsKorea() == true )
    	{
    		const int aiGrandMasterSkillBookCountForLevelUp_euckr[10] =
    		{
    			3, 3, 4, 5, 6, 7, 8, 9, 10, 15, 
    		};
    
    		const int aiGrandMasterSkillBookMinCount_euckr[10] = 
    		{
    			1, 1, 1, 2, 2, 2, 3, 3, 4, 5
    		};
    
    		const int aiGrandMasterSkillBookMaxCount_euckr[10] = 
    		{
    			5, 7, 9, 11, 13, 15, 18, 23, 25, 30
    		};
    
    		iMinReadCount = aiGrandMasterSkillBookMinCount_euckr[idx];
    		iMaxReadCount = aiGrandMasterSkillBookMaxCount_euckr[idx];
    		iBookCount = aiGrandMasterSkillBookCountForLevelUp_euckr[idx];
    	}
    
    	if (FindAffect(AFFECT_SKILL_BOOK_BONUS))
    	{
    		if (iBookCount&1)
    			iBookCount = iBookCount / 2 + 1; 
    		else
    			iBookCount = iBookCount / 2; 
    
    		RemoveAffect(AFFECT_SKILL_BOOK_BONUS);
    	}
    
    	int n = number(1, iBookCount);
    	sys_log(0, "Number(%d)", n);
    
    	DWORD nextTime = get_global_time() + number(28800, 43200);
    
    	sys_log(0, "GrandMaster SkillBookCount min %d cur %d max %d (next_time=%d)", iMinReadCount, iTotalReadCount, iMaxReadCount, nextTime);
    
    	bool bSuccess = n == 2;
    
    	if (iTotalReadCount < iMinReadCount)
    		bSuccess = false;
    	if (iTotalReadCount > iMaxReadCount)
    		bSuccess = true;
    
    	if (bSuccess)
    	{
    		SkillLevelUp(dwSkillVnum, SKILL_UP_BY_QUEST);
    	}
    
    	SetSkillNextReadTime(dwSkillVnum, nextTime);
    
    	if (bLastLevel == GetSkillLevel(dwSkillVnum))
    	{
    		ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("크윽, 기가 역류하고 있어! 이거 설마 주화입마인가!? 젠장!"));
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("수련이 실패로 끝났습니다. 다시 도전해주시기 바랍니다."));
    		LogManager::instance().CharLog(this, dwSkillVnum, "GM_READ_FAIL", "");
    		return false;
    	}
    
    	ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("몸에서 뭔가 힘이 터져 나오는 기분이야!"));
    	ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("뜨거운 무엇이 계속 용솟음치고 있어! 이건, 이것은!"));
    	ChatPacket(CHAT_TYPE_INFO, LC_TEXT("더 높은 경지의 수련을 성공적으로 끝내셨습니다."));
    	LogManager::instance().CharLog(this, dwSkillVnum, "GM_READ_SUCCESS", "");
    	return true;
    }
    // END_OF_ADD_GRANDMASTER_SKILL
    
    static bool FN_should_check_exp(LPCHARACTER ch)
    {
    	if (LC_IsCanada())
    		return ch->GetLevel() < gPlayerMaxLevel;
    
    	if (!LC_IsYMIR())
    		return true;
    
    	return false;
    }
    
    
    bool CHARACTER::LearnSkillByBook(DWORD dwSkillVnum, BYTE bProb)
    {
    	const CSkillProto* pkSk = CSkillManager::instance().Get(dwSkillVnum);
    
    	if (!pkSk)
    		return false;
    
    	if (!IsLearnableSkill(dwSkillVnum))
    	{
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("수련할 수 없는 스킬입니다."));
    		return false;
    	}
    
    	DWORD need_exp = 0;
    
    	if (FN_should_check_exp(this))
    	{
    		need_exp = 20000;
    
    		if ( GetExp() < need_exp )
    		{
    			ChatPacket(CHAT_TYPE_INFO, LC_TEXT("경험치가 부족하여 책을 읽을 수 없습니다."));
    			return false;
    		}
    	}
    
    	// bType이 0이면 처음부터 책으로 수련 가능
    	if (pkSk->dwType != 0)
    	{
    		if (GetSkillMasterType(dwSkillVnum) != SKILL_MASTER)
    		{
    			if (GetSkillMasterType(dwSkillVnum) > SKILL_MASTER)
    				ChatPacket(CHAT_TYPE_INFO, LC_TEXT("이 스킬은 책으로 더이상 수련할 수 없습니다."));
    			else
    				ChatPacket(CHAT_TYPE_INFO, LC_TEXT("이 스킬은 아직 책으로 수련할 경지에 이르지 않았습니다."));
    			return false;
    		}
    	}
    
    	if (get_global_time() < GetSkillNextReadTime(dwSkillVnum))
    	{
    		if (!(test_server && quest::CQuestManager::instance().GetEventFlag("no_read_delay")))
    		{
    			if (FindAffect(AFFECT_SKILL_NO_BOOK_DELAY))
    			{
    				// 주안술서 사용중에는 시간 제한 무시
    				RemoveAffect(AFFECT_SKILL_NO_BOOK_DELAY);
    				ChatPacket(CHAT_TYPE_INFO, LC_TEXT("주안술서를 통해 주화입마에서 빠져나왔습니다."));
    			}
    			else 	    
    			{
    				SkillLearnWaitMoreTimeMessage(GetSkillNextReadTime(dwSkillVnum) - get_global_time());
    				return false;
    			}
    		}
    	}
    
    	// 여기서 확률을 계산합니다.
    	BYTE bLastLevel = GetSkillLevel(dwSkillVnum);
    
    	if (bProb != 0)
    	{
    		// SKILL_BOOK_BONUS
    		if (FindAffect(AFFECT_SKILL_BOOK_BONUS))
    		{
    			bProb += bProb / 2;
    			RemoveAffect(AFFECT_SKILL_BOOK_BONUS);
    		}
    		// END_OF_SKILL_BOOK_BONUS
    
    		sys_log(0, "LearnSkillByBook Pct %u prob %d", dwSkillVnum, bProb);
    
    		if (number(1, 100) <= bProb)
    		{
    			if (test_server)
    				sys_log(0, "LearnSkillByBook %u SUCC", dwSkillVnum);
    
    			SkillLevelUp(dwSkillVnum, SKILL_UP_BY_BOOK);
    		}
    		else
    		{
    			if (test_server)
    				sys_log(0, "LearnSkillByBook %u FAIL", dwSkillVnum);
    		}
    	}
    	else
    	{
    		int idx = MIN(9, GetSkillLevel(dwSkillVnum) - 20);
    
    		sys_log(0, "LearnSkillByBook %s table idx %d value %d", GetName(), idx, aiSkillBookCountForLevelUp[idx]);
    
    		if (!LC_IsYMIR()) 
    		{
    			int need_bookcount = GetSkillLevel(dwSkillVnum) - 20;
    
    			PointChange(POINT_EXP, -need_exp);
    
    			quest::CQuestManager& q = quest::CQuestManager::instance();
    			quest::PC* pPC = q.GetPC(GetPlayerID());
    
    			if (pPC)
    			{
    				char flag[128+1];
    				memset(flag, 0, sizeof(flag));
    				snprintf(flag, sizeof(flag), "traning_master_skill.%u.read_count", dwSkillVnum);
    
    				int read_count = pPC->GetFlag(flag);
    				int percent = 65;
    
    				if (FindAffect(AFFECT_SKILL_BOOK_BONUS))
    				{
    					percent = 0;
    					RemoveAffect(AFFECT_SKILL_BOOK_BONUS);
    				}
    
    				if (number(1, 100) > percent)
    				{
    					// 책읽기에 성공
    					if (read_count >= need_bookcount)
    					{
    						SkillLevelUp(dwSkillVnum, SKILL_UP_BY_BOOK);
    						pPC->SetFlag(flag, 0);
    
    						ChatPacket(CHAT_TYPE_INFO, LC_TEXT("책으로 더 높은 경지의 수련을 성공적으로 끝내셨습니다."));
    						LogManager::instance().CharLog(this, dwSkillVnum, "READ_SUCCESS", "");
    						return true;
    					}
    					else
    					{
    						pPC->SetFlag(flag, read_count + 1);
    
    						switch (number(1, 3))
    						{
    							case 1:
    								ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("어느정도 이 기술에 대해 이해가 되었지만 조금 부족한듯 한데.."));
    								break;
    											
    							case 2:
    								ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("드디어 끝이 보이는 건가...  이 기술은 이해하기가 너무 힘들어.."));
    								break;
    
    							case 3:
    							default:
    								ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("열심히 하는 배움을 가지는 것만이 기술을 배울수 있는 유일한 길이다.."));
    								break;
    						}
    
    						ChatPacket(CHAT_TYPE_INFO, LC_TEXT("%d 권을 더 읽어야 수련을 완료 할 수 있습니다."), need_bookcount - read_count);
    						return true;
    					}
    				}
    			}
    			else
    			{
    				// 사용자의 퀘스트 정보 로드 실패
    			}
    		}
    		// INTERNATIONAL_VERSION
    		else
    		{
    			int iBookCount = 99;
    
    			if (LC_IsYMIR() == true)
    			{
    				const int aiSkillBookCountForLevelUp_euckr[10] =
    				{
    					2, 2, 3, 3, 3, 3, 3, 3, 4, 5
    				};
    
    				iBookCount = aiSkillBookCountForLevelUp_euckr[idx];
    			}
    			else
    				iBookCount = aiSkillBookCountForLevelUp[idx];
    
    			if (FindAffect(AFFECT_SKILL_BOOK_BONUS))
    			{
    				if (iBookCount & 1) // iBookCount % 2
    					iBookCount = iBookCount / 2 + 1;
    				else
    					iBookCount = iBookCount / 2;
    
    				RemoveAffect(AFFECT_SKILL_BOOK_BONUS);
    			}
    
    			if (number(1, iBookCount) == 2)
    				SkillLevelUp(dwSkillVnum, SKILL_UP_BY_BOOK);
    		}
    		// END_OF_INTERNATIONAL_VERSION
    	}
    
    	if (bLastLevel != GetSkillLevel(dwSkillVnum))
    	{
    		ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("몸에서 뭔가 힘이 터져 나오는 기분이야!"));
    		ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("뜨거운 무엇이 계속 용솟음치고 있어! 이건, 이것은!"));
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("책으로 더 높은 경지의 수련을 성공적으로 끝내셨습니다."));
    		LogManager::instance().CharLog(this, dwSkillVnum, "READ_SUCCESS", "");
    	}
    	else
    	{
    		ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("크윽, 기가 역류하고 있어! 이거 설마 주화입마인가!? 젠장!"));
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("수련이 실패로 끝났습니다. 다시 도전해주시기 바랍니다."));
    		LogManager::instance().CharLog(this, dwSkillVnum, "READ_FAIL", "");
    	}
    
    	return true;
    }
    
    bool CHARACTER::SkillLevelDown(DWORD dwVnum)
    {
    	if (NULL == m_pSkillLevels)
    		return false;
    
    	if (g_bSkillDisable)
    		return false;
    
    	if (IsPolymorphed())
    		return false;
    
    	CSkillProto * pkSk = CSkillManager::instance().Get(dwVnum);
    
    	if (!pkSk)
    	{
    		sys_err("There is no such skill by number %u", dwVnum);
    		return false;
    	}
    
    	if (!IsLearnableSkill(dwVnum))
    		return false;
    
    	if (GetSkillMasterType(pkSk->dwVnum) != SKILL_NORMAL)
    		return false;
    
    	if (!GetSkillGroup())
    		return false;
    
    	if (pkSk->dwVnum >= SKILL_MAX_NUM)
    		return false;
    
    	if (m_pSkillLevels[pkSk->dwVnum].bLevel == 0)
    		return false;
    
    #ifdef __7AND8TH_SKILLS__
    	if (pkSk->dwVnum >= SKILL_ANTI_PALBANG && pkSk->dwVnum <= SKILL_HELP_SALPOONG)
    	{
    		if (m_pSkillLevels[pkSk->dwVnum].bLevel == 1)
    			return false;
    	}
    #endif
    
    	int idx = POINT_SKILL;
    	switch (pkSk->dwType)
    	{
    		case 0:
    			idx = POINT_SUB_SKILL;
    			break;
    		case 1:
    		case 2:
    		case 3:
    		case 4:
    		case 6:
    			idx = POINT_SKILL;
    			break;
    		case 5:
    			idx = POINT_HORSE_SKILL;
    			break;
    		default:
    			sys_err("Wrong skill type %d skill vnum %d", pkSk->dwType, pkSk->dwVnum);
    			return false;
    
    	}
    
    	PointChange(idx, +1);
    	SetSkillLevel(pkSk->dwVnum, m_pSkillLevels[pkSk->dwVnum].bLevel - 1);
    
    	sys_log(0, "SkillDown: %s %u %u %u type %u", GetName(), pkSk->dwVnum, m_pSkillLevels[pkSk->dwVnum].bMasterType, m_pSkillLevels[pkSk->dwVnum].bLevel, pkSk->dwType);
    	Save();
    
    	ComputePoints();
    	SkillLevelPacket();
    	return true;
    }
    
    #ifdef __7AND8TH_SKILLS__
    bool CHARACTER::SkillCanUp(DWORD dwVnum)
    {
    	bool canLevelUP = false;
    	switch (dwVnum)
    	{
    		case SKILL_ANTI_PALBANG:
    		{
    			if (GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_AMSEOP:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_SWAERYUNG:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_YONGBI:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_GIGONGCHAM:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_HWAJO:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_MARYUNG:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_BYEURAK:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_ANTI_SALPOONG:
    		{
    			if (GetSkillLevel(SKILL_ANTI_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_ANTI_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_ANTI_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_ANTI_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_ANTI_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_ANTI_BYEURAK) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_PALBANG:
    		{
    			if (GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_AMSEOP:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_SWAERYUNG:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_YONGBI:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_GIGONGCHAM:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_HWAJO:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_MARYUNG:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_BYEURAK:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_SALPOONG) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		case SKILL_HELP_SALPOONG:
    		{
    			if (GetSkillLevel(SKILL_HELP_PALBANG) == 0 &&
    					GetSkillLevel(SKILL_HELP_AMSEOP) == 0 &&
    					GetSkillLevel(SKILL_HELP_SWAERYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_YONGBI) == 0 &&
    					GetSkillLevel(SKILL_HELP_GIGONGCHAM) == 0 &&
    					GetSkillLevel(SKILL_HELP_HWAJO) == 0 &&
    					GetSkillLevel(SKILL_HELP_MARYUNG) == 0 &&
    					GetSkillLevel(SKILL_HELP_BYEURAK) == 0
    			)
    				canLevelUP = true;
    			
    			break;
    		}
    		default:
    			break;
    	}
    	
    	return canLevelUP;
    }
    #endif
    
    void CHARACTER::SkillLevelUp(DWORD dwVnum, BYTE bMethod)
    {
    	if (NULL == m_pSkillLevels)
    		return;
    
    	if (g_bSkillDisable)
    		return;
    
    	if (IsPolymorphed())
    	{
    		ChatPacket(CHAT_TYPE_INFO, LC_TEXT("둔갑 중에는 능력을 올릴 수 없습니다."));
    		return;
    	}
    
    #ifdef __7AND8TH_SKILLS__
    	if (dwVnum >= SKILL_ANTI_PALBANG && dwVnum <= SKILL_HELP_SALPOONG)
    	{
    		if (!SkillCanUp(dwVnum))
    			return;
    	}
    #endif
    
    	if (SKILL_7_A_ANTI_TANHWAN <= dwVnum && dwVnum <= SKILL_8_D_ANTI_BYEURAK)
    	{
    		if (0 == GetSkillLevel(dwVnum))
    			return;
    	}
    
    	const CSkillProto* pkSk = CSkillManager::instance().Get(dwVnum);
    
    	if (!pkSk)
    	{
    		sys_err("There is no such skill by number (vnum %u)", dwVnum);
    		return;
    	}
    
    	if (pkSk->dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("Skill Vnum overflow (vnum %u)", dwVnum);
    		return;
    	}
    
    	if (!IsLearnableSkill(dwVnum))
    		return;
    
    	// 그랜드 마스터는 퀘스트로만 수행가능
    	if (pkSk->dwType != 0)
    	{
    		switch (GetSkillMasterType(pkSk->dwVnum))
    		{
    			case SKILL_GRAND_MASTER:
    				if (bMethod != SKILL_UP_BY_QUEST)
    					return;
    				break;
    
    			case SKILL_PERFECT_MASTER:
    				return;
    		}
    	}
    
    	if (bMethod == SKILL_UP_BY_POINT)
    	{
    		// 마스터가 아닌 상태에서만 수련가능
    		if (GetSkillMasterType(pkSk->dwVnum) != SKILL_NORMAL)
    			return;
    
    		if (IS_SET(pkSk->dwFlag, SKILL_FLAG_DISABLE_BY_POINT_UP))
    			return;
    	}
    	else if (bMethod == SKILL_UP_BY_BOOK)
    	{
    		if (pkSk->dwType != 0) // 직업에 속하지 않았거나 포인트로 올릴수 없는 스킬은 처음부터 책으로 배울 수 있다.
    			if (GetSkillMasterType(pkSk->dwVnum) != SKILL_MASTER)
    				return;
    	}
    
    	if (GetLevel() < pkSk->bLevelLimit)
    		return;
    
    	if (pkSk->preSkillVnum)
    		if (GetSkillMasterType(pkSk->preSkillVnum) == SKILL_NORMAL &&
    			GetSkillLevel(pkSk->preSkillVnum) < pkSk->preSkillLevel)
    			return;
    
    	if (!GetSkillGroup())
    		return;
    
    	if (bMethod == SKILL_UP_BY_POINT)
    	{
    		int idx;
    
    		switch (pkSk->dwType)
    		{
    			case 0:
    				idx = POINT_SUB_SKILL;
    				break;
    
    			case 1:
    			case 2:
    			case 3:
    			case 4:
    			case 6:
    				idx = POINT_SKILL;
    				break;
    
    			case 5:
    				idx = POINT_HORSE_SKILL;
    				break;
    
    			default:
    				sys_err("Wrong skill type %d skill vnum %d", pkSk->dwType, pkSk->dwVnum);
    				return;
    		}
    
    		if (GetPoint(idx) < 1)
    			return;
    
    		PointChange(idx, -1);
    	}
    
    	int SkillPointBefore = GetSkillLevel(pkSk->dwVnum);
    	SetSkillLevel(pkSk->dwVnum, m_pSkillLevels[pkSk->dwVnum].bLevel + 1);
    
    	if (pkSk->dwType != 0)
    	{
    		// 갑자기 그레이드 업하는 코딩
    		switch (GetSkillMasterType(pkSk->dwVnum))
    		{
    			case SKILL_NORMAL:
    				if (GetSkillLevel(pkSk->dwVnum) >= 17)
    				{
    					SetSkillLevel(pkSk->dwVnum, 20);
    				}
    				break;
    				// 번섭은 스킬 업그레이드 17~20 사이 랜덤 마스터 수련
    				// if (GetSkillLevel(pkSk->dwVnum) >= 20)
    				// {
    					// if (GetQuestFlag("reset_scroll.force_to_master_skill") > 0)
    					// {
    						// SetSkillLevel(pkSk->dwVnum, 20);
    						// SetQuestFlag("reset_scroll.force_to_master_skill", 0);
    					// }
    					// else
    					// {
    						// if (number(1, 21 - MIN(20, GetSkillLevel(pkSk->dwVnum))) == 1)
    							// SetSkillLevel(pkSk->dwVnum, 20);
    					// }
    				// }
    				// break;
    
    			case SKILL_MASTER:
    				if (GetSkillLevel(pkSk->dwVnum) >= 30)
    				{
    					if (number(1, 31 - MIN(30, GetSkillLevel(pkSk->dwVnum))) == 1)
    						SetSkillLevel(pkSk->dwVnum, 30);
    				}
    				break;
    
    			case SKILL_GRAND_MASTER:
    				if (GetSkillLevel(pkSk->dwVnum) >= 40)
    				{
    					SetSkillLevel(pkSk->dwVnum, 40);
    				}
    				break;
    		}
    	}
    
    	char szSkillUp[1024];
    
    	snprintf(szSkillUp, sizeof(szSkillUp), "SkillUp: %s %u %d %d[Before:%d] type %u",
    			GetName(), pkSk->dwVnum, m_pSkillLevels[pkSk->dwVnum].bMasterType, m_pSkillLevels[pkSk->dwVnum].bLevel, SkillPointBefore, pkSk->dwType);
    
    	sys_log(0, "%s", szSkillUp);
    
    	LogManager::instance().CharLog(this, pkSk->dwVnum, "SKILLUP", szSkillUp);
    	Save();
    
    	ComputePoints();
    	SkillLevelPacket();
    }
    
    void CHARACTER::ComputeSkillPoints()
    {
    	if (g_bSkillDisable)
    		return;
    }
    
    void CHARACTER::ResetSkill()
    {
    	if (NULL == m_pSkillLevels)
    		return;
    
    	// 보조 스킬은 리셋시키지 않는다
    	std::vector<std::pair<DWORD, TPlayerSkill> > vec;
    	size_t count = sizeof(s_adwSubSkillVnums) / sizeof(s_adwSubSkillVnums[0]);
    
    	for (size_t i = 0; i < count; ++i)
    	{
    		if (s_adwSubSkillVnums[i] >= SKILL_MAX_NUM)
    			continue;
    
    		vec.push_back(std::make_pair(s_adwSubSkillVnums[i], m_pSkillLevels[s_adwSubSkillVnums[i]]));
    	}
    
    	memset(m_pSkillLevels, 0, sizeof(TPlayerSkill) * SKILL_MAX_NUM);
    
    	std::vector<std::pair<DWORD, TPlayerSkill> >::const_iterator iter = vec.begin();
    
    	while (iter != vec.end())
    	{
    		const std::pair<DWORD, TPlayerSkill>& pair = *(iter++);
    		m_pSkillLevels[pair.first] = pair.second;
    	}
    
    	ComputePoints();
    	SkillLevelPacket();
    }
    
    void CHARACTER::ComputePassiveSkill(DWORD dwVnum)
    {
    	if (g_bSkillDisable)
    		return;
    
    	if (GetSkillLevel(dwVnum) == 0)
    		return;
    
    	CSkillProto * pkSk = CSkillManager::instance().Get(dwVnum);
    	pkSk->SetPointVar("k", GetSkillLevel(dwVnum));
    	int iAmount = (int) pkSk->kPointPoly.Eval();
    
    	sys_log(2, "%s passive #%d on %d amount %d", GetName(), dwVnum, pkSk->bPointOn, iAmount);
    	PointChange(pkSk->bPointOn, iAmount);
    }
    
    struct FFindNearVictim
    {
    	FFindNearVictim(LPCHARACTER center, LPCHARACTER attacker, const CHARACTER_SET& excepts_set = empty_set_)
    		: m_pkChrCenter(center),
    	m_pkChrNextTarget(NULL),
    	m_pkChrAttacker(attacker),
    	m_count(0),
    	m_excepts_set(excepts_set)
    	{
    	}
    
    	void operator ()(LPENTITY ent)
    	{
    		if (!ent->IsType(ENTITY_CHARACTER))
    			return;
    
    		LPCHARACTER pkChr = (LPCHARACTER) ent;
    
    		if (!m_excepts_set.empty()) {
    			if (m_excepts_set.find(pkChr) != m_excepts_set.end())
    				return;
    		}
    
    		if (m_pkChrCenter == pkChr)
    			return;
    
    		if (!battle_is_attackable(m_pkChrAttacker, pkChr))
    		{
    			return;
    		}
    
    		if (abs(m_pkChrCenter->GetX() - pkChr->GetX()) > 1000 || abs(m_pkChrCenter->GetY() - pkChr->GetY()) > 1000)
    			return;
    
    		float fDist = DISTANCE_APPROX(m_pkChrCenter->GetX() - pkChr->GetX(), m_pkChrCenter->GetY() - pkChr->GetY());
    
    		if (fDist < 1000)
    		{
    			++m_count;
    
    			if ((m_count == 1) || number(1, m_count) == 1)
    				m_pkChrNextTarget = pkChr;
    		}
    	}
    
    	LPCHARACTER GetVictim()
    	{
    		return m_pkChrNextTarget;
    	}
    
    	LPCHARACTER m_pkChrCenter;
    	LPCHARACTER m_pkChrNextTarget;
    	LPCHARACTER m_pkChrAttacker;
    	int		m_count;
    	const CHARACTER_SET & m_excepts_set;
    private:
    	static CHARACTER_SET empty_set_;
    };
    
    CHARACTER_SET FFindNearVictim::empty_set_;
    
    EVENTINFO(chain_lightning_event_info)
    {
    	DWORD			dwVictim;
    	DWORD			dwChr;
    
    	chain_lightning_event_info()
    	: dwVictim(0)
    	, dwChr(0)
    	{
    	}
    };
    
    EVENTFUNC(ChainLightningEvent)
    {
    	chain_lightning_event_info * info = dynamic_cast<chain_lightning_event_info *>( event->info );
    
    	LPCHARACTER pkChrVictim = CHARACTER_MANAGER::instance().Find(info->dwVictim);
    	LPCHARACTER pkChr = CHARACTER_MANAGER::instance().Find(info->dwChr);
    	LPCHARACTER pkTarget = NULL;
    
    	if (!pkChr || !pkChrVictim)
    	{
    		sys_log(1, "use chainlighting, but no character");
    		return 0;
    	}
    
    	sys_log(1, "chainlighting event %s", pkChr->GetName());
    
    	if (pkChrVictim->GetParty()) // 파티 먼저
    	{
    		pkTarget = pkChrVictim->GetParty()->GetNextOwnership(NULL, pkChrVictim->GetX(), pkChrVictim->GetY());
    		if (pkTarget == pkChrVictim || !number(0, 2) || pkChr->GetChainLightingExcept().find(pkTarget) != pkChr->GetChainLightingExcept().end())
    			pkTarget = NULL;
    	}
    
    	if (!pkTarget)
    	{
    		// 1. Find Next victim
    		FFindNearVictim f(pkChrVictim, pkChr, pkChr->GetChainLightingExcept());
    
    		if (pkChrVictim->GetSectree())
    		{
    			pkChrVictim->GetSectree()->ForEachAround(f);
    			// 2. If exist, compute it again
    			pkTarget = f.GetVictim();
    		}
    	}
    
    	if (pkTarget)
    	{
    		pkChrVictim->CreateFly(FLY_CHAIN_LIGHTNING, pkTarget);
    		pkChr->ComputeSkill(SKILL_CHAIN, pkTarget);
    		pkChr->AddChainLightningExcept(pkTarget);
    	}
    	else
    	{
    		sys_log(1, "%s use chainlighting, but find victim failed near %s", pkChr->GetName(), pkChrVictim->GetName());
    	}
    
    	return 0;
    }
    
    void SetPolyVarForAttack(LPCHARACTER ch, CSkillProto * pkSk, LPITEM pkWeapon)
    {
    	if (ch->IsPC())
    	{
    		if (pkWeapon && pkWeapon->GetType() == ITEM_WEAPON)
    		{
    			int iWep = number(pkWeapon->GetValue(3), pkWeapon->GetValue(4));
    			iWep += pkWeapon->GetValue(5);
    
    			int iMtk = number(pkWeapon->GetValue(1), pkWeapon->GetValue(2));
    			iMtk += pkWeapon->GetValue(5);
    
    			pkSk->SetPointVar("wep", iWep);
    			pkSk->SetPointVar("mtk", iMtk);
    			pkSk->SetPointVar("mwep", iMtk);
    		}
    		else
    		{
    			pkSk->SetPointVar("wep", 0);
    			pkSk->SetPointVar("mtk", 0);
    			pkSk->SetPointVar("mwep", 0);
    		}
    	}
    	else
    	{
    		int iWep = number(ch->GetMobDamageMin(), ch->GetMobDamageMax());
    		pkSk->SetPointVar("wep", iWep);
    		pkSk->SetPointVar("mwep", iWep);
    		pkSk->SetPointVar("mtk", iWep);
    	}
    }
    
    struct FuncSplashDamage
    {
    	FuncSplashDamage(int x, int y, CSkillProto * pkSk, LPCHARACTER pkChr, int iAmount, int iAG, int iMaxHit, LPITEM pkWeapon, bool bDisableCooltime, TSkillUseInfo* pInfo, BYTE bUseSkillPower)
    		:
    		m_x(x), m_y(y), m_pkSk(pkSk), m_pkChr(pkChr), m_iAmount(iAmount), m_iAG(iAG), m_iCount(0), m_iMaxHit(iMaxHit), m_pkWeapon(pkWeapon), m_bDisableCooltime(bDisableCooltime), m_pInfo(pInfo), m_bUseSkillPower(bUseSkillPower)
    		{
    		}
    
    	void operator () (LPENTITY ent)
    	{
    		if (!ent->IsType(ENTITY_CHARACTER))
    		{
    			return;
    		}
    
    		LPCHARACTER pkChrVictim = (LPCHARACTER) ent;
    		if (DISTANCE_APPROX(m_x - pkChrVictim->GetX(), m_y - pkChrVictim->GetY()) > m_pkSk->iSplashRange)
    		{
    			if(test_server)
    				sys_log(0, "XXX target too far %s", m_pkChr->GetName());
    			return;
    		}
    
    		if (!battle_is_attackable(m_pkChr, pkChrVictim))
    		{
    			if(test_server)
    				sys_log(0, "XXX target not attackable %s", m_pkChr->GetName());
    			return;
    		}
    
    		if (m_pkChr->IsPC())
    			if (!(m_pkSk->dwVnum >= GUILD_SKILL_START && m_pkSk->dwVnum <= GUILD_SKILL_END))
    				if (!m_bDisableCooltime && m_pInfo && !m_pInfo->HitOnce(m_pkSk->dwVnum) && m_pkSk->dwVnum != SKILL_MUYEONG)
    				{
    					if(test_server)
    						sys_log(0, "check guild skill %s", m_pkChr->GetName());
    					return;
    				}
    
    		++m_iCount;
    		int iDam;
    
    		m_pkSk->SetPointVar("k", 1.0 * m_bUseSkillPower * m_pkSk->bMaxLevel / 100);
    		m_pkSk->SetPointVar("lv", m_pkChr->GetLevel());
    		m_pkSk->SetPointVar("iq", m_pkChr->GetPoint(POINT_IQ));
    		m_pkSk->SetPointVar("str", m_pkChr->GetPoint(POINT_ST));
    		m_pkSk->SetPointVar("dex", m_pkChr->GetPoint(POINT_DX));
    		m_pkSk->SetPointVar("con", m_pkChr->GetPoint(POINT_HT));
    		m_pkSk->SetPointVar("def", m_pkChr->GetPoint(POINT_DEF_GRADE));
    		m_pkSk->SetPointVar("odef", m_pkChr->GetPoint(POINT_DEF_GRADE) - m_pkChr->GetPoint(POINT_DEF_GRADE_BONUS));
    		m_pkSk->SetPointVar("horse_level", m_pkChr->GetHorseLevel());
    
    		bool bIgnoreDefense = false;
    		if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_PENETRATE))
    		{
    			int iPenetratePct = (int) m_pkSk->kPointPoly2.Eval();
    			if (number(1, 100) <= iPenetratePct)
    				bIgnoreDefense = true;
    		}
    
    		bool bIgnoreTargetRating = false;
    		if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_IGNORE_TARGET_RATING))
    		{
    			int iPct = (int) m_pkSk->kPointPoly2.Eval();
    			if (number(1, 100) <= iPct)
    				bIgnoreTargetRating = true;
    		}
    
    		m_pkSk->SetPointVar("ar", CalcAttackRating(m_pkChr, pkChrVictim, bIgnoreTargetRating));
    		if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_USE_MELEE_DAMAGE))
    			m_pkSk->SetPointVar("atk", CalcMeleeDamage(m_pkChr, pkChrVictim, true, bIgnoreTargetRating));
    		else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_USE_ARROW_DAMAGE))
    		{
    			LPITEM pkBow, pkArrow;
    			if (1 == m_pkChr->GetArrowAndBow(&pkBow, &pkArrow, 1))
    				m_pkSk->SetPointVar("atk", CalcArrowDamage(m_pkChr, pkChrVictim, pkBow, pkArrow, true));
    			else
    				m_pkSk->SetPointVar("atk", 0);
    		}
    
    		if (m_pkSk->bPointOn == POINT_MOV_SPEED)
    			m_pkSk->kPointPoly.SetVar("maxv", pkChrVictim->GetLimitPoint(POINT_MOV_SPEED));
    
    		m_pkSk->SetPointVar("maxhp", pkChrVictim->GetMaxHP());
    		m_pkSk->SetPointVar("maxsp", pkChrVictim->GetMaxSP());
    
    		m_pkSk->SetPointVar("chain", m_pkChr->GetChainLightningIndex());
    		m_pkChr->IncChainLightningIndex();
    
    		bool bUnderEunhyung = m_pkChr->GetAffectedEunhyung() > 0;
    
    		m_pkSk->SetPointVar("ek", m_pkChr->GetAffectedEunhyung()*1./100);
    		SetPolyVarForAttack(m_pkChr, m_pkSk, m_pkWeapon);
    
    		int iAmount = 0;
    		if (m_pkChr->GetUsedSkillMasterType(m_pkSk->dwVnum) >= SKILL_GRAND_MASTER)
    		{
    			iAmount = (int) m_pkSk->kMasterBonusPoly.Eval();
    		}
    		else
    		{
    			iAmount = (int) m_pkSk->kPointPoly.Eval();
    		}
    
    		if (test_server && iAmount == 0 && m_pkSk->bPointOn != POINT_NONE)
    		{
    			m_pkChr->ChatPacket(CHAT_TYPE_INFO, "효과가 없습니다. 스킬 공식을 확인하세요");
    		}
    
    		iAmount = -iAmount;
    		if (m_pkSk->dwVnum == SKILL_AMSEOP)
    		{
    			float fDelta = GetDegreeDelta(m_pkChr->GetRotation(), pkChrVictim->GetRotation());
    			float adjust;
    			if (fDelta < 35.0f)
    			{
    				adjust = 1.5f;
    				if (bUnderEunhyung)
    					adjust += 0.5f;
    
    				if (m_pkChr->GetWear(WEAR_WEAPON) && m_pkChr->GetWear(WEAR_WEAPON)->GetSubType() == WEAPON_DAGGER)
    				{
    					if ( LC_IsYMIR() )
    						adjust += 1.0f;
    					else
    						adjust += 0.5f;
    				}
    			}
    			else
    			{
    				adjust = 1.0f;
    				if ( !LC_IsYMIR() )
    				{
    					if (bUnderEunhyung)
    						adjust += 0.5f;
    
    					if (m_pkChr->GetWear(WEAR_WEAPON) && m_pkChr->GetWear(WEAR_WEAPON)->GetSubType() == WEAPON_DAGGER)
    						adjust += 0.5f;
    				}
    			}
    
    			iAmount = (int) (iAmount * adjust);
    		}
    		else if (m_pkSk->dwVnum == SKILL_GUNGSIN)
    		{
    			float adjust = 1.0;
    			if (m_pkChr->GetWear(WEAR_WEAPON) && m_pkChr->GetWear(WEAR_WEAPON)->GetSubType() == WEAPON_DAGGER)
    			{
    				if ( LC_IsYMIR() )
    					adjust = 1.4f;
    				else
    					adjust = 1.35f;
    			}
    
    			iAmount = (int) (iAmount * adjust);
    		}
    		else if (m_pkSk->dwVnum == SKILL_GONGDAB)
    		{
    			float adjust = 1.0;
    			if (m_pkChr->GetWear(WEAR_WEAPON) && m_pkChr->GetWear(WEAR_WEAPON)->GetSubType() == WEAPON_CLAW)
    			{
    				adjust = 1.35f;
    			}
    
    			iAmount = (int)(iAmount * adjust);
    		}
    
    		iDam = CalcBattleDamage(iAmount, m_pkChr->GetLevel(), pkChrVictim->GetLevel());
    		if (m_pkChr->IsPC() && m_pkChr->m_SkillUseInfo[m_pkSk->dwVnum].GetMainTargetVID() != (DWORD) pkChrVictim->GetVID())
    		{
    			iDam = (int) (iDam * m_pkSk->kSplashAroundDamageAdjustPoly.Eval());
    		}
    
    #ifdef __ANTI_RESIST_MAGIC_BONUS__
    		int reduce_resist_magic = 0;
    #endif
    		EDamageType dt = DAMAGE_TYPE_NONE;
    		switch (m_pkSk->bSkillAttrType)
    		{
    			case SKILL_ATTR_TYPE_NORMAL:
    				break;
    
    			case SKILL_ATTR_TYPE_MELEE:
    				{
    					dt = DAMAGE_TYPE_MELEE;
    
    					LPITEM pkWeapon = m_pkChr->GetWear(WEAR_WEAPON);
    					if (pkWeapon)
    						switch (pkWeapon->GetSubType())
    						{
    							case WEAPON_SWORD:
    								iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_SWORD)) / 100;
    								break;
    
    							case WEAPON_TWO_HANDED:
    								iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_TWOHAND)) / 100;
    								break;
    
    							case WEAPON_DAGGER:
    								iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_DAGGER)) / 100;
    								break;
    
    							case WEAPON_BELL:
    								iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_BELL)) / 100;
    								break;
    
    							case WEAPON_FAN:
    								iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_FAN)) / 100;
    								break;
    
    							case WEAPON_CLAW:
    								iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_DAGGER)) / 100;
    								break;
    						}
    
    					if (!bIgnoreDefense)
    						iDam -= pkChrVictim->GetPoint(POINT_DEF_GRADE);
    				}
    				break;
    
    			case SKILL_ATTR_TYPE_RANGE:
    				dt = DAMAGE_TYPE_RANGE;
    				iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_BOW)) / 100;
    				break;
    
    			case SKILL_ATTR_TYPE_MAGIC:
    				dt = DAMAGE_TYPE_MAGIC;
    				iDam = CalcAttBonus(m_pkChr, pkChrVictim, iDam);
    #ifdef __ANTI_RESIST_MAGIC_BONUS__
    				reduce_resist_magic = pkChrVictim->GetPoint(POINT_RESIST_MAGIC);
    				if (m_pkChr->GetPoint(POINT_ANTI_RESIST_MAGIC) > 0)
    				{
    					// Fix Magic Resistance : If player have more than 100% magic resistance, will be modified to 100%;
    					int fix_magic_resistance = (pkChrVictim->GetPoint(POINT_RESIST_MAGIC) > 100) ? (int)(100) : (int)(pkChrVictim->GetPoint(POINT_RESIST_MAGIC));
    					// End Fix;
    					
    					reduce_resist_magic = fix_magic_resistance - m_pkChr->GetPoint(POINT_ANTI_RESIST_MAGIC);
    					if (reduce_resist_magic < 1)
    						reduce_resist_magic = 0;
    				}
    				iDam = iDam * (100 - reduce_resist_magic) / 100;
    #else
    				iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_MAGIC)) / 100;
    #endif
    				break;
    
    			default:
    				sys_err("Unknown skill attr type %u vnum %u", m_pkSk->bSkillAttrType, m_pkSk->dwVnum);
    				break;
    		}
    
    		if (pkChrVictim->IsNPC())
    		{
    			if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_WIND))
    			{
    				iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_WIND)) / 100;
    			}
    
    			if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_ELEC))
    			{
    				iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_ELEC)) / 100;
    			}
    
    			if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_FIRE))
    			{
    				iDam = iDam * (100 - pkChrVictim->GetPoint(POINT_RESIST_FIRE)) / 100;
    			}
    		}
    
    		if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_COMPUTE_MAGIC_DAMAGE))
    			dt = DAMAGE_TYPE_MAGIC;
    
    		if (pkChrVictim->CanBeginFight())
    			pkChrVictim->BeginFight(m_pkChr);
    
    		if (m_pkSk->dwVnum == SKILL_CHAIN)
    			sys_log(0, "%s CHAIN INDEX %d DAM %d DT %d", m_pkChr->GetName(), m_pkChr->GetChainLightningIndex() - 1, iDam, dt);
    
    #ifdef __7AND8TH_SKILLS__
    		{
    			BYTE HELP_SKILL_ID = 0;
    			switch (m_pkSk->dwVnum)
    			{
    				case SKILL_PALBANG:
    					HELP_SKILL_ID = SKILL_HELP_PALBANG;
    					break;
    				case SKILL_AMSEOP:
    					HELP_SKILL_ID = SKILL_HELP_AMSEOP;
    					break;
    				case SKILL_SWAERYUNG:
    					HELP_SKILL_ID = SKILL_HELP_SWAERYUNG;
    					break;
    				case SKILL_YONGBI:
    					HELP_SKILL_ID = SKILL_HELP_YONGBI;
    					break;
    				case SKILL_GIGONGCHAM:
    					HELP_SKILL_ID = SKILL_HELP_GIGONGCHAM;
    					break;
    				case SKILL_HWAJO:
    					HELP_SKILL_ID = SKILL_HELP_HWAJO;
    					break;
    				case SKILL_MARYUNG:
    					HELP_SKILL_ID = SKILL_HELP_MARYUNG;
    					break;
    				case SKILL_BYEURAK:
    					HELP_SKILL_ID = SKILL_HELP_BYEURAK;
    					break;
    				case SKILL_SALPOONG:
    					HELP_SKILL_ID = SKILL_HELP_SALPOONG;
    					break;
    				default:
    					break;
    			}
    			
    			if (HELP_SKILL_ID != 0)
    			{
    				BYTE HELP_SKILL_LV = m_pkChr->GetSkillLevel(HELP_SKILL_ID);
    				if (HELP_SKILL_LV != 0)
    				{
    					CSkillProto* pkSk = CSkillManager::instance().Get(HELP_SKILL_ID);
    					if (!pkSk)
    						sys_err("Can't find %d skill in skill_proto.", HELP_SKILL_ID);
    					else
    					{
    						pkSk->SetPointVar("k", 1.0f * m_pkChr->GetSkillPower(HELP_SKILL_ID) * pkSk->bMaxLevel / 100);
    						
    						double IncreaseAmount = pkSk->kPointPoly.Eval();
    						sys_log(0, "HELP_SKILL: increase amount: %lf, normal damage: %d, increased damage: %d.", IncreaseAmount, iDam, int(iDam * (IncreaseAmount / 100.0)));
    						iDam += iDam * (IncreaseAmount / 100.0);
    					}
    				}
    			}
    		}
    
    		{
    			BYTE ANTI_SKILL_ID = 0;
    			switch (m_pkSk->dwVnum)
    			{
    				case SKILL_PALBANG:
    					ANTI_SKILL_ID = SKILL_ANTI_PALBANG;
    					break;
    				case SKILL_AMSEOP:
    					ANTI_SKILL_ID = SKILL_ANTI_AMSEOP;
    					break;
    				case SKILL_SWAERYUNG:
    					ANTI_SKILL_ID = SKILL_ANTI_SWAERYUNG;
    					break;
    				case SKILL_YONGBI:
    					ANTI_SKILL_ID = SKILL_ANTI_YONGBI;
    					break;
    				case SKILL_GIGONGCHAM:
    					ANTI_SKILL_ID = SKILL_ANTI_GIGONGCHAM;
    					break;
    				case SKILL_HWAJO:
    					ANTI_SKILL_ID = SKILL_ANTI_HWAJO;
    					break;
    				case SKILL_MARYUNG:
    					ANTI_SKILL_ID = SKILL_ANTI_MARYUNG;
    					break;
    				case SKILL_BYEURAK:
    					ANTI_SKILL_ID = SKILL_ANTI_BYEURAK;
    					break;
    				case SKILL_SALPOONG:
    					ANTI_SKILL_ID = SKILL_ANTI_SALPOONG;
    					break;
    				default:
    					break;
    			}
    
    			if (ANTI_SKILL_ID != 0)
    			{
    				BYTE ANTI_SKILL_LV = pkChrVictim->GetSkillLevel(ANTI_SKILL_ID);
    				if (ANTI_SKILL_LV != 0)
    				{
    					CSkillProto* pkSk = CSkillManager::instance().Get(ANTI_SKILL_ID);
    					if (!pkSk)
    						sys_err("Can't find %d skill in skill_proto.", ANTI_SKILL_ID);
    					else
    					{
    						pkSk->SetPointVar("k", 1.0f * pkChrVictim->GetSkillPower(ANTI_SKILL_ID) * pkSk->bMaxLevel / 100);
    						
    						double ResistAmount = pkSk->kPointPoly.Eval();
    						sys_log(0, "ANTI_SKILL: resist amount: %lf, normal damage: %d, reduced damage: %d.", ResistAmount, iDam, int(iDam * (ResistAmount/100.0)));
    						iDam -= iDam * (ResistAmount / 100.0);
    					}
    				}
    			}
    		}
    #endif
    
    		{
    			BYTE AntiSkillID = 0;
    			switch (m_pkSk->dwVnum)
    			{
    				case SKILL_TANHWAN:
    					AntiSkillID = SKILL_7_A_ANTI_TANHWAN;
    					break;
    
    				case SKILL_AMSEOP:
    					AntiSkillID = SKILL_7_B_ANTI_AMSEOP;
    					break;
    
    				case SKILL_SWAERYUNG:
    					AntiSkillID = SKILL_7_C_ANTI_SWAERYUNG;
    					break;
    
    				case SKILL_YONGBI:
    					AntiSkillID = SKILL_7_D_ANTI_YONGBI;
    					break;
    
    				case SKILL_GIGONGCHAM:
    					AntiSkillID = SKILL_8_A_ANTI_GIGONGCHAM;
    					break;
    
    				case SKILL_YEONSA:
    					AntiSkillID = SKILL_8_B_ANTI_YEONSA;
    					break;
    
    				case SKILL_MAHWAN:
    					AntiSkillID = SKILL_8_C_ANTI_MAHWAN;
    					break;
    
    				case SKILL_BYEURAK:
    					AntiSkillID = SKILL_8_D_ANTI_BYEURAK;
    					break;
    			}
    
    			if (0 != AntiSkillID)
    			{
    				BYTE AntiSkillLevel = pkChrVictim->GetSkillLevel(AntiSkillID);
    				if (0 != AntiSkillLevel)
    				{
    					CSkillProto* pkSk = CSkillManager::instance().Get(AntiSkillID);
    					if (!pkSk)
    					{
    						sys_err ("There is no anti skill(%d) in skill proto", AntiSkillID);
    					}
    					else
    					{
    						pkSk->SetPointVar("k", 1.0f * pkChrVictim->GetSkillPower(AntiSkillID) * pkSk->bMaxLevel / 100);
    						double ResistAmount = pkSk->kPointPoly.Eval();
    
    						sys_log(0, "ANTI_SKILL: Resist(%lf) Orig(%d) Reduce(%d)", ResistAmount, iDam, int(iDam * (ResistAmount/100.0)));
    
    						iDam -= iDam * (ResistAmount/100.0);
    					}
    				}
    			}
    		}
    
    		if (!pkChrVictim->Damage(m_pkChr, iDam, dt) && !pkChrVictim->IsStun())
    		{
    			if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_REMOVE_GOOD_AFFECT))
    			{
    				int iAmount2 = (int) m_pkSk->kPointPoly2.Eval();
    				int iDur2 = (int) m_pkSk->kDurationPoly2.Eval();
    				iDur2 += m_pkChr->GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (number(1, 100) <= iAmount2)
    				{
    					pkChrVictim->RemoveGoodAffect();
    					pkChrVictim->AddAffect(m_pkSk->dwVnum, POINT_NONE, 0, AFF_PABEOP, iDur2, 0, true);
    				}
    			}
    
    			if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_SLOW | SKILL_FLAG_STUN | SKILL_FLAG_FIRE_CONT | SKILL_FLAG_POISON | SKILL_FLAG_BLEEDING))
    			{
    				int iPct = (int) m_pkSk->kPointPoly2.Eval();
    				int iDur = (int) m_pkSk->kDurationPoly2.Eval();
    
    				iDur += m_pkChr->GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_STUN))
    				{
    					SkillAttackAffect(pkChrVictim, iPct, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, iDur, m_pkSk->szName);
    				}
    				else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_SLOW))
    				{
    					SkillAttackAffect(pkChrVictim, iPct, IMMUNE_SLOW, AFFECT_SLOW, POINT_MOV_SPEED, -30, AFF_SLOW, iDur, m_pkSk->szName);
    				}
    				else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_FIRE_CONT))
    				{
    					m_pkSk->SetDurationVar("k", 1.0 * m_bUseSkillPower * m_pkSk->bMaxLevel / 100);
    					m_pkSk->SetDurationVar("iq", m_pkChr->GetPoint(POINT_IQ));
    
    					iDur = (int)m_pkSk->kDurationPoly2.Eval();
    					int bonus = m_pkChr->GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    					if (bonus != 0)
    					{
    						iDur += bonus / 2;
    					}
    
    					if (number(1, 100) <= iDur)
    					{
    						pkChrVictim->AttackedByFire(m_pkChr, iPct, 5);
    					}
    				}
    				else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_POISON))
    				{
    					if (number(1, 100) <= iPct && !pkChrVictim->IsAffectFlag(AFF_BLEEDING))
    						pkChrVictim->AttackedByPoison(m_pkChr);
    				}	
    				else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_BLEEDING))
    				{
    					if (number(1, 100) <= iPct && !pkChrVictim->IsAffectFlag(AFF_POISON))
    						pkChrVictim->AttackedByBleeding(m_pkChr);
    				}
    			}
    
    			if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_CRUSH | SKILL_FLAG_CRUSH_LONG) &&
    				!IS_SET(pkChrVictim->GetAIFlag(), AIFLAG_NOMOVE))
    			{
    				float fCrushSlidingLength = 200;
    
    				if (m_pkChr->IsNPC())
    					fCrushSlidingLength = 400;
    
    				if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_CRUSH_LONG))
    					fCrushSlidingLength *= 2;
    
    				float fx, fy;
    				float degree = GetDegreeFromPositionXY(m_pkChr->GetX(), m_pkChr->GetY(), pkChrVictim->GetX(), pkChrVictim->GetY());
    
    				if (m_pkSk->dwVnum == SKILL_HORSE_WILDATTACK)
    				{
    					degree -= m_pkChr->GetRotation();
    					degree = fmod(degree, 360.0f) - 180.0f;
    
    					if (degree > 0)
    						degree = m_pkChr->GetRotation() + 90.0f;
    					else
    						degree = m_pkChr->GetRotation() - 90.0f;
    				}
    
    				GetDeltaByDegree(degree, fCrushSlidingLength, &fx, &fy);
    				sys_log(0, "CRUSH! %s -> %s (%d %d) -> (%d %d)", m_pkChr->GetName(), pkChrVictim->GetName(), pkChrVictim->GetX(), pkChrVictim->GetY(), (long)(pkChrVictim->GetX()+fx), (long)(pkChrVictim->GetY()+fy));
    				long tx = (long)(pkChrVictim->GetX()+fx);
    				long ty = (long)(pkChrVictim->GetY()+fy);
    
    				pkChrVictim->Sync(tx, ty);
    				pkChrVictim->Goto(tx, ty);
    				pkChrVictim->CalculateMoveDuration();
    
    				if (m_pkChr->IsPC() && m_pkChr->m_SkillUseInfo[m_pkSk->dwVnum].GetMainTargetVID() == (DWORD) pkChrVictim->GetVID())
    				{
    					//if (!g_iUseLocale)
    					if (LC_IsYMIR())
    						SkillAttackAffect(pkChrVictim, 1000, IMMUNE_STUN, m_pkSk->dwVnum, POINT_NONE, 0, AFF_STUN, 3, m_pkSk->szName);
    					else
    						SkillAttackAffect(pkChrVictim, 1000, IMMUNE_STUN, m_pkSk->dwVnum, POINT_NONE, 0, AFF_STUN, 4, m_pkSk->szName);
    				}
    				else
    				{
    					pkChrVictim->SyncPacket();
    				}
    			}
    		}
    
    		if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_HP_ABSORB))
    		{
    			int iPct = (int) m_pkSk->kPointPoly2.Eval();
    			m_pkChr->PointChange(POINT_HP, iDam * iPct / 100);
    		}
    
    		if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_SP_ABSORB))
    		{
    			int iPct = (int) m_pkSk->kPointPoly2.Eval();
    			m_pkChr->PointChange(POINT_SP, iDam * iPct / 100);
    		}
    
    		if (m_pkSk->dwVnum == SKILL_CHAIN && m_pkChr->GetChainLightningIndex() < m_pkChr->GetChainLightningMaxCount())
    		{
    			chain_lightning_event_info* info = AllocEventInfo<chain_lightning_event_info>();
    
    			info->dwVictim = pkChrVictim->GetVID();
    			info->dwChr = m_pkChr->GetVID();
    
    			event_create(ChainLightningEvent, info, passes_per_sec / 5);
    		}
    		if(test_server)
    			sys_log(0, "FuncSplashDamage End :%s ", m_pkChr->GetName());
    	}
    
    	int		m_x;
    	int		m_y;
    	CSkillProto * m_pkSk;
    	LPCHARACTER	m_pkChr;
    	int		m_iAmount;
    	int		m_iAG;
    	int		m_iCount;
    	int		m_iMaxHit;
    	LPITEM	m_pkWeapon;
    	bool m_bDisableCooltime;
    	TSkillUseInfo* m_pInfo;
    	BYTE m_bUseSkillPower;
    };
    
    struct FuncSplashAffect
    {
    	FuncSplashAffect(LPCHARACTER ch, int x, int y, int iDist, DWORD dwVnum, BYTE bPointOn, int iAmount, DWORD dwAffectFlag, int iDuration, int iSPCost, bool bOverride, int iMaxHit)
    	{
    		m_x = x;
    		m_y = y;
    		m_iDist = iDist;
    		m_dwVnum = dwVnum;
    		m_bPointOn = bPointOn;
    		m_iAmount = iAmount;
    		m_dwAffectFlag = dwAffectFlag;
    		m_iDuration = iDuration;
    		m_iSPCost = iSPCost;
    		m_bOverride = bOverride;
    		m_pkChrAttacker = ch;
    		m_iMaxHit = iMaxHit;
    		m_iCount = 0;
    	}
    
    	void operator () (LPENTITY ent)
    	{
    		if (m_iMaxHit && m_iMaxHit <= m_iCount)
    			return;
    
    		if (ent->IsType(ENTITY_CHARACTER))
    		{
    			LPCHARACTER pkChr = (LPCHARACTER) ent;
    
    			if (test_server)
    				sys_log(0, "FuncSplashAffect step 1 : name:%s vnum:%d iDur:%d", pkChr->GetName(), m_dwVnum, m_iDuration);
    			if (DISTANCE_APPROX(m_x - pkChr->GetX(), m_y - pkChr->GetY()) < m_iDist)
    			{
    				if (test_server)
    					sys_log(0, "FuncSplashAffect step 2 : name:%s vnum:%d iDur:%d", pkChr->GetName(), m_dwVnum, m_iDuration);
    				if (m_dwVnum == SKILL_TUSOK)
    					if (pkChr->CanBeginFight())
    						pkChr->BeginFight(m_pkChrAttacker);
    
    				if (pkChr->IsPC() && m_dwVnum == SKILL_TUSOK)
    					pkChr->AddAffect(m_dwVnum, m_bPointOn, m_iAmount, m_dwAffectFlag, m_iDuration/3, m_iSPCost, m_bOverride);
    				else
    					pkChr->AddAffect(m_dwVnum, m_bPointOn, m_iAmount, m_dwAffectFlag, m_iDuration, m_iSPCost, m_bOverride);
    
    				m_iCount ++;
    			}
    		}
    	}
    
    	LPCHARACTER m_pkChrAttacker;
    	int		m_x;
    	int		m_y;
    	int		m_iDist;
    	DWORD	m_dwVnum;
    	BYTE	m_bPointOn;
    	int		m_iAmount;
    	DWORD	m_dwAffectFlag;
    	int		m_iDuration;
    	int		m_iSPCost;
    	bool	m_bOverride;
    	int         m_iMaxHit;
    	int         m_iCount;
    };
    
    EVENTINFO(skill_gwihwan_info)
    {
    	DWORD pid;
    	BYTE bsklv;
    
    	skill_gwihwan_info()
    	: pid( 0 )
    	, bsklv( 0 )
    	{
    	}
    };
    
    EVENTFUNC(skill_gwihwan_event)
    {
    	skill_gwihwan_info* info = dynamic_cast<skill_gwihwan_info*>( event->info );
    
    	if ( info == NULL )
    	{
    		sys_err( "skill_gwihwan_event> <Factor> Null pointer" );
    		return 0;
    	}
    
    	DWORD pid = info->pid;
    	BYTE sklv= info->bsklv;
    	LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(pid);
    
    	if (!ch)
    		return 0;
    
    	int percent = 20 * sklv - 1;
    
    	if (number(1, 100) <= percent)
    	{
    		PIXEL_POSITION pos;
    
    		// 성공
    		if (SECTREE_MANAGER::instance().GetRecallPositionByEmpire(ch->GetMapIndex(), ch->GetEmpire(), pos))
    		{
    			sys_log(1, "Recall: %s %d %d -> %d %d", ch->GetName(), ch->GetX(), ch->GetY(), pos.x, pos.y);
    			ch->WarpSet(pos.x, pos.y);
    		}
    		else
    		{
    			sys_err("CHARACTER::UseItem : cannot find spawn position (name %s, %d x %d)", ch->GetName(), ch->GetX(), ch->GetY());
    			ch->WarpSet(EMPIRE_START_X(ch->GetEmpire()), EMPIRE_START_Y(ch->GetEmpire()));
    		}
    	}
    	else
    	{
    		//실패
    		ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("귀환에 실패하였습니다."));
    	}
    	return 0;
    }
    
    int CHARACTER::ComputeSkillAtPosition(DWORD dwVnum, const PIXEL_POSITION& posTarget, BYTE bSkillLevel)
    {
    	if (GetMountVnum())
    		return BATTLE_NONE;
    
    	if (IsPolymorphed())
    		return BATTLE_NONE;
    
    	if (g_bSkillDisable)
    		return BATTLE_NONE;
    
    	CSkillProto * pkSk = CSkillManager::instance().Get(dwVnum);
    
    	if (!pkSk)
    		return BATTLE_NONE;
    
    	if (test_server)
    	{
    		sys_log(0, "ComputeSkillAtPosition %s vnum %d x %d y %d level %d", 
    				GetName(), dwVnum, posTarget.x, posTarget.y, bSkillLevel); 
    	}
    
    	// 나에게 쓰는 스킬은 내 위치를 쓴다.
    	//if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY))
    	//	posTarget = GetXYZ();
    
    	// 스플래쉬가 아닌 스킬은 주위이면 이상하다
    	if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    		return BATTLE_NONE;
    
    	if (0 == bSkillLevel)
    	{
    		if ((bSkillLevel = GetSkillLevel(pkSk->dwVnum)) == 0)
    		{
    			return BATTLE_NONE;
    		}
    	}
    
    	const float k = 1.0 * GetSkillPower(pkSk->dwVnum, bSkillLevel) * pkSk->bMaxLevel / 100;
    
    	pkSk->SetPointVar("k", k);
    	pkSk->kSplashAroundDamageAdjustPoly.SetVar("k", k);
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_MELEE_DAMAGE))
    	{
    		pkSk->SetPointVar("atk", CalcMeleeDamage(this, this, true, false));
    	}
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_MAGIC_DAMAGE))
    	{
    		pkSk->SetPointVar("atk", CalcMagicDamage(this, this));
    	}
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_ARROW_DAMAGE))
    	{
    		LPITEM pkBow, pkArrow;
    		if (1 == GetArrowAndBow(&pkBow, &pkArrow, 1))
    		{
    			pkSk->SetPointVar("atk", CalcArrowDamage(this, this, pkBow, pkArrow, true));
    		}
    		else
    		{
    			pkSk->SetPointVar("atk", 0);
    		}
    	}
    
    	if (pkSk->bPointOn == POINT_MOV_SPEED)
    	{
    		pkSk->SetPointVar("maxv", this->GetLimitPoint(POINT_MOV_SPEED));
    	}
    
    	pkSk->SetPointVar("lv", GetLevel());
    	pkSk->SetPointVar("iq", GetPoint(POINT_IQ));
    	pkSk->SetPointVar("str", GetPoint(POINT_ST));
    	pkSk->SetPointVar("dex", GetPoint(POINT_DX));
    	pkSk->SetPointVar("con", GetPoint(POINT_HT));
    	pkSk->SetPointVar("maxhp", this->GetMaxHP());
    	pkSk->SetPointVar("maxsp", this->GetMaxSP());
    	pkSk->SetPointVar("chain", 0);
    	pkSk->SetPointVar("ar", CalcAttackRating(this, this));
    	pkSk->SetPointVar("def", GetPoint(POINT_DEF_GRADE));
    	pkSk->SetPointVar("odef", GetPoint(POINT_DEF_GRADE) - GetPoint(POINT_DEF_GRADE_BONUS));
    	pkSk->SetPointVar("horse_level", GetHorseLevel());
    
    	if (pkSk->bSkillAttrType != SKILL_ATTR_TYPE_NORMAL)
    		OnMove(true);
    
    	LPITEM pkWeapon = GetWear(WEAR_WEAPON);
    
    	SetPolyVarForAttack(this, pkSk, pkWeapon);
    
    	pkSk->SetDurationVar("k", k/*bSkillLevel*/);
    
    	int iAmount = (int) pkSk->kPointPoly.Eval();
    	int iAmount2 = (int) pkSk->kPointPoly2.Eval();
    
    	// ADD_GRANDMASTER_SKILL
    	int iAmount3 = (int) pkSk->kPointPoly3.Eval();
    
    	if (GetUsedSkillMasterType(pkSk->dwVnum) >= SKILL_GRAND_MASTER)
    	{
    		/*
    		   if (iAmount >= 0)
    		   iAmount += (int) m_pkSk->kMasterBonusPoly.Eval();
    		   else
    		   iAmount -= (int) m_pkSk->kMasterBonusPoly.Eval();
    		 */
    		iAmount = (int) pkSk->kMasterBonusPoly.Eval();
    	}
    
    	if (test_server && iAmount == 0 && pkSk->bPointOn != POINT_NONE)
    	{
    		ChatPacket(CHAT_TYPE_INFO, "효과가 없습니다. 스킬 공식을 확인하세요");
    	}
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_REMOVE_BAD_AFFECT))
    	{
    		if (number(1, 100) <= iAmount2)
    		{
    			RemoveBadAffect();
    		}
    	}
    	// END_OF_ADD_GRANDMASTER_SKILL
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_ATTACK | SKILL_FLAG_USE_MELEE_DAMAGE | SKILL_FLAG_USE_MAGIC_DAMAGE))
    	{
    		//
    		// 공격 스킬일 경우
    		//
    		bool bAdded = false;
    
    		if (pkSk->bPointOn == POINT_HP && iAmount < 0)
    		{
    			int iAG = 0;
    
    			FuncSplashDamage f(posTarget.x, posTarget.y, pkSk, this, iAmount, iAG, pkSk->lMaxHit, pkWeapon, m_bDisableCooltime, IsPC()?&m_SkillUseInfo[dwVnum]:NULL, GetSkillPower(dwVnum, bSkillLevel));
    
    			if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    			{
    				if (GetSectree())
    					GetSectree()->ForEachAround(f);
    			}
    			else
    			{
    				//if (dwVnum == SKILL_CHAIN) sys_log(0, "CHAIN skill call FuncSplashDamage %s", GetName());
    				f(this);
    			}
    		}
    		else
    		{
    			//if (dwVnum == SKILL_CHAIN) sys_log(0, "CHAIN skill no damage %d %s", iAmount, GetName());
    			int iDur = (int) pkSk->kDurationPoly.Eval();
    
    			if (IsPC())
    				if (!(dwVnum >= GUILD_SKILL_START && dwVnum <= GUILD_SKILL_END)) // 길드 스킬은 쿨타임 처리를 하지 않는다.
    					if (!m_bDisableCooltime && !m_SkillUseInfo[dwVnum].HitOnce(dwVnum) && dwVnum != SKILL_MUYEONG)
    					{
    						//if (dwVnum == SKILL_CHAIN) sys_log(0, "CHAIN skill cannot hit %s", GetName());
    						return BATTLE_NONE;
    					}
    
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					AddAffect(pkSk->dwVnum, pkSk->bPointOn, iAmount, pkSk->dwAffectFlag, iDur, 0, true);
    				else
    				{
    					if (GetSectree())
    					{
    						FuncSplashAffect f(this, posTarget.x, posTarget.y, pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn, iAmount, pkSk->dwAffectFlag, iDur, 0, true, pkSk->lMaxHit);
    						GetSectree()->ForEachAround(f);
    					}
    				}
    				bAdded = true;
    			}
    		}
    
    		if (pkSk->bPointOn2 != POINT_NONE)
    		{
    			int iDur = (int) pkSk->kDurationPoly2.Eval();
    
    			sys_log(1, "try second %u %d %d", pkSk->dwVnum, pkSk->bPointOn2, iDur);
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					AddAffect(pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur, 0, !bAdded);
    				else
    				{
    					if (GetSectree())
    					{
    						FuncSplashAffect f(this, posTarget.x, posTarget.y, pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur, 0, !bAdded, pkSk->lMaxHit);
    						GetSectree()->ForEachAround(f);
    					}
    				}
    				bAdded = true;
    			}
    			else
    			{
    				PointChange(pkSk->bPointOn2, iAmount2);
    			}
    		}
    
    		// ADD_GRANDMASTER_SKILL
    		if (GetUsedSkillMasterType(pkSk->dwVnum) >= SKILL_GRAND_MASTER && pkSk->bPointOn3 != POINT_NONE)
    		{
    			int iDur = (int) pkSk->kDurationPoly3.Eval();
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					AddAffect(pkSk->dwVnum, pkSk->bPointOn3, iAmount3, 0 /*pkSk->dwAffectFlag3*/, iDur, 0, !bAdded);
    				else
    				{
    					if (GetSectree())
    					{
    						FuncSplashAffect f(this, posTarget.x, posTarget.y, pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn3, iAmount3, 0 /*pkSk->dwAffectFlag3*/, iDur, 0, !bAdded, pkSk->lMaxHit);
    						GetSectree()->ForEachAround(f);
    					}
    				}
    			}
    			else
    			{
    				PointChange(pkSk->bPointOn3, iAmount3);
    			}
    		}
    		// END_OF_ADD_GRANDMASTER_SKILL
    
    		return BATTLE_DAMAGE;
    	}
    	else
    	{
    		bool bAdded = false;
    		int iDur = (int) pkSk->kDurationPoly.Eval();
    
    		if (iDur > 0)
    		{
    			iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    			// AffectFlag가 없거나, toggle 하는 것이 아니라면..
    			pkSk->kDurationSPCostPoly.SetVar("k", k/*bSkillLevel*/);
    
    			AddAffect(pkSk->dwVnum,
    					  pkSk->bPointOn,
    					  iAmount,
    					  pkSk->dwAffectFlag,
    					  iDur,
    					  (long) pkSk->kDurationSPCostPoly.Eval(),
    					  !bAdded);
    
    			bAdded = true;
    		}
    		else
    		{
    			PointChange(pkSk->bPointOn, iAmount);
    		}
    
    		if (pkSk->bPointOn2 != POINT_NONE)
    		{
    			int iDur = (int) pkSk->kDurationPoly2.Eval();
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    				AddAffect(pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur, 0, !bAdded);
    				bAdded = true;
    			}
    			else
    			{
    				PointChange(pkSk->bPointOn2, iAmount2);
    			}
    		}
    
    		// ADD_GRANDMASTER_SKILL
    		if (GetUsedSkillMasterType(pkSk->dwVnum) >= SKILL_GRAND_MASTER && pkSk->bPointOn3 != POINT_NONE)
    		{
    			int iDur = (int) pkSk->kDurationPoly3.Eval();
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    				AddAffect(pkSk->dwVnum, pkSk->bPointOn3, iAmount3, 0 /*pkSk->dwAffectFlag3*/, iDur, 0, !bAdded);
    			}
    			else
    			{
    				PointChange(pkSk->bPointOn3, iAmount3);
    			}
    		}
    		// END_OF_ADD_GRANDMASTER_SKILL
    
    		return BATTLE_NONE;
    	}
    }
    
    // bSkillLevel 인자가 0이 아닐 경우에는 m_abSkillLevels를 사용하지 않고 강제로
    // bSkillLevel로 계산한다.
    int CHARACTER::ComputeSkill(DWORD dwVnum, LPCHARACTER pkVictim, BYTE bSkillLevel)
    {
    	const bool bCanUseHorseSkill = CanUseHorseSkill();
    
    	// 말을 타고있지만 스킬은 사용할 수 없는 상태라면 return
    	if (false == bCanUseHorseSkill && true == IsRiding())
    		return BATTLE_NONE;
    
    	if (IsPolymorphed())
    		return BATTLE_NONE;
    
    	if (g_bSkillDisable)
    		return BATTLE_NONE;
    
    	CSkillProto* pkSk = CSkillManager::instance().Get(dwVnum);
    
    	if (!pkSk)
    		return BATTLE_NONE;
    
    	if (bCanUseHorseSkill && pkSk->dwType != SKILL_TYPE_HORSE)
    		return BATTLE_NONE;
    
    	if (!bCanUseHorseSkill && pkSk->dwType == SKILL_TYPE_HORSE)
    		return BATTLE_NONE;
    	
    
    	// 상대방에게 쓰는 것이 아니면 나에게 써야 한다.
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY))
    		pkVictim = this;
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && !GetParty() && !pkVictim)
    		pkVictim = this;
    
    	if (!pkVictim)
    	{
    		if (test_server)
    			sys_log(0, "ComputeSkill: %s Victim == null, skill %d", GetName(), dwVnum);
    
    		return BATTLE_NONE;
    	}
    
    	if (pkSk->dwTargetRange && DISTANCE_SQRT(GetX() - pkVictim->GetX(), GetY() - pkVictim->GetY()) >= pkSk->dwTargetRange + 50)
    	{
    		if (test_server)
    			sys_log(0, "ComputeSkill: Victim too far, skill %d : %s to %s (distance %u limit %u)", 
    					dwVnum,
    					GetName(),
    					pkVictim->GetName(),
    					(long)DISTANCE_SQRT(GetX() - pkVictim->GetX(), GetY() - pkVictim->GetY()),
    					pkSk->dwTargetRange);
    
    		return BATTLE_NONE;
    	}
    
    	if (0 == bSkillLevel)
    	{
    		if ((bSkillLevel = GetSkillLevel(pkSk->dwVnum)) == 0)
    		{
    			if (test_server)
    				sys_log(0, "ComputeSkill : name:%s vnum:%d  skillLevelBySkill : %d ", GetName(), pkSk->dwVnum, bSkillLevel);
    			return BATTLE_NONE;
    		}
    	}
    
    	if (pkVictim->IsAffectFlag(AFF_PABEOP) && pkVictim->IsGoodAffect(dwVnum))
    	{
    		return BATTLE_NONE;
    	}
    
    	const float k = 1.0 * GetSkillPower(pkSk->dwVnum, bSkillLevel) * pkSk->bMaxLevel / 100;
    
    	pkSk->SetPointVar("k", k);
    	pkSk->kSplashAroundDamageAdjustPoly.SetVar("k", k);
    
    	if (pkSk->dwType == SKILL_TYPE_HORSE)
    	{
    		LPITEM pkBow, pkArrow;
    		if (1 == GetArrowAndBow(&pkBow, &pkArrow, 1))
    		{
    			pkSk->SetPointVar("atk", CalcArrowDamage(this, pkVictim, pkBow, pkArrow, true));
    		}
    		else
    		{
    			pkSk->SetPointVar("atk", CalcMeleeDamage(this, pkVictim, true, false));
    		}
    	}
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_MELEE_DAMAGE))
    	{
    		pkSk->SetPointVar("atk", CalcMeleeDamage(this, pkVictim, true, false));
    	}
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_MAGIC_DAMAGE))
    	{
    		pkSk->SetPointVar("atk", CalcMagicDamage(this, pkVictim));
    	}
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_ARROW_DAMAGE))
    	{
    		LPITEM pkBow, pkArrow;
    		if (1 == GetArrowAndBow(&pkBow, &pkArrow, 1))
    		{
    			pkSk->SetPointVar("atk", CalcArrowDamage(this, pkVictim, pkBow, pkArrow, true));
    		}
    		else
    		{
    			pkSk->SetPointVar("atk", 0);
    		}
    	}
    
    	if (pkSk->bPointOn == POINT_MOV_SPEED)
    	{
    		pkSk->SetPointVar("maxv", pkVictim->GetLimitPoint(POINT_MOV_SPEED));
    	}
    
    	pkSk->SetPointVar("lv", GetLevel());
    	pkSk->SetPointVar("iq", GetPoint(POINT_IQ));
    	pkSk->SetPointVar("str", GetPoint(POINT_ST));
    	pkSk->SetPointVar("dex", GetPoint(POINT_DX));
    	pkSk->SetPointVar("con", GetPoint(POINT_HT));
    	pkSk->SetPointVar("maxhp", pkVictim->GetMaxHP());
    	pkSk->SetPointVar("maxsp", pkVictim->GetMaxSP());
    	pkSk->SetPointVar("chain", 0);
    	pkSk->SetPointVar("ar", CalcAttackRating(this, pkVictim));
    	pkSk->SetPointVar("def", GetPoint(POINT_DEF_GRADE));
    	pkSk->SetPointVar("odef", GetPoint(POINT_DEF_GRADE) - GetPoint(POINT_DEF_GRADE_BONUS));
    	pkSk->SetPointVar("horse_level", GetHorseLevel());
    
    	if (pkSk->bSkillAttrType != SKILL_ATTR_TYPE_NORMAL)
    		OnMove(true);
    
    	LPITEM pkWeapon = GetWear(WEAR_WEAPON);
    
    	SetPolyVarForAttack(this, pkSk, pkWeapon);
    
    	pkSk->kDurationPoly.SetVar("k", k/*bSkillLevel*/);
    	pkSk->kDurationPoly2.SetVar("k", k/*bSkillLevel*/);
    
    	int iAmount = (int) pkSk->kPointPoly.Eval();
    	int iAmount2 = (int) pkSk->kPointPoly2.Eval();
    	int iAmount3 = (int) pkSk->kPointPoly3.Eval();
    
    	if (test_server && IsPC())
    		sys_log(0, "iAmount: %d %d %d , atk:%f skLevel:%f k:%f GetSkillPower(%d) MaxLevel:%d Per:%f",
    				iAmount, iAmount2, iAmount3,
    				pkSk->kPointPoly.GetVar("atk"),
    				pkSk->kPointPoly.GetVar("k"),
    				k,
    				GetSkillPower(pkSk->dwVnum, bSkillLevel),
    				pkSk->bMaxLevel,
    				pkSk->bMaxLevel/100
    				);
    
    	// ADD_GRANDMASTER_SKILL
    	if (GetUsedSkillMasterType(pkSk->dwVnum) >= SKILL_GRAND_MASTER)
    	{
    		iAmount = (int) pkSk->kMasterBonusPoly.Eval();
    	}
    
    	if (test_server && iAmount == 0 && pkSk->bPointOn != POINT_NONE)
    	{
    		ChatPacket(CHAT_TYPE_INFO, "효과가 없습니다. 스킬 공식을 확인하세요");
    	}
    	// END_OF_ADD_GRANDMASTER_SKILL
    
    	//sys_log(0, "XXX SKILL Calc %d Amount %d", dwVnum, iAmount);
    
    	// REMOVE_BAD_AFFECT_BUG_FIX
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_REMOVE_BAD_AFFECT))
    	{
    		if (number(1, 100) <= iAmount2)
    		{
    			pkVictim->RemoveBadAffect();
    		}
    	}
    	// END_OF_REMOVE_BAD_AFFECT_BUG_FIX
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_ATTACK | SKILL_FLAG_USE_MELEE_DAMAGE | SKILL_FLAG_USE_MAGIC_DAMAGE) &&
    		!(pkSk->dwVnum == SKILL_MUYEONG && pkVictim == this) && !(pkSk->IsChargeSkill() && pkVictim == this))
    	{
    		bool bAdded = false;
    
    		if (pkSk->bPointOn == POINT_HP && iAmount < 0)
    		{
    			int iAG = 0;
    			
    
    			FuncSplashDamage f(pkVictim->GetX(), pkVictim->GetY(), pkSk, this, iAmount, iAG, pkSk->lMaxHit, pkWeapon, m_bDisableCooltime, IsPC()?&m_SkillUseInfo[dwVnum]:NULL, GetSkillPower(dwVnum, bSkillLevel));
    			if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    			{
    				if (pkVictim->GetSectree())
    					pkVictim->GetSectree()->ForEachAround(f);
    			}
    			else
    			{
    				f(pkVictim);
    			}
    		}
    		else
    		{
    			pkSk->kDurationPoly.SetVar("k", k/*bSkillLevel*/);
    			int iDur = (int) pkSk->kDurationPoly.Eval();
    			
    
    			if (IsPC())
    				if (!(dwVnum >= GUILD_SKILL_START && dwVnum <= GUILD_SKILL_END)) // 길드 스킬은 쿨타임 처리를 하지 않는다.
    					if (!m_bDisableCooltime && !m_SkillUseInfo[dwVnum].HitOnce(dwVnum) && dwVnum != SKILL_MUYEONG)
    					{
    						return BATTLE_NONE;
    					}
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn, iAmount, pkSk->dwAffectFlag, iDur, 0, true);
    				else
    				{
    					if (pkVictim->GetSectree())
    					{
    						FuncSplashAffect f(this, pkVictim->GetX(), pkVictim->GetY(), pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn, iAmount, pkSk->dwAffectFlag, iDur, 0, true, pkSk->lMaxHit);
    						pkVictim->GetSectree()->ForEachAround(f);
    					}
    				}
    				bAdded = true;
    			}
    		}
    
    		if (pkSk->bPointOn2 != POINT_NONE && !pkSk->IsChargeSkill())
    		{
    			pkSk->kDurationPoly2.SetVar("k", k/*bSkillLevel*/);
    			int iDur = (int) pkSk->kDurationPoly2.Eval();
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur, 0, !bAdded);
    				else
    				{
    					if (pkVictim->GetSectree())
    					{
    						FuncSplashAffect f(this, pkVictim->GetX(), pkVictim->GetY(), pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur, 0, !bAdded, pkSk->lMaxHit);
    						pkVictim->GetSectree()->ForEachAround(f);
    					}
    				}
    
    				bAdded = true;
    			}
    			else
    			{
    				pkVictim->PointChange(pkSk->bPointOn2, iAmount2);
    			}
    		}
    
    		// ADD_GRANDMASTER_SKILL
    		if (pkSk->bPointOn3 != POINT_NONE && !pkSk->IsChargeSkill() && GetUsedSkillMasterType(pkSk->dwVnum) >= SKILL_GRAND_MASTER)
    		{
    			pkSk->kDurationPoly3.SetVar("k", k/*bSkillLevel*/);
    			int iDur = (int) pkSk->kDurationPoly3.Eval();
    
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn3, iAmount3, /*pkSk->dwAffectFlag3*/ 0, iDur, 0, !bAdded);
    				else
    				{
    					if (pkVictim->GetSectree())
    					{
    						FuncSplashAffect f(this, pkVictim->GetX(), pkVictim->GetY(), pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn3, iAmount3, /*pkSk->dwAffectFlag3*/ 0, iDur, 0, !bAdded, pkSk->lMaxHit);
    						pkVictim->GetSectree()->ForEachAround(f);
    					}
    				}
    
    				bAdded = true;
    			}
    			else
    			{
    				pkVictim->PointChange(pkSk->bPointOn3, iAmount3);
    			}
    		}
    		// END_OF_ADD_GRANDMASTER_SKILL
    
    		return BATTLE_DAMAGE;
    	}
    	else
    	{
    		if (dwVnum == SKILL_MUYEONG)
    		{
    			pkSk->kDurationPoly.SetVar("k", k/*bSkillLevel*/);
    			pkSk->kDurationSPCostPoly.SetVar("k", k/*bSkillLevel*/);
    
    			int iDur = (long) pkSk->kDurationPoly.Eval();
    			iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    			if (pkVictim == this)
    				AddAffect(dwVnum,
    						POINT_NONE, 0,
    						AFF_MUYEONG, 
    						iDur,
    						(long) pkSk->kDurationSPCostPoly.Eval(),
    						true);
    
    			return BATTLE_NONE;
    		}
    
    		bool bAdded = false;
    		pkSk->kDurationPoly.SetVar("k", k/*bSkillLevel*/);
    		int iDur = (int) pkSk->kDurationPoly.Eval();
    
    		if (iDur > 0)
    		{
    			iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    			// AffectFlag가 없거나, toggle 하는 것이 아니라면..
    			pkSk->kDurationSPCostPoly.SetVar("k", k/*bSkillLevel*/);
    
    			if (pkSk->bPointOn2 != POINT_NONE)
    			{
    				pkVictim->RemoveAffect(pkSk->dwVnum);
    
    				int iDur2 = (int) pkSk->kDurationPoly2.Eval();
    
    				if (iDur2 > 0)
    				{
    					if (test_server)
    						sys_log(0, "SKILL_AFFECT: %s %s Dur:%d To:%d Amount:%d", 
    								GetName(),
    								pkSk->szName,
    								iDur2,
    								pkSk->bPointOn2,
    								iAmount2);
    
    					iDur2 += GetPoint(POINT_PARTY_BUFFER_BONUS);
    					pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur2, 0, false);
    				}
    				else
    				{
    					pkVictim->PointChange(pkSk->bPointOn2, iAmount2);
    				}
    
    				DWORD affact_flag = pkSk->dwAffectFlag;
    
    				// ADD_GRANDMASTER_SKILL
    				//if (g_iUseLocale)
    				if ( !LC_IsYMIR() )
    				{
    					if ((pkSk->dwVnum == SKILL_CHUNKEON && GetUsedSkillMasterType(pkSk->dwVnum) < SKILL_GRAND_MASTER))
    						affact_flag = AFF_CHEONGEUN_WITH_FALL;
    				}
    				else 
    				{
    					if ((pkSk->dwVnum == SKILL_CHUNKEON && GetUsedSkillMasterType(pkSk->dwVnum) < SKILL_MASTER))
    						affact_flag = AFF_CHEONGEUN_WITH_FALL;
    				}
    				// END_OF_ADD_GRANDMASTER_SKILL
    
    				pkVictim->AddAffect(pkSk->dwVnum,
    						pkSk->bPointOn,
    						iAmount,
    						affact_flag,
    						iDur,
    						(long) pkSk->kDurationSPCostPoly.Eval(),
    						false);
    			}
    			else
    			{
    				if (test_server)
    					sys_log(0, "SKILL_AFFECT: %s %s Dur:%d To:%d Amount:%d", 
    							GetName(),
    							pkSk->szName,
    							iDur,
    							pkSk->bPointOn,
    							iAmount);
    
    				pkVictim->AddAffect(pkSk->dwVnum,
    						pkSk->bPointOn,
    						iAmount,
    						pkSk->dwAffectFlag,
    						iDur,
    						(long) pkSk->kDurationSPCostPoly.Eval(),
    						// ADD_GRANDMASTER_SKILL
    						!bAdded);
    				// END_OF_ADD_GRANDMASTER_SKILL
    			}
    
    			bAdded = true;
    		}
    		else
    		{
    			if (!pkSk->IsChargeSkill())
    				pkVictim->PointChange(pkSk->bPointOn, iAmount);
    
    			if (pkSk->bPointOn2 != POINT_NONE)
    			{
    				pkVictim->RemoveAffect(pkSk->dwVnum);
    
    				int iDur2 = (int) pkSk->kDurationPoly2.Eval();
    
    				if (iDur2 > 0)
    				{
    					iDur2 += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    					if (pkSk->IsChargeSkill())
    						pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn2, iAmount2, AFF_TANHWAN_DASH, iDur2, 0, false);
    					else
    						pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn2, iAmount2, pkSk->dwAffectFlag2, iDur2, 0, false);
    				}
    				else
    				{
    					pkVictim->PointChange(pkSk->bPointOn2, iAmount2);
    				}
    
    			}
    		}
    
    		// ADD_GRANDMASTER_SKILL
    		if (pkSk->bPointOn3 != POINT_NONE && !pkSk->IsChargeSkill() && GetUsedSkillMasterType(pkSk->dwVnum) >= SKILL_GRAND_MASTER)
    		{
    
    			pkSk->kDurationPoly3.SetVar("k", k/*bSkillLevel*/);
    			int iDur = (int) pkSk->kDurationPoly3.Eval();
    
    			sys_log(0, "try third %u %d %d %d 1894", pkSk->dwVnum, pkSk->bPointOn3, iDur, iAmount3);
    
    			if (iDur > 0)
    			{
    				iDur += GetPoint(POINT_PARTY_BUFFER_BONUS);
    
    				if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_SPLASH))
    					pkVictim->AddAffect(pkSk->dwVnum, pkSk->bPointOn3, iAmount3, /*pkSk->dwAffectFlag3*/ 0, iDur, 0, !bAdded);
    				else
    				{
    					if (pkVictim->GetSectree())
    					{
    						FuncSplashAffect f(this, pkVictim->GetX(), pkVictim->GetY(), pkSk->iSplashRange, pkSk->dwVnum, pkSk->bPointOn3, iAmount3, /*pkSk->dwAffectFlag3*/ 0, iDur, 0, !bAdded, pkSk->lMaxHit);
    						pkVictim->GetSectree()->ForEachAround(f);
    					}
    				}
    
    				bAdded = true;
    			}
    			else
    			{
    				pkVictim->PointChange(pkSk->bPointOn3, iAmount3);
    			}
    		}
    		// END_OF_ADD_GRANDMASTER_SKILL
    
    		return BATTLE_NONE;
    	}
    }
    
    bool CHARACTER::UseSkill(DWORD dwVnum, LPCHARACTER pkVictim, bool bUseGrandMaster)
    {
    	if (false == CanUseSkill(dwVnum))
    		return false;
    
    #ifdef ITEM_BUFF_SYSTEM
    	if (pkVictim)
    	{
    	if(pkVictim != this && pkVictim->IsAffectFlag(AFF_RESIST_BUFF) && (dwVnum == SKILL_HOSIN))
    		return false;
    	else if(this && IsAffectFlag(AFF_RESIST_BUFF) && (dwVnum == SKILL_HOSIN) && (pkVictim == this))
    		return false;
    	}
    
    	if (pkVictim)
    	{
    	if(pkVictim != this && pkVictim->IsAffectFlag(AFF_CRITICAL_BUFF) && (dwVnum == SKILL_GICHEON))
    		return false;
    	else if(this && IsAffectFlag(AFF_CRITICAL_BUFF) && (dwVnum == SKILL_GICHEON) && (pkVictim == this))
    		return false; 
    	}
    
    	if (pkVictim)
    	{
    	if(pkVictim != this && pkVictim->IsAffectFlag(AFF_REFLECT_BUFF) && (dwVnum == SKILL_REFLECT))
    		return false;
    	else if(this && IsAffectFlag(AFF_REFLECT_BUFF) && (dwVnum == SKILL_REFLECT) && (pkVictim == this))
    		return false; 	
    	}		
    #endif
    
    #ifdef ENABLE_BLOCK_SKILL_OXEVENT
    	DWORD szFlagValue = quest::CQuestManager::instance().GetEventFlag("cannot_use_skill_ox");
    
    	if (szFlagValue > 0 && GetMapIndex() == 113 && !IsGM())
    	{
    		if (quest::CQuestManager::instance().GetEventFlag("oxevent_status") != 0)
    		{
    			ChatPacket(CHAT_TYPE_NOTICE, LC_TEXT("ox_player_skill_block"));
    			return false;
    		}
    	}
    #endif
    
    	// NO_GRANDMASTER
    	if (test_server)
    	{
    		if (quest::CQuestManager::instance().GetEventFlag("no_grand_master"))
    		{
    			bUseGrandMaster = false;
    		}
    	}
    	// END_OF_NO_GRANDMASTER
    
    	if (g_bSkillDisable)
    		return false;
    
    	if (IsObserverMode())
    		return false;
    
    	if (!CanMove())
    		return false;
    
    	if (IsPolymorphed())
    		return false;
    
    	const bool bCanUseHorseSkill = CanUseHorseSkill();
    
    
    	if (dwVnum == SKILL_HORSE_SUMMON)
    	{
    		if (GetSkillLevel(dwVnum) == 0)
    			return false;
    
    		if (GetHorseLevel() <= 0)
    			ChatPacket(CHAT_TYPE_INFO, LC_TEXT("말이 없습니다. 마굿간 경비병을 찾아가세요."));
    		else
    			ChatPacket(CHAT_TYPE_INFO, LC_TEXT("말 소환 아이템을 사용하세요."));
    
    		return true;
    	}
    
    	// 말을 타고있지만 스킬은 사용할 수 없는 상태라면 return false
    	if (false == bCanUseHorseSkill && true == IsRiding())
    		return false;
    
    	CSkillProto * pkSk = CSkillManager::instance().Get(dwVnum);
    	sys_log(0, "%s: USE_SKILL: %d pkVictim %p", GetName(), dwVnum, get_pointer(pkVictim));
    
    	if (!pkSk)
    		return false;
    
    	if (bCanUseHorseSkill && pkSk->dwType != SKILL_TYPE_HORSE)
    		return BATTLE_NONE;
    
    	if (!bCanUseHorseSkill && pkSk->dwType == SKILL_TYPE_HORSE)
    		return BATTLE_NONE;
    
    	if (GetSkillLevel(dwVnum) == 0)
    		return false;
    	
    
    	// NO_GRANDMASTER
    	if (GetSkillMasterType(dwVnum) < SKILL_GRAND_MASTER)
    		bUseGrandMaster = false;
    	// END_OF_NO_GRANDMASTER
    
    	// MINING
    	if (GetWear(WEAR_WEAPON) && (GetWear(WEAR_WEAPON)->GetType() == ITEM_ROD || GetWear(WEAR_WEAPON)->GetType() == ITEM_PICK))
    		return false;
    	// END_OF_MINING
    
    	m_SkillUseInfo[dwVnum].TargetVIDMap.clear();
    
    	if (pkSk->IsChargeSkill())
    	{
    		if (IsAffectFlag(AFF_TANHWAN_DASH) || pkVictim && pkVictim != this)
    		{
    			if (!pkVictim)
    				return false;
    
    			if (!IsAffectFlag(AFF_TANHWAN_DASH))
    			{
    				if (!UseSkill(dwVnum, this))
    					return false;
    			}
    
    			m_SkillUseInfo[dwVnum].SetMainTargetVID(pkVictim->GetVID());
    			// DASH 상태의 탄환격은 공격기술
    			ComputeSkill(dwVnum, pkVictim);
    			RemoveAffect(dwVnum);
    			return true;
    		}
    	}
    
    	if (dwVnum == SKILL_COMBO)
    	{
    		if (m_bComboIndex)
    			m_bComboIndex = 0;
    		else
    			m_bComboIndex = GetSkillLevel(SKILL_COMBO);
    
    		ChatPacket(CHAT_TYPE_COMMAND, "combo %d", m_bComboIndex);
    		return true;
    	}
    
    	// Toggle 할 때는 SP를 쓰지 않음 (SelfOnly로 구분)
    	if ((0 != pkSk->dwAffectFlag || pkSk->dwVnum == SKILL_MUYEONG) && (pkSk->dwFlag & SKILL_FLAG_TOGGLE) && RemoveAffect(pkSk->dwVnum))
    	{
    		return true;
    	}
    
    	if (IsAffectFlag(AFF_REVIVE_INVISIBLE))
    		RemoveAffect(AFFECT_REVIVE_INVISIBLE);
    
    	const float k = 1.0 * GetSkillPower(pkSk->dwVnum) * pkSk->bMaxLevel / 100;
    
    	pkSk->SetPointVar("k", k);
    	pkSk->kSplashAroundDamageAdjustPoly.SetVar("k", k);
    
    	// 쿨타임 체크
    	pkSk->kCooldownPoly.SetVar("k", k);
    	int iCooltime = (int) pkSk->kCooldownPoly.Eval();
    	int lMaxHit = pkSk->lMaxHit ? pkSk->lMaxHit : -1;
    
    	pkSk->SetSPCostVar("k", k);
    
    	DWORD dwCur = get_dword_time();
    
    	if (dwVnum == SKILL_TERROR && m_SkillUseInfo[dwVnum].bUsed && m_SkillUseInfo[dwVnum].dwNextSkillUsableTime > dwCur )
    	{
    		sys_log(0, " SKILL_TERROR's Cooltime is not delta over %u", m_SkillUseInfo[dwVnum].dwNextSkillUsableTime  - dwCur );
    		return false;
    	}
    
    	int iNeededSP = 0;
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_USE_HP_AS_COST))
    	{
    		pkSk->SetSPCostVar("maxhp", GetMaxHP());
    		pkSk->SetSPCostVar("v", GetHP());
    		iNeededSP = (int) pkSk->kSPCostPoly.Eval();
    
    		// ADD_GRANDMASTER_SKILL
    		if (GetSkillMasterType(dwVnum) >= SKILL_GRAND_MASTER && bUseGrandMaster)
    		{
    			iNeededSP = (int) pkSk->kGrandMasterAddSPCostPoly.Eval();
    		}
    		// END_OF_ADD_GRANDMASTER_SKILL	
    
    		if (GetHP() < iNeededSP)
    			return false;
    
    		PointChange(POINT_HP, -iNeededSP);
    	}
    	else
    	{
    		// SKILL_FOMULA_REFACTORING
    		pkSk->SetSPCostVar("maxhp", GetMaxHP());
    		pkSk->SetSPCostVar("maxv", GetMaxSP());
    		pkSk->SetSPCostVar("v", GetSP());
    
    		iNeededSP = (int) pkSk->kSPCostPoly.Eval();
    
    		if (GetSkillMasterType(dwVnum) >= SKILL_GRAND_MASTER && bUseGrandMaster)
    		{
    			iNeededSP = (int) pkSk->kGrandMasterAddSPCostPoly.Eval();
    		}
    		// END_OF_SKILL_FOMULA_REFACTORING
    
    		if (GetSP() < iNeededSP)
    			return false;
    
    		if (test_server)
    			ChatPacket(CHAT_TYPE_INFO, LC_TEXT("%s SP소모: %d"), pkSk->szName, iNeededSP);
    
    		PointChange(POINT_SP, -iNeededSP);
    	}
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY))
    		pkVictim = this;
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && !GetParty() && !pkVictim)
    		pkVictim = this;
    
    	if (pkSk->dwVnum == SKILL_MUYEONG || pkSk->IsChargeSkill() && !IsAffectFlag(AFF_TANHWAN_DASH) && !pkVictim)
    	{
    		// 처음 사용하는 무영진은 자신에게 Affect를 붙인다.
    		pkVictim = this;
    	}
    
    	int iSplashCount = 1;
    
    	if (false == m_bDisableCooltime)
    	{
    		if (false == 
    				m_SkillUseInfo[dwVnum].UseSkill(
    					bUseGrandMaster,
    				   	(NULL != pkVictim && SKILL_HORSE_WILDATTACK != dwVnum) ? pkVictim->GetVID() : NULL,
    				   	ComputeCooltime(iCooltime * 1000),
    				   	iSplashCount,
    				   	lMaxHit))
    		{
    			if (test_server)
    				ChatPacket(CHAT_TYPE_NOTICE, "cooltime not finished %s %d", pkSk->szName, iCooltime);
    
    			return false;
    		}
    	}
    
    	if (dwVnum == SKILL_CHAIN)
    	{
    		ResetChainLightningIndex();
    		AddChainLightningExcept(pkVictim);
    	}
    	
    
    	if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY))
    		ComputeSkill(dwVnum, this);
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && !GetParty() && !pkVictim )
    		ComputeSkill(dwVnum, this);
    	else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && GetParty())
    	{
    		FPartyPIDCollector f;
    		GetParty()->ForEachOnMapMember(f, GetMapIndex());
    			for (std::vector <DWORD>::iterator it = f.vecPIDs.begin(); it != f.vecPIDs.end(); it++)
    			{
    				LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(*it);
    				ComputeSkill(dwVnum, ch);
    			}
    	}
    	else if (!IS_SET(pkSk->dwFlag, SKILL_FLAG_ATTACK))
    		ComputeSkill(dwVnum, pkVictim);
    	else if (dwVnum == SKILL_BYEURAK)
    		ComputeSkill(dwVnum, pkVictim);
    	else if (dwVnum == SKILL_MUYEONG || pkSk->IsChargeSkill())
    		ComputeSkill(dwVnum, pkVictim);
    
    	m_dwLastSkillTime = get_dword_time();
    
    	return true;
    }
    
    int CHARACTER::GetUsedSkillMasterType(DWORD dwVnum)
    {
    	const TSkillUseInfo& rInfo = m_SkillUseInfo[dwVnum];
    
    	if (GetSkillMasterType(dwVnum) < SKILL_GRAND_MASTER)
    		return GetSkillMasterType(dwVnum);
    
    	if (rInfo.isGrandMaster)
    		return GetSkillMasterType(dwVnum);
    
    	return MIN(GetSkillMasterType(dwVnum), SKILL_MASTER);
    }
    
    int CHARACTER::GetSkillMasterType(DWORD dwVnum) const
    {
    	if (!IsPC())
    		return 0;
    
    	if (dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("%s skill vnum overflow %u", GetName(), dwVnum);
    		return 0;
    	}
    
    	return m_pSkillLevels ? m_pSkillLevels[dwVnum].bMasterType:SKILL_NORMAL;
    }
    
    int CHARACTER::GetSkillPower(DWORD dwVnum, BYTE bLevel) const
    {
    	// 인어반지 아이템
    	if (dwVnum >= SKILL_LANGUAGE1 && dwVnum <= SKILL_LANGUAGE3 && IsEquipUniqueGroup(UNIQUE_GROUP_RING_OF_LANGUAGE))
    	{
    		return 100;
    	}
    
    	if (dwVnum >= GUILD_SKILL_START && dwVnum <= GUILD_SKILL_END)
    	{
    		if (GetGuild())
    			return 100 * GetGuild()->GetSkillLevel(dwVnum) / 7 / 7;
    		else
    			return 0;
    	}
    
    	if (bLevel)
    	{
    		//SKILL_POWER_BY_LEVEL
    		return GetSkillPowerByLevel(bLevel, true);
    		//END_SKILL_POWER_BY_LEVEL;
    	}
    
    	if (dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("%s skill vnum overflow %u", GetName(), dwVnum);
    		return 0;
    	}
    
    	//SKILL_POWER_BY_LEVEL
    	return GetSkillPowerByLevel(GetSkillLevel(dwVnum));
    	//SKILL_POWER_BY_LEVEL
    }
    
    int CHARACTER::GetSkillLevel(DWORD dwVnum) const
    {
    	if (dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("%s skill vnum overflow %u", GetName(), dwVnum);
    		sys_log(0, "%s skill vnum overflow %u", GetName(), dwVnum);
    		return 0;
    	}
    
    	return MIN(SKILL_MAX_LEVEL, m_pSkillLevels ? m_pSkillLevels[dwVnum].bLevel : 0);
    }
    
    EVENTFUNC(skill_muyoung_event)
    {
    	char_event_info* info = dynamic_cast<char_event_info*>( event->info );
    
    	if ( info == NULL )
    	{
    		sys_err( "skill_muyoung_event> <Factor> Null pointer" );
    		return 0;
    	}
    
    	LPCHARACTER	ch = info->ch;
    
    	if (ch == NULL) { // <Factor>
    		return 0;
    	}
    
    	if (!ch->IsAffectFlag(AFF_MUYEONG))
    	{
    		ch->StopMuyeongEvent();
    		return 0;
    	}
    
    	// 1. Find Victim
    	FFindNearVictim f(ch, ch);
    	if (ch->GetSectree())
    	{
    		ch->GetSectree()->ForEachAround(f);
    		// 2. Shoot!
    		if (f.GetVictim())
    		{
    			ch->CreateFly(FLY_SKILL_MUYEONG, f.GetVictim());
    			ch->ComputeSkill(SKILL_MUYEONG, f.GetVictim());
    		}
    	}
    
    	return PASSES_PER_SEC(3);
    }
    
    void CHARACTER::StartMuyeongEvent()
    {
    	if (m_pkMuyeongEvent)
    		return;
    
    	char_event_info* info = AllocEventInfo<char_event_info>();
    
    	info->ch = this;
    	m_pkMuyeongEvent = event_create(skill_muyoung_event, info, PASSES_PER_SEC(1));
    }
    
    void CHARACTER::StopMuyeongEvent()
    {
    	event_cancel(&m_pkMuyeongEvent);
    }
    
    void CHARACTER::SkillLearnWaitMoreTimeMessage(DWORD ms)
    {
    	//const char* str = "";
    	//
    	if (ms < 3 * 60)
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("몸 속이 뜨겁군. 하지만 아주 편안해. 이대로 기를 안정시키자."));
    	else if (ms < 5 * 60)
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("그래, 천천히. 좀더 천천히, 그러나 막힘 없이 빠르게!"));
    	else if (ms < 10 * 60) // 10분
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("그래, 이 느낌이야. 체내에 기가 아주 충만해."));
    	else if (ms < 30 * 60) // 30분
    	{
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("다 읽었다! 이제 비급에 적혀있는 대로 전신에 기를 돌리기만 하면,"));
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("그것으로 수련은 끝난 거야!"));
    	}
    	else if (ms < 1 * 3600) // 1시간
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("이제 책의 마지막 장이야! 수련의 끝이 눈에 보이고 있어!"));
    	else if (ms < 2 * 3600) // 2시간
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("얼마 안 남았어! 조금만 더!"));
    	else if (ms < 3 * 3600)
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("좋았어! 조금만 더 읽으면 끝이다!"));
    	else if (ms < 6 * 3600)
    	{
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("책장도 이제 얼마 남지 않았군."));
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("뭔가 몸 안에 힘이 생기는 기분인 걸."));
    	}
    	else if (ms < 12 * 3600)
    	{
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("이제 좀 슬슬 가닥이 잡히는 것 같은데."));
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("좋아, 이 기세로 계속 나간다!"));
    	}
    	else if (ms < 18 * 3600)
    	{
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("아니 어떻게 된 게 종일 읽어도 머리에 안 들어오냐."));
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("공부하기 싫어지네."));
    	}
    	else //if (ms < 2 * 86400)
    	{
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("생각만큼 읽기가 쉽지가 않군. 이해도 어렵고 내용도 난해해."));
    		ChatPacket(CHAT_TYPE_TALKING, "%s", LC_TEXT("이래서야 공부가 안된다구."));
    	}
    	/*
    	   str = "30%";
    	   else if (ms < 3 * 86400)
    	   str = "10%";
    	   else if (ms < 4 * 86400)
    	   str = "5%";
    	   else
    	   str = "0%";*/
    
    	//ChatPacket(CHAT_TYPE_TALKING, "%s", str);
    }
    
    void CHARACTER::DisableCooltime()
    {
    	m_bDisableCooltime = true;
    }
    
    bool CHARACTER::HasMobSkill() const
    {
    	return CountMobSkill() > 0;
    }
    
    size_t CHARACTER::CountMobSkill() const
    {
    	if (!m_pkMobData)
    		return 0;
    
    	size_t c = 0;
    
    	for (size_t i = 0; i < MOB_SKILL_MAX_NUM; ++i)
    		if (m_pkMobData->m_table.Skills[i].dwVnum)
    			++c;
    
    	return c;
    }
    
    const TMobSkillInfo* CHARACTER::GetMobSkill(unsigned int idx) const
    {
    	if (idx >= MOB_SKILL_MAX_NUM)
    		return NULL;
    
    	if (!m_pkMobData)
    		return NULL;
    
    	if (0 == m_pkMobData->m_table.Skills[idx].dwVnum)
    		return NULL;
    
    	return &m_pkMobData->m_mobSkillInfo[idx];
    }
    
    bool CHARACTER::CanUseMobSkill(unsigned int idx) const
    {
    	const TMobSkillInfo* pInfo = GetMobSkill(idx);
    
    	if (!pInfo)
    		return false;
    
    	if (m_adwMobSkillCooltime[idx] > get_dword_time())
    		return false;
    
    	if (number(0, 1))
    		return false;
    
    	return true;
    }
    
    EVENTINFO(mob_skill_event_info)
    {
    	DynamicCharacterPtr ch;
    	PIXEL_POSITION pos;
    	DWORD vnum;
    	int index;
    	BYTE level;
    
    	mob_skill_event_info()
    	: ch()
    	, pos()
    	, vnum(0)
    	, index(0)
    	, level(0)
    	{
    	}
    };
    
    EVENTFUNC(mob_skill_hit_event)
    {
    	mob_skill_event_info * info = dynamic_cast<mob_skill_event_info *>( event->info );
    
    	if ( info == NULL )
    	{
    		sys_err( "mob_skill_event_info> <Factor> Null pointer" );
    		return 0;
    	}
    
    	// <Factor>
    	LPCHARACTER ch = info->ch;
    	if (ch == NULL) {
    		return 0;
    	}
    
    	ch->ComputeSkillAtPosition(info->vnum, info->pos, info->level);
    	ch->m_mapMobSkillEvent.erase(info->index);
    
    	return 0;
    }
    
    #ifdef __VERSION_162__
    struct FHealerParty
    {
    	FHealerParty(LPCHARACTER pkHealer) : m_pkHealer(pkHealer) {}
    	
    	void operator () (LPCHARACTER ch)
    	{
    		int iRevive = (int)(m_pkHealer->GetMaxHP() / 100 * 15);
    		int iHP = (ch->GetMaxHP() >= ch->GetHP() + iRevive) ? (int)(ch->GetHP() + iRevive) : (int)(ch->GetMaxHP());
    		ch->SetHP(iHP);
    		ch->EffectPacket(SE_EFFECT_HEALER);
    		sys_log(0, "FHealerParty: %s (pointer: %p) heal the HP of %s (pointer: %p) with %d (new HP: %d).", m_pkHealer->GetName(), get_pointer(m_pkHealer), ch->GetName(), get_pointer(ch), iRevive, ch->GetHP());
    	}
    	
    	LPCHARACTER	m_pkHealer;
    };
    #endif
    
    bool CHARACTER::UseMobSkill(unsigned int idx)
    {
    	if (IsPC())
    		return false;
    
    	const TMobSkillInfo* pInfo = GetMobSkill(idx);
    
    	if (!pInfo)
    		return false;
    
    	DWORD dwVnum = pInfo->dwSkillVnum;
    	CSkillProto * pkSk = CSkillManager::instance().Get(dwVnum);
    
    	if (!pkSk)
    		return false;
    
    	const float k = 1.0 * GetSkillPower(pkSk->dwVnum, pInfo->bSkillLevel) * pkSk->bMaxLevel / 100;
    
    	pkSk->kCooldownPoly.SetVar("k", k);
    	int iCooltime = (int) (pkSk->kCooldownPoly.Eval() * 1000);
    
    	m_adwMobSkillCooltime[idx] = get_dword_time() + iCooltime;
    
    	sys_log(0, "USE_MOB_SKILL: %s idx %d vnum %u cooltime %d", GetName(), idx, dwVnum, iCooltime);
    
    #ifdef __VERSION_162__
    	if ((IsMonster()) && (pkSk->dwVnum == HEALING_SKILL_VNUM))
    	{
    		LPPARTY pkParty = GetParty();
    		if ((pkParty) && (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY)))
    		{
    			FHealerParty f(this);
    			pkParty->ForEachMemberPtr(f);
    		}
    		else
    		{
    			int iRevive = (int)(GetMaxHP() / 100 * 15);
    			int iHP = (GetMaxHP() >= GetHP() + iRevive) ? (int)(GetHP() + iRevive) : (int)(GetMaxHP());
    			SetHP(iHP);
    			EffectPacket(SE_EFFECT_HEALER);
    			sys_log(0, "FHealer: %s (pointer: %p) heal their HP with %d (new HP: %d).", GetName(), get_pointer(this), iRevive, GetHP());
    		}
    		
    		return true;
    	}
    #endif
    	
    	if (m_pkMobData->m_mobSkillInfo[idx].vecSplashAttack.empty())
    	{
    		sys_err("No skill hit data for mob %s index %d", GetName(), idx);
    		return false;
    	}
    
    	for (size_t i = 0; i < m_pkMobData->m_mobSkillInfo[idx].vecSplashAttack.size(); i++)
    	{
    		PIXEL_POSITION pos = GetXYZ();
    		const TMobSplashAttackInfo& rInfo = m_pkMobData->m_mobSkillInfo[idx].vecSplashAttack[i];
    
    		if (rInfo.dwHitDistance)
    		{
    			float fx, fy;
    			GetDeltaByDegree(GetRotation(), rInfo.dwHitDistance, &fx, &fy);
    			pos.x += (long) fx;
    			pos.y += (long) fy;
    		}
    
    		if (rInfo.dwTiming)
    		{
    			if (test_server)
    				sys_log(0, "               timing %ums", rInfo.dwTiming);
    
    			mob_skill_event_info* info = AllocEventInfo<mob_skill_event_info>();
    
    			info->ch = this;
    			info->pos = pos;
    			info->level = pInfo->bSkillLevel;
    			info->vnum = dwVnum;
    			info->index = i;
    
    			// <Factor> Cancel existing event first
    			itertype(m_mapMobSkillEvent) it = m_mapMobSkillEvent.find(i);
    			if (it != m_mapMobSkillEvent.end()) {
    				LPEVENT existing = it->second;
    				event_cancel(&existing);
    				m_mapMobSkillEvent.erase(it);
    			}
    
    			m_mapMobSkillEvent.insert(std::make_pair(i, event_create(mob_skill_hit_event, info, PASSES_PER_SEC(rInfo.dwTiming) / 1000)));
    		}
    		else
    		{
    			ComputeSkillAtPosition(dwVnum, pos, pInfo->bSkillLevel);
    		}
    	}
    
    	return true;
    }
    
    void CHARACTER::ResetMobSkillCooltime()
    {
    	memset(m_adwMobSkillCooltime, 0, sizeof(m_adwMobSkillCooltime));
    }
    
    bool CHARACTER::IsUsableSkillMotion(DWORD dwMotionIndex) const
    {
    	DWORD selfJobGroup = (GetJob() + 1) * 10 + GetSkillGroup();
    
    	const DWORD SKILL_NUM = 176;
    	static DWORD s_anSkill2JobGroup[SKILL_NUM] = {
    		0, // common_skill 0
    		11, // job_skill 1
    		11, // job_skill 2
    		11, // job_skill 3
    		11, // job_skill 4
    		11, // job_skill 5
    		11, // job_skill 6
    		0, // common_skill 7
    		0, // common_skill 8
    		0, // common_skill 9
    		0, // common_skill 10
    		0, // common_skill 11
    		0, // common_skill 12
    		0, // common_skill 13
    		0, // common_skill 14
    		0, // common_skill 15
    		12, // job_skill 16
    		12, // job_skill 17
    		12, // job_skill 18
    		12, // job_skill 19
    		12, // job_skill 20
    		12, // job_skill 21
    		0, // common_skill 22
    		0, // common_skill 23
    		0, // common_skill 24
    		0, // common_skill 25
    		0, // common_skill 26
    		0, // common_skill 27
    		0, // common_skill 28
    		0, // common_skill 29
    		0, // common_skill 30
    		21, // job_skill 31
    		21, // job_skill 32
    		21, // job_skill 33
    		21, // job_skill 34
    		21, // job_skill 35
    		21, // job_skill 36
    		0, // common_skill 37
    		0, // common_skill 38
    		0, // common_skill 39
    		0, // common_skill 40
    		0, // common_skill 41
    		0, // common_skill 42
    		0, // common_skill 43
    		0, // common_skill 44
    		0, // common_skill 45
    		22, // job_skill 46
    		22, // job_skill 47
    		22, // job_skill 48
    		22, // job_skill 49
    		22, // job_skill 50
    		22, // job_skill 51
    		0, // common_skill 52
    		0, // common_skill 53
    		0, // common_skill 54
    		0, // common_skill 55
    		0, // common_skill 56
    		0, // common_skill 57
    		0, // common_skill 58
    		0, // common_skill 59
    		0, // common_skill 60
    		31, // job_skill 61
    		31, // job_skill 62
    		31, // job_skill 63
    		31, // job_skill 64
    		31, // job_skill 65
    		31, // job_skill 66
    		0, // common_skill 67
    		0, // common_skill 68
    		0, // common_skill 69
    		0, // common_skill 70
    		0, // common_skill 71
    		0, // common_skill 72
    		0, // common_skill 73
    		0, // common_skill 74
    		0, // common_skill 75
    		32, // job_skill 76
    		32, // job_skill 77
    		32, // job_skill 78
    		32, // job_skill 79
    		32, // job_skill 80
    		32, // job_skill 81
    		0, // common_skill 82
    		0, // common_skill 83
    		0, // common_skill 84
    		0, // common_skill 85
    		0, // common_skill 86
    		0, // common_skill 87
    		0, // common_skill 88
    		0, // common_skill 89
    		0, // common_skill 90
    		41, // job_skill 91
    		41, // job_skill 92
    		41, // job_skill 93
    		41, // job_skill 94
    		41, // job_skill 95
    		41, // job_skill 96
    		0, // common_skill 97
    		0, // common_skill 98
    		0, // common_skill 99
    		0, // common_skill 100
    		0, // common_skill 101
    		0, // common_skill 102
    		0, // common_skill 103
    		0, // common_skill 104
    		0, // common_skill 105
    		42, // job_skill 106
    		42, // job_skill 107
    		42, // job_skill 108
    		42, // job_skill 109
    		42, // job_skill 110
    		42, // job_skill 111
    		0, // common_skill 112
    		0, // common_skill 113
    		0, // common_skill 114
    		0, // common_skill 115
    		0, // common_skill 116
    		0, // common_skill 117
    		0, // common_skill 118
    		0, // common_skill 119
    		0, // common_skill 120
    		0, // common_skill 121
    		0, // common_skill 122
    		0, // common_skill 123
    		0, // common_skill 124
    		0, // common_skill 125
    		0, // common_skill 126
    		0, // common_skill 127
    		0, // common_skill 128
    		0, // common_skill 129
    		0, // common_skill 130
    		0, // common_skill 131
    		0, // common_skill 132
    		0, // common_skill 133
    		0, // common_skill 134
    		0, // common_skill 135
    		0, // common_skill 136
    		0, // job_skill 137
    		0, // job_skill 138
    		0, // job_skill 139
    		0, // job_skill 140
    		0, // common_skill 141
    		0, // common_skill 142
    		0, // common_skill 143
    		0, // common_skill 144
    		0, // common_skill 145
    		0, // common_skill 146
    		0, // common_skill 147
    		0, // common_skill 148
    		0, // common_skill 149
    		0, // common_skill 150
    		0, // common_skill 151
    		0, // job_skill 152
    		0, // job_skill 153
    		0, // job_skill 154
    		0, // job_skill 155
    		0, // job_skill 156
    		0, // job_skill 157
    		0, // empty(reserved)	158
    		0, // empty(reserved)	159
    		0, // empty(reserved)	160
    		0, // empty(reserved)	161
    		0, // empty(reserved)	162
    		0, // empty(reserved)	163
    		0, // empty(reserved)	164
    		0, // empty(reserved)	165
    		0, // empty(reserved)	166
    		0, // empty(reserved)	167
    		0, // empty(reserved)	168
    		0, // empty(reserved)	169
    		51, // job_skill(WOLFMAN SKILL)	170
    		51, // job_skill(WOLFMAN SKILL)	171
    		51, // job_skill(WOLFMAN SKILL)	172
    		51, // job_skill(WOLFMAN SKILL)	173
    		51, // job_skill(WOLFMAN SKILL)	174
    		51, // job_skill(WOLFMAN SKILL)	175
    	};
    
    	const DWORD MOTION_MAX_NUM = 124;
    	const DWORD SKILL_LIST_MAX_COUNT = 6;
    
    	static DWORD s_anMotion2SkillVnumList[MOTION_MAX_NUM][SKILL_LIST_MAX_COUNT] =
    	{
    		{ 0, 0, 0, 0, 0 }, //  0
    		{ 5, 1, 31, 61, 91, 170, }, //  1
    		{ 5, 2, 32, 62, 92, 171, }, //  2
    		{ 5, 3, 33, 63, 93, 172, }, //  3
    		{ 5, 4, 34, 64, 94, 173, }, //  4
    		{ 5, 5, 35, 65, 95, 174, }, //  5
    		{ 5, 6, 36, 66, 96, 175, }, //  6
    		{ 0, 0, 0, 0, 0, }, //  7
    		{ 0, 0, 0, 0, 0, }, //  8
    		{ 0, 0, 0, 0, 0, }, //  9
    		{ 0, 0, 0, 0, 0, }, //  10
    		{ 0, 0, 0, 0, 0, }, //  11
    		{ 0, 0, 0, 0, 0, }, //  12
    		{ 0, 0, 0, 0, 0, }, //  13
    		{ 0, 0, 0, 0, 0, }, //  14
    		{ 0, 0, 0, 0, 0, }, //  15
    		{ 4, 16, 46, 76, 106, }, //  16
    		{ 4, 17, 47, 77, 107, }, //  17
    		{ 4, 18, 48, 78, 108, }, //  18
    		{ 4, 19, 49, 79, 109, }, //  19
    		{ 4, 20, 50, 80, 110, }, //  20
    		{ 4, 21, 51, 81, 111, }, //  21
    		{ 0, 0, 0, 0, 0, }, //  22
    		{ 0, 0, 0, 0, 0, }, //  23
    		{ 0, 0, 0, 0, 0, }, //  24
    		{ 0, 0, 0, 0, 0, }, //  25
    		{ 5, 1, 31, 61, 91, 170, }, //  26
    		{ 5, 2, 32, 62, 92, 171, }, //  27
    		{ 5, 3, 33, 63, 93, 172, }, //  28
    		{ 5, 4, 34, 64, 94, 173, }, //  29
    		{ 5, 5, 35, 65, 95, 174, }, //  30
    		{ 5, 6, 36, 66, 96, 175, }, //  31
    		{ 0, 0, 0, 0, 0, }, //  32
    		{ 0, 0, 0, 0, 0, }, //  33
    		{ 0, 0, 0, 0, 0, }, //  34
    		{ 0, 0, 0, 0, 0, }, //  35
    		{ 0, 0, 0, 0, 0, }, //  36
    		{ 0, 0, 0, 0, 0, }, //  37
    		{ 0, 0, 0, 0, 0, }, //  38
    		{ 0, 0, 0, 0, 0, }, //  39
    		{ 0, 0, 0, 0, 0, }, //  40
    		{ 4, 16, 46, 76, 106, }, //  41
    		{ 4, 17, 47, 77, 107, }, //  42
    		{ 4, 18, 48, 78, 108, }, //  43
    		{ 4, 19, 49, 79, 109, }, //  44
    		{ 4, 20, 50, 80, 110, }, //  45
    		{ 4, 21, 51, 81, 111, }, //  46
    		{ 0, 0, 0, 0, 0, }, //  47
    		{ 0, 0, 0, 0, 0, }, //  48
    		{ 0, 0, 0, 0, 0 }, //  49
    		{ 0, 0, 0, 0, 0 }, //  50
    		{ 5, 1, 31, 61, 91, 170, }, //  51
    		{ 5, 2, 32, 62, 92, 171, }, //  52
    		{ 5, 3, 33, 63, 93, 172, }, //  53
    		{ 5, 4, 34, 64, 94, 173, }, //  54
    		{ 5, 5, 35, 65, 95, 174, }, //  55
    		{ 5, 6, 36, 66, 96, 175, }, //  56
    		{ 0, 0, 0, 0, 0, }, //  57
    		{ 0, 0, 0, 0, 0, }, //  58
    		{ 0, 0, 0, 0, 0, }, //  59
    		{ 0, 0, 0, 0, 0, }, //  60
    		{ 0, 0, 0, 0, 0, }, //  61
    		{ 0, 0, 0, 0, 0, }, //  62
    		{ 0, 0, 0, 0, 0, }, //  63
    		{ 0, 0, 0, 0, 0, }, //  64
    		{ 0, 0, 0, 0, 0, }, //  65
    		{ 4, 16, 46, 76, 106, }, //  66
    		{ 4, 17, 47, 77, 107, }, //  67
    		{ 4, 18, 48, 78, 108, }, //  68
    		{ 4, 19, 49, 79, 109, }, //  69
    		{ 4, 20, 50, 80, 110, }, //  70
    		{ 4, 21, 51, 81, 111, }, //  71
    		{ 0, 0, 0, 0, 0, }, //  72
    		{ 0, 0, 0, 0, 0, }, //  73
    		{ 0, 0, 0, 0, 0, }, //  74
    		{ 0, 0, 0, 0, 0, }, //  75
    		{ 5, 1, 31, 61, 91, 170, }, //  76
    		{ 5, 2, 32, 62, 92, 171, }, //  77
    		{ 5, 3, 33, 63, 93, 172, }, //  78
    		{ 5, 4, 34, 64, 94, 173, }, //  79
    		{ 5, 5, 35, 65, 95, 174, }, //  80
    		{ 5, 6, 36, 66, 96, 175, }, //  81
    		{ 0, 0, 0, 0, 0, }, //  82
    		{ 0, 0, 0, 0, 0, }, //  83
    		{ 0, 0, 0, 0, 0, }, //  84
    		{ 0, 0, 0, 0, 0, }, //  85
    		{ 0, 0, 0, 0, 0, }, //  86
    		{ 0, 0, 0, 0, 0, }, //  87
    		{ 0, 0, 0, 0, 0, }, //  88
    		{ 0, 0, 0, 0, 0, }, //  89
    		{ 0, 0, 0, 0, 0, }, //  90
    		{ 4, 16, 46, 76, 106, }, //  91
    		{ 4, 17, 47, 77, 107, }, //  92
    		{ 4, 18, 48, 78, 108, }, //  93
    		{ 4, 19, 49, 79, 109, }, //  94
    		{ 4, 20, 50, 80, 110, }, //  95
    		{ 4, 21, 51, 81, 111, }, //  96
    		{ 0, 0, 0, 0, 0, }, //  97
    		{ 0, 0, 0, 0, 0, }, //  98
    		{ 0, 0, 0, 0, 0, }, //  99
    		{ 0, 0, 0, 0, 0, }, //  100
    		{ 1, 152, 0, 0, 0, }, //  101
    		{ 1, 153, 0, 0, 0, }, //  102
    		{ 1, 154, 0, 0, 0, }, //  103
    		{ 1, 155, 0, 0, 0, }, //  104
    		{ 1, 156, 0, 0, 0, }, //  105
    		{ 1, 157, 0, 0, 0, }, //  106
    		{ 0, 0, 0, 0, 0, }, //  107
    		{ 0, 0, 0, 0, 0, }, //  108
    		{ 0, 0, 0, 0, 0, }, //  109
    		{ 0, 0, 0, 0, 0, }, //  110
    		{ 0, 0, 0, 0, 0, }, //  111
    		{ 0, 0, 0, 0, 0, }, //  112		
    		{ 0, 0, 0, 0, 0, }, //  113
    		{ 0, 0, 0, 0, 0, }, //  114
    		{ 0, 0, 0, 0, 0, }, //  115
    		{ 0, 0, 0, 0, 0, }, //  116
    		{ 0, 0, 0, 0, 0, }, //  117
    		{ 0, 0, 0, 0, 0, }, //  118
    		{ 0, 0, 0, 0, 0, }, //  119
    		{ 0, 0, 0, 0, 0, }, //  120
    		{ 2, 137, 140, 0, 0, }, //  121
    		{ 1, 138, 0, 0, 0, }, //  122
    		{ 1, 139, 0, 0, 0, }, //  123
    	};
    
    	if (dwMotionIndex >= MOTION_MAX_NUM)
    	{
    		sys_err("OUT_OF_MOTION_VNUM: name=%s, motion=%d/%d", GetName(), dwMotionIndex, MOTION_MAX_NUM);
    		return false;
    	}
    
    	DWORD* skillVNums = s_anMotion2SkillVnumList[dwMotionIndex];
    	DWORD skillCount = *skillVNums++;
    	if (skillCount >= SKILL_LIST_MAX_COUNT)
    	{
    		sys_err("OUT_OF_SKILL_LIST: name=%s, count=%d/%d", GetName(), skillCount, SKILL_LIST_MAX_COUNT);
    		return false;
    	}
    
    	for (DWORD skillIndex = 0; skillIndex != skillCount; ++skillIndex)
    	{
    		if (skillIndex >= SKILL_MAX_NUM)
    		{
    			sys_err("OUT_OF_SKILL_VNUM: name=%s, skill=%d/%d", GetName(), skillIndex, SKILL_MAX_NUM);
    			return false;
    		}
    
    		DWORD eachSkillVNum = skillVNums[skillIndex];
    		if ( eachSkillVNum != 0 )
    		{
    			DWORD eachJobGroup = s_anSkill2JobGroup[eachSkillVNum];
    			if (0 == eachJobGroup || eachJobGroup == selfJobGroup)
    			{
    				DWORD eachSkillLevel = 0;
    				if (eachSkillVNum >= GUILD_SKILL_START && eachSkillVNum <= GUILD_SKILL_END)
    				{
    					if (GetGuild())
    						eachSkillLevel = GetGuild()->GetSkillLevel(eachSkillVNum);
    					else
    						eachSkillLevel = 0;
    				}
    				else
    				{
    					eachSkillLevel = GetSkillLevel(eachSkillVNum);
    				}
    
    				if (eachSkillLevel > 0)
    				{
    					return true;
    				}
    			}
    		}
    	}
    
    	return false;
    }
    
    void CHARACTER::ClearSkill()
    {
    	PointChange(POINT_SKILL, 4 + (GetLevel() - 5) - GetPoint(POINT_SKILL));
    
    	RemoveAffect(SKILL_JEONGWI);
    	RemoveAffect(SKILL_GEOMKYUNG);
    	RemoveAffect(SKILL_CHUNKEON);
    	RemoveAffect(SKILL_EUNHYUNG);
    	RemoveAffect(SKILL_GYEONGGONG);
    	RemoveAffect(SKILL_GWIGEOM);
    	RemoveAffect(SKILL_TERROR);
    	RemoveAffect(SKILL_JUMAGAP);
    	RemoveAffect(SKILL_MANASHILED);
    	RemoveAffect(SKILL_HOSIN);
    	RemoveAffect(SKILL_REFLECT);
    	RemoveAffect(SKILL_KWAESOK);
    	RemoveAffect(SKILL_JEUNGRYEOK);
    	RemoveAffect(SKILL_GICHEON);
    	
    	ResetSkill();
    }
    
    void CHARACTER::ClearSubSkill()
    {
    	PointChange(POINT_SUB_SKILL, GetLevel() < 10 ? 0 : (GetLevel() - 9) - GetPoint(POINT_SUB_SKILL));
    
    	if (m_pSkillLevels == NULL)
    	{
    		sys_err("m_pSkillLevels nil (name: %s)", GetName());
    		return;
    	}
    
    	TPlayerSkill CleanSkill;
    	memset(&CleanSkill, 0, sizeof(TPlayerSkill));
    
    	size_t count = sizeof(s_adwSubSkillVnums) / sizeof(s_adwSubSkillVnums[0]);
    
    	for (size_t i = 0; i < count; ++i)
    	{
    		if (s_adwSubSkillVnums[i] >= SKILL_MAX_NUM)
    			continue;
    
    		m_pSkillLevels[s_adwSubSkillVnums[i]] = CleanSkill;
    	}
    
    	ComputePoints();
    	SkillLevelPacket();
    }
    
    bool CHARACTER::ResetOneSkill(DWORD dwVnum)
    {
    	if (NULL == m_pSkillLevels)
    	{
    		sys_err("m_pSkillLevels nil (name %s, vnum %u)", GetName(), dwVnum);
    		return false;
    	}
    
    	if (dwVnum >= SKILL_MAX_NUM)
    	{
    		sys_err("vnum overflow (name %s, vnum %u)", GetName(), dwVnum);
    		return false;
    	}
    
    	BYTE level = m_pSkillLevels[dwVnum].bLevel;
    
    	m_pSkillLevels[dwVnum].bLevel = 0;
    	m_pSkillLevels[dwVnum].bMasterType = 0;
    	m_pSkillLevels[dwVnum].tNextRead = 0;
    
    	if (level > 17)
    		level = 17;
    
    	PointChange(POINT_SKILL, level);
    
    	LogManager::instance().CharLog(this, dwVnum, "ONE_SKILL_RESET_BY_SCROLL", "");
    
    	ComputePoints();
    	SkillLevelPacket();
    
    	return true;
    }
    
    bool CHARACTER::CanUseSkill(DWORD dwSkillVnum) const
    {
    	if (0 == dwSkillVnum) return false;
    
    	if (0 < GetSkillGroup())
    	{
    		const int SKILL_COUNT = 6;
    		static const DWORD SkillList[JOB_MAX_NUM][SKILL_GROUP_MAX_NUM][SKILL_COUNT] =
    		{
    			{{1, 2,	3, 4, 5, 6}, {16, 17, 18, 19, 20, 21}},
    			{{31, 32, 33, 34, 35, 36}, {46, 47, 48, 49, 50, 51}},
    			{{61, 62, 63, 64, 65, 66}, {76, 77, 78, 79, 80, 81}},
    			{{91, 92, 93, 94, 95, 96}, {106, 107, 108, 109, 110, 111}},
    			{{170, 171, 172, 173, 174, 175},},
    		};
    
    		const DWORD* pSkill = SkillList[ GetJob() ][ GetSkillGroup()-1 ];
    		for (int i=0 ; i < SKILL_COUNT ; ++i)
    		{
    			if (pSkill[i] == dwSkillVnum) return true;
    		}
    	}
    
    	if (true == IsRiding())
    	{
    		//binekfix
    		if(GetMountVnum())
    		{
    			if( !((GetMountVnum() >= 20205 && GetMountVnum() <= 20246)	||
    				GetMountVnum() == 20247 || GetMountVnum() == 20248 || GetMountVnum() == 20249	)	)
    					return false;
    		}
    		//if(GetMountVnum())
    		//{
    			// if( !((GetMountVnum() >= 20209 && GetMountVnum() <= 20212)	||
    				// GetMountVnum() == 20215 || GetMountVnum() == 20218 || GetMountVnum() == 20225	)	)
    					// return false;
    		// }
    
    		switch(dwSkillVnum)
    		{
    			case SKILL_HORSE_WILDATTACK:
    			case SKILL_HORSE_CHARGE:
    			case SKILL_HORSE_ESCAPE:
    			case SKILL_HORSE_WILDATTACK_RANGE:
    				return true;
    		}
    	}
    
    	switch( dwSkillVnum )
    	{
    		case 121: case 122: case 124: case 126: case 127: case 128: case 129: case 130:
    		case 131:
    		case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159:
    			return true;
    	}
    
    	return false;
    }
    
    bool CHARACTER::CheckSkillHitCount(const BYTE SkillID, const VID TargetVID)
    {
    	std::map<int, TSkillUseInfo>::iterator iter = m_SkillUseInfo.find(SkillID);
    
    	if (iter == m_SkillUseInfo.end())
    	{
    		sys_log(0, "SkillHack: Skill(%u) is not in container", SkillID);
    		return false;
    	}
    
    	TSkillUseInfo& rSkillUseInfo = iter->second;
    
    	if (false == rSkillUseInfo.bUsed)
    	{
    		sys_log(0, "SkillHack: not used skill(%u)", SkillID);
    		return false;
    	}
    
    	switch (SkillID)
    	{
    		case SKILL_YONGKWON:
    		case SKILL_HWAYEOMPOK:
    		case SKILL_DAEJINGAK:
    		case SKILL_PAERYONG:
    			sys_log(0, "SkillHack: cannot use attack packet for skill(%u)", SkillID);
    			return false;
    	}
    
    	boost::unordered_map<VID, size_t>::iterator iterTargetMap = rSkillUseInfo.TargetVIDMap.find(TargetVID);
    
    	if (rSkillUseInfo.TargetVIDMap.end() != iterTargetMap)
    	{
    		size_t MaxAttackCountPerTarget = 1;
    
    		switch (SkillID)
    		{
    			case SKILL_SAMYEON:
    			case SKILL_CHARYUN:
    			case SKILL_CHAYEOL:
    			/* case SKILL_PASWAE: */
    				MaxAttackCountPerTarget = 3;
    				break;
    
    			case SKILL_HORSE_WILDATTACK_RANGE:
    				MaxAttackCountPerTarget = 5;
    				break;
    
    			case SKILL_YEONSA:
    				MaxAttackCountPerTarget = 7;
    				break;
    
    			case SKILL_HORSE_ESCAPE:
    				MaxAttackCountPerTarget = 10;
    				break;
    		}
    
    		if (iterTargetMap->second >= MaxAttackCountPerTarget)
    		{
    			sys_log(0, "SkillHack: Too Many Hit count from SkillID(%u) count(%u)", SkillID, iterTargetMap->second);
    			return false;
    		}
    
    		iterTargetMap->second++;
    	}
    	else
    	{
    		rSkillUseInfo.TargetVIDMap.insert( std::make_pair(TargetVID, 1) );
    	}
    
    	return true;
    }

     

    On 8/24/2018 at 10:33 AM, WeedHex said:

    Can you show your char_skill.cpp?? Or send me in PM.

    iI sent it to you

×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.