Files
LGram16 dd97ddec92 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>
2025-11-29 20:17:20 +09:00

1251 lines
35 KiB
C++

#include "stdafx.h"
#include "Character.h"
#include <Network/Dispatch/DBAgent/DBAgentRequest.h>
#include <Network/Dispatch/DBAgent/DBAgentDispatch.h>
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
#include <Network/Dispatch/GameClient/SendCharQuest.h>
#include <Network/Dispatch/GameClient/SendCharLevelUp.h>
#include <Network/Dispatch/GameClient/SendCharItem.h>
#include <Network/Dispatch/GameClient/SendCharEtc.h>
#include <Network/Packet/PacketStruct/CharQuestPacket.h>
#include <Item/Item.h>
#include <Item/ItemFactory.h>
#include <Item/ItemMgr.h>
#include <Log/LogStruct.h>
#include <Log/ItemLog.h>
#include <Log/CharacterLog.h>
#include <Creature/Character/ExpTable.h>
#include <Creature/Monster/AwardTable.h>
#include <Map/FieldMap/CellManager.h>
#include <Community/Party/Party.h>
#include <Quest/QuestMgr.h>
#include <Utility/Math/Math.h>
using namespace Quest;
bool CCharacter::CheckQuest(Quest::QuestNode* lpQuestNode, unsigned short& wError)
{
if (NULL == lpQuestNode)
{
wError = PktStartQuest::FAIL_NOT_HAVE_QUEST;
return false;
}
// 이미 한 퀘스트거나, 지금 수행중인 퀘스트 체크
if (true == HasQuest(lpQuestNode->m_wQuestID))
{
wError = PktStartQuest::FAIL_HAS_QUEST;
return false;
}
// 레벨 체크
if(lpQuestNode->m_wMinLevel != 0 && lpQuestNode->m_wMaxLevel != 0)
{
if (lpQuestNode->m_wMinLevel > GetLevel())
{
wError = PktStartQuest::FAIL_MIN_LEVEL;
return false;
}
if (lpQuestNode->m_wMaxLevel < GetLevel())
{
wError = PktStartQuest::FAIL_MAX_LEVEL;
return false;
}
}
// 명성 체크
if(lpQuestNode->m_dwMinFame != 0 && lpQuestNode->m_dwMaxFame != 0)
{
if (lpQuestNode->m_dwMinFame > GetFame())
{
wError = PktStartQuest::FAIL_MIN_FAME;
return false;
}
if (lpQuestNode->m_dwMaxFame < GetFame())
{
wError = PktStartQuest::FAIL_MAX_FAME;
return false;
}
}
// 수행할 수 있는 국적인지 체크
if (true == lpQuestNode->CheckNationDependent(GetNation()))
{
wError = PktStartQuest::FAIL_WRONG_NATION;
return false;
}
// 클래스 제한 체크
if (!(lpQuestNode->m_dwClass & (0x00000001 << (GetClass() - 1))))
{
wError = PktStartQuest::FAIL_WRONG_CLASS;
return false;
}
// 선수행 퀘스트 체크 (0xFFFF 는 클라이언트에서 팝업메뉴에 나타나지 않게 하기위해서 사용)
if (0 != lpQuestNode->m_wCompletedQuest && 0xFFFF != lpQuestNode->m_wCompletedQuest)
{
bool bCompleted = false;
for (int nIndex = 0; nIndex < MAX_HISTORY_QUEST; ++nIndex)
{
if (m_wHistoryQuest[nIndex] == lpQuestNode->m_wCompletedQuest)
{
bCompleted = true;
break;
}
}
if (!bCompleted)
{
wError = PktStartQuest::FAIL_NOT_COMPLETE_PRIOR_QUEST;
return false;
}
}
return true;
}
bool CCharacter::HasQuest(unsigned short wQuestID)
{
int nIndex = 0;
for (nIndex = 0; nIndex < MAX_EXECUTING_QUEST; ++nIndex)
{
if (NULL != m_ExecutingQuest[nIndex].m_QuestNode)
{
if (m_ExecutingQuest[nIndex].m_QuestNode->m_wQuestID == wQuestID)
{
return true;
}
}
}
for (nIndex = 0; nIndex < MAX_HISTORY_QUEST; ++nIndex)
{
if (m_wHistoryQuest[nIndex] == wQuestID)
{
return true;
}
}
return false;
}
bool CCharacter::GiveQuest(QuestNode* lpQuestNode)
{
unsigned char cIndex = 0;
while (cIndex < MAX_EXECUTING_QUEST)
{
if (NULL == m_ExecutingQuest[cIndex].m_QuestNode)
{
break;
}
++cIndex;
}
if (cIndex >= MAX_EXECUTING_QUEST)
{
// 퀘스트를 더 얻을 수 없음
return false;
}
m_ExecutingQuest[cIndex].m_QuestNode = lpQuestNode;
return true;
}
bool CCharacter::StartPhase(unsigned short wQuestID, unsigned char cPhase)
{
for (int nQuestIndex = 0; nQuestIndex < MAX_EXECUTING_QUEST; ++nQuestIndex)
{
QuestNode* lpQuestNode = m_ExecutingQuest[nQuestIndex].m_QuestNode;
if (NULL != lpQuestNode)
{
if (wQuestID == lpQuestNode->m_wQuestID)
{
if (cPhase <= lpQuestNode->m_lstPhase.size())
{
m_ExecutingQuest[nQuestIndex].m_cPhase = cPhase;
PhaseNode* phaseNode = lpQuestNode->m_lstPhase[m_ExecutingQuest[nQuestIndex].m_cPhase - 1];
for (size_t nTriggerIndex = 0; nTriggerIndex < phaseNode->m_lstTrigger.size(); ++nTriggerIndex)
{
m_ExecutingQuest[nQuestIndex].m_cTriggerCount[nTriggerIndex] =
static_cast<unsigned char>(phaseNode->m_lstTrigger[nTriggerIndex]->m_dwMaxCount);
if (TRIGGER_START == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerKind)
{
m_ExecutingQuest[nQuestIndex].m_cTriggerCount[nTriggerIndex]--;
OperateTrigger(lpQuestNode->m_wQuestID, m_ExecutingQuest[nQuestIndex].m_cPhase,
static_cast<unsigned char>(nTriggerIndex), m_ExecutingQuest[nQuestIndex].m_cTriggerCount[nTriggerIndex],
m_CurrentPos);
}
if (TRIGGER_PICK == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerKind)
{
unsigned short wProtoTypeID =
static_cast<unsigned short>(phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerID);
unsigned short wCount = m_Inventory.GetItemNum(wProtoTypeID);
if (0 != wCount)
{
CheckTrigger(TRIGGER_PICK, wProtoTypeID, Position(), wCount);
}
}
if (TRIGGER_FAME == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerKind)
{
CheckTrigger(TRIGGER_FAME, GetFame(), Position(), 1);
}
}
GET_SINGLE_DISPATCH(lpDBAgentDispatch,
CDBAgentDispatch, CDBAgentDispatch::GetDispatchTable());
if (0 != lpDBAgentDispatch)
{
GameClientSendPacket::SendCharQuestInfo(lpDBAgentDispatch->GetSendStream(), this);
}
return true;
}
return false;
}
}
}
return false;
}
bool CCharacter::CancelQuest(unsigned short wQuestID, bool bFinished)
{
for (unsigned char cQuestIndex = 0; cQuestIndex < MAX_EXECUTING_QUEST; ++cQuestIndex)
{
if (NULL != m_ExecutingQuest[cQuestIndex].m_QuestNode)
{
if (m_ExecutingQuest[cQuestIndex].m_QuestNode->m_wQuestID == wQuestID)
{
// 종속 아이템 삭제
if (true == m_ExecutingQuest[cQuestIndex].m_QuestNode->m_bDelLinkItem && !bFinished)
{
for (unsigned char cPhaseIndex = 0; cPhaseIndex < m_ExecutingQuest[cQuestIndex].m_cPhase; ++cPhaseIndex)
{
PhaseNode* lpNode = m_ExecutingQuest[cQuestIndex].m_QuestNode->m_lstPhase[cPhaseIndex];
if(lpNode)
{
for (unsigned char cItemIndex = 0; cItemIndex < lpNode->m_lstDeletedItem.size(); ++cItemIndex)
{
const unsigned short wItemID = lpNode->m_lstDeletedItem[cItemIndex].m_wItemID;
const unsigned short wItemNum =
std::min(static_cast<unsigned short>(lpNode->m_lstDeletedItem[cItemIndex].m_cItemNum), m_Inventory.GetItemNum(wItemID));
// Rodin : << 고쳐야할 사항 >>
//
// * 트리거와 이벤트의 분리
// - 트리거 큐를 두고 트리거가 발생하면 푸쉬
// - 메인 루프에서 트리거 큐에 트리거가 있으면 이벤트를 실행하고 큐에서 팝
// - 이 때 꼭 큐를 복사해서 이 프로세스 중에 발생한 트리거는 새로운 큐에 들어가도록 한다. (다음 펄스에 실행)
// * TRIGGER_PICK의 제거
// * Item::CArrayContainer::DisappearItem 리팩토링
m_Inventory.DisappearItem(wItemID, wItemNum);
}
}
}
}
// 퀘스트 삭제
std::copy(m_ExecutingQuest + cQuestIndex + 1, m_ExecutingQuest + MAX_EXECUTING_QUEST,
m_ExecutingQuest + cQuestIndex);
m_ExecutingQuest[MAX_EXECUTING_QUEST - 1] = ExecutingQuest();
return true;
}
}
}
return false;
}
void CCharacter::CheckTrigger(unsigned char cTriggerKind, unsigned long dwReferenceID, Position Pos, short wCount)
{
for (int nQuestIndex = 0; nQuestIndex < MAX_EXECUTING_QUEST; ++nQuestIndex)
{
QuestNode* lpQuestNode = m_ExecutingQuest[nQuestIndex].m_QuestNode;
if (NULL == lpQuestNode)
{
break;
}
if(lpQuestNode->m_wMaxPhase<m_ExecutingQuest[nQuestIndex].m_cPhase)
{
m_ExecutingQuest[nQuestIndex].m_cPhase = (char)lpQuestNode->m_wMaxPhase;
}
PhaseNode* phaseNode = lpQuestNode->m_lstPhase[m_ExecutingQuest[nQuestIndex].m_cPhase - 1];
if(phaseNode)
{
for (size_t nTriggerIndex = 0; nTriggerIndex < phaseNode->m_lstTrigger.size(); ++nTriggerIndex)
{
if (cTriggerKind == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerKind)
{
bool bOperateTrigger = false;
switch (cTriggerKind)
{
case TRIGGER_START:
break;
case TRIGGER_PUTON:
if (dwReferenceID == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerID)
{
Position TriggerPos(phaseNode->m_lstTrigger[nTriggerIndex]->m_fPosX,
phaseNode->m_lstTrigger[nTriggerIndex]->m_fPosY,
phaseNode->m_lstTrigger[nTriggerIndex]->m_fPosZ);
if (Pos.GetDistance(TriggerPos) <= phaseNode->m_lstTrigger[nTriggerIndex]->m_fDistance)
{
bOperateTrigger = true;
}
}
break;
case TRIGGER_GETON:
case TRIGGER_TALK:
break;
case TRIGGER_KILL:
// edith 2008.01.24 퀘스트 중에서 몬스터 잡을때 1001~1500번 몬스터 아이디를 처리하는 부분
if(1000 < dwReferenceID && dwReferenceID <= 1500)
{
// 1001~1500번은 1~500번과 동일한 아이디이다.
if((dwReferenceID-1000) == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerID)
{
bOperateTrigger = true;
}
}
else if(dwReferenceID == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerID)
{
bOperateTrigger = true;
}
break;
case TRIGGER_PICK:
if(dwReferenceID == phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerID)
{
bOperateTrigger = true;
}
break;
case TRIGGER_FAME:
if (dwReferenceID >= phaseNode->m_lstTrigger[nTriggerIndex]->m_dwTriggerID)
{
bOperateTrigger = true;
}
break;
case TRIGGER_LEVEL_TALK:
if (dwReferenceID >= phaseNode->m_lstTrigger[nTriggerIndex]->m_dwLevel)
{
bOperateTrigger = true;
}
break;
case TRIGGER_FAME_TALK:
if (dwReferenceID >= phaseNode->m_lstTrigger[nTriggerIndex]->m_dwLevel)
{
bOperateTrigger = true;
}
break;
case TRIGGER_ABILITY_TALK:
/*
ID / phaseNode->m_lstTrigger[nTriggerIndex]->m_dwZoneID
LV / phaseNode->m_lstTrigger[nTriggerIndex]->m_dwLevel
if (dwReferenceID >= )
{
bOperateTrigger = true;
}
*/
break;
}
if (true == bOperateTrigger)
{
m_ExecutingQuest[nQuestIndex].m_cTriggerCount[nTriggerIndex] -= wCount;
OperateTrigger(lpQuestNode->m_wQuestID, m_ExecutingQuest[nQuestIndex].m_cPhase,
static_cast<unsigned char>(nTriggerIndex), m_ExecutingQuest[nQuestIndex].m_cTriggerCount[nTriggerIndex], Pos);
if (NULL != GetParty())
{
CCharacterParty* lpParty = reinterpret_cast<CCharacterParty*>(GetParty());
if (NULL != lpParty)
{
lpParty->CheckTrigger(GetCID(), cTriggerKind, dwReferenceID, Pos, wCount);
}
}
// 한 번에 한 번만 트리거 발동
return;
}
}
}
}
}
if (NULL != GetParty())
{
CCharacterParty* lpParty = reinterpret_cast<CCharacterParty*>(GetParty());
if (NULL != lpParty)
{
lpParty->CheckTrigger(GetCID(), cTriggerKind, dwReferenceID, Pos, wCount);
}
}
}
unsigned short CCharacter::OperateTrigger(unsigned short wQuestID, unsigned char cPhase,
unsigned char cTrigger, unsigned char cCount, Position Pos, unsigned char cType)
{
// 큐에 삽입.
CQuestMgr::GetInstance().AddPendingQuest(m_dwCID, wQuestID, cPhase, cTrigger, cCount, Pos);
return PktOperateTrigger::NO_SERVER_ERR;
}
void CCharacter::PendingQuest(TriggerMsg* pTriggerMsg)
{
unsigned short wQuestID = pTriggerMsg->m_wQuestID;
unsigned char cPhase = pTriggerMsg->m_cPhase;
unsigned char cTrigger = pTriggerMsg->m_cTrigger;
unsigned char cCount = pTriggerMsg->m_cCount;
unsigned short wError = PktOperateTrigger::FAIL_WRONG_QUEST;
ErrorCode eError = S_SUCCESS;
bool bSave = false;
int nQuestIndex = 0;
Position Pos(pTriggerMsg->m_Pos);
bool bShowLog = true;
for (; nQuestIndex < MAX_EXECUTING_QUEST; ++nQuestIndex)
{
if (NULL != m_ExecutingQuest[nQuestIndex].m_QuestNode)
{
if (wQuestID == m_ExecutingQuest[nQuestIndex].m_QuestNode->m_wQuestID)
{
if (cPhase != m_ExecutingQuest[nQuestIndex].m_cPhase)
{
wError = PktOperateTrigger::FAIL_WRONG_PHASE;
break;
}
PhaseNode* phaseNode = m_ExecutingQuest[nQuestIndex].m_QuestNode->m_lstPhase[cPhase - 1];
if (cTrigger >= phaseNode->m_lstTrigger.size())
{
wError = PktOperateTrigger::FAIL_WRONG_TRIGGER;
break;
}
// 퀘스트가 끝나면 바로 삭제하므로 그 전에 히스토리에 저장할 것인가의 여부를 남겨둔다.
bSave = m_ExecutingQuest[nQuestIndex].m_QuestNode->m_bSave;
// 트리거 카운트를 새로 설정하고 그 값이 0이 되면 이벤트 발생.
m_ExecutingQuest[nQuestIndex].m_cTriggerCount[cTrigger] = cCount;
if (0 == m_ExecutingQuest[nQuestIndex].m_cTriggerCount[cTrigger])
{
eError = ExecuteEvent(m_ExecutingQuest[nQuestIndex], phaseNode->m_lstTrigger[cTrigger], Pos);
// 디서피어면 로그를 남기지 않는다.
if (eError == E_EVENT_DISAPPEAR)
bShowLog = false;
if (eError == E_EVENT_DISAPPEAR ||
eError == E_EVENT_MONSTERDROP ||
eError == E_EVENT_AWARDITEM )
{
wError = PktOperateTrigger::FAIL_EVENT_ERROR;
++m_ExecutingQuest[nQuestIndex].m_cTriggerCount[cTrigger];
break;
}
else if (eError == E_EVENT_GET)
{
wError = PktOperateTrigger::FAIL_EVENT_GET_ERROR;
++m_ExecutingQuest[nQuestIndex].m_cTriggerCount[cTrigger];
break;
}
}
wError = PktOperateTrigger::NO_SERVER_ERR;
break;
}
}
}
if (NULL != m_lpGameClientDispatch)
{
GameClientSendPacket::SendCharOperateTrigger(m_lpGameClientDispatch->GetSendStream(),
m_dwCID, wQuestID, cPhase, cTrigger, cCount, wError);
if (S_END_QUEST == eError)
{
GameClientSendPacket::SendCharEndQuest(m_lpGameClientDispatch->GetSendStream(),
m_dwCID, wQuestID, bSave);
if(bShowLog && PktOperateTrigger::NO_SERVER_ERR != wError)
{
ERRLOG4(g_Log, "CID:0x%08x 트리거 발동에 에러가 발생하였습니다. [0] 퀘스트:%d(%d), 에러코드:%d", m_dwCID, wQuestID, nQuestIndex, wError);
}
return;
}
}
if (cPhase != m_ExecutingQuest[nQuestIndex].m_cPhase)
{
StartPhase(wQuestID, m_ExecutingQuest[nQuestIndex].m_cPhase);
}
if(bShowLog && PktOperateTrigger::NO_SERVER_ERR != wError)
{
ERRLOG4(g_Log, "CID:0x%08x 트리거 발동에 에러가 발생하였습니다. [1] 퀘스트:%d(%d), 에러코드:%d", m_dwCID, wQuestID, nQuestIndex, wError);
}
}
Quest::ErrorCode CCharacter::ExecuteEvent(ExecutingQuest executingQuest, const TriggerNode* triggerNode, Position Pos)
{
// Desc : 아이템을 없애는 이벤트의 경우 따로 가비지 리스트로 보관하다가
// 다른 종류의 이벤트 발생 혹은 이벤트 종료시 처리한다.
vector<Item::ItemGarbage> vecItemGarbage;
// 돈을 관리하는 자리
vecItemGarbage.push_back(Item::ItemGarbage(NULL, 0));
GAMELOG::ERRType eReturnCode = 0;
for (size_t nIndex = 0; nIndex < triggerNode->m_lstEvent.size(); ++nIndex)
{
EventNode* eventNode = triggerNode->m_lstEvent[nIndex];
if (EVENT_DISAPPEAR != eventNode->m_dwEventKind && false == vecItemGarbage.empty())
{
ClearGarbage(vecItemGarbage, PktDisappearItem::DIC_DEFAULT);
}
switch (eventNode->m_dwEventKind)
{
case EVENT_DISAPPEAR:
{
if (0 != eventNode->m_dwEventAmount2)
{
vecItemGarbage[0].m_dwRemainNum += eventNode->m_dwEventAmount2;
}
if(eventNode->m_dwEventNumber)
{
if (false == m_Inventory.DisappearItem(static_cast<unsigned short>(eventNode->m_dwEventNumber),
static_cast<unsigned short>(eventNode->m_dwEventAmount1), vecItemGarbage))
{
return E_EVENT_DISAPPEAR;
}
}
break;
}
case EVENT_GET:
{
Item::CItem* lpItem =
Item::CItemFactory::GetInstance().CreateItem(static_cast<unsigned short>(eventNode->m_dwEventNumber));
if (NULL == lpItem)
{
ERRLOG2(g_Log, "CID:%10u 아이템 스크립트에 존재하지 않는 아이템을 퀘스트 스크립트에서 요청하였습니다. ItemID:%d",
m_dwCID, eventNode->m_dwEventNumber);
return E_EVENT_GET;
}
if (true == lpItem->IsSet(Item::DetailData::STACKABLE))
{
lpItem->SetNumOrDurability(static_cast<unsigned char>(eventNode->m_dwEventAmount1));
}
Item::CEquipment* lpEquip = Item::CEquipment::DowncastToEquipment(lpItem);
if (NULL != lpEquip)
{
lpEquip->SetNewEquip();
}
if (false == TestItem(lpItem))
{
ERRLOG1(g_Log, "CID:%10u 퀘스트 아이템을 넣을 공간이 없어서 이벤트 실행 실패", m_dwCID);
DELETE_ITEM(lpItem);
return E_EVENT_GET;
}
if (false == GiveItem(lpItem))
{
ERRLOG1(g_Log, "CID:%10u 퀘스트 아이템을 받는데 실패하였습니다.", m_dwCID);
DELETE_ITEM(lpItem);
}
GAMELOG::LogQuestGetReward(*this, executingQuest.m_QuestNode->m_wQuestID, lpItem,
0, 0, 0, 0, eReturnCode);
// GievItem 으로 스택된 경우
if (lpItem->IsSet(Item::DetailData::STACKABLE) && 0 == lpItem->GetNumOrDurability())
{
DELETE_ITEM(lpItem);
}
break;
}
case EVENT_SPAWN:
{
Position DestPos(eventNode->m_fPosX, eventNode->m_fPosY, eventNode->m_fPosZ);
// 배틀 그라운드 서버군의 가상존에서는 몬스터를 만들어 낼수 없다!!
if (0 != GetMapIndex())
{
/* VirtualArea::CVirtualArea* lpVirtualArea = VirtualArea::CVirtualAreaMgr::GetInstance().GetVirtualArea(lpAdmin->GetMapIndex());
if (lpVirtualArea)
{
CVirtualMonsterMgr* lpVirtualMonsterMgr = lpVirtualArea->GetMonsterManager();
if (NULL == lpVirtualMonsterMgr ||
false == lpVirtualMonsterMgr->AdminSummonMonster(lpPktAdmin->m_usProtoTypeID, DestPos, lpAdmin->GetMapIndex()))
{
ERRLOG1(g_Log, "CID:%10u 어드민 명령 오류 : 몬스터를 생성하는데 실패하였습니다.", lpAdmin->GetCID());
break;
}
}
*/
}
else
{
if (0 == eventNode->m_fPosX && 0 == eventNode->m_fPosY && 0 == eventNode->m_fPosZ)
{
// 캐릭터 주변에 랜덤한 위치에...
unsigned long dwTick = timeGetTime();
DestPos.m_fPointX = GetCurrentPos().m_fPointX + static_cast<float>(Math::Random::SimpleRandom(dwTick, 10) - 20);
DestPos.m_fPointX = GetCurrentPos().m_fPointY;
DestPos.m_fPointX = GetCurrentPos().m_fPointZ + static_cast<float>(Math::Random::SimpleRandom(dwTick, 10) - 20);
}
if (false == CCellManager::GetInstance().AdminSummonMonster(eventNode->m_dwEventNumber, DestPos))
{
ERRLOG1(g_Log, "CID:%10u 퀘스트로 몬스터를 생성하는데 실패하였습니다.", GetCID());
break;
}
}
DETLOG5(g_Log, "CID:%10u 퀘스트로 몬스터를 소환하였습니다. 몬스터(ID:%d, (%.1f, %.1f, %.1f))",
GetCID(), eventNode->m_dwEventNumber, DestPos.m_fPointX, DestPos.m_fPointY, DestPos.m_fPointZ);
break;
}
case EVENT_MONSTERDROP:
{
CCell* lpCell = CCellManager::GetInstance().GetCell(0,
static_cast<unsigned long>(Pos.m_fPointX),
static_cast<unsigned long>(Pos.m_fPointY),
static_cast<unsigned long>(Pos.m_fPointZ));
if (NULL == lpCell)
{
ERRLOG4(g_Log, "CID:%10u 퀘스트 아이템을 드랍할 몬스터의 위치가 이상합니다. X:%.1f, Y:%.1f, Z:%.1f",
m_dwCID, Pos.m_fPointX, Pos.m_fPointY, Pos.m_fPointZ);
return E_EVENT_MONSTERDROP;
}
const unsigned short wItemID = static_cast<unsigned short>(eventNode->m_dwEventAmount1);
const unsigned char cNum = static_cast<unsigned char>(eventNode->m_dwEventNumber);
Item::CItem* lpItem = Item::CItemFactory::GetInstance().CreateItem(wItemID);
if (NULL == lpItem)
{
ERRLOG2(g_Log, "CID:%10u 퀘스트 아이템 생성에 실패하였습니다. ItemID:%d",
m_dwCID, eventNode->m_dwEventAmount1);
return E_EVENT_MONSTERDROP;
}
if (true == lpItem->IsSet(Item::DetailData::STACKABLE))
{
lpItem->SetNumOrDurability(cNum);
}
CCell::ItemInfo itemInfo;
lpCell->SetItem(Pos, lpItem, 0, m_dwCID, CCell::NONE, itemInfo);
break;
}
case EVENT_END:
{
EventEnd(executingQuest.m_QuestNode->m_wQuestID, executingQuest.m_QuestNode->m_bSave);
return S_END_QUEST;
}
case EVENT_AWARD:
{
EventAward(eventNode->m_dwEventNumber, eventNode->m_dwEventAmount1, eventNode->m_dwEventAmount2, eventNode->m_dwEventAmount3);
GAMELOG::LogQuestGetReward(*this, executingQuest.m_QuestNode->m_wQuestID, 0,
eventNode->m_dwEventNumber, eventNode->m_dwEventAmount1, eventNode->m_dwEventAmount2, eventNode->m_dwEventAmount3, eReturnCode);
break;
}
case EVENT_MSGBOX:
{
break;
}
case EVENT_ALARMBOX:
{
break;
}
case EVENT_PHASE:
{
for (int nIndex = 0; nIndex < MAX_EXECUTING_QUEST; ++nIndex)
{
if (NULL != m_ExecutingQuest[nIndex].m_QuestNode)
{
if (executingQuest.m_QuestNode->m_wQuestID == m_ExecutingQuest[nIndex].m_QuestNode->m_wQuestID)
{
m_ExecutingQuest[nIndex].m_cPhase = static_cast<unsigned char>(eventNode->m_dwEventNumber);
break;
}
}
}
break;
}
case EVENT_ADDQUEST:
{
unsigned short wError = 0;
unsigned long dwNPCID = eventNode->m_dwEventNumber;
unsigned short wQuestID = static_cast<unsigned short>(eventNode->m_dwEventAmount1);
Quest::QuestNode* lpQuest = CQuestMgr::GetInstance().GetQuestNode(wQuestID);
if (NULL == lpQuest)
{
ERRLOG2(g_Log, "CID:%10u 이벤트 퀘스트 추가 실패 : QuestID:0x%04x 퀘스트가 존재하지 않습니다.",
m_dwCID, wQuestID);
break;
}
if (false == CheckQuest(lpQuest, wError))
{
ERRLOG3(g_Log, "CID:%10u 이벤트 퀘스트 추가 실패 : QuestID:0x%04x, wError : %d", m_dwCID, wQuestID, wError);
break;
}
if (false == GiveQuest(lpQuest))
{
ERRLOG2(g_Log, "CID:%10u 이벤트 퀘스트 추가 실패 : QuestID:0x%4x 퀘스트를 주는데 실패했습니다.",
m_dwCID, wQuestID);
break;
}
if (NULL != m_lpGameClientDispatch)
{
GameClientSendPacket::SendCharStartQuest(m_lpGameClientDispatch->GetSendStream(),
m_dwCID, dwNPCID, wQuestID, PktBase::NO_SERVER_ERR);
}
if (false == StartPhase(wQuestID, 1))
{
ERRLOG2(g_Log, "CID:%10u 이벤트 퀘스트 추가 실패 : QuestID:0x%x 퀘스트의 첫번째 단계를 실행하는데 실패했습니다.",
m_dwCID, wQuestID);
// 해당 퀘스트 제거
CancelQuest(wQuestID);
if (NULL != m_lpGameClientDispatch)
{
GameClientSendPacket::SendCharCancelQuest(m_lpGameClientDispatch->GetSendStream(),
m_dwCID, wQuestID, PktBase::NO_SERVER_ERR);
}
}
break;
}
case EVENT_AWARDABILITY:
{
unsigned short wError = 0;
unsigned short wAbilityID = static_cast<unsigned short>(eventNode->m_dwEventNumber);
unsigned short wAbilityLV = static_cast<unsigned short>(eventNode->m_dwEventAmount1);
// 이미 배워둔 어빌리티인지 체크
if ( wAbilityLV <= GetSkillLockCount(wAbilityID) )
{
ERRLOG3(g_Log, "CID:0x%08x 퀘스트 보상 실패 : AbilityID:0x%08x wAbilityLV:%d 어빌리티를 배우는데 실패하였습니다.",
m_dwCID, wAbilityID, wAbilityLV);
return E_EVENT_AWARDITEM;
}
using namespace Item;
ItemInfo tempItemInfo(wAbilityID);
tempItemInfo.m_UseItemInfo.m_usSkill_ID = wAbilityID;
tempItemInfo.m_UseItemInfo.m_usSkill_LockCount = wAbilityLV;
tempItemInfo.m_DetailData.m_dwFlags |= DetailData::USE_ITEM;
// ItemSerial 이 0 인 임시 아이템을 생성한다.
CUseItem* lpUseItem = CUseItem::DowncastToUseItem(
CItemFactory::GetInstance().CreateTempItem(tempItemInfo));
if (!AbilityCreate(lpUseItem))
{
ERRLOG3(g_Log, "CID:0x%08x 퀘스트 보상 실패 : AbilityID:0x%08x wAbilityLV:%d 어빌리티를 배우는데 실패하였습니다.",
m_dwCID, wAbilityID, wAbilityLV);
return E_EVENT_AWARDITEM;
}
DELETE_ITEM(lpUseItem);
break;
}
case EVENT_AWARDITEM:
{
Item::EquipType::OptionType eOptionType = static_cast<Item::EquipType::OptionType>(eventNode->m_dwEventNumber);
unsigned char cLevel = static_cast<unsigned char>(eventNode->m_dwEventAmount1);
Item::EquipType::Grade eItemGrade = static_cast<Item::EquipType::Grade>(eventNode->m_dwEventAmount2);
unsigned char cBaseNum = static_cast<unsigned char>(eventNode->m_dwEventAmount3);
if (0 == cLevel)
{
// 캐릭터의 레벨을 참조하는 경우
cLevel = m_DBData.m_Info.Level;
}
unsigned short wItemID = AwardTable::CAward::GetInstance().GetAwardEquipment(eOptionType, cLevel, this, true);
if (0 == wItemID)
{
ERRLOG3(g_Log, "CID:%10u 퀘스트 보상으로 줄 아이템이 없습니다. ItemType:%d, ItemLevel:%d",
m_dwCID, eventNode->m_dwEventNumber, eventNode->m_dwEventAmount1);
return E_EVENT_AWARDITEM;
}
Item::CItem* lpItem = Item::CItemFactory::GetInstance().CreateItem(wItemID);
if (NULL == lpItem)
{
ERRLOG2(g_Log, "CID:%10u 아이템 생성에 실패하였습니다. ItemID:%d",
m_dwCID, wItemID);
return E_EVENT_AWARDITEM;
}
Item::CEquipment* lpEquip = Item::CEquipment::DowncastToEquipment(lpItem);
if (NULL != lpEquip)
{
if(lpEquip->AddRandomOption(eItemGrade, cBaseNum, GetMagicChancePoint()))
lpEquip->SetNewEquip(2);
else
lpEquip->SetNewEquip();
}
if (false == GiveItem(lpItem))
{
ERRLOG1(g_Log, "CID:%10u 퀘스트 보상으로 장비를 받는데 실패하였습니다.", m_dwCID);
DELETE_ITEM(lpItem);
}
GAMELOG::LogQuestGetReward(*this, executingQuest.m_QuestNode->m_wQuestID, lpItem,
0, 0, 0, 0, eReturnCode);
// GievItem 으로 스택된 경우
if (lpItem->IsSet(Item::DetailData::STACKABLE) && 0 == lpItem->GetNumOrDurability())
{
DELETE_ITEM(lpItem);
}
break;
}
case EVENT_AWARDITEMID:
{
Item::EquipType::OptionType eOptionType = static_cast<Item::EquipType::OptionType>(eventNode->m_dwEventNumber);
unsigned long nID = static_cast<unsigned long>(eventNode->m_dwEventAmount1);
Item::EquipType::Grade eItemGrade = static_cast<Item::EquipType::Grade>(eventNode->m_dwEventAmount2);
unsigned char cBaseNum = static_cast<unsigned char>(eventNode->m_dwEventAmount3);
unsigned short wItemID = static_cast<unsigned short>(nID);
if (0 == wItemID)
{
ERRLOG3(g_Log, "CID:%10u 퀘스트 보상으로 줄 아이템이 없습니다. ItemType:%d, ItemId:%d",
m_dwCID, eventNode->m_dwEventNumber, eventNode->m_dwEventAmount1);
return E_EVENT_AWARDITEM;
}
Item::CItem* lpItem = Item::CItemFactory::GetInstance().CreateItem(wItemID);
if (NULL == lpItem)
{
ERRLOG2(g_Log, "CID:%10u 아이템 생성에 실패하였습니다. ItemID:%d",
m_dwCID, wItemID);
return E_EVENT_AWARDITEM;
}
Item::CEquipment* lpEquip = Item::CEquipment::DowncastToEquipment(lpItem);
if (NULL != lpEquip)
{
if(lpEquip->AddRandomOption(eItemGrade, cBaseNum, GetMagicChancePoint()))
lpEquip->SetNewEquip(2);
else
lpEquip->SetNewEquip();
}
if (false == GiveItem(lpItem))
{
ERRLOG1(g_Log, "CID:%10u 퀘스트 보상으로 장비를 받는데 실패하였습니다.", m_dwCID);
DELETE_ITEM(lpItem);
}
GAMELOG::LogQuestGetReward(*this, executingQuest.m_QuestNode->m_wQuestID, lpItem,
0, 0, 0, 0, eReturnCode);
// GievItem 으로 스택된 경우
if (lpItem->IsSet(Item::DetailData::STACKABLE) && 0 == lpItem->GetNumOrDurability())
{
DELETE_ITEM(lpItem);
}
break;
}
case EVENT_AWARDITEMCLASS:
{
Item::EquipType::OptionType eOptionType = static_cast<Item::EquipType::OptionType>(eventNode->m_dwEventNumber);
unsigned char cClass = static_cast<unsigned char>(eventNode->m_dwEventAmount1);
unsigned char cLevel = static_cast<unsigned char>(eventNode->m_dwEventAmount2);
Item::EquipType::Grade eItemGrade = static_cast<Item::EquipType::Grade>(eventNode->m_dwEventAmount3);
unsigned char cBaseNum = static_cast<unsigned char>(eventNode->m_fPosX);
if (0 == cClass)
{
// 캐릭터의 레벨을 참조하는 경우
cLevel = static_cast<unsigned char>(m_DBData.m_Info.Class);
}
if (0 == cLevel)
{
// 캐릭터의 레벨을 참조하는 경우
cLevel = m_DBData.m_Info.Level;
}
unsigned short wItemID = AwardTable::CAward::GetInstance().GetQuestEquipmentClass(eOptionType, cClass, cLevel, this);
if (0 == wItemID)
{
ERRLOG3(g_Log, "CID:%10u 퀘스트 보상으로 줄 아이템이 없습니다. ItemType:%d, ItemLevel:%d",
m_dwCID, eventNode->m_dwEventNumber, eventNode->m_dwEventAmount1);
return E_EVENT_AWARDITEM;
}
Item::CItem* lpItem = Item::CItemFactory::GetInstance().CreateItem(wItemID);
if (NULL == lpItem)
{
ERRLOG2(g_Log, "CID:%10u 아이템 생성에 실패하였습니다. ItemID:%d",
m_dwCID, wItemID);
return E_EVENT_AWARDITEM;
}
Item::CEquipment* lpEquip = Item::CEquipment::DowncastToEquipment(lpItem);
if (NULL != lpEquip)
{
if(lpEquip->AddRandomOption(eItemGrade, cBaseNum, GetMagicChancePoint()))
lpEquip->SetNewEquip(2);
else
lpEquip->SetNewEquip();
}
if (false == GiveItem(lpItem))
{
ERRLOG1(g_Log, "CID:%10u 퀘스트 보상으로 장비를 받는데 실패하였습니다.", m_dwCID);
DELETE_ITEM(lpItem);
}
GAMELOG::LogQuestGetReward(*this, executingQuest.m_QuestNode->m_wQuestID, lpItem,
0, 0, 0, 0, eReturnCode);
// GievItem 으로 스택된 경우
if (lpItem->IsSet(Item::DetailData::STACKABLE) && 0 == lpItem->GetNumOrDurability())
{
DELETE_ITEM(lpItem);
}
break;
}
case EVENT_MOVE:
{
if (CServerSetup::GetInstance().GetServerZone() == eventNode->m_dwEventNumber)
{
Position newPos;
newPos.m_fPointX = eventNode->m_fPosX;
newPos.m_fPointY = eventNode->m_fPosY;
newPos.m_fPointZ = eventNode->m_fPosZ;
if (false == MovePos(newPos, static_cast<char>(eventNode->m_dwEventNumber), false))
{
ERRLOG5(g_Log, "CID:%10u 퀘스트 이벤트 존 이동에 실패했습니다. Zone:%d X:%f Y:%f Z:%f",
m_dwCID, eventNode->m_dwEventNumber, newPos.m_fPointX, newPos.m_fPointY, newPos.m_fPointZ);
}
}
else
{
POS newPos;
newPos.fPointX = eventNode->m_fPosX;
newPos.fPointY = eventNode->m_fPosY;
newPos.fPointZ = eventNode->m_fPosZ;
if (false == MoveZone(newPos, static_cast<char>(eventNode->m_dwEventNumber), -1))
{
ERRLOG5(g_Log, "CID:%10u 퀘스트 이벤트 존 이동에 실패했습니다. Zone:%d X:%f Y:%f Z:%f",
m_dwCID, eventNode->m_dwEventNumber, newPos.fPointX, newPos.fPointY, newPos.fPointZ);
}
}
break;
}
case EVENT_THEATERMODE:
{
break;
}
default:
{
ERRLOG2(g_Log, "CID:%10u 존재하지 않는 이벤트 종류입니다. 종류:%d", m_dwCID, eventNode->m_dwEventKind);
break;
}
}
}
if (false == vecItemGarbage.empty())
{
ClearGarbage(vecItemGarbage, PktDisappearItem::DIC_DEFAULT);
}
return S_SUCCESS;
}
void CCharacter::EventEnd(unsigned short wQuestID, bool bSave)
{
if (true == bSave)
{
int nIndex = 0;
for (; nIndex < MAX_HISTORY_QUEST; ++nIndex)
{
if (0 == m_wHistoryQuest[nIndex])
{
m_wHistoryQuest[nIndex] = wQuestID;
break;
}
}
if (MAX_HISTORY_QUEST == nIndex)
{
ERRLOG2(g_Log, "CID:%10u 퀘스트 히스토리에 저장된 퀘스트가 %d(MAX)개를 초과하였습니다.",
m_dwCID, MAX_HISTORY_QUEST);
}
// 최대 스킬 포인트를 늘여주는 퀘스트의 처리
QuestNode* lpQuest = CQuestMgr::GetInstance().GetQuestNode(m_wHistoryQuest[nIndex]);
if (NULL != lpQuest)
{
m_CreatureStatus.m_StatusInfo.m_wSkillPoint += lpQuest->m_cSkillPointBonus;
m_iAbilityPoint += lpQuest->m_cAbilityPointBonus;
}
}
// 퀘스트를 삭제한다.
CancelQuest(wQuestID, true);
}
void CCharacter::EventAward(unsigned long dwExp, unsigned long dwGold, unsigned long dwFame, unsigned long dwMileage)
{
IncrementExp(dwExp);
float fPt = GetAwardPer();
unsigned long dwGold1 = (unsigned long)((float)dwGold*fPt);
unsigned long dwFame1 = (unsigned long)((float)dwFame*fPt);
unsigned long dwMileage1 = (unsigned long)((float)dwMileage*fPt);
if (0 != dwGold1)
{
unsigned long dwSrcGold = GetGold();
AddGold(dwGold1, true);
GAMELOG::LogTakeGold(*this, dwSrcGold, GetGold(), dwGold1,
TakeType::TS_INVEN, TakeType::TS_INVEN, GAMELOG::sTakeGoldLogV2::QUEST_AWARD, 0);
}
SetFame(GetFame() + dwFame1);
SetMileage(GetMileage() + dwMileage1);
if (NULL != m_lpGameClientDispatch)
{
GameClientSendPacket::SendCharFameInfo(m_lpGameClientDispatch->GetSendStream(), this,
"", "", 0, 0, PktFIAck::FAME_INFO, PktBase::NO_SERVER_ERR);
}
}
bool CCharacter::ClearGarbage(vector<Item::ItemGarbage>& vecItemGarbage, unsigned char cCmd)
{
// 돈
unsigned long dwGold = vecItemGarbage[0].m_dwRemainNum;
if (0 != dwGold)
{
unsigned long dwSrcGold = GetGold();
if (false == DeductGold(dwGold, false))
{
return false;
}
else
{
GAMELOG::LogTakeGold(*this, dwSrcGold, GetGold(), dwGold,
TakeType::TS_INVEN, TakeType::TS_INVEN, GAMELOG::sTakeGoldLogV2::QUEST_DEDUCT, 0);
}
if (NULL != m_lpGameClientDispatch)
{
GameClientSendPacket::SendCharTakeGold(m_lpGameClientDispatch->GetSendStream(),
m_dwCID, dwGold, TakeType::TS_INVEN, TakeType::TS_NONE, 0);
}
}
// 아이템
for (size_t nIndex = vecItemGarbage.size() - 1; nIndex > 0; --nIndex)
{
Item::CItem* lpItem = vecItemGarbage[nIndex].m_lpItem;
unsigned char cRemainNum = static_cast<unsigned char>(vecItemGarbage[nIndex].m_dwRemainNum);
if (NULL != m_lpGameClientDispatch)
{
GameClientSendPacket::SendCharDisappearItem(m_lpGameClientDispatch->GetSendStream(),
m_dwCID, lpItem->GetPos(), cRemainNum, cCmd, PktBase::NO_SERVER_ERR);
}
if (0 == cRemainNum)
{
RemoveItem(lpItem->GetPos());
DELETE_ITEM(lpItem);
}
else
{
lpItem->SetNumOrDurability(cRemainNum);
}
vecItemGarbage.pop_back();
}
return true;
}
bool CCharacter::HasExecutingQuest(unsigned short wQuestID)
{
for (int nIndex = 0; nIndex < MAX_EXECUTING_QUEST; ++nIndex)
{
if (NULL != m_ExecutingQuest[nIndex].m_QuestNode)
{
if (m_ExecutingQuest[nIndex].m_QuestNode->m_wQuestID == wQuestID)
{
return true;
}
}
}
return false;
}
bool CCharacter::HasHistoryQuest(unsigned short wQuestID)
{
for (int nIndex = 0; nIndex < MAX_HISTORY_QUEST; ++nIndex)
{
if (m_wHistoryQuest[nIndex] == wQuestID)
{
return true;
}
}
return false;
}
bool CCharacter::InsertHistoryQuest(unsigned short wQuestID)
{
if (0 != m_wHistoryQuest[MAX_HISTORY_QUEST-1]) { return false; }
for (int nIndex = MAX_HISTORY_QUEST-1; nIndex > 0; --nIndex)
{
m_wHistoryQuest[nIndex] = m_wHistoryQuest[nIndex-1];
}
m_wHistoryQuest[0] = wQuestID;
return true;
}
bool CCharacter::DeleteHistoryQuest(unsigned short wQuestID)
{
if (false == HasHistoryQuest(wQuestID)) { return false; }
for (int nIndex = 0; nIndex < MAX_HISTORY_QUEST; ++nIndex)
{
if (wQuestID == m_wHistoryQuest[nIndex])
{
if (nIndex == MAX_HISTORY_QUEST - 1)
{
m_wHistoryQuest[nIndex] = 0;
}
else
{
std::copy(&m_wHistoryQuest[nIndex + 1], &m_wHistoryQuest[MAX_HISTORY_QUEST - 1], &m_wHistoryQuest[nIndex]);
m_wHistoryQuest[MAX_HISTORY_QUEST - 1] = 0;
}
return true;
}
}
return false;
}
void CCharacter::CalculateStatusByQuest(void)
{
// 2009.02.21 해당 문제를 위로 올려 장비창에서 영향을 받게한다.
// 현재 장비창에 있는 정보를 m_CreatureStatus.m_StatusInfo로 업데이트 하게 되어있는데
// m_CreatureStatus.m_StatusInfo 이값은 여기서 1번만 적용이 되고
// 나중에 스펠에 의해 능력치가 + 될때에는 CalculateStatusByQuest() 이함수가 호출되지 않기 때문에 값이 넘어가게된다.
// 퀘스트에 의해 받는 영향을 계산
// 어빌리티는 100% 퀘스트에 의해 영향을 받는다.
m_iAbilityPoint = 0;
m_iAbilityPoint += m_iAdminAbilityPoint;
for (int nIndex = 0; nIndex < MAX_HISTORY_QUEST; ++nIndex)
{
QuestNode* lpQuest = CQuestMgr::GetInstance().GetQuestNode(m_wHistoryQuest[nIndex]);
if (NULL != lpQuest)
{
m_EquipStatus.m_wSkillPoint += lpQuest->m_cSkillPointBonus;
m_iAbilityPoint += lpQuest->m_cAbilityPointBonus;
// m_CreatureStatus.m_StatusInfo.m_wSkillPoint += lpQuest->m_cSkillPointBonus;
}
}
}