595 lines
18 KiB
C++
595 lines
18 KiB
C++
#include "stdafx.h"
|
|
#include "./PlayerManager.h"
|
|
|
|
#include <Macro.h>
|
|
#include <PacketStruct_DG.h>
|
|
#include <ServerSession.h>
|
|
#include <ServerOptionParserEx.h>
|
|
|
|
#include "Player.h"
|
|
#include "ObjectFactory.h"
|
|
#include "StatusManager.h"
|
|
#include "GameZone.h"
|
|
|
|
#include "WareHouseSlotContainer.h"
|
|
#include "GuildWareHouseSlotContainer.h"
|
|
#ifdef _NA_1419_20100121_ACCUMULATE_POINT
|
|
#include "AccumulatePoint.h"
|
|
#endif
|
|
|
|
#ifdef _NA_0_20110613_GAME_SHOP_RENEWAL
|
|
#include "./GameShop/GameShop.Manager.h"
|
|
#endif
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
#if SUN_CODE_DELETE_RESERVED
|
|
// __PLAYER_OBJECT_VERIFY_CODE__
|
|
static struct sPlayerVerifyCodes
|
|
{
|
|
DWORD dwCodeOfPlayer;
|
|
DWORD dwCodeOfSCPlayer;
|
|
DWORD dwCodeOfIGridObject;
|
|
} s_PlayerVerifyCodes;
|
|
|
|
static union s_UNIONPLAYERCODE
|
|
{
|
|
Player* player;
|
|
Character* pCharacter;
|
|
//SCPlayer* pSCPlayer;
|
|
IGridObject* pIGridObject;
|
|
//
|
|
DWORD* pdwVerifyCode;
|
|
} s_PCODE;
|
|
|
|
static Player* s_pActivePlayerInUpdate = NULL;
|
|
|
|
static BOOL GenerateObjectOfPlayer()
|
|
{
|
|
Player* player = new Player();
|
|
s_PCODE.player = player;
|
|
s_PlayerVerifyCodes.dwCodeOfPlayer = *s_PCODE.pdwVerifyCode;
|
|
|
|
//s_PCODE.pSCPlayer = dynamic_cast<SCPlayer*>(player);
|
|
//s_PlayerVerifyCodes.dwCodeOfSCPlayer = *s_PCODE.pdwVerifyCode;
|
|
|
|
s_PCODE.pIGridObject = dynamic_cast<IGridObject*>(player);
|
|
s_PlayerVerifyCodes.dwCodeOfIGridObject = *s_PCODE.pdwVerifyCode;
|
|
|
|
if (!s_PlayerVerifyCodes.dwCodeOfPlayer)
|
|
return false;
|
|
|
|
delete player;
|
|
|
|
return true;
|
|
}
|
|
|
|
Player* Player::VerifyNRestoreTHIS(Player* player)
|
|
{
|
|
DWORD dwResult = 0;
|
|
|
|
// 플레이어가 유효한 메모리 상태인지 체크1
|
|
if (IsBadWritePtr(player, sizeof(*player)))
|
|
{
|
|
SUNLOG(eCRITICAL_LOG, _T("Invalid MemPtr %08X\n"), (DWORD)(__int64)player);
|
|
return NULL;
|
|
}
|
|
|
|
if (eVERIFY_MAGICCODE_INIT != player->m_StartCheckCode)
|
|
dwResult |= 0x01;
|
|
if (eVERIFY_MAGICCODE_INIT != player->m_StartInitCode)
|
|
dwResult |= 0x02;
|
|
if (eVERIFY_MAGICCODE_INIT != player->m_EndCheckCode)
|
|
dwResult |= 0x04;
|
|
if (eVERIFY_MAGICCODE_INIT != player->m_EndInitCode)
|
|
dwResult |= 0x08;
|
|
|
|
s_PCODE.player = player;
|
|
if (s_PlayerVerifyCodes.dwCodeOfPlayer != *s_PCODE.pdwVerifyCode)
|
|
{
|
|
dwResult |= 0x10;
|
|
*s_PCODE.pdwVerifyCode = s_PlayerVerifyCodes.dwCodeOfPlayer;
|
|
}
|
|
|
|
s_PCODE.pIGridObject = dynamic_cast<IGridObject*>(player);
|
|
if (s_PlayerVerifyCodes.dwCodeOfIGridObject != *s_PCODE.pdwVerifyCode)
|
|
{
|
|
dwResult |= 0x20;
|
|
*s_PCODE.pdwVerifyCode = s_PlayerVerifyCodes.dwCodeOfIGridObject;
|
|
}
|
|
|
|
//s_PCODE.pSCPlayer = dynamic_cast<SCPlayer*>(player);
|
|
//if (s_PlayerVerifyCodes.dwCodeOfSCPlayer != *s_PCODE.pdwVerifyCode)
|
|
//{
|
|
// dwResult |= 0x40;
|
|
// *s_PCODE.pdwVerifyCode = s_PlayerVerifyCodes.dwCodeOfSCPlayer;
|
|
//}
|
|
|
|
if (dwResult)
|
|
{
|
|
MessageOutEx(g_pSunLog, CSunLog::cCRITICAL_LOG, CSunLog::cOUTPUT_FILE, CSunLog::cCATE_DEFAULT,
|
|
_T("Corrupted Object Restoration REASON [%08X]"), dwResult);
|
|
return NULL;
|
|
}
|
|
|
|
return player;
|
|
}
|
|
// __PLAYER_OBJECT_VERIFY_CODE__
|
|
#endif //SUN_CODE_DELETE_RESERVED
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
|
|
//==================================================================================================
|
|
//
|
|
|
|
PlayerManager::PlayerManager()
|
|
: sector_delay_interval_(0)
|
|
, object_key_stream_(new ObjectKeyStream())
|
|
{
|
|
static BOOL bRet = GenerateObjectOfPlayer();
|
|
#ifdef _NA_007932_20150112_ATTENDANCE_CHECK_SYSTEM
|
|
m_attendance_day = 0;
|
|
#endif //_NA_007932_20150112_ATTENDANCE_CHECK_SYSTEM
|
|
}
|
|
|
|
PlayerManager::~PlayerManager()
|
|
{
|
|
SAFE_DELETE(object_key_stream_);
|
|
}
|
|
|
|
//==================================================================================================
|
|
//
|
|
//
|
|
|
|
|
|
void PlayerManager::Update(const util::ServerTimeInfoPerFrame* time_info)
|
|
{
|
|
// sector delay interval (0.8 second)
|
|
const DWORD kUpdateDelayTime = 800;
|
|
;{
|
|
sector_delay_interval_ += time_info->delta_tick_;
|
|
m_dwDeltaTick = time_info->delta_tick_;
|
|
};
|
|
;{
|
|
m_SectorUpdateOnUpdate = false;
|
|
if (sector_delay_interval_ > kUpdateDelayTime)
|
|
{
|
|
m_SectorUpdateOnUpdate = true;
|
|
sector_delay_interval_ -= kUpdateDelayTime;
|
|
}
|
|
};
|
|
#ifdef _NA_1419_20100121_ACCUMULATE_POINT
|
|
AccumulatePoint::Calculate_Current_Year(time_info->delta_tick_);
|
|
#endif
|
|
|
|
for (PLAYER_HASHMAP::iterator it = user_guid_table_.begin(); it != user_guid_table_.end(); ++it)
|
|
{
|
|
Player* const player = it->second;
|
|
if (player == NULL || player->IsDeleted())
|
|
{
|
|
continue;
|
|
}
|
|
GameZone* entered_zone = NULL;
|
|
MAPCODE entered_field_code = 0;
|
|
const eZONE_STATE room_state = player->GetGameZone(entered_zone, entered_field_code);
|
|
if (room_state & ePRS_AT_ZONE)
|
|
{
|
|
player->Update(time_info, m_SectorUpdateOnUpdate);
|
|
};
|
|
}
|
|
|
|
#ifdef _NA_007932_20150112_ATTENDANCE_CHECK_SYSTEM
|
|
AttendanceUpdate(time_info);
|
|
#endif // _NA_007932_20150112_ATTENDANCE_CHECK_SYSTEM
|
|
}
|
|
|
|
//─────────────────────────────────────────────────────────────────────
|
|
//
|
|
//
|
|
|
|
void PlayerManager::Disconnect(Player* player)
|
|
{
|
|
//_NA_008506_20150923_LOG_LOGOUT_IN_GAMESERVER
|
|
GAMELOG->LogLogout(player);
|
|
|
|
if (player->IsDeleted())
|
|
{
|
|
SUNLOG(
|
|
eCRITICAL_LOG,
|
|
_T("|%s|%d|Player is already deleted.|UserGuid = %d|"),
|
|
__FUNCTION__,
|
|
__LINE__,
|
|
player->GetUserGuid()
|
|
);
|
|
}
|
|
#ifdef _NA_0_20110613_GAME_SHOP_RENEWAL
|
|
GameShop::Manager::Instance()->CloseShop(player);
|
|
#endif
|
|
ReleasePlayer(player);
|
|
RemovePlayer(player);
|
|
}
|
|
|
|
|
|
void PlayerManager::ReleasePlayer(Player* player)
|
|
{
|
|
if (player->IsDeleted())
|
|
{
|
|
SUNLOG(
|
|
eCRITICAL_LOG,
|
|
_T("|%s|%d|Player is already deleted.|UserGuid = %d|"),
|
|
__FUNCTION__,
|
|
__LINE__,
|
|
player->GetUserGuid()
|
|
);
|
|
}
|
|
GameZone * pEnterZone = NULL;
|
|
MAPCODE EnterFieldCode = 0;
|
|
|
|
eZONE_STATE eRoomState = player->GetGameZone(pEnterZone, EnterFieldCode);
|
|
if (eRoomState & ePRS_AT_ZONE)
|
|
{
|
|
pEnterZone->Leave(player);
|
|
if ((eRoomState == ePRS_AT_FIELD || eRoomState == ePRS_AT_DOMINATION_FIELD)
|
|
&& player->GetHP() == 0
|
|
&& player->GetPreVillageMapCode() != 0) //현재 필드에서 죽은 상태에서 종료한다면..
|
|
player->SetStartMapCode(player->GetPreVillageMapCode()); //이전마을 위치로 저장한다.
|
|
}
|
|
|
|
MSG_DG_CHARINFO_UPDATE_TRANSACTION_CONTROL_SYN msg_transaction;
|
|
msg_transaction.m_byTransControl = msg_transaction.DBUPDATE_LOCKING;
|
|
msg_transaction.char_guid_ = player->GetCharGuid(); // CHANGES: f110412.2L
|
|
player->SendToGameDBPServer(&msg_transaction, sizeof(msg_transaction));
|
|
|
|
player->SerializeInfoToDBProxy(UPDATE_FACTOR_LOGOUT);
|
|
|
|
// 로그아웃시 인벤토리 정보 로그 남기기.
|
|
GAMELOG->LogSnapShot(player);
|
|
|
|
MSG_DG_CHARINFO_DISCONNECT_CMD CharInfoMsg;
|
|
CharInfoMsg.m_ChannelID = g_pGameServer->GetKey().GetChannelID();
|
|
player->SendToGameDBPServer(&CharInfoMsg, sizeof(CharInfoMsg));
|
|
}
|
|
|
|
//==================================================================================================
|
|
//
|
|
void PlayerManager::AllDisconnect()
|
|
{
|
|
// Agent와 연결이 끊어지는 경우에만 사용된다.
|
|
ObjectFactory* pObjectFactory = ObjectFactory::Instance();
|
|
FOREACH_CONTAINER(const PLAYER_HASHMAP::value_type& rNode, user_guid_table_, PLAYER_HASHMAP)
|
|
{
|
|
Player* const player = rNode.second;
|
|
ReleasePlayer(player);
|
|
pObjectFactory->FreeObject(player);
|
|
}
|
|
|
|
object_key_stream_->Clear();
|
|
user_guid_table_.clear();
|
|
char_guid_table_.clear();
|
|
char_name_table_.clear();
|
|
}
|
|
|
|
//─────────────────────────────────────────────────────────────────────
|
|
// <통계 관련>
|
|
//
|
|
|
|
// 사용자 평균 보유 금액 구한다
|
|
MONEY PlayerManager::GetAveUserTotalMoney() const
|
|
{
|
|
MONEY ui64AveUserMoney = 0;
|
|
ULONGLONG ui64UserNum = (ULONGLONG)user_guid_table_.size();
|
|
|
|
// 0으로 나누기 방지
|
|
if (0 == ui64UserNum)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ui64AveUserMoney = GetUserTotalMoney();
|
|
ui64AveUserMoney = MONEY(ui64AveUserMoney / ui64UserNum);
|
|
|
|
return ui64AveUserMoney;
|
|
}
|
|
|
|
MONEY PlayerManager::GetUserTotalMoney() const
|
|
{
|
|
MONEY total_money = 0;
|
|
for (PLAYER_HASHMAP::const_iterator it = user_guid_table_.begin();
|
|
it != user_guid_table_.end(); ++it)
|
|
{
|
|
const Player* const player = it->second;
|
|
if (player == NULL || player->IsDeleted())
|
|
{
|
|
continue;
|
|
}
|
|
total_money += player->GetMoney();
|
|
}
|
|
return total_money;
|
|
}
|
|
|
|
// 유저 평균 통화량 계산
|
|
MONEY PlayerManager::GetAveWarehouseTotalMoney() const
|
|
{
|
|
MONEY ui64AveUserMoney = 0;
|
|
ULONGLONG ui64UserNum = (ULONGLONG)user_guid_table_.size();
|
|
|
|
// 0으로 나누기 방지
|
|
if (0 == ui64UserNum)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ui64AveUserMoney = GetWarehouseTotalMoney();
|
|
ui64AveUserMoney = MONEY(ui64AveUserMoney / ui64UserNum);
|
|
|
|
return ui64AveUserMoney;
|
|
}
|
|
|
|
MONEY PlayerManager::GetWarehouseTotalMoney() const
|
|
{
|
|
MONEY total_money = 0;
|
|
for (PLAYER_HASHMAP::const_iterator it = user_guid_table_.begin();
|
|
it != user_guid_table_.end(); ++it)
|
|
{
|
|
const Player* const player = it->second;
|
|
if (player == NULL || player->IsDeleted())
|
|
{
|
|
continue;
|
|
}
|
|
if (player->GetWarehouseContainer()->IsStarted())
|
|
{
|
|
total_money += player->GetWarehouseContainer()->GetCurMoney();
|
|
}
|
|
}
|
|
return total_money;
|
|
}
|
|
|
|
//==================================================================================================
|
|
//
|
|
//
|
|
bool PlayerManager::AddPlayer(Player* player)
|
|
{
|
|
// 플레이어의 생성 주기 시작
|
|
player->Init();
|
|
|
|
const DWORD ERR_INVALID_KEY = 10;
|
|
const DWORD ERR_SYNCHRONIZE_FAULT = 20;
|
|
const DWORD ERR_ALREADY_EXIST_PLAYER = 30;
|
|
|
|
const DWORD user_guid = player->GetUserKey();
|
|
const DWORD object_key = player->GetObjectKey();
|
|
|
|
FlowControl flow;
|
|
FLOWCONTROL_START()
|
|
{
|
|
// start LOGIC_BLOCK
|
|
IfFailThenBreak(user_guid && object_key, ERR_INVALID_KEY);
|
|
|
|
const ObjectKeyStream::sNode* object_node = object_key_stream_->GetObjectInfo(object_key);
|
|
IfFailThenBreak(object_node && object_node->pObject == 0, ERR_SYNCHRONIZE_FAULT);
|
|
|
|
IfFailThenBreak(user_guid_table_.find(user_guid) == user_guid_table_.end(),
|
|
ERR_ALREADY_EXIST_PLAYER);
|
|
|
|
user_guid_table_.insert(PLAYER_HASHMAP::value_type(user_guid, player));
|
|
object_key_stream_->AddObjectInfo(object_key, player, PlayerObjectKeyStreamAddin());
|
|
|
|
return true;
|
|
}
|
|
FLOWCONTROL_END;
|
|
|
|
switch (flow.GetErrorCode())
|
|
{
|
|
case ERR_INVALID_KEY:
|
|
case ERR_SYNCHRONIZE_FAULT:
|
|
case ERR_ALREADY_EXIST_PLAYER:
|
|
SUNLOG(eCRITICAL_LOG, _T("[%s][Fail][UserKey:%u] %s"),
|
|
__FUNCTION__, user_guid, flow.GetErrorString());
|
|
break;
|
|
default:
|
|
SUNLOG(eCRITICAL_LOG, _T("[%s][Fail] Unexpected Result = %s"),
|
|
__FUNCTION__, flow.GetErrorString());
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//==================================================================================================
|
|
|
|
Player* PlayerManager::FindPlayerByName(const char* char_name) const
|
|
{
|
|
//__NA003373_20090402_NOT_FIND_ENGLISHNAME_BUG
|
|
char lower_char_name[MAX_CHARNAME_LENGTH + 1];
|
|
strncpy(lower_char_name, char_name, _countof(lower_char_name));
|
|
lower_char_name[_countof(lower_char_name) - 1] = '\0';
|
|
_strlwr(lower_char_name);
|
|
|
|
ulong hash_value = util::StreamHashKey::GetStringHashKey(lower_char_name);
|
|
PLAYER_NAME_HASH::const_iterator found_it = char_name_table_.find(hash_value);
|
|
if (found_it == char_name_table_.end()) {
|
|
return NULL;
|
|
};
|
|
const DWORD object_key = found_it->second;
|
|
const ObjectKeyStream::sNode* object_node = object_key_stream_->GetObjectInfo(object_key);
|
|
if (object_node == NULL) {
|
|
return NULL;
|
|
};
|
|
Player* const player = object_node->pObject;
|
|
if (player != NULL && player->IsDeleted())
|
|
{
|
|
return NULL;
|
|
}
|
|
return player;
|
|
//
|
|
}
|
|
|
|
bool PlayerManager::AddPlayerName(Player* player)
|
|
{
|
|
player->GetLoadingCheckTimer().SetTimer(
|
|
ServerOptionParserEx::Instance()->GetServerOption().m_wABUSE_MIN_CLIENT_LOADING_TIME);
|
|
|
|
const DWORD user_guid = player->GetUserKey();
|
|
const char* char_name = player->GetCharName();
|
|
//----------------------------------------------------------------------------------------------
|
|
// CharGuid도 존재
|
|
const DWORD object_key = player->GetObjectKey();
|
|
const DWORD char_guid = player->GetCharGuid();
|
|
if (object_key_stream_->UpdateObjectAddinInfo(object_key,
|
|
PlayerObjectKeyStreamAddin(char_guid)) == false)
|
|
{
|
|
SUNLOG(eFULL_LOG, "[%s]PlayerCharGuid[%s][%u][charguid:%u]",
|
|
__FUNCTION__, char_name, user_guid, char_guid);
|
|
return false;
|
|
}
|
|
char_guid_table_[char_guid] = object_key;
|
|
//----------------------------------------------------------------------------------------------
|
|
char lower_char_name[MAX_CHARNAME_LENGTH + 1];
|
|
strncpy(lower_char_name, char_name, _countof(lower_char_name));
|
|
lower_char_name[_countof(lower_char_name) - 1] = '\0';
|
|
_strlwr(lower_char_name);
|
|
|
|
const ulong hash_value = util::StreamHashKey::GetStringHashKey(lower_char_name);
|
|
player->char_name_hash_ = 0;
|
|
PLAYER_NAME_HASH::iterator found_it = char_name_table_.find(hash_value);
|
|
if (found_it != char_name_table_.end())
|
|
{
|
|
SUNLOG(eCRITICAL_LOG, "[%s][CharGuid=%u]PlayerName exist [%s]",
|
|
__FUNCTION__, user_guid, char_name);
|
|
return false;
|
|
};
|
|
player->char_name_hash_ = hash_value;
|
|
char_name_table_[hash_value] = object_key;
|
|
return true;
|
|
}
|
|
|
|
//==================================================================================================
|
|
|
|
bool PlayerManager::RemovePlayer(Player* player)
|
|
{
|
|
const DWORD ERR_INVALID_USERKEY = 10;
|
|
const DWORD ERR_CANT_FIND_USER = 20;
|
|
|
|
// 플레이어의 생성 주기 삭제
|
|
const DWORD user_guid = player->GetUserKey();
|
|
const DWORD object_key = player->GetObjectKey();
|
|
const DWORD char_guid = player->GetCharGuid();
|
|
|
|
FlowControl flow;
|
|
FLOWCONTROL_START()
|
|
{
|
|
// start LOGIC_BLOCK
|
|
IfFailThenBreak(user_guid && object_key, ERR_INVALID_USERKEY);
|
|
|
|
PLAYER_HASHMAP::iterator found_it = user_guid_table_.find(user_guid);
|
|
IfFailThenBreak(found_it != user_guid_table_.end(), ERR_CANT_FIND_USER);
|
|
|
|
user_guid_table_.erase(found_it);
|
|
object_key_stream_->RemoveObjectInfo(object_key);
|
|
if (char_guid) {
|
|
char_guid_table_.erase(char_guid);
|
|
};
|
|
const DWORD char_name_hash = player->GetCharNameHash();
|
|
if (char_name_hash) {
|
|
char_name_table_.erase(char_name_hash);
|
|
};
|
|
ObjectFactory::Instance()->FreeObject(player);
|
|
|
|
return true;
|
|
}
|
|
FLOWCONTROL_END;
|
|
//----------------------------------------------------------------------------------------------
|
|
switch (flow.GetErrorCode())
|
|
{
|
|
case ERR_INVALID_USERKEY: case ERR_CANT_FIND_USER:
|
|
SUNLOG(eCRITICAL_LOG, _T("[%s][Fail][UserKey:%u] '%s'"),
|
|
__FUNCTION__, user_guid, flow.GetErrorString());
|
|
break;
|
|
default:
|
|
SUNLOG(eCRITICAL_LOG, _T("[%s][Fail] Unexpected Result = '%s'"),
|
|
__FUNCTION__, flow.GetErrorString());
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef _NA_007919_20141211_ADD_GMCOMMAND_EVENT
|
|
VOID PlayerManager::SendPacketToAllPlayer(MSG_BASE_FORWARD* msg, size_t size,bool single)
|
|
{
|
|
if (GetNumberOfPlayers() == 0) {
|
|
return ;
|
|
}
|
|
|
|
PLAYER_HASHMAP::iterator itr = user_guid_table_.begin();
|
|
|
|
while (itr != user_guid_table_.end() )
|
|
{
|
|
Player* player = itr->second;
|
|
if (player == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
player->SendPacket(msg, size);
|
|
if(single){
|
|
return;
|
|
}
|
|
++itr;
|
|
}
|
|
}
|
|
#endif //_NA_007919_20141211_ADD_GMCOMMAND_EVENT
|
|
|
|
|
|
|
|
VOID PlayerManager::SendGmNotice(TCHAR *notice, eNOTICE_TYPE type){ //xiaomi 警속홍륩무멩
|
|
|
|
|
|
|
|
MSG_AG_GM_NOTICE_CMD msg_cmd;
|
|
msg_cmd.m_eType = type;
|
|
|
|
memcpy( msg_cmd.m_tszNoticeMsg,notice,MAX_NOTICE_LEN + 1);
|
|
msg_cmd.m_wLen = _countof( msg_cmd.m_tszNoticeMsg);
|
|
|
|
|
|
|
|
|
|
SendPacketToAllPlayer(&msg_cmd,msg_cmd.GetSize(),true);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
#ifdef _NA_007932_20150112_ATTENDANCE_CHECK_SYSTEM
|
|
void PlayerManager::AttendanceUpdate(const util::ServerTimeInfoPerFrame* time_info)
|
|
{
|
|
if (m_attendance_day != time_info->system_time.wDay)
|
|
{
|
|
m_attendance_day = time_info->system_time.wDay;
|
|
for (PLAYER_HASHMAP::iterator it = user_guid_table_.begin(); it != user_guid_table_.end(); ++it)
|
|
{
|
|
Player* const player = it->second;
|
|
if (player == NULL || player->IsDeleted())
|
|
{
|
|
continue;
|
|
}
|
|
if (player->GetExtraInfoManager().GetAttendanceManager().Attend(
|
|
time_info->system_time.wYear,
|
|
time_info->system_time.wMonth,
|
|
time_info->system_time.wDay - 1,
|
|
player) == true)
|
|
{
|
|
MSG_CG_CHARINFO_ATTENDANCE_INFO_PACKET msg;
|
|
memcpy(&msg.attendance_infos,
|
|
&player->GetExtraInfoManager().GetAttendanceManager().GetAttendanceInfos(),
|
|
sizeof(msg.attendance_infos));
|
|
player->SendPacket(&msg, sizeof(msg));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // _NA_007932_20150112_ATTENDANCE_CHECK_SYSTEM
|