2956 lines
110 KiB
C++
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 ) );
|
|
}
|