474 lines
14 KiB
C++
474 lines
14 KiB
C++
|
|
#include <mmo/ArObject.h>
|
|
#include <mmo/ArTime.h>
|
|
#include <toolkit/XEnv.h>
|
|
#include <mmo/ArScheduler.h>
|
|
#include <mmo/ArcadiaServer.h>
|
|
#include <toolkit/XConsole.h>
|
|
#include <toolkit/XRandom.h>
|
|
|
|
#include "StructMonster.h"
|
|
#include "extern.h"
|
|
#include "GameMessage.h"
|
|
#include "NPCProc.h"
|
|
#include "GameContent.h"
|
|
#include "GameProc.h"
|
|
|
|
volatile LONG g_nDropRespawn = 0;
|
|
volatile LONG g_nRespawnObjectCnt = 0;
|
|
volatile LONG g_nRespawnTryCnt = 0;
|
|
volatile LONG g_nRespawnCnt = 0;
|
|
|
|
struct RespawnObjectBase : ArSchedulerObject, StructMonster::MonsterDeleteHandler
|
|
{
|
|
RespawnObjectBase( const char * name = "RespawnObjectBase::m_CS" )
|
|
: m_CS( name )
|
|
{
|
|
InterlockedIncrement( &g_nRespawnObjectCnt );
|
|
lastDeadTime = 0;
|
|
m_nMaxRespawnNum = 0;
|
|
}
|
|
|
|
virtual GameContent::AREA_BOX getRegenPosition() = 0;
|
|
virtual unsigned getMonsterID() const = 0;
|
|
virtual GameContent::MONSTER_RESPAWN_BASE_INFO & getMonsterRespawnInfo() = 0;
|
|
virtual bool isNeedToAdd() const = 0;
|
|
virtual const char* getProcessName() const = 0;
|
|
|
|
virtual void onMonsterDelete( struct StructMonster * pMonster );
|
|
virtual void onProcess( int nThreadIdx );
|
|
virtual bool ProcDelete()
|
|
{
|
|
delete this;
|
|
return true;
|
|
}
|
|
|
|
unsigned int m_nMaxRespawnNum; // 동적으로 조절되는 최대 몹 리젠 수
|
|
std::vector< AR_HANDLE > m_vRespawnedMonster; // 리젠되어 있는 몬스터 핸들(리젠되어 있는 놈을 감소처리 하기 위함)
|
|
|
|
AR_TIME lastDeadTime;
|
|
XCriticalSection m_CS;
|
|
};
|
|
|
|
struct RespawnObject : RespawnObjectBase
|
|
{
|
|
RespawnObject( const GameContent::MONSTER_RESPAWN_INFO & rh )
|
|
: info( rh )
|
|
, RespawnObjectBase( "RespawnObject::m_CS" )
|
|
{
|
|
m_nMaxRespawnNum = info.prespawn_count;
|
|
}
|
|
|
|
virtual GameContent::AREA_BOX getRegenPosition();
|
|
virtual unsigned getMonsterID() const;
|
|
virtual GameContent::MONSTER_RESPAWN_BASE_INFO & getMonsterRespawnInfo();
|
|
virtual bool isNeedToAdd() const;
|
|
virtual const char* getProcessName() const;
|
|
|
|
GameContent::MONSTER_RESPAWN_INFO info;
|
|
};
|
|
|
|
struct RandomRespawnObject : RespawnObjectBase
|
|
{
|
|
RandomRespawnObject( const GameContent::RANDOM_MONSTER_RESPAWN_INFO & rh )
|
|
: info( rh )
|
|
, RespawnObjectBase( "RandomRespawnObject::m_CS" )
|
|
{
|
|
m_nMaxRespawnNum = info.prespawn_count;
|
|
}
|
|
|
|
virtual GameContent::AREA_BOX getRegenPosition();
|
|
virtual unsigned getMonsterID() const;
|
|
virtual GameContent::MONSTER_RESPAWN_BASE_INFO & getMonsterRespawnInfo();
|
|
virtual bool isNeedToAdd() const;
|
|
virtual const char* getProcessName() const;
|
|
|
|
GameContent::RANDOM_MONSTER_RESPAWN_INFO info;
|
|
};
|
|
|
|
StructMonster * respawnMonster( unsigned x, unsigned y, unsigned char layer, unsigned id, bool is_wandering, int way_point_id, StructMonster::MonsterDeleteHandler * pDeleteHandler, bool bNeedLock, unsigned char difficulty, float face )
|
|
{
|
|
// 몬스터 생성
|
|
StructMonster *pMob = StructMonster::AllocMonster( id, difficulty );
|
|
|
|
if( !pMob )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// 생성코드 설정
|
|
pMob->SetGenerateCode( StructMonster::BY_RESPAWN );
|
|
|
|
// 좌표 설정
|
|
pMob->SetCurrentXY( x, y );
|
|
pMob->SetCurrentLayer( layer );
|
|
pMob->SetFace( face );
|
|
|
|
pMob->SetWandering( is_wandering );
|
|
|
|
// 리스폰 갯수 설정
|
|
pMob->SetDeleteHandler( pDeleteHandler );
|
|
|
|
#ifndef _USE_NEW_ROAMING_ONLY
|
|
if( way_point_id )
|
|
{
|
|
pMob->SetWayPointInfo( GameContent::GetWayPoint( way_point_id ) );
|
|
}
|
|
#endif
|
|
|
|
// lock 을 한 후 몬스터 추가
|
|
|
|
ArcadiaAutoLock lock;
|
|
|
|
if( bNeedLock )
|
|
lock.set( ArcadiaServer::Instance().LockObjectWithVisibleRange( pMob ), __FILE__, __LINE__ );
|
|
|
|
AddMonsterToWorld( pMob );
|
|
|
|
if( way_point_id )
|
|
{
|
|
ArcadiaServer::Instance().SetObjectPriority( pMob, ArSchedulerObject::UPDATE_PRIORITY_NORMAL );
|
|
}
|
|
|
|
InterlockedIncrement( &g_nRespawnCnt );
|
|
|
|
return pMob;
|
|
}
|
|
|
|
void RespawnObjectBase::onMonsterDelete( struct StructMonster * pMonster )
|
|
{
|
|
THREAD_SYNCRONIZE( &m_CS );
|
|
|
|
GameContent::MONSTER_RESPAWN_BASE_INFO & info = getMonsterRespawnInfo();
|
|
|
|
assert( info.count == m_vRespawnedMonster.size() );
|
|
assert( info.count > 0 );
|
|
|
|
lastDeadTime = GetArTime();
|
|
|
|
bool bNeedToStartRespawn = ( info.count == m_nMaxRespawnNum );
|
|
|
|
--info.count;
|
|
|
|
// 리젠되어 있는 몬스터 리스트에서 해당 몬스터를 삭제
|
|
std::vector< AR_HANDLE >::iterator itErase = std::find( m_vRespawnedMonster.begin(), m_vRespawnedMonster.end(), pMonster->GetHandle() );
|
|
if( itErase != m_vRespawnedMonster.end() )
|
|
{
|
|
m_vRespawnedMonster.erase( itErase );
|
|
}
|
|
|
|
// 시간제로 소환되었던 몬스터가 시간 제한에 의해 사라지는 경우에는 리젠 처리를 하지 않음
|
|
if( pMonster->IsLifeTimeOver() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 몹이 한 마리 죽을 때마다 동적으로 최대 리젠 몹 수를 증가시킴
|
|
if( isNeedToAdd() )
|
|
{
|
|
++m_nMaxRespawnNum;
|
|
}
|
|
|
|
if( bNeedToStartRespawn )
|
|
{
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_NORMAL );
|
|
}
|
|
}
|
|
|
|
void RespawnObjectBase::onProcess( int nThreadIdx )
|
|
{
|
|
char buf[255];
|
|
s_sprintf( buf, _countof( buf ), "thread.scheduler.%d.proc", nThreadIdx );
|
|
ENV().Set( buf, getProcessName() );
|
|
|
|
AR_TIME t = GetArTime();
|
|
|
|
extern __declspec( thread ) XSEH::THREAD_INFO s_ThreadInfo;
|
|
s_sprintf( s_ThreadInfo.job_info, _countof( s_ThreadInfo.job_info ), "%s(0x%08X)", getProcessName(), (UINT_PTR)this );
|
|
s_ThreadInfo.last_execute_time = t;
|
|
|
|
THREAD_SYNCRONIZE( &m_CS );
|
|
|
|
GameContent::MONSTER_RESPAWN_BASE_INFO & info = getMonsterRespawnInfo();
|
|
|
|
// 리젠 처리를 해야 하는 경우
|
|
if( info.count < m_nMaxRespawnNum )
|
|
{
|
|
InterlockedIncrement( &g_nRespawnTryCnt );
|
|
|
|
// 리스폰 인터벌이 안 지났으면 아무 것도 안 하고 끝남
|
|
AR_TIME check_time = lastDeadTime + info.interval;
|
|
if( lastDeadTime && check_time > t )
|
|
return;
|
|
|
|
int inc_cnt = std::min( m_nMaxRespawnNum - info.count, info.inc );
|
|
|
|
bool bFirstRegen = false;
|
|
// 첫 실행일경우
|
|
if( !lastDeadTime )
|
|
{
|
|
lastDeadTime = t;
|
|
inc_cnt = m_nMaxRespawnNum;
|
|
bFirstRegen = true;
|
|
}
|
|
|
|
if( inc_cnt > 0 )
|
|
{
|
|
unsigned try_cnt = 0;
|
|
|
|
// { TODO : inc_cnt 만큼 몬스터 생성.
|
|
for( int i = 0 ; i < inc_cnt ; ++i )
|
|
{
|
|
GameContent::AREA_BOX regenBox = getRegenPosition();
|
|
int x, y;
|
|
int monster_id = getMonsterID();
|
|
|
|
do
|
|
{
|
|
x = XRandom( regenBox.left, regenBox.right );
|
|
y = XRandom( regenBox.top, regenBox.bottom );
|
|
|
|
if( bFirstRegen && i == 0 && ++try_cnt > 500 )
|
|
{
|
|
InterlockedIncrement( &g_nDropRespawn );
|
|
// 포기!
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_IDLE );
|
|
ArcadiaServer::Instance().DeleteObject( this );
|
|
FILELOG( "Unable to respawn monster group. RespawnID(%d), MonsterID(%d), box(%d,%d-%d,%d)", info.id, monster_id, (int)regenBox.left, (int)regenBox.top, (int)regenBox.right, (int)regenBox.bottom );
|
|
_cprint( "Unable to respawn monster group. RespawnID(%d), MonsterID(%d), box(%d,%d-%d,%d)\n", info.id, monster_id, (int)regenBox.left, (int)regenBox.top, (int)regenBox.right, (int)regenBox.bottom );
|
|
|
|
return;
|
|
}
|
|
} while( GameContent::IsBlocked( x, y ) );
|
|
|
|
StructMonster * pMob = respawnMonster( x, y, info.layer, monster_id, info.is_wandering, info.way_point_id, this );
|
|
|
|
// Arcadia DB에 없는 몬스터 ID가 지정된 경우에는 pMob == NULL이 됨
|
|
if( pMob )
|
|
{
|
|
if( info.dungeon_id )
|
|
{
|
|
pMob->SetDungeonId( info.dungeon_id );
|
|
}
|
|
|
|
m_vRespawnedMonster.push_back( pMob->GetHandle() );
|
|
}
|
|
|
|
++info.count;
|
|
}
|
|
// }
|
|
}
|
|
|
|
// 최대 리젠 수 만큼 리젠되었으면 priority를 낮춰서 몬스터 수 감소 처리를 하거나 놀도록 변경
|
|
if( info.count == m_nMaxRespawnNum )
|
|
{
|
|
// 리젠되어 있는 몬스터 감소 처리가 불필요한 경우에는 IDLE 상태로 바꿈
|
|
// 1 마리만 리젠, 던전 몬스터, 로밍 몬스터의 경우, 감소되어야 하는 수치만큼만 리젠된 경우
|
|
if( m_nMaxRespawnNum == 1 || m_nMaxRespawnNum == info.prespawn_count || info.dungeon_id || info.way_point_id )
|
|
{
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_IDLE );
|
|
}
|
|
else
|
|
{
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_LOW );
|
|
}
|
|
}
|
|
}
|
|
// 리젠되어 있는 몬스터 제거 처리를 해야하는 경우(던전 몬스터, 싱글 로밍 몬스터 리젠은 제외)
|
|
else if( !info.dungeon_id && !info.way_point_id )
|
|
{
|
|
// 줄일 필요가 없다면 IDLE 상태로 변경만 함
|
|
if( info.prespawn_count == m_nMaxRespawnNum )
|
|
{
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_IDLE );
|
|
return;
|
|
}
|
|
|
|
// 현재 리젠되어 있는 몬스터 수가 최소 리젠 수보다 적다면 버그 있음
|
|
if( m_nMaxRespawnNum < info.prespawn_count )
|
|
{
|
|
assert( 0 );
|
|
|
|
// 다음 onProcess 들어올 때 최소 허용 수와 차이 만큼을 리젠되도록 조정함
|
|
m_nMaxRespawnNum = info.prespawn_count;
|
|
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_NORMAL );
|
|
|
|
return;
|
|
}
|
|
|
|
// 마지막으로 몬스터가 리젠된 후로 지정 시간이 지났으면 몬스터의 수를 최소 리젠 수 만큼으로 줄임
|
|
if( !lastDeadTime || lastDeadTime + info.interval + GameRule::MONSTER_RESPAWN_DECREASE_INTERVAL > t )
|
|
return;
|
|
|
|
// XMemoryPool 구조상 나중에 리젠된 놈이 삭제되는 게 메모리 풀 내에서 사용되는 블럭의 밀집도를 유지시키는데 유리함
|
|
unsigned int nCountToDelete = m_nMaxRespawnNum - info.prespawn_count;
|
|
while( nCountToDelete )
|
|
{
|
|
StructCreature::iterator pit = StructCreature::get( m_vRespawnedMonster.back() );
|
|
StructCreature * pCreature = (*pit);
|
|
|
|
StructMonster * pMonster = ( pCreature && pCreature->IsMonster() ) ? static_cast< StructMonster * >( pCreature ) : NULL;
|
|
|
|
// 없거나 잘못된 핸들 값일 경우, 사망 처리 대기 중인 경우, 다른 놈한테 리젠된 놈인 면 버그에 의한 것임
|
|
if( !pMonster || pMonster->GetDeleteHandler() != this )
|
|
{
|
|
assert( 0 );
|
|
|
|
--m_nMaxRespawnNum;
|
|
--info.count;
|
|
|
|
--nCountToDelete;
|
|
|
|
m_vRespawnedMonster.pop_back();
|
|
continue;
|
|
}
|
|
|
|
// 몬스터 주변에 클라이언트가 있으면 중지(다음 프로세스에서 다시 체크)
|
|
if( pMonster->bIsNearClient )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// 몬스터 리젠해야 할 수와 현재 리젠되어 있는 수 감소 처리(이 이후에 잘못되는 경우는 발생하면 안 됨)
|
|
--m_nMaxRespawnNum;
|
|
--info.count;
|
|
|
|
// 줄여야 할 몬스터 수 감소 처리(루프 탈출 조건)
|
|
--nCountToDelete;
|
|
|
|
m_vRespawnedMonster.pop_back();
|
|
|
|
{
|
|
ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pMonster ) );
|
|
|
|
// 몬스터 사망 처리 중이거나 처리된 놈은 아무것도 하지 않음
|
|
if( pMonster->IsEnable() )
|
|
{
|
|
pMonster->SetDeleteHandler( NULL );
|
|
|
|
// 더이상의 스케쥴러 요청을 무시
|
|
pMonster->Disable();
|
|
|
|
// 월드에서 제거한다.
|
|
if( pMonster->IsInWorld() )
|
|
{
|
|
RemoveMonsterFromWorld( pMonster );
|
|
}
|
|
|
|
// object delete 요청
|
|
ArcadiaServer::Instance().DeleteObject( pMonster );
|
|
}
|
|
else
|
|
{
|
|
// 사망 처리 중인 놈은 지역 락이 걸려 있었어야 하므로 여기 들어올 수 없어야 정상
|
|
assert( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 몬스터 줄일 만큼 줄였으면 프로세스 돌지 않도록 IDLE 상태로 변경(전투 중인 몬스터가 있고 대신 없앨 놈이 없었다면 다음 프로세스 때 다시 체크해야 함)
|
|
if( !nCountToDelete )
|
|
{
|
|
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_IDLE );
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned RespawnObject::getMonsterID() const
|
|
{
|
|
return info.monster_id;
|
|
}
|
|
|
|
GameContent::MONSTER_RESPAWN_BASE_INFO & RespawnObject::getMonsterRespawnInfo()
|
|
{
|
|
return static_cast< GameContent::MONSTER_RESPAWN_BASE_INFO & >(info);
|
|
}
|
|
|
|
bool RespawnObject::isNeedToAdd() const
|
|
{
|
|
return m_nMaxRespawnNum < info.max_num;
|
|
}
|
|
|
|
const char *RespawnObject::getProcessName() const
|
|
{
|
|
return "RespawnObject";
|
|
}
|
|
|
|
GameContent::AREA_BOX RespawnObject::getRegenPosition()
|
|
{
|
|
return GameContent::AREA_BOX( info.left, info.top, info.right, info.bottom );
|
|
}
|
|
|
|
static std::vector< RespawnObjectBase* > & GetRespawnList()
|
|
{
|
|
static std::vector< RespawnObjectBase* > vRegenList;
|
|
return vRegenList;
|
|
}
|
|
|
|
void AddRespawnObject( const GameContent::MONSTER_RESPAWN_INFO & info )
|
|
{
|
|
if( !info.monster_id ) return;
|
|
|
|
// 새 영역정보 생성
|
|
RespawnObject * pRespawn = new RespawnObject( info );
|
|
GetRespawnList().push_back( pRespawn );
|
|
|
|
// 스케쥴러에 등록
|
|
ArcadiaServer::Instance().SetObjectPriority( pRespawn, ArSchedulerObject::UPDATE_PRIORITY_NORMAL );
|
|
|
|
return;
|
|
}
|
|
|
|
void AddRandomRespawnObject( const GameContent::RANDOM_MONSTER_RESPAWN_INFO & info )
|
|
{
|
|
if( !info.random_area_id || info.monster_list.empty() ) return;
|
|
|
|
// 새 영역정보 생성
|
|
RandomRespawnObject * pRespawn = new RandomRespawnObject( info );
|
|
GetRespawnList().push_back( pRespawn );
|
|
|
|
// 스케쥴러에 등록
|
|
ArcadiaServer::Instance().SetObjectPriority( pRespawn, ArSchedulerObject::UPDATE_PRIORITY_NORMAL );
|
|
|
|
return;
|
|
}
|
|
|
|
unsigned RandomRespawnObject::getMonsterID() const
|
|
{
|
|
int nCum = 0;
|
|
int nKey = XRandom( 1, 100000000 );
|
|
|
|
for( std::vector< std::pair< int, int > >::const_iterator it = info.monster_list.begin(); it != info.monster_list.end(); ++it )
|
|
{
|
|
if( !it->first ) break;
|
|
|
|
nCum += it->second;
|
|
|
|
if( nKey > nCum ) continue;
|
|
|
|
return it->first;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
GameContent::MONSTER_RESPAWN_BASE_INFO & RandomRespawnObject::getMonsterRespawnInfo()
|
|
{
|
|
return static_cast< GameContent::MONSTER_RESPAWN_BASE_INFO & >(info);
|
|
}
|
|
|
|
bool RandomRespawnObject::isNeedToAdd() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char *RandomRespawnObject::getProcessName() const
|
|
{
|
|
return "RandomRespawnObject";
|
|
}
|
|
|
|
GameContent::AREA_BOX RandomRespawnObject::getRegenPosition()
|
|
{
|
|
return GameContent::GetRandomRespawnBox( info.random_area_id );
|
|
} |