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

2956 lines
110 KiB
C++

#include <mmo/ArRegion.h>
#include "LogClient/LogClient.h"
#include "ErrorCode/ErrorCode.h"
#include "BattleArenaInstance.h"
#include "DB_Commands.h"
#include "PartyManager.h"
#include "BattleArenaManager.h"
#include "NPCProc.h"
#include "GameProc.h"
#include "StructSummon.h"
#include "ThreadPlayerHelper.h"
static void LeaveParty( int nPartyID, StructPlayer* pPlayer )
{
// 파티장이면서 멤버가 1명만 남아있거나 다른 파티멤버에게 자동 인계가 실패하면 파티 해산
// 그 외의 모든 경우에는 그냥 파티 탈퇴
if( PartyManager::GetInstance().IsLeader( nPartyID, pPlayer->GetPlayerUID() ) )
{
if( PartyManager::GetInstance().GetMemberCount( nPartyID ) == 1 || !PartyManager::GetInstance().AutoPromote( nPartyID, false ) )
{
BroadcastPartyDestroy( 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,
"", LOG::STR_NTS );
PartyManager::GetInstance().DestroyParty( nPartyID );
return;
}
else
{
PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, nPartyID, "PROMOTE|%d|%s|",
nPartyID, PartyManager::GetInstance().GetLeaderDisplayName( nPartyID ).c_str() );
}
}
BroadcastPartyLeave( pPlayer, true );
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,
"", LOG::STR_NTS );
PartyManager::GetInstance().LeaveParty( nPartyID, pPlayer->GetPlayerUID() );
}
BattleArenaInstance::BattleArenaInstance( const BattleArenaBaseServer* pArenaBase,
ThreadSafeIntMap* pPartyToArenaMap,
ThreadSafeIntMap* pPartyToInstanceMap,
unsigned char nInstanceNo )
: m_pArenaBase( pArenaBase )
, m_pPartyToArenaMap( pPartyToArenaMap )
, m_pPartyToInstanceMap( pPartyToInstanceMap )
, m_nInstanceNo( nInstanceNo )
, m_csBattle( "BattleArenaInstance::csBattle" )
{
m_bStart = false;
m_bEnd = false;
m_nWinTeamNo = 0;
m_bObjectiveScoreReached = false;
::memset( m_aTeamInfo, 0, sizeof( m_aTeamInfo ) );
::memset( m_anPartyID, 0, sizeof( m_anPartyID ) );
for( int i = 0; i < GameRule::BATTLE_ARENA_MAX_TEAM_COUNT; ++i )
{
m_vPlayerInfo[i].reserve( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT_PER_TEAM );
}
}
void BattleArenaInstance::Init( _BATTLE_GRADE _eGrade, AR_TIME _nStartTime )
{
m_eGrade = _eGrade;
SetDefaultTimeByStartTime( _nStartTime );
m_vPendedEnterPlayerList.clear();
m_bStart = false;
m_bEnd = false;
m_nWinTeamNo = INVALID_BATTLE_ARENA_TEAM_NO;
m_eRewardType = ART_UNKNOWN;
m_bObjectiveScoreReached = false;
// 팀별 데이터 초기화는 pArenaBase->nTeamCount 개만 해도 되지만 만일의 경우를 위해서...(현재는 다 2이므로 어떻게 해도 같음)
for( int nTeamNo = 0; nTeamNo < GameRule::BATTLE_ARENA_MAX_TEAM_COUNT; ++nTeamNo )
{
m_aTeamInfo[ nTeamNo ].nTotalKillCount = 0;
m_aTeamInfo[ nTeamNo ].nTotalDeathCount = 0;
m_aTeamInfo[ nTeamNo ].nScore = 0;
m_aTeamInfo[ nTeamNo ].fKillScoreRate = 1;
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
pPlayerInfoList->clear();
}
}
m_mapInstanceFlag.clear();
memset( m_anPartyID, 0, sizeof( m_anPartyID ) );
m_vPendedMonsterRespawn.clear();
m_vRespawnedMonster.clear();
m_vPendedFieldPropRespawn.clear();
m_vRespawnedFieldProp.clear();
m_vRespawnedArenaBlockerFieldProp.clear();
m_vPendedScript.clear();
}
void BattleArenaInstance::SetDefaultTimeByStartTime( AR_TIME nStartTime )
{
m_nStartTime = nStartTime;
if( nStartTime != INFINITE_TIME )
{
m_nQuickJoinLimitTime = m_nStartTime + m_pArenaBase->nQuickJoinLimitTime;
m_nEndTime = m_nStartTime + m_pArenaBase->nBattleDuration;
m_nForceQuitTime = m_nEndTime + GameRule::BATTLE_ARENA_FORCE_QUIT_DELAY;
}
else
{
m_nQuickJoinLimitTime = INFINITE_TIME;
m_nEndTime = INFINITE_TIME;
m_nForceQuitTime = INFINITE_TIME;
}
}
ArcadiaLock BattleArenaInstance::LockWholeInstance() const
{
// 데드락 체크
assert( !m_csBattle.IsLockedByCurrentThread() );
return _LockWholeBattleArena( m_pArenaBase, m_nInstanceNo );
}
void BattleArenaInstance::_initInstance()
{
// 경기 생성 시점에 생성됐다가 경기 시작 시점에 사라져야 하는 프랍 리젠(시작 시점까지 경기장 내부로 못 들어가게 막는 프랍)
for( std::vector< const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO* >::const_iterator it = m_pArenaBase->vArenaBlockerFieldPropRespawnList.begin();
it != m_pArenaBase->vArenaBlockerFieldPropRespawnList.end();
++it )
{
const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO* pRespawn = (*it);
StructFieldProp* pProp = StructFieldProp::Create( this, pRespawn, pRespawn->x, pRespawn->y, m_nInstanceNo );
if( pProp == NULL )
{
FILELOG( "Unknown field prop is to be respawned in battle arena. ArenaID(%d), PropID(%d)", m_pArenaBase->nID, pRespawn->nPropId );
_cprint( "Unknown field prop is to be respawned in battle arena. ArenaID(%d), PropID(%d)\n", m_pArenaBase->nID, pRespawn->nPropId );
continue;
}
m_vRespawnedArenaBlockerFieldProp.push_back( pProp );
}
// 팀별 파티 결성 완료
// 참여자에게 팀별 상세 정보 및 경기 시작 카운트(시작 시점) 방송
BattleArenaManager::BroadcastBattleArenaBattleInfoMessage( this );
LOG::Log11N4S( LM_BATTLE_ARENA_CREATE, 0, 0,
m_pArenaBase->nID, m_nInstanceNo, m_eGrade,
_getTeamMemberCount(0), _getTeamMemberCount(1),
0, 0, 0, 0, "", LOG::STR_NTS, "", LOG::STR_NTS,
"ARENA", LOG::STR_NTS,
"", LOG::STR_NTS );
}
void BattleArenaInstance::_onProcess()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 현재 진행 중인 각 경기에 대한 개별 처리
// - 현재 진행 중인 방이 목표 점수 도달된 경우에는 종료 및 승리 처리
// * 다른 조건보다 최우선적으로 처리되어야 승리 시 종료 처리를 즉각 하지 않고 여기서 하는 것으로 인한 피해를 줄일 수 있음(지역락 때문에...)
// - 현재 진행 중인 방의 종료 조건 체크 및 종료 처리
// * 각 방에 대해 확정됐던 승리 처리 외의 다른 처리 이전에 종료 처리 먼저 해줘야 불필요하게 종료될 방에 대한 처리를 하는 걸 조금이라도 줄일 수 있음
// - 시작된 경기의 경우
// * 오프라인 상태로 변경된 후 제한 시간 이내에 재접속하지 않은 유저 이탈 처리
// * 입장 예정 시간이 다 된 유저를 강제로 입장 예약(락 문제 때문에 여기서 바로 못 함)
// * 몬스터 리젠
// * 프랍 리젠
// - 아직 시작되지 않은 경기의 경우
// * 시작 시간이 다 된 경우
// v 시작 상태로 변경
// v 시작 시점에 제거해야 하는 프랍(아레나 내부 진입 금지용 프랍) 제거
// v 경기 중에 사용될 모든 몬스터, 프랍 리젠
// v 시작 스크립트 실행(락 문제 때문에 여기서 바로 못 하고 BattleArenaManager::onProcess에서 경기가 시작된 인스턴스를 확인해서 처리해 줌)
AR_TIME tCurrent = GetArTime();
_ARENA_END_TYPE eEndType = GetFinishType( tCurrent );
if( eEndType != AET_UNKNOWN )
{
_finishBattle( eEndType );
return;
}
RemoveAbsentAndOfflineLimitExpiredPlayer( tCurrent );
// 시작된 경기의 경우
if( m_bStart )
{
// 몬스터 리젠
_procMonsterRespawn();
// 프랍 리젠
_procFieldPropRespawn();
}
// 아직 시작되지 않은 경기의 경우
else
{
TryStart( tCurrent );
}
}
_ARENA_END_TYPE BattleArenaInstance::GetFinishType( AR_TIME tCurrent ) const
{
// { 현재 진행 중인 방이 목표 점수 도달된 경우에는 종료 및 승리 처리
if( m_bObjectiveScoreReached )
{
return AET_MAX_SCORE_REACHED;
}
// } 현재 진행 중인 방이 목표 점수 도달된 경우에는 종료 및 승리 처리
// { 현재 진행 중인 방의 종료 조건 체크 및 종료 처리
// 유저 모두 내쫓고 제거해야 하는 방( bEnd && tCurrent >= nForceQuitTime )은 방 인스턴스를 제거하고
// 인스턴스 번호 관련 처리 등이 필요하며, csBattle을 걸지 않은 상태로 처리해야 할 내용도 있기 때문에
// (사실 배열에 할당된 인스턴스 메모리를 해제하지 않고 재활용하는 형태라 없지만 -_ -;)
// 여기서 처리하지 않고 BattleArenaManager::onProcess에서 처리함
// 연습 경기와 선택 경기의 방 파괴 처리 조건이 다른 부분이 있어서 따로 함
// 연습 경기
if( m_pArenaBase->IsExerciseGameArena() )
{
// 연습 경기는 방 인스턴스 생성 후 첫 번째 유저가 진입해서 ReadyState를 세팅하기 전까지는 방을 파괴하지 않음
if( _getFlag( FLAG_BATTLE_ARENA_EXERCISE_GAME_READY_STATE ).empty() == false )
{
// 경기 시작 전에는 첫 번째 팀 유저가 아무도 없다면 경기 파괴
// 경기 시작 후에는 아무 팀이든 한 명도 없으면 무조건 경기 파괴
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
const std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList == NULL )
continue;
if( !pPlayerInfoList->empty() )
continue;
if( !m_bStart && nTeamNo )
continue;
// 경기는 끝났어도 방이 바로 파괴되면 안되고 GameRule::BATTLE_ARENA_FORCE_QUIT_DELAY 시간만큼 기다려준 후에 유저들 다 내쫓고 방 폭파해야 함
return AET_ONE_TEAM_NO_MEMBER;
}
}
}
// 선택 경기
else
{
// 한 쪽 팀의 인원이 모두 나간 방은 경기 종료
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
const std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList == NULL )
continue;
if( !pPlayerInfoList->empty() )
continue;
// 경기는 끝났어도 방이 바로 파괴되면 안되고 GameRule::BATTLE_ARENA_FORCE_QUIT_DELAY 시간만큼 기다려준 후에 유저들 다 내쫓고 방 폭파해야 함
return AET_ONE_TEAM_NO_MEMBER;
}
}
// 시작한 후에 경기 시간 만료된 방 종료 처리
if( m_bStart && !m_bEnd && tCurrent >= m_nEndTime )
{
// 경기는 끝났어도 방이 바로 파괴되면 안되고 GameRule::BATTLE_ARENA_FORCE_QUIT_DELAY 시간만큼 기다려준 후에 유저들 다 내쫓고 방 폭파해야 함
return AET_TIME_OVER;
}
// 이외의 조건으로 경기가 종료되어야 하는 경우(라고 해봐야 한 팀의 누적 획득 AP가 pArenaBase->nObjectivePoint 이상이 되는 경우밖에 없음)에는
// 해당 이벤트가 발생하는 시점에 즉시 체크해서 경기 종료 처리가 발생됨
// } 현재 진행 중인 방의 종료 조건 체크 및 종료 처리
return AET_UNKNOWN;
}
void BattleArenaInstance::RemoveAbsentAndOfflineLimitExpiredPlayer( AR_TIME tCurrent )
{
// 오프라인 상태로 변경된 후 제한 시간 이내에 재접속하지 않은 유저 이탈 처리
// 입장 예정 시간이 다 된 유저를 강제로 입장 예약(락 문제 때문에 여기서 바로 못 함)
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
// vPlayerInfo 순회하면서 즉시 _removePlayer를 호출하면 vPlayerInfo.erase 가 호출되면서 it가 무효화되기 때문에
// 따로 수집했다가 vPlayerInfo 순회 다 하고 나서 일괄 삭제
std::vector< PlayerUID > vOfflineLimitExpired;
std::vector< StructPlayer* > vAbsentPlayer;
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin(); it != pPlayerInfoList->end(); ++it )
{
PlayerInfo & pi = (*it);
StructPlayer* pPlayer = pi.pPlayer;
// 오프라인 유저면 이탈 제한 시간 체크
if( pPlayer == NULL )
{
// 이탈 시점이 체크되어 있어야 함. 체크 안 된 상태로 오프라인이 되었다면 어디선가 pi.pPlayer = NULL 을 잘못하고 있다는 이야기.
// 잘못된 상태라도 일단 그냥 방치하면 즉시 방에서 튕기는 정도의 버그만 나타나니 일단 별다른 조치 없이 진행
assert( pi.nOfflineLimitTime );
if( pi.nOfflineLimitTime <= tCurrent )
{
vOfflineLimitExpired.push_back( pi.nPlayerUID );
}
}
// 온라인 유저면 처리해야 할 내용 처리
// * 유저가 오프라인 상태라면 입장 관련해서는 아무것도 안 함(이 상태를 유지시켜서 있다가라도 재접하면 바로 안으로 날아 들어가게 해 줌)
else
{
// 잠수 신고 응답 제한 시간 처리
if( pi.bWaitingAbsenceCheck && pi.nLastAbsenceCheckTime <= tCurrent )
{
vAbsentPlayer.push_back( pPlayer );
// 쫓겨났으면 다른 처리는 안 함
continue;
}
// 입장 대기 상태의 유저가 입장 예정 시각이 지났다면 입장 처리
if( pi.nPendedEnterTime && pi.nPendedEnterTime <= tCurrent )
{
m_vPendedEnterPlayerList.push_back( pPlayer );
// 입장 예약 처리 및 중복해서 입장 처리가 발생하지 않도록 입장 예정 시점 초기화
pi.nPendedEnterTime = 0;
}
}
}
}
for( std::vector< PlayerUID >::const_iterator it = vOfflineLimitExpired.begin(); it != vOfflineLimitExpired.end(); ++it )
{
// _removePlayer로 인해 vPlayerInfo의 iterator가 무효화되는 문제를 피하기 위해 매 번 PlayerInfo를 검색...;;
PlayerUID nPlayerUID = (*it);
PlayerInfo* pPlayerInfo = _getPlayerInfo( nPlayerUID, nTeamNo );
// 페널티 부여(연습 경기라면 페널티 없음)
if( !m_pArenaBase->IsExerciseGameArena() )
{
// 유저가 오프라인이니 DB()에 직접 오프라인 유저 페널티 처리(DB_Login과 순서 꼬일 수도 있는데... 답이 없음 ` `;;)
// 구조적으로 오프라인 유저에 대한 데이터 변경 DB 처리가 있을 때 로그인과 순서가 엇갈리지 않게 하려면
// 별도의 매니저가 오프라인 유저에 대한 DB 처리들을 PlayerUID와 같은 고유 기준값을 이용해 리스트를 관리하고,
// 로그인 시도 시에 동일한 PlayerUID에 해당하는 DB 작업들이 아직 남아있을 경우 DB_Login을 나중에 처리되도록
// 대기시켜야 함... 아레나 페널티 따위 때문에 구현하긴 좀...;;
++pPlayerInfo->nPenaltyCount;
time_t tCurrent = time( NULL );
DB().Push( new DB_UpdateBattleArenaPenalty( pPlayerInfo->nPlayerUID,
tCurrent + GameRule::GetBattleArenaBlockDuration( pPlayerInfo->nPenaltyCount ),
pPlayerInfo->nPenaltyCount,
tCurrent + GameRule::GetBattleArenaPenaltyDuration( pPlayerInfo->nPenaltyCount ) ) );
// 오프라인 상태에서 페널티를 받았을 때 경기가 진행되는 상태라면 재접속 시 페널티 부여 안내 메시지 출력을 위해 PlayerUID를 리스트에 추가
BattleArenaManager::Instance().AddOfflinePenaltyReceivedPlayer( nPlayerUID );
// 경기 종료 시 BattleArenaManager::RemoveOfflinePenaltyReceivedPlayer를 호출하기 위해 목록 따로 보관
m_vOfflinePenaltyReceivedPlayerUIDList.push_back( nPlayerUID );
}
// 한 팀이 완전히 비워져서 경기가 취소되어야 하는 경우는 다음 _onProcess가 실행되는 시점(약 1초 후)에 처리되도록 함
// * 너무 방 파괴되어야 하는 코드가 여기저기 붙는 것 같아서 -_ -;;
_removePlayer( nPlayerUID, ALT_DISCONNECT );
}
for( std::vector< StructPlayer* >::iterator it = vAbsentPlayer.begin(); it != vAbsentPlayer.end(); ++it )
{
StructPlayer* pPlayer = (*it);
// 잠수 신고 응답 제한 시간 초과이니 페널티 먹이고 경기에서 추방
_removePlayer( pPlayer, ALT_ABSENT );
// 페널티 부여(연습 경기라면 페널티 없음)
if( !m_pArenaBase->IsExerciseGameArena() )
{
pPlayer->IncBattleArenaPenalty();
pPlayer->DBQuery( new DB_UpdateBattleArenaPenalty( pPlayer ) );
BattleArenaManager::SendBattleArenaPenaltyMessage( pPlayer );
}
BattleArenaQuitFunctor qf( ALT_ABSENT, m_pArenaBase, m_nInstanceNo );
qf( pPlayer );
}
}
}
void BattleArenaInstance::TryStart( AR_TIME tCurrent )
{
// 아직 준비 중인 연습 경기에서는 아무것도 안 함
if( m_nStartTime == INFINITE_TIME )
{
assert( m_pArenaBase->IsExerciseGameArena() );
return;
}
// 시작 시간이 아직 되지 않은 경우 시작 전에 미리 입장하려던 유저 입장 처리 외에는 아무것도 안 함
if( m_nStartTime > tCurrent )
return;
// 시작 시간이 되었으니 경기 시작 상태로 변경
m_bStart = true;
// 프랍 상태값 초기화(0으로 길이가 있는 스트링으로 변경해야 _activateProp 등등이 제대로 동작함 - 일종의 안전장치 'ㅠ '?)
_setFlag( FLAG_BATTLE_ARENA_PROP_STATE, "0" );
// 경기 중에 사용될 모든 몬스터, 프랍 리젠
_initialRespawn();
// 경기 시작 후 경기장 내부에서 카운트다운 완료 시점에 제거해야 하는 프랍(아레나 내부 진입 금지용 프랍) 제거
_clearArenaBlockerFieldProps();
LOG::Log11N4S( LM_BATTLE_ARENA_START, 0, 0,
m_pArenaBase->nID, m_nInstanceNo, m_eGrade,
_getTeamMemberCount(0), _getTeamMemberCount(1),
0, 0, 0, 0, "", LOG::STR_NTS, "", LOG::STR_NTS,
m_pArenaBase->IsExerciseGameArena() ? "EXERCISE" : "ARENA", LOG::STR_NTS,
"", LOG::STR_NTS );
// 시작 스크립트 실행
BattleArenaRunEventScript( m_pArenaBase, BASET_START_INSTANCE, m_nInstanceNo, NULL );
}
int BattleArenaInstance::_getTeamNo( int nPartyID ) const
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
if( nPartyID != 0 )
{
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
for( int i = 0; i < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++i )
{
if( m_anPartyID[ nTeamNo ][ i ] == nPartyID )
{
return nTeamNo;
}
}
}
}
return INVALID_BATTLE_ARENA_TEAM_NO;
}
int BattleArenaInstance::_getTeamMemberCount( int nTeamNo ) const
{
const std::vector< BattleArenaInstance::PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
return static_cast< int >( pPlayerInfoList->size() );
}
const std::vector< BattleArenaInstance::PlayerInfo >* BattleArenaInstance::_getPlayerInfoList( int nTeamNo ) const
{
assert( m_csBattle.IsLockedByCurrentThread() );
assert( 0 <= nTeamNo && nTeamNo < GameRule::BATTLE_ARENA_MAX_TEAM_COUNT );
const std::vector< BattleArenaInstance::PlayerInfo >* pPlayerInfoList = NULL;
if( 0 <= nTeamNo && nTeamNo < GameRule::BATTLE_ARENA_MAX_TEAM_COUNT )
{
pPlayerInfoList = &m_vPlayerInfo[nTeamNo];
}
return pPlayerInfoList;
}
std::vector< BattleArenaInstance::PlayerInfo >* BattleArenaInstance::_getPlayerInfoList( int nTeamNo )
{
assert( m_csBattle.IsLockedByCurrentThread() );
assert( 0 <= nTeamNo && nTeamNo < GameRule::BATTLE_ARENA_MAX_TEAM_COUNT );
std::vector< BattleArenaInstance::PlayerInfo >* pPlayerInfoList = NULL;
if( 0 <= nTeamNo && nTeamNo < GameRule::BATTLE_ARENA_MAX_TEAM_COUNT )
{
pPlayerInfoList = &m_vPlayerInfo[nTeamNo];
}
return pPlayerInfoList;
}
bool BattleArenaInstance::_isQuickJoinAble() const
{
// 근데 빠른 입장으로 입장할 수 없는 방이라면 즉시 0으로 리턴(경기 진행 상태가 아닌 방, 빠른 입장 제한 시간 초과)
AR_TIME tCurrent = GetArTime();
if( !m_bStart || m_bEnd || tCurrent >= m_nQuickJoinLimitTime )
return false;
// 각 팀의 점수 중 빠른 입장 제한 점수 이상인 팀이 있는 경우 체크
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
if( m_aTeamInfo[ nTeamNo ].nScore >= m_pArenaBase->nQuickJoinLimitPoint )
return false;
}
return true;
}
unsigned int BattleArenaInstance::_getQuickJoinPriority() const
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 빠른 입장을 위한 인스턴스별 가중치를 계산
// 진행 중인 경기들에 한해서 우선 순위가 높은 조건을 윗 자리 수 값으로 계산해서
// 대/소 비교를 통해 빠른 입장의 우선 순위가 높은 방이 큰 값을 반환하도록 함
// 근데 빠른 입장으로 입장할 수 없는 방이라면 즉시 0으로 리턴(경기 진행 상태가 아닌 방, 빠른 입장 제한 시간 초과)
if( _isQuickJoinAble() == false )
{
return 0;
}
AR_TIME tCurrent = GetArTime();
unsigned int nPriority = 0;
// * 이하는 복잡해서 일단 두 팀만 있다는 전제 하에 작성한 코드임
assert( m_pArenaBase->nTeamCount == 2 );
// 양 팀의 인원 수가 맞지 않는 경우
int anMemberCount[] = { _getTeamMemberCount(0), _getTeamMemberCount(1) };
if( anMemberCount[ 0 ] != anMemberCount[ 1 ] )
{
// 우선 순위 조건 1. 양 진영의 총 인원 수가 적은 방(31 ~ 26 비트)
assert( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT < ( 1 << 6 ) ); // 양 진영 인원 수 적음의 수치화 결과가 최대 64보다 작아야 함(6비트로 표현)
nPriority += ( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT - anMemberCount[ 0 ] - anMemberCount[ 1 ] ) << 26;
// 우선 순위 조건 2. 양 진영의 인원 수 차이가 높은 방(25 ~ 21 비트)
assert( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT_PER_TEAM < ( 1 << 5 ) );
nPriority += ( anMemberCount[ 0 ] > anMemberCount[ 1 ] ) ? ( anMemberCount[ 0 ] - anMemberCount[ 1 ] ) : ( anMemberCount[ 1 ] - anMemberCount[ 0 ] ) << 21;
}
// 우선 순위 조건 3. 입장 가능 시간이 적게 남은 방(20 ~ 15 비트: 총 시간 대비 지난 시간 비율을 2로 나눈 값(0~50)으로 저장)
nPriority += ( ( tCurrent - m_nStartTime ) * 100 / m_pArenaBase->nQuickJoinLimitTime / 2 ) << 15;
// 우선 순위 조건 4. (공통: 모든 조건이 같은 방) 슬래터 > 빙고 > 클래식(14 ~ 13 비트)
assert( m_pArenaBase->eType >= 0 && m_pArenaBase->eType <= 3 );
nPriority += ( m_pArenaBase->eType ) << 13;
// 우선 순위 조건 5. (공통: 모든 조건이 같은 방) 16:16 > 8:8 > 4:4 > 2:2(12 ~ 10 비트)
int nWholePlayerSizePriority = 0;
if( m_pArenaBase->nMaxMember >= 32 ) nWholePlayerSizePriority = 3;
else if( m_pArenaBase->nMaxMember >= 16 ) nWholePlayerSizePriority = 2;
else if( m_pArenaBase->nMaxMember >= 8 ) nWholePlayerSizePriority = 1;
nPriority += nWholePlayerSizePriority << 10;
return nPriority;
}
unsigned short BattleArenaInstance::_addPlayer( StructPlayer* pPlayer, int nTeamNo )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 대기열 참여를 통해 현재 경기에 참가(최근에 생성된 시작 전의 경기 참가 또는 새 경기 생성 후 참가)하는 경우 혹은 연습 경기 참여(Case 1)와
// 빠른 입장을 통해 플레이 중이던 현재 경기에 입장하는 경우(Case 2)와
// 연습 경기에 초대를 받아 참여한 경우(Case 3)
assert( !pPlayer->GetBattleArenaInstanceNo() &&
( ( pPlayer->GetBattleArenaID() == m_pArenaBase->nID && !m_bStart ) || // Case 1
( !pPlayer->GetBattleArenaID() && m_bStart ) ) ); // Case 2
// _addPlayer Caller 측에서 먼저 pPlayer가 참여 가능한 상태임을 체크해서 보장해야 하지만 만에 하나의 경우를 위해...
int nPrevPartyID = pPlayer->GetPartyID();
assert( !nPrevPartyID || PartyManager::GetInstance().GetPartyType( nPrevPartyID ) == PartyManager::TYPE_NORMAL_PARTY );
const size_t nMaxMemberPerTeam = static_cast< size_t >( m_pArenaBase->nMaxMember / m_pArenaBase->nTeamCount );
// nTeamNo 파라미터가 INVALID_BATTLE_ARENA_TEAM_NO인 경우에는 멤버 수가 가장 적은 팀 검색(만일 동일 인원 상태였던 둘 이상의 팀이 있다면 50% 랜덤)
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
{
nTeamNo = GetLeastMemberCountTeam();
}
// 빈 자리가 없었다면 참여 불가
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO ||
_getTeamMemberCount( nTeamNo ) >= nMaxMemberPerTeam )
return RESULT_LIMIT_MAX;
// 현재 파티에 가입중이라면 파티 탈퇴
if( nPrevPartyID )
{
LeaveParty( nPrevPartyID, pPlayer );
// 파티 해산 또는 탈퇴 처리가 제대로 안됐다면 그냥 참여 자체를 실패로 처리
if( pPlayer->GetPartyID() )
return RESULT_ALREADY_EXIST;
}
unsigned short shResult = FixSameAlias( pPlayer );
if( shResult != RESULT_SUCCESS )
{
return shResult;
}
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList == NULL )
{
return RESULT_LIMIT_MAX;
}
// 들어갈 팀에 참가(자리가 빈 파티가 있다면 빈 파티로, 없다면 새 파티 생성)
for( int nSlotInTeam = 0; nSlotInTeam < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++nSlotInTeam )
{
int & nPartyID = m_anPartyID[ nTeamNo ][ nSlotInTeam ];
if( nPartyID <= 0 )
{
nPartyID = CreateBattleArenaParty( nTeamNo, pPlayer );
if( nPartyID <= 0 )
{
return RESULT_LIMIT_MAX;
}
int nLeadParty = 0;
if( nSlotInTeam != 0 )
{
nLeadParty = m_anPartyID[ nTeamNo ][ 0 ];
}
if( LinkBattleArenaParty( nLeadParty, nPartyID ) == false )
{
// 아놔...;; 뭐여 이건;; 일단 파티 해산;
PartyManager::GetInstance().DestroyParty( nPartyID );
nPartyID = 0;
return RESULT_UNKNOWN;
}
std::string strPartyName = PartyManager::GetInstance().GetPartyName( nPartyID );
PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID );
// 클라에 방송하기로는 파티 타입을 무조건 연습 경기용이 아닌 실제 경기용 파티로 방송
PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nPartyID, "CREATE|%s|%s|%d|",
strPartyName.c_str(), pPlayer->GetAlias(), PartyManager::TYPE_BATTLE_ARENA_TEAM );
LOG::Log11N4S( LM_PARTY_CREATE, pPlayer->GetAccountID(), pPlayer->GetSID(),
nPartyID, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
5,
ePartyType,
0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetAlias(), LOG::STR_NTS,
strPartyName.c_str(), LOG::STR_NTS,
"", LOG::STR_NTS );
// 새로 생성된 파티 ID와 현재 아레나 ID를 연결
m_pPartyToArenaMap->Set( nPartyID, m_pArenaBase->nID );
// 새로 생성된 파티 ID와 현재 인스턴스 번호를 연결
m_pPartyToInstanceMap->Set( nPartyID, m_nInstanceNo );
}
else
{
// 파티가 해산되어버렸거나 최대 인원(팀당 아니고 파티 시스템의 최대 인원 = 8명)이 다 차 있으면 Join이 실패함
if( !PartyManager::GetInstance().JoinParty( nPartyID, pPlayer ) )
continue;
PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nPartyID, "JOIN|%s|%s|",
PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), pPlayer->GetAlias() );
}
SendPartyInfo( pPlayer );
SendLinkedPartyInfo( pPlayer );
BroadcastPartyMemberInfo( nPartyID, pPlayer );
BroadcastLinkedPartyMemberInfo( nPartyID, pPlayer );
SendSummonInfo( pPlayer );
BroadcastSummonInfo( nPartyID, 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,
"", LOG::STR_NTS );
break;
}
pPlayer->SetBattleArenaInstanceNo( m_nInstanceNo );
pPlayerInfoList->push_back( PlayerInfo( pPlayer ) );
// 경기 상태에 따라 입장 예정 시점을 세팅
AR_TIME tCurrent = GetArTime();
if( !m_bStart && tCurrent < m_nStartTime )
{
// 시작 전의 경기라면 경기 시작 시점에 자동 입장
pPlayerInfoList->back().nPendedEnterTime = m_nStartTime;
}
else
{
// 시작된 후의 경기라면 현재 시점보다 지정 기간 이후에 자동 입장(현재는 10초 고정)
pPlayerInfoList->back().nPendedEnterTime = tCurrent + GameRule::BATTLE_ARENA_QUICK_JOIN_COUNTDOWN;
}
LOG::Log11N4S( LM_BATTLE_ARENA_JOIN_BATTLE, pPlayer->GetAccountID(), pPlayer->GetPlayerUID(),
m_pArenaBase->nID, m_nInstanceNo, m_eGrade,
nTeamNo, _getTeamMemberCount(nTeamNo), pPlayer->GetPartyID(),
0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS,
"", LOG::STR_NTS,
pPlayer->GetAlias(), LOG::STR_NTS );
return RESULT_SUCCESS;
}
int BattleArenaInstance::GetLeastMemberCountTeam() const
{
int nTeamNo = INVALID_BATTLE_ARENA_TEAM_NO;
size_t nLeastMemberCount = UINT_MAX;
for( int t = 0; t < m_pArenaBase->nTeamCount; ++t )
{
size_t nMemberCount = _getTeamMemberCount( t );
if( nMemberCount < nLeastMemberCount )
{
nLeastMemberCount = nMemberCount;
nTeamNo = t;
}
// 동일 인원인 팀이 확인되면 50% 확률로 팀을 변경
else if( nMemberCount == nLeastMemberCount )
{
if( XRandom( 1, 100 ) <= 50 )
{
nTeamNo = t;
}
}
}
return nTeamNo;
}
unsigned short BattleArenaInstance::FixSameAlias( StructPlayer* pPlayer ) const
{
// 가명 사용 유저의 경우에 중복되는 가명을 가진 다른 유저가 이미 경기에 참여되어 있다면, 가명 뒤에 숫자 붙여 줌
if( pPlayer->IsUsingAlias() && strlen( pPlayer->GetAlias( true ) ) > 0 )
{
std::string strAlias = pPlayer->GetAlias();
// 중복 검사 시에는 뒤에 붙은 *을 떼 줘야 함
strAlias.resize( strAlias.size() - 1 );
// 이번에 참여하는 유저의 가명을 포함하고, 그 뒤에 숫자가 붙어있는 유저의 가명 목록 추출
bool bSameAliasExist = false;
std::vector< int > vUsedDuplicatedAliasIndices;
for( int t = 0; t < m_pArenaBase->nTeamCount; ++t )
{
const std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( t );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::const_iterator it = pPlayerInfoList->begin(); it != pPlayerInfoList->end(); ++it )
{
const PlayerInfo & pi = (*it);
std::string strOtherAlias;
if( pi.pPlayer )
{
if( !pi.pPlayer->IsUsingAlias() || !strlen( pi.pPlayer->GetAlias( true ) ) )
continue;
strOtherAlias = pi.pPlayer->GetAlias();
}
else
{
strOtherAlias = PartyManager::GetInstance().GetMemberDisplayName( pi.nPartyID, pi.nPlayerUID );
// strOtherAlias.empty() == true 일 가능성은 없어 보이지만...
if( strOtherAlias.empty() || strOtherAlias.at( strOtherAlias.size() - 1 ) != '*' )
continue;
}
// 확인된 가명의 뒤에는 *이 반드시 붙어 있어야 하므로 *을 떼 줌
if( strOtherAlias.at( strOtherAlias.size() - 1 ) != '*' )
{
// 가명 사용 중인 유저의 가명만 받아서 여기로 왔는데 뒤에 *이 없다고;?
assert( 0 );
continue;
}
strOtherAlias.resize( strOtherAlias.size() - 1 );
if( _strnicmp( strOtherAlias.c_str(), strAlias.c_str(), strAlias.size() ) )
continue;
// 동일한 부분을 제외한 부분이 겹치는 Alias에 의해 생성될 수 있는 숫자값인지 체크
strOtherAlias = strOtherAlias.substr( strAlias.size() );
// 완전히 동일한 가명인 경우
if( strOtherAlias.empty() )
{
bSameAliasExist = true;
}
// 2 byte 이하의 추가 내용이 있는 경우 숫자인지 체크
else if( strOtherAlias.size() <= 2 )
{
if( !isdigit( strOtherAlias.at( 0 ) ) || strOtherAlias.at( 0 ) == '0' || ( strOtherAlias.size() == 2 && !isdigit( strOtherAlias.at( 1 ) ) ) )
continue;
int nUsedIndex = atoi( strOtherAlias.c_str() );
if( !nUsedIndex || nUsedIndex >= 100 || std::find( vUsedDuplicatedAliasIndices.begin(), vUsedDuplicatedAliasIndices.end(), nUsedIndex ) != vUsedDuplicatedAliasIndices.end() )
{
// 숫자라고 검사 다 해서 변환하고 보니 정상적인 범위의 값이 아니거나 이미 확인됐던 값이라면
// 로직 상이나 어떤 부분에 버그가 있을 가능성이 높으므로 일단 패스
assert( 0 );
return RESULT_INVALID_TEXT;
}
vUsedDuplicatedAliasIndices.push_back( nUsedIndex );
}
// else 에 해당하는 2 byte 넘게 추가 내용이 있는 경우는 중복 별명 아님
}
}
}
if( bSameAliasExist )
{
// 추출된 중복 가명 목록 인덱스에 해당하지 않는 가장 작은 인덱스 번호 찾아서 유저 가명 뒤에 붙여 줌
// * vUsedDuplicatedAliasIndices를 정렬해두고 검사하면 이렇게 for문을 다 돌 필요도 없이 로직을 간소화시킬 수 있지만
// vUsedDuplicatedAliasIndices의 최대 길이가 32인 점을 감안하면 정렬해가며 처리하는 것 자체가 사치에 가깝다고 보여서
// 그냥 무식하게 for문 돌림(어차피 루프 몇 번 안 돌고 빠져 나와야 정상)
for( int i = 1; i < 100; ++i )
{
if( std::find( vUsedDuplicatedAliasIndices.begin(), vUsedDuplicatedAliasIndices.end(), i ) != vUsedDuplicatedAliasIndices.end() )
continue;
char szIndex[ 3 ];
s_sprintf( szIndex, _countof( szIndex ), "%d", i );
strAlias += szIndex;
// 최대 길이가 넘친다는 건 뭔가 잘못됐다는 건데 -_ -;?
if( strAlias.size() > 17 )
{
assert( 0 );
return RESULT_INVALID_TEXT;
}
// 여기서 strAlias에 *은 안 붙임(아래의 pPlayer->SetAlias()에서 알아서 붙음)
break;
}
// 새로 세팅된 가명으로 유저의 가명을 변경
pPlayer->DBQuery( new DB_ChangeCharacterAlias( pPlayer, strAlias.c_str(), false ) );
pPlayer->SetAlias( strAlias.c_str() );
// 클라에게 가명이 강제로 변경되었음을 TS_SC_RESULT, request_msg_id = TM_CS_CHANGE_ALIAS로 방송함
// * 우측 하단 알림 팝업 창에 메시지를 띄워야 하는데 서버에서는 그 쪽에 메시지를 임의로 띄울 수 없어서...
SendResult( pPlayer, TM_CS_CHANGE_ALIAS, RESULT_ALREADY_EXIST );
}
}
return RESULT_SUCCESS;
}
int BattleArenaInstance::CreateBattleArenaParty( int nTeamNo, StructPlayer* pPlayer )
{
int nPartyID = 0;
char szPartyName[ 64 ] = { 0, };
// 파티 타입 결정
PartyManager::_PARTY_TYPE ePartyType = ( m_pArenaBase->IsExerciseGameArena() && !m_bStart ) ?
PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM : PartyManager::TYPE_BATTLE_ARENA_TEAM;
// 생성할 파티 이름을 "연합군<~~~>", "연합군_00<~~~>", "연합군_01<~~~>" 이런 식으로 번호 증가시키며 시도
// * 기존에 결성되었던 파티들 중 누군가가 해산되면 뒤의 파티들이 앞으로 당겨지기 때문에 nSlotInTeam만으로는 파티 이름을 확정적으로 결정할 수 없음
const int nMaxPartyCountPerTeam = ( ( m_pArenaBase->nMaxMember / m_pArenaBase->nTeamCount ) - 1 ) / PartyManager::MAX_PARTY_MEMBER + 1;
for( int nPartyNameIndex = 0; nPartyNameIndex < nMaxPartyCountPerTeam; ++nPartyNameIndex )
{
if( nPartyNameIndex == 0 )
{
s_sprintf( szPartyName, _countof( szPartyName ), "%s<%d%db>",
GameContent::GetString( GameRule::GetBattleArenaTeamNameStringID( nTeamNo, false ) ),
m_pArenaBase->nID,
m_nInstanceNo );
}
else
{
s_sprintf( szPartyName, _countof( szPartyName ), "%s_%02d<%d%db>",
GameContent::GetString( GameRule::GetBattleArenaTeamNameStringID( nTeamNo, false ) ),
nPartyNameIndex - 1,
m_pArenaBase->nID,
m_nInstanceNo );
}
// 파티 생성 성공하면 루프 중단
nPartyID = PartyManager::GetInstance().MakeParty( szPartyName, pPlayer->GetPlayerUID(), ePartyType );
if( nPartyID > 0 )
{
break;
}
}
if( nPartyID > 0 )
{
// 새로 생성된 파티에 가입시킴
if( !PartyManager::GetInstance().JoinParty( nPartyID, pPlayer ) )
{
// 파장이 가입 실패했으면 뭐... 없애야지 -_ -;;
PartyManager::GetInstance().DestroyParty( nPartyID );
nPartyID = 0;
}
}
return nPartyID;
}
bool BattleArenaInstance::LinkBattleArenaParty( int nLeaderPartyID, int nJoinPartyID )
{
// 배틀 아레나 링크 파티에 가입 또는 생성
if( nLeaderPartyID == 0 )
{
const int nMaxPartyCountPerTeam = ( ( m_pArenaBase->nMaxMember / m_pArenaBase->nTeamCount ) - 1 ) / PartyManager::MAX_PARTY_MEMBER + 1;
if( !PartyManager::GetInstance().MakeBattleArenaTeam( nJoinPartyID, nMaxPartyCountPerTeam ) )
{
return false;
}
}
else
{
if( !PartyManager::GetInstance().JoinBattleArenaTeam( nLeaderPartyID, nJoinPartyID ) )
{
return false;
}
}
return true;
}
unsigned short BattleArenaInstance::_removePlayer( StructPlayer * pPlayer, _ARENA_LEAVE_TYPE eLeaveType )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 현재 경기에 참여해 있는 유저만 탈퇴 가능
assert( pPlayer->GetBattleArenaID() == m_pArenaBase->nID && pPlayer->GetBattleArenaInstanceNo() == m_nInstanceNo );
PlayerUID nPlayerUID = pPlayer->GetPlayerUID();
return _removePlayer( nPlayerUID, eLeaveType );
}
unsigned short BattleArenaInstance::_removePlayer( PlayerUID nPlayerUID, _ARENA_LEAVE_TYPE eLeaveType )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// _removePlayer( StructPlayer * pPlayer, _ARENA_LEAVE_TYPE eLeaveType ) 함수는 참여 정보 삭제와 파티 이탈 처리가 따로 가능하지만
// 이 함수에서는 유저 참여 정보가 없으면 어느 팀/파티에 소속되어 있던 오프라인 유저인지를 알 수 없기 때문에(그렇다고 모든 파티를 다 돌긴 좀 -_ -;)
// 유저 참여 정보가 반드시 있어야만 정상적으로 이탈 처리가 됨
// 팀 번호 확인 및 유저 참여 정보 제거, 파티 관련 이탈 처리
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin(); it != pPlayerInfoList->end(); ++it )
{
if( (*it).nPlayerUID != nPlayerUID )
continue;
// 삭제전 정보 저장
PlayerInfo removePlayerInfo = (*it);
// 유저 참여 정보 제거
pPlayerInfoList->erase( it );
return LeavePlayer( &removePlayerInfo, nTeamNo, eLeaveType );
}
}
}
// 없는 유저에 대해서 _removePlayer를 시도했다는 건 어디선가 _removePlayer를 두 번 이상 호출하는 상태
assert( 0 );
return RESULT_NOT_EXIST;
}
void BattleArenaInstance::_deleteBattleArenaPartyPlayer( int nPartyID, StructPlayer* pPlayer )
{
int nTeamNo = _getTeamNo( nPartyID );
if( nTeamNo != INVALID_BATTLE_ARENA_TEAM_NO )
{
LeaveBattleArenaParty( nTeamNo, nPartyID, pPlayer->GetPlayerUID(), pPlayer, pPlayer->GetAlias(), ALT_UNKNOWN );
}
}
void BattleArenaInstance::_removeAllPlayer( _ARENA_LEAVE_TYPE eLeaveType, ArObjectFunctor& functor )
{
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin(); it != pPlayerInfoList->end(); )
{
// 삭제전 정보 저장
PlayerInfo outPlayerInfo = (*it);
// 유저 참여 정보 제거
it = pPlayerInfoList->erase( it );
LeavePlayer( &outPlayerInfo, nTeamNo, eLeaveType );
StructPlayer* pPlayer = outPlayerInfo.pPlayer;
if( pPlayer )
{
functor( pPlayer );
}
}
}
assert( pPlayerInfoList->empty() );
pPlayerInfoList->clear();
}
#ifdef _DEBUG
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
// 모든 파티 해산
for( int i = 0; i < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++i )
{
int nPartyID = m_anPartyID[ nTeamNo ][ i ];
if( !nPartyID )
continue;
assert( 0 );
}
}
#endif
}
unsigned short BattleArenaInstance::LeavePlayer( PlayerInfo* pPlayerInfo, int nTeamNo, _ARENA_LEAVE_TYPE eLeaveType )
{
// 정상적으로 파티에 소속된 상태에서 이탈되는 경우 처리
int nPartyID = pPlayerInfo->nPartyID;
PlayerUID nPlayerUID = pPlayerInfo->nPlayerUID;
StructPlayer* pPlayer = pPlayerInfo->pPlayer;
// 오프라인 유저는 이름을 파티 멤버 정보에서 얻어 와서 사용함
std::string strNameAlias;
if( pPlayer != NULL )
{
strNameAlias = pPlayer->GetAlias();
}
else
{
strNameAlias = PartyManager::GetInstance().GetMemberDisplayName( nPartyID, nPlayerUID );
}
unsigned short shResult = RESULT_UNKNOWN;
if( nPartyID )
{
shResult = LeaveBattleArenaParty( nTeamNo, nPartyID, nPlayerUID, pPlayer, strNameAlias.c_str(), eLeaveType );
}
else
{
// 파티에 소속되지 않은 상태에서 여기 들어온 경우(비정상 루트로 파티에서만 이탈된 경우)는 조금 다르게 처리함
// 현재 팀의 파티들에 대한 검증(실제로 없는 파티일 가능성도 있으므로)
for( int i = 0; i < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++i )
{
int nCheckPartyID = m_anPartyID[ nTeamNo ][ i ];
if( nCheckPartyID && !PartyManager::GetInstance().IsBattleArenaTeamParty( nCheckPartyID ) )
{
// 파티 ID와 아레나 ID의 연결 정보 삭제
m_pPartyToArenaMap->Remove( nCheckPartyID );
// 파티 ID와 인스턴스 번호의 연결 정보 삭제
m_pPartyToInstanceMap->Remove( nCheckPartyID );
for( int nPartyIndex = i + 1; nPartyIndex < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++nPartyIndex )
{
m_anPartyID[ nTeamNo ][ nPartyIndex - 1 ] = m_anPartyID[ nTeamNo ][ nPartyIndex ];
}
// 팀 내에 맨 마지막에 있던 슬롯은 반드시 0이 되어야 함(위의 for 루프에서 맨 마지막 팀의 슬롯을 0으로 만들지 못함)
m_anPartyID[ nTeamNo ][ GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM - 1 ] = 0;
}
}
// 현재 팀의 파티가 비어있어서 경기가 종료되어야 하는지 체크해서 리턴
shResult = ( m_anPartyID[ nTeamNo ][ 0 ] ) ? RESULT_SUCCESS : RESULT_NOT_ENOUGH_BULLET;
}
// 나머지 유저들에게 이탈되었다는 것을 알려 줌
BattleArenaManager::BroadcastBattleArenaLeaveBattleMessageWithLog( this,
pPlayerInfo, pPlayer,
m_nWinTeamNo, nTeamNo,
strNameAlias.c_str(), eLeaveType );
if( pPlayer != NULL )
{
// 자신에게 이탈되었다는 것을 알려 줌
BattleArenaManager::SendBattleArenaLeaveMessage( pPlayer, eLeaveType );
// 경기 이탈에 성공했으니 캐릭터에게서 가명 사용 여부 해제하고 BattleArena 정보 제거
pPlayer->SetUseAlias( false );
pPlayer->SetBattleArenaID( 0 );
pPlayer->SetBattleArenaInstanceNo( 0 );
}
return shResult;
}
unsigned short BattleArenaInstance::LeaveBattleArenaParty( int nTeamNo,
int nPartyID,
PlayerUID nPlayerUID,
StructPlayer* pPlayer,
const char* pAlias,
_ARENA_LEAVE_TYPE eLeaveType )
{
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
{
return RESULT_UNKNOWN;
}
int nAccountID = 0;
AR_UNIT x = 0;
AR_UNIT y = 0;
unsigned char nLayer = 0;
const char* szAccountName = "";
const char* szPlayerName = pAlias;
if( pPlayer != NULL )
{
nAccountID = pPlayer->GetAccountID();
x = pPlayer->GetX();
y = pPlayer->GetY();
nLayer = pPlayer->GetLayer();
szAccountName = pPlayer->GetAccountName();
szPlayerName = pPlayer->GetName();
}
// 파티장이면서 멤버가 1명만 남아있거나 다른 파티멤버에게 자동 인계가 실패하면 파티 해산
// 그 외의 모든 경우에는 그냥 파티 탈퇴
if( PartyManager::GetInstance().IsLeader( nPartyID, nPlayerUID ) )
{
if( PartyManager::GetInstance().GetMemberCount( nPartyID ) == 1 ||
!PartyManager::GetInstance().AutoPromote( nPartyID, false ) )
{
BroadcastPartyDestroy( nPartyID );
LOG::Log11N4S( LM_PARTY_DESTROY,
nAccountID, nPlayerUID,
nPartyID, x, y, nLayer,
1,
0, 0, 0, 0,
szAccountName, LOG::STR_NTS, szPlayerName, LOG::STR_NTS,
PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS,
"", LOG::STR_NTS );
PartyManager::GetInstance().DestroyParty( nPartyID );
// 파티 ID와 아레나 ID의 연결 정보 삭제
m_pPartyToArenaMap->Remove( nPartyID );
// 파티 ID와 인스턴스 번호의 연결 정보 삭제
m_pPartyToInstanceMap->Remove( nPartyID );
// 파티가 해산될 경우에는 파티 목록에서 해당 파티ID 제거하되, 현재 팀 내의 뒤쪽에 다른 파티ID를 앞으로 당겨 줌(anPartyID 가 팀 단위로는 중간에 0 인 값이 들어있지 않도록 하기 위함)
for( int i = 0; i < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++i )
{
if( m_anPartyID[ nTeamNo ][ i ] != nPartyID )
continue;
m_anPartyID[ nTeamNo ][ i ] = 0;
for( int nPartyIndex = i + 1; nPartyIndex < GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM; ++nPartyIndex )
{
m_anPartyID[ nTeamNo ][ nPartyIndex - 1 ] = m_anPartyID[ nTeamNo ][ nPartyIndex ];
}
break;
}
// 팀 내에 맨 마지막에 있던 슬롯은 반드시 0이 되어야 함(위의 for 루프에서 맨 마지막 팀의 슬롯을 0으로 만들지 못함)
m_anPartyID[ nTeamNo ][ GameRule::BATTLE_ARENA_MAX_PARTY_COUNT_PER_TEAM - 1 ] = 0;
// 만일 현재 팀에 더 이상 남은 파티가 없다면 경기를 종료시켜야 하므로 별도의 리턴값으로 리턴
if( !m_anPartyID[ nTeamNo ][ 0 ] )
{
return RESULT_NOT_ENOUGH_BULLET;
}
}
else
{
PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nPartyID, "PROMOTE|%d|%s|",
nPartyID, PartyManager::GetInstance().GetLeaderDisplayName( nPartyID ).c_str() );
}
}
if( eLeaveType != ALT_EXERCISE_PARTY_KICK )
{
BroadcastPartyLeave( nPartyID, pAlias );
LOG::Log11N4S( LM_PARTY_LEAVE,
nAccountID, nPlayerUID, nPartyID,
x, y, nLayer,
1,
0, 0, 0, 0,
szAccountName, LOG::STR_NTS, szPlayerName, LOG::STR_NTS,
PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS,
"", LOG::STR_NTS );
}
else
{
PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nPartyID, "KICK|%s|%s|",
PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), pAlias );
}
PartyManager::GetInstance().LeaveParty( nPartyID, nPlayerUID );
return RESULT_SUCCESS;
}
unsigned short BattleArenaInstance::_disconnectPlayer( StructPlayer * pPlayer )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 경기가 끝난 후에 접속이 끊기는 경우는 그냥 경기에서 이탈시킴
if( m_bEnd )
{
// 필요한 경우에는 여기서 ALT_GAME_OVER 대신 ALT_DISCONNECT를 쓰는 것도 개념상 가능할 듯
unsigned short nResult = _removePlayer( pPlayer, ALT_GAME_OVER );
// 한 팀이 완전히 비워진 경우라도 이탈은 성공인 경우고, 한 팀이 완전히 비워지든 말든 상관없으므로 RESULT_SUCCESS로 바꿔서 결과를 리턴
if( nResult == RESULT_NOT_ENOUGH_BULLET )
nResult = RESULT_SUCCESS;
return nResult;
}
PlayerUID nPlayerUID = pPlayer->GetPlayerUID();
for( int nTeamNo = 0; nTeamNo < m_pArenaBase->nTeamCount; ++nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it =pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
PlayerInfo & pi = (*it);
if( pi.nPlayerUID != nPlayerUID )
continue;
if( !pi.pPlayer )
{
assert( 0 );
return RESULT_NOT_EXIST;
}
assert( pi.pPlayer == pPlayer );
// 경기장 내부에 있었을 경우 이탈 스크립트 실행(추가로 접속 종료에 의한 것이 아니라는 파라미터를 세팅해 줌)
if( pPlayer->IsInBattleArena() )
{
// 이탈 스크립트 실행(추가로 접속 종료에 의한 것이라는 파라미터를 세팅해 줌)
std::vector< std::string > vTagReplacement;
vTagReplacement.push_back( "#@disconnect@#" );
vTagReplacement.push_back( "1" );
ThreadPlayerHelper TPHelper( pPlayer );
BattleArenaRunEventScript( m_pArenaBase, BASET_LEAVE_INSTANCE, m_nInstanceNo, pPlayer, &vTagReplacement );
}
pi.pPlayer = NULL;
pi.nPenaltyCount = pPlayer->GetBattleArenaPenaltyCount();
pi.nOfflineLimitTime = GetArTime() + GameRule::nBattleArenaReconnectWaitDuration;
// 잠수 신고 응답 대기 상태였으면 해제
if( pi.bWaitingAbsenceCheck )
{
pi.bWaitingAbsenceCheck = false;
// 대신 다시 접속하면 바로 다시 잠수 신고 받을 수 있게 이전 잠수 신고 시점을 0 으로 초기화
pi.nLastAbsenceCheckTime = 0;
}
// 연습 경기일 경우이고 시작 전이었다면 팀의 레디 상태 초기화
if( m_pArenaBase->IsExerciseGameArena() && !m_bStart )
{
std::string strReady = _getFlag( FLAG_BATTLE_ARENA_EXERCISE_GAME_READY_STATE );
int nReadyState = atoi( strReady.c_str() );
nReadyState &= ~( 1 << nTeamNo );
XStringUtil::Format( strReady, "%d", nReadyState );
_setFlag( FLAG_BATTLE_ARENA_EXERCISE_GAME_READY_STATE, strReady.c_str() );
BattleArenaManager::BroadcastBattleArenaExerciseReadyState( this );
}
// 다른 유저들에게 현재 유저가 접속이 끊겼음을 알려 줌
BattleArenaManager::BroadcastBattleArenaDisconnectBattleMessage( this, pPlayer );
// 일단 실제로 이탈은 아니고 접속 종료지만 처리가 제대로 됐다는 표시라도 해주기 위해
// (그리고 향후 불필요한 추가 처리 발생하지 않게 하기 위해) 각 ID 값 초기화
pPlayer->SetBattleArenaID( 0 );
pPlayer->SetBattleArenaInstanceNo( 0 );
// 죽은 상태에서 접속 종료 처리를 해버리면 재접속 시 경기장 밖으로 쫓겨나게 되는 경우에 죽은 상태로 밖에서 로그인되기 때문에 여기서 부활시켜줌
if( pPlayer->IsDead() && pPlayer->IsInBattleArena() )
{
// 시작 지점으로 이동시켜 줄 필요는 없음(재접속 시 상황에 따라 처리될테니)
// 아레나 내부에서 부활 시에는 모든 지속효과를 복구하지만 이 시점에서는 복구를 해주더라도 퇴장 처리에서 결국 모두 삭제되기 때문에 복구해주지 않는다.
pPlayer->Resurrect( CRT_BATTLE_ARENA, pPlayer->GetMaxHP(), pPlayer->GetMaxMP(), 0, true); //false
}
return RESULT_SUCCESS;
}
}
}
return RESULT_NOT_EXIST;
}
unsigned short BattleArenaInstance::_reconnectPlayer( StructPlayer* pPlayer, ArPosition* pPosStart, unsigned char* pnLayer )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
int nPartyID = pPlayer->GetPartyID();
PlayerUID nPlayerUID = pPlayer->GetPlayerUID();
int nTeamNo = _getTeamNo( nPartyID );
if( nTeamNo != INVALID_BATTLE_ARENA_TEAM_NO )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin(); it != pPlayerInfoList->end(); ++it )
{
PlayerInfo& pi = (*it);
if( pi.nPlayerUID != nPlayerUID )
continue;
if( pi.pPlayer )
{
assert( 0 );
return RESULT_ALREADY_EXIST;
}
// 가명 사용 여부 다시 세팅
if( !m_pArenaBase->IsExerciseGameArena() )
pPlayer->SetUseAlias( true );
// 플레이어에게 아레나 정보 다시 세팅
pPlayer->SetBattleArenaID( m_pArenaBase->nID );
pPlayer->SetBattleArenaInstanceNo( m_nInstanceNo );
// PlayerInfo에 유저 온라인 상태로 변경 및 온라인 상태에 따라 갱신되어야 하는 내용 갱신
pi.pPlayer = pPlayer;
pi.nPenaltyCount = pPlayer->GetBattleArenaPenaltyCount();
pi.nOfflineLimitTime = 0;
// OnReconnect -> _reconnectPlayer 가 호출되는 시점은 캐릭터 로그인으로 로드된 정보가
// 클라이언트에 방송되기 전이므로 여기서 패킷을 방송해도 소용 없음
// 스크립트 실행 도 마찬가지로 방송을 동반하는 스크립트가 실행되면
// 클라이언트에서 정상적으로 처리되지 않을 가능성이 높음
// 따라서 이 지점에서 처리해야 할 각종 패킷 방송 및 스크립트 실행을
// OnReconnectPostProc -> _reconnectPlayerPostProc 에서 처리되도록 분리하고,
// 클라로 로그인 시 방송되어야 할 기본적인 정보를 방송한 후에 해당 처리를 함
// 다시 참여한 경기로 진입해야 할 좌표 세팅(해당 팀 시작 지점)
if( pPosStart )
*pPosStart = m_pArenaBase->posStart[ nTeamNo ];
if( pnLayer )
*pnLayer = m_nInstanceNo;
return RESULT_SUCCESS;
}
}
}
return RESULT_ACCESS_DENIED;
}
void BattleArenaInstance::_reconnectPlayerPostProc( StructPlayer * pPlayer ) const
{
int nTeamNo = _getTeamNo( pPlayer->GetPartyID() );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
{
assert( 0 );
return;
}
// 다시 참여된 경기 정보 보내 줌
BattleArenaManager::SendBattleArenaBattleInfoMessage( pPlayer, this );
BattleArenaManager::SendBattleArenaBattleStatusMessage( pPlayer, this );
// 시작되기 전의 연습 경기이고, 팀의 리더라면 레디 상태를 보내 줌
if( m_pArenaBase->IsExerciseGameArena() && !m_bStart )
{
int nPartyID = pPlayer->GetPartyID();
if( nPartyID == m_anPartyID[ nTeamNo ][ 0 ] && PartyManager::GetInstance().IsLeader( nPartyID, pPlayer->GetPlayerUID() ) )
BattleArenaManager::BroadcastBattleArenaExerciseReadyState( this );
}
// 현재 스코어 정보도 보내 줌
BattleArenaManager::SendBattleArenaScoreMessage( pPlayer, this );
// 다른 유저들에게 현재 유저가 재접속했음을 알려 줌
BattleArenaManager::BroadcastBattleArenaReconnectBattleMessage( this, pPlayer );
// 입장 스크립트 실행(추가로 재접속에 의한 것이라는 파라미터를 세팅해 줌)
std::vector< std::string > vTagReplacement;
vTagReplacement.push_back( "#@reconnect@#" );
vTagReplacement.push_back( "1" );
ThreadPlayerHelper TPHelper( pPlayer );
BattleArenaRunEventScript( m_pArenaBase, BASET_ENTER_INSTANCE, m_nInstanceNo, pPlayer, &vTagReplacement );
}
unsigned short BattleArenaInstance::_pendEnterWhileCountdown( StructPlayer * pPlayer )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 입장 처리가 발생하면 페널티를 받게 되는 경우를 미리 필터링
unsigned short nErrorCode = RESULT_SUCCESS;
// 사망 상태로는 이용 불가
if( pPlayer->IsDead() ) nErrorCode = RESULT_NOT_ENOUGH_HP;
// 거래 중엔 이용 불가
else if( pPlayer->IsTrading() ) nErrorCode = RESULT_NOT_ACTABLE_WHILE_TRADING;
// 베어로드, 데스매치, 인던에서는 이용 불가
else if( pPlayer->IsInHuntaholic() ) nErrorCode = RESULT_NOT_ACTABLE_IN_HUNTAHOLIC;
else if( pPlayer->IsInDeathmatch() ) nErrorCode = RESULT_NOT_ACTABLE_IN_DEATHMATCH;
else if( pPlayer->IsInInstanceDungeon() ) nErrorCode = RESULT_NOT_ACTABLE_IN_INSTANCE_DUNGEON;
if( nErrorCode != RESULT_SUCCESS )
return nErrorCode;
int nTeamNo = _getTeamNo( pPlayer->GetPartyID() );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
return RESULT_NOT_EXIST;
// 이미 끝난 경기면 못 들어감(시작된 후에는 들어갈 수 있음 - 빠른 입장으로 입장 카운트 중 난입)
if( m_bEnd )
return RESULT_NOT_ACTABLE;
PlayerUID nPlayerUID = pPlayer->GetPlayerUID();
PlayerInfo * pPlayerInfo = _getPlayerInfo( nPlayerUID, nTeamNo );
if( !pPlayerInfo )
{
assert( 0 );
return RESULT_NOT_EXIST;
}
// 이미 입장된 경우나 혹은 예정 시점이 경과한 경우(이 함수가 호출된 직후에 BattleArenaInstance::_onProcess가 호출되면서 처리될 예정)에는 패스
AR_TIME tCurrent = GetArTime();
if( !pPlayerInfo->nPendedEnterTime || pPlayerInfo->nPendedEnterTime <= tCurrent )
return RESULT_ACCESS_DENIED;
// 입장 예정 시점만 당겨놓고, 실제 입장 처리는 BattleArenaInstance::_onProcess에서 일어나도록 놔둠
pPlayerInfo->nPendedEnterTime = tCurrent;
return RESULT_SUCCESS;
}
unsigned short BattleArenaInstance::_activateProp( StructPlayer * pPlayer, int nPropIndex )
{
if( !m_bStart || m_bEnd )
return RESULT_NOT_ACTABLE;
int nTeamNo = _getTeamNo( pPlayer->GetPartyID() );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
return RESULT_ACCESS_DENIED;
PlayerInfo * pPlayerInfo = _getPlayerInfo( pPlayer->GetPlayerUID(), nTeamNo );
if( !pPlayerInfo )
return RESULT_ACCESS_DENIED;
_PROP_STATE ePropState = ( nTeamNo == 0 ) ? PS_OWNED_BY_TEAM_0 : PS_OWNED_BY_TEAM_1;
std::string strPropState = _getFlag( FLAG_BATTLE_ARENA_PROP_STATE );
if( strPropState.empty() )
return RESULT_NOT_ACTABLE;
if( nPropIndex < 1 || nPropIndex > 9 )
{
assert( 0 );
return RESULT_NOT_EXIST;
}
// Zero-based index로 변환하기 위해 1 뺌
--nPropIndex;
int nWholePropState = atoi( strPropState.c_str() );
int nPropState = ( nWholePropState >> ( nPropIndex * 2 ) ) & 0x3;
// 프랍이 월드에서 사라진 상태라면 실패(빙고에서만 발생)
if( nPropState == PS_NOT_EXIST )
return RESULT_NOT_READY;
// 이미 현재 팀 소유 프랍이면 실패
if( nPropState == ePropState )
return RESULT_ALREADY_EXIST;
// 지정된 프랍 활성화 상태로 플래그 변경
int nBitMask = ~( 0x3 << ( nPropIndex * 2 ) );
nWholePropState = ( nWholePropState & nBitMask ) | ( ePropState << ( nPropIndex * 2 ) );
// 빙고 타입의 경기에서만 사용되는 빙고 체크 값 제거
nWholePropState &= 0x00FFFFFF;
// 변경된 값으로 플래그 세팅
XStringUtil::Format( strPropState, "%d", nWholePropState );
_setFlag( FLAG_BATTLE_ARENA_PROP_STATE, strPropState.c_str() );
// 상태 변경된 채로 참여자들에게 방송
BattleArenaManager::BroadcastBattleArenaBattleStatusMessage( this );
if( !m_pArenaBase->IsExerciseGameArena() )
{
// 개인별 실시간 AP 지급
// 프랍 활성화에 의해 얻을 수 있는 최대 AP 제한 적용
if( pPlayerInfo->nTotalGainAPByProp < m_pArenaBase->nMaxAPByPropActivate )
{
int nGainAP = m_pArenaBase->nMaxAPByPropActivate - pPlayerInfo->nTotalGainAPByProp;
if( nGainAP > m_pArenaBase->nAPPerPropActivate )
nGainAP = m_pArenaBase->nAPPerPropActivate;
pPlayer->AddBattleArenaPoint( nGainAP );
pPlayerInfo->nTotalGainAPByProp += nGainAP;
}
}
++pPlayerInfo->nPropActivateCount;
// 점수 방송(팀 점수 증가는 프랍 활성화로는 없고 킬로만 발생)
BattleArenaManager::BroadcastBattleArenaScoreUpdateMessage( this, pPlayer, nTeamNo );
// 경기 타입별 프랍 동작에 따른 처리
switch( m_pArenaBase->eType )
{
case BAT_SLAUGHTER:
// 슬래터에서는 0번 프랍 소유권에 따라 경험치 배율을 변경해 줌
if( !nPropIndex )
{
if( nTeamNo == 0 )
{
m_aTeamInfo[ 0 ].fKillScoreRate = 2;
m_aTeamInfo[ 1 ].fKillScoreRate = 1;
}
else if( nTeamNo == 1 )
{
m_aTeamInfo[ 0 ].fKillScoreRate = 1;
m_aTeamInfo[ 1 ].fKillScoreRate = 2;
}
else
{
// 두 팀 이상이 참가하는 슬래터 경기 타입은 기획되어 있지 않음
assert( 0 );
}
// 참여자들에게 필요한 채팅 방송
std::string strMessage;
XStringUtil::Format( strMessage, "@2488\v#@arena_team@#\v@%d\v#@player_name@#\v%s",
GameRule::GetBattleArenaTeamNameStringID( nTeamNo, true ), pPlayer->GetAlias() );
for( int _nTeamNo = 0 ; _nTeamNo < m_pArenaBase->nTeamCount ; ++_nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( _nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::const_iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
const PlayerInfo & pi = (*it);
if( !pi.pPlayer )
continue;
SendChatMessage( false, CHAT_CENTER_NOTICE, "@NOTICE", pi.pPlayer, strMessage.c_str() );
if( nTeamNo == _nTeamNo )
SendChatMessage( false, CHAT_BATTLE, "@SYSTEM", pi.pPlayer, "@2489" );
}
}
}
}
break;
case BAT_BINGO:
{
// 동작 시킨 빙고 프랍과 연결된 빙고 라인 체크
static const unsigned char BINGO_PROP_INDICES[ 8 ][ 3 ] = {
{ 0, 1, 2 }, // Line 0
{ 3, 4, 5 }, // Line 1
{ 6, 7, 8 }, // Line 2
{ 0, 3, 6 }, // Line 3
{ 1, 4, 7 }, // Line 4
{ 2, 5, 8 }, // Line 5
{ 0, 4, 8 }, // Line 6
{ 2, 4, 6 } // Line 7
};
static const unsigned char INVALID_LINE_INDEX = 0xFF;
static const unsigned char BINGO_RELATED_LINE_INDICES[ 9 ][ 4 ] = {
{ 0, 3, 6, INVALID_LINE_INDEX },
{ 0, 4, INVALID_LINE_INDEX, INVALID_LINE_INDEX },
{ 0, 5, 7, INVALID_LINE_INDEX },
{ 1, 3, INVALID_LINE_INDEX, INVALID_LINE_INDEX },
{ 1, 4, 6, 7 },
{ 1, 5, INVALID_LINE_INDEX, INVALID_LINE_INDEX },
{ 2, 3, 7, INVALID_LINE_INDEX },
{ 2, 4, INVALID_LINE_INDEX, INVALID_LINE_INDEX },
{ 2, 5, 6, INVALID_LINE_INDEX }
};
std::vector< int > vBingoLines;
for( int nCheckLineIndex = 0 ; nCheckLineIndex < 4 ; ++nCheckLineIndex )
{
int nCurrentLineIndex = BINGO_RELATED_LINE_INDICES[ nPropIndex ][ nCheckLineIndex ];
if( nCurrentLineIndex == INVALID_LINE_INDEX )
break;
int nCheckPropIndex;
for( nCheckPropIndex = 0 ; nCheckPropIndex < 3 ; ++nCheckPropIndex )
{
int nCurrentPropIndex = BINGO_PROP_INDICES[ nCurrentLineIndex ][ nCheckPropIndex ];
if( ( ( nWholePropState >> ( nCurrentPropIndex * 2 ) ) & 0x3 ) != ePropState )
break;
}
// 현재 체크한 라인이 빙고 달성된 경우
if( nCheckPropIndex >= 3 )
{
vBingoLines.push_back( nCurrentLineIndex );
}
}
// 달성된 빙고가 없다면 추가 처리 스킵
if( vBingoLines.empty() )
break;
// 빙고 달성에 따른 스크립트 처리(지역락 문제를 유발할 수 있기 때문에 여기서 직접 실행하지 못하고 예약만 함)
std::string strBingoScript;
XStringUtil::Format( strBingoScript, "on_battle_arena_bingo( %d, %d, %d, %d )", m_pArenaBase->nID, (int)m_nInstanceNo, nTeamNo, (int)vBingoLines.size() );
_pendScript( pPlayer, BASET_BINGO, strBingoScript.c_str() );
// 빙고에 참여한 프랍/라인 상태 변경
std::vector< int > vPropIndicesToBeRemoved;
for( std::vector< int >::const_iterator it = vBingoLines.begin() ; it != vBingoLines.end() ; ++it )
{
int nLineIndex = (*it);
// 프랍 상태 변경(프랍 없음)
for( int nCheckPropIndex = 0 ; nCheckPropIndex < 3 ; ++nCheckPropIndex )
{
int nCurrentPropIndex = BINGO_PROP_INDICES[ nLineIndex ][ nCheckPropIndex ];
nWholePropState &= ~( 0x3 << ( nCurrentPropIndex * 2 ) );
// 빙고 참여 프랍 인덱스 보관(프랍 제거 처리용)
if( std::find( vPropIndicesToBeRemoved.begin(), vPropIndicesToBeRemoved.end(), nCurrentPropIndex ) == vPropIndicesToBeRemoved.end() )
vPropIndicesToBeRemoved.push_back( nCurrentPropIndex );
}
// 빙고 라인 세팅
nWholePropState |= 1 << ( nLineIndex + 24 );
}
// 변경된 값으로 플래그 세팅
XStringUtil::Format( strPropState, "%d", nWholePropState );
_setFlag( FLAG_BATTLE_ARENA_PROP_STATE, strPropState.c_str() );
// 상태 변경된 채로 참여자들에게 방송
BattleArenaManager::BroadcastBattleArenaBattleStatusMessage( this );
// 빙고 라인 번호 플래그 제거(향후 플래그값 방송에서 다시 빙고가 된 것처럼 방송될 가능성 방지)
nWholePropState &= 0x00FFFFFF;
XStringUtil::Format( strPropState, "%d", nWholePropState );
_setFlag( FLAG_BATTLE_ARENA_PROP_STATE, strPropState.c_str() );
// 이 값으로 다시 즉시 방송할 필요는 없음
// 빙고 참여 프랍 제거
// * 특정 빙고 프랍이 동작해서 빙고가 완성되었을 때 지역락은 마지막에 발동된 프랍과 시전자 주변만 걸려있지만
// 같은 레이어 상에 있는 유저들은 csArena, csBattle을 걸지 않은 상태로 vPlayerInfo 에서 빠져나가지 못하므로
// (StructPlayer::LogoutNow -> BattleArenaManager::OnDisconnect)
// 패킷 방송 대상(프랍의 TS_LEAVE를 받을 유저들)이 변할 가능성은 없음.
// 이거 믿고 위에서도 BroadcastBattleArenaBattleStatusMessage 같은 거 막 호출함 -_ -ㅋ;
for( std::vector< int >::const_iterator it = vPropIndicesToBeRemoved.begin() ; it != vPropIndicesToBeRemoved.end() ; ++it )
{
// DB 상에서 지정한 프랍 번호는 1 ~ 9 가 되고, 0 은 번호를 갖지 않는 프랍을 의미함
// vPropIndicesToBeRemoved에 있는 프랍 번호는 0 ~ 8 이므로 1을 더해서 사용해야 함
int nRemovePropIndex = (*it) + 1;
bool bRemoved = false;
for( std::vector< StructFieldProp * >::iterator itProp = m_vRespawnedFieldProp.begin() ; itProp != m_vRespawnedFieldProp.end() ; /* 루프에서 ++itProp 처리 */ )
{
StructFieldProp * pProp = (*itProp);
const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * pRespawn = static_cast< const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * >( pProp->GetRespawnInfo() );
if( pRespawn->nPropIndexInArena != nRemovePropIndex )
{
++itProp;
continue;
}
ArcadiaServer::Instance().RemoveObject( pProp );
StructFieldProp::PendFreeFieldProp( pProp );
bRemoved = true;
break;
}
assert( bRemoved );
}
// 참여자들에게 필요한 채팅 방송
std::string strMessage;
XStringUtil::Format( strMessage, "@2490\v#@arena_team@#\v@%d\v#@player_name@#\v%s",
GameRule::GetBattleArenaTeamNameStringID( nTeamNo, true ), pPlayer->GetAlias() );
for( int _nTeamNo = 0 ; _nTeamNo < m_pArenaBase->nTeamCount ; ++_nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( _nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::const_iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
const PlayerInfo & pi = (*it);
if( !pi.pPlayer )
continue;
SendChatMessage( false, CHAT_CENTER_NOTICE, "@NOTICE", pi.pPlayer, strMessage.c_str() );
if( nTeamNo == _nTeamNo )
SendChatMessage( false, CHAT_BATTLE, "@SYSTEM", pi.pPlayer, "@2491" );
}
}
}
}
break;
}
return RESULT_SUCCESS;
}
_PROP_STATE BattleArenaInstance::_getPropState( int nPropIndex ) const
{
if( nPropIndex < 1 || nPropIndex > 9 )
{
assert( 0 );
return PS_NOT_EXIST;
}
// Zero-based index로 변환하기 위해 1 뺌
--nPropIndex;
std::string strPropState = _getFlag( FLAG_BATTLE_ARENA_PROP_STATE );
int nPropState = atoi( strPropState.c_str() );
return static_cast< _PROP_STATE >( ( nPropState >> ( nPropIndex * 2 ) ) & 0x3 );
}
void BattleArenaInstance::_setPropState( int nPropIndex, _PROP_STATE ePropState )
{
if( nPropIndex < 1 || nPropIndex > 9 )
{
assert( 0 );
return;
}
// Zero-based index로 변환하기 위해 1 뺌
--nPropIndex;
std::string strPropState = _getFlag( FLAG_BATTLE_ARENA_PROP_STATE );
int nPropState = atoi( strPropState.c_str() );
// 지정된 프랍 활성화 상태로 플래그 변경
int nBitMask = ~( 0x3 << ( nPropIndex * 2 ) );
nPropState = ( nPropState & nBitMask ) | ( ePropState << ( nPropIndex * 2 ) );
// 변경된 값으로 플래그 세팅
XStringUtil::Format( strPropState, "%d", nPropState );
_setFlag( FLAG_BATTLE_ARENA_PROP_STATE, strPropState.c_str() );
}
bool BattleArenaInstance::_isCastablePropForTeam( const StructFieldProp * pProp, int nTeamNo ) const
{
// 지정된 프랍이 해당 아레나에서 경기에 영향을 주는 타입으로 리젠된 프랍인지 체크
if( std::find( m_vRespawnedFieldProp.begin(), m_vRespawnedFieldProp.end(), pProp ) == m_vRespawnedFieldProp.end() )
return false;
const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * pRespawn = static_cast< const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * >( pProp->GetRespawnInfo() );
if( pRespawn->nPropIndexInArena < 1 || pRespawn->nPropIndexInArena > 9 )
return false;
// 이미 해당 프랍을 지정된 팀에서 소유 중인지 체크
_PROP_STATE ePropState = _getPropState( pRespawn->nPropIndexInArena );
switch( nTeamNo )
{
case 0: if( ePropState == PS_OWNED_BY_TEAM_0 ) return false; break;
case 1: if( ePropState == PS_OWNED_BY_TEAM_1 ) return false; break;
default:
assert( 0 );
return false;
}
return true;
}
unsigned short BattleArenaInstance::_onPlayerDead( StructPlayer * pPlayer, StructPlayer * pKiller )
{
// 이미 목표 점수를 도달한 팀이 있다면 플레이어 킬에 대해 아무런 처리도 하지 않음
// * 플레이어 킬 외에 점수 획득 수단이 없으므로 여기서만 체크하면 되지만,
// 향후 점수 획득 수단이 늘어나면 각 점수 획득 가능 위치마다 이 체크를 해 줘야 함
if( m_bObjectiveScoreReached )
return RESULT_LIMIT_MAX;
int nTeamNo = _getTeamNo( pPlayer->GetPartyID() );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
return RESULT_NOT_OWN;
int nKillerTeamNo = _getTeamNo( pKiller->GetPartyID() );
if( nKillerTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
return RESULT_NOT_OWN;
// 같은 팀원한테 죽은 상황이면 아무것도 안 함
if( nTeamNo == nKillerTeamNo )
{
// 근데 팀킬이 일반적으로 불가능해야 함;;
assert( 0 );
return RESULT_SUCCESS;
}
// 죽은 사람은 데스 카운트 증가를 위해 PlayerInfo가 필요
PlayerInfo * pDeadPlayerInfo = _getPlayerInfo( pPlayer->GetPlayerUID(), nTeamNo );
if( !pDeadPlayerInfo )
return RESULT_NOT_OWN;
// 킬러의 경우 킬 카운트 및 획득 총점 누적을 위해 PlayerInfo가 필요
PlayerInfo * pKillerPlayerInfo = _getPlayerInfo( pKiller->GetPlayerUID(), nKillerTeamNo );
if( !pKillerPlayerInfo )
return RESULT_NOT_OWN;
// 진행 중인 경기가 아니면 아무것도 안 함(성공으로 리턴하지는 않지만 별 의미 없음)
if( !m_bStart || m_bEnd )
return RESULT_NOT_READY;
// 죽은 사람 팀의 데스 카운트 증가 및 킬러 팀/유저 킬 카운트, 점수 가산 처리
++m_aTeamInfo[ nTeamNo ].nTotalDeathCount;
++m_aTeamInfo[ nKillerTeamNo ].nTotalKillCount;
m_aTeamInfo[ nKillerTeamNo ].nScore += m_aTeamInfo[ nKillerTeamNo ].fKillScoreRate * m_pArenaBase->nScorePerKill;
if( m_aTeamInfo[ nKillerTeamNo ].nScore > m_pArenaBase->nObjectivePoint )
m_aTeamInfo[ nKillerTeamNo ].nScore = m_pArenaBase->nObjectivePoint;
// 킬러 AP 지급(연습 경기면 안 함)
if( !m_pArenaBase->IsExerciseGameArena() )
{
// 개인별 실시간 AP 지급
// 킬에 의해 얻을 수 있는 최대 AP 제한 적용
if( pKillerPlayerInfo->nTotalGainAPByKill < m_pArenaBase->nMaxAPByKill )
{
int nGainAP = m_pArenaBase->nMaxAPByKill - pKillerPlayerInfo->nTotalGainAPByKill;
if( nGainAP > m_pArenaBase->nAPPerKill )
nGainAP = m_pArenaBase->nAPPerKill;
pKiller->AddBattleArenaPoint( nGainAP );
pKillerPlayerInfo->nTotalGainAPByKill += nGainAP;
}
}
++pDeadPlayerInfo->nDeathCount;
++pKillerPlayerInfo->nKillCount;
// 점수 방송
BattleArenaManager::BroadcastBattleArenaScoreUpdateMessage( this, pKiller, nKillerTeamNo, pPlayer, nTeamNo );
// 유저 킬 시 스크립트 실행(지역락 문제를 유발할 수 있기 때문에 여기서 직접 실행하지 못하고 예약만 함)
if( !m_pArenaBase->strScriptOnKill.empty() )
{
std::string strScript = m_pArenaBase->strScriptOnKill;
XStringUtil::ReplaceFormat( strScript, "#@instance_no@#", "%d", (int)m_nInstanceNo );
XStringUtil::ReplaceFormat( strScript, "#@dead_player_name@#", "%s", pPlayer->GetName() );
_pendScript( pKiller, BASET_KILL_PLAYER, strScript.c_str() );
}
// 경기에 참여 중인 모든 유저들에게 방송
std::string strNotice;
XStringUtil::Format( strNotice, "@2439\v#@kill_player_name@#\v%s\v#@death_player_name@#\v%s", pKiller->GetAlias(), pPlayer->GetAlias() );
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::const_iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
const PlayerInfo & pi = (*it);
if( pi.pPlayer )
SendChatMessage( false, CHAT_CENTER_NOTICE, "@NOTICE", pi.pPlayer, strNotice.c_str() );
}
}
}
// 목표 점수 도달 체크
if( m_aTeamInfo[ nKillerTeamNo ].nScore >= m_pArenaBase->nObjectivePoint )
{
// 여기서 바로 _finishBattle을 호출하지 않고 _onProcess에서 _finishBattle이 호출될 수 있도록 플래그만 세팅
// * 지역 락 문제 때문에 특정 유저 종속적인 락을 걸고 실행되는 코드에서 _finishBattle을 호출할 수 없음
// 이로 인해 플래그만 세팅되고 나서 승리 처리 전에 경기가 파토나는 경우가 있을 수 있는데,
// 이미 시작된 경기에 대해 _finishBattle 함수를 통해 종료되는 처리는 모두 BattleArenaInstance::_onProcess 안에 있으므로
// _onProcess 안에서만 종료 조건 체크 순서에 주의하면 로직상 크게 문제가 발생할 사례는 없을 것으로 생각됨.
// 단, 거꾸로 악용해서 모든 유저가 이탈하는 바람에 파토날 걸로 확정된 방에서
// _onProcess 들어오기 전에 킬해서 목표 점수를 도달하면 목표 점수 도달로 끝나게 됨.
// 근데 이 정도는 악용도 쉽지 않거니와 소소한 여흥거리 정도로 봐줘도 문제 없다고 보임.
m_bObjectiveScoreReached = true;
}
return RESULT_SUCCESS;
}
unsigned short BattleArenaInstance::_requestAbsenceCheck( StructPlayer * pPlayer, AR_HANDLE hCheckTarget )
{
int nPartyID = pPlayer->GetPartyID();
int nTeamNo = _getTeamNo( nPartyID );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
return RESULT_NOT_EXIST;
// 자기 자신에게는 잠수 신고 불가
if( pPlayer->GetHandle() == hCheckTarget )
return RESULT_LIMIT_TARGET;
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
// 잠수 신고는 같은 팀원 끼리만 가능하므로 신고자의 팀 내에서만 대상 검색
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
PlayerInfo & pi = (*it);
if( !pi.pPlayer || pi.pPlayer->GetHandle() != hCheckTarget )
continue;
// 현재 잠수 신고 당한 상태 체크
if( pi.bWaitingAbsenceCheck )
return RESULT_ALREADY_EXIST;
AR_TIME tCurrent = GetArTime();
// 잠수 신고 쿨타임 체크(nLastAbsenceCheckTime를 신고 응답 대기 상태가 아닐 때에는 다음 체크 가능 시점으로 사용함)
if( pi.nLastAbsenceCheckTime > tCurrent )
return RESULT_COOL_TIME;
// 잠수 신고 체크 시작
pi.bWaitingAbsenceCheck = true;
pi.nLastAbsenceCheckTime = tCurrent + GameRule::BATTLE_ARENA_ABSENCE_CHECK_ANSWER_LIMIT;
BattleArenaManager::SendBattleArenaAbsenceCheck( pi.pPlayer, pi.nLastAbsenceCheckTime );
// 팀원들에게 누군가가 누군가에게 잠수 신고를 당했다는 내용 방송
PrintfLinkedPartyChatMessage( false, CHAT_BATTLE, "@SYSTEM", nPartyID,
"@2443\v#@report_player@#\v%s\v#@dive_player@#\v%s",
pPlayer->GetAlias(), pi.pPlayer->GetAlias() );
return RESULT_SUCCESS;
}
}
return RESULT_LIMIT_TARGET;
}
unsigned short BattleArenaInstance::_answerAbsenceCheck( StructPlayer * pPlayer, bool bSuccess )
{
int nPartyID = pPlayer->GetPartyID();
int nTeamNo = _getTeamNo( nPartyID );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
return RESULT_NOT_EXIST;
PlayerInfo * pPlayerInfo = _getPlayerInfo( pPlayer->GetPlayerUID(), nTeamNo );
if( !pPlayerInfo )
return RESULT_NOT_EXIST;
// 잠수 신고 응답 대기 상태가 아닌 경우
if( !pPlayerInfo->bWaitingAbsenceCheck )
return RESULT_ACCESS_DENIED;
// 이미 응답 제한 시간이 초과된 경우는 체크하지 않음(onProcess에서 이탈 처리가 되기 전에 응답이 왔다는 거니까)
if( bSuccess )
{
// 응답이 성공한 경우에는 해제 및 쿨타임 세팅
pPlayerInfo->bWaitingAbsenceCheck = false;
pPlayerInfo->nLastAbsenceCheckTime = GetArTime() + GameRule::BATTLE_ARENA_ABSENCE_CHECK_COOLTIME;
// 잠수 신고를 해제했다는 내용을 팀원들에게 방송
PrintfLinkedPartyChatMessage( false, CHAT_BATTLE, "@SYSTEM", nPartyID, "@2444\v#@player_name@#\v%s", pPlayer->GetAlias() );
}
else
{
// 응답이 실패한 경우에는 여기서 직접 접속 종료 처리를 해버리지 않고 시간 제한을 앞당겨서 다음 onProcess에서 튕겨나가게 처리
pPlayerInfo->nLastAbsenceCheckTime = 0;
}
return RESULT_SUCCESS;
}
unsigned short BattleArenaInstance::_setReadyExerciseGame( StructPlayer * pPlayer, bool bReady )
{
// 경기가 시작된 이후나 끝난 상태라면 변경 불가
if( m_bStart || m_bEnd )
return RESULT_NOT_ACTABLE;
// 팀 번호 확인
int nPartyID = pPlayer->GetPartyID();
int nTeamNo = _getTeamNo( nPartyID );
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
{
// 응;?
assert( 0 );
return RESULT_NOT_EXIST;
}
// 레디 상태 변경은 해당 팀의 첫 번째 파티의 파티장만이 가능
if( nPartyID != m_anPartyID[ nTeamNo ][ 0 ] || !PartyManager::GetInstance().IsLeader( nPartyID, pPlayer->GetPlayerUID() ) )
return RESULT_ACCESS_DENIED;
// 레디를 하려고 할 때 해당 팀의 멤버 수가 조건에 맞는지 체크
if( m_pArenaBase->nMinMember / m_pArenaBase->nTeamCount > _getTeamMemberCount( nTeamNo ) )
return RESULT_LIMIT_MIN;
if( m_pArenaBase->nMaxMember / m_pArenaBase->nTeamCount < _getTeamMemberCount( nTeamNo ) )
return RESULT_LIMIT_MAX;
std::string strReady = _getFlag( FLAG_BATTLE_ARENA_EXERCISE_GAME_READY_STATE );
int nReadyState = atoi( strReady.c_str() );
// 이미 요청한 레디 상태에 해당한다면 아무것도 안 함
if( static_cast< bool >( nReadyState & ( 1 << nTeamNo ) ) == bReady )
return RESULT_SUCCESS;
nReadyState ^= ( 1 << nTeamNo );
XStringUtil::Format( strReady, "%d", nReadyState );
_setFlag( FLAG_BATTLE_ARENA_EXERCISE_GAME_READY_STATE, strReady.c_str() );
BattleArenaManager::BroadcastBattleArenaExerciseReadyState( this );
return RESULT_SUCCESS;
}
size_t BattleArenaInstance::_doEachPlayer( ArObjectFunctor & fo )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// fo 내부에서 지역락 걸면 안 됨
size_t nCount = 0;
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
nCount += _doEachPlayerInTeam( nTeamNo, fo );
}
return nCount;
}
size_t BattleArenaInstance::_doEachPlayerInTeam( int nTeamNo, ArObjectFunctor & fo )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 이 함수를 실행하는 do_each_player_in_battle_arena_instance, do_each_player_in_battle_arena_team 함수는
// 아레나 영역 전체에 지역락을 건 상태로만 실행되어야 함
assert( ArcadiaServer::Instance().IsLocked( m_pArenaBase->nArenaRegionLeft, m_pArenaBase->nArenaRegionTop, m_pArenaBase->nArenaRegionRight, m_pArenaBase->nArenaRegionBottom, m_nInstanceNo ) );
// fo 내부에서 지역락 걸면 안 됨
size_t nCount = 0;
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
PlayerInfo & pi = (*it);
StructPlayer * pPlayer = pi.pPlayer;
// 오프라인이거나 입장 대기 중 또는 경기장 외부인 유저는 스크립트 실행 대상 제외(지역 락 이슈)
if( !pPlayer || pi.nPendedEnterTime || !pPlayer->IsInBattleArena() )
continue;
fo( pPlayer );
++nCount;
}
}
return nCount;
}
BattleArenaInstance::PlayerInfo * BattleArenaInstance::_getPlayerInfo( PlayerUID nPlayerUID, int nTeamNo )
{
// vPlayerInfo[ 팀 번호 ].reserve( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT_PER_TEAM ) 가 되어있지 않으면
// 이 함수에서 리턴한 포인터의 주소가 무효화되는 경우가 발생될 수 있음(vPlayerInfo 내부 버퍼의 reallocation이 발생할 수 있으므로)
// 그래서 꼭 vPlayerInfo[ 팀 번호 ].reserve( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT_PER_TEAM ) 를 BattleArenaInstance 생성자에서 해 줌
// 팀 번호가 지정되어 있지 않으면 모든 팀 검색
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
{
for( int _nTeamNo = 0 ; _nTeamNo < m_pArenaBase->nTeamCount ; ++_nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( _nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
if( (*it).nPlayerUID == nPlayerUID )
return &(*it);
}
}
}
}
// 팀 번호가 지정되어 있으면 해당 팀 내에서만 검색
else
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
if( (*it).nPlayerUID == nPlayerUID )
return &(*it);
}
}
}
return NULL;
}
const BattleArenaInstance::PlayerInfo * BattleArenaInstance::_getPlayerInfo( PlayerUID nPlayerUID, int nTeamNo ) const
{
// vPlayerInfo[ 팀 번호 ].reserve( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT_PER_TEAM ) 가 되어있지 않으면
// 이 함수에서 리턴한 포인터의 주소가 무효화되는 경우가 발생될 수 있음(vPlayerInfo 내부 버퍼의 reallocation이 발생할 수 있으므로)
// 그래서 꼭 vPlayerInfo[ 팀 번호 ].reserve( GameRule::BATTLE_ARENA_MAX_MEMBER_COUNT_PER_TEAM ) 를 BattleArenaInstance 생성자에서 해 줌
// 팀 번호가 지정되어 있지 않으면 모든 팀 검색
if( nTeamNo == INVALID_BATTLE_ARENA_TEAM_NO )
{
for( int _nTeamNo = 0 ; _nTeamNo < m_pArenaBase->nTeamCount ; ++_nTeamNo )
{
const std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( _nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::const_iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
if( (*it).nPlayerUID == nPlayerUID )
return &(*it);
}
}
}
}
// 팀 번호가 지정되어 있으면 해당 팀 내에서만 검색
else
{
const std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::const_iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
if( (*it).nPlayerUID == nPlayerUID )
return &(*it);
}
}
}
return NULL;
}
void BattleArenaInstance::_setFlag( const char* pszName, const char* pszValue )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
m_mapInstanceFlag[ pszName ] = pszValue;
}
const std::string& BattleArenaInstance::_getFlag( const char* pszName ) const
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
static const std::string strEmpty;
std::map< std::string, std::string >::const_iterator it = m_mapInstanceFlag.find( pszName );
if( it == m_mapInstanceFlag.end() )
return strEmpty;
return it->second;
}
bool BattleArenaInstance::_delFlag( const char* pszName )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
return (m_mapInstanceFlag.erase( pszName ) != 0);
}
void BattleArenaInstance::_pendScript( StructPlayer * pExecutor, enum _BATTLE_ARENA_SCRIPT_EXEC_TYPE eExecType, const char * pszScript )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
m_vPendedScript.push_back( PendedScript( pExecutor, eExecType, pszScript ) );
}
void BattleArenaInstance::_procPendedScript()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
if( m_vPendedScript.empty() )
return;
// 혹시라도 스크립트에 의해 다시 _pendScript가 호출되면 vPendedScript에 대한 iterator가 무효화될 수 있으니 swap시킨 사본(?) 사용
std::vector< PendedScript > _vPendedScript;
_vPendedScript.swap( m_vPendedScript );
for( std::vector< PendedScript >::const_iterator it = _vPendedScript.begin() ; it != _vPendedScript.end() ; ++it )
{
const PendedScript & pendedScript = (*it);
ThreadPlayerHelper TPHelper( pendedScript.pExecutor );
BattleArenaRunScriptWithLog( m_pArenaBase, pendedScript.eExecType, m_nInstanceNo, pendedScript.strScript, pendedScript.pExecutor );
}
}
void BattleArenaInstance::_initialRespawn()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 몬스터 리젠
for( std::vector< const BATTLE_ARENA_MONSTER_RESPAWN * >::const_iterator it = m_pArenaBase->vMonsterRespawnList.begin() ; it != m_pArenaBase->vMonsterRespawnList.end() ; ++it )
{
const BATTLE_ARENA_MONSTER_RESPAWN * pRespawn = (*it);
// 리젠 마리 수 만큼 반복
for( int nIndex = 0 ; nIndex < pRespawn->nCount ; ++nIndex )
{
int nRespawnX = XRandom( static_cast< int >( pRespawn->bxArea.GetLeft() ), static_cast< int >( pRespawn->bxArea.GetRight() ) );
int nRespawnY = XRandom( static_cast< int >( pRespawn->bxArea.GetTop() ), static_cast< int >( pRespawn->bxArea.GetBottom() ) );
if( GameContent::IsBlocked( nRespawnX, nRespawnY ) == true )
{
FILELOG( "Unable to respawn monster in battle arena. ArenaID(%d), RespawnID(%d)", m_pArenaBase->nID, pRespawn->nID );
_cprint( "Unable to respawn monster in battle arena. ArenaID(%d), RespawnID(%d)\n", m_pArenaBase->nID, pRespawn->nID );
}
StructMonster * pMob = respawnMonster( nRespawnX, nRespawnY, m_nInstanceNo, pRespawn->nMonsterID, pRespawn->bWandering, 0, this, false, 0, pRespawn->fFace );
if( !pMob )
{
FILELOG( "Unknown monster is to be respawned in battle arena. ArenaID(%d), RespawnID(%d)", m_pArenaBase->nID, pRespawn->nID );
_cprint( "Unknown monster is to be respawned in battle arena. ArenaID(%d), RespawnID(%d)\n", m_pArenaBase->nID, pRespawn->nID );
continue;
}
pMob->SetInstanceRespawnID( pRespawn->nID );
m_vRespawnedMonster.push_back( pMob );
}
}
// 프랍 리젠
bool bStatusBroadcastRequired = false;
for( std::vector< const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * >::const_iterator it = m_pArenaBase->vFieldPropRespawnList.begin() ; it != m_pArenaBase->vFieldPropRespawnList.end() ; ++it )
{
const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * pRespawn = (*it);
StructFieldProp * pProp = StructFieldProp::Create( this, pRespawn, pRespawn->x, pRespawn->y, m_nInstanceNo );
if( !pProp )
{
FILELOG( "Unknown field prop is to be respawned in battle arena. ArenaID(%d), PropID(%d)", m_pArenaBase->nID, pRespawn->nPropId );
_cprint( "Unknown field prop is to be respawned in battle arena. ArenaID(%d), PropID(%d)\n", m_pArenaBase->nID, pRespawn->nPropId );
continue;
}
m_vRespawnedFieldProp.push_back( pProp );
// 경기 내 프랍 상태 갱신
if( pRespawn->nPropIndexInArena )
{
bStatusBroadcastRequired = true;
_setPropState( pRespawn->nPropIndexInArena, PS_NEUTRAL );
}
}
// 프랍 상태가 변경되었다면 상태 방송
if( bStatusBroadcastRequired )
BattleArenaManager::BroadcastBattleArenaBattleStatusMessage( this );
}
void BattleArenaInstance::_procMonsterRespawn()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
AR_TIME tCurrent = GetArTime();
// 몹이 리젠되어야 할 방인데 끝난 이후면 리젠시키지 않음
if( m_bEnd )
{
// 끝난 방인데 왜 _procMonsterRespawn가 호출되었는지 확인해서 제거
assert( 0 );
m_vPendedMonsterRespawn.clear();
return;
}
for( std::vector< PendedMonsterRespawnInfo >::iterator it = m_vPendedMonsterRespawn.begin() ; it != m_vPendedMonsterRespawn.end() ; /* 루프에서 ++it 처리 */ )
{
const PendedMonsterRespawnInfo & respawnInfo = (*it);
const BATTLE_ARENA_MONSTER_RESPAWN * pRespawn = respawnInfo.first;
if( respawnInfo.second > tCurrent )
{
++it;
continue;
}
// 경기장 내부에서 몬스터 리젠은 이동 불가 영역에 관계 없이 어디든 리스폰이 되어야 함(프랍과 겹쳐서 화형대 지킴이 몬스터 리젠을 위해)
int nRespawnX = XRandom( static_cast< int >( pRespawn->bxArea.GetLeft() ), static_cast< int >( pRespawn->bxArea.GetRight() ) );
int nRespawnY = XRandom( static_cast< int >( pRespawn->bxArea.GetTop() ), static_cast< int >( pRespawn->bxArea.GetBottom() ) );
if( GameContent::IsBlocked( nRespawnX, nRespawnY ) == true )
{
FILELOG( "Unable to respawn monster in battle arena. ArenaID(%d), RespawnID(%d)", m_pArenaBase->nID, pRespawn->nID );
_cprint( "Unable to respawn monster in battle arena. ArenaID(%d), RespawnID(%d)\n", m_pArenaBase->nID, pRespawn->nID );
}
// 게임 진행 중에 죽은 각 몬스터에 대한 리젠 처리이기 때문에 pRespawn->nCount 로 저장되어 있는 몬스터 마리 수는 무시함
StructMonster * pMob = respawnMonster( nRespawnX, nRespawnY, m_nInstanceNo, pRespawn->nMonsterID, pRespawn->bWandering, 0, this, false, 0, pRespawn->fFace );
if( !pMob )
{
FILELOG( "Unknown monster is to be respawned in battle arena. ArenaID(%d), RespawnID(%d)", m_pArenaBase->nID, pRespawn->nID );
_cprint( "Unknown monster is to be respawned in battle arena. ArenaID(%d), RespawnID(%d)\n", m_pArenaBase->nID, pRespawn->nID );
it = m_vPendedMonsterRespawn.erase( it );
continue;
}
pMob->SetInstanceRespawnID( pRespawn->nID );
m_vRespawnedMonster.push_back( pMob );
it = m_vPendedMonsterRespawn.erase( it );
}
}
void BattleArenaInstance::_procFieldPropRespawn()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
AR_TIME tCurrent = GetArTime();
bool bStatusBroadcastRequired = false;
for( std::vector< PendedFieldPropRespawnInfo >::iterator itRespawn = m_vPendedFieldPropRespawn.begin() ; itRespawn != m_vPendedFieldPropRespawn.end() ; /* 루프에서 ++itRespawn 처리 */ )
{
if( (*itRespawn).second > tCurrent )
{
++itRespawn;
continue;
}
const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * pRespawn = (*itRespawn).first;
StructFieldProp * pProp = StructFieldProp::Create( this, pRespawn, pRespawn->x, pRespawn->y, m_nInstanceNo );
if( !pProp )
{
FILELOG( "Unknown field prop is to be respawned in battle arena. ArenaID(%d), PropID(%d)", m_pArenaBase->nID, pRespawn->nPropId );
_cprint( "Unknown field prop is to be respawned in battle arena. ArenaID(%d), PropID(%d)\n", m_pArenaBase->nID, pRespawn->nPropId );
itRespawn = m_vPendedFieldPropRespawn.erase( itRespawn );
continue;
}
m_vRespawnedFieldProp.push_back( pProp );
itRespawn = m_vPendedFieldPropRespawn.erase( itRespawn );
// 경기 내 프랍 상태 갱신
if( pRespawn->nPropIndexInArena )
{
bStatusBroadcastRequired = true;
_setPropState( pRespawn->nPropIndexInArena, PS_NEUTRAL );
}
}
// 프랍 상태가 변경되었다면 상태 방송
if( bStatusBroadcastRequired )
BattleArenaManager::BroadcastBattleArenaBattleStatusMessage( this );
}
void BattleArenaInstance::_clearMonster()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 리젠 대기 중인 몬스터 목록 제거
m_vPendedMonsterRespawn.clear();
// 몬스터 제거 처리(지역 락 필요)
for( std::vector< StructMonster * >::iterator itMonster = m_vRespawnedMonster.begin() ; itMonster != m_vRespawnedMonster.end() ; /* 루프에서 ++itMonster 처리 */ )
{
StructMonster * pMob = (*itMonster);
if( !pMob->IsEnable() )
{
++itMonster;
continue;
}
pMob->SetDeleteHandler( NULL );
// 더이상의 스케쥴러 요청을 무시
pMob->Disable();
// 월드에서 제거한다.
if( pMob->IsInWorld() )
{
RemoveMonsterFromWorld( pMob );
}
// object delete 요청
ArcadiaServer::Instance().DeleteObject( pMob );
itMonster = m_vRespawnedMonster.erase( itMonster );
}
m_vRespawnedMonster.clear();
}
void BattleArenaInstance::_clearFieldProps()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 리젠 대기 중인 힐링 프랍 제거
m_vPendedFieldPropRespawn.clear();
// 필드 프랍 제거 처리(지역 락 필요)
for( std::vector< StructFieldProp * >::iterator itFieldProp = m_vRespawnedFieldProp.begin() ; itFieldProp != m_vRespawnedFieldProp.end() ; ++itFieldProp )
{
StructFieldProp * pProp = (*itFieldProp);
// 해당 프랍이 ProcDelete에서 다시 리젠되도록 vPendedFieldPropRespawn에 추가되지 않도록 DeleteHandler를 제거하고 삭제함
pProp->SetDeleteHandler( NULL );
// 유저가 사용함으로 인해서 RemoveObject/PendFreeFieldProp은 걸려 있지만 아직 onFieldPropDelete가 호출되지 않은 경우에는 아무것도 하지 않음
if( !pProp->IsInWorld() )
continue;
ArcadiaServer::Instance().RemoveObject( pProp );
StructFieldProp::PendFreeFieldProp( pProp );
}
m_vRespawnedFieldProp.clear();
}
void BattleArenaInstance::_clearArenaBlockerFieldProps()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 필드 프랍 제거 처리(지역 락 필요)
for( std::vector< StructFieldProp * >::iterator itFieldProp = m_vRespawnedArenaBlockerFieldProp.begin() ; itFieldProp != m_vRespawnedArenaBlockerFieldProp.end() ; ++itFieldProp )
{
StructFieldProp * pProp = (*itFieldProp);
// 해당 프랍이 ProcDelete에서 다시 리젠되도록 vPendedFieldPropRespawn에 추가되지 않도록 DeleteHandler를 제거하고 삭제함
// * 아레나 입장 제한 프랍은 원래 DeleteHandler 세팅하지 않으니 이미 NULL이겠지만...
pProp->SetDeleteHandler( NULL );
// 유저가 사용함으로 인해서 RemoveObject/PendFreeFieldProp은 걸려 있지만 아직 onFieldPropDelete가 호출되지 않은 경우에는 아무것도 하지 않음
// * 아레나 입장 제한 프랍은 유저가 사용할 수 있는 프랍이 아니라서 이럴 일은 없지만...
if( !pProp->IsInWorld() )
continue;
ArcadiaServer::Instance().RemoveObject( pProp );
StructFieldProp::PendFreeFieldProp( pProp );
}
m_vRespawnedArenaBlockerFieldProp.clear();
}
void BattleArenaInstance::_clearItems()
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
struct DroppedItemCollector : public ArRegionFunctor, ArObjectFunctor
{
// ArObjectFunctor
virtual void operator()( ArObject *pObj ) const
{
if( !static_cast< GameObject * >( pObj )->IsItem() )
return;
vItemList.push_back( static_cast< StructItem * >( pObj ) );
}
// ArRegionFunctor
virtual void operator()( const struct ArRegion * pRegion )
{
// DoEachStaticOjbect 안에서 ArObjectFunctor가 RemoveStaticObject를 호출하면
// 해당 ArRegion의 m_vStaticObject 순회하던 게 박살나므로 포인터만 모음
pRegion->DoEachStaticObject( *this );
}
mutable std::vector< StructItem * > vItemList;
} droppedItemCollector;
ArcadiaServer::Instance().DoEachRegion( m_pArenaBase->nArenaRegionLeft, m_pArenaBase->nArenaRegionTop,
m_pArenaBase->nArenaRegionRight, m_pArenaBase->nArenaRegionBottom, m_nInstanceNo, droppedItemCollector );
for( std::vector< StructItem * >::iterator it = droppedItemCollector.vItemList.begin() ; it != droppedItemCollector.vItemList.end() ; ++it )
{
StructItem * pItem = (*it);
// 월드에서 제거(ItemCollector::onProcess에서 삭제 중인 아이템이었을 경우 실패함)
if( !RemoveItemFromWorld( pItem ) )
continue;
StructItem::PendFreeItem( pItem );
}
}
void BattleArenaInstance::_finishBattle( _ARENA_END_TYPE eEndType )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
if( m_bEnd )
{
assert( 0 );
return;
}
// 아직 시작되지 않은 경기(카운트다운 중)의 bStart 판정 때문에 여기서 bStart도 true로 그냥 덮어 줌
m_bStart = true;
m_bEnd = true;
// 실제 경기 종료 처리가 발생한 시점을 기준으로 nEndTime, nForceQuitTime을 갱신(nEndTime은 별 의미는 없고 nForceQuitTime이 의미가 실제로 있음)
m_nEndTime = GetArTime();
m_nForceQuitTime = m_nEndTime + GameRule::BATTLE_ARENA_FORCE_QUIT_DELAY;
// 입장을 하려고 대기중이던 유저가 있다면 취소시킴
// 경기 종료/이탈 시 해제해줘야 할 버프도 해제(여기서 해야 점수판 보다 디버프로 죽는 일을 방지할 수 있음. 단, 경기 내에 진입하지 않은 유저는 제외)
// * 지속 효과 말고 스킬 프랍에 의해 입을 수 있는 피해는 피아 판정에 의해 차단될 걸로 예상됨
{
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
PlayerInfo & pi = (*it);
if( pi.nPendedEnterTime )
pi.nPendedEnterTime = 0;
else if( pi.pPlayer )
pi.pPlayer->RemoveAllStateByQuittingBattleArena();
}
}
}
m_vPendedEnterPlayerList.clear();
}
// 리젠되어 있던 모든 객체 제거(월드에 드랍되어 있던 아이템은 점수판 보다가 주워 먹을 수도 있으니 여기서 삭제하지 않고 _wrapUpInstance에서 삭제함)
_clearMonster();
_clearFieldProps();
_clearArenaBlockerFieldProps();
// 경기 중에 오프라인 시간 제한으로 페널티를 받았던 유저 목록을 초기화
for( std::vector< PlayerUID >::const_iterator it = m_vOfflinePenaltyReceivedPlayerUIDList.begin() ; it != m_vOfflinePenaltyReceivedPlayerUIDList.end() ; ++it )
{
BattleArenaManager::Instance().RemoveOfflinePenaltyReceivedPlayer( (*it) );
}
m_vOfflinePenaltyReceivedPlayerUIDList.clear();
// 각 유저에게 걸려있던 버프 모두 제거(해로운 버프 받은 상태로 점수판 보다 죽으면 대략 난감하니까...)
struct StateRemover : public ArObjectFunctor
{
virtual void operator()( ArObject *pObj ) const
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( pObj );
// 아직 경기장 내에 진입하지 않았다면 아무것도 안 함
if( !pPlayer->IsInBattleArena() )
return;
// 지속효과 제거
pPlayer->RemoveAllStateByQuittingBattleArena();
if( pPlayer->GetMainSummon() )
pPlayer->GetMainSummon()->RemoveAllStateByQuittingBattleArena();
if( pPlayer->GetSubSummon() )
pPlayer->GetSubSummon()->RemoveAllStateByQuittingBattleArena();
}
} foStateRemover;
_doEachPlayer( foStateRemover );
// 두 팀만 존재하는 경기에서는 승/패/무승부 3가지만 있지만 여러 팀이 참여하는 걸 고려하면 팀간 순위가 필요해짐
// 여기 걸리게 되면 승패 보상 지급에 대한 기획을 추가(순위별 보상으로 변경되어야 함)해서 해당 내용대로 구현을 변경해야 함
if( m_pArenaBase->nTeamCount > 2 )
{
assert( 0 );
// 보상이고 뭐고 없어. 집에 가! 기획자를 욕해!!
BattleArenaManager::BroadcastBattleArenaFinalScoreMessage( this );
BattleArenaManager::BroadcastBattleArenaResultWithLog( this, eEndType, m_eRewardType, INVALID_BATTLE_ARENA_TEAM_NO, m_aTeamInfo[ 0 ].nScore, m_aTeamInfo[ 1 ].nScore, m_nEndTime - m_nStartTime );
// 방 폭파 타이밍을 당겨서 다음 _onProcess에서 바로 방 파괴되면서 모두 쫓아내도록 처리
m_nForceQuitTime = m_nEndTime;
return;
}
// 종료 스크립트 실행
BattleArenaRunEventScript( m_pArenaBase, BASET_END_INSTANCE, m_nInstanceNo, NULL );
// { 보상 지급이 필요 없는 경우나 보상의 배율 조정 필요한 경우 처리
c_fixed10 fRewardRate = 1;
// 한 팀의 멤버가 모두 나가 경기가 종료되는 경우에는 보상 없음
if( eEndType == AET_ONE_TEAM_NO_MEMBER )
{
m_eRewardType = ART_NO_REWARD_BY_NO_MEMBER;
BattleArenaManager::BroadcastBattleArenaResultWithLog( this, eEndType, m_eRewardType, INVALID_BATTLE_ARENA_TEAM_NO, m_aTeamInfo[ 0 ].nScore, m_aTeamInfo[ 1 ].nScore, m_nEndTime - m_nStartTime );
return;
}
// 최소 경기 시간이 아직 경과하지 않은 경우 보상 없음
if( m_nEndTime - m_nStartTime < m_pArenaBase->nMinPlayDurationForReward )
{
m_eRewardType = ART_NO_REWARD_BY_MIN_TIME;
BattleArenaManager::BroadcastBattleArenaResultWithLog( this, eEndType, m_eRewardType, INVALID_BATTLE_ARENA_TEAM_NO, m_aTeamInfo[ 0 ].nScore, m_aTeamInfo[ 1 ].nScore, m_nEndTime - m_nStartTime );
return;
}
// 한 팀의 인원 체크
m_eRewardType = ART_FULL_REWARD;
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
size_t nMemberCount = _getTeamMemberCount( nTeamNo );
// 한 팀의 인원이 0 이라면 보상 없음
if( !nMemberCount )
{
// eEndType != AET_ONE_TEAM_NO_MEMBER 인데 팀이 0 명인 경우가 있다...?
assert( 0 );
BattleArenaManager::BroadcastBattleArenaResultWithLog( this, eEndType, ART_NO_REWARD_BY_NO_MEMBER, INVALID_BATTLE_ARENA_TEAM_NO, m_aTeamInfo[ 0 ].nScore, m_aTeamInfo[ 1 ].nScore, m_nEndTime - m_nStartTime );
return;
}
// 한 팀의 인원이 최대 인원의 50% 미만이라면 보상 절반
if( nMemberCount < ( m_pArenaBase->nMaxMember / m_pArenaBase->nTeamCount / 2 ) )
{
m_eRewardType = ART_HALF_REWARD_BY_FEW_MEMBER;
fRewardRate = 0.5f;
}
}
// 연습 경기라면 보상 없음(근데 MVP 등은 보여주기 위해 보상 배율만 조정함)
if( m_pArenaBase->IsExerciseGameArena() )
{
m_eRewardType = ART_NO_REWARD_BY_EXERCISE;
fRewardRate = 0;
}
// } 보상 지급이 필요 없는 경우나 보상의 배율 조정 필요한 경우 처리
// 각 팀별 순위 처리(이 부분은 2개 이상의 팀의 순위 가르기에 대해서도 동일하게 동작함)
// 큰 숫자가 낮은 등수(0: 1등, 1: 2등, ...)
int anTeamRank[ GameRule::BATTLE_ARENA_MAX_TEAM_COUNT ] = { 0, };
bool abHasDrawTeam[ GameRule::BATTLE_ARENA_MAX_TEAM_COUNT ] = { false, };
for( int nTeamNo = 1 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
// 현재 팀 이전 팀과의 스코어 비교 및 순위 처리
// * 첫 번째 팀은 비교 대상 팀이 없으므로 nTeamNo를 1부터 시작함
for( int nAnotherTeamNo = 0 ; nAnotherTeamNo < nTeamNo ; ++nAnotherTeamNo )
{
// 현재 팀이 이전 팀보다 점수가 높다면 이전 팀의 순위 하나 낮춤(숫자는 커짐)
if( m_aTeamInfo[ nAnotherTeamNo ].nScore < m_aTeamInfo[ nTeamNo ].nScore )
{
++anTeamRank[ nAnotherTeamNo ];
}
// 같다면 이전 팀의 순위를 하나 낮추고 현재 팀의 순위도 하나 낮춤(동점 시 순위를 하향 평준화. 1위 보상을 여러 팀이 먹으면 안되니까)
else if( m_aTeamInfo[ nAnotherTeamNo ].nScore == m_aTeamInfo[ nTeamNo ].nScore )
{
++anTeamRank[ nAnotherTeamNo ];
++anTeamRank[ nTeamNo ];
abHasDrawTeam[ nAnotherTeamNo ] = true;
abHasDrawTeam[ nTeamNo ] = true;
}
// 이전 팀이 더 높다면 현재 팀의 등 수를 하나 늘림
else
{
++anTeamRank[ nTeamNo ];
}
}
}
// MVP 관련 처리 및 승패 보상 AP 지급은 연습 경기에서는 발생하지 않음
if( !m_pArenaBase->IsExerciseGameArena() )
{
// 팀별 점수 확정 및 MVP 선정을 위한 최고 점수 확인(승패 보상 AP 포함 점수로 산정)
int nHighestPrivateScore = 0;
int anTeamScore[ GameRule::BATTLE_ARENA_MAX_TEAM_COUNT ] = { 0, };
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
// * 2팀 이상이 경기에 참여하게 되면 이 부분도 수정해야 함
// 승리 팀이면 팀 점수를 승자 점수로 세팅
if( !anTeamRank[ nTeamNo ] )
anTeamScore[ nTeamNo ] = m_pArenaBase->nRewardAPWin[ m_eGrade ];
// 비긴 팀이면 팀 점수를 비긴 점수로 세팅
else if( abHasDrawTeam[ nTeamNo ] )
anTeamScore[ nTeamNo ] = m_pArenaBase->nRewardAPDraw[ m_eGrade ];
// 진 팀~
else
anTeamScore[ nTeamNo ] = m_pArenaBase->nRewardAPLose[ m_eGrade ];
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
PlayerInfo & pi = (*it);
// 오프라인인 유저는 실제로 승패 보상 AP는 획득 못하지만 MVP 선정 시에는 팀 승패 보상 AP도 포함한 점수로 뽑음
int nPenaltyCount = ( pi.pPlayer ) ? pi.pPlayer->GetBattleArenaPenaltyCount() : pi.nPenaltyCount;
// 여기서 pi.nTotalGainAPByResult에 팀 보상AP에 페널티를 반영한 값을 미리 저장하고, 실제 지급 처리 시 MVP, 보상률 적용함
pi.nTotalGainAPByResult = ( nPenaltyCount >= GameRule::BATTLE_ARENA_HALF_REWARD_PENALTY_COUNT ) ? anTeamScore[ nTeamNo ] / 2 : anTeamScore[ nTeamNo ];
int nPrivateScore = pi.nTotalGainAPByKill + pi.nTotalGainAPByProp + pi.nTotalGainAPByResult;
if( nPrivateScore > nHighestPrivateScore )
nHighestPrivateScore = nPrivateScore;
}
}
}
// MVP 확정 및 승패 보상 지급, 전적 처리
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
std::vector< PlayerInfo >* pPlayerInfoList = _getPlayerInfoList( nTeamNo );
if( pPlayerInfoList != NULL )
{
for( std::vector< PlayerInfo >::iterator it = pPlayerInfoList->begin() ; it != pPlayerInfoList->end() ; ++it )
{
PlayerInfo & pi = (*it);
int nPrivateScore = pi.nTotalGainAPByKill + pi.nTotalGainAPByProp + pi.nTotalGainAPByResult;
// MVP 여부 확정(온/오프라인 여부와 무관하게 MVP는 결정됨)
pi.bIsMVP = ( nHighestPrivateScore >= m_pArenaBase->nMinAPForMVP && nHighestPrivateScore == nPrivateScore );
// 온라인인 유저만 실질적인 승패 보상 지급 및 전적 처리
if( !pi.pPlayer )
continue;
c_fixed10 fMVPRate = ( pi.bIsMVP ) ? GameRule::BATTLE_ARENA_MVP_REWARD_RATE : c_fixed10( 1 );
// 승패 결과 및 MVP 등에 따른 최종 획득 AP 보관
pi.nTotalGainAPByResult = fRewardRate * fMVPRate * pi.nTotalGainAPByResult;
// 지급
pi.pPlayer->AddBattleArenaPoint( pi.nTotalGainAPByResult );
// { 전적 처리
// MVP
if( pi.bIsMVP )
pi.pPlayer->AddBattleArenaMVPCount();
// 승리
if( anTeamScore[ nTeamNo ] == m_pArenaBase->nRewardAPWin[ m_eGrade ] )
{
pi.pPlayer->AddBattleArenaRecord( m_pArenaBase->eType, true );
}
// 패배
else if( anTeamScore[ nTeamNo ] == m_pArenaBase->nRewardAPLose[ m_eGrade ] )
{
pi.pPlayer->AddBattleArenaRecord( m_pArenaBase->eType, false );
}
// } 전적 처리
}
}
}
}
// 최종적인 점수 방송 및 경기 결과 방송
for( int nTeamNo = 0 ; nTeamNo < m_pArenaBase->nTeamCount ; ++nTeamNo )
{
if( anTeamRank[ nTeamNo ] )
continue;
m_nWinTeamNo = nTeamNo;
break;
}
BattleArenaManager::BroadcastBattleArenaFinalScoreMessage( this );
BattleArenaManager::BroadcastBattleArenaResultWithLog( this, eEndType, m_eRewardType, m_nWinTeamNo, m_aTeamInfo[ 0 ].nScore, m_aTeamInfo[ 1 ].nScore, m_nEndTime - m_nStartTime );
// 줄 거 다 줬으니 이제 점수 판이나 보다가 쫓겨나든 알아서 나가든 유저가 알아서 하면 됨
}
void BattleArenaInstance::_wrapUpInstance( _ARENA_LEAVE_TYPE eLeaveType )
{
// 락 체크
assert( m_csBattle.IsLockedByCurrentThread() );
// 인스턴스 해제 스크립트 실행
ThreadPlayerHelper TPHelper( NULL );
BattleArenaRunEventScript( m_pArenaBase, BASET_DESTROY_INSTANCE, m_nInstanceNo, NULL );
// 유저들 집으로 보내줄 버스 준비
BattleArenaQuitFunctor qf( eLeaveType, m_pArenaBase, m_nInstanceNo );
// { 각 팀별 뒷정리
// 경기장 내부에 있는 유저용 이탈 스크립트 준비(추가로 접속 종료에 의한 것이 아니라는 파라미터를 세팅해 줌)
std::vector< std::string > vTagReplacement;
vTagReplacement.push_back( "#@disconnect@#" );
vTagReplacement.push_back( "0" );
_removeAllPlayer( eLeaveType, qf );
// } 각 팀별 뒷정리
// 경기 인스턴스별 플래그 데이터 초기화
m_mapInstanceFlag.clear();
// 입장을 하려고 대기중이던 유저가 있다면 취소시킴
m_vPendedEnterPlayerList.clear();
// 리젠되어 있는 몬스터, 프랍, 아이템 등의 처리(몬스터, 프랍은 원래 있으면 안 되는데 혹시 모르니까... 월드에 드랍되어 있던 아이템은 이제 안뇽~)
_clearMonster();
_clearFieldProps();
_clearArenaBlockerFieldProps();
_clearItems();
LOG::Log11N4S( LM_BATTLE_ARENA_DESTROY,
0, 0,
m_pArenaBase->nID, m_nInstanceNo, m_eGrade,
_getTeamMemberCount(0), _getTeamMemberCount(1), eLeaveType,
0, 0, 0,
"", LOG::STR_NTS, "", LOG::STR_NTS,
m_pArenaBase->IsExerciseGameArena() ? "EXERCISE" : "ARENA", LOG::STR_NTS,
"", LOG::STR_NTS );
}
void BattleArenaInstance::onMonsterDelete( StructMonster * pMonster )
{
// Do nothing if the match has already ended
if( m_bEnd ) return;
THREAD_SYNCHRONIZE( m_csBattle );
// 경기가 종료된 이후면 아무것도 하지 않음(락 걸고 다시 한 번 체크)
if( m_bEnd )
return;
std::vector< StructMonster * >::iterator it = std::find( m_vRespawnedMonster.begin(), m_vRespawnedMonster.end(), pMonster );
// 이미 삭제 처리 된 몬스터면 리젠 진행시키지 않음(경기 진행 중인데 사망 처리가 2번 들어왔거나 리젠만 되고 vRespawnedMonster에 추가되지 않았거나... -_ -?)
if( it == m_vRespawnedMonster.end() )
{
assert( 0 );
return;
}
m_vRespawnedMonster.erase( it );
// 시간제로 소환되었던 몬스터가 시간 제한에 의해 사라지는 경우에는 리젠 예약 처리를 하지 않음
if( pMonster->IsLifeTimeOver() )
return;
int nRespawnID = pMonster->GetInstanceRespawnID();
if( nRespawnID )
{
std::vector< const BATTLE_ARENA_MONSTER_RESPAWN * >::const_iterator itRespawn;
for( itRespawn = m_pArenaBase->vMonsterRespawnList.begin() ; itRespawn != m_pArenaBase->vMonsterRespawnList.end() ; ++itRespawn )
{
const BATTLE_ARENA_MONSTER_RESPAWN * pRespawn = (*itRespawn);
if( pRespawn->nID != nRespawnID )
continue;
// 리젠 간격이 0 인 경우에는 즉시 리젠이 아니라 한 번 죽으면 다시 리젠시키지 않음
if( !pRespawn->nPeriod )
break;
m_vPendedMonsterRespawn.push_back( PendedMonsterRespawnInfo( pRespawn, GetArTime() + pRespawn->nPeriod ) );
break;
}
assert( itRespawn != m_pArenaBase->vMonsterRespawnList.end() );
}
}
void BattleArenaInstance::onFieldPropDelete( StructFieldProp * pProp )
{
// Do nothing if the match has already ended
if( m_bEnd ) return;
THREAD_SYNCHRONIZE( m_csBattle );
// Do nothing if the match has already ended (lock and check again)
if( m_bEnd ) return;
// If a prop with a prop ID disappears, its state must first be set to PS_NOT_EXIST
const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * pRespawn = static_cast< const BATTLE_ARENA_FIELD_PROP_RESPAWN_INFO * >( pProp->GetRespawnInfo() );
if( pRespawn->nPropIndexInArena )
{
if( _getPropState( pRespawn->nPropIndexInArena ) != PS_NOT_EXIST )
{
assert( 0 );
_setPropState( pRespawn->nPropIndexInArena, PS_NOT_EXIST );
BattleArenaManager::BroadcastBattleArenaBattleStatusMessage( this );
}
}
std::vector< StructFieldProp * >::iterator it = std::find( m_vRespawnedFieldProp.begin(), m_vRespawnedFieldProp.end(), pProp );
// If the prop has already been marked as deleted, do not trigger respawn (e.g. deletion was processed twice during the match, or it respawned but was not added to vRespawnedFieldProp... -_ -?)
if( it == m_vRespawnedFieldProp.end() )
{
assert( 0 );
return;
}
m_vRespawnedFieldProp.erase( it );
// Props with a respawn interval of 0 will not respawn once they disappear
if( !pProp->GetFieldPropBase()->nRegenTime ) return;
m_vPendedFieldPropRespawn.push_back( PendedFieldPropRespawnInfo( pRespawn, GetArTime() + pProp->GetFieldPropBase()->nRegenTime ) );
}