Files
Client/Server/RylServerProject/RylGameLibrary/Creature/Character/CharacterLoginOut.cpp
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

911 lines
30 KiB
C++

#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);
}
}