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

217 lines
9.5 KiB
C++

#include <toolkit/XStringUtil.h>
#include "LogClient/LogClient.h"
#include "BattleArenaCommon.h"
#include "StructSummon.h"
#include "StructPlayer.h"
#include "ChannelManager.h"
#include "LuaVM.h"
#include "ThreadPlayerHelper.h"
const char * FLAG_BATTLE_ARENA_EXERCISE_GAME_READY_STATE = "ExerciseReadyState";
const char * FLAG_BATTLE_ARENA_PROP_STATE = "PropState";
BattleArenaQuitFunctor::BattleArenaQuitFunctor( _ARENA_LEAVE_TYPE eLeaveType,
const BattleArenaBaseServer* pArenaBase,
unsigned char nInstanceNo )
: m_eLeaveType( eLeaveType )
, m_pArenaBase( pArenaBase )
, m_nInstanceNo( nInstanceNo )
{
}
void BattleArenaQuitFunctor::operator()( ArObject* pObj ) const
{
StructPlayer* pPlayer = static_cast< StructPlayer* >( pObj );
// _removePlayer나 _wrapUpInstance에 의해 경기에서 이탈된 상태의 유저만 QuitFuctor에 의해 외부로 워프되어야 함
assert( !pPlayer->GetBattleArenaID() && !pPlayer->GetBattleArenaInstanceNo() );
// 아레나 내부에 있지 않다면 아무것도 안 함
// * 외부에 있는데 내부로 PendWarp 된 상태이거나 BattleArenaManager::onProcess에서 vPendedEnterPlayerList에 포함되어 있지만 락은 풀린 시점이어서
// 곧 PendWarp 될 상태일 수도 있는데, vPendedEnterPlayerList 포함된 상태라면 이미 경기에서 이탈된 상태기 때문에
// 다시 락을 걸고 PendWarp 하기 직전에 검사하는 과정에서 탈락될 걸로 예상.
// 그리고 PendWarp까지 됐지만 아직 ProcessWarp가 호출되지 않은 경우에는 ProcessWarp 안에서 경기에 참여 상태인지 워프 처리 직전에 검사.
if( !pPlayer->IsInBattleArena() )
return;
// 일단 사망 상태면 부활시켜 줌(안 해주면 밖에서 부활하며 온갖 페널티를 먹거나 대모요정의 병을 날림)
// 아레나 내부에서 부활 시에는 모든 지속효과를 복구하지만 이 시점에서는 복구를 해주더라도 퇴장 처리에서 결국 모두 삭제되기 때문에 복구해주지 않는다.
pPlayer->Resurrect( CRT_BATTLE_ARENA, pPlayer->GetMaxHP(), pPlayer->GetMaxMP(), 0, true); //false
// 이탈 스크립트 실행(추가로 접속 종료에 의한 것이 아니라는 파라미터를 세팅해 줌)
std::vector< std::string > vTagReplacement;
vTagReplacement.push_back( "#@disconnect@#" );
vTagReplacement.push_back( "0" );
ThreadPlayerHelper TPHelper( pPlayer );
BattleArenaRunEventScript( m_pArenaBase, BASET_LEAVE_INSTANCE, m_nInstanceNo, pPlayer, &vTagReplacement );
// 지속효과 제거
pPlayer->RemoveAllStateByQuittingBattleArena();
if( pPlayer->GetMainSummon() )
pPlayer->GetMainSummon()->RemoveAllStateByQuittingBattleArena();
if( pPlayer->GetSubSummon() )
pPlayer->GetSubSummon()->RemoveAllStateByQuittingBattleArena();
ArPosition posExit;
pPlayer->GetPositionOnEnterInstanceGame( &posExit );
pPlayer->RestoreStatesOnLeaveInstanceGame( false );
unsigned char nLayer = 0;
// 수련자의 섬 같이 인원 제한 채널에 레이어 설정된 구역으로 워프해야 하는 경우 적당한 레이어 얻기
int nTargetChannel = ChannelManager::GetChannelId( posExit.GetX(), posExit.GetY() );
if( nTargetChannel && ChannelManager::GetChannelType( nTargetChannel ) == ChannelManager::TYPE_USER_LIMIT )
{
nLayer = ChannelManager::GetProperLayer( posExit.GetX(), posExit.GetY() );
}
// PendWarp는 지역락 걸고 해야되는데, 여긴 이미 csBattle이 걸려있어서 지역락을 유저마다 따로 걸어줄 수는 없음
// 근데 애초에 여기 들어올 때 LockWholeInstance나 LockWholeArena를 하고 들어올 거고,
// 모든 유저는 경기장 안에 있을 거라서 따로 지역락은 걸지 않아도 문제는 되지 않을 걸로 예상됨
pPlayer->PendWarp( posExit.GetX(), posExit.GetY(), nLayer );
pPlayer->SetMove( pPlayer->GetCurrentPosition( GetArTime() ), 0 );
ArcadiaServer::Instance().SetObjectPriority( pPlayer, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
}
bool IsValidBattleArenaInstanceNo( unsigned char nInstanceNo )
{
return nInstanceNo > 0 && nInstanceNo <= MAX_BATTLE_ARENA_INSTANCE_NO_PER_ARENA;
}
// 레벨에 따른 배틀 아레나 구분 등급 번호 얻기(유효하지 않은 레벨에 대해서는 -1을 반환, 그 외에는 0 ~ 2 사이의 값을 반환)
_BATTLE_GRADE GetBattleArenaGrade( int nLevel )
{
// 최소 레벨 미만
if( nLevel < GameRule::GetItemLevelLimitByRank( 3 ) )
return BG_INVALID;
// 최저 등급
if( nLevel < GameRule::GetItemLevelLimitByRank( 5 ) )
return BG_ROOKIE;
// 중간 등급
if( nLevel < GameRule::GetItemLevelLimitByRank( 7 ) )
return BG_GROW;
// 최고 등급
return BG_MAJOR;
}
bool IsValidBattleArenaGrade( _BATTLE_GRADE eGrade )
{
if( eGrade == BG_ROOKIE || eGrade == BG_GROW || eGrade == BG_MAJOR )
{
return true;
}
return false;
}
// 직접 호출해서 사용하지 말 것. 이 함수 대신 BattleArena::LockWholeArena, BattleArenaInstance::LockWholeInstance 함수를 사용할 것.
ArcadiaLock _LockWholeBattleArena( const BattleArenaBaseServer* pArenaBase, unsigned short nLayer )
{
return ArcadiaServer::Instance().LockArea( pArenaBase->nArenaRegionLeft, pArenaBase->nArenaRegionTop,
pArenaBase->nArenaRegionRight, pArenaBase->nArenaRegionBottom, nLayer );
}
// 아레나 이벤트별 스크립트에 대한 태그 처리 결과 반환
std::string GetBattleArenaEventScript( const BattleArenaBaseServer* pArenaBase,
_BATTLE_ARENA_SCRIPT_EXEC_TYPE eExecType,
unsigned char nInstanceNo,
const std::vector< std::string >* pvTagReplacement )
{
const char* pszScript = NULL;
switch( eExecType )
{
case BASET_CREATE_INSTANCE: pszScript = pArenaBase->strScriptOnCreate.c_str(); break;
case BASET_ENTER_INSTANCE: pszScript = pArenaBase->strScriptOnEnter.c_str(); break;
case BASET_START_INSTANCE: pszScript = pArenaBase->strScriptOnStart.c_str(); break;
case BASET_LEAVE_INSTANCE: pszScript = pArenaBase->strScriptOnLeave.c_str(); break;
case BASET_END_INSTANCE: pszScript = pArenaBase->strScriptOnEnd.c_str(); break;
case BASET_DESTROY_INSTANCE: pszScript = pArenaBase->strScriptOnDestroy.c_str(); break;
default:
assert( 0 );
return "";
}
// 스크립트가 없거나(이런 경우는 없지만) 길이가 0이면(첫 번째 문자가 NULL) 그냥 리턴
if( !pszScript || !( *pszScript ) )
return "";
std::string strScriptBuffer( pszScript );
// 레이어 번호 태그 처리
XStringUtil::ReplaceFormat( strScriptBuffer, "#@instance_no@#", "%d", (int)nInstanceNo );
// 그 외에 추가 태그 처리
if( pvTagReplacement )
{
size_t nTagCount = pvTagReplacement->size() / 2;
for( int nTagIndex = 0 ; nTagIndex < nTagCount ; ++nTagIndex )
{
const std::string& strTag = (*pvTagReplacement)[ nTagIndex * 2 ];
const std::string& strReplacement = (*pvTagReplacement)[ nTagIndex * 2 + 1 ];
XStringUtil::Replace( strScriptBuffer, strTag.c_str(), strReplacement.c_str() );
}
}
return strScriptBuffer;
}
void BattleArenaRunScriptWithLog( const BattleArenaBaseServer* pArenaBase,
_BATTLE_ARENA_SCRIPT_EXEC_TYPE eExecType,
unsigned char nInstanceNo,
const std::string& strScript,
const StructPlayer* pExecutor )
{
// 경기장 영역 내 혹은 스크립트 호출자 주변으로 지역락이 반드시 걸려 있어야 함
// * 유저간 상호작용은 csBattle 때문에 별 문제 없지만 스크립트가 뭘 할지는 모르는 거니까.
assert( ( pExecutor && ArcadiaServer::Instance().IsLocked( pExecutor ) ) ||
( !pExecutor && ArcadiaServer::Instance().IsLocked(
pArenaBase->nArenaRegionLeft,
pArenaBase->nArenaRegionTop,
pArenaBase->nArenaRegionRight,
pArenaBase->nArenaRegionBottom,
nInstanceNo ) ) );
LUA()->RunString( strScript.c_str() );
LOG::Log11N4S( LM_BATTLE_ARENA_PROCESS,
( pExecutor ) ? pExecutor->GetAccountID() : 0, ( pExecutor ) ? pExecutor->GetPlayerUID() : 0,
pArenaBase->nID, nInstanceNo,
0, 0, 0, 0, 0, 0,
eExecType,
( pExecutor ) ? pExecutor->GetAccountName() : "", LOG::STR_NTS,
( pExecutor ) ? pExecutor->GetAlias() : "", LOG::STR_NTS,
strScript.c_str(), LOG::STR_NTS,
( pExecutor ) ? pExecutor->GetAlias() : "", LOG::STR_NTS );
}
// 각 상황에 맞는 지역락 -> csArena -> csBattle을 걸고 호출해야 함
// * 스크립트 안에서 호출하는 함수가 BattleArenaManager의 멤버 함수를 호출하는 경우 csArena/csBattle을 중첩으로 걸 수도 있으나,
// CRITICAL_SECTION의 특성에 따라 중첩해서 거는 것이 허용됨.
// 단, csArena/csBattle을 걸고 BattleArenaRunEventScript를 호출하는 경우에는 반드시 현재 걸려있는 csArena/csBattle만 걸어야 함.
// csArena/csBattle은 실제로 1개가 아니고 각 아레나별/인스턴스별로 여러 개가 생성되어 있기 때문에
// BattleArenaRunEventScript 함수를 호출하는 시점에 걸려있던 csArena/csBattle과 이름은 같지만 다른 다른 인스턴스를
// 스크립트 안에서 걸게 되면 데드락을 유발하게 됨(이름은 같지만 서로 다른 락을 규칙성 없이 교차해서 걸게 되므로)
void BattleArenaRunEventScript( const BattleArenaBaseServer* pArenaBase,
_BATTLE_ARENA_SCRIPT_EXEC_TYPE eExecType,
unsigned char nInstanceNo,
const StructPlayer * pExecutor,
const std::vector< std::string >* pvTagReplacement )
{
std::string& strEventScript = GetBattleArenaEventScript( pArenaBase, eExecType, nInstanceNo, pvTagReplacement );
BattleArenaRunScriptWithLog( pArenaBase, eExecType, nInstanceNo, strEventScript, pExecutor );
}