Files
2026-06-01 12:46:52 +02:00

1868 lines
69 KiB
C++

#include <toolkit/XEnv.h>
#include <dump/XExceptionHandler.h>
#include <mmo/ArcadiaServer.h>
#include "LogClient/LogClient.h"
#include "ErrorCode/ErrorCode.h"
#include "HuntaholicManager.h"
#include "SendMessage.h"
#include "PartyManager.h"
#include "RankingManager.h"
#include "NPCProc.h"
#include "GameProc.h"
#include "StructFieldProp.h"
#include "DB_Commands.h"
extern __declspec( thread ) XSEH::THREAD_INFO s_ThreadInfo;
struct WarpFunctor : public PartyManager::PartyFunctor
{
WarpFunctor( const AR_UNIT _x, const AR_UNIT _y, const unsigned char _layer, const AR_TIME _begin_time = 0 )
: x( _x )
, y( _y )
, layer( _layer )
, begin_time( _begin_time )
{}
virtual bool operator()( AR_HANDLE handle )
{
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( !pPlayer )
return false;
PendWarpPlayer( pPlayer );
if( begin_time )
{
TS_SC_HUNTAHOLIC_BEGIN_HUNTING msg;
msg.begin_time = begin_time;
PendMessage( pPlayer, &msg );
}
return true;
}
void PendWarpPlayer( StructPlayer * pPlayer )
{
// 랜덤 위치 설정
AR_UNIT targetX, targetY;
bool bValidPos = true;
unsigned short try_cnt = 0;
do
{
targetX = XRandom( x - 60, x + 60 );
targetY = XRandom( y - 60, y + 60 );
if( ++try_cnt > 300 )
{
bValidPos = false;
break;
}
} while( GameContent::CollisionToLine( x, y, targetX, targetY ) );
if( !bValidPos )
{
targetX = x;
targetY = y;
if( GameContent::IsBlocked( targetX, targetY ) )
{
FILELOG( "Unable to PendWarpPlayer (%f, %f)", targetX, targetY );
_cprint( "Unable to PendWarpPlayer (%f, %f)\n", targetX, targetY );
}
}
// 파티 락 걸린 상태로 호출되기도 하는 곳이므로 지역락 걸지 않음
pPlayer->PendWarp( targetX, targetY, layer );
ArcadiaServer::Instance().SetObjectPriority( pPlayer, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
}
AR_UNIT x;
AR_UNIT y;
unsigned char layer;
AR_TIME begin_time;
};
// HuntaholicManager::InstanceDungeon
void HuntaholicManager::InstanceDungeon::onMonsterDelete( StructMonster * pMonster )
{
// 사냥이 종료된 이후면 아무것도 하지 않음
if( bEnd || nScore >= pHuntaholicInfo->pHuntaholicBase->nMaxPoint )
return;
THREAD_SYNCHRONIZE( csInstance );
// 사냥이 종료된 이후면 아무것도 하지 않음(락 걸고 나서 한 번 더 검사)
if( bEnd || nScore >= pHuntaholicInfo->pHuntaholicBase->nMaxPoint )
return;
AR_TIME t = GetArTime();
int nRespawnID = pMonster->GetInstanceRespawnID();
std::vector< StructMonster * >::iterator it = std::find( vRespawnedMonster.begin(), vRespawnedMonster.end(), pMonster );
// 이미 삭제 처리 된 몬스터면 점수 카운트 및 리젠 진행시키지 않음(사냥 진행 중인데 사망 처리가 2번 들어왔거나 리젠만 되고 vRespawnedMonster에 추가되지 않았거나... -_ -?)
if( it == vRespawnedMonster.end() )
{
assert( 0 );
return;
}
vRespawnedMonster.erase( it );
// 시간제로 소환되었던 몬스터가 시간 제한에 의해 사라지는 경우에는 점수 가산, 리젠 예약 처리를 하지 않음
// * 스킬에 의해 소환되었어도 영구 몹이면 포인트 가산 처리 함(단, 리젠은 안 됨. 현재 함수 아래쪽의 리젠 처리 부분 참고)
if( pMonster->IsLifeTimeOver() )
return;
int nMonsterScore = 0;
switch( pMonster->GetMonsterType() )
{
case MonsterBase::MONSTER_TYPE_HUNTAHOLIC_1LV:
nMonsterScore = GameRule::HUNTAHOLIC_SCORE_FOR_LV1_MONSTER;
break;
case MonsterBase::MONSTER_TYPE_HUNTAHOLIC_2LV:
nMonsterScore = GameRule::HUNTAHOLIC_SCORE_FOR_LV2_MONSTER;
break;
case MonsterBase::MONSTER_TYPE_HUNTAHOLIC_3LV:
nMonsterScore = GameRule::HUNTAHOLIC_SCORE_FOR_LV3_MONSTER;
break;
case MonsterBase::MONSTER_TYPE_HUNTAHOLIC_BOSS:
nMonsterScore = GameRule::HUNTAHOLIC_SCORE_FOR_BOSS_MONSTER;
break;
default:
assert( 0 );
return;
}
// 개인별 점수 추가
{
AR_HANDLE hKiller = NULL;
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pMonster ) );
hKiller = pMonster->GetMaxDamageDealer();
}
if( hKiller )
{
_SCORE_TAG * pScoreTag = getScoreTag( hKiller );
if( !pScoreTag )
{
vScoreTag.push_back( _SCORE_TAG( hKiller ) );
pScoreTag = &vScoreTag.back();
}
++( pScoreTag->nKillCount );
pScoreTag->nScore += nMonsterScore;
}
}
// 방 전체 점수 추가
++nKillCount;
nScore += nMonsterScore;
// 최대 점수 도달 시 처리
bool bMaxPointReached = nScore >= pHuntaholicInfo->pHuntaholicBase->nMaxPoint;
if( bMaxPointReached )
{
nScore = pHuntaholicInfo->pHuntaholicBase->nMaxPoint;
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockArea( pHuntaholicInfo->pHuntaholicBase->nDungeonRegionLeft, pHuntaholicInfo->pHuntaholicBase->nDungeonRegionTop,
pHuntaholicInfo->pHuntaholicBase->nDungeonRegionRight, pHuntaholicInfo->pHuntaholicBase->nDungeonRegionBottom, nInstanceNo ) );
clearMonsters();
clearHealingProps();
}
struct MaxPointAchievementBroadcaster : public PartyManager::PartyFunctor
{
virtual bool operator ()( AR_HANDLE handle )
{
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( pPlayer )
PendMessage( pPlayer, &msg );
return true;
}
TS_SC_HUNTAHOLIC_MAX_POINT_ACHIEVED msg;
} fo;
PartyManager::GetInstance().DoEachMember( nPartyID, fo );
}
// 점수 방송
{
TS_SC_HUNTAHOLIC_UPDATE_SCORE msg;
msg.score = nScore;
struct ScoreBroadcaster : public PartyManager::PartyFunctor
{
ScoreBroadcaster( const HuntaholicManager::InstanceDungeon * pInstance, TS_SC_HUNTAHOLIC_UPDATE_SCORE * pMsg )
: m_pInstance( pInstance )
, m_pMsg( pMsg )
{}
virtual bool operator ()( AR_HANDLE handle )
{
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( !pPlayer )
return false;
const _SCORE_TAG * pScoreTag = m_pInstance->getScoreTag( handle );
m_pMsg->kill_count = ( pScoreTag ) ? pScoreTag->nKillCount : 0;
PendMessage( pPlayer, m_pMsg );
return true;
}
const HuntaholicManager::InstanceDungeon * m_pInstance;
TS_SC_HUNTAHOLIC_UPDATE_SCORE * m_pMsg;
} fo( this, &msg );
PartyManager::GetInstance().DoEachMember( nPartyID, fo );
}
// 몬스터 리젠 예약(최대 점수 도달 시 또는 스킬에 의해 리젠되어 리젠 정보 ID 값이 설정되지 않은 몬스터는 리젠 예약하지 않음)
if( !bMaxPointReached && nRespawnID )
{
std::vector< const GameContent::HUNTAHOLIC_MONSTER_RESPAWN_INFO * >::const_iterator itRespawn = pInstanceBase->vRespawnInfo.begin();
for( ; itRespawn != pInstanceBase->vRespawnInfo.end() ; ++itRespawn )
{
if( (*itRespawn)->nID != nRespawnID )
continue;
vPendedMonsterRespawn.push_back( PendedMonsterRespawnInfo( (*itRespawn), t + (*itRespawn)->nPeriod ) );
break;
}
assert( itRespawn != pInstanceBase->vRespawnInfo.end() );
}
}
void HuntaholicManager::InstanceDungeon::clearMonsters()
{
// 리젠 대기 중인 몬스터 목록 제거
vPendedMonsterRespawn.clear();
// 몬스터 제거 처리(지역 락 필요)
for( std::vector< StructMonster * >::iterator itMonster = vRespawnedMonster.begin() ; itMonster != vRespawnedMonster.end() ; /* 루프에서 ++itMonster 처리 */ )
{
if( (*itMonster)->IsEnable() )
{
(*itMonster)->SetDeleteHandler( NULL );
// 더이상의 스케쥴러 요청을 무시
(*itMonster)->Disable();
// 월드에서 제거한다.
if( (*itMonster)->IsInWorld() )
{
RemoveMonsterFromWorld( (*itMonster) );
}
// object delete 요청
ArcadiaServer::Instance().DeleteObject( (*itMonster) );
itMonster = vRespawnedMonster.erase( itMonster );
}
else
{
++itMonster;
}
}
}
void HuntaholicManager::InstanceDungeon::onFieldPropDelete( StructFieldProp * pProp )
{
// 사냥이 종료된 이후면 아무것도 하지 않음
if( bEnd )
return;
THREAD_SYNCHRONIZE( csInstance );
// 사냥이 종료된 이후면 아무것도 하지 않음(락 걸고 나서 한 번 더 검사)
if( bEnd )
return;
std::vector< StructFieldProp * >::iterator itErase = std::find( vRespawnedHealingProp.begin(), vRespawnedHealingProp.end(), pProp );
// 이미 삭제된 프랍이면 아무 처리 안 함(삭제처리가 2번 일어났거나 리젠되기만 하고 vRespawnedHealingProp에는 추가되지 않았거나 -_ -?)
if( itErase == vRespawnedHealingProp.end() )
{
assert( 0 );
return;
}
vRespawnedHealingProp.erase( itErase );
vPendedHealingPropRespawn.push_back( PendedHealingPropRespawnInfo( pProp->GetRespawnInfo(), GetArTime() + pProp->GetFieldPropBase()->nRegenTime ) );
}
void HuntaholicManager::InstanceDungeon::clearHealingProps()
{
// 리젠 대기 중인 힐링 프랍 제거
vPendedHealingPropRespawn.clear();
// 힐링 프랍 제거 처리(지역 락 필요)
for( std::vector< StructFieldProp * >::iterator itHealingProp = vRespawnedHealingProp.begin() ; itHealingProp != vRespawnedHealingProp.end() ; ++itHealingProp )
{
// 해당 프랍이 ProcDelete에서 다시 리젠되도록 vPendedHealingPropRespawn에 추가되지 않도록 DeleteHandler를 제거하고 삭제함
(*itHealingProp)->SetDeleteHandler( NULL );
// 유저가 사용함으로 인해서 RemoveObject/PendFreeFieldProp은 걸려 있지만 아직 onFieldPropDelete가 호출되지 않은 경우에는 아무것도 하지 않음
if( !(*itHealingProp)->IsInWorld() )
continue;
ArcadiaServer::Instance().RemoveObject( (*itHealingProp) );
StructFieldProp::PendFreeFieldProp( (*itHealingProp) );
}
vRespawnedHealingProp.clear();
}
void HuntaholicManager::InstanceDungeon::broadcastInstanceDungeonInfoToMembers() const
{
TS_HUNTAHOLIC_INSTANCE_INFO info;
HuntaholicManager::assembleInstanceInfo( this, &info );
struct InstanceDungeonInfoBroadcaster : public PartyManager::PartyFunctor
{
InstanceDungeonInfoBroadcaster( const TS_HUNTAHOLIC_INSTANCE_INFO & info )
: m_InstanceInfo( info )
{}
virtual bool operator ()( AR_HANDLE handle )
{
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( pPlayer )
SendHuntaholicInstanceInfo( pPlayer, m_InstanceInfo );
return true;
}
const TS_HUNTAHOLIC_INSTANCE_INFO & m_InstanceInfo;
} fo( info );
PartyManager::GetInstance().DoEachMember( nPartyID, fo );
}
const unsigned short HuntaholicManager::InstanceDungeon::joinInstanceDungeon( StructPlayer * pPlayer, const char * pszPassword )
{
// 이미 시작된 방이면 조인 불가
if( tBeginTime )
return RESULT_COOL_TIME;
// 최대 멤버 수 제한 걸렸으면 조인 불가
if( PartyManager::GetInstance().GetMemberCount( nPartyID ) >= nMaxMemberCount )
return RESULT_LIMIT_MAX;
// 레벨대가 맞지 않는 방에는 조인 불가
if( !pInstanceBase->IsProperLevel( pPlayer->GetLevel() ) )
return RESULT_LIMIT_TARGET;
// 비번이 지정되어있는데 맞지 않으면 조인 불가
if( strlen( szPassword ) && strcmp( szPassword, pszPassword ) )
return RESULT_INVALID_PASSWORD;
// 파티에 조인 실패하면 방에도 조인 불가
if( !PartyManager::GetInstance().JoinParty( nPartyID, pPlayer ) )
return RESULT_UNKNOWN;
broadcastInstanceDungeonInfoToMembers();
SendPartyInfo( pPlayer );
BroadcastPartyMemberInfo( nPartyID, pPlayer );
SendTimeSync( pPlayer );
LOG::Log11N4S( LM_PARTY_JOIN, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 1, 0, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
LOG::Log11N4S( LM_HUNTAHOLIC_JOIN, pPlayer->GetAccountID(), pPlayer->GetSID(), pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, nInstanceNo, nMaxMemberCount, !!szPassword[ 0 ], PartyManager::GetInstance().GetMemberCount( nPartyID ), pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
return RESULT_SUCCESS;
}
const unsigned short HuntaholicManager::InstanceDungeon::leaveInstanceDungeon( StructPlayer * pPlayer )
{
// 파티가 이미 해산된 후에 leaveInstanceDungeon이 호출되는 경우라면(endHunting 처리) 걍 패스
if( !pPlayer->IsInParty() )
return RESULT_SUCCESS;
// 현재 인스턴스에 소속되지 않은 플레이어가 나가겠다고 하면 탈퇴 불가
if( !PartyManager::GetInstance().IsMember( nPartyID, pPlayer->GetPlayerUID() ) )
{
assert( 0 );
return RESULT_NOT_OWN;
}
// 파장이 나가려고 하면
if( PartyManager::GetInstance().IsLeader( nPartyID, pPlayer->GetPlayerUID() ) )
{
// 다음 파장 될 사람이 없거나, 다른 파티원이 남아 있긴 한데
// 죄다 오프라인이면 파티 해산(베어로드 파티에는 오프라인 멤버가 있으면 안되지만...)
if( PartyManager::GetInstance().GetMemberCount( nPartyID ) == 1 || !PartyManager::GetInstance().AutoPromote( nPartyID, true ) )
{
LOG::Log11N4S( LM_PARTY_DESTROY, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 1, 0, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
BroadcastPartyDestroy( nPartyID );
PartyManager::GetInstance().DestroyParty( nPartyID );
return RESULT_SUCCESS;
}
// 새 파장 정보 얻기
StructPlayer::iterator itNewLeader = StructPlayer::get( StructPlayer::FindPlayer( PartyManager::GetInstance().GetLeaderName( nPartyID ).c_str() ) );
StructPlayer * pNewLeader = (*itNewLeader);
// 인계는 됐는데 어쩐 일인지 새 파장이 없다... 오프라인이거나... 해산해버리자 -_ -;
if( !pNewLeader )
{
LOG::Log11N4S( LM_PARTY_DESTROY, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 1, 0, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
BroadcastPartyDestroy( nPartyID );
PartyManager::GetInstance().DestroyParty( nPartyID );
return RESULT_SUCCESS;
}
// 다른 사람에게 자동 인계가 처리됐으면 파장 변경 방송 처리
PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, nPartyID, "PROMOTE|%d|%s|", nPartyID, pNewLeader->GetName() );
LOG::Log11N4S( LM_PARTY_PROMOTE, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
pNewLeader->GetAccountID(), pNewLeader->GetSID(), pNewLeader->GetX(), pNewLeader->GetY(), pNewLeader->GetLayer(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, pNewLeader->GetAccountName(), LOG::STR_NTS, pNewLeader->GetName(), LOG::STR_NTS );
}
BroadcastPartyLeave( pPlayer );
PartyManager::GetInstance().LeaveParty( nPartyID, pPlayer->GetPlayerUID() );
LOG::Log11N4S( LM_PARTY_LEAVE, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 1, 0, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
broadcastInstanceDungeonInfoToMembers();
return RESULT_SUCCESS;
}
const unsigned short HuntaholicManager::InstanceDungeon::beginHunting()
{
if( bBegin )
return RESULT_ALREADY_EXIST;
bBegin = true;
// 몬스터 리젠 시키고~
for( std::vector< const GameContent::HUNTAHOLIC_MONSTER_RESPAWN_INFO * >::const_iterator itRespawn = pInstanceBase->vRespawnInfo.begin() ; itRespawn != pInstanceBase->vRespawnInfo.end() ; ++itRespawn )
{
for( int nIndex = 0 ; nIndex < (*itRespawn)->nCount ; ++nIndex )
{
int nRespawnX;
int nRespawnY;
int nRespawnTryCount = 0;
do
{
nRespawnX = XRandom( static_cast< int >( (*itRespawn)->bxArea.GetLeft() ), static_cast< int >( (*itRespawn)->bxArea.GetRight() ) );
nRespawnY = XRandom( static_cast< int >( (*itRespawn)->bxArea.GetTop() ), static_cast< int >( (*itRespawn)->bxArea.GetBottom() ) );
if( ++nRespawnTryCount > 10 )
{
FILELOG( "Unable to respawn monster in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn)->nID );
_cprint( "Unable to respawn monster in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)\n", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn)->nID );
nRespawnTryCount = 0;
break;
}
}
while( GameContent::IsBlocked( nRespawnX, nRespawnY ) );
if( !nRespawnTryCount )
continue;
StructMonster * pMob = respawnMonster( nRespawnX, nRespawnY, nInstanceNo, (*itRespawn)->nMonsterID, (*itRespawn)->bWandering, 0, this );
if( !pMob )
{
FILELOG( "Unknown monster is to be respawned in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn)->nID );
_cprint( "Unknown monster is to be respawned in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)\n", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn)->nID );
continue;
}
pMob->SetInstanceRespawnID( (*itRespawn)->nID );
vRespawnedMonster.push_back( pMob );
}
}
// 프랍 리젠 시키고~
for( std::vector< const GameContent::FIELD_PROP_RESPAWN_INFO * >::const_iterator itHealingProp = pInstanceBase->vHealingPropInfo.begin() ; itHealingProp != pInstanceBase->vHealingPropInfo.end() ; ++itHealingProp )
{
StructFieldProp * pProp = NULL;
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockWithVisibleRange( GetRegionX( (*itHealingProp)->x ), GetRegionY( (*itHealingProp)->y ), nInstanceNo ) );
pProp = StructFieldProp::Create( this, (*itHealingProp), (*itHealingProp)->x, (*itHealingProp)->y, nInstanceNo );
}
if( pProp )
{
vRespawnedHealingProp.push_back( pProp );
}
}
// 모두 입장~
WarpFunctor beginWarpFo( pHuntaholicInfo->pHuntaholicBase->posDungeon.x, pHuntaholicInfo->pHuntaholicBase->posDungeon.y, nInstanceNo, GetArTime() );
PartyManager::GetInstance().DoEachMember( nPartyID, beginWarpFo );
LOG::Log11N4S( LM_HUNTAHOLIC_BEGIN, 0, 0, pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, nInstanceNo, nMaxMemberCount, !!szPassword[ 0 ], PartyManager::GetInstance().GetMemberCount( nPartyID ), 0, 0, 0,
"", 0, "", 0, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
return RESULT_SUCCESS;
}
const unsigned short HuntaholicManager::InstanceDungeon::quitHunting( StructPlayer * pPlayer, const bool bNeedToReward, const HuntaholicManager::_HUNTING_RESULT eResult, int * pnSuppliedRewardPoint )
{
unsigned short nErrorCode = leaveInstanceDungeon( pPlayer );
if( nErrorCode != RESULT_SUCCESS )
return nErrorCode;
if( bNeedToReward )
{
int nPrevPoint = pPlayer->GetHuntaholicPoint();
bool bSuccess = ( eResult == HUNTING_RESULT_SUCCESS );
int nGainPoint = ( bSuccess ) ? ceil( pInstanceBase->fPointAdvantage * nScore ) : 0;
// 게임 중독 방지 시스템에 의한 획득 보상 페널티 적용
if( pPlayer->IsGameTimeLimited() )
{
nGainPoint = GameRule::GetGameTimeLimitPenalty( pPlayer->GetContinuousPlayTime() ) * nGainPoint;
}
InstanceDungeon::_SCORE_TAG * pScoreTag = getScoreTag( pPlayer->GetHandle() );
// 성공 시 성공 보상 및 포인트 지급
if( bSuccess )
{
// AddExp는 onExpChange에서 레벨업 방송이 있으므로 지역 락 걸어야 함
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) );
pPlayer->SetHuntaholicPoint( nPrevPoint + nGainPoint );
pPlayer->AddExp( pInstanceBase->nRewardExp, pInstanceBase->nRewardJp, false );
if( pInstanceBase->nRewardSuccessItemCode )
{
StructItem * pRewardSuccessItem = StructItem::AllocItem( 0, static_cast< ItemBase::ItemCode >( pInstanceBase->nRewardSuccessItemCode ), pInstanceBase->nRewardSuccessItemCount, ItemInstance::BY_HUNTAHOLIC );
if( pRewardSuccessItem->IsJoinable() )
{
PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pPlayer, "@254\v#@item_name@#\v@%d\v#@item_num@#\v%d", pRewardSuccessItem->GetItemBase().nNameId, pRewardSuccessItem->GetCount() );
}
else
{
PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pPlayer, "@253\v#@item_name@#\v@%d", pRewardSuccessItem->GetItemBase().nNameId );
}
StructItem * pNewItem = pPlayer->PushItem( pRewardSuccessItem, pRewardSuccessItem->GetCount() );
if( pNewItem )
{
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), pRewardSuccessItem->GetItemEnhance() * 100 + pRewardSuccessItem->GetItemLevel(), pRewardSuccessItem->GetItemCode(), pRewardSuccessItem->GetCount(), pNewItem->GetCount(), pPlayer->GetGold().GetRawData(), pPlayer->GetGold().GetRawData(), pPlayer->GetX(), pPlayer->GetY(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "HUNTAHOLIC", LOG::STR_NTS );
}
if( pNewItem != pRewardSuccessItem )
{
StructItem::PendFreeItem( pRewardSuccessItem );
}
}
}
if( pnSuppliedRewardPoint )
*pnSuppliedRewardPoint = nGainPoint;
}
// 사망에 의한 실패 및 중동 방지에 의한 실패일 경우에는 실패 보상 지급
else if( ( eResult == HUNTING_RESULT_FAILED_BY_DEATH || eResult == HUNTING_RESULT_FAILED_BY_GAMETIME_LIMIT ) && pInstanceBase->nRewardFailItemCode )
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) );
StructItem * pRewardFailItem = StructItem::AllocItem( 0, static_cast< ItemBase::ItemCode >( pInstanceBase->nRewardFailItemCode ), pInstanceBase->nRewardFailItemCount, ItemInstance::BY_HUNTAHOLIC );
if( pRewardFailItem->IsJoinable() )
{
PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pPlayer, "@254\v#@item_name@#\v@%d\v#@item_num@#\v%d", pRewardFailItem->GetItemBase().nNameId, pRewardFailItem->GetCount() );
}
else
{
PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pPlayer, "@253\v#@item_name@#\v@%d", pRewardFailItem->GetItemBase().nNameId );
}
StructItem * pNewItem = pPlayer->PushItem( pRewardFailItem, pRewardFailItem->GetCount() );
if( pNewItem )
{
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), pRewardFailItem->GetItemEnhance() * 100 + pRewardFailItem->GetItemLevel(), pRewardFailItem->GetItemCode(), pRewardFailItem->GetCount(), pNewItem->GetCount(), pPlayer->GetGold().GetRawData(), pPlayer->GetGold().GetRawData(), pPlayer->GetX(), pPlayer->GetY(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "HUNTAHOLIC", LOG::STR_NTS );
}
if( pNewItem != pRewardFailItem )
{
StructItem::PendFreeItem( pRewardFailItem );
}
}
// 성공도 아니고 사망에 의한 실패도 아니라면 보상을 전혀 지급하지 않고 페널티로 디버프 부여 및 잔여 입장 횟수를 1회 추가 차감
else
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) );
pPlayer->AddHuntaholicEnterableCount( -1 );
AR_TIME t = GetArTime();
pPlayer->AddState( StructState::MOVE_SPEED_SLOWDOWN, pPlayer->GetHandle(), 1, t, t + GameRule::HUNTAHOLIC_QUITTING_PENALTY_DEBUFF_TIME );
}
SendHuntaholicHuntingScore( pPlayer, pHuntaholicInfo->pHuntaholicBase->nID, ( pScoreTag ) ? pScoreTag->nKillCount : 0, ( pScoreTag ) ? pScoreTag->nScore : 0,
nKillCount, nScore, pInstanceBase->fPointAdvantage, nGainPoint, eResult );
LOG::Log11N4S( LM_HUNTAHOLIC_QUIT, pPlayer->GetAccountID(), pPlayer->GetSID(), pHuntaholicInfo->pHuntaholicBase->nID, nInstanceNo, eResult, nGainPoint, pPlayer->GetHuntaholicPoint(), pInstanceBase->nRewardExp, pPlayer->GetEXP(), pInstanceBase->nRewardJp, pPlayer->GetJobPoint(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
}
else
{
LOG::Log11N4S( LM_HUNTAHOLIC_QUIT, pPlayer->GetAccountID(), pPlayer->GetSID(), pHuntaholicInfo->pHuntaholicBase->nID, nInstanceNo, HUNTING_RESULT_NOREWARD, 0, pPlayer->GetHuntaholicPoint(), 0, pPlayer->GetEXP(), 0, pPlayer->GetJobPoint(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
}
return RESULT_SUCCESS;
}
const unsigned short HuntaholicManager::InstanceDungeon::endHunting()
{
if( bEnd )
return RESULT_NOT_EXIST;
bEnd = true;
// 리젠되어 있는 몹/프랍 모두 제거
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockArea( pHuntaholicInfo->pHuntaholicBase->nDungeonRegionLeft, pHuntaholicInfo->pHuntaholicBase->nDungeonRegionTop,
pHuntaholicInfo->pHuntaholicBase->nDungeonRegionRight, pHuntaholicInfo->pHuntaholicBase->nDungeonRegionBottom, nInstanceNo ) );
// 리젠 대기 중인 몬스터 데이터 제거
{
THREAD_SYNCHRONIZE( pHuntaholicInfo->csPendedRespawn );
for( std::vector< GameContent::PENDED_RESPAWN_INFO * >::iterator itRespawn = pHuntaholicInfo->vPendedRespawn.begin() ; itRespawn != pHuntaholicInfo->vPendedRespawn.end() ; /* 루프에서 ++itRespawn 처리 */ )
{
if( (*itRespawn)->layer == nInstanceNo )
{
delete (*itRespawn);
itRespawn = pHuntaholicInfo->vPendedRespawn.erase( itRespawn );
}
else
++itRespawn;
}
}
clearMonsters();
clearHealingProps();
}
struct MemberHandleCollector : public PartyManager::PartyFunctor
{
virtual bool operator()( AR_HANDLE handle )
{
vMember.push_back( handle );
return true;
}
std::vector< AR_HANDLE > vMember;
} fo;
PartyManager::GetInstance().DoEachMember( nPartyID, fo );
BroadcastPartyDestroy( nPartyID );
LOG::Log11N4S( LM_PARTY_DESTROY, 0, 0, nPartyID, 0, 0, 0, 2, 0, 0, 0, 0,
"", 0, "", 0, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
PartyManager::GetInstance().DestroyParty( nPartyID );
// endHunting 함수는 모든 유저가 나가거나 타임아웃으로 동시에 모두 쫓겨나야 할 때만 호출되므로(개별 탈퇴는 quitHunting) 실패 시에도 페널티 없음(사망 실패와 동일하게 처리)
_HUNTING_RESULT eResult = ( pHuntaholicInfo->pHuntaholicBase->nObjectivePoint > nScore ) ? HUNTING_RESULT_FAILED_BY_DEATH : HUNTING_RESULT_SUCCESS;
WarpFunctor endWarpFo( pHuntaholicInfo->pHuntaholicBase->posLobby.x, pHuntaholicInfo->pHuntaholicBase->posLobby.y, pInstanceBase->nID );
for( std::vector< AR_HANDLE >::const_iterator itPlayer = fo.vMember.begin() ; itPlayer != fo.vMember.end() ; ++itPlayer )
{
StructPlayer::iterator it = StructPlayer::get( (*itPlayer) );
StructPlayer * pPlayer = (*it);
if( !pPlayer )
continue;
// 파티 해산 이후에 호출되는 quitHunting 안에서 호출되는 leaveInstanceDungeon은 파티 관련 처리는 아무것도 하지 않으므로 보상 지급&결과 방송 처리만 됨
int nSuppliedRewardPoint = 0;
quitHunting( pPlayer, true, ( pPlayer->IsDead() ) ? HUNTING_RESULT_FAILED_BY_DEATH : eResult, &nSuppliedRewardPoint );
// 성공해서 보상을 지급받았다면 지급된 보상을 랭킹에 적용
if( nSuppliedRewardPoint )
{
RankingManager::Instance().AddRankingScore( RankingManager::RANKING_TYPE_HUNTAHOLIC_THIS_MONTH, pPlayer->GetPlayerUID(), pPlayer->GetName(), nSuppliedRewardPoint, true );
RankingManager::Instance().AddRankingScore( RankingManager::RANKING_TYPE_HUNTAHOLIC_TOTAL, pPlayer->GetPlayerUID(), pPlayer->GetName(), nSuppliedRewardPoint, true );
}
// 로비로 돌려보냄
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) );
endWarpFo.PendWarpPlayer( pPlayer );
}
}
return RESULT_SUCCESS;
}
void HuntaholicManager::InstanceDungeon::procMonsterRespawn( const AR_TIME & tCurrent )
{
for( std::vector< PendedMonsterRespawnInfo >::iterator itRespawn = vPendedMonsterRespawn.begin() ; itRespawn != vPendedMonsterRespawn.end() ; /* 루프에서 ++itRespawn 처리 */ )
{
if( (*itRespawn).second > tCurrent )
{
++itRespawn;
continue;
}
int nRespawnX;
int nRespawnY;
int nRespawnTryCount = 0;
do
{
nRespawnX = XRandom( static_cast< int >( (*itRespawn).first->bxArea.GetLeft() ), static_cast< int >( (*itRespawn).first->bxArea.GetRight() ) );
nRespawnY = XRandom( static_cast< int >( (*itRespawn).first->bxArea.GetTop() ), static_cast< int >( (*itRespawn).first->bxArea.GetBottom() ) );
if( ++nRespawnTryCount > 10 )
{
FILELOG( "Unable to respawn monster in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn).first->nID );
_cprint( "Unable to respawn monster in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)\n", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn).first->nID );
nRespawnTryCount = 0;
break;
}
}
while( GameContent::IsBlocked( nRespawnX, nRespawnY ) );
if( !nRespawnTryCount )
continue;
StructMonster * pMob = respawnMonster( nRespawnX, nRespawnY, nInstanceNo, (*itRespawn).first->nMonsterID, (*itRespawn).first->bWandering, 0, this );
if( !pMob )
{
FILELOG( "Unknown monster is to be respawned in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn).first->nID );
_cprint( "Unknown monster is to be respawned in huntaholic dungeon. HuntaholicID(%d), InstanceID(%d), RespawnID(%d)\n", pHuntaholicInfo->pHuntaholicBase->nID, pInstanceBase->nID, (*itRespawn).first->nID );
continue;
}
pMob->SetInstanceRespawnID( (*itRespawn).first->nID );
vRespawnedMonster.push_back( pMob );
itRespawn = vPendedMonsterRespawn.erase( itRespawn );
}
}
void HuntaholicManager::InstanceDungeon::procHealingPropRespawn( const AR_TIME & tCurrent )
{
for( std::vector< PendedHealingPropRespawnInfo >::iterator itRespawn = vPendedHealingPropRespawn.begin() ; itRespawn != vPendedHealingPropRespawn.end() ; /* 루프에서 ++itRespawn 처리 */ )
{
if( (*itRespawn).second > tCurrent )
{
++itRespawn;
continue;
}
StructFieldProp * pProp = NULL;
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockWithVisibleRange( GetRegionX( (*itRespawn).first->x ), GetRegionY( (*itRespawn).first->y ), nInstanceNo ) );
pProp = StructFieldProp::Create( this, (*itRespawn).first, (*itRespawn).first->x, (*itRespawn).first->y, nInstanceNo );
}
if( pProp )
{
vRespawnedHealingProp.push_back( pProp );
}
itRespawn = vPendedHealingPropRespawn.erase( itRespawn );
}
}
HuntaholicManager::InstanceDungeon::_SCORE_TAG * HuntaholicManager::InstanceDungeon::getScoreTag( const AR_HANDLE & hPlayer )
{
for( std::vector< _SCORE_TAG >::iterator it = vScoreTag.begin() ; it != vScoreTag.end() ; ++it )
{
if( (*it).hOwner != hPlayer )
continue;
return &(*it);
}
return NULL;
}
const HuntaholicManager::InstanceDungeon::_SCORE_TAG * HuntaholicManager::InstanceDungeon::getScoreTag( const AR_HANDLE & hPlayer ) const
{
for( std::vector< _SCORE_TAG >::const_iterator it = vScoreTag.begin() ; it != vScoreTag.end() ; ++it )
{
if( (*it).hOwner != hPlayer )
continue;
return &(*it);
}
return NULL;
}
// HuntaholicManager::HuntaholicInfo
const unsigned char HuntaholicManager::HuntaholicInfo::GetNewInstanceNo() const
{
unsigned char nInstanceNo = 1;
while( stInstanceNo.find( nInstanceNo ) != stInstanceNo.end() && nInstanceNo < 0xFF )
++nInstanceNo;
return nInstanceNo;
}
void HuntaholicManager::HuntaholicInfo::ProcPendedMonsterRespawn()
{
// [지역 락 -> csPEndedRespawn / csInstanceDungeon -> HuntaholicInfo::csInstance -> 지역 락] 지역 락으로 인한 데드락 발생 방지용 락 전략
std::vector< GameContent::PENDED_RESPAWN_INFO * > _vPendedRespawn;
{
THREAD_SYNCHRONIZE( csPendedRespawn );
if( vPendedRespawn.empty() )
return;
_vPendedRespawn.swap( vPendedRespawn );
}
THREAD_SYNCHRONIZE( csInstanceDungeon );
for( std::vector< GameContent::PENDED_RESPAWN_INFO * >::const_iterator itRespawn = _vPendedRespawn.begin() ; itRespawn != _vPendedRespawn.end() ; ++itRespawn )
{
GameContent::PENDED_RESPAWN_INFO * pRespawn = (*itRespawn);
for( std::vector< InstanceDungeon * >::iterator itInstance = vInstanceDungeon.begin() ; itInstance != vInstanceDungeon.end() ; ++itInstance )
{
InstanceDungeon * pInstance = (*itInstance);
if( pInstance->nInstanceNo != pRespawn->layer )
continue;
// 몹이 리젠되어야 할 방인데 아직 시작되지 않은 상태이거나 끝난 이후면 리젠시키지 않음
if( !pInstance->bBegin || pInstance->bEnd || pInstance->nScore >= pHuntaholicBase->nMaxPoint )
break;
THREAD_SYNCHRONIZE( pInstance->csInstance );
// 몹이 리젠되어야 할 방인데 아직 시작되지 않은 상태이거나 끝난 이후면 리젠시키지 않음(락 걸고 나서 한 번 더 검사)
if( !pInstance->bBegin || pInstance->bEnd || pInstance->nScore >= pHuntaholicBase->nMaxPoint )
break;
for( int nCountIdx = 0 ; nCountIdx < pRespawn->respawnCount ; ++nCountIdx )
{
// 랜덤 위치 설정
AR_UNIT x, y;
bool bValidPos = true;
unsigned short try_cnt = 0;
do
{
x = XRandom( pRespawn->x - 60, pRespawn->x + 60 );
y = XRandom( pRespawn->y - 60, pRespawn->y + 60 );
if( ++try_cnt > 300 )
{
bValidPos = false;
break;
}
} while( GameContent::CollisionToLine( pRespawn->x, pRespawn->y, x, y ) );
if( !bValidPos )
{
x = pRespawn->x;
y = pRespawn->y;
if( GameContent::IsBlocked( x, y ) == true )
{
FILELOG( "Unable to respawn monster in huntaholic dungeon. (respawn pos) HuntaholicID(%d), x(%d), y(%d), layer(%d), MonsterID(%d)", pHuntaholicBase->nID, pRespawn->x, pRespawn->y, pRespawn->layer, pRespawn->monsterId );
_cprint( "Unable to respawn monster in huntaholic dungeon. (respawn pos) HuntaholicID(%d), x(%d), y(%d), layer(%d), MonsterID(%d)", pHuntaholicBase->nID, pRespawn->x, pRespawn->y, pRespawn->layer, pRespawn->monsterId );
}
}
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockWithVisibleRange( ::GetRegionX( x ), ::GetRegionY( y ), pRespawn->layer ) );
StructMonster * pMob = respawnMonster( x, y, pRespawn->layer, pRespawn->monsterId, true, 0, pInstance );
if( !pMob )
{
FILELOG( "Unknown monster is to be respawned in huntaholic dungeon by pending. HuntaholicID(%d), x(%d), y(%d), layer(%d), MonsterID(%d)", pHuntaholicBase->nID, pRespawn->x, pRespawn->y, pRespawn->layer, pRespawn->monsterId );
_cprint( "Unknown monster is to be respawned in huntaholic dungeon by pending. HuntaholicID(%d), x(%d), y(%d), layer(%d), MonsterID(%d)\n", pHuntaholicBase->nID, pRespawn->x, pRespawn->y, pRespawn->layer, pRespawn->monsterId );
break;
}
if( pRespawn->initialEnemy )
pMob->AddHate( pRespawn->initialEnemy, 1 );
pMob->SetLifeTime( pRespawn->lifeTime );
pInstance->vRespawnedMonster.push_back( pMob );
}
}
}
delete pRespawn;
}
}
// HuntaholicManager
HuntaholicManager::~HuntaholicManager()
{
if( IsInitialized() )
DeInit();
ClearHuntaholicInfo();
}
HuntaholicManager & HuntaholicManager::Instance()
{
static HuntaholicManager _instance;
return _instance;
}
const bool HuntaholicManager::Init()
{
if( GameRule::bDisableHuntaholic )
return false;
if( IsInitialized() )
return false;
ArcadiaServer::Instance().SetObjectPriority( &HuntaholicManager::Instance(), ArSchedulerObject::UPDATE_PRIORITY_HIGH );
return true;
}
const bool HuntaholicManager::DeInit()
{
if( !IsInitialized() )
return false;
ArcadiaServer::Instance().SetObjectPriority( &HuntaholicManager::Instance(), ArSchedulerObject::UPDATE_PRIORITY_IDLE );
// 진행 중이던 헌터홀릭 던전이 있는 상태에서 종료 또는 뒷 처리를 해야할 경우 여기서 하면 됨
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
// 리젠 대기 중인 몬스터 데이터 제거
{
THREAD_SYNCHRONIZE( (*it)->csPendedRespawn );
for( std::vector< GameContent::PENDED_RESPAWN_INFO * >::const_iterator itRespawn = (*it)->vPendedRespawn.begin() ; itRespawn != (*it)->vPendedRespawn.end() ; ++itRespawn )
{
delete (*itRespawn);
}
(*it)->vPendedRespawn.clear();
}
// 삭제되어야 할 던전 정보를 즉시 삭제하기 위해서 모으는 벡터
std::vector< InstanceDungeon * > vPendedInstanceDungeonToBeDeleted;
{
THREAD_SYNCHRONIZE( (*it)->csInstanceDungeon );
// 삭제 대기 중인 던전들을 즉시 삭제할 목록에 추가 후 대기 리스트 초기화
vPendedInstanceDungeonToBeDeleted.swap( (*it)->vPendedInstanceDungeonToBeDeleted );
(*it)->stInstanceNo.clear();
for( std::vector< InstanceDungeon * >::iterator itInstance = (*it)->vInstanceDungeon.begin() ; itInstance != (*it)->vInstanceDungeon.end() ; /* 루프에서 ++itInstance 처리 */ )
{
InstanceDungeon * pInstance = (*itInstance);
THREAD_SYNCHRONIZE( pInstance->csInstance );
if( pInstance->bBegin && !pInstance->bEnd )
{
pInstance->endHunting();
itInstance = (*it)->vInstanceDungeon.erase( itInstance );
}
else
{
// 사냥 시작한 던전 아니면 파티 박살내고 방 정보 바로 삭제해버림(이쯤 되면 메모리 누수만 안 생기면 어떻게 되도 별 상관없다 -_ -; 어차피 서버 종료되는 중...)
PartyManager::GetInstance().DestroyParty( (*itInstance)->nPartyID );
++itInstance;
}
vPendedInstanceDungeonToBeDeleted.push_back( pInstance );
}
}
for( std::vector< InstanceDungeon * >::const_iterator itInstance = vPendedInstanceDungeonToBeDeleted.begin() ; itInstance != vPendedInstanceDungeonToBeDeleted.end() ; ++itInstance )
{
// 삭제 전에 락을 한 번 걸었다 풀어서 다른 프로세스가 인스턴스 정보를 사용 중일 가능성을 낮춘 후에 삭제
// 그래봐야 여기서 거는 락 때문에 기다리고 있는 쓰레드가 생기면 말짱 꽝이지만,
// 어차피 서버 내려가고 있는데 그딴 거 알 게 뭐삼! 걍 지워!(서버 종료되면서 불필요한 덤프가 남는 주 원인이 될 수도 있음... ``;)
{
THREAD_SYNCHRONIZE( (*itInstance)->csInstance );
}
delete (*itInstance);
}
}
return true;
}
const bool HuntaholicManager::IsInitialized() const
{
return GetFinalPriority() != ArSchedulerObject::UPDATE_PRIORITY_IDLE;
}
const bool HuntaholicManager::ClearHuntaholicInfo()
{
if( IsInitialized() )
return false;
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
// beginHunting에서 몬스터/프랍 리젠 정보를 사용하지만 ClearHuntaholicInfo가 호출되는 상황이면 beginHunting이 처리 중일 수는 없으므로 락 안 걸고 삭제
for( std::vector< const GameContent::HUNTAHOLIC_INSTANCE_BASE * >::const_iterator itInstance = (*it)->pHuntaholicBase->vInstanceBase.begin() ; itInstance != (*it)->pHuntaholicBase->vInstanceBase.end() ; ++itInstance )
{
for( std::vector< const GameContent::HUNTAHOLIC_MONSTER_RESPAWN_INFO * >::const_iterator itRespawn = (*itInstance)->vRespawnInfo.begin() ; itRespawn != (*itInstance)->vRespawnInfo.end() ; ++itRespawn )
{
delete (*itRespawn);
}
for( std::vector< const GameContent::FIELD_PROP_RESPAWN_INFO * >::const_iterator itHealingProp = (*itInstance)->vHealingPropInfo.begin() ; itHealingProp != (*itInstance)->vHealingPropInfo.end() ; ++itHealingProp )
{
delete (*itHealingProp);
}
delete (*itInstance);
}
delete (*it);
}
m_vHuntaholicInfo.clear();
return true;
}
const bool HuntaholicManager::RegisterHuntaholicBase( const GameContent::HUNTAHOLIC_BASE * pHuntaholicBase )
{
assert( !IsInitialized() );
#ifdef _DEBUG
// 디버그 모드일 때만 이미 등록된 헌터홀릭 정보인지 확인
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID == pHuntaholicBase->nID )
return false;
}
#endif
m_vHuntaholicInfo.push_back( new HuntaholicInfo( pHuntaholicBase ) );
return true;
}
const int HuntaholicManager::GetHuntaholicID( const ArPosition & pos ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
unsigned int nRegionX = pos.GetRX();
unsigned int nRegionY = pos.GetRY();
if( ( nRegionX >= (*it)->pHuntaholicBase->nDungeonRegionLeft && nRegionX <= (*it)->pHuntaholicBase->nDungeonRegionRight &&
nRegionY >= (*it)->pHuntaholicBase->nDungeonRegionTop && nRegionY <= (*it)->pHuntaholicBase->nDungeonRegionBottom ) ||
( nRegionX >= (*it)->pHuntaholicBase->nLobbyRegionLeft && nRegionX <= (*it)->pHuntaholicBase->nLobbyRegionRight &&
nRegionY >= (*it)->pHuntaholicBase->nLobbyRegionTop && nRegionY <= (*it)->pHuntaholicBase->nLobbyRegionBottom ) )
return (*it)->pHuntaholicBase->nID;
}
return 0;
}
const bool HuntaholicManager::IsHuntaholicLobby( const ArPosition & pos ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
unsigned int nRegionX = pos.GetRX();
unsigned int nRegionY = pos.GetRY();
if( nRegionX >= (*it)->pHuntaholicBase->nLobbyRegionLeft && nRegionX <= (*it)->pHuntaholicBase->nLobbyRegionRight &&
nRegionY >= (*it)->pHuntaholicBase->nLobbyRegionTop && nRegionY <= (*it)->pHuntaholicBase->nLobbyRegionBottom )
return true;
}
return false;
}
const bool HuntaholicManager::IsHuntaholicDungeon( const ArPosition & pos ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
unsigned int nRegionX = pos.GetRX();
unsigned int nRegionY = pos.GetRY();
if( nRegionX >= (*it)->pHuntaholicBase->nDungeonRegionLeft && nRegionX <= (*it)->pHuntaholicBase->nDungeonRegionRight &&
nRegionY >= (*it)->pHuntaholicBase->nDungeonRegionTop && nRegionY <= (*it)->pHuntaholicBase->nDungeonRegionBottom )
return true;
}
return false;
}
const ArPosition HuntaholicManager::GetLobbyPosition( const int nHuntaholicID ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
return (*it)->pHuntaholicBase->posLobby;
}
assert( 0 );
return ArPosition( 0, 0 );
}
const unsigned char HuntaholicManager::GetProperLobbyLayer( const int nHuntaholicID, const int nLevel ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
return (*it)->pHuntaholicBase->GetProperLobbyLayer( nLevel );
}
assert( 0 );
return GameRule::HUNTAHOLIC_UNUSABLE_LOBBY_LAYER;
}
const size_t HuntaholicManager::GetFreeInstanceCount( const int nHuntaholicID ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( (*it)->csInstanceDungeon );
return (*it)->GetFreeInstanceCount();
}
assert( 0 );
return 0;
}
const size_t HuntaholicManager::GetUsedInstanceCount( const int nHuntaholicID, const int nLevel ) const
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( (*it)->csInstanceDungeon );
if( (*it)->vInstanceDungeon.empty() )
return 0;
size_t nCount = 0;
for( std::vector< InstanceDungeon * >::const_iterator itInstance = (*it)->vInstanceDungeon.begin() ; itInstance != (*it)->vInstanceDungeon.end() ; ++itInstance )
{
if( !nLevel || (*itInstance)->pInstanceBase->IsProperLevel( nLevel ) )
++nCount;
}
return nCount;
}
assert( 0 );
return 0;
}
const size_t HuntaholicManager::GetInstanceList( const int nHuntaholicID, const int nLevel, const int nPage, TS_HUNTAHOLIC_INSTANCE_INFO * pBuffer )
{
for( std::vector< HuntaholicInfo * >::const_iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( (*it)->csInstanceDungeon );
if( (*it)->vInstanceDungeon.size() <= GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT_PER_PAGE * ( nPage - 1 ) )
return 0;
size_t nValidCount = 0;
size_t nTotalCount = 0;
for( std::vector< InstanceDungeon * >::const_iterator itInstance = (*it)->vInstanceDungeon.begin() ; itInstance != (*it)->vInstanceDungeon.end() ; ++itInstance )
{
// 적정 레벨에 해당되지 않는 방은 카운트도 안 하고 건너 뜀
if( !(*itInstance)->pInstanceBase->IsProperLevel( nLevel ) )
continue;
// 이미 사냥을 시작한 방은 없는셈 쳐버림(시작 버튼을 누르자마자 목록에서는 사라지는 걸로 처리)
if( (*itInstance)->tBeginTime )
continue;
// 선택한 페이지 수 만큼의 개수는 건너 뛰기
if( ++nTotalCount <= GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT_PER_PAGE * ( nPage - 1 ) )
continue;
assembleInstanceInfo( (*itInstance), pBuffer + nValidCount );
if( ++nValidCount >= GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT_PER_PAGE )
break;
}
return nValidCount;
}
assert( 0 );
return 0;
}
const unsigned short HuntaholicManager::CreateInstanceDungeon( const int nHuntaholicID, struct StructPlayer * pPlayer, const char * szName, const int nMaxMember, const char * szPassword )
{
if( !IsInitialized() )
return RESULT_ACCESS_DENIED;
if( !IsHuntaholicLobby( pPlayer->GetPos() ) )
return RESULT_ACCESS_DENIED;
if( pPlayer->IsInParty() )
return RESULT_NOT_ACTABLE;
if( pPlayer->IsGameTimeLimited() && pPlayer->GetContinuousPlayTime() >= GameRule::nMaxTiredGameTime )
return RESULT_GAMETIME_LIMITED;
// 나쁜 파티 이름
int code_page = ENV().GetInt( "CodePage", CP_ACP );
if( !GameRule::IsValidName( code_page, szName, static_cast< int >( strlen( szName ) + 1 ), 1, GameRule::HUNTAHOLIC_MAX_INSTANCE_NAME_LENGTH ) ||
GameContent::IsBannedWord( code_page, szName ) )
return RESULT_INVALID_TEXT;
// 최대 인원 수가 비정상적인 값인 경우
if( nMaxMember != 4 && nMaxMember != 6 && nMaxMember != 8 )
return RESULT_INVALID_ARGUMENT;
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
HuntaholicInfo * pHuntaholicInfo = (*it);
if( pHuntaholicInfo->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( pHuntaholicInfo->csInstanceDungeon );
if( pHuntaholicInfo->IsInstanceFull() )
return RESULT_LIMIT_MAX;
const GameContent::HUNTAHOLIC_INSTANCE_BASE * pInstanceBase = NULL;
for( std::vector< const GameContent::HUNTAHOLIC_INSTANCE_BASE * >::const_iterator itInstance = pHuntaholicInfo->pHuntaholicBase->vInstanceBase.begin() ; itInstance != pHuntaholicInfo->pHuntaholicBase->vInstanceBase.end() ; ++itInstance )
{
if( (*itInstance)->IsProperLevel( pPlayer->GetLevel() ) )
{
pInstanceBase = (*itInstance);
break;
}
}
if( !pInstanceBase )
return RESULT_NOT_ACTABLE;
// 헌터홀릭 시스템용 파티 이름 생성(외부의 파티 이름과 중첩되지 않도록 유저가 지정한 이름 뒤에 추가 문자를 붙임)
std::string strHuntaholicPartyName;
int nSuffix;
int nPartyID = 0;
for( nSuffix = 0 ; nSuffix < GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT ; ++nSuffix )
{
XStringUtil::Format( strHuntaholicPartyName, "%s<%d_h>", szName, nSuffix );
nPartyID = PartyManager::GetInstance().MakeParty( strHuntaholicPartyName.c_str(), pPlayer->GetPlayerUID(), PartyManager::TYPE_HUNTAHOLIC_PARTY );
if( nPartyID > 0 )
break;
}
if( nSuffix >= GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT )
return RESULT_ALREADY_EXIST;
if( !PartyManager::GetInstance().JoinParty( nPartyID, pPlayer ) )
{
PartyManager::GetInstance().DestroyParty( nPartyID );
return RESULT_UNKNOWN;
}
// TS_SC_HUNTAHOLIC_BEGIN_HUNTING 메시지에서 사용되는 헌터홀릭 시작 시간
SendTimeSync( pPlayer );
SendPartyInfo( pPlayer );
LOG::Log11N4S( LM_PARTY_CREATE, pPlayer->GetAccountID(), pPlayer->GetSID(), pPlayer->GetPartyID(), pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 4, PartyManager::TYPE_HUNTAHOLIC_PARTY, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, strHuntaholicPartyName.c_str(), LOG::STR_NTS, "", 0 );
unsigned char nInstanceNo = pHuntaholicInfo->GetNewInstanceNo();
InstanceDungeon * pInstance = new InstanceDungeon( pHuntaholicInfo, pInstanceBase, nInstanceNo, pPlayer->GetPartyID(), nMaxMember, szPassword );
pHuntaholicInfo->stInstanceNo.insert( nInstanceNo );
pHuntaholicInfo->vInstanceDungeon.push_back( pInstance );
pInstance->broadcastInstanceDungeonInfoToMembers();
LOG::Log11N4S( LM_HUNTAHOLIC_CREATE, pPlayer->GetAccountID(), pPlayer->GetSID(), nHuntaholicID, pInstance->pInstanceBase->nID, pInstance->nInstanceNo, pInstance->nMaxMemberCount, !!pInstance->szPassword[ 0 ], (*it)->vInstanceDungeon.size(), pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, strHuntaholicPartyName.c_str(), LOG::STR_NTS, szPassword, LOG::STR_NTS );
return RESULT_SUCCESS;
}
assert( 0 );
return RESULT_NOT_EXIST;
}
const unsigned short HuntaholicManager::JoinInstanceDungeon( const int nHuntaholicID, struct StructPlayer * pPlayer, const int nInstanceNo, const char * szPassword )
{
if( !IsInitialized() )
return RESULT_ACCESS_DENIED;
if( !IsHuntaholicLobby( pPlayer->GetPos() ) )
return RESULT_ACCESS_DENIED;
if( pPlayer->IsInParty() )
return RESULT_ALREADY_EXIST;
if( pPlayer->IsGameTimeLimited() && pPlayer->GetContinuousPlayTime() >= GameRule::nMaxTiredGameTime )
return RESULT_GAMETIME_LIMITED;
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( (*it)->csInstanceDungeon );
for( std::vector< InstanceDungeon * >::iterator itInstance = (*it)->vInstanceDungeon.begin() ; itInstance != (*it)->vInstanceDungeon.end() ; ++itInstance )
{
InstanceDungeon * pInstance = (*itInstance);
if( pInstance->nInstanceNo != nInstanceNo )
continue;
// 이미 시작된 방이면 조인 불가(시작 처리가 아닌 시작 버튼을 누른 시점부터 입장 불가)
if( pInstance->tBeginTime )
return RESULT_COOL_TIME;
THREAD_SYNCHRONIZE( pInstance->csInstance );
return pInstance->joinInstanceDungeon( pPlayer, szPassword );
}
// 유저가 가지고 있는 방 목록이 갱신되지 않은 상태에서 방이 없어지면
// 그 방에 조인을 시도할 경우 여기까지 오게 됨.(정상적인 경우이므로 assert 없음)
return RESULT_NOT_EXIST;
}
assert( 0 );
return RESULT_NOT_EXIST;
}
const unsigned short HuntaholicManager::LeaveInstanceDungeon( const int nHuntaholicID, struct StructPlayer * pPlayer )
{
if( !IsHuntaholicLobby( pPlayer->GetPos() ) )
return RESULT_ACCESS_DENIED;
int nPartyID = pPlayer->GetPartyID();
if( !nPartyID )
return RESULT_NOT_EXIST;
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
HuntaholicInfo * pHuntaholicInfo = (*it);
if( pHuntaholicInfo->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( pHuntaholicInfo->csInstanceDungeon );
for( std::vector< InstanceDungeon * >::iterator itInstance = pHuntaholicInfo->vInstanceDungeon.begin() ; itInstance != pHuntaholicInfo->vInstanceDungeon.end() ; ++itInstance )
{
InstanceDungeon * pInstance = (*itInstance);
if( pInstance->nPartyID != nPartyID )
continue;
std::string strPartyName( PartyManager::GetInstance().GetPartyName( pInstance->nPartyID ) );
// 사냥 시작 후일 경우 탈퇴 불가(QuitHunting 함수를 통해 사냥을 종료하고 나가야 함)
if( pInstance->bBegin )
return RESULT_COOL_TIME;
THREAD_SYNCHRONIZE( pInstance->csInstance );
// 사냥 시작 후일 경우 탈퇴 불가(락 걸고 한 번 더 검사)
if( pInstance->bBegin )
return RESULT_COOL_TIME;
unsigned short nErrorCode = pInstance->leaveInstanceDungeon( pPlayer );
if( nErrorCode != RESULT_SUCCESS )
return nErrorCode;
LOG::Log11N4S( LM_HUNTAHOLIC_LEAVE, pPlayer->GetAccountID(), pPlayer->GetSID(), nHuntaholicID, pInstance->pInstanceBase->nID, pInstance->nInstanceNo, pInstance->nMaxMemberCount, !!pInstance->szPassword[ 0 ], PartyManager::GetInstance().GetMemberCount( pInstance->nPartyID ), pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, strPartyName.c_str(), LOG::STR_NTS, "", 0 );
// 아무도 남지 않은 경우 방 정보 삭제 대기 처리(아직 시작되지도 않았던 방이므로 몹/힐링프랍 없으므로 그냥 방 정보만 삭제하면 됨)
if( !PartyManager::GetInstance().GetMemberCount( pInstance->nPartyID ) )
{
LOG::Log11N4S( LM_HUNTAHOLIC_DESTROY, pPlayer->GetAccountID(), pPlayer->GetSID(), nHuntaholicID, pInstance->pInstanceBase->nID, pInstance->nInstanceNo, pInstance->nMaxMemberCount, !!pInstance->szPassword[ 0 ], 0, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, strPartyName.c_str(), LOG::STR_NTS, "", 0 );
pHuntaholicInfo->vInstanceDungeon.erase( itInstance );
pHuntaholicInfo->vPendedInstanceDungeonToBeDeleted.push_back( pInstance );
}
return RESULT_SUCCESS;
}
// 여기 걸리는 건 방은 예전에 폭파됐는 데 사람은 남았다가 사람이 방에서 나가려고 하는 경우(로비에서 방 가입된 상태로 서버 다운 후 로그인하는 경우)
return RESULT_NOT_EXIST;
}
assert( 0 );
return RESULT_NOT_EXIST;
}
const unsigned short HuntaholicManager::BeginHunting( const int nHuntaholicID, struct StructPlayer * pPlayer )
{
if( !IsHuntaholicLobby( pPlayer->GetPos() ) )
return RESULT_ACCESS_DENIED;
int nPartyID = pPlayer->GetPartyID();
if( !nPartyID )
return RESULT_NOT_EXIST;
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
HuntaholicInfo * pHuntaholicInfo = (*it);
if( pHuntaholicInfo->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( pHuntaholicInfo->csInstanceDungeon );
for( std::vector< InstanceDungeon * >::iterator itInstance = pHuntaholicInfo->vInstanceDungeon.begin() ; itInstance != pHuntaholicInfo->vInstanceDungeon.end() ; ++itInstance )
{
InstanceDungeon * pInstance = (*itInstance);
if( pInstance->nPartyID != nPartyID )
continue;
if( !PartyManager::GetInstance().IsLeader( pInstance->nPartyID, pPlayer->GetPlayerUID() ) )
return RESULT_NOT_OWN;
// 이미 시작 버튼이 눌린 던전은 시작 요청 불가
if( pInstance->tBeginTime )
return RESULT_ALREADY_EXIST;
THREAD_SYNCHRONIZE( pInstance->csInstance );
// 이미 시작 버튼이 눌린 던전은 시작 요청 불가
if( pInstance->tBeginTime )
return RESULT_ALREADY_EXIST;
// 파티원 중 입장 불가 대상이 있는지 체크
struct AdmissionChecker : public PartyManager::PartyFunctor
{
AdmissionChecker()
: bAllAdmittable( true )
{}
virtual bool operator()( AR_HANDLE handle )
{
if( !bAllAdmittable )
return false;
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( !pPlayer || !HuntaholicManager::Instance().IsHuntaholicLobby( pPlayer->GetPos() ) || !pPlayer->GetHuntaholicEnterableCount() )
{
bAllAdmittable = false;
return false;
}
return true;
}
bool bAllAdmittable;
} foAdmissionChecker;
PartyManager::GetInstance().DoEachMember( pInstance->nPartyID, foAdmissionChecker );
if( !foAdmissionChecker.bAllAdmittable )
return RESULT_NOT_ACTABLE;
// 방 시작 방송
struct BeginCountdownBroadcaster : public PartyManager::PartyFunctor
{
virtual bool operator()( AR_HANDLE handle )
{
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( !pPlayer )
return false;
pPlayer->AddHuntaholicEnterableCount( -1 );
SendTimeSync( pPlayer );
PendMessage( pPlayer, &msg );
return true;
}
TS_SC_HUNTAHOLIC_BEGIN_COUNTDOWN msg;
} foBeginCountdownBroadcaster;
PartyManager::GetInstance().DoEachMember( pInstance->nPartyID, foBeginCountdownBroadcaster );
pInstance->tBeginTime = GetArTime() + GameRule::HUNTAHOLIC_DUNGEON_BEGIN_COUNTDOWN_TIME;
LOG::Log11N4S( LM_HUNTAHOLIC_BEGIN_COUNTDOWN, 0, 0, pHuntaholicInfo->pHuntaholicBase->nID, pInstance->pInstanceBase->nID, pInstance->nInstanceNo, pInstance->nMaxMemberCount, !!pInstance->szPassword[ 0 ], PartyManager::GetInstance().GetMemberCount( pInstance->nPartyID ), 0, 0, 0,
"", 0, "", 0, PartyManager::GetInstance().GetPartyName( pInstance->nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
return RESULT_SUCCESS;
}
break;
}
return RESULT_NOT_EXIST;
}
const unsigned short HuntaholicManager::QuitHunting( const int nHuntaholicID, struct StructPlayer * pPlayer, const bool bNeedToReward, const HuntaholicManager::_HUNTING_RESULT eResult )
{
int nPartyID = pPlayer->GetPartyID();
if( !nPartyID )
return RESULT_ACCESS_DENIED;
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
HuntaholicInfo * pHuntaholicInfo = (*it);
if( pHuntaholicInfo->pHuntaholicBase->nID != nHuntaholicID )
continue;
THREAD_SYNCHRONIZE( pHuntaholicInfo->csInstanceDungeon );
for( std::vector< InstanceDungeon * >::iterator itInstance = pHuntaholicInfo->vInstanceDungeon.begin() ; itInstance != pHuntaholicInfo->vInstanceDungeon.end() ; /* 루프에서 ++itInstance 처리 */ )
{
InstanceDungeon * pInstance = (*itInstance);
if( pInstance->nPartyID != nPartyID )
{
++itInstance;
continue;
}
std::string strPartyName( PartyManager::GetInstance().GetPartyName( pInstance->nPartyID ) );
{
THREAD_SYNCHRONIZE( pInstance->csInstance );
// eResult 가 지정되지 않았을 경우(eResult == HUNTING_RESULT_UNKNOWN) 목표 점수에 도달하지 못했으면 실질적으로 retire 처리
// bNeedToReward == false 여도 죽었으면 fail_by_death, retire 처리(실제로 bNeedToReward == false이면 eResult는 참조되지도 않음 -_ -)
_HUNTING_RESULT eFinalResult = eResult;
if( eFinalResult == HUNTING_RESULT_UNKNOWN )
{
if( pPlayer->IsDead() )
eFinalResult = HUNTING_RESULT_FAILED_BY_DEATH;
else if( pInstance->nScore < pHuntaholicInfo->pHuntaholicBase->nObjectivePoint )
{
if( pPlayer->IsGameTimeLimited() && pPlayer->GetContinuousPlayTime() >= GameRule::nMaxTiredGameTime )
eFinalResult = HUNTING_RESULT_FAILED_BY_GAMETIME_LIMIT;
else
eFinalResult = HUNTING_RESULT_RETIRED;
}
else if( !bNeedToReward )
eFinalResult = HUNTING_RESULT_RETIRED;
else
eFinalResult = HUNTING_RESULT_SUCCESS;
}
int nSuppliedRewardPoint = 0;
unsigned short nErrorCode = pInstance->quitHunting( pPlayer, bNeedToReward, eFinalResult, &nSuppliedRewardPoint );
if( nErrorCode != RESULT_SUCCESS )
return nErrorCode;
// 로비로 워프 처리
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) );
pPlayer->PendWarp( pHuntaholicInfo->pHuntaholicBase->posLobby.x, pHuntaholicInfo->pHuntaholicBase->posLobby.y, pInstance->pInstanceBase->nID );
ArcadiaServer::Instance().SetObjectPriority( pPlayer, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
}
// 성공해서 보상을 지급받았다면 지급된 보상을 랭킹에 적용
if( nSuppliedRewardPoint )
{
RankingManager::Instance().AddRankingScore( RankingManager::RANKING_TYPE_HUNTAHOLIC_THIS_MONTH, pPlayer->GetPlayerUID(), pPlayer->GetName(), nSuppliedRewardPoint, true );
RankingManager::Instance().AddRankingScore( RankingManager::RANKING_TYPE_HUNTAHOLIC_TOTAL, pPlayer->GetPlayerUID(), pPlayer->GetName(), nSuppliedRewardPoint, true );
}
// 아무도 남지 않은 경우 방 정보 삭제 대기 처리
if( !PartyManager::GetInstance().GetMemberCount( nPartyID ) )
{
pInstance->endHunting();
LOG::Log11N4S( LM_HUNTAHOLIC_DESTROY, pPlayer->GetAccountID(), pPlayer->GetSID(), nHuntaholicID, pInstance->pInstanceBase->nID, pInstance->nInstanceNo, pInstance->nMaxMemberCount, !!pInstance->szPassword[ 0 ], 0, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, strPartyName.c_str(), LOG::STR_NTS, "", 0 );
itInstance = pHuntaholicInfo->vInstanceDungeon.erase( itInstance );
pHuntaholicInfo->vPendedInstanceDungeonToBeDeleted.push_back( pInstance );
}
}
break;
}
// 로그인 시 헌터홀릭 내에서 파티 중이었을 경우(섭다 등으로 인한 로그아웃 시 처리 안 된 상태) 파티 탈퇴시키고 로비로 이동만이라도 시켜야 함
if( pPlayer->IsInParty() )
{
if( PartyManager::GetInstance().IsLeader( nPartyID, pPlayer->GetPlayerUID() ) )
{
LOG::Log11N4S( LM_PARTY_DESTROY, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 10, 0, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
BroadcastPartyDestroy( nPartyID );
PartyManager::GetInstance().DestroyParty( nPartyID );
}
else
{
BroadcastPartyLeave( pPlayer );
LOG::Log11N4S( LM_PARTY_LEAVE, pPlayer->GetAccountID(), pPlayer->GetSID(), nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 4, 0, 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 );
PartyManager::GetInstance().LeaveParty( nPartyID, pPlayer->GetPlayerUID() );
}
// 파티 탈퇴가 제대로 되었어야 함
assert( !PartyManager::GetInstance().IsMember( nPartyID, pPlayer->GetPlayerUID() ) );
}
unsigned char nLayer = pHuntaholicInfo->pHuntaholicBase->GetProperLobbyLayer( pPlayer->GetLevel() );
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) );
// quitHunting 에서 로비로 빼는 처리가 안된 경우에도 파탈 처리 제대로 안된 경우처럼 워프
if( IsHuntaholicDungeon( pPlayer->GetPos() ) && !pPlayer->IsPendWarp() )
{
if( nLayer != GameRule::HUNTAHOLIC_UNUSABLE_LOBBY_LAYER )
pPlayer->PendWarp( pHuntaholicInfo->pHuntaholicBase->posLobby.GetX(), pHuntaholicInfo->pHuntaholicBase->posLobby.GetY(), nLayer );
else
{
ArPosition posReturn;
pPlayer->GetPositionOnEnterInstanceGame( &posReturn );
pPlayer->PendWarp( posReturn.GetX(), posReturn.GetY(), 0 );
}
ArcadiaServer::Instance().SetObjectPriority( pPlayer, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
}
}
return RESULT_SUCCESS;
}
assert( 0 );
return RESULT_NOT_EXIST;
}
void HuntaholicManager::PendRespawnMonster( const int nHuntaholicID, const AR_UNIT x, const AR_UNIT y, const unsigned char nLayer, const int nMonsterID, const int nRespawnCount, const AR_TIME nLifeTime, const AR_HANDLE hInitialEnemy )
{
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
if( (*it)->pHuntaholicBase->nID != nHuntaholicID )
continue;
// 지역 락을 걸고 호출되는 함수이므로 csInstanceDungeon을 걸면 안됨
{
THREAD_SYNCHRONIZE( (*it)->csPendedRespawn );
(*it)->vPendedRespawn.push_back( new GameContent::PENDED_RESPAWN_INFO( x, y, nLayer, nMonsterID, nRespawnCount, true, nLifeTime, hInitialEnemy/* , 0*/ ) );
}
return;
}
assert( 0 );
}
void HuntaholicManager::onProcess( int nThreadIdx )
{
char buf[255];
s_sprintf( buf, _countof( buf ), "thread.scheduler.%d.proc", nThreadIdx );
ENV().Set( buf, "HuntaholicManager" );
AR_TIME t = GetArTime();
s_sprintf( s_ThreadInfo.job_info, _countof( s_ThreadInfo.job_info ), "HuntaholicManager(0x%08X)", (UINT_PTR)this );
s_ThreadInfo.last_execute_time = t;
if( !IsInitialized() )
return;
for( std::vector< HuntaholicInfo * >::iterator it = m_vHuntaholicInfo.begin() ; it != m_vHuntaholicInfo.end() ; ++it )
{
HuntaholicInfo * pHuntaholic = (*it);
{
THREAD_SYNCHRONIZE( pHuntaholic->csInstanceDungeon );
// 삭제 대기 중인 인스턴스 던전 삭제 처리
// * 현재 생성되어 있는 방들을 처리하기 전에 이걸 먼저 처리함으로써
// 이번 onProcess에서 삭제 대기 상태가 된 인스턴스를 다음 onProcess에서 삭제하도록 여유 시간을 발생시킴.
for( std::vector< InstanceDungeon * >::const_iterator itInstace = pHuntaholic->vPendedInstanceDungeonToBeDeleted.begin() ; itInstace != pHuntaholic->vPendedInstanceDungeonToBeDeleted.end() ; ++itInstace )
{
// 삭제 전에 락을 한 번 걸었다 풀어서 다른 프로세스가 인스턴스 정보를 사용 중일 가능성을 낮춘 후에 삭제
// 그래봐야 여기서 거는 락 때문에 기다리고 있는 쓰레드가 생기면 말짱 꽝이지만,
// 인스턴스 정보가 vPendedInstanceDungeonToBeDelete 리스트에 추가되고 여기까지 도달하기 전에 어지간한 것들은 다 종료되기에 충분할 것으로 가정
{
THREAD_SYNCHRONIZE( (*itInstace)->csInstance );
}
pHuntaholic->stInstanceNo.erase( (*itInstace)->nInstanceNo );
delete (*itInstace);
}
pHuntaholic->vPendedInstanceDungeonToBeDeleted.clear();
// 현재 생성되어 있는 인스턴스 던전 처리
for( std::vector< InstanceDungeon * >::iterator itInstance = pHuntaholic->vInstanceDungeon.begin() ; itInstance != pHuntaholic->vInstanceDungeon.end() ; /* 루프에서 ++itInstance 처리 */ )
{
InstanceDungeon * pInstance = (*itInstance);
THREAD_SYNCHRONIZE( pInstance->csInstance );
// 사냥 시작 전 상태인 방
if( !pInstance->bBegin )
{
// 파티가 정상적으로 유지되어 있는지 확인(일종의 유효성 검사, 정상적이면 LeaveInstanceDungeon에서 방이 삭제처리되었어야 함)
if( !PartyManager::GetInstance().GetMemberCount( pInstance->nPartyID ) )
{
assert( 0 );
PartyManager::GetInstance().DestroyParty( pInstance->nPartyID );
itInstance = pHuntaholic->vInstanceDungeon.erase( itInstance );
pHuntaholic->vPendedInstanceDungeonToBeDeleted.push_back( pInstance );
continue;
}
// 시작 버튼이 눌린 상태이고 시작 시각을 넘어갔으면 시작(시작 버튼 누르고 10초 후)
if( pInstance->tBeginTime && t >= pInstance->tBeginTime )
{
pInstance->beginHunting();
}
}
// 사냥 중인 방
else
{
// 사냥이 다 끝났거나 사냥 시간이 만료됐거나 아무도 없는 방 제거
if( pInstance->bEnd || pInstance->tBeginTime + pHuntaholic->pHuntaholicBase->tHuntingPeriod * 100 < t ||
!PartyManager::GetInstance().GetMemberCount( pInstance->nPartyID ) )
{
pInstance->endHunting();
itInstance = pHuntaholic->vInstanceDungeon.erase( itInstance );
pHuntaholic->vPendedInstanceDungeonToBeDeleted.push_back( pInstance );
continue;
}
// 종료 시간 1분 전 미만이고 공지된 적 없는 던전에는 종료 예정 공지
if( !pInstance->bEndNoticed && pInstance->tBeginTime + pHuntaholic->pHuntaholicBase->tHuntingPeriod * 100 - GameRule::HUNTAHOLIC_DUNGEON_END_NOTICE_TIME < t )
{
pInstance->bEndNoticed = true;
struct DungeonEndNotifier : PartyManager::PartyFunctor
{
virtual bool operator()( AR_HANDLE handle )
{
StructPlayer::iterator itPlayer = StructPlayer::get( handle );
StructPlayer * pPlayer = (*itPlayer);
if( !pPlayer )
return false;
PrintfChatMessage( false, CHAT_HUNTAHOLIC_SYSTEM, "@HUNTAHOLIC", pPlayer, "@1111\v#@min@#\v%d", static_cast< int >( GameRule::HUNTAHOLIC_DUNGEON_END_NOTICE_TIME / 6000 ) );
return true;
}
} fo;
PartyManager::GetInstance().DoEachMember( pInstance->nPartyID, fo );
}
// 종료랑 관계 없는 방이면 리젠 처리
pInstance->procMonsterRespawn( t );
pInstance->procHealingPropRespawn( t );
}
++itInstance;
}
}
// 리젠 대기 상태인 몬스터를 리젠 처리
// 위의 처리가 끝나고 하는 이유는 종료될 방은 종료시키고 나서 해야 쓸데없는 몹 리젠/삭제가 반복되지 않기 때문
// 아예 pHuntaholic->csInstanceDungeon 걸기 전에
pHuntaholic->ProcPendedMonsterRespawn();
}
}
void HuntaholicManager::assembleInstanceInfo( const InstanceDungeon * pInstance, TS_HUNTAHOLIC_INSTANCE_INFO * pInfo )
{
pInfo->instance_no = pInstance->nInstanceNo;
s_strcpy( pInfo->name, _countof( pInfo->name ), PartyManager::GetInstance().GetPartyName( pInstance->nPartyID ).c_str() );
pInfo->current_member_count = static_cast< unsigned char >( PartyManager::GetInstance().GetMemberCount( pInstance->nPartyID ) );
pInfo->max_member_count = pInstance->nMaxMemberCount;
pInfo->require_password = *pInstance->szPassword;
}