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

3181 lines
93 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <string>
#include <openssl/md5.h>
#include <dump/XException.h>
#include <toolkit/khash.h>
#include <mmo/ArcadiaServer.h>
#include <toolkit/XConsole.h>
#include <toolkit/XStringUtil.h>
#include <toolkit/XEnv.h>
#include <logging/FileLog.h>
#include <geometry/X2DQuadTree.h>
#include <geometry/X2DQuadTree_divide_polygon.h>
#include <geometry/X2DPathFinder.h>
#include <toolkit/XDirectoryScanner.h>
#include "LogClient/LogClient.h"
#include "ErrorCode/ErrorCode.h"
#include "GameContent.h"
#include "BitmapBox.h"
#include "StructItem.h"
#include "LuaVM.h"
#include "DungeonManager.h"
#include "StructNPC.h"
#include "StructPlayer.h"
#include "StructSummon.h"
#include "StructSkill.h"
#include "GameProc.h"
#include "Extern.h"
#include "ChannelManager.h"
#include "RoamingManager.h"
#include "NPCProc.h"
#include "TimeUtil.h"
struct _EXP_TABLE
{
__int64 exp;
__int64 jp[4];
};
struct _SUMMON_EXP_TABLE
{
__int64 normal_exp;
__int64 growth_exp;
__int64 evolve_exp;
};
struct RANDOM_POOL
{
RANDOM_POOL( int _group_id ) : group_id( _group_id ) {}
int group_id;
std::vector< GameContent::RANDOM_POOL_INFO > vInfo;
};
typedef struct _QUEST_TEXT_ID {
_QUEST_TEXT_ID( const int _nStartTextId, const int _nInProgressTextId, const int _nEndTextId )
: nStartTextId( _nStartTextId )
, nInProgressTextId( _nInProgressTextId )
, nEndTextId( _nEndTextId )
{}
int nStartTextId;
int nInProgressTextId;
int nEndTextId;
} QUEST_TEXT_ID;
const bool GameContent::EVENT_AREA_INFO::IsActivatable( const struct StructPlayer * pPlayer, const int nAreaIndex ) const
{
int nCurrentTimeInToday = static_cast< int >( time( NULL ) - GetDayBeginTime() );
if( ( m_nBeginTime && m_nBeginTime > nCurrentTimeInToday ) ||
( m_nEndTime && m_nEndTime < nCurrentTimeInToday ) )
{
return false;
}
if( ( m_nMinLevel && m_nMinLevel > pPlayer->GetLevel() ) ||
( m_nMaxLevel && m_nMaxLevel < pPlayer->GetLevel() ) )
{
return false;
}
// 종족 제한이 있는 경우
if( m_nRaceJobLimit & RACE_JOB_FLAG_ALL_RACES )
{
switch( pPlayer->GetRace() )
{
case JobInfo::GAIA: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_RACE_GAIA ) ) return false; break;
case JobInfo::DEVA: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_RACE_DEVA ) ) return false; break;
case JobInfo::ASURA: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_RACE_ASURA ) ) return false; break;
}
}
// 직업 제한이 있는 경우(종족 플래그를 제외한 모든 비트에 1개라도 값이 있으면 직업 제한으로 간주)
if( m_nRaceJobLimit & ~RACE_JOB_FLAG_ALL_RACES )
{
switch( pPlayer->GetJobId() )
{
// 가이아 직업들
case 100: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_ROGUE ) ) return false; break;
case 101: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_FIGHTER ) ) return false; break;
case 102: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_KAHUNA ) ) return false; break;
case 103: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SPELL_SINGER ) ) return false; break;
case 110: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_CHAMPION ) ) return false; break;
case 111: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_ARCHER ) ) return false; break;
case 112: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_DRUID ) ) return false; break;
case 113: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_BATTLE_KAHUNA ) ) return false; break;
case 114: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_EVOKER ) ) return false; break;
case 120: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_BERSERKER ) ) return false; break;
case 121: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_MASTER_ARCHER ) ) return false; break;
case 122: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_HIGH_DRUID ) ) return false; break;
case 123: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_GREAT_KAHUNA ) ) return false; break;
case 124: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_BEAST_MASTER ) ) return false; break;
// 데바 직업들
case 200: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_GUIDE ) ) return false; break;
case 201: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_HOLY_WARRIOR ) ) return false; break;
case 202: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_CLERIC ) ) return false; break;
case 203: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_TAMER ) ) return false; break;
case 210: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_KNIGHT ) ) return false; break;
case 211: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SOLDIER ) ) return false; break;
case 212: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_MAGE ) ) return false; break;
case 213: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_PRIEST ) ) return false; break;
case 214: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_BREEDER ) ) return false; break;
case 220: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_CRUSADER ) ) return false; break;
case 221: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_BLADER ) ) return false; break;
case 222: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_ARCH_MAGE ) ) return false; break;
case 223: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_HIGH_PRIEST ) ) return false; break;
case 224: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SOUL_BREEDER ) ) return false; break;
// 아수라 직업들
case 300: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_STEPPER ) ) return false; break;
case 301: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_STRIDER ) ) return false; break;
case 302: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_MAGICIAN ) ) return false; break;
case 303: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SUMMONER ) ) return false; break;
case 310: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_ASSASSIN ) ) return false; break;
case 311: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_RANGER ) ) return false; break;
case 312: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SORCERER ) ) return false; break;
case 313: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_DARK_MAGICIAN ) ) return false; break;
case 314: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_BATTLE_SUMMONER ) ) return false; break;
case 320: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SHADOW_CHASER ) ) return false; break;
case 321: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_SHADOW_HUNTER ) ) return false; break;
case 322: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_LICH ) ) return false; break;
case 323: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_WARLOCK ) ) return false; break;
case 324: if( !( m_nRaceJobLimit & RACE_JOB_FLAG_JOB_NECROMANCER ) ) return false; break;
}
}
// 발동 조건 관련 체크
for( int i = 0 ; i < MAX_ACTIVATE_CONDITION ; ++i )
{
switch( m_nActivateCondition[ i ] )
{
case LIMIT_CONDITION_ITEM_COUNT_GE:
{
StructItem * pItem = pPlayer->FindItem( static_cast< ItemBase::ItemCode >( m_nActivateValue[ i ][ 0 ] ) );
if( !pItem || pItem->GetCount() < m_nActivateValue[ i ][ 1 ] )
return false;
}
break;
case LIMIT_CONDITION_QUEST_STATUS:
if( pPlayer->GetQuestProgress( m_nActivateValue[ i ][ 0 ] ) != m_nActivateValue[ i ][ 1 ] )
return false;
break;
case LIMIT_CONDITION_SKILL_LEVEL_GE:
{
StructSkill * pSkill = pPlayer->GetSkill( m_nActivateValue[ i ][ 0 ] );
if( !pSkill || pSkill->GetBaseSkillLevel() < m_nActivateValue[ i ][ 1 ] )
return false;
}
break;
case LIMIT_CONDITION_ITEM_WEARING:
{
bool bWearing = false;
for( int nWearIdx = 0 ; nWearIdx < ItemBase::MAX_ITEM_WEAR ; ++nWearIdx )
{
StructItem * pItem = pPlayer->GetWearedItem( static_cast< ItemBase::ItemWearType >( nWearIdx ) );
if( pItem && pItem->GetItemCode() == m_nActivateValue[ i ][ 0 ] )
{
bWearing = true;
break;
}
}
// m_nActivateValue[ i ][ 1 ] == 1 이면 착용 시 발동, 2 이면 미착용 시 발동
if( !( ( m_nActivateValue[ i ][ 1 ] == 1 && bWearing ) || ( m_nActivateValue[ i ][ 1 ] == 2 && !bWearing ) ) )
return false;
}
break;
case LIMIT_CONDITION_SUMMON_OWNING:
{
bool bOwning = false;
if( ( pPlayer->GetMainSummon() && pPlayer->GetMainSummon()->GetSummonCode() == m_nActivateValue[ i ][ 0 ] ) ||
( pPlayer->GetSubSummon() && pPlayer->GetSubSummon()->GetSummonCode() == m_nActivateValue[ i ][ 0 ] ) )
bOwning = true;
// m_nActivateValue[ i ][ 1 ] == 1 이면 보유 시 발동, 2 이면 미보유 시 발동
if( !( ( m_nActivateValue[ i ][ 1 ] == 1 && bOwning ) || ( m_nActivateValue[ i ][ 1 ] == 2 && !bOwning ) ) )
return false;
}
break;
case LIMIT_CONDITION_STATE:
{
bool bStateOwning = pPlayer->GetState( static_cast< StructState::StateCode >( m_nActivateValue[ i ][ 0 ] ) );
// m_nActivateValue[ i ][ 1 ] == 1 이면 보유 시 발동, 2 이면 미보유 시 발동
if( !( ( m_nActivateValue[ i ][ 1 ] == 1 && bStateOwning ) || ( m_nActivateValue[ i ][ 1 ] == 2 && !bStateOwning ) ) )
return false;
}
break;
default:
break;
}
}
// 좌표 기반 인접 여부 체크
ArPosition posCurrent = pPlayer->GetCurrentPosition( GetArTime() );
X2D::Polygon< int > * pArea = m_vArea[ nAreaIndex ];
// 정밀 포함 체크 만족하면 통과(폴리곤을 포함하는 최소 사각형 체크 -> 가로선 긋고 각 엣지와 교점 수 체크 알고리즘으로 포함관계 체크)
if( pArea->IsLooseInclude( posCurrent.x, posCurrent.y ) )
return true;
// 정확히 도형에 포함되지 않는 경우 폴리곤을 포함하는 최소 사각형의 5미터 이내에 없으면 그냥 바깥으로 간주
X2D::Box< int > bxArea = pArea->GetBoundingBox();
bxArea.Set( bxArea.GetLeft() - 60, bxArea.GetTop() - 60, bxArea.GetRight() + 60, bxArea.GetBottom() + 60 );
if( !bxArea.IsLooseInclude( posCurrent.x, posCurrent.y ) )
return false;
// Measure the distance to each line segment, and if the distance is within 5 meters, allow passage
// Ref: http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/
size_t nSegmentCount = pArea->Size();
for( size_t nSegmentIdx = 0; nSegmentIdx < nSegmentCount; ++nSegmentIdx )
{
X2D::Line< int > line = pArea->GetSegment( nSegmentIdx );
int dx = line.end.x - line.begin.x;
int dy = line.end.y - line.begin.y;
int nPrimeLineLength = dx * dx + dy * dy;
float u = (float)( ( posCurrent.x - line.begin.x ) * ( line.end.x - line.begin.x ) + ( posCurrent.y - line.begin.y ) * ( line.end.y - line.begin.y ) )
/ nPrimeLineLength;
// If theres a perpendicular line connecting the point and the line, and the contact point is within 5 meters, allow passage
if( 0.0f <= u && u <= 1.0f )
{
ArPosition posCross;
posCross.x = line.begin.x + u * ( line.end.x - line.begin.x );
posCross.y = line.begin.y + u * ( line.end.y - line.begin.y );
if( posCross.GetDistance( posCurrent ) <= 60 )
{
return true;
}
}
// If there is no perpendicular line connecting the point and the line, allow passage if
// the distance between the point and the nearer of the lines start or end points is within 5 meters
else if( ( u < 0.0f && posCurrent.GetDistance( ArPosition( line.begin.x, line.begin.y ) ) <= 60 )
|| ( u > 1.0f && posCurrent.GetDistance( ArPosition( line.end.x, line.end.y ) ) <= 60 ) )
{
return true;
}
}
// In the polygon inclusion check, none of the conditions were met, so it is not included
return false;
}
struct polygon_allocator
{
X2D::Polygon<int>* operator()()
{
return new X2D::Polygon< int >();
}
};
struct polygon_deleter
{
void operator()( X2D::Polygon<int>* p )
{
delete p;
}
};
#ifndef _USE_NEW_ROAMING_ONLY
std::vector< GameContent::WAY_POINT_INFO > g_vWayPoint;
#endif
std::vector< _EXP_TABLE > g_vExpTable;
std::vector< _SUMMON_EXP_TABLE > g_vSummonExpTable;
std::vector< MonsterBase > g_vMonsterInfo;
std::vector< std::vector< MonsterBase::MONSTER_TRIGGER > > g_vMonsterTrigger;
std::vector< std::vector< MonsterBase::MONSTER_SKILL_INFO > > g_vMonsterSkill;
std::vector< std::vector< MonsterBase::MONSTER_ITEM_DROP_INFO > > g_vMonsterItemDrop;
std::vector< DropGroup > g_vDropGroupInfo;
std::vector< SummonBase > g_vSummonInfo;
std::vector< PetBase > g_vPetInfo;
std::vector< SkillBase > g_vSkillBase;
std::vector< QuestLink > g_vQuestLink;
KHash< QUEST_TEXT_ID, hashPr_mod_int > g_hsQuestTextId;
std::vector< CreatureStat > g_vStatInfo;
std::vector< StateInfo > g_vStateInfo;
std::vector< JobInfo > g_vJobInfo;
std::vector< JobLevelBonus > g_vJobLevelBonusInfo;
std::vector< SummonLevelBonus > g_vSummonLevelBonusInfo;
std::vector< StructNPC* > g_vNPC;
std::vector< _PROP_CONTACT_SCRIPT_INFO > g_vPropScriptInfo;
std::vector< std::string > g_vBanWord;
KHash< std::vector< SetItemEffectInfo * > *, hashPr_mod_int > g_hsSetItemEffectInfo;
KHash< std::vector< EffectInfo * > *, hashPr_mod_int > g_hsEffectInfo;
KHash< std::vector< EnhanceEffectInfo * > *, hashPr_mod_int > g_hsEnhanceEffectInfo;
std::vector< DeathmatchInfo * > g_vDeathmatchInfo;
std::vector< CreatureEnhanceInfo * > g_vCreatureEnhanceInfo;
std::vector< CreatureFarmInfo * > g_vCreatureFarmInfo;
X2D::QuadTree< AR_UNIT, GameContent::MapLocationInfo, true, 10 > g_qtLocationInfo( 0, 0, g_nMapWidth, g_nMapHeight );
// 한 폴리곤에 1개 이상의 점이 있다면 분할을 시도한다.(즉 MAX_DEPTH 까지 분할을 무조건 한다.)
// QuadTree_divide_polygon 사용 중지
// X2D::QuadTree_divide_polygon< int, X2D::Polygon<int>*, 1, 11 > g_qtBlockInfo( 0, 0, g_nMapWidth, g_nMapHeight );
X2D::QuadTree< int, X2D::Polygon<int>*, true, 10 > g_qtBlockInfo( 0, 0, g_nMapWidth, g_nMapHeight ); // 이전 충돌 맵
KHash< GameContent::EVENT_AREA_INFO *, hashPr_mod_int > g_hsEventAreaInfo;
std::vector< int > g_avSummonUniqueName[10];
std::vector< int > g_vSummonPrefix;
std::vector< int > g_vSummonPostFix;
static KHash< char*, hashPr_mod_int > g_hsString;
std::vector< NPCBase > g_vNPCInfo;
std::vector< std::vector< SkillTree > > g_vSkillTree;
std::vector< GameContent::MONSTER_RESPAWN_INFO > g_vMonsterRespawnInfo;
std::vector< GameContent::MONSTER_RESPAWN_INFO > g_vRaidMonsterRespawnInfo;
std::vector< GameContent::RANDOM_MONSTER_RESPAWN_INFO > g_vRandomMonsterRespawnInfo;
std::vector< GameContent::RANDOM_AREA_INFO > g_vRandomAreaInfo;
std::vector< GameContent::RANDOM_MONSTER_GROUP_INFO > g_vRandomMonsterGroupInfo;
std::vector< MonsterCreatureInfo > g_vMonsterCreatureInfo;
std::vector< SummonRandomSkillInfo > g_vSummonRandomSkillInfo;
std::vector< RANDOM_POOL > g_vRandomPool;
void GameContent::Nomalize( AR_UNIT * px, AR_UNIT * py )
{
*px += (CELL_SIZE/2);
*py += (CELL_SIZE/2);
*px /= CELL_SIZE;
*py /= CELL_SIZE;
*px = (int)*px;
*py = (int)*py;
*px *= CELL_SIZE;
*py *= CELL_SIZE;
}
std::vector< SkillTree >* GameContent::GetSkillTree( int skill_tree_id )
{
std::vector< std::vector< SkillTree > >::iterator it;
for( it = g_vSkillTree.begin(); it != g_vSkillTree.end(); ++it )
{
if( (*it).front().skill_tree_id == skill_tree_id ) return &(*it);
}
return NULL;
}
bool GameContent::IsValidItemCode( ItemBase::ItemCode code )
{
return StructItem::IsValidItemCode( code );
}
// 길찾기 부하를 위한 임시 로그 클래스
class LongsetTimeLogger
{
public:
LongsetTimeLogger( const char* szFunction )
{
m_szFunction = szFunction;
m_dLongestTick = 0;
m_dwUpdateTick = ::GetTickCount();
LARGE_INTEGER li;
::QueryPerformanceFrequency( &li );
m_freq = li.QuadPart;
}
void Start()
{
::QueryPerformanceCounter( &m_startTime );
}
void End( AR_UNIT x1, AR_UNIT y1, AR_UNIT x2, AR_UNIT y2 )
{
LARGE_INTEGER endTime;
::QueryPerformanceCounter( &endTime );
double dRunTick = static_cast<double>( (endTime.QuadPart - m_startTime.QuadPart) ) / m_freq;
if( dRunTick > m_dLongestTick )
{
m_dLongestTick = dRunTick;
#ifdef _DEBUG
FILELOG( "%s: %f (s), (%f,%f) -> (%f,%f)", m_szFunction, m_dLongestTick, x1, y1, x2, y2 );
_cprint( "%s: %f (s), (%f,%f) -> (%f,%f)\n", m_szFunction, m_dLongestTick, x1, y1, x2, y2 );
#endif
}
DWORD dwNowTick = ::GetTickCount();
if( (dwNowTick - m_dwUpdateTick) >= 60*60*1000 ) // 한시간에 한번 초기화
{
m_dLongestTick = 0;
m_dwUpdateTick = dwNowTick;
}
}
private:
const char* m_szFunction;
double m_dLongestTick;
DWORD m_dwUpdateTick;
__int64 m_freq;
LARGE_INTEGER m_startTime;
};
class LongsetTimeHelper
{
public:
LongsetTimeHelper( LongsetTimeLogger& logger, AR_UNIT x1, AR_UNIT y1, AR_UNIT x2, AR_UNIT y2 )
: m_logger( logger )
, m_x1( x1 )
, m_y1( y1 )
, m_x2( x2 )
, m_y2( y2 )
{
m_logger.Start();
}
~LongsetTimeHelper()
{
m_logger.End( m_x1, m_y1, m_x2, m_y2 );
}
private:
LongsetTimeLogger& m_logger;
AR_UNIT m_x1;
AR_UNIT m_y1;
AR_UNIT m_x2;
AR_UNIT m_y2;
};
bool GameContent::IsBlocked( AR_UNIT x, AR_UNIT y )
{
if( int(x) < 0 || int(x) > g_nMapWidth || int(y) < 0 || int(y) > g_nMapHeight )
return true;
if( !GameRule::bIsNoCollisionCheck )
{
X2D::Point< int > pt( x, y );
static LongsetTimeLogger logger( "GameContent::IsBlocked" );
LongsetTimeHelper helper( logger, x, y, 0, 0 );
if( g_qtBlockInfo.LooseCollision( pt ) )
{
return true;
}
}
return false;
}
bool GameContent::FindPath( AR_UNIT x1, AR_UNIT y1, AR_UNIT x2, AR_UNIT y2, std::vector< ArPosition >& vMoveInfo )
{
std::vector< X2D::Polygon<int>* > pathFindPolygons;
GameContent::GetCollisionPolygons( x1, y1, x2, y2, pathFindPolygons );
if( pathFindPolygons.empty() )
{
vMoveInfo.push_back( ArPosition( x1, y1 ) );
vMoveInfo.push_back( ArPosition( x2, y2 ) );
return true;
}
typedef std::vector< X2D::Point<int> > PATH;
PATH vPath;
X2D::Point< int > ptStart( x1, y1 ), ptEnd( x2, y2 );
static LongsetTimeLogger logger( "X2D::PathFinder::Find" );
LongsetTimeHelper helper( logger, x1, y1, x2, y2 );
// 최대 길찾기를 통하여 갈 수 있는 거리는 가시거리 * 2이다. (적절한 파라미터 테스트 중)
X2D::PathFinder< int, X2D::Polygon<int>* >::Find( pathFindPolygons.begin(), pathFindPolygons.end(), ptStart, ptEnd, vPath, GameRule::VISIBLE_RANGE * 2 );
if( vPath.empty() )
{
return false;
}
for( auto it = vPath.begin() ; it != vPath.end() ; ++it )
{
vMoveInfo.push_back( ArPosition( (*it).GetX(), (*it).GetY() ) );
}
return true;
}
void GameContent::GetCollisionPolygons( AR_UNIT x1, AR_UNIT y1, AR_UNIT x2, AR_UNIT y2, std::vector<X2D::Polygon<int> *> & vPolygons )
{
X2D::Point<int> ptStart( x1, y1 ), ptEnd( x2, y2 );
X2D::Box<int> boxRegion( ptStart, ptEnd );
static LongsetTimeLogger logger( "GameContent::GetCollisionPolygons" );
LongsetTimeHelper helper( logger, x1, y1, x2, y2 );
g_qtBlockInfo.EnumLoose( boxRegion, &vPolygons );
}
bool GameContent::GetIntersectPoint( AR_UNIT x1, AR_UNIT y1, AR_UNIT x2, AR_UNIT y2, AR_UNIT& xr, AR_UNIT& yr )
{
static LongsetTimeLogger logger( "GameContent::GetIntersectPoint" );
LongsetTimeHelper helper( logger, x1, y1, x2, y2 );
X2D::Line<int> ray(x1, y1, x2, y2);
X2D::Point<int> ptStart( x1, y1 ), ptEnd( x2, y2 );
double nSmallestSquareDist = sqrt( ((double)ptEnd.x - ptStart.x) * (ptEnd.x - ptStart.x) + (ptEnd.y - ptStart.y) * (ptEnd.y - ptStart.y) );
X2D::Box<int> boxRegion(ptStart, ptEnd);
std::vector<X2D::Polygon<int> *> vPolygons;
g_qtBlockInfo.EnumLoose(boxRegion, &vPolygons);
bool isIntersect = false;
for( std::vector<X2D::Polygon<int> *>::iterator it = vPolygons.begin(); it != vPolygons.end(); ++it )
{
for( size_t my_idx = 0; my_idx < (*it)->Size(); ++my_idx )
{
X2D::Point<int> ptTemp;
X2D::Line<int> currLine = (*it)->GetSegment( my_idx );
if( ray.GetIntersectPoint( currLine, &ptTemp ) )
{
double nSquareDist = sqrt( ((double)ptTemp.x - ptStart.x) * (ptTemp.x - ptStart.x) + (ptTemp.y - ptStart.y) * (ptTemp.y - ptStart.y) );
if(nSmallestSquareDist > nSquareDist)
{
nSmallestSquareDist = nSquareDist;
xr = ptTemp.x;
yr = ptTemp.y;
isIntersect = true;
}
}
}
}
if( isIntersect == true )
{
// 교차점이고 소수점을 버리면서 충돌이 나는 것 같다. 시작점 방향으로 좀 땡기는게 좋을듯.
// 1(단위백터)만큼 당긴다.
AR_UNIT v_x = (xr-x1);
AR_UNIT v_y = (yr-y1);
AR_UNIT dist = sqrt( (float) v_x*v_x + v_y*v_y );
AR_UNIT i_vx = v_x / dist;
AR_UNIT i_vy = v_y / dist;
//xr = (xr - i_vx);
if( v_x < 0 )
{
xr = xr + 1;
}
else
{
xr = xr - 1;
}
//yr = (yr - i_vy);
if( v_y < 0 )
{
yr = yr + 1;
}
else
{
yr = yr - 1;
}
if( GameContent::CollisionToLine( x1, y1, xr, yr ) == true )
{
xr = 0;
yr = 0;
isIntersect = false;
}
}
return isIntersect;
}
bool GameContent::CollisionToLine( AR_UNIT x1, AR_UNIT y1, AR_UNIT x2, AR_UNIT y2 )
{
X2D::Line<int> line( x1, y1, x2, y2 );
if( !GameRule::bIsNoCollisionCheck )
{
static LongsetTimeLogger logger( "GameContent::CollisionToLine" );
LongsetTimeHelper helper( logger, x1, y1, x2, y2 );
return g_qtBlockInfo.LooseCollision( line );
}
return false;
}
bool GameContent::GetValidRandomPos( const ArPosition& center, ArPosition& valid_pos, int v )
{
valid_pos = center;
valid_pos.x += ( (XRandom() % v) - (v / 2) );
valid_pos.y += ( (XRandom() % v) - (v / 2) );
if( GameContent::CollisionToLine( center.x, center.y, valid_pos.x, valid_pos.y ) == true )
{
valid_pos = center;
if( GameContent::IsBlocked( valid_pos.x, valid_pos.y ) == false )
{
return true;
}
return false;
}
return true;
}
KHash< size_t, hashPr_mod_int > & GetPropScriptHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetJobCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetStatCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetMonsterCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetMonsterTriggerCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetMonsterSkillCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetMonsterItemDropCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetDropGroupCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetSummonCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetPetCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
KHash< size_t, hashPr_mod_int > & GetSkillCodeHash()
{
static KHash< size_t, hashPr_mod_int > inst;
return inst;
}
const CreatureStat & GameContent::GetStatInfo( int stat_id )
{
static CreatureStat default_value;
size_t idx;
if( GetStatCodeHash().lookup( stat_id, idx ) )
{
return g_vStatInfo[idx];
}
assert( 0 && "Stat ID" );
return default_value;
}
bool GameContent::IsBannedWord( int code_page, const char *szString )
{
// 대문자화
char szUprWord[256];
XStringUtil::ToUpperThroughWChar( szUprWord, _countof( szUprWord ), szString, strlen( szString ), code_page );
// strstr이 대/소문자를 구분하므로 모두 대문자화 되어있음
for( std::vector< std::string >::iterator it = g_vBanWord.begin(); it != g_vBanWord.end(); ++it )
{
if( strstr( szUprWord, (*it).c_str() ) )
return true;
}
return false;
}
__declspec( thread ) char g_szFailString[64];
const char* GameContent::GetString( int string_id )
{
char *pString = "<NULL>";
if( g_hsString.lookup( string_id, pString ) == false )
{
if( s_sprintf( g_szFailString, _countof( g_szFailString ), "SVR%d", string_id ) < 0 )
{
g_szFailString[0] = '\0';
}
pString = g_szFailString;
}
return pString;
}
const StateInfo* GameContent::GetStateInfo( int state_id )
{
std::vector< StateInfo >::iterator it;
for( it = g_vStateInfo.begin(); it != g_vStateInfo.end(); ++it )
{
if( (*it).id == state_id ) return &(*it);
}
return NULL;
}
#ifndef _USE_NEW_ROAMING_ONLY
void GameContent::SetWayPointSpeed( int way_point_id, int way_point_speed )
{
for( std::vector< GameContent::WAY_POINT_INFO >::iterator it = g_vWayPoint.begin(); it != g_vWayPoint.end(); ++it )
{
if( (*it).way_point_id == way_point_id )
{
(*it).way_point_speed = way_point_speed;
return;
}
}
GameContent::WAY_POINT_INFO info;
info.way_point_id = way_point_id;
info.way_point_type = GameContent::WAY_POINT_CIRCULAR;
info.way_point_speed = way_point_speed;
g_vWayPoint.push_back( info );
}
void GameContent::SetWayPointType( int way_point_id, int way_point_type )
{
for( std::vector< GameContent::WAY_POINT_INFO >::iterator it = g_vWayPoint.begin(); it != g_vWayPoint.end(); ++it )
{
if( (*it).way_point_id == way_point_id )
{
(*it).way_point_type = way_point_type;
return;
}
}
GameContent::WAY_POINT_INFO info;
info.way_point_id = way_point_id;
info.way_point_type = way_point_type;
info.way_point_speed = 0;
g_vWayPoint.push_back( info );
}
void GameContent::AddWayPoint( int way_point_id, AR_UNIT x, AR_UNIT y )
{
for( std::vector< GameContent::WAY_POINT_INFO >::iterator it = g_vWayPoint.begin(); it != g_vWayPoint.end(); ++it )
{
if( (*it).way_point_id == way_point_id )
{
(*it).vWayPoint.push_back( ArPosition( x, y ) );
return;
}
}
GameContent::WAY_POINT_INFO info;
info.way_point_id = way_point_id;
info.way_point_type = GameContent::WAY_POINT_CIRCULAR;
info.way_point_speed = 0;
info.vWayPoint.push_back( ArPosition( x, y ) );
g_vWayPoint.push_back( info );
}
const GameContent::WAY_POINT_INFO * GameContent::GetWayPoint( int way_point_id )
{
for( std::vector< GameContent::WAY_POINT_INFO >::iterator it = g_vWayPoint.begin(); it != g_vWayPoint.end(); ++it )
{
if( (*it).way_point_id == way_point_id )
{
return &(*it);
}
}
return NULL;
}
#endif
void GameContent::EnumNPCByNPCId( std::vector< struct StructNPC* > & vNPCList, int nNPCId )
{
std::vector< StructNPC* >::iterator it;
for( it = g_vNPC.begin(); it != g_vNPC.end(); ++it )
{
if( (*it)->GetNPCID() != nNPCId ) continue;
vNPCList.push_back( *it );
}
}
const bool GameContent::SelectItemIDFromDropGroup( const int nDropGroupID, ItemBase::ItemCode & nItemID, __int64 & nItemCount )
{
DropGroup *pInfo = GameContent::GetDropGroupInfo( nDropGroupID );
assert( pInfo );
if( pInfo )
{
int nCum = 0;
int nKey = XRandom( 1, 100000000 );
for( int i = 0; i < DropGroup::MAX_DROP_GROUP; ++i )
{
if( !pInfo->drop_item_id[i] ) break;
nCum += pInfo->drop_percentage[i];
if( nKey > nCum ) continue;
nItemID = pInfo->drop_item_id[i];
nItemCount = XRandom( pInfo->drop_min_count[i], pInfo->drop_max_count[i] );
return true;
}
}
nItemID = 0;
nItemCount = 1;
return false;
}
DropGroup* GameContent::GetDropGroupInfo( int drop_group_id )
{
static DropGroup _minfo;
size_t idx;
if( GetDropGroupCodeHash().lookup( drop_group_id, idx ) )
{
return &g_vDropGroupInfo[idx];
}
// assert( 0 );
return &_minfo;
}
MonsterBase * GameContent::GetMonsterInfo( int monster_id )
{
static MonsterBase _minfo;
size_t idx;
if( GetMonsterCodeHash().lookup( monster_id, idx ) )
{
return &g_vMonsterInfo[idx];
}
// assert( 0 );
return &_minfo;
}
const std::vector< MonsterBase::MONSTER_TRIGGER > * GameContent::GetMonsterTriggerInfo( int nSkillTriggerID )
{
static std::vector< MonsterBase::MONSTER_TRIGGER > dummy;
// ID 값이 0이면 비어있는 더미 데이터를 반환
if( !nSkillTriggerID )
return &dummy;
size_t idx;
if( GetMonsterTriggerCodeHash().lookup( nSkillTriggerID, idx ) )
{
return &g_vMonsterTrigger[idx];
}
// 트리거는 없으나 스킬만 있는 몬스터의 경우 정상인 데이터이므로 assert를 무조건 걸지는 않음
assert( GetMonsterSkillCodeHash().lookup( nSkillTriggerID ) );
return &dummy;
}
const std::vector< MonsterBase::MONSTER_SKILL_INFO > * GameContent::GetMonsterSkillInfo( int nSkillTriggerID )
{
static std::vector< MonsterBase::MONSTER_SKILL_INFO > dummy;
// ID 값이 0이면 비어있는 더미 데이터를 반환
if( !nSkillTriggerID )
return &dummy;
size_t idx;
if( GetMonsterSkillCodeHash().lookup( nSkillTriggerID, idx ) )
{
return &g_vMonsterSkill[idx];
}
// 스킬은 없으나 트리거만 있는 몬스터의 경우 정상인 데이터이므로 assert를 무조건 걸지는 않음
assert( GetMonsterTriggerCodeHash().lookup( nSkillTriggerID ) );
return &dummy;
}
const std::vector< MonsterBase::MONSTER_ITEM_DROP_INFO > * GameContent::GetMonsterDropTable( int nItemDropInfoID )
{
static std::vector< MonsterBase::MONSTER_ITEM_DROP_INFO > dummy;
// ID 값이 0이면 비어있는 더미 데이터를 반환
if( !nItemDropInfoID )
return &dummy;
size_t idx;
if( GetMonsterItemDropCodeHash().lookup( nItemDropInfoID, idx ) )
{
return &g_vMonsterItemDrop[idx];
}
assert( 0 && "Monster Drop Table Info ID" );
return &dummy;
}
SummonBase * GameContent::GetSummonInfo( int summon_id )
{
static SummonBase _minfo;
size_t idx;
if( GetSummonCodeHash().lookup( summon_id, idx ) )
{
return &g_vSummonInfo[idx];
}
// assert( 0 ); 프로그램 서버에서 빈번하게 발생되는 스크립트라 assert 구문 삭제
return &_minfo;
}
bool GameContent::IsUniqueSummonName( const char * name )
{
for( int i = 0; i < 10; ++i )
{
std::vector< int >::iterator it;
for( it = g_avSummonUniqueName[i].begin(); it != g_avSummonUniqueName[i].end(); ++i )
{
if( !_stricmp( GetString(*it), name ) )
return false;
}
}
return true;
}
std::string GameContent::GetUniqueName( int summon_type )
{
if( summon_type < 0 || summon_type >= 10 )
return "";
return GetString( g_avSummonUniqueName[summon_type][ XRandom() % g_avSummonUniqueName[summon_type].size() ] );
}
std::string GameContent::GetSummonName()
{
std::string name;
name = GetString( g_vSummonPrefix[ XRandom() % g_vSummonPrefix.size() ] );
name += GetString( g_vSummonPostFix[ XRandom() % g_vSummonPostFix.size() ] );
return name;
}
PetBase * GameContent::GetPetInfo( int pet_id )
{
static PetBase _minfo;
size_t idx;
if( GetPetCodeHash().lookup( pet_id, idx ) )
{
return &g_vPetInfo[idx];
}
assert( 0 && "Pet ID" );
return &_minfo;
}
SkillBase * GameContent::GetSkillBase( int skill_id )
{
size_t idx;
if( GetSkillCodeHash().lookup( skill_id, idx ) )
{
return &g_vSkillBase[idx];
}
return NULL;
}
__int64 GameContent::GetNeedExp( int level )
{
if( level < 1 )
level = 1;
if( level > GameRule::MAX_LEVEL )
level = GameRule::MAX_LEVEL;
if( g_vExpTable.size() < static_cast< size_t >( level ) )
{
level = static_cast< int >( g_vExpTable.size() ) - 1;
}
return g_vExpTable[level-1].exp;
}
__int64 GameContent::GetNeedSummonExp( int level )
{
if( g_vSummonExpTable.size() < static_cast< size_t >( level ) )
{
return 0;
}
return g_vSummonExpTable[level-1].normal_exp;
}
const int GameContent::GetAllowedMaxSkillLevel( std::vector< SkillTree >* skillTree, const int skill_id )
{
if ( skillTree == NULL )
{
return 0;
}
int allowedLevel = 0;
for( std::vector< SkillTree >::iterator it = (*skillTree).begin(); it != skillTree->end(); ++it )
{
// 더 높은 단계의 스킬 트리에서 낮은 단계의 스킬을 레벨만 더 높한 상태로 가지고 있는 경우가 있음
// 이 경우는 현재 스킬 레벨이 스킬 트리의 해당 범위에 들어가는 경우에만 포함되는 것으로 간주한다.
if( it->skill_id == skill_id )
{
allowedLevel = std::max( allowedLevel, it->max_skill_lv );
}
}
return allowedLevel;
}
__int64 GameContent::GetNeedJpForJobLevelUp( int level, int job_depth )
{
assert( g_vExpTable.size() >= static_cast< size_t >( level ) );
if( job_depth > 3 )
{
assert( 0 );
job_depth = 3;
}
if( job_depth < 0 )
{
assert( 0 );
job_depth = 0;
}
return g_vExpTable[level-1].jp[job_depth];
}
__int64 GameContent::GetNeedJpForSkillLevelUp( int skill_id, int skill_level, int skill_tree_id )
{
SkillBase *pSkillBase = GetSkillBase( skill_id );
if( !pSkillBase || skill_level > SkillBase::MAX_SKILL_LEVEL )
{
return -1.0f;
}
// 배우려는 스킬이 레벨 0일 경우 JP 소모 없음. (랜덤 스킬 기억을 위해 존재)
if( skill_level == 0 )
return 0;
c_fixed10 jp_ratio = -1;
std::vector< SkillTree >* pTree = GetSkillTree( skill_tree_id );
std::vector< SkillTree >::iterator it;
if( !pTree )
{
return -1;
}
for( it = pTree->begin(); it != pTree->end(); ++it )
{
if( (*it).skill_id != skill_id ) continue;
if( (*it).max_skill_lv < skill_level ) continue;
jp_ratio = (*it).jp_ratio;
}
if( jp_ratio == -1 )
{
jp_ratio = 1;
}
return ( jp_ratio * pSkillBase->GetNeedJobPoint( skill_level ) ).GetAsInt64();
}
int GameContent::GetNeedTpForSkillLevelUp( int skill_id, int skill_level )
{
SkillBase *pSkillBase = GetSkillBase( skill_id );
if( !pSkillBase || skill_level > SkillBase::MAX_SKILL_LEVEL )
{
return -1;
}
// 배우려는 스킬 레벨이 0인 경우 TP소모 없음. (랜덤 스킬 기억을 위해 존재)
if( skill_level == 0 )
return 0;
return pSkillBase->GetNeedTalentPoint( skill_level );
}
void GameContent::ClearQuestLink()
{
// { 기존 NPC-퀘스트 링크 정보 다 지우고
std::vector< StructNPC* >::iterator it;
for( it = g_vNPC.begin(); it != g_vNPC.end(); ++it )
{
(*it)->ClearQuestLink();
}
// }
// 링크 데이터도 지운다
g_vQuestLink.clear();
// NPC없이 진행되는 퀘스트 텍스트 정보도 지움
g_hsQuestTextId.clear();
}
void GameContent::ApplyQuestLink()
{
for( std::vector< QuestLink >::iterator it = g_vQuestLink.begin(); it != g_vQuestLink.end(); ++it )
{
if( (*it).nNPCId )
{
std::vector< StructNPC* > vNPCList;
GameContent::EnumNPCByNPCId( vNPCList, (*it).nNPCId );
if( vNPCList.empty() )
{
FILELOG( "Invalid Quest Link Information : Q:%d -> NPC:%d", (*it).code, (*it).nNPCId );
_cprint( "Invalid Quest Link Information : Q:%d -> NPC:%d\n", (*it).code, (*it).nNPCId );
continue;
}
std::vector< StructNPC* >::iterator npc_it;
for( npc_it = vNPCList.begin(); npc_it != vNPCList.end(); ++npc_it )
{
// _oprint( "Quest Link : Q:%d -> NPC:%d\n", (*it).code, (*npc_it)->GetNPCID() );
(*npc_it)->LinkQuest( (*it) );
}
}
else
{
// NPC에 의해 진행되는 퀘스트가 아닌 경우에는 시작/중간/종료 텍스트 ID를 퀘스트 정보에
RegisterQuestTextId( (*it).code, (*it).nStartTextId, (*it).nInProgressTextId, (*it).nEndTextId );
}
}
}
const int GameContent::GetQuestTextId( const QuestBase::QuestCode nQuestCode, const int nProgress )
{
QUEST_TEXT_ID qti( 0, 0, 0 );
if (!g_hsQuestTextId.lookup(nQuestCode, qti))
{
_oprint("Quest text id not found for code: %d\n", nQuestCode);
return 0;
}
switch( nProgress )
{
case QUEST_IS_STARTABLE: return qti.nStartTextId;
case QUEST_IS_IN_PROGRESS: return qti.nInProgressTextId;
case QUEST_IS_FINISHABLE: return qti.nEndTextId;
}
assert( 0 && "Quest Progress Type Error" );
return 0;
}
bool GameContent::RegisterQuestLink( QuestLink & info )
{
g_vQuestLink.push_back( info );
return true;
}
void GameContent::RegisterQuestTextId( const QuestBase::QuestCode nQuestCode, const int nStartTextId, const int nInProgressTextId, const int nEndTextId )
{
QUEST_TEXT_ID qti( nStartTextId, nInProgressTextId, nEndTextId );
if( g_hsQuestTextId.lookup( nQuestCode, qti ) )
{
assert( 0 && "Already Exist Quest Text ID" );
qti.nStartTextId = nStartTextId;
qti.nInProgressTextId = nInProgressTextId;
qti.nEndTextId = nEndTextId;
}
else
g_hsQuestTextId.add( nQuestCode, qti );
}
void GameContent::RegisterStateInfo( const StateInfo & info )
{
std::vector< StateInfo >::iterator it;
for( it = g_vStateInfo.begin(); it != g_vStateInfo.end(); ++it )
{
if( (*it).id == info.id )
{
(*it) = info;
return;
}
}
g_vStateInfo.push_back( info );
}
bool GameContent::RegisterPropContactScriptInfo( int prop_id, int prop_type, int model_info, float x, float y, std::vector< _PROP_CONTACT_SCRIPT_INFO::_FUNCTION_LIST > vFunctionList )
{
if( GetPropScriptHash().has( prop_id ) )
{
throw XException( "Duplicate prop index!" );
return false;
}
_PROP_CONTACT_SCRIPT_INFO tag;
tag.prop_id = prop_id;
tag.x = x;
tag.y = y;
tag.model_info = model_info;
tag.vFunctionList = vFunctionList;
tag.prop_type = prop_type;
g_vPropScriptInfo.push_back( tag );
GetPropScriptHash().add( prop_id, g_vPropScriptInfo.size() - 1 );
//if( tag.prop_type == 0 ) _oprint( "NPC (%d) %d,%d %s\n", tag.prop_id, (int)tag.x, (int)tag.y, tag.vFunctionList[0].stdFunction.c_str() );
return true;
}
void GameContent::RegisterDropGroupInfo( DropGroup & info )
{
size_t idx = 0;
// 이미 존재하면 수정
if( GetDropGroupCodeHash().lookup( info.uid, idx ) )
{
g_vDropGroupInfo[ idx ] = info;
}
else
{
g_vDropGroupInfo.push_back( info );
idx = g_vDropGroupInfo.size() - 1;
GetDropGroupCodeHash().add( info.uid, idx );
}
}
void GameContent::RegisterMonsterInfo( MonsterBase & info )
{
size_t idx = 0;
// 이미 존재하면 수정
if( GetMonsterCodeHash().lookup( info.uid, idx ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 local_flag가 잘못되어 1개의 몬스터 id가 중첩 로딩된 것이므로 걸림
assert( ENV().GetString( "game.loading" ) == "complete" );
g_vMonsterInfo[ idx ] = info;
}
else
{
// 로딩 완료 이전이라면 최초 로딩에 의해 추가되는 것이니 허용
// 로딩 완료상태라면 g_vMonsterInfo에 push_back이 일어나 전체적으로 reallocation 발생할 시
// GetMonsterInfo로 포인터를 가지고 있던 기존에 리젠되어 있던 몬스터들의 m_pContentInfo가
// 무효한 메모리 영역을 참조하는 포인터가 되므로 서버가 다운 됨
assert( ENV().GetString( "game.loading" ) != "complete" );
g_vMonsterInfo.push_back( info );
idx = g_vMonsterInfo.size() - 1;
GetMonsterCodeHash().add( info.uid, idx );
}
}
void GameContent::RegisterMonsterTriggerInfo( const int nID, const std::vector< MonsterBase::MONSTER_TRIGGER > & vMonsterTriggerInfo )
{
size_t idx = 0;
if( GetMonsterTriggerCodeHash().lookup( nID, idx ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
assert( ENV().GetString( "game.loading" ) == "complete" );
g_vMonsterTrigger[ idx ] = vMonsterTriggerInfo;
}
else
{
// 로딩 완료 이전이라면 최초 로딩에 의해 추가되는 것이니 허용
// 로딩 완료상태라면 g_vMonsterTrigger에 push_back이 일어나 전체적으로 reallocation 발생할 시
// 기존의 포인터가 무효화되므로 서버 다운 됨
assert( ENV().GetString( "game.loading" ) != "complete" );
g_vMonsterTrigger.push_back( vMonsterTriggerInfo );
idx = g_vMonsterTrigger.size() - 1;
GetMonsterTriggerCodeHash().add( nID, idx );
}
}
void GameContent::RegisterMonsterSkillInfo( const int nID, const std::vector< MonsterBase::MONSTER_SKILL_INFO > & vMonsterSkillInfo )
{
size_t idx = 0;
if( GetMonsterSkillCodeHash().lookup( nID, idx ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
assert( ENV().GetString( "game.loading" ) == "complete" );
g_vMonsterSkill[ idx ] = vMonsterSkillInfo;
}
else
{
// 로딩 완료 이전이라면 최초 로딩에 의해 추가되는 것이니 허용
// 로딩 완료상태라면 g_vMonsterSkill에 push_back이 일어나 전체적으로 reallocation 발생할 시
// 기존의 포인터가 무효화되므로 서버 다운 됨
assert( ENV().GetString( "game.loading" ) != "complete" );
g_vMonsterSkill.push_back( vMonsterSkillInfo );
idx = g_vMonsterSkill.size() - 1;
GetMonsterSkillCodeHash().add( nID, idx );
}
}
void GameContent::RegisterMonsterDropTable( const int nID, const std::vector< MonsterBase::MONSTER_ITEM_DROP_INFO > & vMonsterItemDropInfo )
{
size_t idx = 0;
if( GetMonsterItemDropCodeHash().lookup( nID, idx ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
assert( ENV().GetString( "game.loading" ) == "complete" );
g_vMonsterItemDrop[ idx ] = vMonsterItemDropInfo;
}
else
{
// 로딩 완료 이전이라면 최초 로딩에 의해 추가되는 것이니 허용
// 로딩 완료상태라면 g_vMonsterItemDrop에 push_back이 일어나 전체적으로 reallocation 발생할 시
// 기존의 포인터가 무효화되므로 서버 다운 됨
assert( ENV().GetString( "game.loading" ) != "complete" );
g_vMonsterItemDrop.push_back( vMonsterItemDropInfo );
idx = g_vMonsterItemDrop.size() - 1;
GetMonsterItemDropCodeHash().add( nID, idx );
}
}
bool GameContent::RegisterSummonInfo( SummonBase & info )
{
size_t idx = 0;
// Summon Base에 미리 로딩한 스킬트리 정보를 연결시켜준다.
for( int i = 0 ; i < GameRule::MAX_SUMMON_SKILL_TREE ; ++i )
{
if( !info.skill_tree_id[i] )
continue;
std::vector< std::vector< SkillTree > >::iterator it;
for( it = g_vSkillTree.begin() ; it != g_vSkillTree.end() ; ++it )
{
if( (*it).front().skill_tree_id == info.skill_tree_id[i] )
{
info.skill_tree[i] = &(*it);
break;
}
}
}
for( int i = 0 ; i < GameRule::MAX_SUMMON_SKILL_TREE ; ++i )
{
if( !info.skill_tree_id[i] )
continue;
if( !info.skill_tree[i] || info.skill_tree[i]->front().skill_tree_id != info.skill_tree_id[i] )
{
assert( 0 );
_cprint( "MonsterLoader::RegisterSummonInfo - SkillTreeID provided, but cannot find the data envolved:[SummonCode:%d/SkillID:%d]\n", info.uid, info.skill_tree_id[i] );
FILELOG( "MonsterLoader::RegisterSummonInfo - SkillTreeID provided, but cannot find the data envolved:[SummonCode:%d/SkillID:%d]", info.uid, info.skill_tree_id[i] );
info.skill_tree_id[i] = 0;
info.skill_tree[i] = NULL;
}
}
// 이미 존재하면 수정
if( GetSummonCodeHash().lookup( info.uid, idx ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 local_flag가 잘못되어 1개의 소환수 id가 중첩 로딩된 것이므로 걸림
assert( ENV().GetString( "game.loading" ) == "complete" );
g_vSummonInfo[ idx ] = info;
}
else
{
// 소환수 레벨 스텟 보너스 정보도 함께 추가(더미만 추가)
SummonLevelBonus bonus;
g_vSummonInfo.push_back( info );
g_vSummonLevelBonusInfo.push_back( bonus );
idx = g_vSummonInfo.size() - 1;
GetSummonCodeHash().add( info.uid, idx );
}
return true;
}
void GameContent::RegisterSummonLevelBonusInfo( const SummonLevelBonus & level_bonus )
{
size_t idx = 0;
if( !GetSummonCodeHash().lookup( level_bonus.id, idx ) )
{
assert( 0 && "Already Exist Summon Level Bonus ID" );
return;
}
g_vSummonLevelBonusInfo[ idx ] = level_bonus;
}
bool GameContent::RegisterPetInfo( PetBase & info )
{
size_t idx = 0;
// 이미 존재하면 수정
if( GetPetCodeHash().lookup( info.uid, idx ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 local_flag가 잘못되어 1개의 펫 id가 중첩 로딩된 것이므로 걸림
assert( ENV().GetString( "game.loading" ) == "complete" );
g_vPetInfo[ idx ] = info;
}
else
{
g_vPetInfo.push_back( info );
idx = g_vPetInfo.size() - 1;
GetPetCodeHash().add( info.uid, idx );
}
return true;
}
bool GameContent::RegisterSkillBase( SkillBase & info )
{
size_t idx = 0;
// 이미 존재하면 수정
if( GetSkillCodeHash().lookup( info.GetID(), idx ) )
{
g_vSkillBase[ idx ] = info;
}
else
{
g_vSkillBase.push_back( info );
idx = g_vSkillBase.size() - 1;
GetSkillCodeHash().add( info.GetID(), idx );
}
return true;
}
bool GameContent::RegisterSkillJobPointTable( int skill_id, int *need_jp )
{
SkillBase *pBase = GetSkillBase( skill_id );
if( !pBase ) return false;
for( int i = 1; i <= SkillBase::MAX_SKILL_LEVEL; ++i )
{
pBase->SetNeedJobPoint( i, need_jp[i-1] );
}
return true;
}
bool GameContent::RegisterStringInfo( int string_id, const char *szString )
{
if( !string_id ) return true;
char *pValue = NULL;
size_t size = strlen(szString)+1;
char *pString = new char[ size ];
s_strcpy( pString, size, szString );
// 이미 존재하면 수정
if( g_hsString.lookup( string_id, pValue ) )
{
_oprint( "%d : %s\n", string_id, szString );
delete [] pValue;
if( !g_hsString.modify( string_id, pString ) ) assert( 0 );
}
else
{
g_hsString.add( string_id, pString );
}
return true;
}
void GameContent::RegisterExpTable( int level, __int64 exp, __int64 jp_0, __int64 jp_1, __int64 jp_2, __int64 jp_3 )
{
// 이미 존재하면 수정
if( g_vExpTable.size() >= static_cast< size_t >( level ) )
{
g_vExpTable[level-1].exp = exp;
g_vExpTable[level-1].jp[0] = jp_0;
g_vExpTable[level-1].jp[1] = jp_1;
g_vExpTable[level-1].jp[2] = jp_2;
g_vExpTable[level-1].jp[3] = jp_3;
return;
}
_EXP_TABLE tmpTable = {exp,{ jp_0, jp_1, jp_2, jp_3 } };
for( size_t i = g_vExpTable.size(); i < static_cast< size_t >( level ); ++i )
{
g_vExpTable.push_back( tmpTable );
}
}
void GameContent::RegisterSummonExpTable( int level, __int64 normal_exp, __int64 growth_exp, __int64 evolve_exp )
{
// 이미 존재하면 수정
if( g_vSummonExpTable.size() >= static_cast< size_t >( level ) )
{
g_vSummonExpTable[level-1].normal_exp = normal_exp;
return;
}
_SUMMON_EXP_TABLE tmpTable = { normal_exp, growth_exp, evolve_exp };
for( size_t i = g_vSummonExpTable.size(); i < static_cast< size_t >( level ); ++i )
{
g_vSummonExpTable.push_back( tmpTable );
}
}
void GameContent::RegisterStatInfo( const CreatureStat & info )
{
size_t idx = 0;
// 이미 존재하면 수정
if( GetStatCodeHash().lookup( info.stat_id, idx ) )
{
g_vStatInfo[ idx ] = info;
}
else
{
g_vStatInfo.push_back( info );
idx = g_vStatInfo.size() - 1;
GetStatCodeHash().add( info.stat_id, idx );
}
}
const JobInfo * GameContent::GetJobInfo( int job_id )
{
size_t idx;
if( GetJobCodeHash().lookup( job_id, idx ) )
{
return &g_vJobInfo[idx];
}
return NULL;
}
void GameContent::RegisterJobInfo( JobInfo & info )
{
size_t idx = 0;
// Job Info에 미리 로딩한 스킬트리 정보를 연결시켜준다
std::vector< std::vector< SkillTree > >::iterator it;
for( it = g_vSkillTree.begin() ; it != g_vSkillTree.end() ; ++it )
{
if( (*it).front().skill_tree_id == info.skill_tree_id )
{
info.skill_tree = &(*it);
break;
}
}
if( !info.skill_tree_id || !info.skill_tree || info.skill_tree->front().skill_tree_id != info.skill_tree_id )
{
assert( 0 );
_cprint( "ETCLoader::RegisterJobInfo - SkillTreeID provided, but cannot find the data envolved:[JobID:%d/SkillTreeID:%d]\n", info.id, info.skill_tree_id );
FILELOG( "ETCLoader::RegisterJobInfo - SkillTreeID provided, but cannot find the data envolved:[JobID:%d/SkillTreeID:%d]", info.id, info.skill_tree_id );
info.skill_tree_id = 0;
info.skill_tree = NULL;
}
// 이미 존재하면 수정
if( GetJobCodeHash().lookup( info.id, idx ) )
{
g_vJobInfo[ idx ] = info;
}
else
{
JobLevelBonus bonus;
g_vJobInfo.push_back( info );
g_vJobLevelBonusInfo.push_back( bonus ); // insert dummy;
idx = g_vJobInfo.size() - 1;
GetJobCodeHash().add( info.id, idx );
}
return;
}
void GameContent::RegisterJobLevelBonusInfo( const JobLevelBonus & level_bonus )
{
size_t idx = 0;
if( !GetJobCodeHash().lookup( level_bonus.job_id, idx ) )
{
assert( 0 && "Already Exist Job Level Bonus ID" );
return;
}
g_vJobLevelBonusInfo[ idx ] = level_bonus;
}
int GameContent::isLearnableSkill( struct StructCreature *pCreature, int skill_id, int skill_level, int skill_tree_id, int creature_job_level )
{
std::vector< SkillTree >* pTree = GameContent::GetSkillTree( skill_tree_id );
if( !pTree ) return RESULT_ACCESS_DENIED;
std::vector< SkillTree >::iterator it;
bool bMaxLimit = false;
bool bLevelLimit = false;
bool bEnhanceLimit = false;
for( it = pTree->begin(); it != pTree->end(); ++it )
{
// 스킬 ID가 틀리면 continue
if( (*it).skill_id != skill_id )
continue;
// 이미 max level 이면 배울 수 없음
if( (*it).max_skill_lv < skill_level )
{
bMaxLimit = true;
continue;
}
// level 후달림
if( (*it).lv > pCreature->GetLevel() )
return RESULT_NOT_ENOUGH_LEVEL;
// job level 후달림
if( (*it).job_lv > creature_job_level )
{
bLevelLimit = true;
continue;
}
//AziaMafia Fix Skill S25
//if( (*it).max_enhance < pCreature->GetEnhance() || (*it).min_enhance > pCreature->GetEnhance() )
if( 25 < pCreature->GetEnhance() || (*it).min_enhance > pCreature->GetEnhance() )
{
bEnhanceLimit = true;
continue;
}
// 필요 스킬레벨 후달림
for( int i = 0; i < _countof((*it).need_skill_id); ++i )
{
if( !(*it).need_skill_id[i] ) break;
if( pCreature->GetBaseSkillLevel( (*it).need_skill_id[i] ) < (*it).need_skill_lv[i] ) return RESULT_NOT_ENOUGH_SKILL;
}
// JP 검사
SkillBase *pBase = GameContent::GetSkillBase( skill_id );
if( !pBase ) return RESULT_ACCESS_DENIED;
if( pBase->GetNeedJobPoint( skill_level ) * (*it).jp_ratio > (float) pCreature->GetJobPoint() ) return RESULT_NOT_ENOUGH_JP;
// 특성 포인트 검사
if( pBase->GetNeedTalentPoint( skill_level ) > pCreature->GetTalentPoint() ) return RESULT_NOT_ENOUGH_TP;
return RESULT_SUCCESS;
}
if( bLevelLimit )
return RESULT_NOT_ENOUGH_JOB_LEVEL;
if( bMaxLimit )
return RESULT_LIMIT_MAX;
if( bEnhanceLimit )
return RESULT_ENHANCE_LIMIT;
return RESULT_ACCESS_DENIED;
}
/*ServerPill*/
bool GameContent::LearnAllSkill( struct StructCreature *pCreature )
{
for( int nJobDepth = 0, nSkillTreeCnt = 0; nJobDepth <= pCreature->GetJobDepth(); )
{
int nCurProccesingJobId = 0;
if ( nJobDepth < pCreature->GetJobDepth() ) nCurProccesingJobId = pCreature->GetPrevJobId( nJobDepth );
else if ( nJobDepth == pCreature->GetJobDepth() ) nCurProccesingJobId = pCreature->GetJobId();
std::vector< struct SkillTree > *vpSkillTree = NULL;
if( pCreature->IsSummon() )
{
SummonBase *pBase = GameContent::GetSummonInfo( nCurProccesingJobId );
if( !pBase )
return false;
if( nSkillTreeCnt >= GameRule::MAX_SUMMON_SKILL_TREE )
{
nSkillTreeCnt = 0;
nJobDepth++;
continue;
}
if( !pBase->skill_tree_id[ nSkillTreeCnt ] || !pBase->skill_tree[ nSkillTreeCnt ] )
{
nSkillTreeCnt++;
continue;
}
vpSkillTree = pBase->skill_tree[ nSkillTreeCnt++ ];
}
else if( pCreature->IsPlayer() )
{
const JobInfo *pInfo = GameContent::GetJobInfo( nCurProccesingJobId );
if( !pInfo || !pInfo->skill_tree)
return false;
vpSkillTree = pInfo->skill_tree;
++nJobDepth;
}
else
return false;
for( std::vector< struct SkillTree >::iterator itSkillTree = vpSkillTree->begin(); itSkillTree != vpSkillTree->end(); ++itSkillTree )
{
int nSkillID = (*itSkillTree).skill_id;
// 스킬 트리에서 가져온 스킬이 랜덤 스킬일 경우
// 이미 배운 스킬이면 그 스킬을 nSkillID로 사용, 아니면 직접 랜덤으로 하나 뽑는다.
// 랜덤으로 뽑힌 진짜 스킬의 나머지 룰(최대 레벨, 강화에 따른 배울 수 있는 수준 등)은
// 그 전 껍데기 랜덤 스킬의 룰을 따른다 (itSkillTree를 따른다는 말)
if( (*itSkillTree).skill_group_id )
{
StructSkill * pSkill = NULL;
for( int i = 0 ; i < GameRule::MAX_RANDOM_SKILL_COUNT ; ++i )
{
pSkill = pCreature->GetSkill( GetRandomSkillIDFromGroupID( (*itSkillTree).skill_group_id, i ) );
if( pSkill )
{
nSkillID = pSkill->GetSkillId();
break;
}
}
if( !pSkill )
{
nSkillID = GetSummonRandomSkillID( (*itSkillTree).skill_group_id );
}
}
int nNextSkillLv = pCreature->GetBaseSkillLevel( nSkillID ) + 1;
if( nNextSkillLv > (*itSkillTree).max_skill_lv ) continue;
//AziaMafia Fix Skill S25
//if( pCreature->GetEnhance() > (*itSkillTree).max_enhance ) continue;
if (pCreature->GetEnhance() > 25) continue;
if( pCreature->GetEnhance() < (*itSkillTree).min_enhance ) continue;
int nNeedJp = 0;
int nNeedTp = 0;
while( nNextSkillLv <= (*itSkillTree).max_skill_lv )
{
int nNeedPoint = 0;
nNeedPoint = GameContent::GetNeedJpForSkillLevelUp( nSkillID, nNextSkillLv, (*vpSkillTree).front().skill_tree_id );
if( nNeedPoint != -1 ) nNeedJp += nNeedPoint;
nNeedPoint = GameContent::GetNeedTpForSkillLevelUp( nSkillID, nNextSkillLv );
if( nNeedPoint != -1 ) nNeedTp += nNeedPoint;
++nNextSkillLv;
}
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pCreature ) );
if( nNeedJp ) pCreature->SetJP( nNeedJp );
if( nNeedTp )
{
if( pCreature->IsPlayer() )
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( pCreature );
LOG::Log11N4S( LM_CHARACTER_GAIN_TP, pPlayer->GetAccountID(),
pPlayer->GetSID(), 0,
pPlayer->GetPrevJobId( pPlayer->GetJobDepth() - 1 ), pPlayer->GetJobId(),
pPlayer->GetPrevJobLevel( pPlayer->GetJobDepth() - 1 ), pPlayer->GetJobLevel(),
pPlayer->GetTalentPoint(), nNeedTp,
0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS,
pPlayer->GetName(), LOG::STR_NTS,
"", 0,
"ByLearnAllSKill", LOG::STR_NTS );
}
pCreature->SetTalentPoint( nNeedTp );
}
pCreature->RegisterSkill( nSkillID, (*itSkillTree).max_skill_lv );
}
}
return true;
}
void GameContent::RegisterSkillTree( const SkillTree & info )
{
std::vector< SkillTree >* pTree = GetSkillTree( info.skill_tree_id );
if( pTree )
{
for( std::vector< SkillTree >::iterator it = pTree->begin(); it != pTree->end(); ++it )
{
if( (*it).skill_id == info.skill_id )
{
if( (*it).max_skill_lv > info.max_skill_lv )
{
// 최대 스킬 레벨은 강화를 할 수록 증가한다. 따라서 최대 스킬 레벨만 체크.
assert( (*it).max_enhance >= info.max_enhance );
SkillTree prev_tree = (*it);
(*it) = info;
pTree->push_back( prev_tree );
return;
}
break;
}
}
pTree->push_back( info );
return;
}
std::vector< SkillTree > vTree;
vTree.push_back( info );
g_vSkillTree.push_back( vTree );
}
typedef KHash< std::string, hashPr_string_nocase > FILE_HASH;
namespace ResourceManager
{
FILE_HASH & GetResourceFileHash()
{
static FILE_HASH _hash;
return _hash;
}
void Clear()
{
GetResourceFileHash().clear();
}
void Init()
{
ResourceManager::Clear();
struct _myScanner : IDirectoryScanner::Scanner
{
bool onFile( const char *szFullPath, const char *szDirectory, const char *szFileName )
{
ResourceManager::RegisterFile( szFullPath, szFileName );
return true;
}
} scanner;
IDirectoryScanner::Instance().Scan( ENV().GetString( "game.resource", "./Resource/" ).c_str(), &scanner );
}
std::string GetFullPathname( const char *szFileName )
{
std::string result;
GetResourceFileHash().lookup( szFileName, result );
return result;
}
void RegisterFile( const char *szFullPathName, const char *szFileName )
{
GetResourceFileHash().add( szFileName, szFullPathName );
}
};
void GameContent::LoadScript()
{
// LuaVM에 보관되어 있는 로드된 스크립트 파일 이름 목록 재설정
LUA()->ResetLoadedScriptList();
FILE_HASH::node* pValueNode = NULL;
bool bQuitFlag = ResourceManager::GetResourceFileHash().get_first_node( pValueNode );
while( bQuitFlag )
{
// .lua 파일일 경우만 스크립트 다시 로드
size_t len = pValueNode->value.size();
if( len > 4 && !_stricmp( (pValueNode->value.c_str()+(len-4)), ".lua" ) )
{
if( LUA()->LoadScript( pValueNode->value.c_str(), false ) == false )
{
FILELOG( "lua script file load failed. All functionality of lua is disabled. File: %s", pValueNode->value.c_str() );
_cprint( "lua script file load failed. All functionality of lua is disabled. File: %s\n", pValueNode->value.c_str() );
#ifndef _DEBUG
LUA()->DeInit();
throw XException( "lua script file load failed. All functionality of lua is disabled." );
#endif
}
}
bQuitFlag = ResourceManager::GetResourceFileHash().get_next_node( pValueNode );
}
// 로드되어야 할 ELA 파일 수
int nExpectedELACount = ENV().GetInt( "game.ela_count" );
if( nExpectedELACount )
{
size_t nLoadedELACount = 0;
bQuitFlag = ResourceManager::GetResourceFileHash().get_first_node( pValueNode );
while( bQuitFlag )
{
// .ela 파일일 경우만 스크립트 다시 로드
size_t len = pValueNode->value.size();
if( len > 4 && !_stricmp( (pValueNode->value.c_str()+(len-4)), ".ela" ) )
{
if( LUA()->LoadScript( pValueNode->value.c_str(), true ) )
{
// 서로 다른 폴더에 있는 같은 이름의 파일은 로드되지 않음(GetResourceFileHash()가 KHash에 파일 이름을 키로 사용해서...;;)
std::string strResult;
LUA()->RunString( "return get_module_name()", &strResult );
unsigned char szMD5Buffer[ 256 ];
if( strResult.empty() || strResult == "nil" || strResult.length() >= _countof( szMD5Buffer ) )
{
LUA()->DeInit();
FILELOG( "Invalid lua script file detected. All functionality of lua is disabled. File: %s, strResult: %s", pValueNode->value.c_str(), strResult.c_str() );
_cprint( "Invalid lua script file detected. All functionality of lua is disabled. File: %s, strResult: %s\n", pValueNode->value.c_str(), strResult.c_str() );
throw XException( "Invalid lua script file detected. All functionality of lua is disabled." );
}
strResult.insert( 0, "lua::" );
s_memcpy( szMD5Buffer, sizeof( szMD5Buffer ), strResult.c_str(), strResult.length() );
szMD5Buffer[ strResult.length() ] = 0;
MD5_CTX context;
MD5_Init( &context );
MD5_Update( &context, szMD5Buffer, static_cast< unsigned int >( strResult.length() ) );
unsigned char digest[ 16 ];
MD5_Final( digest, &context );
char szOutput[ 37 ];
s_sprintf( szOutput, _countof( szOutput ), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x.ela",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15] );
if( _stricmp( pValueNode->key.m_pStr, szOutput ) )
{
LUA()->DeInit();
FILELOG( "Incorrect lua script file detected. All functionality of lua is disabled. File: %s, Key: %s, Output: %s(Result: %s)", pValueNode->value.c_str(), pValueNode->key.m_pStr, szOutput, strResult.c_str() );
_cprint( "Incorrect lua script file detected. All functionality of lua is disabled. File: %s, Key: %s, Output: %s(Result: %s)\n", pValueNode->value.c_str(), pValueNode->key.m_pStr, szOutput, strResult.c_str() );
throw XException( "Incorrect lua script file detected. All functionality of lua is disabled." );
}
++nLoadedELACount;
}
else
{
LUA()->DeInit();
FILELOG( "lua ela script file load failed. All functionality of lua is disabled. File: %s", pValueNode->value.c_str() );
_cprint( "lua ela script file load failed. All functionality of lua is disabled. File: %s\n", pValueNode->value.c_str() );
throw XException( "lua ela script file load failed. All functionality of lua is disabled." );
}
}
bQuitFlag = ResourceManager::GetResourceFileHash().get_next_node( pValueNode );
}
// 로드된 총 ELA 파일 수 체크
if( nExpectedELACount != nLoadedELACount )
{
LUA()->DeInit();
FILELOG( "Error on loading lua script files. All functionality of lua is disabled." );
_cprint( "Error on loading lua script files. All functionality of lua is disabled.\n" );
throw XException( "Error on loading lua script files. All functionality of lua is disabled." );
}
}
}
void GameContent::ClearBannedWord()
{
g_vBanWord.clear();
}
void GameContent::RegisterBannedWord( const char *szString )
{
g_vBanWord.push_back( szString );
}
CreatureStat GameContent::GetJobLevelBonus( int depth, int * jobs, int * levels )
{
assert( depth >= 0 && depth < 4 );
CreatureStatServer stat;
memset( &stat, 0, sizeof( stat ) );
for( int i = 0; i <= depth; ++i )
{
size_t idx = 0;
bool bRet = GetJobCodeHash().lookup( jobs[i], idx );
assert( bRet );
// k1 * min( 20, level ) + max( 0, k2 * min( 20, (20 - level) ) ) + max( 0, k3 * min( 20, (40 - level ) ) )
// * k1 1차구간 스탯보너스(레벨20까지적용) * k2 2차구간 스탯보너스(레벨40까지적용) * k3 3차구간 스탯보너스(레벨60까지적용)
const JobLevelBonus & bonus = g_vJobLevelBonusInfo[idx];
stat.strength += bonus.strength[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.strength[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.strength[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.strength[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.vital += bonus.vital[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.vital[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.vital[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.vital[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.dexterity += bonus.dexterity[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.dexterity[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.dexterity[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.dexterity[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.agility += bonus.agility[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.agility[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.agility[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.agility[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.intelligence += bonus.intelligence[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.intelligence[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.intelligence[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.intelligence[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.mentality += bonus.mentality[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.mentality[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.mentality[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.mentality[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.luck += bonus.luck[0] * std::min( (int) 20, levels[i] )
+ std::max( (int) 0, bonus.luck[1] * std::min( (int) 20, (levels[i] - 20) ) )
+ std::max( (int) 0, bonus.luck[2] * std::min( (int) 20, (levels[i] - 40) ) )
+ std::max( (int) 0, bonus.luck[3] * std::min( (int) 40, (levels[i] - 60) ) );
stat.strength = stat.strength / 100 * 100;
stat.vital = stat.vital / 100 * 100;
stat.dexterity = stat.dexterity / 100 * 100;
stat.agility = stat.agility / 100 * 100;
stat.intelligence = stat.intelligence / 100 * 100;
stat.mentality = stat.mentality / 100 * 100;
stat.luck = stat.luck / 100 * 100;
}
CreatureStat pStat;
pStat.stat_id = stat.stat_id;
pStat.strength = stat.strength / 100;
pStat.vital = stat.vital / 100;
pStat.dexterity = stat.dexterity / 100;
pStat.agility = stat.agility / 100;
pStat.intelligence = stat.intelligence / 100;
pStat.mentality = stat.mentality / 100;
pStat.luck = stat.luck / 100;
return pStat;
}
CreatureStat GameContent::GetSummonLevelBonus( int summon_code, int growth_depth, int level )
{
assert( level > 0 && level <= GameRule::nMaxCreatureLevel );
CreatureStat stat;
memset( &stat, 0, sizeof( stat ) );
size_t idx = 0;
if( !GetSummonCodeHash().lookup( summon_code, idx ) )
{
assert( 0 && "Summon Level Bonus ID" );
return stat;
}
if( static_cast< int >( g_vSummonLevelBonusInfo.size() ) <= idx )
{
assert( 0 );
return stat;
}
if( growth_depth == 1 )
{
stat.strength = (int)( g_vSummonLevelBonusInfo[ idx ].strength * level );
stat.vital = (int)( g_vSummonLevelBonusInfo[ idx ].vitality * level );
stat.dexterity = (int)( g_vSummonLevelBonusInfo[ idx ].dexterity * level );
stat.agility = (int)( g_vSummonLevelBonusInfo[ idx ].agility * level );
stat.intelligence = (int)( g_vSummonLevelBonusInfo[ idx ].intelligence * level );
stat.mentality = (int)( g_vSummonLevelBonusInfo[ idx ].mentality * level );
stat.luck = (int)( g_vSummonLevelBonusInfo[ idx ].luck * level );
}
else
{
if( !g_vSummonLevelBonusInfo[idx].strength &&
!g_vSummonLevelBonusInfo[idx].vitality &&
!g_vSummonLevelBonusInfo[idx].dexterity &&
!g_vSummonLevelBonusInfo[idx].agility &&
!g_vSummonLevelBonusInfo[idx].intelligence &&
!g_vSummonLevelBonusInfo[idx].mentality &&
!g_vSummonLevelBonusInfo[idx].luck ) return stat;
stat.strength = (int)( g_vSummonLevelBonusInfo[ idx ].strength * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
stat.vital = (int)( g_vSummonLevelBonusInfo[ idx ].vitality * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
stat.dexterity = (int)( g_vSummonLevelBonusInfo[ idx ].dexterity * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
stat.agility = (int)( g_vSummonLevelBonusInfo[ idx ].agility * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
stat.intelligence = (int)( g_vSummonLevelBonusInfo[ idx ].intelligence * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
stat.mentality = (int)( g_vSummonLevelBonusInfo[ idx ].mentality * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
stat.luck = (int)( g_vSummonLevelBonusInfo[ idx ].luck * ( level + 1 - ( growth_depth - 1 ) * 50 ) );
}
return stat;
}
void GameContent::RegisterBlockInfo( const X2D::Polygon< int > & block_info )
{
X2D::Polygon< int > * pBlock_Info = new X2D::Polygon< int > ( block_info );
g_qtBlockInfo.Add( pBlock_Info );
}
void GameContent::RegisterEventAreaInfo( const int nEventAreaID, const EVENT_AREA_INFO & event_area )
{
EVENT_AREA_INFO * pEventArea = NULL;
if( g_hsEventAreaInfo.lookup( nEventAreaID, pEventArea ) )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 중첩 로딩된 것이므로 걸림
assert( ENV().GetString( "game.loading" ) == "complete" );
*pEventArea = event_area;
}
else
{
pEventArea = new EVENT_AREA_INFO( event_area );
g_hsEventAreaInfo.add( nEventAreaID, pEventArea );
}
}
void GameContent::RegisterEventAreaBlock( const int nEventAreaID, const X2D::Polygon< int > & block )
{
EVENT_AREA_INFO * pEventArea = NULL;
if( !g_hsEventAreaInfo.lookup( nEventAreaID, pEventArea ) )
{
// RegisterEventAreaInfo 함수를 통해 이미 등록된 EVENT_AREA_INFO에 대해서만 블록 추가가 가능
// EventAreaResource의 데이터 중 스크립트 내용이 비어있어서 이벤트 영역 데이터가 추가되지 않은 것일 수 있음
_cprint( "unknown battle area id. id(%d)\n", nEventAreaID );
FILELOG( "unknown battle area id. id(%d)", nEventAreaID );
return;
}
#ifdef _DEBUG
for( std::vector< X2D::Polygon< int > * >::const_iterator it = pEventArea->m_vArea.begin() ; it != pEventArea->m_vArea.end() ; ++it )
{
assert( !( block == (*(*it)) ) );
}
#endif
pEventArea->m_vArea.push_back( new X2D::Polygon< int >( block ) );
}
void GameContent::RegisterMapLocationInfo( const MapLocationInfo & location_info )
{
g_qtLocationInfo.Add( location_info );
}
const bool GameContent::IsActivatableEventArea( const StructPlayer * pPlayer, const int nEventAreaID, const int nAreaIndex )
{
EVENT_AREA_INFO * pEventArea = NULL;
if( !g_hsEventAreaInfo.lookup( nEventAreaID, pEventArea ) )
return false;
// nAreaIndex가 정상적으로 검색된 이벤트 영역의 구역 목록 개수 이내인지 체크
if( nAreaIndex < 0 || static_cast< size_t >( nAreaIndex ) >= pEventArea->m_vArea.size() )
return false;
// 발동 횟수 제한 체크(EVENT_AREA_INFO::IsActivatable 함수에서는 해당 이벤트 영역의 ID 값을 알 수 없으므로 여기서 해야 함)
if( pEventArea->m_nLimitActivateCount )
{
if( pPlayer->GetEventAreaEnterCount( nEventAreaID ) >= pEventArea->m_nLimitActivateCount )
return false;
}
return pEventArea->IsActivatable( pPlayer, nAreaIndex );
}
const int GameContent::GetEnterCountLimitOfEventArea( const int nEventAreaID )
{
EVENT_AREA_INFO * pEventArea = NULL;
if( !g_hsEventAreaInfo.lookup( nEventAreaID, pEventArea ) )
return 0;
return pEventArea->m_nLimitActivateCount;
}
const char * GameContent::GetEventAreaEnterHandler( const int nEventAreaID )
{
EVENT_AREA_INFO * pEventArea = NULL;
if( !g_hsEventAreaInfo.lookup( nEventAreaID, pEventArea ) )
{
assert( 0 && "Event Area ID" );
}
return ( pEventArea ) ? pEventArea->m_strEnterHandler.c_str() : "";
}
const char * GameContent::GetEventAreaLeaveHandler( const int nEventAreaID )
{
EVENT_AREA_INFO * pEventArea = NULL;
if( !g_hsEventAreaInfo.lookup( nEventAreaID, pEventArea ) )
{
assert( 0 && "Event Area ID" );
}
return ( pEventArea ) ? pEventArea->m_strLeaveHandler.c_str() : "";
}
const GameContent::RANDOM_AREA_INFO GameContent::GetRandomAreaInfo( int random_area_id )
{
for( std::vector< GameContent::RANDOM_AREA_INFO >::const_iterator it = g_vRandomAreaInfo.begin(); it != g_vRandomAreaInfo.end(); ++it )
{
if( (*it).random_area_id == random_area_id )
{
return (*it);
}
}
return GameContent::RANDOM_AREA_INFO();
}
const GameContent::AREA_BOX GameContent::GetRandomRespawnBox( int random_area_id )
{
GameContent::RANDOM_AREA_INFO areaInfo = GameContent::GetRandomAreaInfo( random_area_id );
if( areaInfo.random_area_id == 0 || areaInfo.vRandomBox.empty() )
return GameContent::AREA_BOX();
return areaInfo.vRandomBox.at( XRandom( 0, static_cast< int >( areaInfo.vRandomBox.size()-1 ) ) );
}
const int GameContent::GetRandomRespawnChannelID( int random_area_id )
{
GameContent::RANDOM_AREA_INFO areaInfo = GameContent::GetRandomAreaInfo( random_area_id );
return areaInfo.channel_id;
}
const int GameContent::GetRandomRespawnDungeonID( int random_area_id )
{
GameContent::RANDOM_AREA_INFO areaInfo = GameContent::GetRandomAreaInfo( random_area_id );
return areaInfo.dungeon_id;
}
int GameContent::GetLocationId( AR_UNIT x, AR_UNIT y )
{
std::vector< MapLocationInfo > vLocations;
X2D::Point< AR_UNIT > pt( x, y );
g_qtLocationInfo.Enum( pt, &vLocations );
int priority = 0x7fffffff;
int location_id = 0;
std::vector< MapLocationInfo >::iterator it;
for( it = vLocations.begin(); it != vLocations.end(); ++it )
{
if( (*it).priority < priority )
{
priority = (*it).priority;
location_id = (*it).location_id;
}
}
return location_id;
}
void GameContent::RegisterSummonDefaultName( int id, bool bIsPrefix )
{
if( bIsPrefix )
g_vSummonPrefix.push_back( id );
else
g_vSummonPostFix.push_back( id );
}
void GameContent::RegisterSummonUniqueName( int summon_type, int id )
{
if( summon_type < 0 || summon_type >= 10 )
return;
g_avSummonUniqueName[summon_type].push_back( id );
}
void GameContent::RegisterNPCInfo( struct NPCBase & npc_info )
{
g_vNPCInfo.push_back( npc_info );
}
struct NPCBase & GameContent::GetNPCInfo( const int id )
{
for( std::vector< struct NPCBase >::iterator it = g_vNPCInfo.begin() ; it != g_vNPCInfo.end() ; ++it )
{
if( (*it).id == id )
return (*it);
}
static NPCBase npc_base;
npc_base.id = 0;
return npc_base;
}
void GameContent::RegisterMonsterRespawnInfo( const GameContent::MONSTER_RESPAWN_INFO & info )
{
g_vMonsterRespawnInfo.push_back( info );
}
void GameContent::RegisterRaidMonsterRespawnInfo( const GameContent::MONSTER_RESPAWN_INFO & info )
{
g_vRaidMonsterRespawnInfo.push_back( info );
}
void GameContent::RegisterRandomMonsterRespawnInfo( const GameContent::RANDOM_MONSTER_RESPAWN_INFO & info )
{
for( std::vector< GameContent::RANDOM_MONSTER_RESPAWN_INFO >::iterator it = g_vRandomMonsterRespawnInfo.begin(); it != g_vRandomMonsterRespawnInfo.end(); ++it )
{
if( (*it).id == info.id )
{
(*it).id = info.id;
(*it).interval = info.interval;
(*it).random_area_id = info.random_area_id;
(*it).inc = info.inc;
(*it).is_wandering = info.is_wandering;
(*it).way_point_id = info.way_point_id;
(*it).prespawn_count = info.prespawn_count;
(*it).except_raid_siege = info.except_raid_siege;
return;
}
}
g_vRandomMonsterRespawnInfo.push_back( info );
}
void GameContent::AddRandomAreaInfo( const int random_area_id, const AR_UNIT left, const AR_UNIT top, const AR_UNIT right, const AR_UNIT bottom )
{
assert( left <= right );
assert( top <= bottom );
int channel_id = ChannelManager::GetChannelId( left + ( right - left ) / 2.0f, bottom + ( top - bottom ) / 2.0f );
int dungeon_id = DungeonManager::Instance().GetDungeonID( left + ( right - left ) / 2.0f, bottom + ( top - bottom ) / 2.0f );
for( std::vector< GameContent::RANDOM_AREA_INFO >::iterator it = g_vRandomAreaInfo.begin(); it != g_vRandomAreaInfo.end(); ++it )
{
if( (*it).random_area_id == random_area_id )
{
assert( (*it).channel_id == channel_id );
assert( (*it).dungeon_id == dungeon_id );
(*it).vRandomBox.push_back( GameContent::AREA_BOX( left, top, right, bottom ) );
return;
}
}
g_vRandomAreaInfo.push_back( GameContent::RANDOM_AREA_INFO( random_area_id, channel_id, dungeon_id, GameContent::AREA_BOX( left, top, right, bottom ) ) );
}
void GameContent::AddRandomMonster( int id, int monster_id, int ratio )
{
for( std::vector< GameContent::RANDOM_MONSTER_RESPAWN_INFO >::iterator it = g_vRandomMonsterRespawnInfo.begin(); it != g_vRandomMonsterRespawnInfo.end(); ++it )
{
if( (*it).id == id )
{
(*it).monster_list.push_back( std::make_pair( monster_id, ratio ) );
return;
}
}
}
void GameContent::AddRandomMonsterGroup( int group_id, int monster_id, int ratio )
{
for( std::vector< GameContent::RANDOM_MONSTER_GROUP_INFO >::iterator it = g_vRandomMonsterGroupInfo.begin(); it != g_vRandomMonsterGroupInfo.end(); ++it )
{
if( (*it).group_id == group_id )
{
(*it).monster_list.push_back( std::make_pair( monster_id, ratio ) );
return;
}
}
g_vRandomMonsterGroupInfo.push_back( GameContent::RANDOM_MONSTER_GROUP_INFO( group_id ) );
g_vRandomMonsterGroupInfo[ g_vRandomMonsterGroupInfo.size() - 1 ].monster_list.push_back( std::make_pair( monster_id, ratio ) );
}
void GameContent::RegisterSetItemEffectInfo( const int nSetID, const SetItemEffectInfo & info )
{
std::vector< SetItemEffectInfo * > * pvSetItemEffectInfo = NULL;
// 기존에 있던 벡터면 벡터 안에서 재검색
if( g_hsSetItemEffectInfo.lookup( nSetID, pvSetItemEffectInfo ) )
{
for( std::vector< SetItemEffectInfo * >::iterator it = pvSetItemEffectInfo->begin() ; it != pvSetItemEffectInfo->end() ; ++it )
{
// 기존에 있던 세트ID/세트파트ID 이므로 기존에 로드된 녀석을 덮어 씀
if( (*it)->nSetPartID == info.nSetPartID )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 1개의 SetID에 SetPartID가 중첩 로딩된 것이므로 걸림
assert( ENV().GetString( "game.loading" ) == "complete" );
*(*it) = info;
return;
}
}
}
// 기존에 한 번도 등록되지 않은 세트ID라면 벡터 추가
else
{
pvSetItemEffectInfo = new std::vector< SetItemEffectInfo * >;
g_hsSetItemEffectInfo.add( nSetID, pvSetItemEffectInfo );
}
// 기존에 없던 세트파트ID 이므로 추가
SetItemEffectInfo * pInfo = new SetItemEffectInfo( info );
pvSetItemEffectInfo->push_back( pInfo );
}
void GameContent::RegisterEffectInfo( const int nEffectID, const EffectInfo & info )
{
std::vector< EffectInfo * > * pvEffectList = NULL;
// 기존에 있던 벡터면 벡터 안에서 재검색
if( g_hsEffectInfo.lookup( nEffectID, pvEffectList ) )
{
for( std::vector< EffectInfo * >::iterator it = pvEffectList->begin() ; it != pvEffectList->end() ; ++it )
{
// 기존에 있던 성능ID/순서ID 이므로 기존에 로드된 녀석을 덮어 씀
if( (*it)->nOrdinalID == info.nOrdinalID )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 1개의 EffectID에 OrdinalID가 중첩 로딩된 것이므로 걸림
assert( ENV().GetString( "game.loading" ) == "complete" );
*(*it) = info;
return;
}
}
}
// 기존에 한 번도 등록되지 않은 성능ID라면 벡터 추가
else
{
pvEffectList = new std::vector< EffectInfo * >;
g_hsEffectInfo.add( nEffectID, pvEffectList );
}
// EffectInfo::nOrdinalID 값이 순서에 맞지 않게 들어오는 경우 체크
assert( pvEffectList->empty() || pvEffectList->back()->nOrdinalID + 1 == info.nOrdinalID );
// 기존에 없던 순서ID이므로 추가
EffectInfo * pInfo = new EffectInfo( info );
pvEffectList->push_back( pInfo );
}
void GameContent::RigsterEnhanceEffectInfo( const int nEnhanceEffectID, const int nSubID, const EnhanceEffectInfo & info )
{
std::vector< EnhanceEffectInfo * > * pvEffectList = NULL;
// 기존에 있던 벡터면 벡터 안에서 재검색
if( g_hsEnhanceEffectInfo.lookup( nEnhanceEffectID, pvEffectList ) )
{
int subID = 1;
for( std::vector< EnhanceEffectInfo * >::iterator it = pvEffectList->begin() ; it != pvEffectList->end() ; ++it, ++subID )
{
// SubID는 1번부터 순차적으로 빈틈없이 간다고 가정
if( subID == nSubID )
{
// 로딩 완료상태에서 있던 정보가 다시 들어오는 건 SCRIPT_Reload에 의해서이니 허용
// 로딩 완료 이전이라면 nSubID가 같을 수는 없으므로 걸림. (사실 nSubID가 Primary Key Set이라서 걸리진 않을 듯)
assert( ENV().GetString( "game.loading" ) == "complete" );
*(*it) = info;
return;
}
}
}
// 기존에 한 번도 등록되지 않은 성능ID라면 벡터 추가
else
{
pvEffectList = new std::vector< EnhanceEffectInfo * >;
g_hsEnhanceEffectInfo.add( nEnhanceEffectID, pvEffectList );
}
// 기존에 없던 순서ID이므로 추가
EnhanceEffectInfo * pInfo = new EnhanceEffectInfo( info );
pvEffectList->push_back( pInfo );
// nSubID 순서대로 빈틈없이 들어오는 지 체크
assert( pvEffectList->empty() || pvEffectList->size() == nSubID );
}
__declspec( thread ) StructNPC * s_pCurrentInitNPC = NULL;
StructNPC * GetCurrentThreadInitNPC()
{
return s_pCurrentInitNPC;
}
void AddNPCToRespawnObject( StructNPC * pNPC );
void InitNPCRespawnObject();
StructNPC * GameContent::GetNewNPC( const NPCBase & info, const unsigned char nLayer )
{
StructNPC *pNPC = new StructNPC( &info );
pNPC->SetCurrentLayer( nLayer );
g_vNPC.push_back( pNPC );
pNPC->CalculateStat();
s_pCurrentInitNPC = pNPC;
for( std::vector< QuestLink >::iterator qit = g_vQuestLink.begin() ; qit != g_vQuestLink.end() ; ++qit )
{
if( (*qit).nNPCId == pNPC->GetNPCID() )
{
pNPC->LinkQuest( (*qit) );
}
}
return pNPC;
}
void GameContent::AddNPCToWorld()
{
std::vector< NPCBase >::iterator it;
for( it = g_vNPCInfo.begin(); it != g_vNPCInfo.end(); ++it )
{
// 일반, 캐쉬 NPC만 리젠시키되, 캐쉬 NPC는 bIsCashUsableServer 설정이 있거나 테섭이어야만 리젠
if( (*it).spawn_type != NPCBase::SPAWN_NORMAL && ( (*it).spawn_type != NPCBase::SPAWN_CASH || ( !GameRule::bIsCashUsableServer && ENV().GetInt( "game.ServiceServer" ) != 0 ) ) )
{
continue;
}
if( (*it).roaming_id )
{
RoamingManager::Instance().AddRoamingCreatureRespawnInfo( (*it).roaming_id, GameContent::ROAMING_CREATURE_RESPAWN_INFO( StructRoamer::ROAMING_CREATURE_NPC, (*it).id, (*it).regen_time, (*it).x, (*it).y * GameRule::DEFAULT_UNIT_SIZE ) );
continue;
}
int channel_id = ChannelManager::GetChannelId( (*it).x, (*it).y );
std::vector< unsigned char > vLayers;
ChannelManager::GetLayersOfChannel( channel_id, vLayers );
for( std::vector< unsigned char >::iterator lit = vLayers.begin() ; lit != vLayers.end() ; ++lit )
{
StructNPC * pNPC = GetNewNPC( (*it), (*lit) );
AddNPCToRespawnObject( pNPC );
}
}
InitNPCRespawnObject();
}
void GameContent::AddRespawnObjectToWorld()
{
std::vector< GameContent::MONSTER_RESPAWN_INFO >::iterator it;
for( it = g_vMonsterRespawnInfo.begin(); it != g_vMonsterRespawnInfo.end(); ++it )
{
GameContent::MONSTER_RESPAWN_INFO info = (*it);
ArPosition posCenter( info.left + ( info.right - info.left ) / 2.0f, info.bottom + ( info.top - info.bottom ) / 2.0f );
int channel_id = ChannelManager::GetChannelId( posCenter.GetX(), posCenter.GetY() );
int dungeon_id = DungeonManager::Instance().GetDungeonID( info.left + ( info.right - info.left ) / 2.0f, info.bottom + ( info.top - info.bottom ) / 2.0f );
if( channel_id )
{
if( ChannelManager::GetChannelType( channel_id ) == ChannelManager::TYPE_USER_LIMIT )
{
std::vector< unsigned char > vLayers;
ChannelManager::GetLayersOfChannel( channel_id, vLayers );
for( std::vector< unsigned char >::iterator lit = vLayers.begin() ; lit != vLayers.end() ; ++lit )
{
info.layer = (*lit);
AddRespawnObject( info );
}
}
else if( dungeon_id )
{
info.dungeon_id = dungeon_id;
AddRespawnObject( info );
DungeonManager::Instance().RegisterDungeonMonsterRespawnInfo( dungeon_id, info );
}
}
else
{
AddRespawnObject( info );
}
}
std::vector< GameContent::RANDOM_MONSTER_RESPAWN_INFO >::iterator rIt;
for( rIt = g_vRandomMonsterRespawnInfo.begin(); rIt != g_vRandomMonsterRespawnInfo.end(); ++rIt )
{
GameContent::RANDOM_MONSTER_RESPAWN_INFO info = (*rIt);
int channel_id = GameContent::GetRandomRespawnChannelID( info.random_area_id );
int dungeon_id = GameContent::GetRandomRespawnDungeonID( info.random_area_id );
if( channel_id )
{
if( ChannelManager::GetChannelType( channel_id ) == ChannelManager::TYPE_USER_LIMIT )
{
std::vector< unsigned char > vLayers;
ChannelManager::GetLayersOfChannel( channel_id, vLayers );
for( std::vector< unsigned char >::iterator lit = vLayers.begin() ; lit != vLayers.end() ; ++lit )
{
info.layer = (*lit);
AddRandomRespawnObject( info );
}
}
else if( dungeon_id )
{
info.dungeon_id = dungeon_id;
AddRandomRespawnObject( info );
if( !info.except_raid_siege )
DungeonManager::Instance().RegisterRandomDungeonMonsterRespawnInfo( dungeon_id, info );
}
}
else
{
AddRandomRespawnObject( info );
}
}
}
void GameContent::RegisterRandomPoolInfo( int group_id, int quest_target_id, int target_level )
{
std::vector< RANDOM_POOL >::iterator it;
RANDOM_POOL * pRandomPool = NULL;
for( it = g_vRandomPool.begin(); it != g_vRandomPool.end(); ++it )
{
if( group_id == (*it).group_id )
{
pRandomPool = &(*it);
break;
}
}
if( !pRandomPool )
{
g_vRandomPool.push_back( RANDOM_POOL( group_id ) );
pRandomPool = &(g_vRandomPool.back() );
}
pRandomPool->vInfo.push_back( GameContent::RANDOM_POOL_INFO( quest_target_id, target_level ) );
}
bool GameContent::IsInRandomPoolMonster( int group_id, int monster_id )
{
if( group_id == monster_id )
return true;
if( group_id >= 0 )
return false;
std::vector< RANDOM_POOL >::iterator it;
for( it = g_vRandomPool.begin(); it != g_vRandomPool.end(); ++it )
{
if( group_id == (*it).group_id )
{
for( std::vector< GameContent::RANDOM_POOL_INFO >::iterator itRandom = (*it).vInfo.begin(); itRandom != (*it).vInfo.end(); ++itRandom )
{
if( (*itRandom).quest_target_id == monster_id )
return true;
}
break;
}
}
return false;
}
const std::vector< SetItemEffectInfo * > * GameContent::GetSetItemEffectInfoVector( const int nSetID )
{
static std::vector< SetItemEffectInfo * > vDummy;
std::vector< SetItemEffectInfo * > * pvInfo = NULL;
if( g_hsSetItemEffectInfo.lookup( nSetID, pvInfo ) )
return pvInfo;
assert( 0 && "Set Item Effect ID" );
return &vDummy;
}
const std::vector< EffectInfo * > * GameContent::GetEffectInfoVector( const int nEffectID )
{
static std::vector< EffectInfo * > vDummy;
std::vector< EffectInfo * > * pvInfo = NULL;
if( g_hsEffectInfo.lookup( nEffectID, pvInfo ) )
return pvInfo;
assert( 0 && "Effect Info Vector ID" );
return &vDummy;
}
const std::vector< EnhanceEffectInfo * > * GameContent::GetEnhanceEffectVector( const int nEnhanceEffectID )
{
static std::vector< EnhanceEffectInfo * > vDummy;
std::vector< EnhanceEffectInfo * > * pvInfo = NULL;
if( g_hsEnhanceEffectInfo.lookup( nEnhanceEffectID, pvInfo ) )
return pvInfo;
assert( 0 && "EnhanceEffect information does not exist" );
return &vDummy;
}
bool GameContent::GetRandomPoolInfo( int group_id, std::vector< GameContent::RANDOM_POOL_INFO > & rRandomPoolInfo, int level )
{
std::vector< RANDOM_POOL >::iterator it;
for( it = g_vRandomPool.begin(); it != g_vRandomPool.end(); ++it )
{
if( group_id == (*it).group_id )
{
rRandomPoolInfo = (*it).vInfo;
for( std::vector< GameContent::RANDOM_POOL_INFO >::iterator itRandom = rRandomPoolInfo.begin(); itRandom != rRandomPoolInfo.end(); )
{
if( (*itRandom).level > level + 4 || (*itRandom).level < level - 4 )
{
itRandom = rRandomPoolInfo.erase( itRandom );
}
else
{
++itRandom;
}
}
return true;
}
}
return false;
}
void GameContent::RegisterDeathmatchInfo( const int id, const int deathmatch_type, const int min_level, const int max_level, const ArPosition position[] )
{
std::vector< ArPosition > vPosition;
for( int i=0; i<5; i++ )
{
if( position[i].GetX() == 0 && position[i].GetY() == 0 ) break;
vPosition.push_back( position[i] );
}
// 비어있다면 데이터에 문제가 있음.
assert( !vPosition.empty() );
DeathmatchInfo *deathmatchInfo = new DeathmatchInfo( id, deathmatch_type, min_level, max_level, vPosition );
g_vDeathmatchInfo.push_back( deathmatchInfo );
}
const ArPosition GameContent::GetDeathmatchPosition( const int level, const int deathmatch_type )
{
std::vector< DeathmatchInfo * >::iterator it;
for( it = g_vDeathmatchInfo.begin(); it != g_vDeathmatchInfo.end(); ++it )
{
if( (*it)->deathmatch_type == deathmatch_type && (*it)->min_level <= level && level <= (*it)->max_level )
{
break;
}
}
int index = XRandom(0, static_cast< int >((*it)->m_vPosition.size()-1));
return (*it)->m_vPosition[index];
}
const int GameContent::GetDeathmatchType( const ArPosition pos )
{
int channel_id = ChannelManager::GetChannelId( pos.x, pos.y );
if( ( channel_id >= TYPE_DEATHMATCH_1_RANKED && channel_id <= TYPE_DEATHMATCH_4_RANKED ) || ( channel_id >= TYPE_DEATHMATCH_5_RANKED && channel_id <= TYPE_DEATHMATCH_7_RANKED ) )
return 1;
else if( channel_id == TYPE_DEATHMATCH_FREED )
return 2;
return -1;
}
void GameContent::RegisterCreatureEnhanceInfo( const short enhance_level, const float stat_amplify, const short card_durability, const short slot_amount, const short jp_addition )
{
// 데이터는 순차/연속적이어야 한다.
assert( g_vCreatureEnhanceInfo.size() == enhance_level );
CreatureEnhanceInfo *creatureEnhanceInfo = new CreatureEnhanceInfo( stat_amplify, card_durability, slot_amount, jp_addition );
g_vCreatureEnhanceInfo.push_back( creatureEnhanceInfo );
}
const CreatureEnhanceInfo *GameContent::GetCreatureEnhanceInfo( const short enhance_level )
{
assert( enhance_level >= 0 && static_cast< size_t >( enhance_level ) < g_vCreatureEnhanceInfo.size() );
return g_vCreatureEnhanceInfo[ enhance_level ];
}
void GameContent::RegisterCreatureFarmInfo( const char rate, const char form, const char enhance_level, const char ticket_count )
{
CreatureFarmInfo *creatureFarmInfo = new CreatureFarmInfo( rate, form, enhance_level, ticket_count );
g_vCreatureFarmInfo.push_back( creatureFarmInfo );
}
const char GameContent::GetCreatureFarmTicketCount( const char rate, const char form, const char enhance_level )
{
std::vector< CreatureFarmInfo * >::const_iterator it;
for( it = g_vCreatureFarmInfo.begin(); it != g_vCreatureFarmInfo.end(); ++it )
{
if( (*it)->rate == rate &&
(*it)->form == form &&
(*it)->enhance_level == enhance_level )
return (*it)->ticket_count;
}
assert( 0 && "Creature Farm Info ID" );
return 0;
}
const int GameContent::GetRandomMonsterID( const int group_id )
{
if( group_id < 1 ) return 0;
for( std::vector< RANDOM_MONSTER_GROUP_INFO >::const_iterator it = g_vRandomMonsterGroupInfo.begin(); it != g_vRandomMonsterGroupInfo.end(); ++it )
{
if( (*it).group_id == group_id )
{
int nCum = 0;
int nKey = XRandom( 1, 100000000 );
for( std::vector< std::pair< int, int > >::const_iterator mIt = (*it).monster_list.begin(); mIt != (*it).monster_list.end(); ++mIt )
{
// 꽝
if( !mIt->first ) return 0;
nCum += mIt->second;
if( nKey > nCum ) continue;
return mIt->first;
}
}
}
return 0;
}
void GameContent::RegisterMonsterCreatureInfo( MonsterCreatureInfo & monster_creater )
{
std::vector<MonsterCreatureInfo>::iterator it;
bool find = false;
for( it = g_vMonsterCreatureInfo.begin() ; it != g_vMonsterCreatureInfo.end() ; ++it )
{
if( (*it).nSid == monster_creater.nSid )
{
find = true;
(*it).nSid = monster_creater.nSid;
(*it).nUseCode = monster_creater.nUseCode;
for( int i = 0 ; i < GameRule::MAX_MONSTER_CREATURE_COUNT ; ++i )
{
(*it).arSummonCode[ i ] = monster_creater.arSummonCode[ i ];
}
break;
}
}
if( !find )
{
MonsterCreatureInfo info;
info.nSid = monster_creater.nSid;
info.nUseCode = monster_creater.nUseCode;
for( int i = 0 ; i < GameRule::MAX_MONSTER_CREATURE_COUNT ; ++i )
{
info.arSummonCode[ i ] = monster_creater.arSummonCode[ i ];
}
g_vMonsterCreatureInfo.push_back( info );
}
}
const int GameContent::GetMonsterCreatureTameCode( int _nMonsterCreatureCode )
{
if( !g_vMonsterCreatureInfo.size() )
{
return 0;
}
std::vector<MonsterCreatureInfo>::iterator it;
for( it = g_vMonsterCreatureInfo.begin() ; it != g_vMonsterCreatureInfo.end() ; ++it )
{
if( (*it).nSid == _nMonsterCreatureCode )
{
int nSummonGroupIndex = XRandom( 1, (*it).nUseCode );
nSummonGroupIndex = nSummonGroupIndex - 1;
if( GameRule::MAX_MONSTER_CREATURE_COUNT < nSummonGroupIndex )
{
// 배열의 Index 검증. Overflow Error 방지
return 0;
}
return (*it).arSummonCode[ nSummonGroupIndex ];
}
}
return 0;
}
// 소울 크리처와 일반 크리처가 데이터로 비교 불가능하기 때문에 몬스터 테이밍 정보를 간접적으로 참고하여 소울크리처인지 판단해야 한다.
// 따라서 nSummonCode에 "기본형" 소환수 코드를 줘야 제대로 판별이 가능하다.
// 진화된 소울 크리처들은 이 함수의 사용을 위해서는 PrevJobId를 조사하여 기본형 소환수 코드로 넘길 것.
// 함수 이름이 IsSoulCreature이므로 오해를 불러 일으킬 수 있으니 시간이 될 때 소울 크리처의 데이터 구조를 수정해야 할 듯.
bool GameContent::IsSoulCreature( int nSummonCode )
{
std::vector< MonsterCreatureInfo >::iterator it = g_vMonsterCreatureInfo.begin();
for( ; it != g_vMonsterCreatureInfo.end() ; ++it )
{
for( int i = 0 ; i < (*it).nUseCode ; ++i )
{
if( nSummonCode == (*it).arSummonCode[ i ] )
return true;
}
}
return false;
}
void GameContent::RegisterSummonRandomSkillInfo( SummonRandomSkillInfo & summon_random_skill )
{
std::vector<SummonRandomSkillInfo>::iterator it;
bool find = false;
for( it = g_vSummonRandomSkillInfo.begin() ; it != g_vSummonRandomSkillInfo.end() ; ++it )
{
if( (*it).nSid == summon_random_skill.nSid )
{
find = true;
(*it).nSid = summon_random_skill.nSid;
(*it).nUseCode = summon_random_skill.nUseCode;
for( int i = 0 ; i < GameRule::MAX_RANDOM_SKILL_COUNT ; ++i )
{
(*it).arSummonSkillID[ i ] = summon_random_skill.arSummonSkillID[ i ];
}
break;
}
}
if( !find )
{
SummonRandomSkillInfo info;
info.nSid = summon_random_skill.nSid;
info.nUseCode = summon_random_skill.nUseCode;
for( int i = 0 ; i < GameRule::MAX_RANDOM_SKILL_COUNT ; ++i )
{
info.arSummonSkillID[ i ] = summon_random_skill.arSummonSkillID[ i ];
}
g_vSummonRandomSkillInfo.push_back( info );
}
}
const int GameContent::GetSummonRandomSkillID( int _nRandomSkillGroupID )
{
if( !g_vSummonRandomSkillInfo.size() )
{
return 0;
}
std::vector<SummonRandomSkillInfo>::iterator it;
for( it = g_vSummonRandomSkillInfo.begin() ; it != g_vSummonRandomSkillInfo.end() ; ++it )
{
if( (*it).nSid == _nRandomSkillGroupID )
{
int nRandomSkillIndex = XRandom( 1, (*it).nUseCode );
nRandomSkillIndex = nRandomSkillIndex - 1;
if( GameRule::MAX_RANDOM_SKILL_COUNT <= nRandomSkillIndex )
{
// 배열의 Index 검증. Overflow Error 방지
return 0;
}
return (*it).arSummonSkillID[ nRandomSkillIndex ];
}
}
return 0;
}
const int GameContent::GetRandomSkillIDFromGroupID( int nGroupID, int nIndex )
{
if( nIndex > GameRule::MAX_RANDOM_SKILL_COUNT-1 )
return NULL;
for( std::vector<SummonRandomSkillInfo>::iterator it = g_vSummonRandomSkillInfo.begin() ; it != g_vSummonRandomSkillInfo.end() ; ++it )
{
if( (*it).nSid == nGroupID )
{
return (*it).arSummonSkillID[ nIndex ];
}
}
return NULL;
}
const int GameContent::GetSkillTreeGroupID( StructCreature *_pCreature, int _skill_id )
{
int nMacCnt = _pCreature->GetJobDepth();
for( int nCnt = 0 ; nCnt <= nMacCnt ; ++nCnt )
{
int nJobID = 0;
if( nCnt < nMacCnt )
{
nJobID = _pCreature->GetPrevJobId( nCnt );
}
else
{
nJobID = _pCreature->GetJobId();
}
if( _pCreature->IsPlayer() )
{
std::vector< SkillTree > *vSkillTree = NULL;
vSkillTree = GameContent::GetJobInfo( nJobID )->skill_tree;
if( vSkillTree )
{
for( std::vector< SkillTree >::iterator it = (*vSkillTree).begin() ; it != (*vSkillTree).end() ; ++it )
{
if( (*it).skill_id == _skill_id )
{
return (*it).skill_group_id;
}
}
}
}
else if( _pCreature->IsSummon() )
{
std::vector< SkillTree > **vSkillTree = NULL;
vSkillTree = GameContent::GetSummonInfo( nJobID )->skill_tree;
for( int i = 0; i < GameRule::MAX_SUMMON_SKILL_TREE ; ++i )
{
if( !vSkillTree[i] )
continue;
for( std::vector< SkillTree >::iterator it = (*vSkillTree[i]).begin() ; it != (*vSkillTree[i]).end() ; ++it )
if( (*it).skill_id == _skill_id )
{
return (*it).skill_group_id;
}
}
}
}
assert( 0 && "SkillTree Resource Error" );
return 0;
}