Restructure repository to include all source folders
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>
This commit is contained in:
@@ -0,0 +1,726 @@
|
||||
#include "stdafx.h"
|
||||
#include "CharRespawnMgr.h"
|
||||
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <Utility/DelimitedFile.h>
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
#include <Community/Guild/Guild.h>
|
||||
#include <Community/Guild/GuildMgr.h>
|
||||
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/Character/Character.h>
|
||||
#include <Creature/Siege/SiegeObjectMgr.h>
|
||||
#include <Creature/Siege/SiegeObject.h>
|
||||
#include <Creature/Siege/Camp.h>
|
||||
|
||||
#include <Castle/Castle.h>
|
||||
#include <Castle/CastleMgr.h>
|
||||
|
||||
#include <GameTime/GameTimeMgr.h>
|
||||
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharAttack.h>
|
||||
#include <Network/Packet/PacketStruct/CharAttackPacket.h>
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
#include <Network/Packet/WrapPacket.h>
|
||||
|
||||
const char* CCharRespawnMgr::ms_szRespawnScriptFileName = "./Script/Game/RespawnScript.txt";
|
||||
|
||||
CCharRespawnMgr& CCharRespawnMgr::GetInstance()
|
||||
{
|
||||
static CCharRespawnMgr ms_this;
|
||||
return ms_this;
|
||||
}
|
||||
|
||||
CCharRespawnMgr::CCharRespawnMgr()
|
||||
{
|
||||
}
|
||||
|
||||
CCharRespawnMgr::~CCharRespawnMgr()
|
||||
{
|
||||
m_ProtoTypeList.clear();
|
||||
m_AllZoneTownList.clear();
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::LoadRespawnFromFile(const char* szFileName)
|
||||
{
|
||||
int nLineCount = 0;
|
||||
char strTemp[MAX_PATH];
|
||||
|
||||
CDelimitedFile DelimitedFile; // 객체 소멸시, 자동 Close.
|
||||
RespawnProtoType tempProtoType;
|
||||
Position tempRespawnPos;
|
||||
|
||||
m_ProtoTypeList.clear();
|
||||
m_AllZoneTownList.clear();
|
||||
|
||||
// 매크로에 로그 코드 삽입을 잊지 말 것.
|
||||
// 매크로에서 \뒤에 공백이나 문자 삽입되지 않도록 주의할 것.
|
||||
// ( '이스케이프 시퀀스가 잘못되었습니다' 에러 발생 )
|
||||
#define READ_DATA(ColumnName, Argument) \
|
||||
if (!DelimitedFile.ReadData(Argument)) { \
|
||||
ERRLOG2(g_Log, "리스폰 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define READ_STRING(ColumnName, Buffer, BufferSize) \
|
||||
if (!DelimitedFile.ReadString(Buffer, BufferSize)) { \
|
||||
ERRLOG2(g_Log, "리스폰 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
if (!DelimitedFile.Open(szFileName ? szFileName : ms_szRespawnScriptFileName))
|
||||
{
|
||||
ERRLOG1(g_Log, "%s 파일을 열 수 없습니다.", szFileName ? szFileName : ms_szRespawnScriptFileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (DelimitedFile.ReadLine())
|
||||
{
|
||||
++nLineCount;
|
||||
|
||||
// 순서가 바뀌면 곤란하다니깐~!!! (버럭!)
|
||||
tempProtoType.m_dwGID = 0;
|
||||
READ_STRING("TownID", strTemp, MAX_PATH);
|
||||
tempProtoType.m_dwTownID = Math::Convert::Atoi(strTemp);
|
||||
|
||||
READ_DATA("Nation", tempProtoType.m_cNation);
|
||||
READ_DATA("Zone", tempProtoType.m_cZone);
|
||||
|
||||
int iRespawnPosCount = 0;
|
||||
READ_DATA("RespawnPosCount", iRespawnPosCount);
|
||||
|
||||
if (CServerSetup::GetInstance().GetServerZone() == tempProtoType.m_cZone)
|
||||
{
|
||||
for (int i=0; i<iRespawnPosCount; ++i)
|
||||
{
|
||||
READ_DATA("RespawnPosX", tempRespawnPos.m_fPointX);
|
||||
READ_DATA("RespawnPosY", tempRespawnPos.m_fPointY);
|
||||
READ_DATA("RespawnPosZ", tempRespawnPos.m_fPointZ);
|
||||
|
||||
tempProtoType.m_RespawnPosList.push_back(tempRespawnPos);
|
||||
}
|
||||
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(tempProtoType.m_dwTownID);
|
||||
if (itr == m_ProtoTypeList.end())
|
||||
{
|
||||
m_ProtoTypeList.insert(std::make_pair(tempProtoType.m_dwTownID, tempProtoType));
|
||||
|
||||
// 마을일 경우 모든 존의 마을 리스폰 정보 맵에 집어 넣는다.
|
||||
if (0 != (tempProtoType.m_dwTownID & Castle::TOWN_BIT))
|
||||
{
|
||||
m_AllZoneTownList.insert(std::make_pair(tempProtoType.m_dwTownID, tempProtoType));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG1(g_Log, "리스폰 스크립트에 마을 혹은 성의 ID가 같은 것이 두개이상 존재합니다. TownID : %d", tempProtoType.m_dwTownID);
|
||||
return false;
|
||||
}
|
||||
|
||||
RespawnCharMap::iterator CharItr = m_CharList.find(tempProtoType.m_dwTownID);
|
||||
if (CharItr == m_CharList.end())
|
||||
{
|
||||
RespawnCharList tempRespawnCharList;
|
||||
tempRespawnCharList.clear();
|
||||
m_CharList.insert(std::make_pair(tempProtoType.m_dwTownID, tempRespawnCharList));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 마을일 경우 모든 존의 마을 리스폰 정보 맵에 집어 넣는다.
|
||||
if (0 != (tempProtoType.m_dwTownID & Castle::TOWN_BIT))
|
||||
{
|
||||
for (int i=0; i<iRespawnPosCount; ++i)
|
||||
{
|
||||
READ_DATA("RespawnPosX", tempRespawnPos.m_fPointX);
|
||||
READ_DATA("RespawnPosY", tempRespawnPos.m_fPointY);
|
||||
READ_DATA("RespawnPosZ", tempRespawnPos.m_fPointZ);
|
||||
|
||||
tempProtoType.m_RespawnPosList.push_back(tempRespawnPos);
|
||||
}
|
||||
|
||||
RespawnProtoTypeMap::iterator itr = m_AllZoneTownList.find(tempProtoType.m_dwTownID);
|
||||
if (itr == m_AllZoneTownList.end())
|
||||
{
|
||||
m_AllZoneTownList.insert(std::make_pair(tempProtoType.m_dwTownID, tempProtoType));
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG1(g_Log, "리스폰 스크립트에 마을 혹은 성의 ID가 같은 것이 두개이상 존재합니다. TownID : %d", tempProtoType.m_dwTownID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tempProtoType.m_RespawnPosList.clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::AddCampRespawnPoint(unsigned long dwCampID, unsigned long dwGID, const Position& RespawnPos)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwCampID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
ERRLOG1(g_Log, "CampID : 0x%08x 이미 존재하는 리스폰 포인트(길드 요새) 입니다.", dwCampID);
|
||||
return false;
|
||||
}
|
||||
|
||||
Guild::CGuild* lpGuild = Guild::CGuildMgr::GetInstance().GetGuild(dwGID);
|
||||
if (lpGuild)
|
||||
{
|
||||
RespawnProtoType tempProtoType;
|
||||
tempProtoType.m_dwTownID = dwCampID;
|
||||
tempProtoType.m_dwGID = dwGID;
|
||||
tempProtoType.m_cNation = lpGuild->GetNation();
|
||||
tempProtoType.m_cZone = CServerSetup::GetInstance().GetServerZone();
|
||||
tempProtoType.m_RespawnPosList.push_back(RespawnPos);
|
||||
m_ProtoTypeList.insert(std::make_pair(tempProtoType.m_dwTownID, tempProtoType));
|
||||
|
||||
RespawnCharList tempCharList;
|
||||
tempCharList.clear();
|
||||
m_CharList.insert(std::make_pair(tempProtoType.m_dwTownID, tempCharList));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::DeleteCampRespawnPoint(unsigned long dwCampID)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwCampID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
m_ProtoTypeList.erase(itr);
|
||||
return true;
|
||||
}
|
||||
|
||||
ERRLOG1(g_Log, "CampID : 0x%08x 존재하지 않는 리스폰 포인트(길드 요새) 입니다.", dwCampID);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::AddRealmStatueRespawnPoint(unsigned long dwIndex, unsigned char cNation, const Position& RespawnPos)
|
||||
{
|
||||
unsigned long dwStatueID = Castle::STATUE_BIT | (dwIndex << 16);
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwStatueID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
ERRLOG2(g_Log, "Statue ID : 0x%08x (0x%08x) 이미 존재하는 리스폰 포인트(다크 카나번 석상) 입니다.", dwStatueID, dwIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
RespawnProtoType tempProtoType;
|
||||
tempProtoType.m_dwTownID = dwStatueID;
|
||||
tempProtoType.m_dwGID = 0;
|
||||
tempProtoType.m_cNation = cNation;
|
||||
tempProtoType.m_cZone = CServerSetup::GetInstance().GetServerZone();
|
||||
tempProtoType.m_RespawnPosList.push_back(RespawnPos);
|
||||
m_ProtoTypeList.insert(std::make_pair(tempProtoType.m_dwTownID, tempProtoType));
|
||||
|
||||
RespawnCharList tempCharList;
|
||||
tempCharList.clear();
|
||||
m_CharList.insert(std::make_pair(tempProtoType.m_dwTownID, tempCharList));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::DeleteRealmStatueRespawnPoint(unsigned long dwIndex)
|
||||
{
|
||||
unsigned long dwStatueID = Castle::STATUE_BIT | (dwIndex << 16);
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwStatueID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
m_ProtoTypeList.erase(itr);
|
||||
return true;
|
||||
}
|
||||
|
||||
ERRLOG2(g_Log, "Statue ID : 0x%08x (0x%08x) 존재하지 않는 리스폰 포인트(다크 카나번 석상) 입니다.", dwStatueID, dwIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::SetCastleRespawnPointNation(unsigned long dwCastleNameID, unsigned char cNation)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.begin();
|
||||
RespawnProtoTypeMap::iterator end = m_ProtoTypeList.end();
|
||||
bool bRetFound = false;
|
||||
|
||||
while (itr != end)
|
||||
{
|
||||
RespawnProtoType& protoType = itr->second;
|
||||
if (0 != (Castle::CASTLE_BIT & protoType.m_dwTownID))
|
||||
{
|
||||
unsigned long dwMask = dwCastleNameID << Castle::CASTLE_NAME_BIT_SHIFT;
|
||||
if (dwMask == (dwMask & protoType.m_dwTownID))
|
||||
{
|
||||
if (0 == (Castle::SIEGE_BIT & protoType.m_dwTownID))
|
||||
{
|
||||
// 수성
|
||||
bRetFound = true;
|
||||
protoType.m_cNation = cNation;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 공성
|
||||
switch (cNation)
|
||||
{
|
||||
case Creature::KARTERANT:
|
||||
protoType.m_cNation = Creature::MERKADIA;
|
||||
break;
|
||||
|
||||
case Creature::MERKADIA:
|
||||
protoType.m_cNation = Creature::KARTERANT;
|
||||
break;
|
||||
|
||||
case Creature::ALMIGHTY_PIRATE:
|
||||
case Creature::STATELESS:
|
||||
default:
|
||||
protoType.m_cNation = Creature::STATELESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
|
||||
if (false == bRetFound)
|
||||
{
|
||||
ERRLOG1(g_Log, "CastleNameID : %d 존재하지 않는 리스폰 포인트(성) 입니다.", dwCastleNameID);
|
||||
}
|
||||
|
||||
return bRetFound;
|
||||
}
|
||||
/*
|
||||
void CCharRespawnMgr::DelCharacter(unsigned long dwTownID, unsigned long dwCID)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwTownID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = itr->second;
|
||||
|
||||
RespawnCharMap::iterator it = m_CharList.find(dwTownID);
|
||||
if (it != m_CharList.end())
|
||||
{
|
||||
RespawnCharList& tempCharList = it->second;
|
||||
|
||||
RespawnCharList::iterator CharListItr = tempCharList.begin();
|
||||
while (CharListItr != tempCharList.end())
|
||||
{
|
||||
RespawnCharInfo& tempCharInfo = *CharListItr;
|
||||
|
||||
if(tempCharInfo.m_dwCID == dwCID)
|
||||
{
|
||||
CharListItr = tempCharList.erase(CharListItr);
|
||||
return;
|
||||
}
|
||||
|
||||
++CharListItr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
CCharRespawnMgr::eReturn CCharRespawnMgr::AddCharacter(unsigned long dwTownID, unsigned long dwCID)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwTownID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = itr->second;
|
||||
|
||||
if (0 != (dwTownID & Castle::ABTOWN_BIT))
|
||||
{
|
||||
return RET_TOWN;
|
||||
}
|
||||
|
||||
if (0 != (dwTownID & Castle::TOWN_BIT))
|
||||
{
|
||||
return RET_TOWN;
|
||||
}
|
||||
|
||||
RespawnCharMap::iterator it = m_CharList.find(dwTownID);
|
||||
if (it != m_CharList.end())
|
||||
{
|
||||
unsigned long dwRemainSec = 0;
|
||||
RespawnCharList& tempCharList = it->second;
|
||||
if (0 == tempCharList.size())
|
||||
{
|
||||
dwRemainSec = static_cast<unsigned long>(RESPAWN_DELAY_SEC);
|
||||
}
|
||||
else
|
||||
{
|
||||
RespawnCharList::reverse_iterator CharInfoItr = tempCharList.rbegin();
|
||||
RespawnCharInfo& tempCharInfo = *CharInfoItr;
|
||||
dwRemainSec = static_cast<unsigned long>(tempCharInfo.m_dwRemainSec + RESPAWN_DELAY_SEC);
|
||||
}
|
||||
|
||||
tempCharList.push_back(RespawnCharInfo(dwCID, dwRemainSec));
|
||||
return RET_TRUE;
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
|
||||
return RET_FALSE;
|
||||
}
|
||||
|
||||
void CCharRespawnMgr::ProcessRespawn()
|
||||
{
|
||||
RespawnCharMap::iterator itr = m_CharList.begin();
|
||||
while (itr != m_CharList.end())
|
||||
{
|
||||
if (0 != (itr->first & Castle::TOWN_BIT))
|
||||
{
|
||||
// 마을은 그냥 넘어간다.
|
||||
++itr;
|
||||
continue;
|
||||
}
|
||||
|
||||
RespawnCharList& tempCharList = itr->second;
|
||||
if (tempCharList.size() > 0)
|
||||
{
|
||||
RespawnCharList::iterator CharListItr = tempCharList.begin();
|
||||
while (CharListItr != tempCharList.end())
|
||||
{
|
||||
RespawnCharInfo& tempCharInfo = *CharListItr;
|
||||
|
||||
if (0 < tempCharInfo.m_dwRemainSec)
|
||||
{
|
||||
--tempCharInfo.m_dwRemainSec;
|
||||
|
||||
// 2000초(약 30분). 이 숫자를 넘으면 로그를 찍는다
|
||||
if (2000 < tempCharInfo.m_dwRemainSec)
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:0x%08x/RemainSec:%u/리스폰 시간 이상",
|
||||
tempCharInfo.m_dwCID, tempCharInfo.m_dwRemainSec);
|
||||
}
|
||||
}
|
||||
else if (0 == tempCharInfo.m_dwRemainSec)
|
||||
{
|
||||
// 리스폰 시키고, 하나를 빼낸다.
|
||||
CCharacter* lpCharacter = CCreatureManager::GetInstance().GetCharacter(tempCharInfo.m_dwCID);
|
||||
if (lpCharacter)
|
||||
{
|
||||
if(lpCharacter->GetRespawnTownID() == 0)
|
||||
{
|
||||
CharListItr = tempCharList.erase(CharListItr);
|
||||
continue;
|
||||
}
|
||||
|
||||
RespawnProtoTypeMap::iterator ProtoTypeItr = m_ProtoTypeList.find(itr->first);
|
||||
if (ProtoTypeItr != m_ProtoTypeList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = ProtoTypeItr->second;
|
||||
size_t iIndex = Math::Random::ComplexRandom(static_cast<int>(tempProtoType.m_RespawnPosList.size()));
|
||||
Position resapwnPos = tempProtoType.m_RespawnPosList[iIndex];
|
||||
resapwnPos.m_fPointX += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
resapwnPos.m_fPointZ += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
|
||||
lpCharacter->Respawn(CCharRespawnMgr::RST_FORTRESS, resapwnPos);
|
||||
|
||||
if (0 != lpCharacter->GetPID())
|
||||
{
|
||||
// 파티원 리스폰을 알린다.
|
||||
GameClientSendPacket::SendCharDeadToParty(lpCharacter, 0, PktDeadInfo::RESPAWN);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Position resapwnPos = GetDefaultRespawnPos(lpCharacter->GetNation());
|
||||
resapwnPos.m_fPointX += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
resapwnPos.m_fPointZ += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
|
||||
lpCharacter->Respawn(CCharRespawnMgr::RST_FORTRESS, resapwnPos);
|
||||
|
||||
if (0 != lpCharacter->GetPID())
|
||||
{
|
||||
// 파티원 리스폰을 알린다.
|
||||
GameClientSendPacket::SendCharDeadToParty(lpCharacter, 0, PktDeadInfo::RESPAWN);
|
||||
}
|
||||
}
|
||||
|
||||
CharListItr = tempCharList.erase(CharListItr);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
++CharListItr;
|
||||
}
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
const Position CCharRespawnMgr::GetDefaultRespawnPos(unsigned char cNation)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.begin();
|
||||
while (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = itr->second;
|
||||
|
||||
if (0 != (tempProtoType.m_dwTownID & Castle::TOWN_BIT))
|
||||
{
|
||||
if (Creature::STATELESS == tempProtoType.m_cNation || cNation == tempProtoType.m_cNation)
|
||||
{
|
||||
int iRespawnPosCount = static_cast<int>( tempProtoType.m_RespawnPosList.size() );
|
||||
int iIndex = Math::Random::SimpleRandom(GetTickCount(), iRespawnPosCount);
|
||||
Position resapwnPos = tempProtoType.m_RespawnPosList[iIndex];
|
||||
resapwnPos.m_fPointX += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
resapwnPos.m_fPointZ += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
return resapwnPos;
|
||||
}
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
|
||||
ERRLOG2(g_Log, "존의 기본 리스폰 위치를 찾을수 없습니다. Zone : %d, Nation : %d",
|
||||
CServerSetup::GetInstance().GetServerZone(), cNation);
|
||||
|
||||
return Position(0, 0, 0);
|
||||
}
|
||||
|
||||
const Position CCharRespawnMgr::GetTownRespawnPos(unsigned char cZone, unsigned char cNation)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_AllZoneTownList.begin();
|
||||
while (itr != m_AllZoneTownList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = itr->second;
|
||||
|
||||
if (tempProtoType.m_cZone == cZone)
|
||||
{
|
||||
if (Creature::STATELESS == tempProtoType.m_cNation || cNation == tempProtoType.m_cNation)
|
||||
{
|
||||
int iRespawnPosCount = static_cast<int>( tempProtoType.m_RespawnPosList.size() );
|
||||
int iIndex = Math::Random::SimpleRandom(GetTickCount(), iRespawnPosCount);
|
||||
Position resapwnPos = tempProtoType.m_RespawnPosList[iIndex];
|
||||
resapwnPos.m_fPointX += Math::Random::SimpleRandom(GetTickCount(), 20) - 10;
|
||||
resapwnPos.m_fPointZ += Math::Random::SimpleRandom(GetTickCount(), 20) - 10;
|
||||
return resapwnPos;
|
||||
}
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
|
||||
ERRLOG2(g_Log, "존의 기본 리스폰 위치를 찾을수 없습니다. Zone : %d, Nation : %d", cZone, cNation);
|
||||
return Position(0, 0, 0);
|
||||
}
|
||||
|
||||
const Position CCharRespawnMgr::GetRespawnPos(unsigned long dwTownID)
|
||||
{
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.find(dwTownID);
|
||||
if (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = itr->second;
|
||||
size_t iIndex = Math::Random::ComplexRandom(static_cast<int>(tempProtoType.m_RespawnPosList.size()));
|
||||
Position resapwnPos = tempProtoType.m_RespawnPosList[iIndex];
|
||||
resapwnPos.m_fPointX += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
resapwnPos.m_fPointZ += Math::Random::SimpleRandom(GetTickCount(), 10) - 5;
|
||||
return resapwnPos;
|
||||
}
|
||||
|
||||
ERRLOG2(g_Log, "존의 해당 리스폰 위치를 찾을수 없습니다. Zone : %d, dwTownID : %d",
|
||||
CServerSetup::GetInstance().GetServerZone(), dwTownID);
|
||||
|
||||
return Position(0, 0, 0);
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::SendRespawnInfo(unsigned long dwCID)
|
||||
{
|
||||
CCharacter* lpCharacter = CCreatureManager::GetInstance().GetCharacter(dwCID);
|
||||
if (NULL == lpCharacter)
|
||||
{
|
||||
ERRLOG1(g_Log, "CID : 0x%08x 리스폰 하려는 캐릭터가 존재하지 않습니다.", dwCID);
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned short BufferSize = sizeof(PktRsInfoAck) + sizeof(RespawnArea) * PktRsInfoAck::MAX_RESPAWN_POINT;
|
||||
char szBuffer[BufferSize];
|
||||
|
||||
unsigned short wBufferSize = 0;
|
||||
unsigned char cRespawnAreaNum = 0; // 리스폰 포인트 수
|
||||
unsigned char cNation = lpCharacter->GetNation();
|
||||
RespawnArea* lpRespawnArea = reinterpret_cast<RespawnArea*>(szBuffer + sizeof(PktRsInfoAck));
|
||||
Guild::CGuild* lpGuild = Guild::CGuildMgr::GetInstance().GetGuild(lpCharacter->GetGID());
|
||||
|
||||
RespawnProtoTypeMap::iterator itr = m_ProtoTypeList.begin();
|
||||
while (itr != m_ProtoTypeList.end())
|
||||
{
|
||||
RespawnProtoType& tempProtoType = itr->second;
|
||||
|
||||
if (0 != (tempProtoType.m_dwTownID & Castle::ABTOWN_BIT))
|
||||
{
|
||||
if( lpCharacter->GetAbilityValue(Skill::Type::AB_RESPAWN_EX))
|
||||
{
|
||||
// 자신의 국가가 속한 마을이라면 패킷에 추가한다.
|
||||
if (Creature::STATELESS == tempProtoType.m_cNation || cNation == tempProtoType.m_cNation)
|
||||
{
|
||||
lpRespawnArea->m_dwTownID = tempProtoType.m_dwTownID;
|
||||
lpRespawnArea->m_RespawnPos.fPointX = tempProtoType.m_RespawnPosList[0].m_fPointX;
|
||||
lpRespawnArea->m_RespawnPos.fPointY = tempProtoType.m_RespawnPosList[0].m_fPointY;
|
||||
lpRespawnArea->m_RespawnPos.fPointZ = tempProtoType.m_RespawnPosList[0].m_fPointZ;
|
||||
|
||||
wBufferSize += sizeof(RespawnArea);
|
||||
++cRespawnAreaNum;
|
||||
++lpRespawnArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (0 != (tempProtoType.m_dwTownID & Castle::TOWN_BIT))
|
||||
{
|
||||
// 자신의 국가가 속한 마을이라면 패킷에 추가한다.
|
||||
if (Creature::STATELESS == tempProtoType.m_cNation || cNation == tempProtoType.m_cNation)
|
||||
{
|
||||
lpRespawnArea->m_dwTownID = tempProtoType.m_dwTownID;
|
||||
lpRespawnArea->m_RespawnPos.fPointX = tempProtoType.m_RespawnPosList[0].m_fPointX;
|
||||
lpRespawnArea->m_RespawnPos.fPointY = tempProtoType.m_RespawnPosList[0].m_fPointY;
|
||||
lpRespawnArea->m_RespawnPos.fPointZ = tempProtoType.m_RespawnPosList[0].m_fPointZ;
|
||||
|
||||
wBufferSize += sizeof(RespawnArea);
|
||||
++cRespawnAreaNum;
|
||||
++lpRespawnArea;
|
||||
}
|
||||
}
|
||||
else if (lpGuild && Guild::JOIN_WAIT != lpGuild->GetTitle(dwCID) && 0 != (tempProtoType.m_dwTownID & Castle::CAMP_BIT))
|
||||
{
|
||||
// 진지라면 자기 길드의 곳이라면 패킷에 추가한다.
|
||||
if (lpGuild->GetGID() == tempProtoType.m_dwGID)
|
||||
{
|
||||
lpRespawnArea->m_dwTownID = tempProtoType.m_dwTownID;
|
||||
lpRespawnArea->m_RespawnPos.fPointX = tempProtoType.m_RespawnPosList[0].m_fPointX;
|
||||
lpRespawnArea->m_RespawnPos.fPointY = tempProtoType.m_RespawnPosList[0].m_fPointY;
|
||||
lpRespawnArea->m_RespawnPos.fPointZ = tempProtoType.m_RespawnPosList[0].m_fPointZ;
|
||||
|
||||
wBufferSize += sizeof(RespawnArea);
|
||||
++cRespawnAreaNum;
|
||||
++lpRespawnArea;
|
||||
}
|
||||
}
|
||||
else if (0 != (tempProtoType.m_dwTownID & Castle::CASTLE_BIT))
|
||||
{
|
||||
// 자신과 같은 국적 수성/공성 리스폰 포인트라면 패킷에 추가한다.
|
||||
if (cNation == tempProtoType.m_cNation)
|
||||
{
|
||||
// 공성측 리스폰 포인트는 공성 시간에만 활성화 된다.
|
||||
bool bAddRespawnPoint = true;
|
||||
if (0 != (tempProtoType.m_dwTownID & Castle::SIEGE_BIT))
|
||||
{
|
||||
if (!CGameTimeMgr::GetInstance().IsSiegeWarTime())
|
||||
{
|
||||
bAddRespawnPoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bAddRespawnPoint)
|
||||
{
|
||||
lpRespawnArea->m_dwTownID = tempProtoType.m_dwTownID;
|
||||
lpRespawnArea->m_RespawnPos.fPointX = tempProtoType.m_RespawnPosList[0].m_fPointX;
|
||||
lpRespawnArea->m_RespawnPos.fPointY = tempProtoType.m_RespawnPosList[0].m_fPointY;
|
||||
lpRespawnArea->m_RespawnPos.fPointZ = tempProtoType.m_RespawnPosList[0].m_fPointZ;
|
||||
|
||||
wBufferSize += sizeof(RespawnArea);
|
||||
++cRespawnAreaNum;
|
||||
++lpRespawnArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (0 != (tempProtoType.m_dwTownID & Castle::STATUE_BIT))
|
||||
{
|
||||
// 다크 카나번 석상이라면 자신의 국가와 일치(종족과 일치)하면 패킷에 추가한다.
|
||||
if (Creature::STATELESS == tempProtoType.m_cNation || cNation == tempProtoType.m_cNation)
|
||||
{
|
||||
lpRespawnArea->m_dwTownID = tempProtoType.m_dwTownID;
|
||||
lpRespawnArea->m_RespawnPos.fPointX = tempProtoType.m_RespawnPosList[0].m_fPointX;
|
||||
lpRespawnArea->m_RespawnPos.fPointY = tempProtoType.m_RespawnPosList[0].m_fPointY;
|
||||
lpRespawnArea->m_RespawnPos.fPointZ = tempProtoType.m_RespawnPosList[0].m_fPointZ;
|
||||
|
||||
wBufferSize += sizeof(RespawnArea);
|
||||
++cRespawnAreaNum;
|
||||
++lpRespawnArea;
|
||||
}
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
|
||||
// edith 2009.07.21 워프나 포탈 사용시 현재 좌표갱신하기 (불법이동 방지 검출 피하기 위해서)
|
||||
if(lpCharacter)
|
||||
lpCharacter->SetCurrentPos(lpRespawnArea->m_RespawnPos.fPointX, lpRespawnArea->m_RespawnPos.fPointY, lpRespawnArea->m_RespawnPos.fPointZ);
|
||||
|
||||
PktRsInfoAck* lpRsInfoAck = reinterpret_cast<PktRsInfoAck*>(szBuffer);
|
||||
lpRsInfoAck->m_dwCharID = dwCID;
|
||||
lpRsInfoAck->m_cRsAreaNum = cRespawnAreaNum;
|
||||
lpRsInfoAck->m_wSize = wBufferSize;
|
||||
|
||||
CGameClientDispatch* lpDispatch = lpCharacter->GetDispatcher();
|
||||
if (NULL != lpDispatch)
|
||||
{
|
||||
CSendStream& SendStream = lpDispatch->GetSendStream();
|
||||
return SendStream.WrapCompress(szBuffer, sizeof(PktRsInfoAck) + wBufferSize, CmdCharRespawnInfo, 0, 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharRespawnMgr::SendRespawnAreaInfo(unsigned long dwCID, unsigned long dwTownID, bool bCount)
|
||||
{
|
||||
CCharacter* lpCharacter = CCreatureManager::GetInstance().GetCharacter(dwCID);
|
||||
if (NULL == lpCharacter)
|
||||
{
|
||||
ERRLOG1(g_Log, "CID : 0x%08x 리스폰 하려는 캐릭터가 존재하지 않습니다.", dwCID);
|
||||
return false;
|
||||
}
|
||||
|
||||
RespawnProtoTypeMap::iterator RespawnItr = m_ProtoTypeList.find(dwTownID);
|
||||
if (RespawnItr == m_ProtoTypeList.end())
|
||||
{
|
||||
ERRLOG1(g_Log, "TownID : 0x%08x 리스폰 지점이 존재하지 않습니다.", dwTownID);
|
||||
return false;
|
||||
}
|
||||
|
||||
RespawnCharMap::iterator itr = m_CharList.find(dwTownID);
|
||||
if (itr != m_CharList.end())
|
||||
{
|
||||
RespawnCharList& tempCharList = itr->second;
|
||||
|
||||
PktRsAreaInfoAck pktRsAreaInfoAck;
|
||||
pktRsAreaInfoAck.m_dwCharID = dwCID;
|
||||
pktRsAreaInfoAck.m_dwTownID = dwTownID;
|
||||
pktRsAreaInfoAck.m_bCount = bCount;
|
||||
pktRsAreaInfoAck.m_nWaitOrder = static_cast<unsigned short>(tempCharList.size());
|
||||
|
||||
unsigned long dwSec = RESPAWN_DELAY_SEC;
|
||||
if (0 != tempCharList.size())
|
||||
{
|
||||
RespawnCharList::reverse_iterator CharInfoItr = tempCharList.rbegin();
|
||||
RespawnCharInfo& tempCharInfo = *CharInfoItr;
|
||||
dwSec += tempCharInfo.m_dwRemainSec;
|
||||
}
|
||||
|
||||
// 시간 계산
|
||||
pktRsAreaInfoAck.m_cRemainHour = static_cast<unsigned char>(dwSec / SEC_PER_HOUR);
|
||||
pktRsAreaInfoAck.m_cRemainMin = static_cast<unsigned char>((dwSec / SEC_PER_MIN) / MIN_PER_HOUR);
|
||||
pktRsAreaInfoAck.m_cRemainSec = static_cast<unsigned char>((dwSec % SEC_PER_HOUR) % SEC_PER_MIN);
|
||||
|
||||
char* szPacket = reinterpret_cast<char *>(&pktRsAreaInfoAck);
|
||||
if (PacketWrap::WrapCrypt(szPacket, sizeof(PktRsAreaInfoAck), CmdCharRespawnAreaInfo, 0, 0))
|
||||
{
|
||||
CGameClientDispatch* lpDispatch = lpCharacter->GetDispatcher();
|
||||
if (NULL != lpDispatch)
|
||||
{
|
||||
return lpDispatch->GetSendStream().PutBuffer(szPacket, sizeof(PktRsAreaInfoAck), CmdCharRespawnAreaInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ERRLOG1(g_Log, "dwTownID : 0x%08x 해당 마을(성, 길드 요새, 다크 카나번 석상)이 리스폰 포인트로 존재하지 않습니다.", dwTownID);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#ifndef _CHARACTER_RESPAWN_MANAGER_H_
|
||||
#define _CHARACTER_RESPAWN_MANAGER_H_
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Creature/CreatureStructure.h>
|
||||
|
||||
class CCharRespawnMgr
|
||||
{
|
||||
public:
|
||||
enum Const
|
||||
{
|
||||
RESPAWN_DELAY_SEC = 5,
|
||||
|
||||
SEC_PER_HOUR = 3600,
|
||||
SEC_PER_MIN = 60,
|
||||
MIN_PER_HOUR = 60
|
||||
};
|
||||
|
||||
enum eReturn
|
||||
{
|
||||
RET_FALSE = 0,
|
||||
RET_TRUE = 1,
|
||||
RET_TOWN = 2
|
||||
};
|
||||
|
||||
enum RespawnType
|
||||
{
|
||||
RST_TOWN = 0, // 죽거나 포탈 이용으로 마을에서 부활 (일반적인 리스폰)
|
||||
RST_FORTRESS = 1, // 죽거나 포탈 이용으로 요새나 성 및 다크 카나번 석상에서 부활
|
||||
RST_RESURRECTION = 2, // 레저렉션으로 부활
|
||||
RST_BATTLE = 3 // 배틀 그라운드나 배틀 로한에서 리스폰 시
|
||||
};
|
||||
|
||||
struct RespawnProtoType
|
||||
{
|
||||
unsigned long m_dwTownID; // 마을, 길드 요새, 성 ID, 다크 카나번 석상 ID(0x800x0000)
|
||||
unsigned long m_dwGID; // 길드 요새
|
||||
unsigned char m_cNation; // 국적 (휴먼, 아칸, 제3세력)
|
||||
unsigned char m_cZone; // 존
|
||||
std::vector<Position> m_RespawnPosList; // 리스폰 위치
|
||||
};
|
||||
|
||||
struct RespawnCharInfo
|
||||
{
|
||||
unsigned long m_dwCID;
|
||||
unsigned long m_dwRemainSec;
|
||||
|
||||
RespawnCharInfo(unsigned long dwCID, unsigned long dwSec)
|
||||
: m_dwCID(dwCID), m_dwRemainSec(dwSec) { }
|
||||
};
|
||||
|
||||
typedef std::list<RespawnCharInfo> RespawnCharList;
|
||||
typedef std::map<unsigned long, RespawnProtoType> RespawnProtoTypeMap;
|
||||
typedef std::map<unsigned long, RespawnCharList> RespawnCharMap;
|
||||
|
||||
~CCharRespawnMgr();
|
||||
|
||||
static CCharRespawnMgr& GetInstance();
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 리스폰 스크립트 (RespawnScript.txt)
|
||||
// 스크립트의 0x40001000 은 Castle::CASTLE_BIT | (성의 NameID << Castle::CASTLE_NAME_BIT_SHIFT) 입니다.
|
||||
// 이후에 성 정보를 DB중계 서버로부터 받아와서 Map 정보에 제대로된 CastleID 가 key 로 들어갑니다.
|
||||
bool LoadRespawnFromFile(const char* szFileName = 0);
|
||||
|
||||
|
||||
// 요새 리스폰 포인트 사용
|
||||
bool AddCampRespawnPoint(unsigned long dwCampID, unsigned long dwGID, const Position& RespawnPos);
|
||||
bool DeleteCampRespawnPoint(unsigned long dwCampID);
|
||||
|
||||
|
||||
// 다크 카나번 석상 리스폰 포인트 사용
|
||||
// TownID 로 0x80010000 처럼 Castle::STATUE_BIT | (석상 Index) << 16) 입니다.
|
||||
bool AddRealmStatueRespawnPoint(unsigned long dwIndex, unsigned char cNation, const Position& RespawnPos);
|
||||
bool DeleteRealmStatueRespawnPoint(unsigned long dwIndex);
|
||||
|
||||
|
||||
// 성 리스폰 포인트 사용
|
||||
// 수성측 성 리스폰 포인트 키 : CASTLE_BIT | (NameID << Castle::CASTLE_NAME_BIT_SHIFT)
|
||||
// 공성측 임시 리스폰 포인트 키 : CASTLE_BIT | SIEGE_BIT | (NameID << Castle::CASTLE_NAME_BIT_SHIFT) | ID(갯수별)
|
||||
bool SetCastleRespawnPointNation(unsigned long dwCastleNameID, unsigned char cNation);
|
||||
|
||||
|
||||
eReturn AddCharacter(unsigned long dwTownID, unsigned long dwCID);
|
||||
// void DelCharacter(unsigned long dwTownID, unsigned long dwCID);
|
||||
|
||||
void ProcessRespawn();
|
||||
|
||||
const Position GetDefaultRespawnPos(unsigned char cNation);
|
||||
const Position GetRespawnPos(unsigned long dwTownID);
|
||||
const Position GetTownRespawnPos(unsigned char cZone, unsigned char cNation);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Send 함수
|
||||
bool SendRespawnInfo(unsigned long dwCID);
|
||||
bool SendRespawnAreaInfo(unsigned long dwCID, unsigned long dwTownID, bool bCount=false);
|
||||
|
||||
|
||||
private:
|
||||
CCharRespawnMgr();
|
||||
|
||||
RespawnProtoTypeMap m_ProtoTypeList; // 현재 존의 리스폰 포인트 정보
|
||||
RespawnProtoTypeMap m_AllZoneTownList; // 전 존의 마을 리스폰 포인트 정보
|
||||
RespawnCharMap m_CharList;
|
||||
|
||||
static const char* ms_szRespawnScriptFileName;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,485 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Item/Container/ItemContainer.h>
|
||||
#include <Item/Container/ContainerConstant.h>
|
||||
#include <Item/Container/ExchangeContainer.h>
|
||||
#include <Item/Container/EquipmentsContainer.h>
|
||||
|
||||
#include <Log/GameLog.h>
|
||||
#include <Log/CharacterLog.h>
|
||||
#include <Log/LogStruct.h>
|
||||
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
#include <Map/FieldMap/CellManager.h>
|
||||
#include <Map/FieldMap//VirtualArea/VirtualArea.h>
|
||||
#include <Map/FieldMap//VirtualArea/VirtualAreaMgr.h>
|
||||
#include <Map/DuelMap/DuelCellManager.h>
|
||||
|
||||
#include <Skill/SkillMgr.h>
|
||||
#include <Skill/SkillTable.h>
|
||||
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharEtc.h>
|
||||
#include <Network/Dispatch/Chat/ChatDispatch.h>
|
||||
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/NPC/NPC.h>
|
||||
#include <Creature/Siege/SiegeObject.h>
|
||||
#include <Creature/Siege/SiegeObjectMgr.h>
|
||||
#include <Creature/Siege/CampShop.h>
|
||||
|
||||
#include <Community/Guild/GuildMgr.h>
|
||||
#include <Community/Guild/Guild.h>
|
||||
|
||||
#include "Character.h"
|
||||
|
||||
CCharacter::CCharacter(unsigned long dwCID, unsigned long dwSessionID)
|
||||
: CAggresiveCreature(dwCID),
|
||||
m_dwUID(0), m_dwSessionID(dwSessionID),
|
||||
m_nLogoutCount(LOGOUT_COUNT), m_nDBUpdateCount(DBUPDATE_COUNT), m_cConsumeMPCount(0),
|
||||
m_lpGameClientDispatch(NULL), m_lpSummonee(NULL),
|
||||
m_cOperationFlags(0), m_cHandPos(0), m_cRidePos(0), m_friendList(dwCID, &CXRefFriends::GetInstance()), m_banList(dwCID, &CXRefBans::GetInstance()),
|
||||
m_dwLastUpdateExTime(0), m_cMoveUpdateExCount(0), m_dwRideArmsCID(0), m_dwRespawnSpeed(RESPAWN_PULSE),
|
||||
m_dwProtectGateCID(0), m_dwLastShoutTime(0),
|
||||
m_dwLastSendPartyAttackInfoTime(0), m_bChatBan(false),
|
||||
m_cAccountNation(0), // WORK_LIST 2.4 계정 국적을 게임서버의 캐릭터가 가지도록 구현
|
||||
m_cNameChangeCount(0),
|
||||
m_cGuildWarFlag(Creature::WAR_OFF),
|
||||
m_cRealmWarFlag(Creature::WAR_OFF),
|
||||
m_dwExchangeID(0),
|
||||
m_cGMModelFlag(0),
|
||||
m_cRealmPoint(0),
|
||||
m_dwPlayTime(0),
|
||||
m_lPremiumTime(0),
|
||||
m_cGuildSafe(-1),
|
||||
m_iAbilityPoint(0),
|
||||
m_iUseAbilityPoint(0),
|
||||
m_iAdminAbilityPoint(0),
|
||||
m_bRealmWarBuffer(FALSE),
|
||||
m_bDead(FALSE),
|
||||
m_eLastDeadType(DEAD_BY_NONE),
|
||||
m_dwRespawnTownID(0)
|
||||
{
|
||||
m_StartTime = CTime::GetCurrentTime();
|
||||
m_StartPremiumTime = CTime::GetCurrentTime();
|
||||
|
||||
memset(&m_FightInfo, 0, sizeof(CharacterFightInfo));
|
||||
memset(&m_DBData, 0, sizeof(m_DBData));
|
||||
memset(&m_CharacterStatus, 0, sizeof(m_CharacterStatus));
|
||||
memset(m_szAccountName, 0, sizeof(char) * CHAR_INFOST::MAX_ACCOUNT_LEN);
|
||||
|
||||
memset(m_AbilityValue, 0, sizeof(m_AbilityValue));
|
||||
|
||||
std::fill_n(m_wHistoryQuest, int(Quest::MAX_HISTORY_QUEST), 0);
|
||||
std::fill_n(m_cUsingMastery, int(MAX_USING_MASTERY), char(Skill::NONE_MASTERY));
|
||||
std::fill_n(m_bPadding, int(PAD_BYTE), 0x65);
|
||||
}
|
||||
|
||||
|
||||
CCharacter::~CCharacter()
|
||||
{
|
||||
for(int nCount = 0; nCount < PAD_BYTE; ++nCount)
|
||||
{
|
||||
if (0x65 != (unsigned char)m_bPadding[nCount])
|
||||
{
|
||||
SERLOG1(g_Log, "CID:0x%08x 캐릭터의 패딩 바이트가 바뀌었습니다. "
|
||||
"버퍼 오버플로우가 일어났을 수 있습니다.", m_dwCID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::fill_n(m_bPadding, int(PAD_BYTE), 0x67);
|
||||
|
||||
SetDispatcher(NULL);
|
||||
|
||||
m_SpellMgr.GetAffectedInfo().ClearAll();
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::Initialize(CGameClientDispatch* lpGameClientDispatch)
|
||||
{
|
||||
SetDispatcher(lpGameClientDispatch);
|
||||
|
||||
if (!m_Inventory.Initialize(m_dwCID,
|
||||
ContainerConstant::INVENTORY_WIDTH,
|
||||
ContainerConstant::INVENTORY_HEIGHT,
|
||||
ContainerConstant::MAX_INVENTORY_TAB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Equipments.Initialize(this,
|
||||
Item::EquipmentPos::MAX_EQUPMENT_POS))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_ExtraSpace.Initialize(m_dwCID,
|
||||
Item::ExtraSpacePos::MAX_EXTRA_SPACE_NUM))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Exchange.Initialize(this,
|
||||
ContainerConstant::EXCHANGE_WIDTH,
|
||||
ContainerConstant::EXCHANGE_HEIGHT))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Deposit.Initialize(this,
|
||||
ContainerConstant::DEPOSIT_WIDTH,
|
||||
ContainerConstant::DEPOSIT_HEIGHT,
|
||||
ContainerConstant::MAX_DEPOSIT_TAB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Stall.Initialize(m_dwCID,
|
||||
ContainerConstant::STALL_WIDTH,
|
||||
ContainerConstant::STALL_HEIGHT))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_TempInven.Initialize(this, Item::MAX_TEMP_INVEN_ITEM_NUM))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void CCharacter::SetDispatcher(CGameClientDispatch* lpGameClientDispatch)
|
||||
{
|
||||
DETLOG5(g_Log, "UID:%d/CID:0x%08x(0x%p)/DispatchPointer:0x%p에서 -> 0x%p로 바꿉니다.",
|
||||
m_dwUID, m_dwCID, this, m_lpGameClientDispatch, lpGameClientDispatch);
|
||||
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
m_lpGameClientDispatch->SetCharacter(NULL);
|
||||
|
||||
if (false == IsOperationFlagSet(CHAR_ZONE_MOVED))
|
||||
{
|
||||
m_lpGameClientDispatch->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
m_lpGameClientDispatch = lpGameClientDispatch;
|
||||
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
m_bLogout = false;
|
||||
m_dwUID = m_lpGameClientDispatch->GetUID();
|
||||
m_lpGameClientDispatch->SetCharacter(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (false == m_bLogout)
|
||||
{
|
||||
CCharacter* lpCharacter = CCreatureManager::GetInstance().GetCharacter(m_Stall.GetOtherOwner());
|
||||
|
||||
// 노점상 마무리
|
||||
if (0 != strcmp(m_Stall.GetStallName(), ""))
|
||||
{
|
||||
m_Stall.Close();
|
||||
m_Stall.SendCharStallOpen("");
|
||||
}
|
||||
|
||||
if (0 != m_Stall.GetOtherOwner())
|
||||
{
|
||||
if (Creature::CT_PC == Creature::GetCreatureType(m_Stall.GetOtherOwner()))
|
||||
{
|
||||
if (NULL != lpCharacter)
|
||||
{
|
||||
lpCharacter->GetStall().Leave(this);
|
||||
lpCharacter->GetStall().SendCharStallEnter(m_dwCID, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CCampShop* lpShop =
|
||||
reinterpret_cast<CCampShop*>(CCreatureManager::GetInstance().GetSiegeObject(m_Stall.GetOtherOwner()));
|
||||
if (NULL != lpShop)
|
||||
{
|
||||
lpShop->GetContainer().Leave(this);
|
||||
lpShop->GetContainer().SendCharStallEnter(m_dwCID, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_bLogout = true;
|
||||
CCreatureManager::GetInstance().EnqueueLogout(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::GetCharacterView(CHAR_VIEW& charView)
|
||||
{
|
||||
charView.CID = m_dwCID;
|
||||
|
||||
memcpy(charView.Name, m_DBData.m_Info.Name, CHAR_INFOST::MAX_NAME_LEN); // 캐릭터 이름
|
||||
|
||||
charView.Sex = m_DBData.m_Info.Sex; // 캐릭터 성
|
||||
charView.Hair = m_DBData.m_Info.Hair; // 캐릭터 머리 모양
|
||||
charView.Face = m_DBData.m_Info.Face; // 캐릭터 얼굴 모양
|
||||
charView.Race = m_DBData.m_Info.Race; // 캐릭터 종족
|
||||
charView.Class = m_DBData.m_Info.Class; // 캐릭터 클래스
|
||||
charView.Fame = m_DBData.m_Info.Fame; // 명성
|
||||
charView.Mileage = m_DBData.m_Info.Mileage; // 마일리지
|
||||
charView.GID = m_DBData.m_Info.GID; // 캐릭터 길드
|
||||
charView.PID = m_DBData.m_Info.PID; // 캐릭터 파티
|
||||
charView.Level = m_DBData.m_Info.Level; // 캐릭터 레벨
|
||||
|
||||
m_Equipments.GetEquipmentView(charView.Equip); // 장비(겉보기)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::BindPositionToNPC(const unsigned long dwNPCID)
|
||||
{
|
||||
/*
|
||||
GAMELOG::ERRType eReturnCode = 0;
|
||||
|
||||
CNPC* pNPC = CCreatureManager::GetInstance().GetNPC(dwNPCID);
|
||||
if (NULL != pNPC&& true == pNPC->IsBindable() &&
|
||||
pNPC->GetCurrentPos().GetDistance(m_CurrentPos) < CNPC::MAX_CONTACT_RANGE)
|
||||
{
|
||||
int nZone = CServerSetup::GetInstance().GetServerZone();
|
||||
if (GetMapIndex() != 0)
|
||||
{
|
||||
VirtualArea::CVirtualArea* lpVirtualArea = VirtualArea::CVirtualAreaMgr::GetInstance().GetVirtualArea(GetMapIndex());
|
||||
if (NULL != lpVirtualArea)
|
||||
{
|
||||
nZone = static_cast<int>(lpVirtualArea->GetVirtualZone());
|
||||
}
|
||||
}
|
||||
|
||||
if (pNPC->GetZone() == nZone)
|
||||
{
|
||||
m_DBData.m_Pos.SavePoint.fPointX = m_CurrentPos.m_fPointX;
|
||||
m_DBData.m_Pos.SavePoint.fPointY = m_CurrentPos.m_fPointY;
|
||||
m_DBData.m_Pos.SavePoint.fPointZ = m_CurrentPos.m_fPointZ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
eReturnCode = 1;
|
||||
}
|
||||
|
||||
// BindPosition로그를 남긴다.
|
||||
GAMELOG::LogCharBindPos(*this, dwNPCID, eReturnCode);
|
||||
|
||||
return (0 == eReturnCode);
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCharacter::ControlOption(const RejectOption Reject, bool bLogin)
|
||||
{
|
||||
m_RejectOption = Reject;
|
||||
|
||||
// TODO : 키 세팅 저장
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned char CCharacter::GetNation(void) const
|
||||
{
|
||||
CGuild* lpGuild = CGuildMgr::GetInstance().GetGuild(GetGID());
|
||||
if (NULL != lpGuild)
|
||||
{
|
||||
if (Guild::JOIN_WAIT != lpGuild->GetTitle(m_dwCID))
|
||||
{
|
||||
return lpGuild->GetNation();
|
||||
}
|
||||
}
|
||||
|
||||
// WORK_LIST 2.4 계정 국적을 게임서버의 캐릭터가 가지도록 구현
|
||||
return m_cAccountNation;
|
||||
/*
|
||||
switch (m_DBData.m_Info.Race)
|
||||
{
|
||||
case CClass::HUMAN: return Creature::KARTERANT;
|
||||
case CClass::AKHAN: return Creature::MERKADIA;
|
||||
}
|
||||
|
||||
ERRLOG2(g_Log, "CID:0x%08x 캐릭터의 종족 설정이 이상하여 제3국가로 사용합니다. 종족:%d",
|
||||
m_dwCID, m_DBData.m_Info.Race);
|
||||
return Creature::ALMIGHTY_PIRATE;
|
||||
*/
|
||||
}
|
||||
|
||||
void CCharacter::SetPID(unsigned long dwPID)
|
||||
{
|
||||
m_DBData.m_Info.PID = dwPID;
|
||||
}
|
||||
|
||||
void CCharacter::SetGID(unsigned long dwGID)
|
||||
{
|
||||
m_DBData.m_Info.GID = dwGID;
|
||||
}
|
||||
|
||||
ChState CCharacter::GetState(void)
|
||||
{
|
||||
ChState state;
|
||||
|
||||
state.m_wIP = m_DBData.m_Info.IP;
|
||||
state.m_wSTR = m_DBData.m_Info.STR;
|
||||
state.m_wDEX = m_DBData.m_Info.DEX;
|
||||
state.m_wCON = m_DBData.m_Info.CON;
|
||||
state.m_wINT = m_DBData.m_Info.INT;
|
||||
state.m_wWIS = m_DBData.m_Info.WIS;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void CCharacter::SetFame(unsigned long dwFame)
|
||||
{
|
||||
char cOldEliteBonus = GetEliteBonus();
|
||||
m_DBData.m_Info.Fame = dwFame;
|
||||
char cNewEliteBonus = GetEliteBonus();
|
||||
|
||||
if (cNewEliteBonus != cOldEliteBonus)
|
||||
{
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharEliteBonus(
|
||||
m_lpGameClientDispatch->GetSendStream(), cNewEliteBonus);
|
||||
}
|
||||
}
|
||||
|
||||
// 길드원 정보 업데이트
|
||||
CGuild* lpGuild = Guild::CGuildMgr::GetInstance().GetGuild(GetGID());
|
||||
if (NULL != lpGuild)
|
||||
{
|
||||
lpGuild->UpdateMemberInfo(m_dwCID, m_DBData.m_Info.Fame, Guild::TYPE_FAME);
|
||||
}
|
||||
|
||||
// 퀘스트 트리거 체크
|
||||
CheckTrigger(Quest::TRIGGER_FAME, dwFame, GetCurrentPos(), 1);
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::SetDuelOpponent(CCharacter* lpCharacter)
|
||||
{
|
||||
const POS DuelPos1 = { 3099, 1137, 3215 }; // 듀얼 스타트 위치
|
||||
const POS DuelPos2 = { 3156, 1137, 3209 };
|
||||
|
||||
if (CServerSetup::GetInstance().GetDuelModeCheck())
|
||||
{
|
||||
CCell* lpDuelCell = NULL;
|
||||
unsigned long Cell_ID = 0;
|
||||
|
||||
if (NULL == lpCharacter)
|
||||
{
|
||||
// 듀열 셀
|
||||
Cell_ID = m_FightInfo.m_dwCellID;
|
||||
|
||||
lpDuelCell = CDuelCellManager::GetInstance().GetCell(Cell_ID);
|
||||
if (NULL == lpDuelCell)
|
||||
{
|
||||
ERRLOG1(g_Log, "(CID:0x%08x) 듀얼 셀 얻기에 실패했습니다.", GetCID());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 듀얼 셀에서 제거
|
||||
lpDuelCell->DeleteCreature(m_dwCID);
|
||||
if (0 == lpDuelCell->GetCharacterNum())
|
||||
{
|
||||
// 셀 제거
|
||||
CDuelCellManager::GetInstance().DestroyCell(Cell_ID);
|
||||
}
|
||||
|
||||
// 정보 설정
|
||||
m_FightInfo.m_pDuelOpponent = lpCharacter;
|
||||
m_FightInfo.m_dwCellID = 0;
|
||||
|
||||
// 이전 셀로 복귀
|
||||
m_CellPos.MoveTo(m_CurrentPos);
|
||||
if (NULL == m_CellPos.m_lpCell)
|
||||
{
|
||||
ERRLOG1(g_Log, "(CID:0x%08x) 현재 셀 없음.", GetCID());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_CellPos.m_lpCell->SetCreature(m_dwCID, this);
|
||||
|
||||
GameClientSendPacket::SendCharBindPosition(*this, 0, 1,
|
||||
m_DBData.m_Pos.LastPoint, CServerSetup::GetInstance().GetServerZone(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
POS DuelPos = {0,};
|
||||
Cell_ID = lpCharacter->m_FightInfo.m_dwCellID;
|
||||
if (0 == Cell_ID)
|
||||
{
|
||||
Cell_ID = GetCID();
|
||||
|
||||
lpDuelCell = CDuelCellManager::GetInstance().CreateCell(Cell_ID);
|
||||
if (NULL == lpDuelCell)
|
||||
{
|
||||
ERRLOG0(g_Log, "듀얼 셀 생성에 실패했습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DuelPos = DuelPos1;
|
||||
}
|
||||
else
|
||||
{
|
||||
lpDuelCell = CDuelCellManager::GetInstance().GetCell(Cell_ID);
|
||||
if (NULL == lpDuelCell)
|
||||
{
|
||||
ERRLOG0(g_Log, "듀얼 셀 생성에 실패했습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DuelPos = DuelPos2;
|
||||
}
|
||||
|
||||
// 이전 셀에서 제거
|
||||
if (NULL == m_CellPos.m_lpCell)
|
||||
{
|
||||
ERRLOG1(g_Log, "(CID:0x%08x) 현재 셀 없음.", GetCID());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_CellPos.m_lpCell->DeleteCreature(GetCID());
|
||||
m_CellPos.m_lpCell = NULL;
|
||||
|
||||
// 듀얼 셀로 추가
|
||||
lpDuelCell->SetCreature(m_dwCID, this);
|
||||
GameClientSendPacket::SendCharBindPosition(*this, 0, 1, DuelPos, 100, 0);
|
||||
|
||||
// 정보 설정
|
||||
m_FightInfo.m_pDuelOpponent = lpCharacter;
|
||||
m_FightInfo.m_dwCellID = Cell_ID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FightInfo.m_pDuelOpponent = lpCharacter;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void CCharacter::SetGuildWarFlag(unsigned char cFlag)
|
||||
{
|
||||
if (cFlag >= Creature::WAR_OFF && cFlag <= Creature::WAR_INSTANCE)
|
||||
{
|
||||
m_cGuildWarFlag = cFlag;
|
||||
|
||||
// 길드원 정보 업데이트
|
||||
CGuild* lpGuild = Guild::CGuildMgr::GetInstance().GetGuild(GetGID());
|
||||
if (NULL != lpGuild)
|
||||
{
|
||||
lpGuild->UpdateMemberInfo(m_dwCID, m_cGuildWarFlag, Guild::TYPE_WARFLAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,818 @@
|
||||
#ifndef _CCHARACTER_H_
|
||||
#define _CCHARACTER_H_
|
||||
|
||||
#include <Item/Container/ItemContainer.h>
|
||||
#include <Item/Container/EquipmentsContainer.h>
|
||||
#include <Item/Container/ExchangeContainer.h>
|
||||
#include <Item/Container/DepositContainer.h>
|
||||
#include <Item/Container/StallContainer.h>
|
||||
#include <Item/Container/TempInvenContainer.h>
|
||||
#include <Item/Container/InventoryContainer.h>
|
||||
|
||||
#include <Network/Packet/PacketStruct/CharLoginOutPacketStruct.h>
|
||||
#include <Network/Packet/PacketStruct/CharItemPacketStruct.h>
|
||||
#include <Network/Packet/PacketStruct/CharCommunityPacketStruct.h>
|
||||
#include <Network/Packet/PacketStruct/CharStatusPacketStruct.h>
|
||||
#include <Network/Packet/PacketStruct/CharConfigPacketStruct.h>
|
||||
|
||||
#include <Creature/AggresiveCreature.h>
|
||||
#include <Creature/CreatureManager.h>
|
||||
|
||||
#include <Community/FriendList.h>
|
||||
#include <Community/BanList.h>
|
||||
|
||||
#include <Quest/QuestStruct.h>
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
#include "CharacterClass.h"
|
||||
|
||||
#include <Network/Broadcast/SerializeCharacterData.h>
|
||||
|
||||
#include <atltime.h>
|
||||
#include <GameTime/GameTimeMgr.h>
|
||||
#include <GameTime/GameTimeConstants.h>
|
||||
|
||||
|
||||
// 전방 참조
|
||||
class CChatPacket;
|
||||
class CGameClientDispatch;
|
||||
struct GiveItemInfo;
|
||||
|
||||
namespace Broadcast2nd
|
||||
{
|
||||
class CSerializeCharacterData;
|
||||
};
|
||||
|
||||
namespace Item
|
||||
{
|
||||
class CItem;
|
||||
class CEquipment;
|
||||
class CUseItem;
|
||||
|
||||
struct ItemPos;
|
||||
};
|
||||
|
||||
namespace BattleInclination
|
||||
{
|
||||
void SetCharData(CCreature& creature, BattleInclination::CharData& charData, bool bSetRideArmsInfo = false);
|
||||
void SetRelationData(CCreature& ownerCreature, CCreature& targetCreature, BattleInclination::RelationData& relationData);
|
||||
};
|
||||
|
||||
namespace RealmSkill
|
||||
{
|
||||
void RealmInchantAdd(CCharacter* lpCharacter);
|
||||
void RealmInchantRemove(CCharacter* lpCharacter);
|
||||
};
|
||||
|
||||
class CCharacter : public CAggresiveCreature
|
||||
{
|
||||
public:
|
||||
|
||||
// DB중계에서 테이블로 읽어서 TempInven으로 주어진 아이템의 CreationID를 저장한다.
|
||||
typedef std::vector<unsigned long> GivenItemList;
|
||||
|
||||
enum Const
|
||||
{
|
||||
LOGOUT_PULSE = 5,
|
||||
LOGOUT_COUNT = 5,
|
||||
|
||||
DBUPDATE_PULSE = 10,
|
||||
DBUPDATE_COUNT = 180,
|
||||
|
||||
BATTLE_GROUND_PULSE = 600,
|
||||
|
||||
RESPAWN_PULSE = 100, // 10 초
|
||||
|
||||
MELEE_ATTACK_RANGE = 10, // 캐릭터의 근접 공격 제한 거리
|
||||
RANGE_ATTACK_RANGE = 50 // 캐릭터의 원거리 공격 제한 거리
|
||||
};
|
||||
|
||||
enum OperationFlag
|
||||
{
|
||||
CHAR_INFO_LOADED = ( 1 << 0),
|
||||
CHAR_ZONE_MOVED = ( 1 << 1),
|
||||
MOVEZONE_PROCESSED = ( 1 << 2),
|
||||
SERVERZONE_PROCESSED = ( 1 << 3)
|
||||
};
|
||||
|
||||
enum DURABILITY_DECRESE_PERCENTAGE
|
||||
{
|
||||
// edith 아이템의 내구도 랜덤값
|
||||
WEAPON_DURABILITY_DECRESE_PERCENTAGE = 100, // 200
|
||||
ARMOUR_DURABILITY_DECRESE_PERCENTAGE = 25, // 50
|
||||
SHIELD_DURABILITY_DECRESE_PERCENTAGE = 10, // 20
|
||||
|
||||
STARTAXDURABILITY = 10, // 최대 내구도 감소폭 시작값
|
||||
DOWNMAXDURABILITY = 10 // 최대 내구도 감소폭
|
||||
};
|
||||
|
||||
enum DURABILITY_DECRESE_PERSENT
|
||||
{
|
||||
DURABILITY_DECREASE_PERSENT_BY_MONSTER = 20,
|
||||
DURABILITY_DECREASE_PERSENT_BY_SUICIDE = 3
|
||||
};
|
||||
|
||||
enum LAST_DEAD_TYPE
|
||||
{
|
||||
DEAD_BY_NONE,
|
||||
DEAD_BY_SUICIDE,
|
||||
DEAD_BY_MONSTER,
|
||||
DEAD_BY_CHARACTER
|
||||
};
|
||||
|
||||
enum RESPAWN_HP_MP_RECOVERY_PERCENTAGE
|
||||
{
|
||||
TOWN_RECOVERY_PERCENTAGE = 25,
|
||||
FORTRESS_RECOVERY_PERCENTAGE = 50,
|
||||
RESURRECTION_RECOVERY_PERCENTAGE = 75
|
||||
};
|
||||
|
||||
|
||||
bool Initialize(CGameClientDispatch* lpGameClientDispatch); // 초기화
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 캐릭터 로그인시 처리될 부분들 --------------------------------------------------------------
|
||||
|
||||
void PrepareLogin(void);
|
||||
bool Login(void); // 캐릭터 정보가 캐릭터 테이블에 로드됨(로그인함)
|
||||
|
||||
bool IsLogined(void) { return IsOperationFlagSet(CHAR_INFO_LOADED); }
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 캐릭터 로그아웃시 처리될 부분들 ------------------------------------------------------------
|
||||
|
||||
inline void ResetLogoutCount(void) { m_nLogoutCount = LOGOUT_COUNT; }
|
||||
inline bool StillAlive(void) { return --m_nLogoutCount >= 0; }
|
||||
inline void SetDBUpdateCount(LONG nDBUpdateCount) { m_nDBUpdateCount = nDBUpdateCount; }
|
||||
|
||||
// 캐릭터를 맵에서 로그아웃시키고, 데이터를 DB로 보낸다.
|
||||
bool Logout(DBUpdateData::UpdateType eUpdateType = DBUpdateData::LOGOUT);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 캐릭터 데이터 업데이트 - CharacterDataUpdate.cpp -------------------------------------------
|
||||
|
||||
bool GetCharacterInfo(char* pBuffer, int* nBufferSize_InOut, unsigned short* lpUpdateLen);
|
||||
bool SetCharacterInfo(char* pBuffer, unsigned short usUpdateLen[DBUpdateData::MAX_UPDATE_DB]);
|
||||
bool DBUpdate(DBUpdateData::UpdateType eUpdateType);
|
||||
bool DBUpdateForce(DBUpdateData::UpdateType eUpdateType)
|
||||
{
|
||||
m_nDBUpdateCount = -1;
|
||||
return DBUpdate(DBUpdateData::UPDATE);
|
||||
}
|
||||
|
||||
bool MoveZoneProcess(unsigned long dwServerID);
|
||||
bool ItemDump(char* pBuffer, int* nBufferSize_InOut) const;
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 아이템, 돈 이동 및 사용 관련 메소드 - CharacterItem.cpp ------------------------------------
|
||||
|
||||
inline unsigned long GetGold(void) const { return m_DBData.m_Info.Gold; }
|
||||
unsigned long GetGold(unsigned char cPos);
|
||||
bool AddGold(unsigned long dwGold, bool bNotice);
|
||||
bool DeductGold(unsigned long dwGold, bool bNotice);
|
||||
bool MoveGold(unsigned long dwGold, unsigned char cSrcPos,
|
||||
unsigned char cDstPos, unsigned short& usError); // 돈 옮기기
|
||||
|
||||
bool DeductMileage(unsigned long dwMileage, bool bNotice);
|
||||
|
||||
bool MoveItem(const TakeType takeType, bool bChk = true); // 아이템 이동 및 스택
|
||||
bool SwapItem(const TakeType SrcType, const TakeType DstType); // 아이템 위치 스왑
|
||||
Item::CItem* SplitItem(const TakeType takeType); // 아이템 개수 나누기
|
||||
|
||||
bool Pickup(Item::CItem* lpItem, Item::ItemPos dstPos); // 아이템 집기
|
||||
Item::CItem* Drop(Item::ItemPos SrcPos, unsigned char cNum); // 아이템 버리기
|
||||
|
||||
// 아이템을 인벤토리에 넣어준다. (자리가 없으면 임시 인벤토리에 넣는다.)
|
||||
bool GiveItem(Item::CItem* lpItem);
|
||||
bool TestItem(Item::CItem* lpItem); // 아이템을 넣을 수 있는지 체크한다.
|
||||
|
||||
bool UseLottery(unsigned short usItemID);
|
||||
bool UseCashItem(unsigned long dwSender, unsigned long dwReceiver, Item::ItemPos itemPos, unsigned short wCashType, unsigned short& wError);
|
||||
bool UseStartKit(unsigned short wObjectType, unsigned short& wError);
|
||||
|
||||
bool CheckItemRuneSkill(Item::ItemPos SrcPos, Item::ItemPos DstPos, bool bChk); // 아이템 이동시 룬 속성 체크
|
||||
|
||||
const Broadcast2nd::CNetworkEquipGrade GetNetworkEquipGrade(void); // 이펙트를 위한 장비 그레이드 정보
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 캐릭터 인벤토리 관련(아이템 Get, Set, Reset) - CharacterInventoryItem.cpp ------------------
|
||||
|
||||
public:
|
||||
|
||||
Item::CItem* GetItem(Item::ItemPos SrcPos);
|
||||
bool SetItem(Item::ItemPos SrcPos, Item::CItem* lpItemBase);
|
||||
bool RemoveItem(Item::ItemPos SrcPos);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 아이템 팔고 사고 수리하는 메서드 - CharacterTradeItem.cpp ----------------------------------
|
||||
|
||||
unsigned long RepairItem(const unsigned long dwNPCID, Item::ItemPos itemPos, unsigned short& wError);
|
||||
|
||||
unsigned long RepairAllItem(const unsigned long dwNPCID, unsigned short& wError);
|
||||
|
||||
Item::CItem* SellToCharacter(CCharacter *lpCustomer, unsigned short wKindItem, TakeType takeType,
|
||||
Item::CItem* lpRequestItem, unsigned long &dwPrice, unsigned short wCouponID, unsigned short &usError);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 스테이터스 관련 메소드 - CharacterStatus.cpp -----------------------------------------------
|
||||
|
||||
// 능력치 및 스테이터스 데이터를 DBData에서 읽어서 재계산. HP, MP가 Full이 된다.
|
||||
bool CalculateStatusData(bool bFullHPandMP);
|
||||
bool CalculateAbility(const SKILL& skill); // 어빌리티의 정보를 갱신한다.
|
||||
|
||||
bool ChangeClass(unsigned char cClassType); // CClass 참조
|
||||
bool IncrementExp(unsigned long dwExp); // 레벨업 상황이 되면 레벨업한다.
|
||||
bool GetHuntingExp(CAggresiveCreature* lpDeadCreature, unsigned long dwExp, unsigned char cMemberNum);
|
||||
|
||||
unsigned short AddState(unsigned char Type_In, ChState& ChState_Out);
|
||||
bool StateRedistribution(ChState& State); // 스탯을 해당 클래스의 처음 상태로 돌리는 함수
|
||||
bool StatusRetrain(ChState& State, Item::ItemPos InvenPos); // 스탯을 일정량 돌려받는 처리를 하는 함수 (망각의 돌 사용)
|
||||
|
||||
bool ChangeWeaponAndShield(unsigned char cSelect); // 무기 바꾸기
|
||||
bool ChangeRide(unsigned char cSelect); // 말 타기 내리기
|
||||
|
||||
bool CalculateEquipDurability(unsigned short wAttackType);
|
||||
unsigned char GetCurrentDurability(unsigned char cEquipmentIndex);
|
||||
bool CalculateAllEquipDurability(unsigned char cDecreasePersent);
|
||||
|
||||
// HP와 MP를 Regen한다. 추가 값이 0인 경우는 기본 값으로 Regen한다.
|
||||
virtual bool RegenHPAndMP(unsigned short usAdditionalHP, unsigned short usAdditionalMP, bool bAddDefaultRegenValue);
|
||||
|
||||
// Admin 명령으로 레벨을 다운 시킬때 사용하는 함수 (레벨 1의 초기 클래스로 만들어 버린다.)
|
||||
bool InitLevel1Char(unsigned char cClassType);
|
||||
|
||||
bool CheckRenameWarrant(Item::ItemPos InvenPos, bool bItemAccept);
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 전투 관련 메소드 - CharacterFight.cpp ------------------------------------------------------
|
||||
|
||||
virtual const int CalculateFixLevelGap(CAggresiveCreature *pDefender);
|
||||
|
||||
void Casting(AtType attackType, AtNode& attackNode);
|
||||
|
||||
bool AttackCID(AtType attackType, AtNode& attackNode, unsigned short& wError);
|
||||
bool Attack(AtType attackType, unsigned char cDefenderNum, CAggresiveCreature** pDefenders, unsigned char* cDefenserJudges, unsigned short* wDefenderMPHeal);
|
||||
bool AttackUsingBow(unsigned short wType);
|
||||
bool UseAmmo(Item::CUseItem* pUseItem);
|
||||
|
||||
virtual bool Dead(CAggresiveCreature* pOffencer);
|
||||
bool Respawn(unsigned char cType = 0, const Position& Pos = Position::Position());
|
||||
bool AutoRespawn(void); // 배틀 그라운드에서 초렙 존의 위치로 자동 리스폰
|
||||
|
||||
bool IsPeaceMode(void);
|
||||
EnemyCheck::EnemyType IsEnemy(CCreature* lpTarget, unsigned char* lpResult = NULL);
|
||||
|
||||
void SendAttackedToParty(unsigned long dwAttackerID, AtType atType,
|
||||
unsigned short wDamage, unsigned char cDefenserJudge, unsigned short wMPHeal);
|
||||
|
||||
char GetEliteBonus(void);
|
||||
char GetCaste(void);
|
||||
|
||||
void CalculateFame(CCharacter* lpWinnerCharacter);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 스킬 관련 메소드 - CharacterSkill.cpp ------------------------------------------------------
|
||||
|
||||
unsigned short ReadSkill(SKILLSLOT SkillSlot, unsigned short wSkillID, unsigned short wSkillLockCount);
|
||||
bool RedistributionSkill(void);
|
||||
bool CalculateMaxSkillSlot(void);
|
||||
|
||||
bool SkillCreate(Item::CUseItem* pUseItem);
|
||||
bool SkillErase(unsigned char Index_In, Item::ItemPos InvenPos);
|
||||
bool SkillLock(unsigned char Index_In);
|
||||
bool SkillUnLock(unsigned char Index_In, Item::ItemPos* InvenPos, bool b_SkillFifthUnlock = false);
|
||||
bool SkillFifthLock(unsigned short wSKilID);
|
||||
bool SkillFifthUnlock(unsigned short wSKilID);
|
||||
|
||||
virtual bool HasSkill(unsigned short usSkillType, unsigned char cLockCount, unsigned char cLevel);
|
||||
short GetSkillLockCount(unsigned short usSkillType);
|
||||
short GetSkillLevel(unsigned short usSkillType);
|
||||
short GetSkillSlotIndex(unsigned short usSkillType);
|
||||
|
||||
bool AddWorldWeaponEnchant(CAggresiveCreature* lpWeapon, unsigned char cNation);
|
||||
bool ClearWorldWeaponEnchant(void);
|
||||
|
||||
void CheckSkillVaild(void);
|
||||
|
||||
|
||||
bool AbilityCreate(Item::CUseItem* pUseItem);
|
||||
bool AbilityErase(unsigned char Index_In, Item::ItemPos InvenPos);
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 퀵슬롯 관련 메소드 - CharacterQuickSlot.cpp ------------------------------------------------
|
||||
|
||||
void UpdateQuickSlotSkill(SKILLSLOT Slot);
|
||||
bool MoveQuickSlot(const TakeType takeType, const unsigned short usSkillID, unsigned char cLockCount, unsigned char cSkillLevel);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 캐릭터 컨트롤 관련 메소드 - CharacterControl.cpp -------------------------------------------
|
||||
|
||||
bool DropItem(unsigned short usProtoTypeID, unsigned char cNum);
|
||||
|
||||
bool MovePos(Position Pos, char cZone, const bool bSitDown);
|
||||
bool MoveZone(POS NewPos, char cZone, char Channel);
|
||||
bool Kill(CAggresiveCreature* lpAttacker);
|
||||
|
||||
bool NotifyInfo(unsigned long dwAdminCID);
|
||||
|
||||
bool DuelInit(unsigned char cCmd);
|
||||
|
||||
// 길드전, 국가전에 해당하는 존으로 이동
|
||||
void MoveToGuildWarZone();
|
||||
void MoveToRealmWarZone();
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 캐릭터 퀘스트 관련 메소드 - CharacterQuest.cpp ---------------------------------------------
|
||||
|
||||
bool CheckQuest(Quest::QuestNode* lpQuestNode, unsigned short& wError);
|
||||
bool HasQuest(unsigned short wQuestID);
|
||||
bool GiveQuest(Quest::QuestNode* lpQuestNode);
|
||||
bool StartPhase(unsigned short wQuestID, unsigned char cPhase);
|
||||
bool CancelQuest(unsigned short wQuestID, bool bFinished = false);
|
||||
|
||||
void CheckTrigger(unsigned char cTriggerKind, unsigned long dwReferenceID, Position Pos, short wCount);
|
||||
|
||||
unsigned short OperateTrigger(unsigned short wQuestID, unsigned char cPhase, unsigned char cTrigger,
|
||||
unsigned char cCount, Position Pos, unsigned char cType = 0);
|
||||
|
||||
void PendingQuest(Quest::TriggerMsg* pTriggerMsg);
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
// 친구 정보 업데이트
|
||||
|
||||
void FriendInfoUpdate(unsigned long dwUID, unsigned long dwCID, unsigned long dwGID, unsigned short wClass, char cLevel, unsigned long dwServerID);
|
||||
|
||||
private:
|
||||
|
||||
Quest::ErrorCode ExecuteEvent(Quest::ExecutingQuest executingQuest, const Quest::TriggerNode* triggerNode, Position Pos);
|
||||
void EventEnd(unsigned short wQuestID, bool bSave);
|
||||
void EventAward(unsigned long dwExp, unsigned long dwGold, unsigned long dwFame, unsigned long dwMileage);
|
||||
|
||||
public:
|
||||
|
||||
// 스킬 관련 //
|
||||
void SkillClear();
|
||||
|
||||
bool ClearGarbage(vector<Item::ItemGarbage>& vecItemGarbage, unsigned char cCmd);
|
||||
|
||||
bool HasExecutingQuest(unsigned short wQuestID);
|
||||
bool HasHistoryQuest(unsigned short wQuestID);
|
||||
bool InsertHistoryQuest(unsigned short wQuestID);
|
||||
bool DeleteHistoryQuest(unsigned short wQuestID);
|
||||
|
||||
virtual void CalculateStatusByQuest(void);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 세션 관련 메소드 -------------------------------------------------------------------------
|
||||
|
||||
CGameClientDispatch* GetDispatcher(void) { return m_lpGameClientDispatch; }
|
||||
void SetDispatcher(CGameClientDispatch* lpGameClientDispatch);
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 기타 메소드 -------------------------------------------------------------------------------
|
||||
|
||||
bool GetCharacterView(CHAR_VIEW& charView);
|
||||
|
||||
// TODO : AggresiveCreature로 올리고 GetName으로 수정합시다. (Creature로 올려도 좋고...)
|
||||
const char* GetCharacterName() const { return m_DBData.m_Info.Name; }
|
||||
void SetCharacterName(const char* szName)
|
||||
{
|
||||
strncpy(m_DBData.m_Info.Name, szName, CHAR_INFOST::MAX_NAME_LEN);
|
||||
m_DBData.m_Info.Name[CHAR_INFOST::MAX_NAME_LEN - 1] = 0;
|
||||
}
|
||||
|
||||
const char* GetAccountName() const { return m_szAccountName; }
|
||||
void SetAccountName(const char* szAccountName)
|
||||
{
|
||||
strncpy(m_szAccountName, szAccountName, CHAR_INFOST::MAX_ACCOUNT_LEN);
|
||||
m_szAccountName[CHAR_INFOST::MAX_ACCOUNT_LEN - 1] = 0;
|
||||
}
|
||||
|
||||
bool BindPositionToNPC(const unsigned long dwNPCID); // 리스폰 위치를 NPC위치에 바인딩
|
||||
|
||||
bool ControlOption(const RejectOption Reject, bool bLogin); // 각종 옵션 조정
|
||||
|
||||
void UpgradeRespawnSpeedByEmblem(unsigned char cUpgradeType, unsigned char cUpgradeStep);
|
||||
void DegradeRespawnSpeedByEmblem();
|
||||
|
||||
virtual unsigned long GetUID(void) const { return m_dwUID; }
|
||||
|
||||
char GetConsumeMPCount(void) { return m_cConsumeMPCount; }
|
||||
unsigned short GetClass(void) { return m_DBData.m_Info.Class; }
|
||||
unsigned char GetLevel(void) { return m_DBData.m_Info.Level; }
|
||||
|
||||
CClass::RaceType GetRace(void) { return static_cast<CClass::RaceType>(m_DBData.m_Info.Race); }
|
||||
|
||||
unsigned char GetNation(void) const;
|
||||
// WORK_LIST 2.4 계정 국적을 게임서버의 캐릭터가 가지도록 구현
|
||||
void SetNation(unsigned char cNation) { m_cAccountNation = cNation; }
|
||||
unsigned long GetPID(void) const { return m_DBData.m_Info.PID; }
|
||||
void SetPID(unsigned long dwPID);
|
||||
unsigned long GetGID(void) const { return m_DBData.m_Info.GID; }
|
||||
void SetGID(unsigned long dwGID);
|
||||
|
||||
unsigned char GetHair(void) { return m_DBData.m_Info.Hair; }
|
||||
unsigned char GetFace(void) { return m_DBData.m_Info.Face; }
|
||||
unsigned char GetSex(void) { return m_DBData.m_Info.Sex; } // 0 : 남자, 1 : 여자
|
||||
unsigned char GetHand(void) { return m_cHandPos; }
|
||||
unsigned char GetRide(void) { return m_cRidePos; }
|
||||
|
||||
|
||||
ChState GetState(void);
|
||||
const SKILL& GetSkill(void) { return m_DBData.m_Skill; }
|
||||
|
||||
const unsigned long GetFame(void) { return m_DBData.m_Info.Fame; }
|
||||
void SetFame(unsigned long dwFame);
|
||||
const unsigned long GetMileage(void) { return m_DBData.m_Info.Mileage; }
|
||||
void SetMileage(unsigned long dwMileage) { m_DBData.m_Info.Mileage = dwMileage; }
|
||||
|
||||
bool IsAdmin(void) const { return (0 < m_DBData.m_cAdminLevel); }
|
||||
unsigned char GetAdminLevel() const { return m_DBData.m_cAdminLevel; }
|
||||
void SetAdminLevel(unsigned char cAdminLevel) { m_DBData.m_cAdminLevel = cAdminLevel; }
|
||||
|
||||
bool IsRideArms(void) const { return (0 != m_dwRideArmsCID); }
|
||||
unsigned long GetRideArmsCID(void) const { return m_dwRideArmsCID; }
|
||||
void Ride(unsigned long dwArmsID) { m_dwRideArmsCID = dwArmsID; }
|
||||
void GetOff(void) { m_dwRideArmsCID = 0; }
|
||||
|
||||
void SetProtectGateCID(unsigned long dwCID) { m_dwProtectGateCID = dwCID; }
|
||||
|
||||
const RejectOption& GetRejectOption(void) { return m_RejectOption; }
|
||||
|
||||
CAggresiveCreature* GetDuelOpponent(void) { return m_FightInfo.m_pDuelOpponent; }
|
||||
bool SetDuelOpponent(CCharacter* pCharacter);
|
||||
|
||||
CMonster* GetSummonee(void) { return m_lpSummonee; }
|
||||
void SetSummonee(CMonster* lpSummonee) { m_lpSummonee = lpSummonee; }
|
||||
|
||||
bool CanShout(void) const;
|
||||
void Shouted(void);
|
||||
|
||||
unsigned long GetLastTime() const { return m_dwLastTime; }
|
||||
|
||||
CFriendList& GetFriendList(void) { return m_friendList; }
|
||||
CBanList& GetBanList(void) { return m_banList; }
|
||||
|
||||
inline void SaveToDBData();
|
||||
inline Item::CItemContainer* GetItemContainer(unsigned char cPos);
|
||||
|
||||
Item::CInventoryContainer& GetInventory(void) { return m_Inventory; }
|
||||
Item::CEquipmentsContainer& GetEquipments(void) { return m_Equipments; }
|
||||
Item::CListContainer& GetExtra(void) { return m_ExtraSpace; }
|
||||
Item::CExchangeContainer& GetExchange(void) { return m_Exchange; }
|
||||
Item::CDepositContainer& GetDeposit(void) { return m_Deposit; }
|
||||
Item::CCharacterShopContainer& GetStall(void) { return m_Stall; }
|
||||
Item::CTempInvenContainer& GetTempInven(void) { return m_TempInven; }
|
||||
|
||||
// DB중계서버의 요청으로 아이템을 임시인벤에 넣어준다.
|
||||
bool GiveItemByDBAgentRequest(GiveItemInfo& giveItemInfo);
|
||||
|
||||
inline unsigned short* GetHistoryQuest(void) { return m_wHistoryQuest; }
|
||||
inline Quest::ExecutingQuest* GetExecutingQuest(void) { return m_ExecutingQuest; }
|
||||
|
||||
const FightStatus& GetEtcTypeStatus(Creature::StatusType eType) { return m_EtcTypeStatus[eType]; }
|
||||
|
||||
Broadcast2nd::CSerializeCharacterData& GetSerializeData(void) { return m_SerializeCharacterData; }
|
||||
|
||||
friend class Skill::CFunctions;
|
||||
|
||||
void SetOperationFlag(OperationFlag eOperationFlag) { m_cOperationFlags |= eOperationFlag; }
|
||||
void ResetOperationFlag(OperationFlag eOperationFlag) { m_cOperationFlags &= ~eOperationFlag; }
|
||||
bool IsOperationFlagSet(OperationFlag eOperationFlag) { return 0 != (m_cOperationFlags & eOperationFlag); }
|
||||
|
||||
unsigned long GetLastMoveUpdateTime(void) const { return m_dwLastUpdateExTime; }
|
||||
void SetLastMoveUpdateTime(unsigned long dwLastUpdateTime) { m_dwLastUpdateExTime = dwLastUpdateTime; }
|
||||
|
||||
unsigned char GetMoveUpdateCount(void) const { return m_cMoveUpdateExCount; }
|
||||
void IncrementMoveUpdateCount(void) { ++m_cMoveUpdateExCount; }
|
||||
|
||||
bool IsChatBan(void) const { return m_bChatBan; }
|
||||
void SetChatBan(bool bBan) { m_bChatBan = bBan; }
|
||||
|
||||
void SetGMModelFlag(unsigned char cGMModelFlag) { m_cGMModelFlag = cGMModelFlag; }
|
||||
unsigned char GetGMModelFlag() { return m_cGMModelFlag; }
|
||||
|
||||
unsigned char GetNameChangeCount() const { return m_cNameChangeCount; }
|
||||
void SetNameChangeCount(unsigned char cNameChangeCount) { m_cNameChangeCount = cNameChangeCount; }
|
||||
|
||||
unsigned char GetRealmWarFlag() const { return m_cRealmWarFlag; }
|
||||
unsigned char GetGuildWarFlag() const { return m_cGuildWarFlag; }
|
||||
void SetRealmWarFlag(unsigned char cFlag) { m_cRealmWarFlag = cFlag; }
|
||||
void SetGuildWarFlag(unsigned char cFlag);
|
||||
|
||||
unsigned char GetTacticsFlag() const { return m_cTactics; }
|
||||
void SetTacticsFlag(unsigned char cTactics) { m_cTactics = cTactics; }
|
||||
|
||||
// 국가 전쟁 공헌훈장 포인트.
|
||||
|
||||
virtual unsigned char GetRealmPoint() { return m_cRealmPoint; }
|
||||
|
||||
void SetRealmPoint(unsigned char cRealmPoint) { m_cRealmPoint = cRealmPoint; }
|
||||
|
||||
|
||||
int GetMagicChancePoint()
|
||||
{
|
||||
// 매찬 포인트 계상하는곳
|
||||
int iMCPoint = 0;
|
||||
|
||||
// edtih 2009.06.20 룬에의한 매직찬스값 추가
|
||||
iMCPoint = (int)m_EquipStatus.m_fLuckResistRate;
|
||||
|
||||
// if(GetRealmPoint() > 0)
|
||||
// iMCPoint = GetRealmPoint()*5; // 랠름포인트는 1개당 5퍼센트씩
|
||||
|
||||
// 매찬에 스틸핸드가 미치는 영향.
|
||||
if(GetEnchantInfo().GetFlag(Skill::SpellID::StealHand))
|
||||
{
|
||||
// 3-0부터 1래벨당 1퍼센트씩 매찬기능 추가
|
||||
int iLevel = GetEnchantLevel(Skill::SpellID::StealHand)-12;
|
||||
|
||||
if(iLevel > 0)
|
||||
iMCPoint += iLevel;
|
||||
}
|
||||
|
||||
return iMCPoint;
|
||||
}
|
||||
|
||||
|
||||
void SetPlayTime(unsigned int dwPlayTime)
|
||||
{
|
||||
m_StartTime = CTime::GetCurrentTime();
|
||||
m_dwPlayTime = dwPlayTime;
|
||||
}
|
||||
|
||||
unsigned int GetPlayTime() { return m_dwPlayTime; }
|
||||
|
||||
void SetPremiumService(long lPremiumTime, int iPremiumType)
|
||||
{
|
||||
// 프리미엄 타임 남은 시간
|
||||
m_StartPremiumTime = CTime::GetCurrentTime();
|
||||
m_lPremiumTime = lPremiumTime;
|
||||
m_iPremiumType = iPremiumType;
|
||||
}
|
||||
|
||||
long GetPremiumTime() { return m_lPremiumTime; }
|
||||
int GetPremiumType() { return m_iPremiumType; }
|
||||
|
||||
float GetPremiumPt()
|
||||
{
|
||||
if(m_iPremiumType == 1)
|
||||
return 0.5f;
|
||||
else if(m_iPremiumType == 2)
|
||||
return 0.3f;
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bool CheckPremiumTime()
|
||||
{
|
||||
if(m_lPremiumTime == 0)
|
||||
return false;
|
||||
|
||||
CTime nowTime = CTime::GetCurrentTime();
|
||||
CTimeSpan tm(m_StartPremiumTime.GetDay(), m_StartPremiumTime.GetHour(), m_StartPremiumTime.GetMinute(), m_StartPremiumTime.GetSecond());
|
||||
CTime tmPrev = nowTime-tm;
|
||||
|
||||
int iTime = (tmPrev.GetHour()*60)+tmPrev.GetMinute();
|
||||
|
||||
//
|
||||
iTime = m_lPremiumTime-iTime;
|
||||
|
||||
if(iTime <= 0)
|
||||
{
|
||||
m_iPremiumType = 0;
|
||||
m_lPremiumTime = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float GetAwardPer()
|
||||
{
|
||||
float fEventAdd = 0.0f;
|
||||
if (CGameTimeMgr::GetInstance().GetCurrentEventTime() == GameTime::EVENT_All20)
|
||||
fEventAdd = 0.2f;
|
||||
else if (CGameTimeMgr::GetInstance().GetCurrentEventTime() == GameTime::EVENT_All50)
|
||||
fEventAdd = 0.5f;
|
||||
|
||||
if(GameRYL::CHINA != CServerSetup::GetInstance().GetNationType())
|
||||
return 1.0f+fEventAdd;
|
||||
|
||||
if(m_dwPlayTime == 0)
|
||||
return 1.0f+fEventAdd;
|
||||
|
||||
CTime nowTime = CTime::GetCurrentTime();
|
||||
CTimeSpan tm(m_StartTime.GetDay(), m_StartTime.GetHour(), m_StartTime.GetMinute(), m_StartTime.GetSecond());
|
||||
CTime tmPrev = nowTime-tm;
|
||||
|
||||
int iTime = (tmPrev.GetHour()*60)+tmPrev.GetMinute();
|
||||
|
||||
iTime += m_dwPlayTime;
|
||||
/*
|
||||
// 테스트를 위해 플레이타음을 약간 바꿔준다.
|
||||
if(iTime <= 2)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
else if(2 < iTime && iTime <= 4)
|
||||
{
|
||||
return 0.5f;
|
||||
}
|
||||
*/
|
||||
if(iTime <= 180)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
else if(180 < iTime && iTime <= 300)
|
||||
{
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// 교환 관련 메소드.
|
||||
|
||||
unsigned long GetExchangeID() const { return m_dwExchangeID; }
|
||||
void SetExchangeID(unsigned long dwExchangeID) { m_dwExchangeID = dwExchangeID; }
|
||||
|
||||
SPELL GetSpell() { return m_DBData.m_Spell; }
|
||||
void SetSpell(SPELL spell) { m_DBData.m_Spell = spell; }
|
||||
|
||||
void SaveSpell(BOOL bDead = FALSE);
|
||||
|
||||
|
||||
// 길드 관련 메소드
|
||||
void SetGuildSafe(char cGuildSafe) { m_cGuildSafe = cGuildSafe; }
|
||||
char GetGuildSafe() { return m_cGuildSafe; }
|
||||
|
||||
void SetDead(BOOL bDead) { m_bDead = bDead; }
|
||||
BOOL IsDead() { return m_bDead; }
|
||||
|
||||
void SetRealmWarBuffer(BOOL bEnable) { m_bRealmWarBuffer = bEnable; }
|
||||
BOOL IsRealmWarBuffer() { return m_bRealmWarBuffer; }
|
||||
|
||||
// 리스폰 타운 정보 아이디
|
||||
void SetRespawnTownID(unsigned long dwTownID) { m_dwRespawnTownID = dwTownID; }
|
||||
unsigned long GetRespawnTownID() const { return m_dwRespawnTownID; }
|
||||
|
||||
void SetAdminAbilityPoint(int point) { m_iAdminAbilityPoint = point; }
|
||||
int GetAbilityPoint() { return m_iAbilityPoint; }
|
||||
int GetUseAbilityPoint() { return m_iUseAbilityPoint; }
|
||||
void UpdateUseAbilityPoint();
|
||||
|
||||
unsigned short GetAbilityValue(int nAbilityType)
|
||||
{
|
||||
if(nAbilityType >= Skill::Type::MAX_ABILITY_TYPE )
|
||||
return 0;
|
||||
|
||||
return m_AbilityValue[nAbilityType];
|
||||
}
|
||||
|
||||
|
||||
CCharacter(unsigned long dwCID, unsigned long dwSessionID);
|
||||
virtual ~CCharacter();
|
||||
|
||||
protected:
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_USING_MASTERY = 2, // 동시에 최대로 적용 가능한 마스터리수
|
||||
PAD_BYTE = 3 // byte alignment 를 맞추기 위한 배열의 크기
|
||||
};
|
||||
|
||||
CharacterFightInfo m_FightInfo; // 듀얼 정보
|
||||
// PeaceModeInfo m_PeaceMode; // 반전 모드 정보
|
||||
|
||||
Item::CInventoryContainer m_Inventory; // 인벤토리
|
||||
Item::CEquipmentsContainer m_Equipments; // 장비
|
||||
Item::CListContainer m_ExtraSpace; // 추가 공간
|
||||
Item::CExchangeContainer m_Exchange; // 교환창
|
||||
Item::CDepositContainer m_Deposit; // 창고
|
||||
Item::CCharacterShopContainer m_Stall; // 노점상
|
||||
Item::CTempInvenContainer m_TempInven; // 임시 인벤토리
|
||||
|
||||
GivenItemList m_GivenItemList; // DB중계로부터 받은 아이템들의 CreationID
|
||||
|
||||
CFriendList m_friendList; // 친구 리스트
|
||||
CBanList m_banList; // 거부 리스트
|
||||
|
||||
unsigned short m_wHistoryQuest[Quest::MAX_HISTORY_QUEST]; // 수행 완료한 퀘스트
|
||||
Quest::ExecutingQuest m_ExecutingQuest[Quest::MAX_EXECUTING_QUEST]; // 수행중인 퀘스트
|
||||
|
||||
CharacterDBData m_DBData; // DB에서 읽어 오는 데이터
|
||||
RejectOption m_RejectOption; // 각종 거부 옵션
|
||||
|
||||
FightStatus m_EtcTypeStatus[Creature::MAX_STATUS_TYPE]; // 특수한 경우에 사용되는 스탯들
|
||||
|
||||
unsigned long m_dwUID; // 유저 ID
|
||||
unsigned long m_dwSessionID; // 세션 ID - DB에 업데이트할 때 쓰인다.
|
||||
unsigned long m_dwLastShoutTime; // 마지막으로 외친 시간
|
||||
unsigned long m_dwLastSendPartyAttackInfoTime; // 마지막으로 파티원에게 공격당했다는 정보를 보낸 시간
|
||||
int m_nLogoutCount; // 로그아웃시까지 남은 카운트 수 저장 (0 이하가 될 수 있으므로 반드시 signed로!)
|
||||
int m_nDBUpdateCount; // DB Update때까지 남은 카운트 수 저장 (0 이하가 될 수 있으므로 반드시 signed로!)
|
||||
|
||||
unsigned long m_dwExchangeID; // 교환할 유저 ID.
|
||||
|
||||
unsigned char m_cGMModelFlag; // GM 모델로 변경 체크.
|
||||
|
||||
CGameClientDispatch* m_lpGameClientDispatch;
|
||||
|
||||
CMonster* m_lpSummonee;
|
||||
unsigned long m_dwLastUpdateExTime; // 마지막 이동 업데이트 시간
|
||||
unsigned long m_dwRideArmsCID; // 타고 있는 공성관련 병기의 CID
|
||||
unsigned long m_dwProtectGateCID; // 성문막기를 하고 있는 성문의 CID
|
||||
unsigned long m_dwRespawnSpeed; // 리스폰 속도 (pulse 사용)
|
||||
|
||||
LAST_DEAD_TYPE m_eLastDeadType; // 마지막에 어떻게 죽었는지 체크
|
||||
|
||||
Broadcast2nd::CSerializeCharacterData m_SerializeCharacterData; // 캐릭터 데이터 보관 및 변경된 데이터 보관
|
||||
|
||||
char m_szAccountName[CHAR_INFOST::MAX_ACCOUNT_LEN];
|
||||
|
||||
char m_cUsingMastery[MAX_USING_MASTERY]; // 적용중인 마스터리 종류
|
||||
char m_cConsumeMPCount; // MP 소모까지 남은 카운트 수 저장
|
||||
char m_cOperationFlags; // 각종 처리가 완료되었는지를 검사하는 루틴
|
||||
char m_cHandPos; // HandPos
|
||||
char m_cRidePos; // Ride Pos
|
||||
|
||||
char m_cMoveUpdateExCount; // 이동 업데이트 Count
|
||||
bool m_bChatBan; // 채팅 금지
|
||||
unsigned char m_cNameChangeCount; // 이름 바꿀 수 있는 회수
|
||||
|
||||
unsigned char m_cGuildWarFlag; // 길드 전쟁 참여 플래그
|
||||
unsigned char m_cRealmWarFlag; // 국가 전챙 참여 플래그
|
||||
unsigned char m_cTactics; // 용병 플레그.
|
||||
|
||||
char m_cGuildSafe; // edith 2008.03.15 길드 입출금 관련 플래스
|
||||
|
||||
unsigned char m_cRealmPoint; // 국가 전쟁 공헌훈장 포인트.
|
||||
|
||||
unsigned int m_dwPlayTime; // 중국판을 위한 게임 연속 플레이시간.
|
||||
CTime m_StartTime;
|
||||
|
||||
long m_lPremiumTime; // 프리미엄 서비스
|
||||
int m_iPremiumType;
|
||||
CTime m_StartPremiumTime;
|
||||
|
||||
BOOL m_bDead;
|
||||
BOOL m_bRealmWarBuffer;
|
||||
|
||||
unsigned long m_dwRespawnTownID;
|
||||
|
||||
// WORK_LIST 2.4 계정 국적을 게임서버의 캐릭터가 가지도록 구현
|
||||
unsigned char m_cAccountNation; // 계정 국적
|
||||
|
||||
char m_bPadding[PAD_BYTE]; // 4Byte Alignment를 맞추기 위한 도구
|
||||
|
||||
// edith 2009.10.22 어빌리티 작업중..
|
||||
int m_iAbilityPoint;
|
||||
int m_iUseAbilityPoint;
|
||||
int m_iAdminAbilityPoint; // 어드민이 임시적으로 주는 포인트 (기본 0, 값은 저장되지 않음)
|
||||
|
||||
// edith 2009.11.10 어빌리티 능력치 제어
|
||||
unsigned short m_AbilityValue[Skill::Type::MAX_ABILITY_TYPE];
|
||||
};
|
||||
|
||||
inline void CCharacter::SaveToDBData()
|
||||
{
|
||||
m_DBData.m_Info.Exp = m_CreatureStatus.m_nExp;
|
||||
m_DBData.m_Info.Level = m_CreatureStatus.m_nLevel;
|
||||
m_DBData.m_Info.HP = m_CreatureStatus.m_nNowHP;
|
||||
m_DBData.m_Info.MP = m_CreatureStatus.m_nNowMP;
|
||||
|
||||
m_DBData.m_Pos.LastPoint.fPointX = m_CurrentPos.m_fPointX;
|
||||
m_DBData.m_Pos.LastPoint.fPointY = m_CurrentPos.m_fPointY;
|
||||
m_DBData.m_Pos.LastPoint.fPointZ = m_CurrentPos.m_fPointZ;
|
||||
}
|
||||
|
||||
|
||||
inline Item::CItemContainer* CCharacter::GetItemContainer(unsigned char cPos)
|
||||
{
|
||||
switch(cPos)
|
||||
{
|
||||
case TakeType::TS_EQUIP: return &m_Equipments;
|
||||
case TakeType::TS_INVEN: return &m_Inventory;
|
||||
case TakeType::TS_TEMP: return &m_ExtraSpace;
|
||||
case TakeType::TS_EXTRA: return &m_ExtraSpace;
|
||||
case TakeType::TS_EXCHANGE: return &m_Exchange;
|
||||
case TakeType::TS_DEPOSIT: return &m_Deposit;
|
||||
case TakeType::TS_STALL: return &m_Stall;
|
||||
case TakeType::TS_TEMPINVEN: return &m_TempInven;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,670 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Creature/Character/CharacterStructure.h>
|
||||
#include <Network/Packet/PacketStruct/CharStatusPacketStruct.h>
|
||||
|
||||
#include "CharacterClass.h"
|
||||
#include "GMMemory.h"
|
||||
|
||||
|
||||
// 홀수 레벨일 때 증가량을 기준으로 작성.
|
||||
CClass ClassTable[CClass::MAX_CLASS] =
|
||||
{
|
||||
CClass(),
|
||||
CClass(CClass::Fighter, CClass::Fighter, CClass::DEFAULT_CLASS, CClass::HUMAN, CClass::STR, 1, CClass::CON, 1, false),
|
||||
CClass(CClass::Rogue, CClass::Rogue, CClass::DEFAULT_CLASS, CClass::HUMAN, CClass::DEX, 1, CClass::STR, 1, false),
|
||||
CClass(CClass::Mage, CClass::Mage, CClass::DEFAULT_CLASS, CClass::HUMAN, CClass::INT, 1, CClass::DEX, 1, false),
|
||||
CClass(CClass::Acolyte, CClass::Acolyte, CClass::DEFAULT_CLASS, CClass::HUMAN, CClass::WIS, 1, CClass::CON, 1, false),
|
||||
CClass(CClass::Defender, CClass::Fighter, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::STR, 2, CClass::CON, 1, true),
|
||||
CClass(CClass::Warrior, CClass::Fighter, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::STR, 2, CClass::CON, 1, false),
|
||||
CClass(CClass::Assassin, CClass::Rogue, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::DEX, 2, CClass::STR, 1, false),
|
||||
CClass(CClass::Archer, CClass::Rogue, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::DEX, 2, CClass::STR, 1, true),
|
||||
CClass(CClass::Sorcerer, CClass::Mage, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::INT, 2, CClass::DEX, 1, false),
|
||||
CClass(CClass::Enchanter, CClass::Mage, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::INT, 2, CClass::DEX, 1, true),
|
||||
CClass(CClass::Priest, CClass::Acolyte, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::WIS, 2, CClass::CON, 1, true),
|
||||
CClass(CClass::Cleric, CClass::Acolyte, CClass::JOB_CHANGE_1ST, CClass::HUMAN, CClass::WIS, 2, CClass::CON, 1, false),
|
||||
CClass(),
|
||||
CClass(),
|
||||
CClass(),
|
||||
CClass(),
|
||||
CClass(CClass::Combatant, CClass::Combatant, CClass::DEFAULT_CLASS, CClass::AKHAN, CClass::STR, 1, CClass::NONE_STAT, 0, false),
|
||||
CClass(CClass::Officiator, CClass::Officiator, CClass::DEFAULT_CLASS, CClass::AKHAN, CClass::DEX, 1, CClass::NONE_STAT, 0, false),
|
||||
CClass(CClass::Templar, CClass::Combatant, CClass::JOB_CHANGE_1ST, CClass::AKHAN, CClass::STR, 2, CClass::CON, 1, true),
|
||||
CClass(CClass::Attacker, CClass::Combatant, CClass::JOB_CHANGE_1ST, CClass::AKHAN, CClass::STR, 2, CClass::CON, 1, false),
|
||||
CClass(CClass::Gunner, CClass::Combatant, CClass::JOB_CHANGE_1ST, CClass::AKHAN, CClass::DEX, 2, CClass::STR, 1, true),
|
||||
CClass(CClass::RuneOff, CClass::Officiator, CClass::JOB_CHANGE_1ST, CClass::AKHAN, CClass::INT, 2, CClass::DEX, 1, false),
|
||||
CClass(CClass::LifeOff, CClass::Officiator, CClass::JOB_CHANGE_1ST, CClass::AKHAN, CClass::WIS, 2, CClass::DEX, 1, false),
|
||||
CClass(CClass::ShadowOff, CClass::Officiator, CClass::JOB_CHANGE_1ST, CClass::AKHAN, CClass::DEX, 2, CClass::STR, 1, false)
|
||||
};
|
||||
|
||||
|
||||
CClass::CClass(const JobType eJobType, const JobType ePrevJobType, const JobLevel eJobLevel, const RaceType eRace,
|
||||
const CClass::StatusType eType1, const unsigned char cIncrement1,
|
||||
const CClass::StatusType eType2, const unsigned char cIncrement2,
|
||||
const bool bLevelSwap)
|
||||
: m_eJobType(eJobType), m_ePrevJobType(ePrevJobType), m_eJobLevel(eJobLevel),
|
||||
m_eRace(eRace), m_bLevelSwap(bLevelSwap)
|
||||
{
|
||||
m_eIncrementType[0] = eType1; m_eIncrementType[1] = eType2;
|
||||
m_cIncrementValue[0] = cIncrement1; m_cIncrementValue[1] = cIncrement2;
|
||||
}
|
||||
|
||||
CClass::CClass()
|
||||
: m_eJobType(NONE_JOB), m_ePrevJobType(NONE_JOB),
|
||||
m_eJobLevel(DEFAULT_CLASS), m_eRace(MAX_RACE), m_bLevelSwap(false)
|
||||
{
|
||||
m_eIncrementType[0] = NONE_STAT; m_eIncrementType[1] = NONE_STAT;
|
||||
m_cIncrementValue[0] = 0; m_cIncrementValue[1] = 0;
|
||||
}
|
||||
|
||||
void CClass::LevelUp(CHAR_INFOST* InfoSt)
|
||||
{
|
||||
unsigned char cIndex1 = m_bLevelSwap ? ((1 == InfoSt->Level % 2) ? 0 : 1) : 0;
|
||||
unsigned char cIndex2 = m_bLevelSwap ? ((1 == InfoSt->Level % 2) ? 1 : 0) : 1;
|
||||
IncrementByType(InfoSt, 0, cIndex1);
|
||||
IncrementByType(InfoSt, 1, cIndex2);
|
||||
|
||||
if (AKHAN == GetRace(static_cast<unsigned char>(InfoSt->Class)))
|
||||
{
|
||||
if (JOB_CHANGE_1ST == GetJobLevel(static_cast<unsigned char>(InfoSt->Class)))
|
||||
{
|
||||
InfoSt->IP += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InfoSt->IP += 2;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char CClass::GetRequiredIP(unsigned short usClass, unsigned char cType)
|
||||
{
|
||||
if (usClass >= MAX_CLASS || cType >= MAX_TYPE) { return 0xFF; }
|
||||
|
||||
if (ClassTable[usClass].m_eJobLevel == JOB_CHANGE_1ST)
|
||||
{
|
||||
return (ClassTable[usClass].m_eIncrementType[0] == cType ||
|
||||
ClassTable[usClass].m_eIncrementType[1] == cType) ? 2 : 1;
|
||||
}
|
||||
|
||||
for (int nClassIndex = 0; nClassIndex < MAX_CLASS; nClassIndex++)
|
||||
{
|
||||
if (ClassTable[nClassIndex].m_eJobType == usClass)
|
||||
{
|
||||
if (ClassTable[nClassIndex].m_eIncrementType[0] == cType ||
|
||||
ClassTable[nClassIndex].m_eIncrementType[1] == cType)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool CClass::JobChange(CharacterDBData* DBData, unsigned char cClassType)
|
||||
{
|
||||
if (DBData->m_Info.Level < 10) { return false; }
|
||||
|
||||
switch (m_eJobLevel)
|
||||
{
|
||||
case DEFAULT_CLASS: return DowngradeClass(DBData, cClassType);
|
||||
case JOB_CHANGE_1ST: return UpgradeClass(DBData, cClassType);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// edith 2008.02.18 아칸전직시 스탯을 주는걸 인간과 동일, 아칸도 랩업당 +2의 스킬포인트주어지고 전직시에 주지 않는다.
|
||||
bool CClass::UpgradeClass(CharacterDBData* DBData, unsigned char cClassType)
|
||||
{
|
||||
if (DBData->m_Info.Class != ClassTable[cClassType].m_ePrevJobType ||
|
||||
ClassTable[DBData->m_Info.Class].m_eJobLevel != DEFAULT_CLASS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 환원되는 IP 계산을 위한 임시 변수들
|
||||
unsigned short StatusA = 0;
|
||||
unsigned short StatusB = 0;
|
||||
unsigned short StatusC = 0;
|
||||
|
||||
switch (cClassType)
|
||||
{
|
||||
// 인간
|
||||
case Defender:
|
||||
DBData->m_Info.STR += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f);
|
||||
DBData->m_Info.CON += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f + 0.5f);
|
||||
|
||||
StatusA = DBData->m_Info.STR - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.CON - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.DEX + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Defender;
|
||||
break;
|
||||
|
||||
case Warrior:
|
||||
DBData->m_Info.STR += DBData->m_Info.Level - 1;
|
||||
|
||||
StatusA = DBData->m_Info.STR - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.CON - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.DEX + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Warrior;
|
||||
break;
|
||||
|
||||
case Assassin:
|
||||
DBData->m_Info.DEX += DBData->m_Info.Level - 1;
|
||||
|
||||
StatusA = DBData->m_Info.DEX - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.STR - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.CON + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Assassin;
|
||||
break;
|
||||
|
||||
case Archer:
|
||||
DBData->m_Info.DEX += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f);
|
||||
DBData->m_Info.STR += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f + 0.5f);
|
||||
|
||||
StatusA = DBData->m_Info.DEX - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.STR - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.CON + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Archer;
|
||||
break;
|
||||
|
||||
case Sorcerer:
|
||||
DBData->m_Info.INT += DBData->m_Info.Level - 1;
|
||||
|
||||
StatusA = DBData->m_Info.INT - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.DEX - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.STR + DBData->m_Info.CON + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Sorcerer;
|
||||
break;
|
||||
|
||||
case Enchanter:
|
||||
DBData->m_Info.INT += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f);
|
||||
DBData->m_Info.DEX += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f + 0.5f);
|
||||
|
||||
StatusA = DBData->m_Info.INT - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.DEX - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.STR + DBData->m_Info.CON + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Enchanter;
|
||||
break;
|
||||
|
||||
case Priest:
|
||||
DBData->m_Info.WIS += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f);
|
||||
DBData->m_Info.CON += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f + 0.5f);
|
||||
|
||||
StatusA = DBData->m_Info.WIS - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.CON - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.STR + DBData->m_Info.DEX + DBData->m_Info.INT - 60;
|
||||
|
||||
DBData->m_Info.Class = Priest;
|
||||
break;
|
||||
|
||||
case Cleric:
|
||||
DBData->m_Info.WIS += DBData->m_Info.Level - 1;
|
||||
|
||||
StatusA = DBData->m_Info.WIS - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.CON - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.STR + DBData->m_Info.DEX + DBData->m_Info.INT - 60;
|
||||
|
||||
DBData->m_Info.Class = Cleric;
|
||||
break;
|
||||
|
||||
// 아칸
|
||||
case Templar:
|
||||
DBData->m_Info.STR += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f + 0.5f);
|
||||
DBData->m_Info.CON += static_cast<int>((DBData->m_Info.Level - 1) * 1.5f);
|
||||
DBData->m_Info.IP += (DBData->m_Info.Level - 1) * 2;
|
||||
|
||||
StatusA = DBData->m_Info.STR - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.CON - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.DEX + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Templar;
|
||||
break;
|
||||
|
||||
case Attacker:
|
||||
DBData->m_Info.STR += DBData->m_Info.Level - 1;
|
||||
DBData->m_Info.CON += DBData->m_Info.Level - 1;
|
||||
DBData->m_Info.IP += (DBData->m_Info.Level - 1) * 2;
|
||||
|
||||
StatusA = DBData->m_Info.STR - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.CON - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.DEX + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Attacker;
|
||||
break;
|
||||
|
||||
case Gunner:
|
||||
DBData->m_Info.DEX += static_cast<int>((DBData->m_Info.Level - 1) * 1.5f);
|
||||
DBData->m_Info.STR += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f + 0.5f);
|
||||
DBData->m_Info.IP += (DBData->m_Info.Level - 1) * 2;
|
||||
|
||||
StatusA = DBData->m_Info.DEX - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.STR - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.CON + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = Gunner;
|
||||
break;
|
||||
|
||||
case RuneOff:
|
||||
DBData->m_Info.INT += (DBData->m_Info.Level - 1) * 2;
|
||||
DBData->m_Info.IP += (DBData->m_Info.Level - 1) * 2;
|
||||
|
||||
StatusA = DBData->m_Info.INT - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.DEX - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.STR + DBData->m_Info.CON + DBData->m_Info.WIS - 60;
|
||||
|
||||
DBData->m_Info.Class = RuneOff;
|
||||
break;
|
||||
|
||||
case LifeOff:
|
||||
DBData->m_Info.WIS += (DBData->m_Info.Level - 1) * 2;
|
||||
DBData->m_Info.IP += (DBData->m_Info.Level - 1) * 2;
|
||||
|
||||
StatusA = DBData->m_Info.WIS - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.DEX - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.STR + DBData->m_Info.CON + DBData->m_Info.INT - 60;
|
||||
|
||||
DBData->m_Info.Class = LifeOff;
|
||||
break;
|
||||
|
||||
case ShadowOff:
|
||||
DBData->m_Info.STR += DBData->m_Info.Level - 1;
|
||||
DBData->m_Info.DEX += DBData->m_Info.Level - 1;
|
||||
|
||||
StatusA = DBData->m_Info.DEX - (20 + (DBData->m_Info.Level - 1) * 2);
|
||||
StatusB = DBData->m_Info.STR - (20 + DBData->m_Info.Level - 1);
|
||||
StatusC = DBData->m_Info.CON + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
|
||||
/*
|
||||
DBData->m_Info.STR += static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f);
|
||||
DBData->m_Info.DEX += static_cast<int>((DBData->m_Info.Level - 1) / 2.0f);
|
||||
DBData->m_Info.IP += (DBData->m_Info.Level - 1) * 2;
|
||||
|
||||
StatusA = DBData->m_Info.STR - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f));
|
||||
StatusB = DBData->m_Info.DEX - (20 + static_cast<int>((DBData->m_Info.Level - 1) * 1.5f + 0.5f));
|
||||
StatusC = DBData->m_Info.CON + DBData->m_Info.INT + DBData->m_Info.WIS - 60;
|
||||
*/
|
||||
DBData->m_Info.Class = ShadowOff;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
DBData->m_Info.IP += 10 + (DBData->m_Info.Level - 1) * 2 - ((StatusA + StatusB) * 2 + StatusC) - DBData->m_Info.IP;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CClass::DowngradeClass(CharacterDBData* DBData, unsigned char cClassType)
|
||||
{
|
||||
// 현재는 거너만 가능하다.
|
||||
if (Gunner != DBData->m_Info.Class) { return false; }
|
||||
|
||||
if (cClassType != ClassTable[DBData->m_Info.Class].m_ePrevJobType ||
|
||||
ClassTable[DBData->m_Info.Class].m_eJobLevel != JOB_CHANGE_1ST)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 레벨이 10% 감소한다.
|
||||
DBData->m_Info.Level -= static_cast<char>(DBData->m_Info.Level * 0.1f);
|
||||
DBData->m_Info.Level = max(DBData->m_Info.Level, 10);
|
||||
DBData->m_Info.Exp = 0;
|
||||
|
||||
// 명성치가 0이 된다.
|
||||
DBData->m_Info.Fame = 0;
|
||||
|
||||
// 스탯이 초기화된다.
|
||||
switch (cClassType)
|
||||
{
|
||||
case Combatant:
|
||||
DBData->m_Info.STR = 20 + DBData->m_Info.Level - 1;
|
||||
DBData->m_Info.DEX = 20;
|
||||
DBData->m_Info.CON = 20;
|
||||
DBData->m_Info.INT = 20;
|
||||
DBData->m_Info.WIS = 20;
|
||||
|
||||
DBData->m_Info.Class = Combatant;
|
||||
DBData->m_Info.IP = 10;
|
||||
break;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
|
||||
// 스킬, 퀵슬롯 초기화
|
||||
DBData->m_Skill = SKILL::SKILL();
|
||||
DBData->m_Quick = QUICK::QUICK();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CClass::InitializeClass(CharacterDBData* DBData, unsigned char cClassType)
|
||||
{
|
||||
if (ClassTable[cClassType].m_eJobLevel != DEFAULT_CLASS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 레벨이 10% 감소한다.
|
||||
DBData->m_Info.Level = 1;
|
||||
DBData->m_Info.Exp = 0;
|
||||
|
||||
// 명성치가 0이 된다.
|
||||
DBData->m_Info.Fame = 0;
|
||||
|
||||
// 스탯이 초기화된다.
|
||||
DBData->m_Info.STR = 20;
|
||||
DBData->m_Info.DEX = 20;
|
||||
DBData->m_Info.CON = 20;
|
||||
DBData->m_Info.INT = 20;
|
||||
DBData->m_Info.WIS = 20;
|
||||
DBData->m_Info.IP = 10;
|
||||
DBData->m_Info.Class = cClassType;
|
||||
|
||||
// 스킬, 퀵슬롯 초기화
|
||||
DBData->m_Skill = SKILL::SKILL();
|
||||
DBData->m_Quick = QUICK::QUICK();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CClass::CheckState(ChState& State, unsigned char cLevel)
|
||||
{
|
||||
// unsigned short이면 해킹으로 인해 65535가 되면 검출할수 있는 방법이 없다. int로 바꾸자.
|
||||
// unsigned short wTIP = State.m_wIP;
|
||||
int wTIP = State.m_wIP;
|
||||
|
||||
switch (m_eJobType)
|
||||
{
|
||||
case Fighter:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Rogue:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Mage:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Acolyte:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20 - (cLevel - 1)) * 2;
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Defender:
|
||||
wTIP += (State.m_wSTR - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Warrior:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1) * 2) * 2;
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Assassin:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1) * 2) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Archer:
|
||||
wTIP += (State.m_wSTR - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wDEX - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Sorcerer:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20 - (cLevel - 1) * 2) * 2;
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Enchanter:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Priest:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Cleric:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20 - (cLevel - 1) * 2) * 2;
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Combatant:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > 10) { return false; }
|
||||
break;
|
||||
|
||||
case Officiator:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > 10) { return false; }
|
||||
break;
|
||||
|
||||
case Templar:
|
||||
wTIP += (State.m_wSTR - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Attacker:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1) * 2) * 2;
|
||||
wTIP += (State.m_wDEX - 20);
|
||||
wTIP += (State.m_wCON - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case Gunner:
|
||||
wTIP += (State.m_wSTR - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wDEX - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case RuneOff:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20 - (cLevel - 1) * 2) * 2;
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case LifeOff:
|
||||
wTIP += (State.m_wSTR - 20);
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20 - (cLevel - 1) * 2) * 2;
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
break;
|
||||
|
||||
case ShadowOff:
|
||||
wTIP += (State.m_wSTR - 20 - (cLevel - 1) * 1) * 2;
|
||||
wTIP += (State.m_wDEX - 20 - (cLevel - 1) * 2) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
/*
|
||||
wTIP += (State.m_wSTR - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f + 0.5f)) * 2;
|
||||
wTIP += (State.m_wDEX - 20 - static_cast<unsigned short>((cLevel - 1) * 1.5f)) * 2;
|
||||
wTIP += (State.m_wCON - 20);
|
||||
wTIP += (State.m_wINT - 20);
|
||||
wTIP += (State.m_wWIS - 20);
|
||||
|
||||
if (wTIP > (10 + (cLevel - 1) * 2)) { return false; }
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CClass::CheckMinState(ChState& State, unsigned char cLevel)
|
||||
{
|
||||
if (State.m_wSTR < GetMinState(STR, cLevel)) { return false; }
|
||||
if (State.m_wDEX < GetMinState(DEX, cLevel)) { return false; }
|
||||
if (State.m_wCON < GetMinState(CON, cLevel)) { return false; }
|
||||
if (State.m_wINT < GetMinState(INT, cLevel)) { return false; }
|
||||
if (State.m_wWIS < GetMinState(WIS, cLevel)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned short CClass::GetMinState(StatusType eType, unsigned char cLevel)
|
||||
{
|
||||
// 스탯 기본값
|
||||
unsigned short wResult = 20;
|
||||
|
||||
cLevel = cLevel - 1;
|
||||
|
||||
if (eType == m_eIncrementType[0] || eType == m_eIncrementType[1])
|
||||
{
|
||||
// 레벨에 관계없이 똑같이 성장하는 경우
|
||||
if (false == m_bLevelSwap)
|
||||
{
|
||||
if (eType == m_eIncrementType[0])
|
||||
{
|
||||
wResult += cLevel * m_cIncrementValue[0];
|
||||
}
|
||||
|
||||
if (eType == m_eIncrementType[1])
|
||||
{
|
||||
wResult += cLevel * m_cIncrementValue[1];
|
||||
}
|
||||
}
|
||||
// 레벨에 따라 스탯이 번갈아가며 성장하는 경우
|
||||
else
|
||||
{
|
||||
if (eType == m_eIncrementType[0])
|
||||
{
|
||||
wResult += static_cast<unsigned short>(cLevel * 1.5f);
|
||||
}
|
||||
|
||||
if (eType == m_eIncrementType[1])
|
||||
{
|
||||
wResult += static_cast<unsigned short>(cLevel * 1.5f + 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wResult;
|
||||
}
|
||||
|
||||
CClass::JobLevel CClass::GetJobLevel(unsigned char cClass)
|
||||
{
|
||||
if (cClass >= MAX_CLASS) { return DEFAULT_CLASS; }
|
||||
return ClassTable[cClass].m_eJobLevel;
|
||||
}
|
||||
|
||||
unsigned char CClass::GetPreviousJob(unsigned char cClass)
|
||||
{
|
||||
if (cClass >= MAX_CLASS) { return NONE_JOB; }
|
||||
return ClassTable[cClass].m_ePrevJobType;
|
||||
}
|
||||
|
||||
CClass::RaceType CClass::GetRace(unsigned char cClass)
|
||||
{
|
||||
if (cClass >= MAX_CLASS) { return MAX_RACE; }
|
||||
return ClassTable[cClass].m_eRace;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#ifndef _CHARACTER_CLASS_H_
|
||||
#define _CHARACTER_CLASS_H_
|
||||
|
||||
#include <DB/DBDefine.h>
|
||||
|
||||
|
||||
// 전방 참조
|
||||
struct CharacterDBData;
|
||||
struct ChState;
|
||||
|
||||
class CClass
|
||||
{
|
||||
public:
|
||||
|
||||
enum JobType
|
||||
{
|
||||
NONE_JOB = 0,
|
||||
|
||||
// 인간 기본클래스
|
||||
Fighter = 1, Rogue = 2,
|
||||
Mage = 3, Acolyte = 4,
|
||||
|
||||
// 인간 1st클래스
|
||||
Defender = 5, Warrior = 6,
|
||||
Assassin = 7, Archer = 8,
|
||||
Sorcerer = 9, Enchanter = 10,
|
||||
Priest = 11, Cleric = 12,
|
||||
|
||||
// 아칸 기본클래스
|
||||
Combatant = 17, Officiator = 18,
|
||||
|
||||
// 아칸 1st클래스
|
||||
Templar = 19, Attacker = 20,
|
||||
Gunner = 21, RuneOff = 22,
|
||||
LifeOff = 23, ShadowOff = 24,
|
||||
|
||||
MAX_CLASS = 25
|
||||
};
|
||||
|
||||
enum StatusType
|
||||
{
|
||||
NONE_STAT = 0,
|
||||
|
||||
STR = 1,
|
||||
DEX = 2,
|
||||
CON = 3,
|
||||
INT = 4,
|
||||
WIS = 5,
|
||||
|
||||
MAX_TYPE = 6
|
||||
};
|
||||
|
||||
enum JobLevel
|
||||
{
|
||||
NONE_CLASS = 0,
|
||||
DEFAULT_CLASS = 1,
|
||||
JOB_CHANGE_1ST = 2
|
||||
};
|
||||
|
||||
enum RaceType
|
||||
{
|
||||
HUMAN = 0,
|
||||
AKHAN = 1,
|
||||
MAX_RACE = 2
|
||||
};
|
||||
|
||||
CClass(const JobType eJobType, const JobType ePrevJobType, const JobLevel eJobLevel, const RaceType eRace,
|
||||
const StatusType eType1, const unsigned char cIncrement1,
|
||||
const StatusType eType2, const unsigned char cIncrement2,
|
||||
const bool bLevelSwap);
|
||||
|
||||
CClass();
|
||||
|
||||
void LevelUp(CHAR_INFOST* InfoSt);
|
||||
bool JobChange(CharacterDBData* DBData, unsigned char cClassType);
|
||||
bool UpgradeClass(CharacterDBData* DBData, unsigned char cClassType);
|
||||
bool DowngradeClass(CharacterDBData* DBData, unsigned char cClassType);
|
||||
bool InitializeClass(CharacterDBData* DBData, unsigned char cClassType); // 레벨 1의 기본 클래스로 만든다.
|
||||
|
||||
bool CheckState(ChState& State, unsigned char cLevel);
|
||||
bool CheckMinState(ChState& State, unsigned char cLevel);
|
||||
unsigned short GetMinState(StatusType eType, unsigned char cLevel);
|
||||
|
||||
static JobLevel GetJobLevel(unsigned char cClass);
|
||||
static unsigned char GetPreviousJob(unsigned char cClass);
|
||||
static unsigned char GetRequiredIP(unsigned short usClass, unsigned char cType);
|
||||
static RaceType GetRace(unsigned char cClass);
|
||||
|
||||
protected:
|
||||
|
||||
StatusType m_eIncrementType[2]; // 성장시 추가 능력치 타입
|
||||
unsigned char m_cIncrementValue[2]; // 성장시 추가 능력치 값
|
||||
|
||||
JobType m_eJobType; // 현재 직업
|
||||
JobType m_ePrevJobType; // 바로 하위 클래스의 직업
|
||||
JobLevel m_eJobLevel; // 현재 직업의 레벨
|
||||
RaceType m_eRace; // 소속 종족
|
||||
|
||||
bool m_bLevelSwap; // 레벨에 따라 스탯이 번갈아가면서 성장함 (짝수 2/1 성장이면 홀수 1/2 성장)
|
||||
|
||||
inline void IncrementByType(CHAR_INFOST* InfoSt,
|
||||
unsigned char cTypeIndex, unsigned char cIncrementIndex);
|
||||
};
|
||||
|
||||
extern CClass ClassTable[CClass::MAX_CLASS];
|
||||
|
||||
|
||||
inline void CClass::IncrementByType(CHAR_INFOST* InfoSt,
|
||||
unsigned char cTypeIndex, unsigned char cIncrementIndex)
|
||||
{
|
||||
switch(m_eIncrementType[cTypeIndex]) {
|
||||
case STR: InfoSt->STR += m_cIncrementValue[cIncrementIndex]; break;
|
||||
case DEX: InfoSt->DEX += m_cIncrementValue[cIncrementIndex]; break;
|
||||
case CON: InfoSt->CON += m_cIncrementValue[cIncrementIndex]; break;
|
||||
case INT: InfoSt->INT += m_cIncrementValue[cIncrementIndex]; break;
|
||||
case WIS: InfoSt->WIS += m_cIncrementValue[cIncrementIndex]; break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "stdafx.h"
|
||||
#include "Character.h"
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
|
||||
#include <Network/Packet/ChatPacket.h>
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
#include <Network/Packet/PacketStruct/CharCommunityPacket.h>
|
||||
|
||||
#include <mmsystem.h>
|
||||
|
||||
|
||||
bool CCharacter::CanShout() const
|
||||
{
|
||||
const long MAX_SHOUT_DELAY = 15000;
|
||||
unsigned long dwCurrentTime = timeGetTime();
|
||||
|
||||
return (0 == m_dwLastShoutTime || MAX_SHOUT_DELAY < dwCurrentTime - m_dwLastShoutTime);
|
||||
}
|
||||
|
||||
void CCharacter::Shouted()
|
||||
{
|
||||
m_dwLastShoutTime = timeGetTime();
|
||||
|
||||
if(0 == m_dwLastShoutTime)
|
||||
{
|
||||
++m_dwLastShoutTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Character.h"
|
||||
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharEtc.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharAttack.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharLoginOut.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharCommunity.h>
|
||||
#include <Network/Dispatch/DBAgent/DBAgentRequest.h>
|
||||
#include <Network/Dispatch/Chat/ChatDispatch.h>
|
||||
|
||||
#include <Network/Packet/ChatPacket.h>
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
#include <Network/Packet/PacketStruct/CharStatusPacket.h>
|
||||
#include <Network/Packet/PacketStruct/ServerInfo.h>
|
||||
|
||||
#include <Item/Item.h>
|
||||
#include <Item/ItemFactory.h>
|
||||
|
||||
#include <Map/FieldMap/Cell.h>
|
||||
#include <Map/FieldMap/CellManager.h>
|
||||
|
||||
#include <Community/Party/Party.h>
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/Character/CharRespawnMgr.h>
|
||||
#include <Log/CharacterLog.h>
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <utility/Setup/ServerSetup.h>
|
||||
|
||||
|
||||
bool CCharacter::DropItem(unsigned short usProtoTypeID, unsigned char cNum)
|
||||
{
|
||||
Item::CItem* lpItem = Item::CItemFactory::GetInstance().CreateItem(usProtoTypeID);
|
||||
if (NULL != lpItem)
|
||||
{
|
||||
lpItem->SetNumOrDurability(min(cNum, lpItem->GetMaxNumOrDurability()));
|
||||
|
||||
// edith 2009.08.19 DropItem 어드민 명령어로 장비 드랍시 시즌레코드 수정.
|
||||
Item::CEquipment* lpEquip = Item::CEquipment::DowncastToEquipment(lpItem);
|
||||
if(lpEquip && lpEquip->GetSeasonRecord() == 0)
|
||||
lpEquip->SetNewEquip();
|
||||
|
||||
if (false == GiveItem(lpItem))
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:%10u %d종류의 아이템을 떨구지 못했습니다.",
|
||||
GetCID(), usProtoTypeID);
|
||||
|
||||
DELETE_ITEM(lpItem);
|
||||
return false;
|
||||
}
|
||||
|
||||
// GievItem 으로 스택된 경우
|
||||
if (lpItem->IsSet(Item::DetailData::STACKABLE) && 0 == lpItem->GetNumOrDurability())
|
||||
{
|
||||
DELETE_ITEM(lpItem);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCharacter::MovePos(Position Pos, char cZone, const bool bSitDown)
|
||||
{
|
||||
if (0 != CCellManager::GetInstance().GetCell(m_CellPos.m_wMapIndex, Pos.PositionToPOS()))
|
||||
{
|
||||
DETLOG5(g_Log, "CID:%10u 워프 명령을 사용하였습니다. NewPos : %.1f, %.1f, %.1f, Zone : %d",
|
||||
m_dwCID, Pos.m_fPointX, Pos.m_fPointY, Pos.m_fPointZ, cZone);
|
||||
|
||||
// edith 2009.07.21 워프나 포탈 사용시 현재 좌표갱신하기 (불법이동 방지 검출 피하기 위해서)
|
||||
m_CurrentPos.m_fPointX = Pos.m_fPointX;
|
||||
m_CurrentPos.m_fPointZ = Pos.m_fPointZ;
|
||||
return GameClientSendPacket::SendCharBindPosition(*this, 0, PktBP::BP_WARP, Pos, cZone, 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharacter::MoveZone(POS NewPos, char cZone, char Channel)
|
||||
{
|
||||
if (IsOperationFlagSet(CCharacter::MOVEZONE_PROCESSED))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// BG_TODO : 배틀 그라운드 전용 서버군 작업시 추가해야 한다.
|
||||
/*
|
||||
if (cZone == SERVER_ID::ZONE3)
|
||||
{
|
||||
SYSTEMTIME systemTime;
|
||||
GetSystemTime(&systemTime);
|
||||
|
||||
// 배틀 그라운드 휴식 시간동안에는 진입을 할수 없다.
|
||||
if ((systemTime.wMinute >= CCreatureManager::STATUE_REST_TIME_1ST_START && systemTime.wMinute <= CCreatureManager::STATUE_REST_TIME_1ST_END) ||
|
||||
(systemTime.wMinute >= CCreatureManager::STATUE_REST_TIME_2ND_START && systemTime.wMinute <= CCreatureManager::STATUE_REST_TIME_2ND_END))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
CDBRequest DBRequest(*m_lpGameClientDispatch, 600);
|
||||
|
||||
if (DBRequest.IsValid())
|
||||
{
|
||||
if (GameClientSendPacket::SendMoveZoneToDBAgent(DBRequest.GetSendStream(),
|
||||
NewPos, DBRequest.GetRequestKey(), GetUID(), cZone, Channel))
|
||||
{
|
||||
char* szPeaceMode = (true == IsPeaceMode()) ? "평화모드" : "전쟁모드";
|
||||
|
||||
DETLOG5(g_Log, "UID:%d/CID:%10u(0x%p)/RequestKey:%d/DispatchPointer:0x%p 존 이동 명령을 사용하였습니다.",
|
||||
m_dwUID, m_dwCID, this, DBRequest.GetRequestKey(), m_lpGameClientDispatch);
|
||||
|
||||
DETLOG8(g_Log, "UID:%d/CID:%10u/ 존이동 당시의 정보입니다. NewPos : (%.1f, %.1f, %.1f), Zone : %d, Channel : %d (%s)",
|
||||
m_dwUID, m_dwCID, NewPos.fPointX, NewPos.fPointY, NewPos.fPointZ, cZone, Channel, szPeaceMode);
|
||||
|
||||
GAMELOG::LogZoneMove(*this, cZone, Channel, 0);
|
||||
|
||||
SetOperationFlag(CCharacter::MOVEZONE_PROCESSED);
|
||||
m_lpGameClientDispatch->PushRequestKey(DBRequest.GetRequestKey());
|
||||
return true;
|
||||
}
|
||||
|
||||
DBRequest.CancelRequest();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 길드전, 국가전에 해당하는 존으로 이동
|
||||
void CCharacter::MoveToGuildWarZone()
|
||||
{
|
||||
const Position targetPos = CCharRespawnMgr::GetInstance().GetTownRespawnPos(SERVER_ID::CAPITAL, GetNation());
|
||||
|
||||
if (CServerSetup::GetInstance().GetServerZone() == SERVER_ID::CAPITAL)
|
||||
{
|
||||
MoveTo(targetPos, false);
|
||||
|
||||
POS pos;
|
||||
pos.fPointX = targetPos.m_fPointX;
|
||||
pos.fPointY = targetPos.m_fPointY;
|
||||
pos.fPointZ = targetPos.m_fPointZ;
|
||||
|
||||
GameClientSendPacket::SendCharBindPosition(*this, 0, PktBP::BP_WARP, pos, CServerSetup::GetInstance().GetServerZone(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
POS pos;
|
||||
pos.fPointX = targetPos.m_fPointX;
|
||||
pos.fPointY = targetPos.m_fPointY;
|
||||
pos.fPointZ = targetPos.m_fPointZ;
|
||||
|
||||
MoveZone(pos, SERVER_ID::CAPITAL, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void CCharacter::MoveToRealmWarZone()
|
||||
{
|
||||
const int iLevel = GetLevel();
|
||||
int iZone = SERVER_ID::STONE_WAR1;
|
||||
|
||||
// edith 2009.06.13 고렙쟁과 저렙쟁 구분
|
||||
if(iLevel <= 60)
|
||||
iZone = SERVER_ID::STONE_WAR1;
|
||||
else //if(iLevel <= 80)
|
||||
iZone = SERVER_ID::STONE_WAR2;
|
||||
// else
|
||||
// iZone = SERVER_ID::STONE_WAR3;
|
||||
|
||||
const Position targetPos = CCharRespawnMgr::GetInstance().GetTownRespawnPos(iZone, GetNation());
|
||||
|
||||
|
||||
if (CServerSetup::GetInstance().GetServerZone() == iZone)
|
||||
{
|
||||
MoveTo(targetPos, false);
|
||||
|
||||
POS pos;
|
||||
pos.fPointX = targetPos.m_fPointX;
|
||||
pos.fPointY = targetPos.m_fPointY;
|
||||
pos.fPointZ = targetPos.m_fPointZ;
|
||||
|
||||
GameClientSendPacket::SendCharBindPosition(*this, 0, PktBP::BP_WARP, pos, CServerSetup::GetInstance().GetServerZone(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
POS pos;
|
||||
pos.fPointX = targetPos.m_fPointX;
|
||||
pos.fPointY = targetPos.m_fPointY;
|
||||
pos.fPointZ = targetPos.m_fPointZ;
|
||||
|
||||
MoveZone(pos, iZone, -1);
|
||||
}
|
||||
}
|
||||
|
||||
bool CCharacter::Kill(CAggresiveCreature* lpAttacker)
|
||||
{
|
||||
if (true == Dead(NULL))
|
||||
{
|
||||
AtType Type = {0,};
|
||||
Type.m_wType = 1;
|
||||
|
||||
const unsigned short wDamage = m_CreatureStatus.m_nNowHP;
|
||||
m_CreatureStatus.m_nNowHP = 0;
|
||||
|
||||
// 공격자가 NULL 이면 운영자가 다른 존에 있는 캐릭터를 운영자 명령으로 죽인 경우이다.
|
||||
// 이럴때는 자기가 죽인것으로 처리한다.
|
||||
if (NULL == lpAttacker) lpAttacker = this;
|
||||
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
return GameClientSendPacket::SendCharAttacked(m_lpGameClientDispatch->GetSendStream(),
|
||||
lpAttacker, this, Type, 0, wDamage, 0, 0, PktBase::NO_SERVER_ERR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharacter::NotifyInfo(unsigned long dwAdminCID)
|
||||
{
|
||||
char szMessage[PktChat::PktChatMaxSize] = "";
|
||||
|
||||
int nLen = _snprintf(szMessage, PktChat::PktChatMaxSize,
|
||||
"Name:%s, Class:%d, HP:%3d%%, MP:%3d%%, Zone:%d, X:%.1f, Y:%.1f, Z:%.1f, "
|
||||
"MinD:%d, MaxD:%d, HitRate:%d, Evade:%d, Armor:%d",
|
||||
|
||||
GetCharacterName(), m_DBData.m_Info.Class,
|
||||
m_CreatureStatus.m_nNowHP * 100 / m_CreatureStatus.m_StatusInfo.m_nMaxHP,
|
||||
m_CreatureStatus.m_nNowMP * 100 / m_CreatureStatus.m_StatusInfo.m_nMaxMP,
|
||||
CServerSetup::GetInstance().GetServerZone(), m_CurrentPos.m_fPointX, m_CurrentPos.m_fPointY, m_CurrentPos.m_fPointZ,
|
||||
|
||||
m_CreatureStatus.m_StatusInfo.m_lMinDamage, m_CreatureStatus.m_StatusInfo.m_lMaxDamage,
|
||||
m_CreatureStatus.m_StatusInfo.m_wHitRate,
|
||||
m_CreatureStatus.m_StatusInfo.m_wEvade,
|
||||
m_CreatureStatus.m_StatusInfo.m_wArmor);
|
||||
|
||||
if (0 < nLen)
|
||||
{
|
||||
szMessage[PktChat::PktChatMaxSize - 1] = 0;
|
||||
|
||||
// 채팅 서버로 정보를 보낸다.
|
||||
GET_SINGLE_DISPATCH(lpChatDispatch, CChatDispatch,
|
||||
CChatDispatch::GetDispatchTable());
|
||||
|
||||
if (0 != lpChatDispatch)
|
||||
{
|
||||
const Position& pos = GetCurrentPos();
|
||||
char strAdminCID[CHAR_INFOST::MAX_NAME_LEN] = "";
|
||||
strcpy(strAdminCID, "0x");
|
||||
char* strHexPos = (strAdminCID + 2);
|
||||
Math::Convert::Hex32ToStr(strHexPos, dwAdminCID);
|
||||
|
||||
CChatRequestPacket chatReqPacket(szMessage,
|
||||
PktChat::NOTIFY_CHAR_INFO, 0, GetUID(), GetCID(),
|
||||
static_cast<unsigned short>(pos.m_fPointX),
|
||||
static_cast<unsigned short>(pos.m_fPointY),
|
||||
static_cast<unsigned short>(pos.m_fPointZ),
|
||||
strAdminCID, 1);
|
||||
|
||||
if (chatReqPacket.IsValid())
|
||||
{
|
||||
return lpChatDispatch->GetSendStream().PutBuffer(
|
||||
chatReqPacket.GetCompressedPacket(),
|
||||
chatReqPacket.GetCompressedSize(), CmdCharChat);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCharacter::DuelInit(unsigned char cCmd)
|
||||
{
|
||||
// 듀얼중이라면 듀얼 취소
|
||||
CCharacter* lpDuelOpponent = static_cast<CCharacter*>(GetDuelOpponent());
|
||||
if (NULL != lpDuelOpponent)
|
||||
{
|
||||
CGameClientDispatch* lpOpponentDispatch = lpDuelOpponent->GetDispatcher();
|
||||
if (NULL != lpOpponentDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharDuelCmd(lpOpponentDispatch->GetSendStream(), m_dwCID,
|
||||
lpDuelOpponent->GetCID(), cCmd, PktDuC::NO_SERVER_ERR);
|
||||
}
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharDuelCmd(m_lpGameClientDispatch->GetSendStream(), m_dwCID,
|
||||
lpDuelOpponent->GetCID(), cCmd, PktDuC::NO_SERVER_ERR);
|
||||
}
|
||||
|
||||
lpDuelOpponent->SetDuelOpponent(NULL);
|
||||
SetDuelOpponent(NULL);
|
||||
}
|
||||
|
||||
// 팀배틀에서 빠짐
|
||||
if (NULL != m_pParty)
|
||||
{
|
||||
if (NULL != reinterpret_cast<CCharacterParty* >(m_pParty)->GetHostileParty())
|
||||
{
|
||||
if (PktDuC::DUC_CANCEL == cCmd ||
|
||||
0 == reinterpret_cast<CCharacterParty* >(m_pParty)->DropMember(this, static_cast<PktDuC::DuelCmd>(cCmd)))
|
||||
{
|
||||
reinterpret_cast<CCharacterParty* >(m_pParty)->GetHostileParty()->EndTeamBattle();
|
||||
reinterpret_cast<CCharacterParty* >(m_pParty)->EndTeamBattle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Item/Item.h>
|
||||
#include <Network/Packet/PacketStruct/ServerInfo.h>
|
||||
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
#include "CharacterClass.h"
|
||||
#include "CharacterCreate.h"
|
||||
|
||||
|
||||
bool CharCreate::CheckCharCreateName(const char *Name_In, bool HanCheck_In)
|
||||
{
|
||||
const unsigned short MIN_CHAR_NAME = 4;
|
||||
const unsigned short MAX_CHAR_NAME = 20;
|
||||
|
||||
if(Name_In == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 길이 제한
|
||||
size_t Len = strlen(Name_In);
|
||||
if(Len < MIN_CHAR_NAME || Len > MAX_CHAR_NAME)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LPBYTE CheckName = (LPBYTE)Name_In;
|
||||
|
||||
if(true == HanCheck_In)
|
||||
{
|
||||
// 제한 문자 검사
|
||||
int ACount = 0;
|
||||
for(unsigned short LCount = 0; LCount < Len; LCount++)
|
||||
{
|
||||
if((CheckName[LCount] & 0x80) == 0x80)
|
||||
{
|
||||
// 2Byte 문자 체크
|
||||
if(CheckName[LCount + 1] == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 허용 범위 체크 (한글)
|
||||
if(CheckName[LCount] < 0xB0 || CheckName[LCount] > 0xC9)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(CheckName[LCount + 1] < 0xA1 || CheckName[LCount + 1] > 0xFE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 한글 부분 불 허용
|
||||
for(ACount = 0; ACount < ALLOW_HAN_NUM; ACount++)
|
||||
{
|
||||
if(MAKEWORD(CheckName[LCount + 1], CheckName[LCount]) == AllowHans[ACount])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ACount != ALLOW_HAN_NUM)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LCount += 1;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 영문자 허용
|
||||
if((CheckName[LCount] >= 'A' && CheckName[LCount] <= 'Z') ||
|
||||
(CheckName[LCount] >= 'a' && CheckName[LCount] <= 'z'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 숫자 허용
|
||||
if(CheckName[LCount] >= '0' && CheckName[LCount] <= '9')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 특수 기호 부분 허용
|
||||
for(ACount = 0; ACount < ALLOW_LETTER_NUM; ACount++)
|
||||
{
|
||||
if(CheckName[LCount] == AllowLetters[ACount])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ACount == ALLOW_LETTER_NUM)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!strcmp(Name_In, ""))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// 영문, 숫자, 특수기호만 입력가능.
|
||||
// 제한 문자 검사
|
||||
int ACount = 0;
|
||||
for(unsigned short LCount = 0; LCount < Len; LCount++)
|
||||
{
|
||||
// 영문자 허용
|
||||
if((CheckName[LCount] >= 'A' && CheckName[LCount] <= 'Z') ||
|
||||
(CheckName[LCount] >= 'a' && CheckName[LCount] <= 'z'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 숫자 허용
|
||||
if(CheckName[LCount] >= '0' && CheckName[LCount] <= '9')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 특수 기호 부분 허용
|
||||
for(ACount = 0; ACount < ALLOW_LETTER_NUM; ACount++)
|
||||
{
|
||||
if(CheckName[LCount] == AllowLetters[ACount])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ACount == ALLOW_LETTER_NUM)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 특수기호 사용못함
|
||||
for(int LCount = 0; LCount < DISALLOW_LETTER_NUM; LCount++)
|
||||
{
|
||||
if(_tcschr(Name_In, DisAllowLetters[LCount]) != NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CharCreate::CheckCharCreateData(CHAR_CREATE &CharCreate_In)
|
||||
{
|
||||
if (CharCreate_In.Race == CClass::HUMAN)
|
||||
{
|
||||
if(CharCreate_In.Equip[Item::EquipmentPos::SHIRT] < 201 ||
|
||||
CharCreate_In.Equip[Item::EquipmentPos::SHIRT] > 209)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(601 != CharCreate_In.Equip[Item::EquipmentPos::BOOTS])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(CClass::Fighter == CharCreate_In.Class)
|
||||
{ // 전사
|
||||
if(701 != CharCreate_In.Equip[Item::EquipmentPos::WEAPON_HAND1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(CClass::Rogue == CharCreate_In.Class)
|
||||
{ // 로그
|
||||
if(1601 != CharCreate_In.Equip[Item::EquipmentPos::WEAPON_HAND1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(CClass::Mage == CharCreate_In.Class)
|
||||
{ // 메이지
|
||||
if(1501 != CharCreate_In.Equip[Item::EquipmentPos::WEAPON_HAND1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(CClass::Acolyte == CharCreate_In.Class)
|
||||
{ // 어콜라이트
|
||||
if(801 != CharCreate_In.Equip[Item::EquipmentPos::WEAPON_HAND1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(CharCreate_In.STR < 20 || CharCreate_In.CON < 20 ||
|
||||
CharCreate_In.DEX < 20 || CharCreate_In.INT < 20 || CharCreate_In.WIS < 20)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(CharCreate_In.STR + CharCreate_In.CON +
|
||||
CharCreate_In.DEX + CharCreate_In.INT + CharCreate_In.WIS > 105)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (CharCreate_In.Race == CClass::AKHAN)
|
||||
{
|
||||
if(CClass::Combatant == CharCreate_In.Class)
|
||||
{ // 컨배턴트
|
||||
if(5401 != CharCreate_In.Equip[Item::EquipmentPos::WEAPON])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(5101 != CharCreate_In.Equip[Item::EquipmentPos::BODY])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(5201 != CharCreate_In.Equip[Item::EquipmentPos::PELVIS])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(CClass::Officiator == CharCreate_In.Class)
|
||||
{ // 오피세이터
|
||||
if(5801 != CharCreate_In.Equip[Item::EquipmentPos::WEAPON])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(5110 != CharCreate_In.Equip[Item::EquipmentPos::BODY])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(5210 != CharCreate_In.Equip[Item::EquipmentPos::PELVIS])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(CharCreate_In.STR < 20 || CharCreate_In.CON < 20 ||
|
||||
CharCreate_In.DEX < 20 || CharCreate_In.INT < 20 || CharCreate_In.WIS < 20)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(CharCreate_In.STR + CharCreate_In.CON + CharCreate_In.DEX +
|
||||
CharCreate_In.INT + CharCreate_In.WIS > 105)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long CharCreate::GetDefaultStartGold(void)
|
||||
{
|
||||
return START_GOLD;
|
||||
}
|
||||
|
||||
POS CharCreate::GetDefaultCharacterPos(unsigned long dwRace, unsigned long dwRacePlayerNum)
|
||||
{
|
||||
if (true == CServerSetup::GetInstance().IsBattleAuthServer() ||
|
||||
true == CServerSetup::GetInstance().IsBattleGameServer())
|
||||
{
|
||||
POS StartPos = BGServerStartPos[dwRace][Math::Random::ComplexRandom(MAX_LOBBY_RESPAWN_POS)];
|
||||
StartPos.fPointX += Math::Random::SimpleRandom(GetTickCount(), 20) - 10;
|
||||
StartPos.fPointZ += Math::Random::SimpleRandom(GetTickCount(), 20) - 10;
|
||||
return StartPos;
|
||||
}
|
||||
|
||||
// 통합존의 생성 위치는 하나이다.
|
||||
unsigned long dwIndex = 0;
|
||||
|
||||
if (CClass::MAX_RACE <= dwRace)
|
||||
{
|
||||
dwRace = 0;
|
||||
}
|
||||
|
||||
for (; dwIndex < MAX_START_POS_NUM; ++dwIndex)
|
||||
{
|
||||
if (dwRacePlayerNum < StartPointVariation[dwIndex])
|
||||
{
|
||||
if (0 < dwIndex)
|
||||
{
|
||||
dwIndex = Math::Random::ComplexRandom(dwIndex + 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
POS startPos = StartPosNum[dwRace][dwIndex];
|
||||
|
||||
startPos.fPointX += static_cast<float>(Math::Random::ComplexRandom(14)) - 7.0f;
|
||||
startPos.fPointZ += static_cast<float>(Math::Random::ComplexRandom(14)) - 7.0f;
|
||||
|
||||
return startPos;
|
||||
}
|
||||
|
||||
|
||||
inline bool operator == (const POS& lhs, const POS& rhs)
|
||||
{
|
||||
return (lhs.fPointX == rhs.fPointX) && (lhs.fPointY == rhs.fPointY) && (lhs.fPointZ == rhs.fPointZ);
|
||||
}
|
||||
|
||||
inline bool operator != (const POS& lhs, const POS& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
|
||||
#include "CharacterClass.h"
|
||||
|
||||
namespace CharCreate
|
||||
{
|
||||
enum Const
|
||||
{
|
||||
START_GOLD = 0,
|
||||
MAX_START_POS_NUM = 6,
|
||||
|
||||
MAX_LOBBY_RESPAWN_POS = 2
|
||||
};
|
||||
|
||||
// 파트 1용 리젠포인트 갔다 써라.
|
||||
const POS StartPosNum[CClass::MAX_RACE][MAX_START_POS_NUM] = {
|
||||
// 휴먼
|
||||
{
|
||||
{ 2740, 23, 606 },
|
||||
{ 2738, 24, 647 },
|
||||
{ 2801, 22, 644 },
|
||||
{ 1442, 12, 1973 },
|
||||
{ 1464, 12, 2064 },
|
||||
{ 1419, 12, 2065 }
|
||||
},
|
||||
|
||||
// 아칸
|
||||
{
|
||||
{ 2446, 33, 3208 },
|
||||
{ 2370, 32, 3233 },
|
||||
{ 2324, 32, 3244 },
|
||||
{ 2059, 65, 1663 },
|
||||
{ 2014, 65, 1648 },
|
||||
{ 2009, 65, 1690 }
|
||||
}
|
||||
};
|
||||
/*
|
||||
const POS StartPosNum[CClass::MAX_RACE][MAX_START_POS_NUM] = {
|
||||
// 휴먼
|
||||
{
|
||||
{ 2812, 15, 2498 },
|
||||
{ 2817, 15, 2471 },
|
||||
{ 2792, 15, 2515 },
|
||||
{ 2792, 15, 2515 },
|
||||
{ 2792, 15, 2515 },
|
||||
{ 2792, 15, 2515 }
|
||||
},
|
||||
|
||||
// 아칸
|
||||
{
|
||||
{ 2812, 15, 2498 },
|
||||
{ 2834, 15, 2482 },
|
||||
{ 2804, 15, 2514 },
|
||||
{ 2804, 15, 2514 },
|
||||
{ 2804, 15, 2514 },
|
||||
{ 2804, 15, 2514 }
|
||||
}
|
||||
};
|
||||
*/
|
||||
const POS BGServerStartPos[CClass::MAX_RACE][MAX_LOBBY_RESPAWN_POS] = {
|
||||
// Human
|
||||
{
|
||||
{ 2165, 1135, 1005 },
|
||||
{ 1727, 1135, 1005 }
|
||||
},
|
||||
|
||||
// Akhan
|
||||
{
|
||||
{ 2119, 1132, 1841 },
|
||||
{ 1683, 1132, 1841 }
|
||||
}
|
||||
};
|
||||
|
||||
const unsigned long StartPointVariation[MAX_START_POS_NUM] = {
|
||||
500, 1000, 1500, 2000, 2500, 0xFFFFFFFF
|
||||
};
|
||||
|
||||
const unsigned short ALLOW_HAN_NUM = 39;
|
||||
const unsigned short AllowHans[ALLOW_HAN_NUM] = {
|
||||
'갉', '갊', '걺', '괆', '녠',
|
||||
'닒', '롼', '뢸', '룀', '룁',
|
||||
'룅', '뤠', '륄', '륌', '륏',
|
||||
'륑', '륩', '륫', '릊', '릍',
|
||||
'멂', '몲', '뭬', '뮴', '밗',
|
||||
'뱝', '뾔', '쓿', '줴', '쥣',
|
||||
'짢', '췽', '퀭', '푤', '푭',
|
||||
'풩', '핥', '홅', '흖'
|
||||
};
|
||||
|
||||
const unsigned short ALLOW_LETTER_NUM = 2;
|
||||
const char AllowLetters[ALLOW_LETTER_NUM] = {
|
||||
'-', '_'
|
||||
};
|
||||
|
||||
const unsigned short DISALLOW_LETTER_NUM = 9;
|
||||
const char DisAllowLetters[DISALLOW_LETTER_NUM] = {
|
||||
' ', '\'', '\"', '#', '~', '!', '@', '[', ']'
|
||||
};
|
||||
|
||||
|
||||
bool CheckCharCreateName(const char *Name_In, bool HanCheck_In);
|
||||
bool CheckCharCreateData(CHAR_CREATE &CharCreate_In);
|
||||
|
||||
unsigned long GetDefaultStartGold(void);
|
||||
POS GetDefaultCharacterPos(unsigned long dwRace, unsigned long dwRacePlayerNum);
|
||||
};
|
||||
@@ -0,0 +1,356 @@
|
||||
#include "stdafx.h"
|
||||
#include "Character.h"
|
||||
|
||||
#include <Community/Party/Party.h>
|
||||
#include <Community/Guild/GuildConstants.h>
|
||||
#include <Community/Guild/Guild.h>
|
||||
#include <Community/Guild/GuildMgr.h>
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <Log/LogCommands.h>
|
||||
#include <Log/CharacterLog.h>
|
||||
#include <Item/Container/ItemContainer.h>
|
||||
#include <Item/ItemFactory.h>
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
#include <Network/Packet/PacketStruct/CharLoginOutPacket.h>
|
||||
#include <Network/Dispatch/DBAgent/DBAgentRequest.h>
|
||||
#include <Network/Dispatch/DBAgent/DBAgentDispatch.h>
|
||||
#include <RylChatServer/ChatGameServerDispatch.h>
|
||||
#include <Network/Dispatch/Chat/ChatDispatch.h>
|
||||
|
||||
bool CCharacter::DBUpdate(DBUpdateData::UpdateType eUpdateType)
|
||||
{
|
||||
if(!IsOperationFlagSet(CHAR_INFO_LOADED) || (m_bLogout && DBUpdateData::LOGOUT != eUpdateType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(--m_nDBUpdateCount > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nDBUpdateCount = DBUPDATE_COUNT;
|
||||
|
||||
int nTotalSize = sizeof(PktDBUpdate) + DBUpdateData::MAX_DBUPDATE_SIZE;
|
||||
char szCharInfo[sizeof(PktDBUpdate) + DBUpdateData::MAX_DBUPDATE_SIZE];
|
||||
char* lpCharacterInfo = szCharInfo + sizeof(PktDBUpdate);
|
||||
|
||||
PktDBUpdate* lpPktDBUpdate = reinterpret_cast<PktDBUpdate*>(szCharInfo);
|
||||
memset(lpPktDBUpdate, 0, sizeof(PktDBUpdate));
|
||||
|
||||
unsigned short usLogError = 0;
|
||||
unsigned char cLogCMD = (DBUpdateData::LOGOUT == eUpdateType) ?
|
||||
GAMELOG::CMD::CHAR_LOGOUT : GAMELOG::CMD::CHAR_DBUPDATE;
|
||||
|
||||
unsigned char cAdmin = (true == IsAdmin()) ? 1 : 0;
|
||||
|
||||
if(!GetCharacterInfo(lpCharacterInfo, &nTotalSize, lpPktDBUpdate->m_usUpdate))
|
||||
{
|
||||
nTotalSize = 0; usLogError = 1;
|
||||
ERRLOG1(g_Log, "CID:0x%08x DBUpdate실패 : 데이터를 복사해 올 수 없습니다.", m_dwCID);
|
||||
}
|
||||
else
|
||||
{
|
||||
GET_SINGLE_DISPATCH(lpDBAgentDispatch,
|
||||
CDBAgentDispatch, CDBAgentDispatch::GetDispatchTable());
|
||||
|
||||
if (0 == lpDBAgentDispatch)
|
||||
{
|
||||
usLogError = 2;
|
||||
ERRLOG1(g_Log, "CID:0x%08x DBUpdate실패 : DBAgentDispatch 를 얻을 수 없습니다.", m_dwCID);
|
||||
}
|
||||
else
|
||||
{
|
||||
CSendStream& AgentSendStream = lpDBAgentDispatch->GetSendStream();
|
||||
|
||||
// 창고 데이터 업데이트(창고가 열려 있으면 업데이트한다.)
|
||||
// 순서 주의! DB에 Update할 때는 창고 업데이트 후 캐릭터를 업데이트한다.
|
||||
if(!m_Deposit.DBUpdate(AgentSendStream))
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:0x%08x 창고 업데이트 실패", m_dwCID);
|
||||
}
|
||||
|
||||
lpPktDBUpdate->m_dlItemSerial = Item::CItemFactory::GetInstance().GetItemUID();
|
||||
lpPktDBUpdate->m_dwSessionID = m_dwSessionID;
|
||||
lpPktDBUpdate->m_dwUserID = m_dwUID;
|
||||
lpPktDBUpdate->m_dwCharID = m_dwCID;
|
||||
lpPktDBUpdate->m_TypeCode = eUpdateType;
|
||||
|
||||
lpPktDBUpdate->m_dwRequestKey = 0;
|
||||
lpPktDBUpdate->m_Address.S_un.S_addr = 0;
|
||||
lpPktDBUpdate->m_cAdminLevel = 0;
|
||||
|
||||
// WORK_LIST 2.4 계정 국적을 게임서버의 캐릭터가 가지도록 구현
|
||||
lpPktDBUpdate->m_cAccountNation = 0;
|
||||
lpPktDBUpdate->m_cNameChangeCount = 0;
|
||||
lpPktDBUpdate->m_cGuildWarFlag = 0;
|
||||
lpPktDBUpdate->m_cRealmWarFlag = 0;
|
||||
lpPktDBUpdate->m_cRealmPoint = 0;
|
||||
lpPktDBUpdate->m_cTacticsFlag = 0;
|
||||
|
||||
if(!AgentSendStream.WrapCompress(reinterpret_cast<char*>(lpPktDBUpdate),
|
||||
static_cast<unsigned short>(sizeof(PktDBUpdate) + nTotalSize), CmdDBUpdateData, 0, 0))
|
||||
{
|
||||
usLogError = 3;
|
||||
ERRLOG1(g_Log, "CID:0x%08x DBUpdate실패 : WrapCompress를 실패했습니다.", m_dwCID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(DBUpdateData::LOGOUT == eUpdateType || 0 != usLogError)
|
||||
{
|
||||
SOCKADDR_IN remoteAddr;
|
||||
if (0 != m_lpGameClientDispatch)
|
||||
{
|
||||
remoteAddr = m_lpGameClientDispatch->GetRemoteAddr().get_addr_in();
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(&remoteAddr, 0, sizeof(SOCKADDR_IN));
|
||||
}
|
||||
|
||||
GAMELOG::LogCharLoginOut(m_dwUID, this, &remoteAddr, lpCharacterInfo, nTotalSize,
|
||||
lpPktDBUpdate->m_usUpdate, cLogCMD, usLogError);
|
||||
}
|
||||
|
||||
LOG_INOUT(
|
||||
|
||||
char szExp[64];
|
||||
const char* szUpdateType = "Unknown";
|
||||
|
||||
switch (eUpdateType)
|
||||
{
|
||||
case DBUpdateData::LOGIN: szUpdateType = "Login"; break;
|
||||
case DBUpdateData::LOGOUT: szUpdateType = "Logout"; break;
|
||||
case DBUpdateData::UPDATE: szUpdateType = "Update"; break;
|
||||
case DBUpdateData::ADMIN_LOGIN: szUpdateType = "AdminLogin"; break;
|
||||
case DBUpdateData::ZONEMOVE: szUpdateType = "ZoneMove"; break;
|
||||
}
|
||||
|
||||
unsigned long dwDispatchUID = (NULL != m_lpGameClientDispatch) ?
|
||||
m_lpGameClientDispatch->GetUID() : 0;
|
||||
|
||||
Math::Convert::Hex64ToStr(szExp, m_DBData.m_Info.Exp);
|
||||
DETLOG8(g_Log, "UID:%d/CID:0x%08x(0x%p)/DispatchUID:%d 의 캐릭터 정보를 DBAgent에 업데이트합니다. "
|
||||
" 업데이트 타입은 %s입니다. 몇가지 기본 정보를 찍습니다. %s(lev:%2d, exp:%s)",
|
||||
m_dwUID, m_dwCID, this, dwDispatchUID, szUpdateType, m_DBData.m_Info.Name,
|
||||
m_DBData.m_Info.Level, szExp)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::GetCharacterInfo(char* pBuffer, int* nBufferSize_InOut, unsigned short* lpUpdateLen)
|
||||
{
|
||||
unsigned long dwSize = 0;
|
||||
unsigned short usTotalSize = 0;
|
||||
|
||||
if(!IsOperationFlagSet(CHAR_INFO_LOADED))
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:0x%08x 캐릭터 정보가 세팅되지 않은 상태에서 캐릭터 정보를 얻으려 하였습니다.",
|
||||
m_dwCID);
|
||||
return false;
|
||||
}
|
||||
|
||||
SaveToDBData();
|
||||
|
||||
for (int nCount = 0; nCount < DBUpdateData::MAX_UPDATE_DB; ++nCount)
|
||||
{
|
||||
switch (nCount)
|
||||
{
|
||||
case DBUpdateData::STATUS_UPDATE:
|
||||
{
|
||||
*reinterpret_cast<CHAR_INFOST*>(pBuffer) = m_DBData.m_Info;
|
||||
lpUpdateLen[nCount] = sizeof(CHAR_INFOST);
|
||||
}
|
||||
break;
|
||||
|
||||
case DBUpdateData::POSITION_UPDATE:
|
||||
*reinterpret_cast<CHAR_POS*>(pBuffer) = m_DBData.m_Pos;
|
||||
lpUpdateLen[nCount] = sizeof(CHAR_POS);
|
||||
break;
|
||||
|
||||
case DBUpdateData::SKILL_UPDATE:
|
||||
*reinterpret_cast<SKILL*>(pBuffer) = m_DBData.m_Skill;
|
||||
lpUpdateLen[nCount] = sizeof(SKILL);
|
||||
break;
|
||||
|
||||
case DBUpdateData::QUICKSLOT_UPDATE:
|
||||
*reinterpret_cast<QUICK*>(pBuffer) = m_DBData.m_Quick;
|
||||
lpUpdateLen[nCount] = sizeof(QUICK);
|
||||
break;
|
||||
|
||||
case DBUpdateData::SPELL_UPDATE:
|
||||
*reinterpret_cast<SPELL*>(pBuffer) = m_DBData.m_Spell;
|
||||
lpUpdateLen[nCount] = sizeof(SPELL);
|
||||
break;
|
||||
|
||||
case DBUpdateData::ITEM_EQUIP_UPDATE:
|
||||
dwSize = *nBufferSize_InOut; m_Equipments.SerializeOut(pBuffer, dwSize);
|
||||
lpUpdateLen[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case DBUpdateData::ITEM_INVEN_UPDATE:
|
||||
dwSize = *nBufferSize_InOut; m_Inventory.SerializeOut(pBuffer, dwSize);
|
||||
lpUpdateLen[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case DBUpdateData::ITEM_EXTRA_UPDATE:
|
||||
dwSize = *nBufferSize_InOut; m_ExtraSpace.SerializeOut(pBuffer, dwSize);
|
||||
lpUpdateLen[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case DBUpdateData::ITEM_EXCHANGE_UPDATE:
|
||||
dwSize = *nBufferSize_InOut; m_Exchange.SerializeOut(pBuffer, dwSize);
|
||||
lpUpdateLen[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case DBUpdateData::ITEM_TEMPINVEN_UPDATE:
|
||||
dwSize = *nBufferSize_InOut; m_TempInven.SerializeOut(pBuffer, dwSize);
|
||||
lpUpdateLen[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
}
|
||||
|
||||
pBuffer += lpUpdateLen[nCount];
|
||||
usTotalSize += lpUpdateLen[nCount];
|
||||
*nBufferSize_InOut -= dwSize;
|
||||
}
|
||||
|
||||
*nBufferSize_InOut = usTotalSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool CCharacter::SetCharacterInfo(char* pBuffer, unsigned short usUpdateLen[DBUpdateData::MAX_UPDATE_DB])
|
||||
{
|
||||
for (int nCount = 0; nCount < DBUpdateData::MAX_UPDATE_DB; ++nCount)
|
||||
{
|
||||
unsigned long dwUpdateLen = usUpdateLen[nCount];
|
||||
|
||||
switch (nCount)
|
||||
{
|
||||
case DBUpdateData::STATUS_UPDATE: m_DBData.m_Info = *reinterpret_cast<CHAR_INFOST*>(pBuffer); break;
|
||||
case DBUpdateData::POSITION_UPDATE: m_DBData.m_Pos = *reinterpret_cast<CHAR_POS*>(pBuffer); break;
|
||||
case DBUpdateData::SKILL_UPDATE: m_DBData.m_Skill = *reinterpret_cast<SKILL*>(pBuffer); break;
|
||||
case DBUpdateData::QUICKSLOT_UPDATE: m_DBData.m_Quick = *reinterpret_cast<QUICK*>(pBuffer); break;
|
||||
case DBUpdateData::SPELL_UPDATE: m_DBData.m_Spell = *reinterpret_cast<SPELL*>(pBuffer); break;
|
||||
case DBUpdateData::ITEM_EQUIP_UPDATE: m_Equipments.SerializeIn(pBuffer, dwUpdateLen); break;
|
||||
case DBUpdateData::ITEM_INVEN_UPDATE: m_Inventory.SerializeIn(pBuffer, dwUpdateLen); break;
|
||||
case DBUpdateData::ITEM_EXTRA_UPDATE: m_ExtraSpace.SerializeIn(pBuffer, dwUpdateLen); break;
|
||||
case DBUpdateData::ITEM_EXCHANGE_UPDATE: m_Exchange.SerializeIn(pBuffer, dwUpdateLen); break;
|
||||
case DBUpdateData::ITEM_TEMPINVEN_UPDATE: m_TempInven.SerializeIn(pBuffer, dwUpdateLen); break;
|
||||
}
|
||||
|
||||
pBuffer += dwUpdateLen;
|
||||
}
|
||||
|
||||
m_CreatureStatus.Init(m_DBData.m_Info);
|
||||
m_CurrentPos.m_fPointX = m_DBData.m_Pos.LastPoint.fPointX;
|
||||
m_CurrentPos.m_fPointY = m_DBData.m_Pos.LastPoint.fPointY;
|
||||
m_CurrentPos.m_fPointZ = m_DBData.m_Pos.LastPoint.fPointZ;
|
||||
|
||||
return CalculateStatusData(false);
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::MoveZoneProcess(unsigned long dwServerID)
|
||||
{
|
||||
// 파티에 존이동했음을 보냄
|
||||
if (0 != m_pParty)
|
||||
{
|
||||
CCharacterParty* lpParty = static_cast<CCharacterParty*>(m_pParty);
|
||||
lpParty->SendPartyMemberDataToDBAgent(m_dwCID, 0, 0, dwServerID, 0,
|
||||
GetCharacterName(), PktDD::SCmdMoveZonePartyMem);
|
||||
}
|
||||
|
||||
// 친구 리스트에 존이동 메세지를 보낸다 //
|
||||
FriendInfoUpdate(GetUID(), GetCID(), GetGID(), GetClass(), GetLevel(), dwServerID);
|
||||
|
||||
m_bLogout = true;
|
||||
SetOperationFlag(CHAR_ZONE_MOVED);
|
||||
return Logout(DBUpdateData::ZONEMOVE);
|
||||
}
|
||||
|
||||
void CCharacter::FriendInfoUpdate(unsigned long dwUID, unsigned long dwCID, unsigned long dwGID, unsigned short wClass,
|
||||
char cLevel, unsigned long dwServerID)
|
||||
{
|
||||
// 친구 리스트에 업데이트 메세지를 보낸다 //
|
||||
|
||||
GET_SINGLE_DISPATCH(lpChatDispatch, CChatDispatch, CChatDispatch::GetDispatchTable());
|
||||
|
||||
if(lpChatDispatch)
|
||||
{
|
||||
char* lpBuffer = lpChatDispatch->GetSendStream().GetBuffer(sizeof(PktFriendDB));
|
||||
|
||||
if(lpBuffer)
|
||||
{
|
||||
PktFriendDB* lpPktFriendDB = reinterpret_cast<PktFriendDB*>(lpBuffer);
|
||||
|
||||
lpPktFriendDB->m_dwOwnerUID = dwUID;
|
||||
lpPktFriendDB->m_dwOwnerCID = dwCID;
|
||||
lpPktFriendDB->m_dwReferenceUID = 0;
|
||||
lpPktFriendDB->m_dwReferenceCID = 0;
|
||||
lpPktFriendDB->m_dwData = 0;
|
||||
lpPktFriendDB->m_cCmd = PktFriendDB::FRIEND_INFO_UPDATE;
|
||||
lpPktFriendDB->m_dwGID = dwGID;
|
||||
lpPktFriendDB->m_wClass = wClass;
|
||||
lpPktFriendDB->m_cLevel = cLevel;
|
||||
lpPktFriendDB->m_dwServerID = dwServerID;
|
||||
|
||||
lpChatDispatch->GetSendStream().WrapCrypt(sizeof(PktFriendDB), CmdFriendDB, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CCharacter::ItemDump(char* pBuffer, int* nBufferSize_InOut) const
|
||||
{
|
||||
using namespace GAMELOG;
|
||||
|
||||
sItemDump* lpItemDump = reinterpret_cast<sItemDump*>(pBuffer);
|
||||
char* lpItems = reinterpret_cast<char*>(&lpItemDump[1]);
|
||||
|
||||
std::fill_n(lpItemDump->m_usDataSize, int(sItemDump::MAX_DUMP), 0);
|
||||
|
||||
unsigned long dwSize = 0;
|
||||
unsigned short usTotalSize = sizeof(sItemDump);
|
||||
|
||||
for (int nCount = 0; nCount < sItemDump::MAX_DUMP; ++nCount)
|
||||
{
|
||||
switch (nCount)
|
||||
{
|
||||
case sItemDump::EQUIP_DUMP:
|
||||
dwSize = *nBufferSize_InOut;
|
||||
m_Equipments.SerializeOut(lpItems, dwSize);
|
||||
lpItemDump->m_usDataSize[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case sItemDump::INVEN_DUMP:
|
||||
dwSize = *nBufferSize_InOut;
|
||||
m_Inventory.SerializeOut(lpItems, dwSize);
|
||||
lpItemDump->m_usDataSize[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case sItemDump::EXTRA_DUMP:
|
||||
dwSize = *nBufferSize_InOut;
|
||||
m_ExtraSpace.SerializeOut(lpItems, dwSize);
|
||||
lpItemDump->m_usDataSize[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
|
||||
case sItemDump::EXCHANGE_DUMP:
|
||||
dwSize = *nBufferSize_InOut;
|
||||
m_Exchange.SerializeOut(lpItems, dwSize);
|
||||
lpItemDump->m_usDataSize[nCount] = static_cast<unsigned short>(dwSize);
|
||||
break;
|
||||
}
|
||||
|
||||
lpItems += lpItemDump->m_usDataSize[nCount];
|
||||
usTotalSize += lpItemDump->m_usDataSize[nCount];
|
||||
*nBufferSize_InOut -= dwSize;
|
||||
}
|
||||
|
||||
*nBufferSize_InOut = usTotalSize;
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Item/Container/ItemContainer.h>
|
||||
#include <Item/Container/EquipmentsContainer.h>
|
||||
#include <Item/Container/ExchangeContainer.h>
|
||||
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharItem.h>
|
||||
#include <Network/Packet/PacketStruct/CharCommunityPacket.h>
|
||||
|
||||
#include "Character.h"
|
||||
|
||||
|
||||
Item::CItem* CCharacter::GetItem(Item::ItemPos SrcPos)
|
||||
{
|
||||
switch (SrcPos.m_cPos)
|
||||
{
|
||||
case TakeType::TS_EQUIP: return m_Equipments.GetItem(SrcPos);
|
||||
case TakeType::TS_INVEN: return m_Inventory.GetItem(SrcPos);
|
||||
case TakeType::TS_TEMP: SrcPos.m_cIndex = Item::ExtraSpacePos::HOLDITEM_POS;
|
||||
case TakeType::TS_EXTRA: return m_ExtraSpace.GetItem(SrcPos);
|
||||
case TakeType::TS_EXCHANGE: return m_Exchange.GetItem(SrcPos);
|
||||
case TakeType::TS_DEPOSIT: return m_Deposit.GetItem(SrcPos);
|
||||
case TakeType::TS_STALL: return m_Stall.GetItem(SrcPos);
|
||||
case TakeType::TS_TEMPINVEN: return m_TempInven.GetItem(SrcPos);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool CCharacter::RemoveItem(Item::ItemPos SrcPos)
|
||||
{
|
||||
Item::CItem* lpItem = GetItem(SrcPos);
|
||||
if (NULL == lpItem)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:0x%08x 삭제할 아이템이 존재하지 않습니다. SrcPos:(%d/%d)",
|
||||
m_dwCID, SrcPos.m_cPos, SrcPos.m_cIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 노점상에서의 삭제가 아닌데 노점상 정보가 남아 있을 시... (잘못하면 서버가 죽을 수 있습니다.)
|
||||
if (TakeType::TS_STALL != SrcPos.m_cPos && TakeType::TS_STALL == lpItem->GetRealPos().m_cPos)
|
||||
{
|
||||
// RemoveItem() 으로 RealPos 가 바뀌므로 미리 저장해두고, 그 값으로 SendRemoveItem 에 보내준다. by Vincent
|
||||
Item::ItemPos realPos = lpItem->GetRealPos();
|
||||
|
||||
m_Stall.RemoveItem(lpItem->GetRealPos());
|
||||
unsigned char cNum = (lpItem->IsSet(Item::DetailData::STACKABLE)) ? lpItem->GetNumOrDurability() : 1;
|
||||
m_Stall.SendRemoveItem(TakeType::TakeType(realPos, realPos, cNum), PktStRI::SC_CANCEL, "");
|
||||
}
|
||||
|
||||
switch (SrcPos.m_cPos)
|
||||
{
|
||||
case TakeType::TS_EQUIP: return m_Equipments.RemoveItem(SrcPos);
|
||||
case TakeType::TS_INVEN: return m_Inventory.RemoveItem(SrcPos);
|
||||
case TakeType::TS_TEMP: SrcPos.m_cIndex = Item::ExtraSpacePos::HOLDITEM_POS;
|
||||
case TakeType::TS_EXTRA: return m_ExtraSpace.RemoveItem(SrcPos);
|
||||
case TakeType::TS_EXCHANGE: return m_Exchange.RemoveItem(SrcPos);
|
||||
case TakeType::TS_DEPOSIT: return m_Deposit.RemoveItem(SrcPos);
|
||||
|
||||
case TakeType::TS_STALL:
|
||||
{
|
||||
bool bResult = m_Stall.RemoveItem(SrcPos);
|
||||
if (true == bResult)
|
||||
{
|
||||
unsigned char cNum = (lpItem->IsSet(Item::DetailData::STACKABLE)) ? lpItem->GetNumOrDurability() : 1;
|
||||
m_Stall.SendRemoveItem(TakeType::TakeType(SrcPos, SrcPos, cNum), PktStRI::SC_CANCEL, "");
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
case TakeType::TS_TEMPINVEN: return m_TempInven.RemoveItem(SrcPos);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::SetItem(Item::ItemPos SrcPos, Item::CItem* lpItem)
|
||||
{
|
||||
switch (SrcPos.m_cPos)
|
||||
{
|
||||
case TakeType::TS_EQUIP: return m_Equipments.SetItem(SrcPos, lpItem);
|
||||
case TakeType::TS_INVEN: return m_Inventory.SetItem(SrcPos, lpItem);
|
||||
case TakeType::TS_TEMP: SrcPos.m_cIndex = Item::ExtraSpacePos::HOLDITEM_POS;
|
||||
case TakeType::TS_EXTRA: return m_ExtraSpace.SetItem(SrcPos, lpItem);
|
||||
case TakeType::TS_EXCHANGE: return m_Exchange.SetItem(SrcPos, lpItem);
|
||||
case TakeType::TS_DEPOSIT: return m_Deposit.SetItem(SrcPos, lpItem);
|
||||
case TakeType::TS_STALL: return m_Stall.SetItem(SrcPos, lpItem);
|
||||
case TakeType::TS_TEMPINVEN: return m_TempInven.SetItem(SrcPos, lpItem);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,911 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Log/LogCommands.h>
|
||||
#include <Log/CharacterLog.h>
|
||||
|
||||
#include <Utility/Math/Math.h>
|
||||
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
#include <Network/Packet/PacketStruct/ServerInfo.h>
|
||||
#include <Network/Packet/PacketStruct/CharAttackPacket.h>
|
||||
#include <Network/Packet/PacketStruct/CharItemPacket.h>
|
||||
#include <Network/Packet/PacketStruct/CharStatusPacket.h>
|
||||
#include <Network/Packet/PacketStruct/ServerLogPacket.h>
|
||||
|
||||
#include <Network/Dispatch/DBAgent/DBAgentRequest.h>
|
||||
#include <Network/Dispatch/DBAgent/DBAgentDispatch.h>
|
||||
#include <Network/Dispatch/DBAgent/DBAgentPacketParse.h>
|
||||
#include <Network/Dispatch/DBAgent/RegularAgentDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharLoginOut.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharAttack.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharCommunity.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharQuest.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharEtc.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharCastle.h>
|
||||
#include <Network/Dispatch/GameLog/SendLogPacket.h>
|
||||
|
||||
#include <Network/Dispatch/DBAgent/CastlePacketParse.h>
|
||||
|
||||
#include <Network/Dispatch/Chat/ChatDispatch.h>
|
||||
|
||||
#include <Map/FieldMap/Cell.h>
|
||||
#include <Map/FieldMap/VirtualArea/VirtualArea.h>
|
||||
#include <Map/FieldMap/VirtualArea/VirtualAreaMgr.h>
|
||||
#include <Map/FieldMap/CellManager.h>
|
||||
|
||||
#include <Item/ItemFactory.h>
|
||||
#include <Item/Container/ExchangeContainer.h>
|
||||
|
||||
#include <Skill/SkillTable.h>
|
||||
#include <Skill/Spell/SpellUtil.h>
|
||||
#include <Skill/Spell/SpellTable.h>
|
||||
#include <Skill/Spell/GlobalSpellMgr.h>
|
||||
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/Character/CharRespawnMgr.h>
|
||||
#include <Creature/Monster/Monster.h>
|
||||
#include <Creature/Character/ExpTable.h>
|
||||
#include <Creature/Siege/SiegeObject.h>
|
||||
#include <Creature/Siege/Camp.h>
|
||||
#include <Creature/Siege/CastleGate.h>
|
||||
#include <Creature/Siege/SiegeObjectMgr.h>
|
||||
|
||||
|
||||
#include <Castle/Castle.h>
|
||||
#include <Castle/CastleMgr.h>
|
||||
|
||||
#include <Community/Guild/GuildMgr.h>
|
||||
#include <Community/Guild/Guild.h>
|
||||
|
||||
#include <Community/Party/PartyMgr.h>
|
||||
|
||||
#include <GameTime/GameTimeConstants.h>
|
||||
#include <GameTime/GameTimeMgr.h>
|
||||
|
||||
#include "Character.h"
|
||||
#include "CharacterCreate.h"
|
||||
#include "SphereTree/CharSphereTree.h"
|
||||
|
||||
// forward decl.
|
||||
void CheckDuplicatedItem(CCharacter& character);
|
||||
|
||||
void SendItemDuplicatedLog(CSendStream& SendStream, unsigned __int64 dwItemSerial,
|
||||
Item::CItemOwnerInfo& itemOwnerInfo, unsigned long dwItemQty);
|
||||
|
||||
void CCharacter::PrepareLogin()
|
||||
{
|
||||
// HP/MP세팅
|
||||
m_CreatureStatus.m_nNowHP = m_DBData.m_Info.HP;
|
||||
m_CreatureStatus.m_nNowMP = m_DBData.m_Info.MP;
|
||||
|
||||
// 복사 아이템 검사
|
||||
CheckDuplicatedItem(*this);
|
||||
|
||||
// edith 2009.02.07 석상전 위치 내의 지역에서 로그인하는거 방지
|
||||
if (SERVER_ID::STONE_WAR1 <= CServerSetup::GetInstance().GetServerZone() && CServerSetup::GetInstance().GetServerZone() <= SERVER_ID::STONE_WAR3)
|
||||
{
|
||||
// 좌표 검사
|
||||
if (!CCellManager::GetInstance().CheckPositionInZone(m_CurrentPos))
|
||||
{
|
||||
if (CServerSetup::GetInstance().IsBattleGameServer())
|
||||
{
|
||||
m_CurrentPos = CharCreate::BGServerStartPos[GetRace()][Math::Random::ComplexRandom(CharCreate::MAX_LOBBY_RESPAWN_POS)];
|
||||
m_CurrentPos.m_fPointX += Math::Random::SimpleRandom(GetTickCount(), 20) - 10;
|
||||
m_CurrentPos.m_fPointZ += Math::Random::SimpleRandom(GetTickCount(), 20) - 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CurrentPos = CCharRespawnMgr::GetInstance().GetDefaultRespawnPos(GetNation());
|
||||
}
|
||||
|
||||
m_DBData.m_Pos.LastPoint.fPointX = m_CurrentPos.m_fPointX;
|
||||
m_DBData.m_Pos.LastPoint.fPointY = m_CurrentPos.m_fPointY;
|
||||
m_DBData.m_Pos.LastPoint.fPointZ = m_CurrentPos.m_fPointZ;
|
||||
}
|
||||
}
|
||||
|
||||
// 브로드캐스팅 데이터 준비
|
||||
m_SerializeCharacterData.PrepareData(*this);
|
||||
m_SerializeCharacterData.ClearDeltaData();
|
||||
|
||||
SetOperationFlag(CHAR_INFO_LOADED);
|
||||
}
|
||||
|
||||
bool CCharacter::Login()
|
||||
{
|
||||
if (NULL == m_lpGameClientDispatch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CSendStream& SendStream = m_lpGameClientDispatch->GetSendStream();
|
||||
/*
|
||||
#ifndef NO_GAMEGUARD
|
||||
// 로그인 하기전에 게임가드 처리
|
||||
// edith 2009.08.11 게임가드 테스트
|
||||
if (NULL != m_lpGameClientDispatch)// && 0 == GetAdminLevel())
|
||||
{
|
||||
// edith 2009.08.11 게임가드 2.5 업그레이드
|
||||
// if (false == m_lpGameClientDispatch->IsAuth())
|
||||
// {
|
||||
// ERRLOG1(g_Log, "CID:0x%08x 게임 가드 인증 코드를 보내지않아 연결을 끊습니다.", GetCID());
|
||||
// m_lpGameClientDispatch->Disconnect();
|
||||
// return false;
|
||||
// }
|
||||
|
||||
GG_AUTH_DATA* lpAuthData = NULL;
|
||||
if (false == m_lpGameClientDispatch->GetAuthQuery(&lpAuthData))
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:0x%08x 게임 가드 인증 코드(2) 체크에 실패하여 연결을 끊습니다.", GetCID());
|
||||
m_lpGameClientDispatch->Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
GameClientSendPacket::SendCSAuth(SendStream,
|
||||
GetCID(), m_lpGameClientDispatch->GetAuthCode(), lpAuthData, PktBase::NO_SERVER_ERR);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
|
||||
|
||||
// DELETE_ME : 더이상 클라이언트가 반전 모드로의 변경은 불가능하다. 개념만 남아있을 뿐. (2005-05-31 by 로딘)
|
||||
/*
|
||||
// 전쟁 모드 제한
|
||||
if (SERVER_ID::ZONE12 == CServerSetup::GetInstance().GetServerZone() ||
|
||||
SERVER_ID::CAPITAL == CServerSetup::GetInstance().GetServerZone())
|
||||
{
|
||||
m_PeaceMode.m_bPeace = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_PeaceMode.m_bPeace = false;
|
||||
}
|
||||
|
||||
SetPeaceMode(m_PeaceMode, false);
|
||||
GameClientSendPacket::SendCharPeaceMode(SendStream, m_dwCID, 0, m_PeaceMode.m_bPeace, 0);
|
||||
*/
|
||||
|
||||
// 배틀 그라운드 서버군에서 레벨은 40으로 고정
|
||||
if (SERVER_ID::BATTLE_SERVER == CServerSetup::GetInstance().GetServerZone())
|
||||
{
|
||||
// 레벨은 40으로 고정
|
||||
while (static_cast<unsigned long>(m_CreatureStatus.m_nLevel) < 40)
|
||||
{
|
||||
if (false == IncrementExp(static_cast<unsigned long>(
|
||||
EXP::ExpTable[m_CreatureStatus.m_nLevel - 1])))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cell 일 설정되지 않은 상태이므로 MoveTo 함수를 한번 호출해준다.
|
||||
MoveTo(GetCurrentPos(), false);
|
||||
|
||||
// edith 2009.06.13 16번 존 / 17번 존에서 무적시간 조정
|
||||
int InvincibleTime = 30;
|
||||
|
||||
if (SERVER_ID::STONE_WAR1 <= CServerSetup::GetInstance().GetServerZone() && CServerSetup::GetInstance().GetServerZone() <= SERVER_ID::STONE_WAR3)
|
||||
{
|
||||
InvincibleTime = 15;
|
||||
|
||||
char cZone = CServerSetup::GetInstance().GetServerZone();
|
||||
// edith 2009.06.13 석상전에서 로그인할 경우 스타팅 포인트에서 리스폰되어서 시작
|
||||
// 위치를 이동시키려면 MovePos 즉 워프를 이용해서 해당좌표로 강제이동시킨다.
|
||||
const Position targetPos = CCharRespawnMgr::GetInstance().GetTownRespawnPos(cZone, GetNation());
|
||||
MovePos(targetPos, cZone, false);
|
||||
|
||||
// 죽은 상태면 리스폰 시킨다.
|
||||
if (0 == m_CreatureStatus.m_nNowHP)
|
||||
{
|
||||
Respawn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 로그인시 30초간 무적
|
||||
Skill::CAddSpell<CInvincibleSpell>(CSpell::Spell_Info(Skill::CProcessTable::ProcessInfo::m_NullProtoType, this,
|
||||
Skill::SpellType::MAGICAL_SPELL, Skill::SpellID::Invincible, 1, InvincibleTime))(this);
|
||||
|
||||
// 월드 웨폰이 존재하면 월드 웨폰 인챈트 적용
|
||||
CCamp* lpWorldWeapon = CSiegeObjectMgr::GetInstance().GetWorldWeapon();
|
||||
if (lpWorldWeapon)
|
||||
{
|
||||
AddWorldWeaponEnchant(reinterpret_cast<CAggresiveCreature*>(lpWorldWeapon), lpWorldWeapon->GetNation());
|
||||
}
|
||||
|
||||
// DB에서 스펠을 읽어와 다시 걸어준다.
|
||||
const SPELL spell = GetSpell();
|
||||
GetSpellMgr().SetSpell(spell);
|
||||
|
||||
|
||||
// 길드 정보를 준다.
|
||||
CGuild* lpGuild = 0;
|
||||
if (0 != GetGID() && 0 != (lpGuild = CGuildMgr::GetInstance().GetGuild(GetGID())))
|
||||
{
|
||||
unsigned short wError = PktBase::NO_SERVER_ERR;
|
||||
unsigned char cTitle = lpGuild->GetTitle(m_dwCID);
|
||||
|
||||
if (Guild::NONE == cTitle)
|
||||
{
|
||||
wError = PktBase::SERVER_ERROR;
|
||||
}
|
||||
|
||||
GameClientSendPacket::SendCharMyGuildInfo(SendStream,
|
||||
lpGuild->GetGold(), lpGuild->GetRight(), cTitle, wError);
|
||||
}
|
||||
|
||||
// 성 정보 전송
|
||||
GameClientSendPacket::SendCharCastleInfo(SendStream);
|
||||
|
||||
// 게임 시간 정보 전송
|
||||
GameClientSendPacket::SendCharGameTimeInfo(SendStream);
|
||||
|
||||
// 길드 요새 정보 전송
|
||||
GameClientSendPacket::SendCharCampInfo(SendStream);
|
||||
|
||||
// CASTLE_TODO : 성이 길드 소유가 아니므로 일단 막아둔다.
|
||||
|
||||
// 리스폰 속도 향상 적용
|
||||
// if (0 != GetGID())
|
||||
// {
|
||||
// Castle::CCastle* lpCastle = Castle::CCastleMgr::GetInstance().GetCastleByGID(GetGID());
|
||||
// if (lpCastle)
|
||||
// {
|
||||
// CSiegeObject* lpEmblem = lpCastle->GetCastleEmblem();
|
||||
// if (lpEmblem && lpEmblem->GetUpgradeStep() > 0)
|
||||
// {
|
||||
// UpgradeRespawnSpeedByEmblem(lpEmblem->GetUpgradeType(), lpEmblem->GetUpgradeStep());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 엘리트 보너스 정보 전송
|
||||
GameClientSendPacket::SendCharEliteBonus(SendStream, GetEliteBonus());
|
||||
|
||||
// 거부 옵션 정보 전송
|
||||
GameClientSendPacket::SendCharControlOption(SendStream, m_dwCID, m_RejectOption);
|
||||
|
||||
// 어빌리티 포인트 업데이트
|
||||
UpdateUseAbilityPoint();
|
||||
|
||||
// edith 2008.06.03 공헌훈장 포인트효과
|
||||
// 컨텐츠 : 다크 카나번 국가 전쟁
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::STONE_BATTLE))
|
||||
{
|
||||
// 국가전쟁 공헌훈장 포인트 효과.
|
||||
if (CGameTimeMgr::GetInstance().IsRealmWarTime() &&
|
||||
(SERVER_ID::STONE_WAR1 <= CServerSetup::GetInstance().GetServerZone() && CServerSetup::GetInstance().GetServerZone() <= SERVER_ID::STONE_WAR3))
|
||||
{
|
||||
RealmSkill::RealmInchantAdd(this);
|
||||
}
|
||||
}
|
||||
// Admin 캐릭터에는 예외처리 (QA 쪽 요청).
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::STONE_BATTLE))
|
||||
{
|
||||
if(GetAdminLevel())
|
||||
{
|
||||
goto lb_move;
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::NEWZONE_ZONE9))
|
||||
{
|
||||
if(GetAdminLevel())
|
||||
{
|
||||
goto lb_move;
|
||||
}
|
||||
}
|
||||
*/
|
||||
// 길드전, 국가전 참여 캐릭터는 해당 시간에 로그인시 존 이동 처리를 해준다.
|
||||
if (CGameTimeMgr::GetInstance().IsGuildWarTime() &&
|
||||
(GetGuildWarFlag() == Creature::WAR_ON || GetGuildWarFlag() == Creature::WAR_INSTANCE))
|
||||
{
|
||||
MoveToGuildWarZone();
|
||||
}
|
||||
|
||||
// 공성전 중 로그인할 경우 공성측은 리스폰 지역으로 이동시킨다.
|
||||
// edith 2008.07.17 캐피탈존에 로그인했는데 공성시간이면 리스폰지역으로 이동함.
|
||||
if(CServerSetup::GetInstance().GetServerZone() == SERVER_ID::CAPITAL)
|
||||
{
|
||||
if(CGameTimeMgr::GetInstance().IsSiegeWarTime())
|
||||
{
|
||||
using namespace DBAgentPacketParse;
|
||||
|
||||
|
||||
SiegeMovePos(this);
|
||||
}
|
||||
}
|
||||
|
||||
// 컨텐츠 : 다크 카나번 국가 전쟁
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::STONE_BATTLE))
|
||||
{
|
||||
if ((CGameTimeMgr::GetInstance().IsRealmWarReadyTime() || CGameTimeMgr::GetInstance().IsRealmWarTime()) &&
|
||||
(GetRealmWarFlag() == Creature::WAR_ON || GetRealmWarFlag() == Creature::WAR_INSTANCE) &&
|
||||
|
||||
(SERVER_ID::STONE_WAR1 < CServerSetup::GetInstance().GetServerZone() || CServerSetup::GetInstance().GetServerZone() > SERVER_ID::STONE_WAR3) )
|
||||
{
|
||||
// 랠름전 시간인데 전투참가가 되어있고.하지만 16번이 아니니 강제로 16번으로 이동.
|
||||
MoveToRealmWarZone();
|
||||
}
|
||||
|
||||
if (CGameTimeMgr::GetInstance().IsRealmWarTime() &&
|
||||
GetRealmWarFlag() == Creature::WAR_OFF &&
|
||||
(SERVER_ID::STONE_WAR1 <= CServerSetup::GetInstance().GetServerZone() && CServerSetup::GetInstance().GetServerZone() <= SERVER_ID::STONE_WAR3))
|
||||
{
|
||||
// edith 2008.07.08 쟁에 참여를 안했는데 16번 존이고.. 랠름전 시간이면 강제 참여
|
||||
GET_SINGLE_DISPATCH(lpDBAgentDispatch,
|
||||
CDBAgentDispatch, CDBAgentDispatch::GetDispatchTable());
|
||||
|
||||
if (0 != lpDBAgentDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendWarOnOff(lpDBAgentDispatch->GetSendStream(), GetCID(), GameTime::REALM, Creature::WAR_INSTANCE, 0);
|
||||
}
|
||||
|
||||
// 전쟁시간에 로그인했는데 얼래 내가 석상전 맵에서 로그인을 했어.. 이르면 리스폰위치로 강제이동해..
|
||||
// edith 2008.07.08 전쟁에 참가안한 사람이지만 16번존에 존재하면 리스폰위치로 강제 이동시킨다.
|
||||
// MoveToRealmWarZone();
|
||||
}
|
||||
|
||||
lb_move:
|
||||
|
||||
// edith 2008.06.03 석상 인첸트효과
|
||||
// 다크 카나번에 로그인시
|
||||
if (SERVER_ID::STONE_WAR1 <= CServerSetup::GetInstance().GetServerZone() && CServerSetup::GetInstance().GetServerZone() <= SERVER_ID::STONE_WAR3)
|
||||
{
|
||||
// 석상 정보를 보내준다.
|
||||
CCreatureManager::GetInstance().SendRealmStatueDisplayInfo(SendStream);
|
||||
|
||||
bool bAddRealmStatueEnchant = false;
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::STONE_BATTLE))
|
||||
{
|
||||
// 국가 전쟁 시간이 아니라면, 석상 인챈트 효과를 걸어준다.
|
||||
if (!CGameTimeMgr::GetInstance().IsRealmWarTime())
|
||||
{
|
||||
bAddRealmStatueEnchant = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
// 공선전용 맵과 석상전용 맵이 따로기 때문에 해당 로직 불필요
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::SIEGE))
|
||||
{
|
||||
// 공성전 컨텐츠 포함시 공성전 시간도 아니라면, 석상 인챈트 효과를 걸어준다.
|
||||
if (CGameTimeMgr::GetInstance().IsSiegeWarTime())
|
||||
{
|
||||
bAddRealmStatueEnchant = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*
|
||||
// 석상 인챈트 효과를 적용할 수 있는 상황이라면....
|
||||
if (bAddRealmStatueEnchant)
|
||||
{
|
||||
// edith 2008.06.03 석상전이 끝날때 11시간 버프를 걸어준다.
|
||||
CCreatureManager::GetInstance().AddRealmStatueEnchant(this);
|
||||
}
|
||||
*/
|
||||
}
|
||||
// 신규존 로그인시
|
||||
else if (CServerSetup::GetInstance().GetServerZone() == SERVER_ID::ZONE5)
|
||||
{
|
||||
// 생명축출기 정보를 보내준다.
|
||||
CCreatureManager::GetInstance().SendRealmStatueDisplayInfo(SendStream);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INOUT(
|
||||
|
||||
const int MAX_LOG_BUFFER = 1024;
|
||||
char szBuffer[MAX_LOG_BUFFER];
|
||||
unsigned long dwDispatchUID = m_lpGameClientDispatch->GetUID();
|
||||
const SOCKADDR_IN& sockAddr = m_lpGameClientDispatch->GetRemoteAddr().get_addr_in();
|
||||
|
||||
_snprintf(szBuffer, MAX_LOG_BUFFER - 1,
|
||||
"UID:%d/CID:0x%08x(0x%p)/DispatchUID:%d/DispatchPointer:0x%p 로그인에 성공하였습니다. "
|
||||
"몇가지 기본 정보를 찍습니다. %s(lev:%2d, exp:%016I64u) IP:%15s:%5u",
|
||||
m_dwUID, m_dwCID, this, dwDispatchUID, m_lpGameClientDispatch,
|
||||
m_DBData.m_Info.Name, m_DBData.m_Info.Level, m_DBData.m_Info.Exp,
|
||||
inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port));
|
||||
|
||||
szBuffer[MAX_LOG_BUFFER - 1] = 0;
|
||||
DETLOG0(g_Log, szBuffer);
|
||||
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool CCharacter::Logout(DBUpdateData::UpdateType eUpdateType)
|
||||
{
|
||||
// 배틀그라운드의 경우 (죽은 상태라면) 리스폰 큐에서 제거
|
||||
if (SERVER_ID::ZONE3 == CServerSetup::GetInstance().GetServerZone())
|
||||
{
|
||||
CCreatureManager::GetInstance().PopRespawnQueue(this);
|
||||
}
|
||||
|
||||
// 죽었는데 리스폰 안하고 로그아웃하면
|
||||
// 내구도가 안깍이는 버그가 있어서 로그아웃할때 부활한 상태가 아니면 내구도를 깍아버린다.
|
||||
if(IsDead())
|
||||
{
|
||||
switch(m_eLastDeadType)
|
||||
{
|
||||
case DEAD_BY_NONE: // 올 수 없음
|
||||
case DEAD_BY_CHARACTER: // 캐릭터에 의해 사망
|
||||
break;
|
||||
|
||||
case DEAD_BY_SUICIDE: // 자살
|
||||
|
||||
// 자살시 내구도 감소
|
||||
CalculateAllEquipDurability(DURABILITY_DECREASE_PERSENT_BY_SUICIDE);
|
||||
break;
|
||||
|
||||
case DEAD_BY_MONSTER: // 몬스터에 의해 사망
|
||||
|
||||
if (!CServerSetup::GetInstance().GetDeathPenaltyEvent())
|
||||
{
|
||||
unsigned char cDecreasePersent = 0;
|
||||
|
||||
if(m_AbilityValue[Skill::Type::AB_ENDUR_SHILD] != 0)
|
||||
cDecreasePersent = DURABILITY_DECREASE_PERSENT_BY_MONSTER*m_AbilityValue[Skill::Type::AB_ENDUR_SHILD]/100;
|
||||
|
||||
// 몬스터에게 죽었을 때 내구도 감소
|
||||
CalculateAllEquipDurability(DURABILITY_DECREASE_PERSENT_BY_MONSTER-cDecreasePersent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 월드 웨폰 인챈트를 제거한다.
|
||||
ClearWorldWeaponEnchant();
|
||||
|
||||
// 성문을 막고 있던 중이었다면... 성문 막기를 취소한다.
|
||||
if (0 != m_dwProtectGateCID)
|
||||
{
|
||||
CCastleGate* lpGate = reinterpret_cast<CCastleGate*>( CSiegeObjectMgr::GetInstance().GetSiegeObject(m_dwProtectGateCID) );
|
||||
if (lpGate)
|
||||
{
|
||||
lpGate->DeleteProtectGate(this);
|
||||
|
||||
// 클라이언트에게 전송
|
||||
CGameClientDispatch* lpDispatch = GetDispatcher();
|
||||
if (NULL != lpDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharCastleCmd(lpDispatch->GetSendStream(), lpGate->GetCastleID(),
|
||||
lpGate->GetCID(), 0, 0,
|
||||
PktCastleCmd::CASTLE_GATE_PROTECT_CANCEL,
|
||||
PktCastleCmd::NO_SERVER_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 셀 로그아웃
|
||||
if (NULL != m_CellPos.m_lpCell)
|
||||
{
|
||||
m_CellPos.m_lpCell->DeleteCreature(m_dwCID);
|
||||
m_CellPos.m_lpCell = NULL;
|
||||
}
|
||||
|
||||
// 채팅 서버로 로그아웃을 던진다.
|
||||
m_SerializeCharacterData.SendChatLogout(*this);
|
||||
|
||||
// BG_TODO : 듀얼이나, 던젼의 경우에는.. 다시 처리해야 할지도 모른다.
|
||||
// VirtualArea 에 있었다면, VirtualArea 의 캐릭터리스트에서 제거
|
||||
if (0 != GetMapIndex())
|
||||
{
|
||||
VirtualArea::CVirtualAreaMgr::GetInstance().LeaveVirtualArea(this);
|
||||
}
|
||||
|
||||
// 소환수가 있다면 없앤다.
|
||||
if (NULL != m_lpSummonee)
|
||||
{
|
||||
m_lpSummonee->Dead(NULL);
|
||||
}
|
||||
|
||||
// 거래중이라면 거래 취소
|
||||
CCharacter* lpExchangeCharacter = m_Exchange.GetExchangeCharacter();
|
||||
if (NULL != lpExchangeCharacter)
|
||||
{
|
||||
m_Exchange.ExchangeOK(false);
|
||||
|
||||
CGameClientDispatch* lpExchangerDispatch = lpExchangeCharacter->GetDispatcher();
|
||||
if (NULL != lpExchangerDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharExchangeCmd(lpExchangerDispatch->GetSendStream(),
|
||||
m_dwCID, lpExchangeCharacter->GetCID(), PktExC::EXC_QUIT, PktExC::NO_SERVER_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
// 듀얼 초기화
|
||||
DuelInit(PktDuC::DUC_LOGOUT);
|
||||
|
||||
// 파티찾기 리스트에서 삭제
|
||||
CPartyMgr::GetInstance().DeleteFindPartyList(m_dwCID);
|
||||
|
||||
// 파티 주문 제거.
|
||||
if (0 != GetPID())
|
||||
{
|
||||
// 파티 로그아웃
|
||||
if (NULL != m_pParty)
|
||||
{
|
||||
m_pParty->PrepareLogout(m_dwCID);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:0x%08x 파티에 속한 녀석의 파티 포인터가 NULL입니다. PID:0x%08x, PartyPointer:0x%08x",
|
||||
m_dwCID, m_DBData.m_Info.PID, m_pParty);
|
||||
}
|
||||
}
|
||||
|
||||
// 캐릭터에 현제 걸려있는 스펠들을 DB에 저장한다.
|
||||
// 죽은게 아니면 스펠을 다시 읽지 않는다.
|
||||
// 죽은직후 스펠을 저장한후 스펠을 삭제하기 때문에 여기서 다시 스펠을 저장하면
|
||||
// 스펠이 사라진다.
|
||||
if(!IsDead())
|
||||
{
|
||||
const SPELL spell = GetSpellMgr().GetAffectedInfo().GetSpellInfo();
|
||||
SetSpell(spell);
|
||||
}
|
||||
|
||||
// 환전소 기능을 사용해서 임시 객체가 남아있다면 삭제
|
||||
CRegularAgentDispatch::GetTempCharacterMgr().EraseChar(m_dwCID);
|
||||
|
||||
GET_SINGLE_DISPATCH(lpDBAgentDispatch,
|
||||
CDBAgentDispatch, CDBAgentDispatch::GetDispatchTable());
|
||||
|
||||
if (0 != lpDBAgentDispatch)
|
||||
{
|
||||
CSendStream& AgentSendStream = lpDBAgentDispatch->GetSendStream();
|
||||
|
||||
// 퀘스트 정보 저장
|
||||
GameClientSendPacket::SendCharQuestInfo(AgentSendStream, this);
|
||||
|
||||
// 환경 설정 정보 저장
|
||||
GameClientSendPacket::SendConfigInfoDB(AgentSendStream, this);
|
||||
}
|
||||
|
||||
// 병기에 탑승중이라면 내린것으로 간주
|
||||
if (IsRideArms())
|
||||
{
|
||||
CSiegeObject* lpArms = CSiegeObjectMgr::GetInstance().GetSiegeObject(m_dwRideArmsCID);
|
||||
if (lpArms)
|
||||
{
|
||||
// 수송선 조종사가 나간경우
|
||||
if (Siege::RIDER_FOR_OWNER == lpArms->IsRider(m_dwCID))
|
||||
{
|
||||
lpArms->AllGetOff();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dwRideArmsCID = 0;
|
||||
lpArms->GetOff(m_dwCID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int nTotalSize = sizeof(PktDBUpdate) + DBUpdateData::MAX_DBUPDATE_SIZE;
|
||||
char szCharBuffer[sizeof(PktDBUpdate) + DBUpdateData::MAX_DBUPDATE_SIZE];
|
||||
char* lpCharacterInfo = szCharBuffer + sizeof(PktDBUpdate);
|
||||
|
||||
PktDBUpdate* lpPktDBUpdate = reinterpret_cast<PktDBUpdate*>(szCharBuffer);
|
||||
memset(lpPktDBUpdate, 0, sizeof(PktDBUpdate));
|
||||
|
||||
unsigned short usError = 0;
|
||||
unsigned char cAdmin = (true == IsAdmin()) ? 1 : 0;
|
||||
|
||||
bool bCharacterUpdate = GetCharacterInfo(lpCharacterInfo, &nTotalSize, lpPktDBUpdate->m_usUpdate);
|
||||
if (!bCharacterUpdate)
|
||||
{
|
||||
nTotalSize = 0;
|
||||
usError = 1;
|
||||
std::fill_n(lpPktDBUpdate->m_usUpdate, unsigned short(DBUpdateData::MAX_UPDATE_DB), 0);
|
||||
ERRLOG1(g_Log, "CID:0x%08x 데이터를 복사해 올 수 없습니다. DB에 업데이트 할 수 없습니다.", m_dwCID);
|
||||
}
|
||||
else if (0 != lpDBAgentDispatch)
|
||||
{
|
||||
if (IsOperationFlagSet(CHAR_INFO_LOADED))
|
||||
{
|
||||
CSendStream& AgentSendStream = lpDBAgentDispatch->GetSendStream();
|
||||
|
||||
// 창고 데이터 업데이트(창고가 열려 있으면 업데이트한다.)
|
||||
// 순서 주의! DB에 Update할 때는 창고 업데이트 후, 캐릭터를 업데이트한다.
|
||||
if (!m_Deposit.DBUpdate(AgentSendStream))
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:0x%08x 창고 업데이트 실패", m_dwCID);
|
||||
}
|
||||
|
||||
// 로그아웃 정보 DBAgent에 보내기
|
||||
lpPktDBUpdate->m_dlItemSerial = Item::CItemFactory::GetInstance().GetItemUID();
|
||||
lpPktDBUpdate->m_dwSessionID = m_dwSessionID;
|
||||
lpPktDBUpdate->m_dwUserID = m_dwUID;
|
||||
lpPktDBUpdate->m_dwCharID = m_dwCID;
|
||||
lpPktDBUpdate->m_TypeCode = eUpdateType;
|
||||
lpPktDBUpdate->m_dwRequestKey = 0;
|
||||
lpPktDBUpdate->m_cAdminFlag = GetGMModelFlag();
|
||||
|
||||
lpPktDBUpdate->m_dwRequestKey = 0;
|
||||
lpPktDBUpdate->m_Address.S_un.S_addr = 0;
|
||||
lpPktDBUpdate->m_cAdminLevel = 0;
|
||||
// WORK_LIST 2.4 계정 국적을 게임서버의 캐릭터가 가지도록 구현
|
||||
//lpPktDBUpdate->m_cPadding = 0;
|
||||
lpPktDBUpdate->m_cAccountNation = 0;
|
||||
lpPktDBUpdate->m_cNameChangeCount = 0;
|
||||
lpPktDBUpdate->m_cGuildWarFlag = 0;
|
||||
lpPktDBUpdate->m_cRealmWarFlag = 0;
|
||||
lpPktDBUpdate->m_cRealmPoint = 0;
|
||||
lpPktDBUpdate->m_cTacticsFlag = 0;
|
||||
lpPktDBUpdate->m_PlayTime = 0;
|
||||
lpPktDBUpdate->m_PremiumTime = 0;
|
||||
lpPktDBUpdate->m_PremiumType = 0;
|
||||
|
||||
|
||||
if (AgentSendStream.WrapCompress(reinterpret_cast<char*>(lpPktDBUpdate),
|
||||
static_cast<unsigned short>(sizeof(PktDBUpdate) + nTotalSize), CmdDBUpdateData, 0, 0))
|
||||
{
|
||||
LOG_INOUT(
|
||||
char szExp[64];
|
||||
Math::Convert::Hex64ToStr(szExp, m_DBData.m_Info.Exp);
|
||||
DETLOG7(g_Log, "UID:%d/CID:0x%08x(0x%p)/DispatchPointer:0x%p 의 캐릭터 정보를 DBAgent에 업데이트합니다. 유저 로그아웃을 처리합니다."
|
||||
" 로그아웃에 성공하였습니다. 몇가지 기본 정보를 찍습니다. %s(lev:%2d, exp:%s)",
|
||||
m_dwUID, m_dwCID, this, m_lpGameClientDispatch, m_DBData.m_Info.Name, m_DBData.m_Info.Level, szExp)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:0x%08x DB에 캐릭터 업데이트를 할 수 없습니다. 전송에 실패했습니다.", m_dwCID);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 데이터가 세팅되지 않았으니, 정상 로그아웃은 아니다.
|
||||
DBAgentPacketParse::SendAbnormalLogout(
|
||||
m_dwUID, m_dwCID, m_dwSessionID, 0, m_lpGameClientDispatch);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:0x%08x/AgentSession:0x%p/ DBUpdate failed.",
|
||||
m_dwCID, lpDBAgentDispatch);
|
||||
}
|
||||
|
||||
SOCKADDR_IN remoteAddr;
|
||||
if (0 != m_lpGameClientDispatch)
|
||||
{
|
||||
remoteAddr = m_lpGameClientDispatch->GetRemoteAddr().get_addr_in();
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(&remoteAddr, 0, sizeof(SOCKADDR_IN));
|
||||
}
|
||||
|
||||
// 게임로그에 캐릭터 로그아웃 남기기
|
||||
GAMELOG::LogCharLoginOut(m_dwUID, this, &remoteAddr, lpCharacterInfo, nTotalSize,
|
||||
lpPktDBUpdate->m_usUpdate, GAMELOG::CMD::CHAR_LOGOUT, usError);
|
||||
|
||||
// 로그 서버에 로그아웃 보내기.
|
||||
// SendLogPacket::CharLogout(*this);
|
||||
|
||||
LOG_INOUT(DETLOG4(g_Log, "UID:%d/CID:0x%08x(0x%p)/DispatchPointer:0x%p 다음 유저의 로그아웃을 처리합니다. 다음 유저를 제거합니다.",
|
||||
m_dwUID, m_dwCID, this, m_lpGameClientDispatch));
|
||||
|
||||
return CCreatureManager::GetInstance().DeleteCreature(m_dwCID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CheckDuplicatedItem(CCharacter& character)
|
||||
{
|
||||
using namespace Item;
|
||||
|
||||
CItemOwnerInfo itemOwnerInfo(
|
||||
character.GetCharacterName(), character.GetUID(), character.GetCID());
|
||||
|
||||
CItemOwnerInfo* lpDuplicateOwner = 0;
|
||||
|
||||
CItemFactory& itemFactory = Item::CItemFactory::GetInstance();
|
||||
|
||||
// first : 소유자 정보 / second : 복사 아이템 Qty
|
||||
typedef std::pair<CItemOwnerInfo*, unsigned long> DuplicateInfo;
|
||||
|
||||
typedef std::multimap<unsigned __int64, DuplicateInfo, std::less<unsigned __int64>,
|
||||
boost::fast_pool_allocator<std::pair<unsigned __int64, DuplicateInfo> > > DuplicatedMap;
|
||||
|
||||
DuplicatedMap duplicatedItemMap;
|
||||
|
||||
// 복사 데이터가 있으면 DB중계로 송신하기 위해서..
|
||||
GET_SINGLE_DISPATCH(lpDBAgentDispatch, CDBAgentDispatch,
|
||||
CDBAgentDispatch::GetDispatchTable());
|
||||
|
||||
// 아이템 컨테이너들을 배열에 넣는다.
|
||||
const int MAX_CONTAINER_SET = 7;
|
||||
CItemContainer* containerSet[MAX_CONTAINER_SET] =
|
||||
{
|
||||
&character.GetInventory(),
|
||||
&character.GetEquipments(),
|
||||
&character.GetExtra(),
|
||||
&character.GetExchange(),
|
||||
&character.GetDeposit(),
|
||||
&character.GetStall(),
|
||||
&character.GetTempInven(),
|
||||
};
|
||||
|
||||
CItemContainer** lppContainerPos = containerSet;
|
||||
CItemContainer** lppContainerEnd = containerSet + MAX_CONTAINER_SET;
|
||||
|
||||
for(; lppContainerPos != lppContainerEnd; ++lppContainerPos)
|
||||
{
|
||||
CItemContainer& itemContainer = **lppContainerPos;
|
||||
|
||||
CItemContainer::iterator pos = itemContainer.begin();
|
||||
CItemContainer::iterator end = itemContainer.end();
|
||||
|
||||
for(; pos != end; ++pos)
|
||||
{
|
||||
CItem* lpItem = *pos;
|
||||
|
||||
if (0 != lpItem)
|
||||
{
|
||||
unsigned __int64 dwItemSerial = lpItem->GetUID();
|
||||
|
||||
lpDuplicateOwner = itemFactory.AddItemMap(dwItemSerial, itemOwnerInfo);
|
||||
if (0 != lpDuplicateOwner)
|
||||
{
|
||||
// 복사 발견. 일단 나와 이녀석의 시리얼 및 정보를 리스트 등으로 기억해두었다가
|
||||
// 개수를 세어서 나중에 한번에 보낸다.
|
||||
|
||||
// 내 아이템 정보 : dwItemSerial, itemOwnerInfo
|
||||
// 상대 아이템 정보 : dwItemSerial, lpDuplicateOwner
|
||||
|
||||
std::pair<DuplicatedMap::iterator, DuplicatedMap::iterator> result =
|
||||
duplicatedItemMap.equal_range(dwItemSerial);
|
||||
|
||||
bool bAddedQty = false;
|
||||
bool bAddedDuplicateOwner = false;
|
||||
|
||||
for(;result.first != result.second; ++result.first)
|
||||
{
|
||||
unsigned __int64 dwItemUID = result.first->first;
|
||||
|
||||
// first : 소유자 정보 / second : 복사 아이템 Qty
|
||||
DuplicateInfo& duplicateInfo = result.first->second;
|
||||
CItemOwnerInfo& duplicateOwnerInfo = *duplicateInfo.first;
|
||||
|
||||
if (duplicateOwnerInfo.GetCID() == itemOwnerInfo.GetCID())
|
||||
{
|
||||
// 소유자가 같은 아이템이다. Qty를 증가시켜준다.
|
||||
// Qty는 복제된 아이템을 '내가' 몇개나 가지고 있는지를 나타낸다.
|
||||
++duplicateInfo.second;
|
||||
bAddedQty = true;
|
||||
}
|
||||
else if (duplicateOwnerInfo.GetCID() == lpDuplicateOwner->GetCID())
|
||||
{
|
||||
// 이전에 가지고 있던 사람이 내가 아니라 다른 사람이다.
|
||||
bAddedDuplicateOwner = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bAddedQty)
|
||||
{
|
||||
// 숫자가 추가되지 않았으면, 항목에 새로 추가.
|
||||
duplicatedItemMap.insert(result.second, DuplicatedMap::value_type(dwItemSerial,
|
||||
DuplicateInfo(&itemOwnerInfo, (itemOwnerInfo.GetCID() == lpDuplicateOwner->GetCID()) ? 2 : 1)));
|
||||
}
|
||||
|
||||
if (!bAddedDuplicateOwner && lpDuplicateOwner->GetCID() != itemOwnerInfo.GetCID())
|
||||
{
|
||||
duplicatedItemMap.insert(result.second,
|
||||
DuplicatedMap::value_type(dwItemSerial, DuplicateInfo(lpDuplicateOwner, 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DuplicatedMap::iterator pos = duplicatedItemMap.begin();
|
||||
DuplicatedMap::iterator end = duplicatedItemMap.end();
|
||||
|
||||
if (0 != lpDBAgentDispatch)
|
||||
{
|
||||
// 전송
|
||||
for(; pos != end; ++pos)
|
||||
{
|
||||
SendItemDuplicatedLog(lpDBAgentDispatch->GetSendStream(),
|
||||
pos->first, *pos->second.first, pos->second.second);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 로그를 남긴다.
|
||||
for(; pos != end; ++pos)
|
||||
{
|
||||
DuplicateInfo& dupInfo = pos->second;
|
||||
|
||||
ERRLOG5(g_Log, "UID:%10u / CID:%10u / CharName:%s / ItemSerial:0x%I64X / Qty:%u / 아이템 복사가 발견되었습니다",
|
||||
dupInfo.first->GetUID(), dupInfo.first->GetCID(), dupInfo.first->GetName(), pos->first, dupInfo.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SendItemDuplicatedLog(CSendStream& SendStream, unsigned __int64 dwItemSerial,
|
||||
Item::CItemOwnerInfo& itemOwnerInfo, unsigned long dwItemQty)
|
||||
{
|
||||
PktItemDuplicated* lpPktItemDuplicated =
|
||||
reinterpret_cast<PktItemDuplicated*>(SendStream.GetBuffer(sizeof(PktItemDuplicated)));
|
||||
|
||||
if (0 != lpPktItemDuplicated)
|
||||
{
|
||||
lpPktItemDuplicated->m_cLogCmd = PktServerLog::ITEM_DUPLICATED_LOG;
|
||||
|
||||
strncpy(lpPktItemDuplicated->m_szName, itemOwnerInfo.GetName(), PktItemDuplicated::MAX_NAME - 1);
|
||||
lpPktItemDuplicated->m_szName[PktItemDuplicated::MAX_NAME - 1] = 0;
|
||||
|
||||
lpPktItemDuplicated->m_dwItemSerial = dwItemSerial;
|
||||
lpPktItemDuplicated->m_dwUID = itemOwnerInfo.GetUID();
|
||||
lpPktItemDuplicated->m_dwCID = itemOwnerInfo.GetCID();
|
||||
lpPktItemDuplicated->m_dwQty = dwItemQty;
|
||||
|
||||
SendStream.WrapHeader(sizeof(PktItemDuplicated), CmdServerLog, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void RealmSkill::RealmInchantAdd(CCharacter* lpCharacter)
|
||||
{
|
||||
// edith 2010.01.02 공헌 훈장의 개념이 바뀌면서 공헌훈장 버프를 제외한다.
|
||||
return;
|
||||
|
||||
// edith 2008.05.28 공헌 훈장별 버프 설정
|
||||
// 공헌 훈장 포인트 세팅 //
|
||||
unsigned char cLevel[2][6] =
|
||||
{
|
||||
0, 1, 1, 2, 2, 3,
|
||||
0, 0, 1, 1, 2, 2
|
||||
};
|
||||
|
||||
if(!lpCharacter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char cPoint = lpCharacter->GetRealmPoint();
|
||||
|
||||
if(!cPoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// HP 효과.
|
||||
if(cLevel[0][cPoint])
|
||||
{
|
||||
Skill::CAddSpell<CRealmHPSpell>(CSpell::Spell_Info(Skill::CProcessTable::ProcessInfo::m_NullProtoType, lpCharacter,
|
||||
Skill::SpellType::REALM_SPELL, Skill::SpellID::RealmHP, cLevel[0][cPoint], CSpell::INFINITE_DURATION))(lpCharacter);
|
||||
}
|
||||
|
||||
// MP 효과.
|
||||
if(cLevel[1][cPoint])
|
||||
{
|
||||
Skill::CAddSpell<CRealmMPSpell>(CSpell::Spell_Info(Skill::CProcessTable::ProcessInfo::m_NullProtoType, lpCharacter,
|
||||
Skill::SpellType::REALM_SPELL, Skill::SpellID::RealmMP, cLevel[1][cPoint], CSpell::INFINITE_DURATION))(lpCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
void RealmSkill::RealmInchantRemove(CCharacter* lpCharacter)
|
||||
{
|
||||
if(!lpCharacter)
|
||||
return;
|
||||
|
||||
// HP 효과.
|
||||
if(lpCharacter->GetSpellMgr().GetAffectedInfo().GetSpell(Skill::SpellID::RealmHP))
|
||||
{
|
||||
lpCharacter->GetSpellMgr().GetAffectedInfo().RemoveEnchantBySpellType(Skill::SpellID::RealmHP);
|
||||
}
|
||||
|
||||
// MP 효과.
|
||||
if(lpCharacter->GetSpellMgr().GetAffectedInfo().GetSpell(Skill::SpellID::RealmMP))
|
||||
{
|
||||
lpCharacter->GetSpellMgr().GetAffectedInfo().RemoveEnchantBySpellType(Skill::SpellID::RealmMP);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Item/Item.h>
|
||||
#include <Skill/SkillMgr.h>
|
||||
|
||||
#include "Character.h"
|
||||
|
||||
void CCharacter::UpdateQuickSlotSkill(SKILLSLOT Slot)
|
||||
{
|
||||
for (int nQSlot = 0; nQSlot < QUICK::MAX_QUICK_NUM; ++nQSlot)
|
||||
{
|
||||
QUICKSLOT& QuickSlot = m_DBData.m_Quick.Slots[nQSlot];
|
||||
|
||||
if (QUICKSLOT::SKILL == QuickSlot.nType
|
||||
&& QuickSlot.wID == Slot.SKILLINFO.wSkill)
|
||||
{
|
||||
QuickSlot.nSkillLevel = Slot.SKILLINFO.cSkillLevel;
|
||||
QuickSlot.nSkillLockCount = Slot.SKILLINFO.cLockCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CCharacter::MoveQuickSlot(const TakeType takeType, const unsigned short usSkillID, unsigned char cLockCount, unsigned char cSkillLevel)
|
||||
{
|
||||
bool bResult = 0;
|
||||
|
||||
if (QUICK::MAX_QUICK_NUM <= takeType.m_dstPos.m_cIndex)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 퀵슬롯 이동 이상. 퀵슬롯 인덱스 위치가 이상합니다."
|
||||
"Dst(%2d,%2d)", m_dwCID, takeType.m_dstPos.m_cPos, takeType.m_dstPos.m_cIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (takeType.m_srcPos.m_cPos)
|
||||
{
|
||||
case TakeType::TS_INVEN:
|
||||
case TakeType::TS_TEMP:
|
||||
{
|
||||
Item::CItem* pItem = GetItem(takeType.m_srcPos);
|
||||
if (NULL == pItem)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 퀵슬롯 이동 이상. 아이템을 얻어올 수 없습니다. Src(%2d,%2d)",
|
||||
m_dwCID, takeType.m_srcPos.m_cPos, takeType.m_srcPos.m_cIndex);
|
||||
}
|
||||
else if (pItem->IsSet(Item::DetailData::QUICKSLOT_IN))
|
||||
{
|
||||
m_DBData.m_Quick.Slots[takeType.m_dstPos.m_cIndex] =
|
||||
QUICKSLOT(QUICKSLOT::ITEM, 0, 0, pItem->GetPrototypeID());
|
||||
return true;
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
case TakeType::TS_SSLOT:
|
||||
{
|
||||
if (SKILL::MAX_SLOT_NUM <= takeType.m_srcPos.m_cIndex)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 옮기려는 스킬 위치가 이상합니다. Src(%2d,%2d)",
|
||||
m_dwCID, takeType.m_srcPos.m_cPos, takeType.m_srcPos.m_cIndex);
|
||||
}
|
||||
else if (TakeType::TS_QSLOT == takeType.m_dstPos.m_cPos)
|
||||
{
|
||||
const Skill::ProtoType* lpProtoType = CSkillMgr::GetInstance().GetSkillProtoType(usSkillID);
|
||||
if (NULL == lpProtoType)
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:%10u 옮기려는 스킬의 아이디가 존재하지 않습니다. ID:0x%04x", m_dwCID, usSkillID);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char cSkillLockCount = 0;
|
||||
unsigned char cSkillCurLevel = 1;
|
||||
// 액션 스크립트이면
|
||||
if(lpProtoType->m_eSkillType == Skill::Type::ACTION)
|
||||
{
|
||||
cSkillLockCount = cLockCount;
|
||||
cSkillCurLevel = cSkillLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
SKILLSLOT SkillSlot = m_DBData.m_Skill.SSlot[takeType.m_srcPos.m_cIndex];
|
||||
|
||||
if (false == lpProtoType->m_bIsClassSkill)
|
||||
{
|
||||
cSkillLockCount = SkillSlot.SKILLINFO.cLockCount;
|
||||
cSkillCurLevel = SkillSlot.SKILLINFO.cSkillLevel;
|
||||
}
|
||||
}
|
||||
|
||||
m_DBData.m_Quick.Slots[takeType.m_dstPos.m_cIndex] =
|
||||
QUICKSLOT(QUICKSLOT::SKILL, cSkillLockCount, cSkillCurLevel, usSkillID);
|
||||
return true;
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
case TakeType::TS_QSLOT:
|
||||
{
|
||||
if (QUICK::MAX_QUICK_NUM <= takeType.m_srcPos.m_cIndex)
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:%10u 옮기려는 퀵슬롯 위치가 이상합니다. 스킬 위치 %d",
|
||||
m_dwCID, takeType.m_srcPos.m_cIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TakeType::TS_QSLOT == takeType.m_dstPos.m_cPos)
|
||||
{
|
||||
std::swap(m_DBData.m_Quick.Slots[takeType.m_srcPos.m_cIndex],
|
||||
m_DBData.m_Quick.Slots[takeType.m_dstPos.m_cIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DBData.m_Quick.Slots[takeType.m_srcPos.m_cIndex] =
|
||||
QUICKSLOT(QUICKSLOT::NONE, 0, 0, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
#include "stdafx.h"
|
||||
#include "CharacterStructure.h"
|
||||
#include "GMMemory.h"
|
||||
|
||||
|
||||
CharacterFightInfo::CharacterFightInfo()
|
||||
: m_pDuelOpponent(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef _CHARACTER_STRUCTURE_H_
|
||||
#define _CHARACTER_STRUCTURE_H_
|
||||
|
||||
#include <Creature/CreatureStructure.h>
|
||||
|
||||
|
||||
// 전방 참조
|
||||
class CCharacter;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// DB 에서 처음에 읽어 오는 데이터들. 중간중간 저장해서 DB에 갱신함.
|
||||
// 되도록이면 게임 처리중에는 건드리지 않는다. ( 레벨업 등등을 제외하고 ... )
|
||||
|
||||
struct CharacterDBData
|
||||
{
|
||||
CHAR_INFOST m_Info; // 외모, 이름, 능력치, hp,mp 등 기본 정보
|
||||
CHAR_POS m_Pos; // 종료시 위치 정보, 저장된 위치 정보 ( Respawn 장소 )
|
||||
SKILL m_Skill;
|
||||
QUICK m_Quick;
|
||||
SPELL m_Spell;
|
||||
unsigned char m_cAdminLevel;
|
||||
};
|
||||
typedef CharacterDBData* LPCharacterDBData;
|
||||
|
||||
|
||||
// 유저 접속 정보. 유저의 IP를 넣음.
|
||||
struct ConnectInfo
|
||||
{
|
||||
SOCKADDR_IN m_siAgentHost; // agent UDP address of host
|
||||
SOCKADDR_IN m_siPrivateHost; // private UDP address of host
|
||||
SOCKADDR_IN m_siPublicHost; // public UDP address of host
|
||||
};
|
||||
typedef ConnectInfo* LPConnectInfo;
|
||||
|
||||
|
||||
// 캐릭터끼리의 전투 정보 (듀얼, 전투)
|
||||
struct CharacterFightInfo
|
||||
{
|
||||
CCharacter* m_pDuelOpponent;
|
||||
|
||||
POS m_Pos;
|
||||
unsigned long m_dwCellID;
|
||||
|
||||
__int64 m_nRestoreExp; // 스킬 리저렉션을 통해 부활할 경우 일정량의 경험치가 복구될 수 있다.
|
||||
|
||||
CharacterFightInfo();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,337 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Item/Item.h>
|
||||
#include <Item/ItemFactory.h>
|
||||
|
||||
#include <Creature/NPC/NPC.h>
|
||||
#include <Creature/CreatureManager.h>
|
||||
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharItem.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharCommunity.h>
|
||||
#include <Network/Packet/PacketStruct/CharCommunityPacket.h>
|
||||
|
||||
#include <Map/FieldMap/VirtualArea/VirtualArea.h>
|
||||
#include <Map/FieldMap/VirtualArea/VirtualAreaMgr.h>
|
||||
|
||||
#include <Log/ItemLog.h>
|
||||
|
||||
#include "Character.h"
|
||||
|
||||
|
||||
unsigned long CCharacter::RepairItem(const unsigned long dwNPCID, Item::ItemPos itemPos, unsigned short& wError)
|
||||
{
|
||||
CCreature* lpCreature = CCreatureManager::GetInstance().GetCreature(dwNPCID);
|
||||
if (NULL == lpCreature)
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:%10u 수리해줄 크리쳐가 존재하지 않습니다. 수리 실패. CID:%10u", m_dwCID, dwNPCID);
|
||||
wError = PktRpI::FAIL_NOT_CREATURE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Creature::CT_NPC == Creature::GetCreatureType(dwNPCID))
|
||||
{
|
||||
int nZone = CServerSetup::GetInstance().GetServerZone();
|
||||
if (GetMapIndex() != 0)
|
||||
{
|
||||
VirtualArea::CVirtualArea* lpVirtualArea = VirtualArea::CVirtualAreaMgr::GetInstance().GetVirtualArea(GetMapIndex());
|
||||
if (NULL != lpVirtualArea)
|
||||
{
|
||||
nZone = static_cast<int>(lpVirtualArea->GetVirtualZone());
|
||||
}
|
||||
}
|
||||
|
||||
CNPC* lpNPC = reinterpret_cast<CNPC*>(lpCreature);
|
||||
if (lpNPC->GetZone() != nZone)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u NPC의 존 번호가 다릅니다. Server Zone : %d, NPC Zone : %d", m_dwCID, nZone, lpNPC->GetZone());
|
||||
wError = PktRpI::FAIL_NOT_NPCZONE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Item::CItem* lpItem = GetItem(itemPos);
|
||||
if (NULL == lpItem)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 아이템이 %d Pos, %d Index에 없습니다", m_dwCID, itemPos.m_cPos, itemPos.m_cIndex);
|
||||
wError = PktRpI::FAIL_NOT_POSITEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (true == lpItem->IsSet(Item::DetailData::STACKABLE))
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:%10u 스택 아이템이어서 수리할 수 없습니다", m_dwCID);
|
||||
wError = PktRpI::FAIL_NOT_STACKABLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (false == lpItem->IsSet(Item::DetailData::EQUIP))
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:%10u 장비 아이템이 아니어서 수리할 수 없습니다", m_dwCID);
|
||||
wError = PktRpI::FAIL_NOT_EQUIP;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lpItem->GetNumOrDurability() >= lpItem->GetMaxNumOrDurability())
|
||||
{
|
||||
// ERRLOG4(g_Log, "CID:%10u 장비가 이미 최대 내구도에 도달해서, 수리할 수 없습니다."
|
||||
// " 종류ID:%d, 현재 내구도:%d, 최대 내구도:%d", m_dwCID, lpItem->GetPrototypeID(),
|
||||
// lpItem->GetNumOrDurability(), lpItem->GetMaxNumOrDurability());
|
||||
wError = PktRpI::FAIL_FULL_DRUA;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Item::CEquipment* lpEquipment = Item::CEquipment::DowncastToEquipment(lpItem);
|
||||
unsigned long dwGold = lpCreature->RepairItem(lpEquipment, m_DBData.m_Info.Gold);
|
||||
if (0 != dwGold)
|
||||
{
|
||||
CalculateStatusData(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
wError = PktRpAI::FAIL_NOT_REPAIR;
|
||||
}
|
||||
|
||||
return dwGold;
|
||||
}
|
||||
|
||||
unsigned long CCharacter::RepairAllItem(const unsigned long dwNPCID, unsigned short& wError)
|
||||
{
|
||||
CCreature* lpCreature = CCreatureManager::GetInstance().GetCreature(dwNPCID);
|
||||
if (NULL == lpCreature)
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:%10u 수리해줄 크리쳐가 존재하지 않습니다. 수리 실패. CID:%10u", m_dwCID, dwNPCID);
|
||||
wError = PktRpAI::FAIL_NOT_CREATURE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Creature::CT_NPC == Creature::GetCreatureType(dwNPCID))
|
||||
{
|
||||
int nZone = CServerSetup::GetInstance().GetServerZone();
|
||||
if (GetMapIndex() != 0)
|
||||
{
|
||||
VirtualArea::CVirtualArea* lpVirtualArea = VirtualArea::CVirtualAreaMgr::GetInstance().GetVirtualArea(GetMapIndex());
|
||||
if (NULL != lpVirtualArea)
|
||||
{
|
||||
nZone = static_cast<int>(lpVirtualArea->GetVirtualZone());
|
||||
}
|
||||
}
|
||||
|
||||
CNPC* lpNPC = reinterpret_cast<CNPC*>(lpCreature);
|
||||
if (lpNPC->GetZone() != nZone)
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u NPC의 존 번호가 다릅니다. Server Zone : %d, NPC Zone : %d", m_dwCID, nZone, lpNPC->GetZone());
|
||||
wError = PktRpAI::FAIL_NOT_NPCZONE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long dwRepairGold = 0;
|
||||
Item::ItemPos itemPos;
|
||||
itemPos.m_cPos = TakeType::TS_EQUIP;
|
||||
// 1단계 검사
|
||||
for (unsigned char cIndex = 0; cIndex < Item::EquipmentPos::MAX_EQUPMENT_POS; ++cIndex)
|
||||
{
|
||||
if (GetRace() == CClass::AKHAN &&
|
||||
(Item::EquipmentPos::SHIRT == cIndex ||
|
||||
Item::EquipmentPos::TUNIC == cIndex ||
|
||||
Item::EquipmentPos::SHIELD_HAND2 == cIndex ||
|
||||
Item::EquipmentPos::WEAPON_HAND2 < cIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemPos.m_cIndex = cIndex;
|
||||
Item::CItem* lpItem = GetItem(itemPos);
|
||||
if (NULL == lpItem) { continue; }
|
||||
|
||||
if (true == lpItem->IsSet(Item::DetailData::STACKABLE))
|
||||
{
|
||||
// 화살/볼트의 경우 수리 불가
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false == lpItem->IsSet(Item::DetailData::EQUIP))
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 수리하는 아이템이 장비 아이템이 아닙니다. (%d Pos, %d Index)",
|
||||
m_dwCID, itemPos.m_cPos, itemPos.m_cIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lpItem->GetNumOrDurability() >= lpItem->GetMaxNumOrDurability())
|
||||
{
|
||||
// 풀내구의 장비는 스킵
|
||||
continue;
|
||||
}
|
||||
|
||||
// 골드를 계산한다.
|
||||
Item::CEquipment* lpEquipment = Item::CEquipment::DowncastToEquipment(lpItem);
|
||||
if(lpEquipment)
|
||||
dwRepairGold += lpEquipment->GetRepairPrice();
|
||||
}
|
||||
|
||||
// 돈이 실제보다 많이들면 에러를 리턴한다.
|
||||
if (dwRepairGold > GetGold())
|
||||
{
|
||||
wError = PktRpAI::NOT_ENOUGH_GOLD;
|
||||
return 0;
|
||||
}
|
||||
|
||||
dwRepairGold = 0;
|
||||
// 2단계 실제 수리
|
||||
for (unsigned char cIndex = 0; cIndex < Item::EquipmentPos::MAX_EQUPMENT_POS; ++cIndex)
|
||||
{
|
||||
if (GetRace() == CClass::AKHAN &&
|
||||
(Item::EquipmentPos::SHIRT == cIndex ||
|
||||
Item::EquipmentPos::TUNIC == cIndex ||
|
||||
Item::EquipmentPos::SHIELD_HAND2 == cIndex ||
|
||||
Item::EquipmentPos::WEAPON_HAND2 < cIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemPos.m_cIndex = cIndex;
|
||||
Item::CItem* lpItem = GetItem(itemPos);
|
||||
if (NULL == lpItem) { continue; }
|
||||
|
||||
if (true == lpItem->IsSet(Item::DetailData::STACKABLE))
|
||||
{
|
||||
// 화살/볼트의 경우 수리 불가
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false == lpItem->IsSet(Item::DetailData::EQUIP))
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 수리하는 아이템이 장비 아이템이 아닙니다. (%d Pos, %d Index)",
|
||||
m_dwCID, itemPos.m_cPos, itemPos.m_cIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lpItem->GetNumOrDurability() >= lpItem->GetMaxNumOrDurability())
|
||||
{
|
||||
// 풀내구의 장비는 스킵
|
||||
continue;
|
||||
}
|
||||
|
||||
Item::CEquipment* lpEquipment = Item::CEquipment::DowncastToEquipment(lpItem);
|
||||
|
||||
if(lpEquipment)
|
||||
dwRepairGold += lpCreature->RepairItem(lpEquipment, m_DBData.m_Info.Gold);
|
||||
}
|
||||
|
||||
if (0 != dwRepairGold)
|
||||
{
|
||||
CalculateStatusData(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
wError = PktRpAI::FAIL_NOT_REPAIR;
|
||||
}
|
||||
|
||||
return dwRepairGold;
|
||||
}
|
||||
|
||||
Item::CItem* CCharacter::SellToCharacter(CCharacter *lpCustomer, unsigned short wKindItem, TakeType takeType,
|
||||
Item::CItem* lpRequestItem, unsigned long &dwPrice, unsigned short wCouponID, unsigned short &usError)
|
||||
{
|
||||
Item::CItem* lpItem = m_Stall.GetItem(takeType.m_srcPos);
|
||||
if (NULL == lpItem) { return NULL; }
|
||||
|
||||
unsigned long dwCustomerCID = lpCustomer->GetCID();
|
||||
unsigned long dwCurrentGold = lpCustomer->GetGold();
|
||||
dwPrice = lpItem->GetBuyPrice() * takeType.m_cNum;
|
||||
|
||||
if (dwPrice > dwCurrentGold)
|
||||
{
|
||||
ERRLOG2(g_Log, "노점상 오류 : 돈이 부족합니다. 가격:%d, 소지금:%d", dwPrice, dwCurrentGold);
|
||||
GAMELOG::LogTradeItem(*this, dwCustomerCID, dwPrice, lpItem, takeType.m_srcPos, PktTr::TRC_SELL, PktTr::FAIL_ITEM_BUY);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 가질수 있는 최대 소지금 초과
|
||||
if (GetGold() > ULONG_MAX - dwPrice)
|
||||
{
|
||||
ERRLOG2(g_Log, "노점상 오류 : 거래후 Gold가 최대 소지금을 초과합니다. 가격:%d, 소지금:%d", dwPrice, GetGold());
|
||||
GAMELOG::LogTradeItem(*this, dwCustomerCID, dwPrice, lpItem, takeType.m_srcPos, PktTr::TRC_SELL, PktTr::FAIL_GOLD_OVERFLOW);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (false == lpCustomer->GetInventory().TestItem(takeType.m_dstPos, lpItem->GetPrototypeID(), takeType.m_cNum))
|
||||
{
|
||||
Item::CItemContainer* lpItemContainer = lpCustomer->GetItemContainer(takeType.m_dstPos.m_cPos);
|
||||
if (NULL != lpItemContainer)
|
||||
{
|
||||
lpItemContainer->DumpItemInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:%10u 아이템 덤프를 출력할 수 없습니다.", dwCustomerCID);
|
||||
}
|
||||
|
||||
ERRLOG4(g_Log, "CID:%10u 아이템 종류:%d를 (%2d:%2d)에 아이템 넣기 실패.",
|
||||
dwCustomerCID, lpItem->GetPrototypeID(), takeType.m_dstPos.m_cPos, takeType.m_dstPos.m_cIndex);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool bStackable = lpItem->IsSet(Item::DetailData::STACKABLE);
|
||||
unsigned char cNumOrDurability = lpItem->GetNumOrDurability();
|
||||
Item::ItemPos ItemPos = lpItem->GetPos();
|
||||
|
||||
// 스택 가능한 아이템인 경우, 개수제한 확인.
|
||||
if (!(bStackable && (cNumOrDurability < takeType.m_cNum)))
|
||||
{
|
||||
// 스택이 불가능하거나, 전부 파는 경우에는 아이템 제거.
|
||||
if (!bStackable || (bStackable && (takeType.m_cNum == lpItem->GetNumOrDurability())))
|
||||
{
|
||||
if (false == m_Stall.RemoveItem(takeType.m_srcPos))
|
||||
{
|
||||
ERRLOG3(g_Log, "CID:%10u 아이템을 (%2d, %4x)위치로부터 지우는 데 실패했습니다.",
|
||||
m_dwCID, takeType.m_srcPos.m_cPos, takeType.m_srcPos.m_cIndex);
|
||||
GAMELOG::LogTradeItem(*this, dwCustomerCID, dwPrice, lpItem, takeType.m_srcPos, PktTr::TRC_SELL, PktTr::FAIL_ITEM_BUY);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveItem(ItemPos);
|
||||
|
||||
m_Stall.SendRemoveItem(takeType, PktStRI::SC_CANCEL, lpCustomer->GetCharacterName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lpItem->SetNumOrDurability(cNumOrDurability - takeType.m_cNum);
|
||||
|
||||
m_Stall.SendRemoveItem(takeType, PktStRI::SC_CANCEL, lpCustomer->GetCharacterName());
|
||||
|
||||
unsigned short usProtoTypeID = lpItem->GetPrototypeID();
|
||||
lpItem = Item::CItemFactory::GetInstance().CreateItem(usProtoTypeID);
|
||||
if (NULL == lpItem)
|
||||
{
|
||||
ERRLOG1(g_Log, "노점상 오류 : 아이템 생성 실패. ProtoTypeID : %d", usProtoTypeID);
|
||||
GAMELOG::LogTradeItem(*this, dwCustomerCID, dwPrice, lpItem, takeType.m_srcPos, PktTr::TRC_SELL, PktTr::FAIL_ITEM_BUY);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lpItem->SetNumOrDurability(takeType.m_cNum);
|
||||
}
|
||||
|
||||
AddGold(dwPrice, false);
|
||||
|
||||
if (NULL != m_lpGameClientDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharTradeItem(m_lpGameClientDispatch->GetSendStream(), this, lpCustomer->GetCID(),
|
||||
Item::ItemPos(), NULL, ItemPos, takeType.m_cNum, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG4(g_Log, "노점상 오류 : (%2d, %4x)의 아이템 개수 : %d개 팔려는 아이템 개수 : %d개",
|
||||
takeType.m_srcPos.m_cPos, takeType.m_srcPos.m_cIndex, cNumOrDurability, takeType.m_cNum);
|
||||
GAMELOG::LogTradeItem(*this, dwCustomerCID, dwPrice, lpItem, takeType.m_srcPos, PktTr::TRC_SELL, PktTr::FAIL_ITEM_BUY);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GAMELOG::LogTradeItem(*this, dwCustomerCID, dwPrice, lpItem, takeType.m_srcPos, PktTr::TRC_SELL, PktTr::NO_SERVER_ERR);
|
||||
return lpItem;
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
#ifndef _EXP_TABLE_H_
|
||||
#define _EXP_TABLE_H_
|
||||
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
namespace EXP
|
||||
{
|
||||
static unsigned char GetUsingMaxLevel(void)
|
||||
{
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::LEVEL_LIMIT_100))
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
else if (true == CServerSetup::GetInstance().UseContents(GameRYL::LEVEL_LIMIT_90))
|
||||
{
|
||||
return 90;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 80;
|
||||
}
|
||||
}
|
||||
|
||||
const __int64 ExpTable[Creature::PC_MAX_LEVEL] = {
|
||||
200 ,
|
||||
300 ,
|
||||
422 ,
|
||||
571 ,
|
||||
752 ,
|
||||
974 ,
|
||||
1244 ,
|
||||
1574 ,
|
||||
1976 ,
|
||||
2467 ,
|
||||
3066 ,
|
||||
3796 ,
|
||||
4687 ,
|
||||
5775 ,
|
||||
7101 ,
|
||||
8719 ,
|
||||
10693 ,
|
||||
13102 ,
|
||||
16040 ,
|
||||
19625 ,
|
||||
23999 ,
|
||||
29335 ,
|
||||
35844 ,
|
||||
43786 ,
|
||||
53475 ,
|
||||
65296 ,
|
||||
79717 ,
|
||||
97310 ,
|
||||
118774 ,
|
||||
144961 ,
|
||||
176908 ,
|
||||
215884 ,
|
||||
263434 ,
|
||||
321446 ,
|
||||
392220 ,
|
||||
478564 ,
|
||||
583905 ,
|
||||
712420 ,
|
||||
869208 ,
|
||||
1060490 ,
|
||||
1293854 ,
|
||||
1578557 ,
|
||||
1925896 ,
|
||||
2349649 ,
|
||||
2866628 ,
|
||||
3497342 ,
|
||||
4266813 ,
|
||||
5205568 ,
|
||||
6350849 ,
|
||||
7748092 ,
|
||||
9452728 ,
|
||||
11532384 ,
|
||||
14069565 ,
|
||||
17164925 ,
|
||||
20941264 ,
|
||||
25548398 ,
|
||||
31169102 ,
|
||||
38026360 ,
|
||||
46392216 ,
|
||||
56598559 ,
|
||||
69050298 ,
|
||||
84241420 ,
|
||||
102774588 ,
|
||||
125385054 ,
|
||||
152969821 ,
|
||||
186623238 ,
|
||||
227680407 ,
|
||||
277770152 ,
|
||||
338879641 ,
|
||||
413433218 ,
|
||||
504388583 ,
|
||||
615354127 ,
|
||||
750732091 ,
|
||||
915893207 ,
|
||||
1117389768 ,
|
||||
1363215573 ,
|
||||
1663123055 ,
|
||||
2029010183 ,
|
||||
2475392479 ,
|
||||
3019978881 ,
|
||||
3684374291 ,
|
||||
4494936691 ,
|
||||
5483822818 ,
|
||||
6690263894 ,
|
||||
8162122007 ,
|
||||
9957788905 ,
|
||||
12148502520 ,
|
||||
14821173130 ,
|
||||
18081831275 ,
|
||||
22059834211 ,
|
||||
26912997794 ,
|
||||
32833857365 ,
|
||||
40057306041 ,
|
||||
48869913426 ,
|
||||
59621294436 ,
|
||||
72737979268 ,
|
||||
88740334762 ,
|
||||
108263208466 ,
|
||||
132081114385 ,
|
||||
132081114385 // 100레벨 부터 이해 못해-_-
|
||||
};
|
||||
|
||||
|
||||
const unsigned long ExpCapTable[Creature::PC_MAX_LEVEL] = {
|
||||
58,
|
||||
62,
|
||||
67,
|
||||
72,
|
||||
82,
|
||||
86,
|
||||
96,
|
||||
202,
|
||||
221,
|
||||
240,
|
||||
259,
|
||||
278,
|
||||
298,
|
||||
326,
|
||||
355,
|
||||
384,
|
||||
413,
|
||||
442,
|
||||
480,
|
||||
518,
|
||||
566,
|
||||
605,
|
||||
653,
|
||||
710,
|
||||
768,
|
||||
826,
|
||||
893,
|
||||
970,
|
||||
1046,
|
||||
1133,
|
||||
1219,
|
||||
1315,
|
||||
1421,
|
||||
1536,
|
||||
1661,
|
||||
1795,
|
||||
1939,
|
||||
2093,
|
||||
2266,
|
||||
2438,
|
||||
2640,
|
||||
2851,
|
||||
3082,
|
||||
3322,
|
||||
3590,
|
||||
3878,
|
||||
4186,
|
||||
4522,
|
||||
4886,
|
||||
5280,
|
||||
5702,
|
||||
6163,
|
||||
6653,
|
||||
7181,
|
||||
7757,
|
||||
8381,
|
||||
9053,
|
||||
9773,
|
||||
10560,
|
||||
11405,
|
||||
12317,
|
||||
13306,
|
||||
14371,
|
||||
15514,
|
||||
16762,
|
||||
18096,
|
||||
19546,
|
||||
21110,
|
||||
22800,
|
||||
24624,
|
||||
26602,
|
||||
28723,
|
||||
31027,
|
||||
33504,
|
||||
36192,
|
||||
39082,
|
||||
42211,
|
||||
45590,
|
||||
49238,
|
||||
53174,
|
||||
57427,
|
||||
62026,
|
||||
66989,
|
||||
72346,
|
||||
78134,
|
||||
84384,
|
||||
91133,
|
||||
98419,
|
||||
106301,
|
||||
114806,
|
||||
123984,
|
||||
133910,
|
||||
144624,
|
||||
156192,
|
||||
168682,
|
||||
182179,
|
||||
196752,
|
||||
212496,
|
||||
229498,
|
||||
247853
|
||||
};
|
||||
|
||||
const float ExpConvertTable[Creature::PC_MAX_LEVEL] =
|
||||
{
|
||||
1.2f,
|
||||
0.652173913f,
|
||||
0.395833333f,
|
||||
0.221052632f,
|
||||
0.147058824f,
|
||||
0.107142857f,
|
||||
0.080434783f,
|
||||
0.06056338f,
|
||||
0.04952381f,
|
||||
0.064356436f,
|
||||
0.051724138f,
|
||||
0.044887781f,
|
||||
0.038745387f,
|
||||
0.033426184f,
|
||||
0.031149302f,
|
||||
0.029661017f,
|
||||
0.027516779f,
|
||||
0.027027027f,
|
||||
0.025438596f,
|
||||
0.023050847f,
|
||||
0.021866667f,
|
||||
0.020726496f,
|
||||
0.020138889f,
|
||||
0.019400856f,
|
||||
0.018979834f,
|
||||
0.019104478f,
|
||||
0.019107744f,
|
||||
0.019010043f,
|
||||
0.019446154f,
|
||||
0.018932528f,
|
||||
0.018588137f,
|
||||
0.018548387f,
|
||||
0.018669076f,
|
||||
0.019495352f,
|
||||
0.020523708f,
|
||||
0.021702486f,
|
||||
0.023075468f,
|
||||
0.024573379f,
|
||||
0.025590848f,
|
||||
0.026882439f,
|
||||
0.028578261f,
|
||||
0.03020531f,
|
||||
0.032458698f,
|
||||
0.034889868f,
|
||||
0.037479936f,
|
||||
0.040498899f,
|
||||
0.044010767f,
|
||||
0.047801858f,
|
||||
0.052198744f,
|
||||
0.056302083f,
|
||||
0.060962822f,
|
||||
0.066287215f,
|
||||
0.072166196f,
|
||||
0.078949329f,
|
||||
0.086399724f,
|
||||
0.094809356f,
|
||||
0.104353011f,
|
||||
0.115183537f,
|
||||
0.122776247f,
|
||||
0.131411711f,
|
||||
0.141578839f,
|
||||
0.153099017f,
|
||||
0.166192541f,
|
||||
0.181208054f,
|
||||
0.197992027f,
|
||||
0.216885605f,
|
||||
0.238397966f,
|
||||
0.262553292f,
|
||||
0.289851868f,
|
||||
0.308373111f,
|
||||
0.330063006f,
|
||||
0.35528795f,
|
||||
0.384373361f,
|
||||
0.417348834f,
|
||||
0.454948957f,
|
||||
0.497478302f,
|
||||
0.545998585f,
|
||||
0.600566859f,
|
||||
0.662433912f,
|
||||
0.701894612f,
|
||||
0.749179452f,
|
||||
0.804414873f,
|
||||
0.868368606f,
|
||||
0.941942784f,
|
||||
1.025947304f,
|
||||
1.1215602f,
|
||||
1.230182368f,
|
||||
1.353346121f,
|
||||
1.492902584f,
|
||||
1.209087319f,
|
||||
1.071556113f,
|
||||
1.001289906f,
|
||||
0.968735572f,
|
||||
0.960942686f,
|
||||
0.971115208f,
|
||||
0.995614754f,
|
||||
1.032601272f,
|
||||
1.081251392f,
|
||||
1.141193513f,
|
||||
1.198466357f // 100레벨 부터 이해 못해-_-
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,775 @@
|
||||
#include "CharSphereTree.h"
|
||||
#include <cassert>
|
||||
|
||||
#include <Creature/Character/Character.h>
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
|
||||
CCharSphereTree& CCharSphereTree::GetInstance()
|
||||
{
|
||||
static CCharSphereTree ms_this( SphereConst::MAX_SPHERE_NODE,
|
||||
SphereConst::DEFAULT_ROOT_SIZE,
|
||||
SphereConst::DEFAULT_LEAF_SIZE,
|
||||
SphereConst::DEFAULT_GRAVY );
|
||||
return ms_this;
|
||||
}
|
||||
|
||||
CCharSphereNode::CCharSphereNode() :
|
||||
m_dwCID(0),
|
||||
m_pCharacter(NULL),
|
||||
m_pParent(NULL),
|
||||
m_pChildren(NULL),
|
||||
m_pPrevSibling(NULL),
|
||||
m_pNextSibling(NULL),
|
||||
m_ppRecomuteFifo(NULL),
|
||||
m_ppIntegrateFifo(NULL),
|
||||
m_iChildCount(0),
|
||||
m_dwFlag(0),
|
||||
m_fBindingDistance2(0),
|
||||
m_pLink(NULL),
|
||||
m_pTree(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
CCharSphereNode::CCharSphereNode( CCharSphereTree* pTree, const Position& Pos, float fRadius, void* pLink ) :
|
||||
CSphere(Pos, fRadius),
|
||||
m_dwCID(0),
|
||||
m_pCharacter(NULL),
|
||||
m_pParent(NULL),
|
||||
m_pChildren(NULL),
|
||||
m_pPrevSibling(NULL),
|
||||
m_pNextSibling(NULL),
|
||||
m_ppRecomuteFifo(NULL),
|
||||
m_ppIntegrateFifo(NULL),
|
||||
m_iChildCount(0),
|
||||
m_dwFlag(0),
|
||||
m_fBindingDistance2(0),
|
||||
m_pLink(pLink),
|
||||
m_pTree(pTree)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::Init( CCharSphereTree* pTree, const Position& Pos, float fRadius, void* pLink )
|
||||
{
|
||||
m_pTree = pTree;
|
||||
m_Center = Pos;
|
||||
SetRadius( fRadius );
|
||||
m_pLink = pLink;
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::AddChild( CCharSphereNode* pChild )
|
||||
{
|
||||
CCharSphereNode* pOldChild = m_pChildren;
|
||||
m_pChildren = pChild;
|
||||
|
||||
pChild->SetNextSibling( pOldChild );
|
||||
pChild->SetPrevSibling( NULL );
|
||||
pChild->SetParent( this );
|
||||
|
||||
if ( pOldChild )
|
||||
{
|
||||
pOldChild->SetPrevSibling( pChild );
|
||||
}
|
||||
|
||||
++m_iChildCount;
|
||||
|
||||
float fDist = Distance2( pChild );
|
||||
float fRadius = sqrtf( fDist ) + pChild->GetRadius();
|
||||
|
||||
assert( fRadius <= GetRadius() );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::DeleteChild( CCharSphereNode* pChild )
|
||||
{
|
||||
assert( m_iChildCount );
|
||||
assert( m_pChildren );
|
||||
|
||||
#ifdef _DEBUG
|
||||
// pChild 가 현재 노드의 자식인지 체크
|
||||
CCharSphereNode* pChildren = m_pChildren;
|
||||
bool bFound = false;
|
||||
|
||||
while ( pChildren )
|
||||
{
|
||||
if ( pChildren == pChild )
|
||||
{
|
||||
assert( !bFound );
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
pChildren = pChildren->GetNextSibling();
|
||||
}
|
||||
|
||||
assert( bFound );
|
||||
#endif
|
||||
|
||||
// Prev 와 Next Sibling 다시 설정
|
||||
CCharSphereNode* pPrev = pChild->GetPrevSibling();
|
||||
if ( pPrev )
|
||||
{
|
||||
CCharSphereNode* pNext = pChild->GetNextSibling();
|
||||
pPrev->SetNextSibling( pNext );
|
||||
if ( pNext ) pNext->SetPrevSibling( pPrev );
|
||||
}
|
||||
else
|
||||
{
|
||||
CCharSphereNode* pNext = pChild->GetNextSibling();
|
||||
m_pChildren = pNext;
|
||||
if ( m_pChildren ) m_pChildren->SetPrevSibling( NULL );
|
||||
}
|
||||
|
||||
--m_iChildCount;
|
||||
|
||||
if ( 0 == m_iChildCount && HasSphereNodeFlag( SNF_SUPERSPHERE ) )
|
||||
{
|
||||
m_pTree->DeleteSphere( this );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::Unlink()
|
||||
{
|
||||
// Recompute & Integrate Fifo 에서 제거한다.
|
||||
if ( m_ppRecomuteFifo )
|
||||
{
|
||||
*m_ppRecomuteFifo = NULL;
|
||||
m_ppRecomuteFifo = NULL;
|
||||
}
|
||||
|
||||
if ( m_ppIntegrateFifo )
|
||||
{
|
||||
*m_ppIntegrateFifo = NULL;
|
||||
m_ppIntegrateFifo = NULL;
|
||||
}
|
||||
|
||||
if ( m_pParent )
|
||||
{
|
||||
m_pParent->DeleteChild( this );
|
||||
}
|
||||
|
||||
assert( !m_pChildren );
|
||||
|
||||
m_pParent = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::NewPos( const Position& Pos )
|
||||
{
|
||||
// 새로운 좌표 설정
|
||||
m_Center = Pos;
|
||||
|
||||
// 부모가 있고, 재구성되어야 할 플래그가 없다면, 재구성을 할지를 판단한다.
|
||||
if ( m_pParent && !HasSphereNodeFlag( SNF_INTEGRATE ) )
|
||||
{
|
||||
// 부모와의 거리의 제곱값을 계산한다.
|
||||
float fDistance2 = Distance2( m_pParent );
|
||||
|
||||
// 거리가 바인딩 거리를 벗어난 경우
|
||||
if ( fDistance2 >= m_fBindingDistance2 )
|
||||
{
|
||||
// 재계산 플래그가 없다면, 부모의 크기가 재계산되어야 한다.
|
||||
if ( !m_pParent->HasSphereNodeFlag( SNF_RECOMPUTE ) )
|
||||
{
|
||||
m_pTree->AddRecompute( m_pParent );
|
||||
}
|
||||
|
||||
// 부모와의 연결을 끊고, 해당 Root 노드에 연결한다.
|
||||
Unlink();
|
||||
m_pTree->AddIntegrate( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::NewPos( float fX, float fY, float fZ )
|
||||
{
|
||||
NewPos( Position(fX, fY, fZ) );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::NewPosRadius( const Position& Pos, float fRadius )
|
||||
{
|
||||
// 새로운 좌표 설정
|
||||
m_Center = Pos;
|
||||
|
||||
// 부모가 있고, 재구성되어야 할 플래그가 없다면, 재구성을 할지를 판단한다.
|
||||
if ( m_pParent && !HasSphereNodeFlag( SNF_INTEGRATE ) )
|
||||
{
|
||||
// 반지름이 변경 되었는지를 체크해서 변경되었다면,
|
||||
// 반지름을 다시 설정하고, 바인딩 거리를 재계산한다.
|
||||
if ( fRadius != GetRadius() )
|
||||
{
|
||||
SetRadius( fRadius );
|
||||
ComputeBindingDistance( m_pParent );
|
||||
}
|
||||
|
||||
// 부모와의 거리의 제곱값을 계산한다.
|
||||
float fDistance2 = Distance2( m_pParent );
|
||||
|
||||
// 거리가 바인딩 거리를 벗어난 경우
|
||||
if ( fDistance2 >= m_fBindingDistance2 )
|
||||
{
|
||||
// 재계산 플래그가 없다면, 부모의 크기가 재계산되어야 한다.
|
||||
if ( !m_pParent->HasSphereNodeFlag( SNF_RECOMPUTE ) )
|
||||
{
|
||||
m_pTree->AddRecompute( m_pParent );
|
||||
}
|
||||
|
||||
// 부모와의 연결을 끊고, 해당 Root 노드에 연결한다.
|
||||
Unlink();
|
||||
m_pTree->AddIntegrate( this );
|
||||
}
|
||||
// 자식이 부모의 바인딩 거리안에 있을 경우
|
||||
else
|
||||
{
|
||||
// 부모의 바인딩 거리를 줄일수 있을수도 있기 때문에, 재계산을 한다.
|
||||
if ( !m_pParent->HasSphereNodeFlag( SNF_RECOMPUTE ) )
|
||||
{
|
||||
m_pTree->AddRecompute( m_pParent );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::NewPosRadius( float fX, float fY, float fZ, float fRadius )
|
||||
{
|
||||
NewPosRadius( Position(fX, fY, fZ), fRadius );
|
||||
}
|
||||
|
||||
float
|
||||
CCharSphereNode::Distance2( const CCharSphereNode* pNode )
|
||||
{
|
||||
return static_cast<float>( m_Center.GetSquaredDistance( pNode->GetCenter() ) );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::ComputeBindingDistance( const CCharSphereNode* pParent )
|
||||
{
|
||||
m_fBindingDistance2 = pParent->GetRadius() - GetRadius();
|
||||
if ( m_fBindingDistance2 <= 0 ) m_fBindingDistance2 = 0;
|
||||
else m_fBindingDistance2 *= m_fBindingDistance2;
|
||||
}
|
||||
|
||||
bool
|
||||
CCharSphereNode::Recompute( float fGravy )
|
||||
{
|
||||
if ( !m_pChildren ) return true; // 제거 요망
|
||||
if ( HasSphereNodeFlag( SNF_ROOT_NODE ) ) return false; // Root 는 재계산하지 않는다.
|
||||
|
||||
Position sumPos;
|
||||
int iCount = 0;
|
||||
|
||||
// 자식들의 위치를 모두 더해서 평균을 구해서,
|
||||
// 새로운 중심 좌표를 계산해 낸다.
|
||||
CCharSphereNode* pChild = m_pChildren;
|
||||
while ( pChild )
|
||||
{
|
||||
sumPos.m_fPointX += pChild->GetCenter().m_fPointX;
|
||||
sumPos.m_fPointY += pChild->GetCenter().m_fPointY;
|
||||
sumPos.m_fPointZ += pChild->GetCenter().m_fPointZ;
|
||||
|
||||
++iCount;
|
||||
|
||||
pChild = pChild->GetNextSibling();
|
||||
}
|
||||
|
||||
if ( iCount )
|
||||
{
|
||||
float fRecip = 1.0f / float(iCount);
|
||||
sumPos.m_fPointX *= fRecip;
|
||||
sumPos.m_fPointY *= fRecip;
|
||||
sumPos.m_fPointZ *= fRecip;
|
||||
|
||||
Position oldCenter = m_Center;
|
||||
m_Center = sumPos; // 새로 구해준 중심 좌표로 설정
|
||||
float fMaxRadius = 0;
|
||||
|
||||
// 자식들이 포함되는가 확인한다.
|
||||
pChild = m_pChildren;
|
||||
while ( pChild )
|
||||
{
|
||||
float fDistance2 = Distance2( pChild );
|
||||
float fRadius = sqrtf( fDistance2 ) + pChild->GetRadius();
|
||||
|
||||
if ( fRadius > fMaxRadius )
|
||||
{
|
||||
fMaxRadius = fRadius;
|
||||
if ( (fMaxRadius + fGravy) >= GetRadius() )
|
||||
{
|
||||
m_Center = oldCenter;
|
||||
ClearShpereNodeFlag( SNF_RECOMPUTE );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pChild = pChild->GetNextSibling();
|
||||
}
|
||||
|
||||
// 더 커진 반지름으로 재 설정
|
||||
fMaxRadius += fGravy;
|
||||
SetRadius( fMaxRadius );
|
||||
|
||||
// 자식들의 바인딩 거리를 재 계산
|
||||
pChild = m_pChildren;
|
||||
while ( pChild )
|
||||
{
|
||||
pChild->ComputeBindingDistance( this );
|
||||
pChild = pChild->GetNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
ClearShpereNodeFlag( SNF_RECOMPUTE );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
CCharSphereTree::CCharSphereTree( int iMaxSphereNode, float fRootSize, float fLeafSize, float fGravy ) :
|
||||
m_fMaxRootSize(fRootSize), m_fMaxLeafSize(fLeafSize), m_fSuperSphereGravy(fGravy)
|
||||
{
|
||||
iMaxSphereNode *= 4;
|
||||
m_Recompute = new CCharSphereNodeFifo( iMaxSphereNode );
|
||||
m_Integrate = new CCharSphereNodeFifo( iMaxSphereNode );
|
||||
|
||||
// Root Tree 생성
|
||||
Position rootPos;
|
||||
m_pRoot = new CCharSphereNode();
|
||||
m_pRoot->Init( this, rootPos, 65535, NULL );
|
||||
m_pRoot->SetSphereNodeFlag( eSphereNodeFlag(SNF_ROOT_NODE | SNF_SUPERSPHERE | SNF_ROOT_TREE) );
|
||||
|
||||
// Leaf Tree 생성
|
||||
m_pLeaf = new CCharSphereNode();
|
||||
m_pLeaf->Init( this, rootPos, 16384, NULL );
|
||||
m_pLeaf->SetSphereNodeFlag( eSphereNodeFlag(SNF_ROOT_NODE | SNF_SUPERSPHERE | SNF_LEAF_TREE) );
|
||||
}
|
||||
|
||||
CCharSphereTree::~CCharSphereTree()
|
||||
{
|
||||
DeleteAllChildSphere( m_pRoot );
|
||||
DeleteAllChildSphere( m_pLeaf );
|
||||
|
||||
SAFE_DELETE( m_pRoot );
|
||||
SAFE_DELETE( m_pLeaf );
|
||||
|
||||
SAFE_DELETE( m_Recompute );
|
||||
SAFE_DELETE( m_Integrate );
|
||||
|
||||
m_CharSphereMap.clear();
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::Process()
|
||||
{
|
||||
// 재계산 되어야 할 목록에 있는 노드를 재계산한다.
|
||||
// 만약 leaf node 가 부모를 벗어났다면, 부모가 재 구성되거나, 자식이 없다면, 제거되어야 한다.
|
||||
if ( m_Recompute->GetCount() > 0 )
|
||||
{
|
||||
bool bKill;
|
||||
CCharSphereNode* pTempNode = NULL;
|
||||
|
||||
int iCount = m_Recompute->GetCount();
|
||||
for (int i=0; i<iCount; ++i)
|
||||
{
|
||||
pTempNode = m_Recompute->Pop();
|
||||
if ( !pTempNode ) continue;
|
||||
pTempNode->SetRecomputeFifo( NULL );
|
||||
|
||||
bKill = pTempNode->Recompute( m_fSuperSphereGravy );
|
||||
if ( bKill )
|
||||
{
|
||||
DeleteSphere( pTempNode );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 재구성 되어야 할 목록에 있는 노드를 재구성한다.
|
||||
if ( m_Integrate->GetCount() > 0 )
|
||||
{
|
||||
CCharSphereNode* pTempNode = NULL;
|
||||
|
||||
int iCount = m_Integrate->GetCount();
|
||||
for (int i=0; i<iCount; ++i)
|
||||
{
|
||||
pTempNode = m_Integrate->Pop();
|
||||
if ( !pTempNode ) continue;
|
||||
pTempNode->SetIntegrateFifo( NULL );
|
||||
|
||||
if ( pTempNode->HasSphereNodeFlag( SNF_ROOT_TREE ) )
|
||||
Integrate( pTempNode, m_pRoot, m_fMaxRootSize ); // integrate this one single dude against the root node.
|
||||
else
|
||||
Integrate( pTempNode, m_pLeaf, m_fMaxLeafSize ); // integrate this one single dude against the root node.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CCharSphereTree::AddCharacter( unsigned long dwCID, CCharacter* pCharacter, const Position& Pos )
|
||||
{
|
||||
CCharSphereNode* pNode = AddSphere( Pos, SphereConst::CHAR_RADIUS, NULL );
|
||||
if ( pNode )
|
||||
{
|
||||
pNode->SetCID( dwCID );
|
||||
pNode->SetCharacter( pCharacter );
|
||||
pNode->SetSphereNodeFlag( SNF_ACTOR_NODE );
|
||||
|
||||
return m_CharSphereMap.insert( std::make_pair(dwCID, pNode) ).second;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
CCharSphereTree::DeleteCharacter( unsigned long dwCID )
|
||||
{
|
||||
CharSphereMap::iterator itr = m_CharSphereMap.find( dwCID );
|
||||
if ( itr != m_CharSphereMap.end() )
|
||||
{
|
||||
CCharSphereNode* pNode = itr->second;
|
||||
if ( pNode )
|
||||
{
|
||||
DeleteSphere( pNode );
|
||||
}
|
||||
|
||||
m_CharSphereMap.erase( itr );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::MoveTo( unsigned long dwCID, const Position& NewPos )
|
||||
{
|
||||
CharSphereMap::iterator itr = m_CharSphereMap.find( dwCID );
|
||||
if ( itr != m_CharSphereMap.end() )
|
||||
{
|
||||
CCharSphereNode* pNode = itr->second;
|
||||
if ( pNode && pNode->GetCenter() != NewPos )
|
||||
{
|
||||
pNode->NewPos( NewPos );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CCharSphereNode*
|
||||
CCharSphereTree::AddSphere( const Position& Pos, float fRadius, void* pLink, unsigned long dwFlag )
|
||||
{
|
||||
// 새로운 노드를 만든다.
|
||||
CCharSphereNode* pNode = new CCharSphereNode();
|
||||
assert( pNode );
|
||||
|
||||
if ( pNode )
|
||||
{
|
||||
// Root 와 Leaf 중에 한쪽의 멤버로 붙여주고, 재 구성 리스트에 넣는다.
|
||||
if ( dwFlag & SNF_ROOT_TREE )
|
||||
{
|
||||
pNode->Init( this, Pos, fRadius, pLink );
|
||||
pNode->SetSphereNodeFlag( SNF_ROOT_TREE );
|
||||
AddIntegrate( pNode );
|
||||
}
|
||||
else
|
||||
{
|
||||
pNode->Init( this, Pos, fRadius, pLink );
|
||||
pNode->SetSphereNodeFlag( SNF_LEAF_TREE );
|
||||
AddIntegrate( pNode );
|
||||
}
|
||||
}
|
||||
|
||||
return pNode;
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::DeleteSphere( CCharSphereNode* pNode )
|
||||
{
|
||||
// Root node 는 사용자가 지워준다.
|
||||
if ( pNode->HasSphereNodeFlag( SNF_ROOT_NODE ) ) return;
|
||||
|
||||
// Leaf node tree 의 supersphere 는 링크된 root node tree 의 node 를 지워준다.
|
||||
if ( pNode->HasSphereNodeFlag( SNF_SUPERSPHERE ) && pNode->HasSphereNodeFlag( SNF_LEAF_TREE) )
|
||||
{
|
||||
CCharSphereNode* pLink = (CCharSphereNode*) pNode->GetLink();
|
||||
DeleteSphere( pLink );
|
||||
}
|
||||
|
||||
pNode->Unlink();
|
||||
SAFE_DELETE( pNode );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::DeleteAllChildSphere( CCharSphereNode* pRootNode )
|
||||
{
|
||||
CCharSphereNode* pTempNode = NULL;
|
||||
CCharSphereNode* pChild = pRootNode->GetChildren();
|
||||
while ( pChild )
|
||||
{
|
||||
if ( pChild->GetChildCount() )
|
||||
{
|
||||
DeleteAllChildSphere( pChild );
|
||||
}
|
||||
|
||||
pTempNode = pChild;
|
||||
pChild = pChild->GetNextSibling();
|
||||
|
||||
SAFE_DELETE( pTempNode );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::AddRecompute( CCharSphereNode* pNode )
|
||||
{
|
||||
if ( !pNode->HasSphereNodeFlag( SNF_RECOMPUTE ) )
|
||||
{
|
||||
if ( pNode->GetChildCount() )
|
||||
{
|
||||
pNode->SetSphereNodeFlag( SNF_RECOMPUTE );
|
||||
CCharSphereNode** ppFifo = m_Recompute->Push( pNode );
|
||||
pNode->SetRecomputeFifo( ppFifo );
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteSphere( pNode );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::AddIntegrate( CCharSphereNode* pNode )
|
||||
{
|
||||
if ( pNode->HasSphereNodeFlag( SNF_ROOT_TREE ) )
|
||||
{
|
||||
m_pRoot->AddChild( pNode );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pLeaf->AddChild( pNode );
|
||||
}
|
||||
|
||||
pNode->SetSphereNodeFlag( SNF_INTEGRATE );
|
||||
CCharSphereNode** ppFifo = m_Integrate->Push( pNode );
|
||||
pNode->SetIntegrateFifo( ppFifo );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::Integrate( CCharSphereNode* pNode, CCharSphereNode* pSuperSphere, float fNodeSize )
|
||||
{
|
||||
// 재구성 단계 첫번째로 pNode 와 가장 가까이 있는 SuperSphere 를 찾는다.
|
||||
CCharSphereNode* pSearch = pSuperSphere->GetChildren();
|
||||
|
||||
CCharSphereNode* pNearestNode1st = NULL; // pNode 를 완전히 둘러쌀수 있는 SuperSphere
|
||||
CCharSphereNode* pNearestNode2nd = NULL; // pNode 를 추가하기 위해 약간 크기를 증가해야 하는 SuperSphere
|
||||
float fNearDist1st2 = 1e9;
|
||||
float fNearDist2nd2 = 1e9;
|
||||
|
||||
while ( pSearch )
|
||||
{
|
||||
if ( !pSearch->HasSphereNodeFlag( SNF_ROOT_NODE ) &&
|
||||
pSearch->HasSphereNodeFlag( SNF_SUPERSPHERE ) &&
|
||||
pSearch->GetChildCount() )
|
||||
{
|
||||
float fDistance2 = pNode->Distance2( pSearch );
|
||||
|
||||
if ( pNearestNode1st )
|
||||
{
|
||||
// 찾아 놓은 완전히 포함하는 SuperSphere 보다 더 가까운 녀석을 찾은 경우
|
||||
if ( fDistance2 < fNearDist1st2 )
|
||||
{
|
||||
float fDist = sqrtf( fDistance2 ) + pNode->GetRadius();
|
||||
if ( fDist <= pSearch->GetRadius() )
|
||||
{
|
||||
pNearestNode1st = pSearch;
|
||||
fNearDist1st2 = fDistance2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float fDist = sqrtf( fDistance2 ) + pNode->GetRadius() - pSearch->GetRadius();
|
||||
if ( fDist < fNearDist2nd2 )
|
||||
{
|
||||
if ( fDist < 0 )
|
||||
{
|
||||
// 완전히 포함하는 SuperSphere 를 찾은 경우
|
||||
pNearestNode1st = pSearch;
|
||||
fNearDist1st2 = fDistance2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 완전히 포함하지는 못하지만, 반지름을 키워서 포함할 수 있는
|
||||
// 가능성이 있는 SuperSphere 를 찾은 경우
|
||||
pNearestNode2nd = pSearch;
|
||||
fNearDist2nd2 = fDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pSearch = pSearch->GetNextSibling();
|
||||
}
|
||||
|
||||
// pNode 를 완전히 포함할 수 있는 SuperSphere 를 찾은 경우
|
||||
if ( pNearestNode1st )
|
||||
{
|
||||
// pNode 를 pNearestNode1st 의 자식으로 붙여주면 된다.
|
||||
pNode->Unlink();
|
||||
pNearestNode1st->AddChild( pNode );
|
||||
pNearestNode1st->Recompute( m_fSuperSphereGravy );
|
||||
pNode->ComputeBindingDistance( pNearestNode1st );
|
||||
|
||||
// 만약 leaf node 의 supersphere 라면, root tree 의 link node 또한 좌표와 크기를 갱신해준다.
|
||||
if ( pNearestNode1st->HasSphereNodeFlag( SNF_LEAF_TREE ) )
|
||||
{
|
||||
CCharSphereNode* pLink = (CCharSphereNode*) pNearestNode1st->GetLink();
|
||||
pLink->NewPosRadius( pNearestNode1st->GetCenter(), pNearestNode1st->GetRadius() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool bCreateNewSphere = true;
|
||||
|
||||
// 크기를 약간 키우면 pNode 를 포함할 수 있을만한 SuperSphere 를 찾은 경우
|
||||
if ( pNearestNode2nd )
|
||||
{
|
||||
float fNewSize = fNearDist2nd2 + pNearestNode2nd->GetRadius() + m_fSuperSphereGravy;
|
||||
|
||||
if ( fNewSize <= fNodeSize )
|
||||
{
|
||||
pNode->Unlink();
|
||||
|
||||
pNearestNode2nd->SetRadius( fNewSize );
|
||||
pNearestNode2nd->AddChild( pNode );
|
||||
pNearestNode2nd->Recompute( m_fSuperSphereGravy );
|
||||
pNode->ComputeBindingDistance( pNearestNode2nd );
|
||||
|
||||
if ( pNearestNode2nd->HasSphereNodeFlag( SNF_LEAF_TREE ) )
|
||||
{
|
||||
CCharSphereNode* pLink = (CCharSphereNode*) pNearestNode2nd->GetLink();
|
||||
pLink->NewPosRadius( pNearestNode2nd->GetCenter(), pNearestNode2nd->GetRadius() );
|
||||
}
|
||||
|
||||
bCreateNewSphere = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 새로운 SuperSphere 를 만들어야 하는 경우
|
||||
if ( bCreateNewSphere )
|
||||
{
|
||||
assert( pSuperSphere->HasSphereNodeFlag( SNF_ROOT_NODE ) );
|
||||
|
||||
pNode->Unlink();
|
||||
|
||||
CCharSphereNode* pParent = new CCharSphereNode();
|
||||
assert( pParent );
|
||||
pParent->Init( this, pNode->GetCenter(), pNode->GetRadius() + m_fSuperSphereGravy, NULL );
|
||||
|
||||
if ( pSuperSphere->HasSphereNodeFlag( SNF_ROOT_TREE ) )
|
||||
{
|
||||
pParent->SetSphereNodeFlag( eSphereNodeFlag(SNF_SUPERSPHERE | SNF_ROOT_TREE) );
|
||||
}
|
||||
else
|
||||
{
|
||||
pParent->SetSphereNodeFlag( eSphereNodeFlag(SNF_SUPERSPHERE | SNF_LEAF_TREE) );
|
||||
}
|
||||
|
||||
pParent->AddChild( pNode );
|
||||
pSuperSphere->AddChild( pParent );
|
||||
pParent->Recompute( m_fSuperSphereGravy );
|
||||
pNode->ComputeBindingDistance( pParent );
|
||||
|
||||
if ( pParent->HasSphereNodeFlag( SNF_LEAF_TREE ) )
|
||||
{
|
||||
// root node 에 link 를 생성해야 한다.
|
||||
CCharSphereNode* pLink = AddSphere( pParent->GetCenter(), pParent->GetRadius(), pParent, SNF_ROOT_TREE );
|
||||
pParent->SetLink( pLink );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pNode->ClearShpereNodeFlag( SNF_INTEGRATE );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::RangeTest( const Position& centerPos, float fDistance, ICharSphereTreeCallBack* pCallBack )
|
||||
{
|
||||
float fDist = static_cast<float>( m_Center.GetDistance( centerPos ) );
|
||||
if ( (fDist - fDistance) > GetRadius() ) return;
|
||||
|
||||
if ( HasSphereNodeFlag( SNF_SUPERSPHERE ) )
|
||||
{
|
||||
CCharSphereNode* pNode = m_pChildren;
|
||||
while ( pNode )
|
||||
{
|
||||
pNode->RangeTest( centerPos, fDistance, pCallBack );
|
||||
pNode = pNode->GetNextSibling();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( HasSphereNodeFlag( SNF_ROOT_TREE ) )
|
||||
{
|
||||
CCharSphereNode* pLink = (CCharSphereNode*) GetLink();
|
||||
if ( pLink )
|
||||
{
|
||||
pLink->RangeTest( centerPos, fDistance, pCallBack ) ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pCallBack->RangeTestCallBack( centerPos, fDistance, this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::RangeTest( const Position& centerPos, float fDistance, ICharSphereTreeCallBack* pCallBack )
|
||||
{
|
||||
m_pRoot->RangeTest( centerPos, fDistance, pCallBack );
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereNode::SendToRange( const Position& centerPos, float fDistance, const char* szPacket, unsigned long dwPacketSize, unsigned char cCMD_In )
|
||||
{
|
||||
float fDist = static_cast<float>( m_Center.GetDistance( centerPos ) );
|
||||
if ( (fDist - fDistance) > GetRadius() ) return;
|
||||
|
||||
if ( HasSphereNodeFlag( SNF_SUPERSPHERE ) )
|
||||
{
|
||||
CCharSphereNode* pNode = m_pChildren;
|
||||
while ( pNode )
|
||||
{
|
||||
pNode->SendToRange( centerPos, fDistance, szPacket, dwPacketSize, cCMD_In );
|
||||
pNode = pNode->GetNextSibling();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( HasSphereNodeFlag( SNF_ROOT_TREE ) )
|
||||
{
|
||||
CCharSphereNode* pLink = (CCharSphereNode*) GetLink();
|
||||
if ( pLink )
|
||||
{
|
||||
pLink->SendToRange( centerPos, fDistance, szPacket, dwPacketSize, cCMD_In ) ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Packet 전송
|
||||
if (NULL != m_pCharacter)
|
||||
{
|
||||
CGameClientDispatch* lpDispatcher = m_pCharacter->GetDispatcher();
|
||||
if (NULL != lpDispatcher)
|
||||
{
|
||||
lpDispatcher->GetSendStream().PutBuffer( szPacket, dwPacketSize, cCMD_In );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CCharSphereTree::SendToRange( const Position& centerPos, float fDistance, const char* szPacket, unsigned long dwPacketSize, unsigned char cCMD_In )
|
||||
{
|
||||
m_pRoot->SendToRange( centerPos, fDistance, szPacket, dwPacketSize, cCMD_In );
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
#ifndef __CHARACTER_SPHERE_TREE_H__
|
||||
#define __CHARACTER_SPHERE_TREE_H__
|
||||
|
||||
#include "Sphere.h"
|
||||
#include "CharSphereTreeConstants.h"
|
||||
using namespace SphereConst;
|
||||
|
||||
#include <map>
|
||||
|
||||
#ifndef SAFE_DELETE
|
||||
#define SAFE_DELETE(p) { if ((p)) { delete (p); (p) = NULL; } }
|
||||
#endif
|
||||
|
||||
#ifndef SAFE_DELETE_ARRAY
|
||||
#define SAFE_DELETE_ARRAY(p) { if ((p)) { delete [] (p); (p) = NULL; } }
|
||||
#endif
|
||||
|
||||
//======================================================================================
|
||||
struct Position;
|
||||
class CCharacter;
|
||||
class CSphere;
|
||||
class CCharSphereNode;
|
||||
class CCharSphereTree;
|
||||
class CCharSphereNodeFifo;
|
||||
class ICharSphereTreeCallBack;
|
||||
|
||||
//======================================================================================
|
||||
class ICharSphereTreeCallBack
|
||||
{
|
||||
public:
|
||||
virtual void RangeTestCallBack( const Position& centerPos, float fDistance, CCharSphereNode* pNode )
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
//======================================================================================
|
||||
class CCharSphereNode : public CSphere
|
||||
{
|
||||
public:
|
||||
CCharSphereNode();
|
||||
CCharSphereNode( CCharSphereTree* pTree, const Position& Pos, float fRadius, void* pLink );
|
||||
|
||||
void Init( CCharSphereTree* pTree, const Position& Pos, float fRadius, void* pLink );
|
||||
|
||||
// ===================================================================
|
||||
// Flag 함수
|
||||
void SetSphereNodeFlag( eSphereNodeFlag flag ) { m_dwFlag |= flag; }
|
||||
void ClearShpereNodeFlag( eSphereNodeFlag flag ) { m_dwFlag &= ~flag; }
|
||||
bool HasSphereNodeFlag( eSphereNodeFlag flag ) const
|
||||
{
|
||||
if ( m_dwFlag & flag ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 캐릭터 관련 함수
|
||||
void SetCID( unsigned long dwCID ) { m_dwCID = dwCID; }
|
||||
unsigned long GetCID() const { return m_dwCID; }
|
||||
void SetCharacter(CCharacter* pCharacter) { m_pCharacter = pCharacter; }
|
||||
CCharacter* GetCharacter() const { return m_pCharacter; }
|
||||
|
||||
// ===================================================================
|
||||
// 부모 & 자식 함수
|
||||
void SetParent( CCharSphereNode* pParent ) { m_pParent = pParent; }
|
||||
CCharSphereNode* GetParent() const { return m_pParent; }
|
||||
|
||||
void AddChild( CCharSphereNode* pChild );
|
||||
void DeleteChild( CCharSphereNode* pChild );
|
||||
CCharSphereNode* GetChildren() const { return m_pChildren; }
|
||||
int GetChildCount() const { return m_iChildCount; }
|
||||
|
||||
// ===================================================================
|
||||
// 같은 레벨의 형제 함수
|
||||
void SetPrevSibling( CCharSphereNode* pNode ) { m_pPrevSibling = pNode; }
|
||||
void SetNextSibling( CCharSphereNode* pNode ) { m_pNextSibling = pNode; }
|
||||
CCharSphereNode* GetPrevSibling() const { return m_pPrevSibling; }
|
||||
CCharSphereNode* GetNextSibling() const { return m_pNextSibling; }
|
||||
|
||||
// ===================================================================
|
||||
// Fifo 포인터 관리 함수
|
||||
void SetRecomputeFifo( CCharSphereNode** ppFifo ) { m_ppRecomuteFifo = ppFifo; }
|
||||
void SetIntegrateFifo( CCharSphereNode** ppFifo ) { m_ppIntegrateFifo = ppFifo; }
|
||||
|
||||
// ===================================================================
|
||||
// 부모와 자식 모두의 연결을 끊는 함수 (실제로는 부모와만 연결을 끊는다.)
|
||||
void Unlink();
|
||||
|
||||
// ===================================================================
|
||||
// Root tree node 의 Link ( Leaf tree 의 Root 가 아닌 SuperSphere node 가 가진다 )
|
||||
void SetLink( void* pLink ) { m_pLink = pLink; }
|
||||
void* GetLink() const { return m_pLink; }
|
||||
|
||||
// ===================================================================
|
||||
// 좌표 변경 함수
|
||||
void NewPos( const Position& Pos );
|
||||
void NewPos( float fX, float fY, float fZ );
|
||||
void NewPosRadius( const Position& Pos, float fRadius );
|
||||
void NewPosRadius( float fX, float fY, float fZ, float fRadius );
|
||||
|
||||
// ===================================================================
|
||||
// 거리 및 바인딩거리 계산 함수
|
||||
float Distance2( const CCharSphereNode* pNode );
|
||||
void ComputeBindingDistance( const CCharSphereNode* pParent );
|
||||
|
||||
// ===================================================================
|
||||
// 크기 재계산 함수 ( true 리턴시에는 제거해야함 )
|
||||
bool Recompute( float fGravy );
|
||||
|
||||
// ===================================================================
|
||||
// 범위 테스트용 함수
|
||||
void RangeTest( const Position& centerPos, float fDistance, ICharSphereTreeCallBack* pCallBack );
|
||||
void SendToRange( const Position& centerPos, float fDistance, const char* szPacket,
|
||||
unsigned long dwPacketSize, unsigned char cCMD_In );
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
|
||||
unsigned long m_dwCID;
|
||||
CCharacter* m_pCharacter;
|
||||
|
||||
CCharSphereNode* m_pParent;
|
||||
CCharSphereNode* m_pChildren;
|
||||
|
||||
CCharSphereNode* m_pPrevSibling;
|
||||
CCharSphereNode* m_pNextSibling;
|
||||
|
||||
CCharSphereNode** m_ppRecomuteFifo; // Recompute Fifo 내의 위치 포인터
|
||||
CCharSphereNode** m_ppIntegrateFifo; // Integrate Fifo 내의 위치 포인터
|
||||
|
||||
int m_iChildCount;
|
||||
unsigned long m_dwFlag;
|
||||
float m_fBindingDistance2;
|
||||
|
||||
void* m_pLink;
|
||||
CCharSphereTree* m_pTree;
|
||||
};
|
||||
|
||||
|
||||
//======================================================================================
|
||||
class CCharSphereTree
|
||||
{
|
||||
public:
|
||||
CCharSphereTree( int iMaxSphereNode, float fRootSize, float fLeafSize, float fGravy );
|
||||
virtual ~CCharSphereTree();
|
||||
|
||||
typedef std::map< unsigned long, CCharSphereNode* > CharSphereMap;
|
||||
|
||||
static CCharSphereTree& GetInstance();
|
||||
|
||||
// ===================================================================
|
||||
// Tree 의 Node 를 재계산하고 재구성하는 처리 함수
|
||||
void Process();
|
||||
|
||||
// ===================================================================
|
||||
// 캐릭터 관련 함수
|
||||
bool AddCharacter( unsigned long dwCID, CCharacter* pCharacter, const Position& Pos ) ;
|
||||
bool DeleteCharacter( unsigned long dwCID );
|
||||
void MoveTo( unsigned long dwCID, const Position& NewPos );
|
||||
|
||||
// ===================================================================
|
||||
// CCharSphereNode 를 생성(캐릭터 제외)해서 재구성하는 함수 와 삭제하는 함수
|
||||
CCharSphereNode* AddSphere( const Position& Pos, float fRadius, void* pLink, unsigned long dwFlag = SNF_LEAF_TREE );
|
||||
void DeleteSphere( CCharSphereNode* pNode );
|
||||
|
||||
// ===================================================================
|
||||
// 재계산, 재구성 리스트에 Node 추가 함수
|
||||
void AddRecompute( CCharSphereNode* pNode );
|
||||
void AddIntegrate( CCharSphereNode* pNode );
|
||||
|
||||
// ===================================================================
|
||||
// Tree 를 재구성하는 함수
|
||||
void Integrate( CCharSphereNode* pNode, CCharSphereNode* pSuperSphere, float fNodeSize );
|
||||
|
||||
// ===================================================================
|
||||
// 범위 테스트용 함수
|
||||
void RangeTest( const Position& centerPos, float fDistance, ICharSphereTreeCallBack* pCallBack );
|
||||
void SendToRange( const Position& centerPos, float fDistance, const char* szPacket,
|
||||
unsigned long dwPacketSize, unsigned char cCMD_In );
|
||||
|
||||
protected:
|
||||
// ===================================================================
|
||||
// 모든 자식 Node 를 메모리 해제하는 함수
|
||||
void DeleteAllChildSphere( CCharSphereNode* pRootNode );
|
||||
|
||||
private:
|
||||
CCharSphereNode* m_pRoot;
|
||||
CCharSphereNode* m_pLeaf;
|
||||
|
||||
CCharSphereNodeFifo* m_Recompute;
|
||||
CCharSphereNodeFifo* m_Integrate;
|
||||
|
||||
float m_fMaxRootSize;
|
||||
float m_fMaxLeafSize;
|
||||
float m_fSuperSphereGravy;
|
||||
|
||||
CharSphereMap m_CharSphereMap;
|
||||
};
|
||||
|
||||
|
||||
//======================================================================================
|
||||
class CCharSphereNodeFifo
|
||||
{
|
||||
public:
|
||||
CCharSphereNodeFifo( int iSize )
|
||||
{
|
||||
m_iCount = 0;
|
||||
m_iStackPointer = 0;
|
||||
m_iBottom = 0;
|
||||
m_iSize = iSize;
|
||||
m_ppFifo = new CCharSphereNode *[ m_iSize ];
|
||||
};
|
||||
|
||||
~CCharSphereNodeFifo()
|
||||
{
|
||||
SAFE_DELETE_ARRAY( m_ppFifo );
|
||||
};
|
||||
|
||||
CCharSphereNode** Push( CCharSphereNode* pNode )
|
||||
{
|
||||
m_iCount++;
|
||||
CCharSphereNode** ret = &m_ppFifo[ m_iStackPointer ];
|
||||
m_ppFifo[ m_iStackPointer ] = pNode;
|
||||
++m_iStackPointer;
|
||||
if ( m_iStackPointer == m_iSize ) m_iStackPointer = 0;
|
||||
return ret;
|
||||
};
|
||||
|
||||
CCharSphereNode* Pop()
|
||||
{
|
||||
while ( m_iStackPointer != m_iBottom )
|
||||
{
|
||||
--m_iCount;
|
||||
CCharSphereNode* ret = m_ppFifo[ m_iBottom ];
|
||||
++m_iBottom;
|
||||
if ( m_iBottom == m_iSize ) m_iBottom = 0;
|
||||
if ( ret ) return ret;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool Flush( CCharSphereNode* pNode )
|
||||
{
|
||||
if ( m_iStackPointer == m_iBottom ) return false;
|
||||
int i = m_iBottom;
|
||||
while ( i != m_iStackPointer )
|
||||
{
|
||||
if ( m_ppFifo[i] == pNode )
|
||||
{
|
||||
m_ppFifo[i] = NULL;
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
if ( i == m_iSize ) i = 0;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
int GetCount(void) const { return m_iCount; }
|
||||
|
||||
private:
|
||||
|
||||
int m_iCount;
|
||||
int m_iStackPointer;
|
||||
int m_iBottom;
|
||||
int m_iSize;
|
||||
CCharSphereNode** m_ppFifo;
|
||||
};
|
||||
|
||||
|
||||
#endif __CHARACTER_SPHERE_TREE_H__
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef __SPHERE_TREE_CONST_H__
|
||||
#define __SPHERE_TREE_CONST_H__
|
||||
|
||||
namespace SphereConst
|
||||
{
|
||||
enum eSphereNodeFlag
|
||||
{
|
||||
SNF_ROOT_NODE = (1<<0), // this is the root node
|
||||
SNF_SUPERSPHERE = (1<<1), // this is a supersphere, allocated and deleted by us
|
||||
SNF_ACTOR_NODE = (1<<2), // this is real actor node
|
||||
SNF_ROOT_TREE = (1<<3), // member of the root tree
|
||||
SNF_LEAF_TREE = (1<<4), // member of the leaf node tree
|
||||
SNF_RECOMPUTE = (1<<5), // needs recomputed bounding sphere
|
||||
SNF_INTEGRATE = (1<<6) // needs to be reintegrated into tree
|
||||
};
|
||||
|
||||
enum eViewState
|
||||
{
|
||||
VS_INSIDE, // completely inside the frustum.
|
||||
VS_PARTIAL, // partially inside and partially outside the frustum.
|
||||
VS_OUTSIDE // completely outside the frustum
|
||||
};
|
||||
|
||||
enum eConst
|
||||
{
|
||||
CHAR_RADIUS = 2, // 캐릭터 반경 2m
|
||||
MAX_SPHERE_NODE = 2000, // MAX 2000 명까지
|
||||
DEFAULT_ROOT_SIZE = 256, // Root node 의 사이즈
|
||||
DEFAULT_LEAF_SIZE = 64, // Leaf node 의 사이즈
|
||||
DEFAULT_GRAVY = 15 // 부모원과 자식원의 반지름 차
|
||||
};
|
||||
};
|
||||
|
||||
#endif __SPHERE_TREE_CONST_H__
|
||||
@@ -0,0 +1,214 @@
|
||||
#ifndef __SPHERE_H__
|
||||
#define __SPHERE_H__
|
||||
|
||||
#include <math.h>
|
||||
#include <Creature/CreatureStructure.h>
|
||||
|
||||
//======================================================================================
|
||||
// 3차원 좌표 구조체
|
||||
/*
|
||||
#ifndef EPSILON
|
||||
#define EPSILON 0.001
|
||||
#endif
|
||||
|
||||
struct sVector3
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
float v[3];
|
||||
};
|
||||
|
||||
sVector3()
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
}
|
||||
|
||||
sVector3( float fX, float fY, float fZ )
|
||||
{
|
||||
x = fX;
|
||||
y = fY;
|
||||
z = fZ;
|
||||
}
|
||||
|
||||
float Length() const
|
||||
{
|
||||
return sqrtf( x*x + y*y + z*z );
|
||||
}
|
||||
|
||||
float Distance( const sVector3& inPos ) const
|
||||
{
|
||||
sVector3 tempVector( inPos.x - x, inPos.y - y, inPos.z - z );
|
||||
return tempVector.Length();
|
||||
}
|
||||
|
||||
float Distance2( const sVector3& inPos ) const
|
||||
{
|
||||
return ( (inPos.x - x) * (inPos.x - x) + (inPos.y - y) * (inPos.y - y) + (inPos.z - z) * (inPos.z - z) );
|
||||
}
|
||||
|
||||
bool operator == ( const sVector3& inPos ) const
|
||||
{
|
||||
if ( fabs(x - inPos.x) <= EPSILON && fabs(y - inPos.y) <= EPSILON && fabs(z - inPos.z) <= EPSILON )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator != ( const sVector3& inPos ) const
|
||||
{
|
||||
if ( fabs(x - inPos.x) > EPSILON || fabs(y - inPos.y) > EPSILON || fabs(z - inPos.z) > EPSILON )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
//======================================================================================
|
||||
// 기본 구 클래스
|
||||
class CSphere
|
||||
{
|
||||
public:
|
||||
|
||||
CSphere();
|
||||
CSphere( float fX, float fY, float fZ, float fRadius );
|
||||
CSphere( const Position& inPos, float fRadius );
|
||||
|
||||
// ===============================================================
|
||||
void Set( float fX, float fY, float fZ, float fRadius );
|
||||
void Set( const Position& inPos, float fRadius );
|
||||
void SetRadius( float fRadius );
|
||||
|
||||
float GetX() const { return m_Center.m_fPointX; }
|
||||
float GetY() const { return m_Center.m_fPointY; }
|
||||
float GetZ() const { return m_Center.m_fPointZ; }
|
||||
const Position& GetCenter() const { return m_Center; }
|
||||
float GetRadius() const { return m_fRadius; }
|
||||
float GetRadius2() const { return m_fRadius2; }
|
||||
|
||||
// ===============================================================
|
||||
// 3차원 좌표가 구안에 있는지 검사
|
||||
// 구의 반지름 + fDistance 안에 있는지 검사를 한다.
|
||||
bool InSphere( float fX, float fY, float fZ, float fDistance );
|
||||
bool InSphere( const Position& inPos, float fDistance );
|
||||
|
||||
// 2차원 좌표가 구안에 있는지 검사
|
||||
// 구의 반지름 + fDistance 안에 있는지 검사를 한다.
|
||||
bool InSphereXY( float fX, float fY, float fDistance );
|
||||
bool InSphereXY( const Position& inPos, float fDistance );
|
||||
|
||||
protected:
|
||||
|
||||
Position m_Center; // 구의 중심 좌표
|
||||
|
||||
private:
|
||||
|
||||
float m_fRadius; // 구의 반지름
|
||||
float m_fRadius2; // 구의 반지름 제곲 값
|
||||
};
|
||||
|
||||
inline
|
||||
CSphere::CSphere() :
|
||||
m_fRadius(0), m_fRadius2(0)
|
||||
{
|
||||
}
|
||||
|
||||
inline
|
||||
CSphere::CSphere( float fX, float fY, float fZ, float fRadius ) :
|
||||
m_fRadius(fRadius), m_fRadius2(fRadius * fRadius)
|
||||
{
|
||||
}
|
||||
|
||||
inline
|
||||
CSphere::CSphere( const Position& inPos, float fRadius ) :
|
||||
m_Center(inPos), m_fRadius(fRadius), m_fRadius2(fRadius * fRadius)
|
||||
{
|
||||
}
|
||||
|
||||
inline void
|
||||
CSphere::Set( float fX, float fY, float fZ, float fRadius )
|
||||
{
|
||||
m_Center.m_fPointX = fX;
|
||||
m_Center.m_fPointY = fY;
|
||||
m_Center.m_fPointZ = fZ;
|
||||
|
||||
m_fRadius = fRadius;
|
||||
m_fRadius2 = fRadius * fRadius;
|
||||
}
|
||||
|
||||
inline void
|
||||
CSphere::Set( const Position& inPos, float fRadius )
|
||||
{
|
||||
m_Center = inPos;
|
||||
|
||||
m_fRadius = fRadius;
|
||||
m_fRadius2 = fRadius * fRadius;
|
||||
}
|
||||
|
||||
inline void
|
||||
CSphere::SetRadius( float fRadius )
|
||||
{
|
||||
m_fRadius = fRadius;
|
||||
m_fRadius2 = fRadius * fRadius;
|
||||
}
|
||||
|
||||
inline bool
|
||||
CSphere::InSphere( float fX, float fY, float fZ, float fDistance )
|
||||
{
|
||||
float dx = fX - m_Center.m_fPointX;
|
||||
float dy = fY - m_Center.m_fPointY;
|
||||
float dz = fZ - m_Center.m_fPointZ;
|
||||
float dist = sqrtf( dx*dx + dy*dy + dz*dz );
|
||||
|
||||
if ( dist < (m_fRadius + fDistance) ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool
|
||||
CSphere::InSphere( const Position& inPos, float fDistance )
|
||||
{
|
||||
float dx = inPos.m_fPointX - m_Center.m_fPointX;
|
||||
float dy = inPos.m_fPointY - m_Center.m_fPointY;
|
||||
float dz = inPos.m_fPointZ - m_Center.m_fPointZ;
|
||||
float dist = sqrtf( dx*dx + dy*dy + dz*dz );
|
||||
|
||||
if ( dist < (m_fRadius + fDistance) ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool
|
||||
CSphere::InSphereXY( float fX, float fY, float fDistance )
|
||||
{
|
||||
float dx = fX - m_Center.m_fPointX;
|
||||
float dy = fY - m_Center.m_fPointY;
|
||||
float dist = sqrtf( dx*dx + dy*dy );
|
||||
|
||||
if ( dist < (m_fRadius + fDistance) ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool
|
||||
CSphere::InSphereXY( const Position& inPos, float fDistance )
|
||||
{
|
||||
float dx = inPos.m_fPointX - m_Center.m_fPointX;
|
||||
float dy = inPos.m_fPointY - m_Center.m_fPointY;
|
||||
float dist = sqrtf( dx*dx + dy*dy );
|
||||
|
||||
if ( dist < (m_fRadius + fDistance) ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif __SPHERE_H__
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "TempCharacter.h"
|
||||
|
||||
|
||||
CTempCharacter::CTempCharacter()
|
||||
: m_dwUID(0), m_dwCID(0), m_nDataRequestCount(0), m_cGroup(-1), m_cFlag(0)
|
||||
{
|
||||
memset(&m_szCharacterName, 0, sizeof(char) * CHAR_INFOST::MAX_NAME_LEN);
|
||||
|
||||
memset(&m_CharInfoEX, 0, sizeof(CHAR_INFOEX));
|
||||
memset(&m_Quest, 0, sizeof(QUEST));
|
||||
memset(&m_History, 0, sizeof(HISTORY));
|
||||
memset(&m_Config, 0, sizeof(CONFIG));
|
||||
memset(&m_StoreInfo, 0, sizeof(STORE_INFO));
|
||||
}
|
||||
|
||||
CTempCharacter::~CTempCharacter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CTempCharacterMgr::~CTempCharacterMgr()
|
||||
{
|
||||
isMapCharList::iterator pos = m_mapTempChar.begin();
|
||||
isMapCharList::iterator end = m_mapTempChar.end();
|
||||
|
||||
for(;pos != end; ++pos)
|
||||
{
|
||||
m_tempCharPool.destroy(pos->second);
|
||||
}
|
||||
|
||||
m_mapTempChar.clear();
|
||||
}
|
||||
|
||||
|
||||
// CID/Group을 키로 하고, 일치하는 클래스가 있으면 가져온다. 없으면 생성한 후 리턴한다.
|
||||
CTempCharacter* CTempCharacterMgr::GetCharacter(unsigned long dwBattleCID, unsigned char cGroup)
|
||||
{
|
||||
std::pair<isMapCharList::iterator, isMapCharList::iterator>
|
||||
result = m_mapTempChar.equal_range(dwBattleCID);
|
||||
|
||||
CTempCharacter* lpCharacter = 0;
|
||||
|
||||
for(; result.first != result.second; ++result.first)
|
||||
{
|
||||
lpCharacter = result.first->second;
|
||||
|
||||
if(cGroup == lpCharacter->GetGroup())
|
||||
{
|
||||
return lpCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
// 못찾았다. 하나 삽입한다.
|
||||
lpCharacter = m_tempCharPool.construct();
|
||||
|
||||
if(0 != lpCharacter)
|
||||
{
|
||||
lpCharacter->SetGroup(cGroup);
|
||||
m_mapTempChar.insert(result.first, std::make_pair(dwBattleCID, lpCharacter));
|
||||
}
|
||||
|
||||
return lpCharacter;
|
||||
}
|
||||
|
||||
// 캐릭터 로그아웃시 호출한다.
|
||||
bool CTempCharacterMgr::EraseChar(unsigned long dwBattleCID)
|
||||
{
|
||||
std::pair<isMapCharList::iterator, isMapCharList::iterator>
|
||||
result = m_mapTempChar.equal_range(dwBattleCID);
|
||||
|
||||
for(; result.first != result.second;)
|
||||
{
|
||||
m_tempCharPool.destroy(result.first->second);
|
||||
m_mapTempChar.erase(result.first++);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef _TEMP_CHARACTER_H_
|
||||
#define _TEMP_CHARACTER_H_
|
||||
|
||||
#include <boost/pool/pool_alloc.hpp>
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <DB/DBdefine.h>
|
||||
|
||||
class CTempCharacterMgr;
|
||||
|
||||
// TODO : 환전소(배틀그라운드 서버군)용으로 제작된 클래스입니다.
|
||||
// 운영툴에서 사용하고 있는 CModifyCharacter의 기능 일부를 차용해왔는데...
|
||||
// 추후 CModifyCharacter가 이 클래스를 상속받아 약간 수정하면 좋을 듯 합니다.
|
||||
class CTempCharacter
|
||||
{
|
||||
public:
|
||||
|
||||
enum Const
|
||||
{
|
||||
SLOT_INFO_SET = (1 << 0), // 정섭 UID/CID/이름 이 세팅되었는지 여부
|
||||
CHAR_DATA_SET = (1 << 1), // 캐릭터 데이터가 세팅되었는지 여부
|
||||
|
||||
SLOT_INFO_REQ = (1 << 2), // 정섭 UID/CID/이름 을 요청하였음.
|
||||
CHAR_DATA_REQ = (1 << 3) // 캐릭터 데이터를 요청하였음
|
||||
};
|
||||
|
||||
CTempCharacter();
|
||||
~CTempCharacter();
|
||||
|
||||
unsigned char GetGroup(void) { return m_cGroup; }
|
||||
void SetGroup(unsigned char cGroup) { m_cGroup = cGroup; }
|
||||
|
||||
// 임시 객체이므로 아이디를 마음대로 설정할 수 있다. 다른 곳(상/하위 클래스)에서 사용하면 위험하다.
|
||||
void SetUID(unsigned long dwUID) { m_dwUID = dwUID; }
|
||||
void SetCID(unsigned long dwCID) { m_dwCID = dwCID; }
|
||||
|
||||
// 저장되는 UID/CID는 정섭 UID, 정섭 CID이다. (배틀로한과는 다르다!)
|
||||
unsigned long GetUID() const { return m_dwUID; }
|
||||
unsigned long GetCID() const { return m_dwCID; }
|
||||
|
||||
const char* GetCharacterName() const { return m_szCharacterName; }
|
||||
void SetCharacterName(const char* szName)
|
||||
{
|
||||
strncpy(m_szCharacterName, szName, CHAR_INFOST::MAX_NAME_LEN);
|
||||
m_szCharacterName[CHAR_INFOST::MAX_NAME_LEN] = 0;
|
||||
}
|
||||
|
||||
CHAR_INFOEX& GetCharInfoEx() { return m_CharInfoEX; }
|
||||
QUEST& GetQuest() { return m_Quest; }
|
||||
HISTORY& GetHistory() { return m_History; }
|
||||
CONFIG& GetConfig() { return m_Config; }
|
||||
STORE_INFO& GetStoreInfo() { return m_StoreInfo; }
|
||||
|
||||
bool IsSetData(unsigned char cFlag) const { return 0 != (m_cFlag & cFlag); }
|
||||
void SetData(unsigned char cFlag) { m_cFlag |= cFlag; }
|
||||
void ResetData(unsigned char cFlag) { m_cFlag &= ~cFlag; }
|
||||
void ClearData() { m_cFlag = 0; }
|
||||
|
||||
int AddDataRequestCount() { return ++m_nDataRequestCount; }
|
||||
int ReleaseDataRequestCount() { return --m_nDataRequestCount; }
|
||||
int GetDataRequestCount() { return m_nDataRequestCount; }
|
||||
|
||||
private:
|
||||
|
||||
unsigned long m_dwUID;
|
||||
unsigned long m_dwCID;
|
||||
|
||||
int m_nDataRequestCount;
|
||||
|
||||
char m_szCharacterName[CHAR_INFOST::MAX_NAME_LEN];
|
||||
|
||||
CHAR_INFOEX m_CharInfoEX;
|
||||
QUEST m_Quest;
|
||||
HISTORY m_History;
|
||||
CONFIG m_Config;
|
||||
STORE_INFO m_StoreInfo;
|
||||
|
||||
unsigned char m_cGroup; // 속한 서버군
|
||||
unsigned char m_cFlag;
|
||||
};
|
||||
|
||||
class CTempCharacterMgr
|
||||
{
|
||||
public:
|
||||
|
||||
// Key : 배틀로한 캐릭터 CID / Value : 캐릭터 정보
|
||||
typedef std::multimap<unsigned long, CTempCharacter*, std::less<unsigned long>,
|
||||
boost::fast_pool_allocator<std::pair<unsigned long, CTempCharacter*> > > isMapCharList;
|
||||
|
||||
// CID/Group을 키로 하고, 일치하는 클래스가 있으면 가져온다. 없으면 생성한 후 리턴한다.
|
||||
CTempCharacter* GetCharacter(unsigned long dwBattleCID, unsigned char cGroup);
|
||||
|
||||
// 캐릭터 로그아웃시 호출한다.
|
||||
bool EraseChar(unsigned long dwBattleCID);
|
||||
|
||||
~CTempCharacterMgr();
|
||||
|
||||
private:
|
||||
|
||||
boost::object_pool<CTempCharacter> m_tempCharPool;
|
||||
isMapCharList m_mapTempChar;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user