Move git root from Client/ to src/ to track all source code: - Client: Game client source (moved to Client/Client/) - Server: Game server source - GameTools: Development tools - CryptoSource: Encryption utilities - database: Database scripts - Script: Game scripts - rylCoder_16.02.2008_src: Legacy coder tools - GMFont, Game: Additional resources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
487 lines
16 KiB
C++
487 lines
16 KiB
C++
#include "stdafx.h"
|
|
|
|
#include <Log/ServerLog.h>
|
|
#include <Utility/Math/Math.h>
|
|
#include <Utility/Compress/MiniLZO/MiniLZOWrapper.h>
|
|
#include <Utility/Resource/EnsureCleanup.h>
|
|
#include <Network/XORCrypt/XORCrypt.h>
|
|
|
|
#include <algorithm>
|
|
#include "SkillMgr.h"
|
|
#include "../Utility/DelimitedFile.h"
|
|
|
|
#define DECODE_HEADER(Start_In, Length_In, PageVer_In, PageNum_In) CXORCrypt::GetInstance().DecodeHeader((Start_In),(Length_In),(PageVer_In),(PageNum_In))
|
|
#define ENCODE_HEADER(Start_In, Length_In, PageVer_In, PageNum_In) CXORCrypt::GetInstance().EncodeHeader((Start_In),(Length_In),(PageVer_In),(PageNum_In))
|
|
#define COMPRESS(In, In_len, Out, Out_len) CMiniLZOCompress::Compress((In), (In_len), (Out), (Out_len))
|
|
#define DECOMPRESS(In, In_len, Out, Out_len) CMiniLZOCompress::Decompress((In), (In_len), (Out), (Out_len))
|
|
|
|
|
|
CSkillMgr CSkillMgr::ms_this;
|
|
|
|
|
|
#ifdef _TEST_SERVER_
|
|
const char* CSkillMgr::ms_szSkillScriptFileName = "TestSkillScript.txt";
|
|
#else
|
|
const char* CSkillMgr::ms_szSkillScriptFileName = "SkillScript.txt";
|
|
#endif
|
|
|
|
|
|
|
|
CSkillMgr::CSkillMgr()
|
|
: m_ProtoTypeArray(NULL)
|
|
{
|
|
|
|
}
|
|
|
|
CSkillMgr::~CSkillMgr()
|
|
{
|
|
ClearProtoType();
|
|
}
|
|
|
|
void CSkillMgr::ClearProtoType()
|
|
{
|
|
if(NULL != m_ProtoTypeArray)
|
|
{
|
|
delete [] m_ProtoTypeArray;
|
|
m_ProtoTypeArray = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool CSkillMgr::LoadSkillsFromFile(const char* szFileName)
|
|
{
|
|
using namespace Skill;
|
|
|
|
// 임시변수들
|
|
const int SKILL_ID_LEN = 32;
|
|
char szBuffer[SKILL_ID_LEN];
|
|
unsigned char chTempValue;
|
|
|
|
int nLineCount = 0;
|
|
int nSkillCount = 0;
|
|
CDelimitedFile DelimitedFile; // 객체 소멸시, 자동 Close.
|
|
|
|
ProtoTypeArray tempProtoType;
|
|
|
|
// 매크로에 로그 코드 삽입을 잊지 말 것.
|
|
// 매크로에서 \뒤에 공백이나 문자 삽입되지 않도록 주의할 것.
|
|
// ( '이스케이프 시퀀스가 잘못되었습니다' 에러 발생 )
|
|
#define READ_DATA(ColumnName, Argument) \
|
|
if(!DelimitedFile.ReadData(ColumnName, Argument)) \
|
|
{ \
|
|
ERRLOG2(g_Log, "스킬 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
|
return false; \
|
|
}
|
|
|
|
#define READ_STRING(ColumnName, Buffer, BufferSize) \
|
|
if(!DelimitedFile.ReadString(ColumnName, Buffer, BufferSize)) \
|
|
{\
|
|
ERRLOG2(g_Log, "스킬 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName);\
|
|
return false;\
|
|
}
|
|
|
|
#define READ_STRING_TO_TYPE_VALUE(saveType, saveValue, szColumn, TypeArray, nMaxType)\
|
|
saveValue = static_cast<saveType>(ReadStringToTypeValue(DelimitedFile, szColumn, TypeArray, nMaxType));\
|
|
if(nMaxType == saveValue)\
|
|
{\
|
|
ERRLOG2(g_Log, "스킬 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #szColumn); \
|
|
return false;\
|
|
}
|
|
|
|
if (!DelimitedFile.Open(szFileName ? szFileName : ms_szSkillScriptFileName, 1 /* HeadLine */))
|
|
{
|
|
ERRLOG1(g_Log, "%s 파일을 열 수 없습니다.", szFileName ? szFileName : ms_szSkillScriptFileName);
|
|
return false;
|
|
}
|
|
|
|
std::vector<ProtoTypeArray> ProtoTypeVector;
|
|
|
|
|
|
while(DelimitedFile.ReadLine())
|
|
{
|
|
++nLineCount;
|
|
|
|
READ_STRING("ID", szBuffer, SKILL_ID_LEN);
|
|
tempProtoType.m_ProtoTypes[nSkillCount].m_usSkill_ID = Math::Convert::StrToHex16(szBuffer);
|
|
|
|
READ_STRING_TO_TYPE_VALUE(Skill::Type::Type, tempProtoType.m_ProtoTypes[nSkillCount].m_eSkillType,
|
|
"Type", Type::SkillTypes, Type::MAX_SKILL_TYPE);
|
|
|
|
READ_DATA("ClassSkill", chTempValue); tempProtoType.m_ProtoTypes[nSkillCount].m_bIsClassSkill = chTempValue != 0;
|
|
READ_STRING("Name[5]", tempProtoType.m_ProtoTypes[nSkillCount].m_SpriteInfo.m_szName, ProtoType::MAX_SKILL_NAME);
|
|
READ_STRING_TO_TYPE_VALUE(Skill::UseLimit::Type, tempProtoType.m_ProtoTypes[nSkillCount].m_eUseLimit,
|
|
"Limit", UseLimit::UseLimits, UseLimit::MAX_USE_LIMIT);
|
|
|
|
READ_STRING_TO_TYPE_VALUE(unsigned char, tempProtoType.m_ProtoTypes[nSkillCount].m_StatusLimitType[0],
|
|
"Stat[0]", StatusLimit::StatusLimits, StatusLimit::MAX_STATUS_LIMIT);
|
|
READ_DATA("Value[0]", tempProtoType.m_ProtoTypes[nSkillCount].m_StatusLimitValue[0]);
|
|
|
|
READ_STRING_TO_TYPE_VALUE(unsigned char, tempProtoType.m_ProtoTypes[nSkillCount].m_StatusLimitType[1],
|
|
"Stat[1]", StatusLimit::StatusLimits, StatusLimit::MAX_STATUS_LIMIT);
|
|
READ_DATA("Value[1]", tempProtoType.m_ProtoTypes[nSkillCount].m_StatusLimitValue[1]);
|
|
|
|
READ_STRING_TO_TYPE_VALUE(Skill::Target::Type, tempProtoType.m_ProtoTypes[nSkillCount].m_eTargetType,
|
|
"Target", Target::TargetTypes, Target::MAX_TARGET_TYPE);
|
|
|
|
READ_DATA("Range", tempProtoType.m_ProtoTypes[nSkillCount].m_EffectDistance);
|
|
READ_DATA("Region", tempProtoType.m_ProtoTypes[nSkillCount].m_EffectExtent);
|
|
READ_DATA("StartMP", tempProtoType.m_ProtoTypes[nSkillCount].m_StartMP);
|
|
READ_DATA("LevelMP", tempProtoType.m_ProtoTypes[nSkillCount].m_LevelMP);
|
|
READ_DATA("Starttick", tempProtoType.m_ProtoTypes[nSkillCount].m_StartTick);
|
|
READ_DATA("LevelTick", tempProtoType.m_ProtoTypes[nSkillCount].m_LevelTick);
|
|
READ_STRING("CastingFlag", tempProtoType.m_ProtoTypes[nSkillCount].m_szCastingFileName, ProtoType::MAX_FILE_NAME);
|
|
READ_STRING("EffectFlag", tempProtoType.m_ProtoTypes[nSkillCount].m_szEffectFileName, ProtoType::MAX_FILE_NAME);
|
|
READ_STRING("HitFlag", tempProtoType.m_ProtoTypes[nSkillCount].m_szHitFileName, ProtoType::MAX_FILE_NAME);
|
|
READ_DATA("CoolDownTime", tempProtoType.m_ProtoTypes[nSkillCount].m_usCoolDownTime);
|
|
READ_DATA("EndCoolDown", tempProtoType.m_ProtoTypes[nSkillCount].m_cEndCoolDown);
|
|
READ_STRING("dds", tempProtoType.m_ProtoTypes[nSkillCount].m_SpriteInfo.m_szSpriteName, ProtoType::MAX_FILE_NAME);
|
|
READ_DATA("MinX", tempProtoType.m_ProtoTypes[nSkillCount].m_SpriteInfo.m_nSpriteMinX);
|
|
READ_DATA("MinY", tempProtoType.m_ProtoTypes[nSkillCount].m_SpriteInfo.m_nSpriteMinY);
|
|
READ_DATA("MaxX", tempProtoType.m_ProtoTypes[nSkillCount].m_SpriteInfo.m_nSpriteMaxX);
|
|
READ_DATA("MaxY", tempProtoType.m_ProtoTypes[nSkillCount].m_SpriteInfo.m_nSpriteMaxY);
|
|
READ_DATA("Hit", tempProtoType.m_ProtoTypes[nSkillCount].m_cStrikeNum);
|
|
READ_DATA("EndScript", tempProtoType.m_ProtoTypes[nSkillCount].m_cEndScript);
|
|
READ_DATA("Protection", chTempValue); tempProtoType.m_ProtoTypes[nSkillCount].m_bProtection = chTempValue != 0;
|
|
READ_DATA("Interrupt", chTempValue); tempProtoType.m_ProtoTypes[nSkillCount].m_bInterrupt = chTempValue != 0;
|
|
|
|
READ_DATA("Gauge", chTempValue); tempProtoType.m_ProtoTypes[nSkillCount].m_bGauge = chTempValue != 0;
|
|
READ_DATA("MinRange", tempProtoType.m_ProtoTypes[nSkillCount].m_cMinRange);
|
|
READ_DATA("MaxRange", tempProtoType.m_ProtoTypes[nSkillCount].m_cMaxRange);
|
|
|
|
if(tempProtoType.m_ProtoTypes[0].m_usSkill_ID != tempProtoType.m_ProtoTypes[nSkillCount].m_usSkill_ID)
|
|
{
|
|
ERRLOG2(g_Log, "스킬 숫자가 맞지 않습니다. 클래스 스킬을 제외하고는, 각 스킬은 반드시 같은 ID가"
|
|
" 5개가 연속해서 나와야 합니다. 현재 스킬 번호는 %d, 개수는 %d입니다.",
|
|
tempProtoType.m_ProtoTypes[nSkillCount].m_usSkill_ID, nSkillCount);
|
|
return false;
|
|
}
|
|
|
|
tempProtoType.m_ProtoTypes[nSkillCount].m_cSpell_LockCount = nSkillCount;
|
|
++nSkillCount;
|
|
|
|
if (MAX_SKILL_LOCKCOUNT == nSkillCount || tempProtoType.m_ProtoTypes[0].m_bIsClassSkill)
|
|
{
|
|
ProtoTypeVector.push_back(tempProtoType);
|
|
|
|
for(int nIndex = 0; nIndex < nSkillCount; ++nIndex)
|
|
{
|
|
tempProtoType.m_ProtoTypes[nIndex].Initialize();
|
|
}
|
|
|
|
nSkillCount = 0;
|
|
}
|
|
}
|
|
|
|
std::sort(ProtoTypeVector.begin(), ProtoTypeVector.end());
|
|
|
|
for(std::vector<ProtoTypeArray>::iterator itr = ProtoTypeVector.begin();
|
|
itr != ProtoTypeVector.end() - 1; ++itr)
|
|
{
|
|
if(itr->m_ProtoTypes[0].m_usSkill_ID == (itr+1)->m_ProtoTypes[0].m_usSkill_ID)
|
|
{
|
|
ERRLOG1(g_Log, "겹치는 스킬 종류 ID가 있습니다. 종류ID:%d", itr->m_ProtoTypes[0].m_usSkill_ID);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
m_nSkillNum = ProtoTypeVector.size();
|
|
m_ProtoTypeArray = new ProtoTypeArray[m_nSkillNum];
|
|
if(NULL == m_ProtoTypeArray)
|
|
{
|
|
ERRLOG0(g_Log, "메모리 할당에 실패했습니다.");
|
|
return false;
|
|
}
|
|
|
|
std::copy(ProtoTypeVector.begin(), ProtoTypeVector.end(), m_ProtoTypeArray);
|
|
return true;
|
|
}
|
|
|
|
|
|
unsigned char CSkillMgr::ReadStringToTypeValue(CDelimitedFile& DelimitedFile, const char* szColumn,
|
|
const CTypeName* TypeArray, const unsigned char nMaxType)
|
|
{
|
|
char szReadData[MAX_PATH];
|
|
unsigned char nCount = 0;
|
|
|
|
// 등급 구분 읽기.
|
|
if (!DelimitedFile.ReadString(szColumn, szReadData, MAX_PATH))
|
|
{
|
|
ERRLOG0(g_Log, "등급 구분 읽기에 실패하였습니다.");
|
|
return nMaxType; // 실패시 nMaxType을 리턴.
|
|
}
|
|
|
|
for (nCount = 0; nCount < nMaxType; ++nCount)
|
|
{
|
|
if (0 == strcmp(szReadData, TypeArray[nCount].GetTypeName()))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nCount == nMaxType)
|
|
{
|
|
ERRLOG0(g_Log, "읽어들인 정보가 타입 배열에 존재하지 않습니다.");
|
|
return nMaxType;
|
|
}
|
|
|
|
return TypeArray[nCount].GetTypeValue();
|
|
}
|
|
|
|
|
|
|
|
const Skill::ProtoType* CSkillMgr::GetSkillProtoType(unsigned short usSkill_ID)
|
|
{
|
|
ProtoTypeArray* lpFirst = m_ProtoTypeArray;
|
|
ProtoTypeArray* lpLast = m_ProtoTypeArray + m_nSkillNum;
|
|
ProtoTypeArray* lpMid = NULL;
|
|
|
|
size_t nCount = m_nSkillNum;
|
|
size_t nCount2 = 0;
|
|
|
|
for (; 0 < nCount; )
|
|
{
|
|
nCount2 = nCount / 2;
|
|
lpMid = lpFirst + nCount2;
|
|
|
|
if(lpMid->m_ProtoTypes[0].m_usSkill_ID < usSkill_ID)
|
|
{
|
|
lpFirst = ++lpMid, nCount -= nCount2 + 1;
|
|
}
|
|
else
|
|
{
|
|
nCount = nCount2;
|
|
}
|
|
}
|
|
|
|
return (lpFirst != lpLast && !(usSkill_ID < lpFirst->m_ProtoTypes[0].m_usSkill_ID)) ?
|
|
lpFirst->m_ProtoTypes : NULL;
|
|
}
|
|
|
|
|
|
|
|
bool CSkillMgr::LoadSkillsFromBinary(const char* szFileNameBinary)
|
|
{
|
|
HANDLE hFile = CreateFile((0 == szFileNameBinary) ? ms_szSkillScriptFileName : szFileNameBinary,
|
|
GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE) { return false; }
|
|
|
|
CEnsureCloseHandle hfile(hFile);
|
|
|
|
DWORD dwRead = 0;
|
|
DWORD dwFileHighSize = 0;
|
|
DWORD dwFileSize = GetFileSize(hFile, &dwFileHighSize);
|
|
|
|
char* lpAllocated = new char[dwFileSize];
|
|
CEnsureDeleteArray<char> allocated(lpAllocated);
|
|
|
|
if(NULL == lpAllocated)
|
|
{
|
|
ERRLOG0(g_Log, "메모리가 부족합니다.");
|
|
return false;
|
|
}
|
|
|
|
if(!ReadFile(hFile, lpAllocated, dwFileSize, &dwRead, NULL))
|
|
{
|
|
ERRLOG1(g_Log, "읽기 실패 : %d에러", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
DWORD dwHeaderSize = sizeof(DWORD) + *reinterpret_cast<DWORD*>(lpAllocated) + sizeof(DWORD);
|
|
DWORD dwDecompressedSize = *reinterpret_cast<DWORD*>(lpAllocated + dwHeaderSize - sizeof(DWORD));
|
|
|
|
ClearProtoType();
|
|
|
|
m_nSkillNum = dwDecompressedSize/sizeof(ProtoTypeArray);
|
|
m_ProtoTypeArray = new ProtoTypeArray[m_nSkillNum];
|
|
if(NULL == m_ProtoTypeArray)
|
|
{
|
|
ERRLOG0(g_Log, "스킬 스크립트 내용을 불러올 수 없습니다. 메모리가 부족합니다.");
|
|
return false;
|
|
}
|
|
|
|
DECOMPRESS(lpAllocated + dwHeaderSize, dwFileSize - dwHeaderSize,
|
|
reinterpret_cast<char*>(m_ProtoTypeArray), &dwDecompressedSize);
|
|
|
|
DECODE_HEADER(reinterpret_cast<char*>(m_ProtoTypeArray), dwDecompressedSize, 0, 1);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CSkillMgr::SaveSkillsToBinary(const char* szFileNameBinary, const char* szTrashFile)
|
|
{
|
|
HANDLE hFile = CreateFile((0 == szFileNameBinary) ? ms_szSkillScriptFileName : szFileNameBinary,
|
|
GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE) { return false; }
|
|
|
|
CEnsureCloseHandle file(hFile);
|
|
|
|
DWORD dwSkillSize = static_cast<DWORD>(sizeof(ProtoTypeArray) * m_nSkillNum);
|
|
DWORD dwCompressSize = dwSkillSize;
|
|
|
|
char* lpSkill_Info = new char[dwSkillSize];
|
|
char* lpCompressedInfo = new char[dwSkillSize];
|
|
|
|
if(NULL == lpSkill_Info || NULL == lpCompressedInfo)
|
|
{
|
|
ERRLOG0(g_Log, "메모리가 부족합니다.");
|
|
return false;
|
|
}
|
|
|
|
CEnsureDeleteArray<char> skill_info(lpSkill_Info);
|
|
CEnsureDeleteArray<char> compress_info(lpCompressedInfo);
|
|
|
|
memcpy(lpSkill_Info, m_ProtoTypeArray, dwSkillSize);
|
|
ENCODE_HEADER(lpSkill_Info, dwSkillSize, 0, 1);
|
|
COMPRESS(lpSkill_Info, dwSkillSize, lpCompressedInfo, &dwCompressSize);
|
|
|
|
DWORD dwWritten = 0;
|
|
|
|
// 쓰레기(더미) 자료
|
|
HANDLE hTrashFile = CreateFile(szTrashFile, GENERIC_READ, FILE_SHARE_READ, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (hTrashFile == INVALID_HANDLE_VALUE) { return false; }
|
|
|
|
CEnsureCloseHandle trashFile(hTrashFile);
|
|
|
|
DWORD dwRead = 0;
|
|
DWORD dwFileHighSize = 0;
|
|
DWORD dwFileSize = GetFileSize(hTrashFile, &dwFileHighSize);
|
|
|
|
char* lpAllocated = new char[dwFileSize];
|
|
CEnsureDeleteArray<char> allocated(lpAllocated);
|
|
|
|
if (!ReadFile(hTrashFile, lpAllocated, dwFileSize, &dwRead, NULL))
|
|
{
|
|
ERRLOG0(g_Log, "쓰레기 파일을 읽을 수 없습니다.");
|
|
return false;
|
|
}
|
|
|
|
WriteFile(hFile, &dwFileSize, sizeof(DWORD), &dwWritten, 0);
|
|
WriteFile(hFile, lpAllocated, dwFileSize, &dwWritten, 0);
|
|
|
|
// 올바른 자료
|
|
WriteFile(hFile, &dwSkillSize, sizeof(DWORD), &dwWritten, 0);
|
|
WriteFile(hFile, lpCompressedInfo, dwCompressSize, &dwWritten, 0);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
#ifndef _RYL_GAME_CLIENT_
|
|
|
|
void CSkillMgr::ProcessSkillException(SKILL* pSkill, unsigned short cClass)
|
|
{
|
|
// 대거 마스터리가 로그용에서 어쌔신용으로 바뀌면서 기존의 것들을 지우는 루틴
|
|
bool IsDaggerMastery = false;
|
|
for (int nSkillIndex = 0; nSkillIndex < pSkill->wSlotNum; nSkillIndex++)
|
|
{
|
|
if (pSkill->SSlot[nSkillIndex].SKILLINFO.wSkill == 0x8202)
|
|
{
|
|
pSkill->wSkillNum -= (pSkill->SSlot[nSkillIndex].SKILLINFO.cLockCount * (SKILLSLOT::MAX_SKILL_LEVEL - 1) +
|
|
pSkill->SSlot[nSkillIndex].SKILLINFO.cSkillLevel);
|
|
IsDaggerMastery = true;
|
|
continue;
|
|
}
|
|
|
|
if (true == IsDaggerMastery) {
|
|
pSkill->SSlot[nSkillIndex-1] = pSkill->SSlot[nSkillIndex];
|
|
}
|
|
}
|
|
|
|
if (true == IsDaggerMastery)
|
|
{
|
|
pSkill->SSlot[nSkillIndex-1] = SKILLSLOT();
|
|
pSkill->wSlotNum--;
|
|
}
|
|
|
|
// 넷이 컴배턴트용에서 템플러와 어태커용으로 바뀌면서 기존의 것들을 지우는 루틴
|
|
bool IsNet = false;
|
|
for (int nSkillIndex = 0; nSkillIndex < pSkill->wSlotNum; nSkillIndex++)
|
|
{
|
|
if (pSkill->SSlot[nSkillIndex].SKILLINFO.wSkill == 0x9101)
|
|
{
|
|
IsNet = true;
|
|
continue;
|
|
}
|
|
|
|
if (true == IsNet) {
|
|
pSkill->SSlot[nSkillIndex-1] = pSkill->SSlot[nSkillIndex];
|
|
}
|
|
}
|
|
|
|
if (true == IsNet)
|
|
{
|
|
pSkill->SSlot[nSkillIndex-1] = SKILLSLOT();
|
|
pSkill->wSlotNum--;
|
|
}
|
|
|
|
// 클래스 스킬 넣어주기
|
|
unsigned short usClassSkillID = NULL;
|
|
bool IsClassSkill = false;
|
|
|
|
usClassSkillID = CClass::GetClassSkillID(static_cast<unsigned char>(cClass));
|
|
if (NULL != usClassSkillID)
|
|
{
|
|
for (int nSkillIndex = 0; nSkillIndex < pSkill->wSlotNum; nSkillIndex++)
|
|
{
|
|
if (pSkill->SSlot[nSkillIndex].SKILLINFO.wSkill == usClassSkillID)
|
|
{
|
|
IsClassSkill = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (false == IsClassSkill)
|
|
{
|
|
pSkill->SSlot[pSkill->wSlotNum] = SKILLSLOT(usClassSkillID, 0, 1);
|
|
pSkill->wSlotNum++;
|
|
}
|
|
}
|
|
|
|
usClassSkillID = NULL;
|
|
IsClassSkill = false;
|
|
|
|
if (CClass::JOB_CHANGE_1ST == CClass::GetJobLevel(static_cast<unsigned char>(cClass)))
|
|
{
|
|
usClassSkillID = CClass::GetClassSkillID(CClass::GetPreviousJob(static_cast<unsigned char>(cClass)));
|
|
if (NULL != usClassSkillID)
|
|
{
|
|
for (int nSkillIndex = 0; nSkillIndex < pSkill->wSlotNum; nSkillIndex++)
|
|
{
|
|
if (pSkill->SSlot[nSkillIndex].SKILLINFO.wSkill == usClassSkillID)
|
|
{
|
|
IsClassSkill = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (false == IsClassSkill)
|
|
{
|
|
pSkill->SSlot[pSkill->wSlotNum] = SKILLSLOT(usClassSkillID, 0, 1);
|
|
pSkill->wSlotNum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 스킬 갯수 재조정
|
|
pSkill->wSkillNum = 0;
|
|
for (int nSkillIndex = 0; nSkillIndex < pSkill->wSlotNum; nSkillIndex++)
|
|
{
|
|
Skill::LPProtoType lpProtoType = GetSkillProtoType(pSkill->SSlot[nSkillIndex].SKILLINFO.wSkill);
|
|
if (NULL != lpProtoType)
|
|
{
|
|
if (false == lpProtoType->m_bIsClassSkill)
|
|
{
|
|
pSkill->wSkillNum += (pSkill->SSlot[nSkillIndex].SKILLINFO.cLockCount *
|
|
(SKILLSLOT::MAX_SKILL_LEVEL - 1) + pSkill->SSlot[nSkillIndex].SKILLINFO.cSkillLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
*/ |