// 코드 정리 해야함. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ErrorCode/ErrorCode.h" #include "LogClient/LogClient.h" #include "GameMessage.h" #include "SendMessage.h" #include "StructCreature.h" #include "StructPlayer.h" #include "StructSummon.h" #include "StructPet.h" #include "StructItem.h" #include "StructSkill.h" #include "StructMonster.h" #include "Constant.h" #include "GameAllocator.h" #include "StructNPC.h" #include "LuaVM.h" #include "Extern.h" #include "DB_Commands.h" // #include "DB_CheckSecurityNo.h" 2009. 8. 11 floyd 보안비밀번호 2차 수정 #include "GameContent.h" #include "GameProc.h" #include "PartyManager.h" #include "GuildManager.h" #include "StructWorldLocation.h" #include "DungeonManager.h" #include "HuntaholicManager.h" #include "InstanceDungeonManager.h" #include "BattleArenaManager.h" #include "MixManager.h" #include "ChannelManager.h" #include "AuctionManager.h" #include "CompeteManager.h" #include "RankingManager.h" #include "TimeUtil.h" #include "XSecuritySolutionManager.h" #include "NetworkReceiverPerformanceTracker.h" #include "../Community/PartyMatchingManager.h" // Requester 기능을 사용해야 할만한 나쁜 놈들 줄 파일 빌드할 때 사용하자 //#define USE_REQUESTER #ifdef USE_REQUESTER #include #endif extern volatile LONG g_nRecvMessageCount; extern volatile LONG g_nRecvMessageByte; extern IStreamSocketConnection * g_pAuthConnection; extern IStreamSocketConnection * g_pUploadConnection; extern IStreamSocketConnection * g_pCoordinatorConnection; __declspec( thread ) StructPlayer * s_pCurrentPlayer = NULL; static XCriticalSection s_AuthAccountCS( "s_AuthAccountCS" ); static KHash< IStreamSocketConnection *, hashPr_string_nocase > s_hsAuthAccount; extern XCriticalSection g_ConnectionTagLock; extern __declspec( thread ) XSEH::THREAD_INFO s_ThreadInfo; extern KHash< char, hashPr_mod_int > g_hsDonationRewardDropGroup; StructPlayer *GetCurrentThreadPlayer() { return s_pCurrentPlayer; } inline bool isValidTarget( StructPlayer *pClient, GameObject *pObj ) { return IsVisibleRegion( pClient->GetRX(), pClient->GetRY(), pObj->GetRX(), pObj->GetRY() ); } //#define _NOAUTH void onLogin( IStreamSocketConnection * pConnection, TS_LOGIN* pMsg ) { // DB 처리 중인 사항이 있을 경우 로그인 불가(캐릭터 삭제 요청 직후 로그인 문제) if( static_cast< XIOCPConnection * >( pConnection )->GetVar() ) return; if( GameRule::bUseLoginLogoutDebug ) { _ctprint( "onLogin (%s)\n", pMsg->szName ); FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "LoginLogout_Debug", "onLogin (%s)", pMsg->szName ); } THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); #ifndef _NOAUTH if( !pTag->szAccountName[0] ) { _cprint( "Illegal login try detected :%s\n", pMsg->szName ); pConnection->Close(); return; } #endif // _NOAUTH std::vector< std::string >::iterator itPlayerName; for( itPlayerName = pTag->vCharacterNameList.begin(); itPlayerName != pTag->vCharacterNameList.end(); ++itPlayerName ) { if( !_stricmp( (*itPlayerName).c_str(), pMsg->szName ) ) { break; } } if( itPlayerName == pTag->vCharacterNameList.end() ) { FILELOG( "Login try with a character which is beloned to another account detected :%s [%s]", pMsg->szName, pTag->szAccountName ); _cprint( "Login try with a character which is beloned to another account detected :%s [%s]\n", pMsg->szName, pTag->szAccountName ); pConnection->Close(); return; } AR_HANDLE hPlayer = StructPlayer::FindPlayer( pMsg->szName ); if( hPlayer ) { FILELOG( "Login try with a character which is already logged in :%s [%s]", pMsg->szName, pTag->szAccountName ); _cprint( "Login try with a character which is already logged in :%s [%s]\n", pMsg->szName, pTag->szAccountName ); //pConnection->Close(); StructPlayer::iterator pit = StructPlayer::get( hPlayer ); StructPlayer *pPlayer = *pit; // pPlayer는 있는데 pConnection만 NULL인 경우가 발생(2006-08-18 16:25) // onDisconnect 호출 전에 onLogin이 다시 들어온 경우로 추정. // pConnection도 존재해야만 Close 하도록 조건 추가. if( pPlayer ) { pPlayer->Save(); IStreamSocketConnection* pConn = pPlayer->pConnection; if( pPlayer->IsLogin() ) { pPlayer->LogoutNow( 1 ); } if( pConn && pConn->IsConnected() ) { SendDisconnectDesc( pConn, TS_SC_DISCONNECT_DESC::DISCONNECT_TYPE_ANOTHER_LOGIN ); pConn->Close(); } } } static_cast< XIOCPConnection* >( pConnection )->IncVar(); char szName[31]; s_strcpy( szName, _countof( szName ), pMsg->szName ); // load character pTag->pPlayer = StructPlayer::AllocPlayer(); pTag->pPlayer->pConnection = pConnection; pTag->pPlayer->SetAccount( pTag->nAccountID, pTag->szAccountName ); // 기존에 있던 컨넥션에서 이미 창고 보안 비밀번호를 입력했고 이후에 입력하지 않게 되어 있었더라도 // (game.use_storage_security == 1, game.check_storage_security_always == 0) 캐릭터 선택만 다시 한 경우 // 다시 입력하게 보안 비밀번호 입력 상태를 초기화 함 pTag->bStorageSecurityCheck = false; // _oprint( "ALLOC PLAYER 1 : 0x%08X\n", pTag->pPlayer ); // 클라에서 알고 있어야 할 Url들을 전송 // pConnection이 세팅된 후에 사용해야 함(SetConnection 함수가 호출된 후에) SendUrlList( pTag->pPlayer ); DB().Push( new DB_Login( pMsg->szName, pTag->nAccountID, pTag->pPlayer, pTag->nPCBangMode, pTag->nEventCode, pTag->nAge, pTag->nContinuousPlayTime, pTag->nContinuousLogoutTime, pConnection ) ); } void onRequestLogoutTimer( StructPlayer * pClient, int nMsg ) { if( GameRule::bUseLoginLogoutDebug ) { _ctprint( "onRequestLogoutTimer (%s, %s)\n", pClient->GetAccountName(), pClient->GetName() ); FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "LoginLogout_Debug", "onRequestLogoutTimer (%s, %s)", pClient->GetAccountName(), pClient->GetName() ); } // 로비로 돌아가기/로그아웃 요청이 왔을 때 즉시 로그아웃 불가능 상태일 경우 // 타이머 요청 시점부터 로그아웃 가능 시간까지 불가능 처리를 위해 기록 // CanLogoutNow 체크 이전에 설정해야 함 pClient->SetLastLogoutRequestedTime( GetArTime() ); if ( pClient->CanLogoutNow() ) SendResult( pClient, nMsg, RESULT_SUCCESS, 0 ); else { int nLogoutTime = GameRule::nLogoutTimer / 100; SendResult( pClient, nMsg, RESULT_COOL_TIME, nLogoutTime ); } } void onLogout( StructPlayer * pClient, TS_CS_LOGOUT* pMsg ) { if( GameRule::bUseLoginLogoutDebug ) { _ctprint( "onLogout(%s, %s)\n", pClient->GetAccountName(), pClient->GetName() ); FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "LoginLogout_Debug", "onLogout(%s, %s)", pClient->GetAccountName(), pClient->GetName() ); } // 즉시 로그아웃 불가능할 경우 로그아웃 불가 if( !pClient->CanLogoutNow() ) return; // Credits to AziaMafia: offline market if (pClient->IsBoothOpen() && ENV().GetInt("game.offline_market_enabled", 0) ) { std::string textaccount; XStringUtil::Format(textaccount, "MarketSystem:OfflineMarketOnLogout('%s','%s')", pClient->GetAccountName(), pClient->GetName() ); LUA()->RunString(textaccount.c_str()); return; } if( pClient->GetBattleArenaID() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 특정 경기에 참여 상태일 경우에는 경기 이탈 if( pClient->GetBattleArenaID() ) BattleArenaManager::Instance().QuitGame( pClient, false, false, ALT_USER_REQUEST ); assert( !pClient->GetBattleArenaID() ); } pClient->LogoutNowWithAccount( 4 ); } void onHideEquipInfo( StructPlayer * pClient, TS_CS_HIDE_EQUIP_INFO * pMsg ) { if( pClient->GetHideEquipFlag() == pMsg->nHideEquipFlag ) return; // 어차피 패킷 처리 중인 StructPlayer 객체는 메모리에서는 delete되지 않으며, 딱히 값이 이상해도 별로 상관 없으므로 락 없이 세팅함 pClient->SetHideEquipFlag( pMsg->nHideEquipFlag ); TS_SC_HIDE_EQUIP_INFO msg; msg.hPlayer = pClient->GetHandle(); msg.nHideEquipFlag = pMsg->nHideEquipFlag; pClient->DBQuery( new DB_UpdateCharacterHideEquipFlag( pClient ) ); ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &msg ); } bool GetValidWayPoint( StructPlayer * pClient, StructCreature* pMObj, TS_MOVE_REQUEST* pMsg, std::vector< ArPosition >& vMoveInfo, ArPosition& startPos ) { ArPosition curPosFromServer = pMObj->GetCurrentPosition( GetArTime() ); ArPosition wayPoint( pMsg->x, pMsg->y ); // 1. 클라에서 보낸 현재 위치 좌표의 유효성 여부 검사 if( wayPoint.x < 0 || wayPoint.x > g_nMapWidth || wayPoint.y < 0 || wayPoint.y > g_nMapHeight ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return false; } // 2. 클라에서 보낸 현재 위치가 서버에서 알고있는 현재 위치로 부터 가시거리(43.75 미터) 밖이면 즐 if( curPosFromServer.GetDistance( wayPoint ) > GameRule::VISIBLE_RANGE ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return false; } if( pMObj->IsPlayer() ) { // 서버의 현재 위치에서 클라이언트의 현재 위치까지 갈 수 있는가? if( GameContent::CollisionToLine( curPosFromServer.x, curPosFromServer.y, wayPoint.x, wayPoint.y ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); #ifdef _DEBUG _cprint( "LineCollision Detected While Server Pos To Client Pos PlayerUID[%d] Server[%f,%f] Client[%f,%f]\n", pClient->GetPlayerUID(), curPosFromServer.x, curPosFromServer.y, wayPoint.x, wayPoint.y ); FILELOG( "LineCollision Detected While Server Pos To Client Pos PlayerUID[%d] Server[%f,%f] Client[%f,%f]", pClient->GetPlayerUID(), curPosFromServer.x, curPosFromServer.y, wayPoint.x, wayPoint.y ); #endif // 이동이 실패한다면 서버와 위치를 동기화 시켜 다시 시도하게 유도한다. // 서버에서 알고 있는 위치가 못가는 지역이라면 끼인다. if( GameContent::IsBlocked( curPosFromServer.x, curPosFromServer.y ) ) { #ifdef _DEBUG _cprint( "LineCollision (Server Pos To Client Pos) Detected And the User Got Stuck! - PlayerUID[%d] Pos[%f,%f]\n", pClient->GetPlayerUID(), curPosFromServer.x, curPosFromServer.y ); FILELOG( "LineCollision (Server Pos To Client Pos) Detected And the User Got Stuck! - PlayerUID[%d] Pos[%f,%f]", pClient->GetPlayerUID(), curPosFromServer.x, curPosFromServer.y ); #endif return false; } startPos = curPosFromServer; vMoveInfo.clear(); vMoveInfo.push_back( curPosFromServer ); return true; } } TS_MOVE_REQUEST::MOVE_INFO * pMoveInfo = reinterpret_cast< TS_MOVE_REQUEST::MOVE_INFO * >( pMsg + 1 ); for( unsigned short i = 0; i < pMsg->count; ++i, ++pMoveInfo ) { // 유효성 검사 // 3. 목표 이동 장소 좌표의 유효성 여부 검사 if( pMoveInfo->tx < 0 || pMoveInfo->tx > g_nMapWidth || pMoveInfo->ty < 0 || pMoveInfo->ty > g_nMapHeight ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return false; } if( pMObj->IsPlayer() ) { // Player라면 LineCollision 여부 검증 (2014-01-02, 던전만 체크에서 전 필드로 확대) if( GameContent::CollisionToLine( wayPoint.x, wayPoint.y, pMoveInfo->tx, pMoveInfo->ty ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); #ifdef _DEBUG _cprint( "LineCollision Detected While Pathfinding - %d/%d: PlayerUID[%d] Start[%f,%f] End[%f,%f]\n", i+1, pMsg->count, pClient->GetPlayerUID(), wayPoint.x, wayPoint.y, pMoveInfo->tx, pMoveInfo->ty ); FILELOG( "LineCollision Detected While Pathfinding - %d/%d: PlayerUID[%d] Start[%f,%f] End[%f,%f]", i+1, pMsg->count, pClient->GetPlayerUID(), wayPoint.x, wayPoint.y, pMoveInfo->tx, pMoveInfo->ty ); #endif // 이동이 실패한다면 서버와 위치를 동기화 시켜 다시 시도하게 유도한다. // 서버에서 알고 있는 위치가 못가는 지역이라면 끼인다. // CollisionToLine을 확인 해도 가는 도중의 좌표가 끼이는 경우가 있는 것 같다. // 일단 목적지는 끼이지 않을수 있으니 일단 가자. if( GameContent::IsBlocked( curPosFromServer.x, curPosFromServer.y ) ) { #ifdef _DEBUG _cprint( "LineCollision Detected And the User Got Stuck! - PlayerUID[%d] Pos[%f,%f]\n", pClient->GetPlayerUID(), curPosFromServer.x, curPosFromServer.y ); FILELOG( "LineCollision Detected And the User Got Stuck! - PlayerUID[%d] Pos[%f,%f]", pClient->GetPlayerUID(), curPosFromServer.x, curPosFromServer.y ); #endif return false; } startPos = curPosFromServer; vMoveInfo.clear(); vMoveInfo.push_back( curPosFromServer ); return true; } } else { // Pets cannot be directly controlled by the player, so their item pickup is not validated. // Creatures, however, are controlled with the mouse, so their coordinate validity is // considered more reliable than keyboard movement. // Even if they move into an unreachable area, there is usually not much they can actually do there. } wayPoint = ArPosition( pMoveInfo->tx, pMoveInfo->ty, 0 ); vMoveInfo.push_back( wayPoint ); } startPos = ArPosition( pMsg->x, pMsg->y ); return true; } void onMoveRequest( StructPlayer * pClient, TS_MOVE_REQUEST* pMsg ) { if( pClient->IsDead() || !pClient->IsInWorld() ) return; AR_TIME t = GetArTime(); #ifdef _QUAD_DEBUG _cprint( "MoveReq : %f, %f\n", pMsg->tx, pMsg->ty ); #endif // _QUAD_DEBUG // Client pathfinding triggered a lovely incident... and crashed the server. -_- if( pMsg->size != sizeof( TS_MOVE_REQUEST ) + pMsg->count * sizeof( TS_MOVE_REQUEST::MOVE_INFO ) ) { return; } StructCreature* pMObj = pClient; int speed = pClient->GetRealMoveSpeed(); if( pMsg->handle && pMsg->handle != pClient->GetHandle() ) { pMObj = pClient->GetSummon( pMsg->handle ); if( pMObj ) { // Handle summoned creature case if( pClient->IsRiding() && pClient->GetRideObject() == pMObj ) { pMObj = pClient; } else { speed = pMObj->GetRealMoveSpeed(); if (pMsg->speed_sync) { speed = pClient->GetRealMoveSpeed(); ArPosition pos(pMsg->x, pMsg->y); AR_UNIT distance = pos.GetDistance(pClient->GetPos()); if (distance >= GameRule::SUMMON_FOLLOWING_LIMIT_RANGE) { //SendResult(pClient, pMsg->id, RESULT_TOO_FAR); return; } else if (distance >= GameRule::SUMMON_FOLLOWING_SECOND_SPEED_UP_RANGE / 2 ) { speed = pClient->GetRealMoveSpeed() * 10 ; // 2 } else if (distance >= GameRule::SUMMON_FOLLOWING_FIRST_SPEED_UP_RANGE) { speed *= GameRule::SUMMON_FOLLOWING_FIRST_SPEED_UP_RATE; } } } } else { // Handle pet case pMObj = pClient->GetSummonedPet(); if( pMObj && pMObj->GetHandle() == pMsg->handle ) { speed = pClient->GetRealMoveSpeed(); ArPosition pos( pMsg->x, pMsg->y ); AR_UNIT distance = pos.GetDistance( pClient->GetPos() ); if( distance >= GameRule::PET_FOLLOWING_LIMIT_RANGE ) { SendResult( pClient, pMsg->id, RESULT_TOO_FAR ); return; } else if( distance >= GameRule::PET_FOLLOWING_HURRY_SPPED_RANGE ) { speed *= GameRule::PET_FOLLOWING_HURRY_SPEED_RATE; } else { speed *= GameRule::PET_FOLLOWING_COMMON_SPEED_RATE; } } } } if( pMObj == pClient && ( pClient->IsRiding() || pClient->HasRidingState() ) ) { speed = pClient->GetRealRidingSpeed(); } if( !pMObj ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } speed = speed < 1 ? 1 : speed; speed = speed > UCHAR_MAX ? UCHAR_MAX : speed; unsigned char moveSpeed = static_cast< unsigned char >( speed ); std::vector< ArPosition > vMoveInfo; ArPosition start_pos; if( GetValidWayPoint( pClient, pMObj, pMsg, vMoveInfo, start_pos ) == false ) { return; } if( vMoveInfo.size() < 1 ) return; if( !StructCreature::QuadTreeItem::IsCanAdd( vMoveInfo.back().x, vMoveInfo.back().y ) ) { SendResult( pClient, pMsg->id, RESULT_ALREADY_EXIST ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithSpecificRegion( pMObj, ::GetRegionX( start_pos.x ), ::GetRegionY( start_pos.y ) ) ); if( !pClient->IsInWorld() ) return; if( pMObj->IsAttacking() ) { pMObj->CancelAttack(); } // Restrict movement in Battle Arena: before match start, prevent leaving the starting area if( pClient->IsInBattleArena() ) { if( BattleArenaManager::Instance().ValidateMovableRoute( pClient->GetBattleArenaID(), pClient->GetBattleArenaInstanceNo(), pClient->GetPartyID(), pMObj->GetCurrentPosition( GetArTime() ), vMoveInfo ) ) { // If path adjustment leaves no movement points, movement is blocked if( vMoveInfo.empty() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } } } // Devildom if (pClient->GetLocationId() > 0 && pClient->IsInInstanceDungeon() && !vMoveInfo.empty()) { struct PositionCube { PositionCube(float x1, float x2, float y1, float y2) : fX1(x1), fX2(x2), fY1(y1), fY2(y2) {}; static int Orientation(ArPosition a, ArPosition b, ArPosition c) { float val = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); if (val == 0) return 0; return (val > 0) ? 1 : 2; } static bool OnSegment(ArPosition a, ArPosition b, ArPosition c) { return b.x <= std::max(a.x, c.x) && b.x >= std::min(a.x, c.x) && b.y <= std::max(a.y, c.y) && b.y >= std::min(a.y, c.y); } static bool DoLinesIntersect(ArPosition p1, ArPosition q1, ArPosition p2, ArPosition q2) { int o1 = Orientation(p1, q1, p2); int o2 = Orientation(p1, q1, q2); int o3 = Orientation(p2, q2, p1); int o4 = Orientation(p2, q2, q1); if (o1 != o2 && o3 != o4) return true; if (o1 == 0 && OnSegment(p1, p2, q1)) return true; if (o2 == 0 && OnSegment(p1, q2, q1)) return true; if (o3 == 0 && OnSegment(p2, p1, q2)) return true; if (o4 == 0 && OnSegment(p2, q1, q2)) return true; return false; } static bool IsInside(const PositionCube& cube, float x, float y) { return (x >= cube.fX1 && x <= cube.fX2 && y >= cube.fY1 && y <= cube.fY2); } static bool LineIntersectsRect(ArPosition p1, ArPosition p2, const PositionCube& cube) { ArPosition tl = ArPosition(cube.fX1, cube.fY1 ); ArPosition tr = ArPosition(cube.fX2, cube.fY1 ); ArPosition bl = ArPosition(cube.fX1, cube.fY2 ); ArPosition br = ArPosition(cube.fX2, cube.fY2 ); if (IsInside(cube, p1.x, p1.y) || IsInside(cube, p2.x, p2.y)) return true; if (DoLinesIntersect(p1, p2, tl, tr)) return true; if (DoLinesIntersect(p1, p2, tr, br)) return true; if (DoLinesIntersect(p1, p2, br, bl)) return true; if (DoLinesIntersect(p1, p2, bl, tl)) return true; return false; } float fX1, fX2, fY1, fY2; }; std::map CollisionMap; std::vector Vec = InstanceDungeonManager::Instance().findInstanceDungeonType(InstanceDungeonManager::Instance().findInstanceDungeonInfo(InstanceDungeonManager::Instance().GetInstanceDungeonID(pClient->GetPos())), pClient->GetLayer())->vRespawnedProp; for (int i = 0; i < Vec.size(); i++) { int fOffsetX = Vec.at(i)->GetFieldPropBase()->nCollisionX; int fOffsetY = Vec.at(i)->GetFieldPropBase()->nCollisionY; if (fOffsetX == 0 || fOffsetY == 0) break; ArPosition pos = ArPosition(Vec.at(i)->GetX(), Vec.at(i)->GetY()); int nLayer = (int)static_cast(Vec.at(i)->GetLayer()); float fScaleX = Vec.at(i)->GetScaleX(); float fScaleY = Vec.at(i)->GetScaleY(); float fRadians = Vec.at(i)->GetRotateZ(); int nPropID = Vec.at(i)->GetFieldPropBase()->nPropId; float fHalfX = fOffsetX * fScaleX; float fHalfY = fOffsetY * fScaleY; ArPosition Corners[4]; Corners[0] = ArPosition( -fHalfX, -fHalfY ); Corners[1] = ArPosition( fHalfX, -fHalfY ); Corners[2] = ArPosition( fHalfX, fHalfY ); Corners[3] = ArPosition( -fHalfX, fHalfY ); ArPosition RotatedCorners[4]; for (int cor = 0; cor < 4; ++cor) { float fX = Corners[cor].x; float fY = Corners[cor].y; RotatedCorners[cor].x = fX * cos(fRadians) - fY * sin(fRadians) + pos.x; RotatedCorners[cor].y = fX * sin(fRadians) + fY * cos(fRadians) + pos.y; } float fMinX = RotatedCorners[0].x; float fMaxX = RotatedCorners[0].x; float fMinY = RotatedCorners[0].y; float fMaxY = RotatedCorners[0].y; for (int cor = 1; cor < 4; ++cor) { fMinX = std::min(fMinX, RotatedCorners[cor].x); fMaxX = std::max(fMaxX, RotatedCorners[cor].x); fMinY = std::min(fMinY, RotatedCorners[cor].y); fMaxY = std::max(fMaxY, RotatedCorners[cor].y); } CollisionMap.insert( std::make_pair(nPropID, PositionCube(fMinX, fMaxX, fMinY, fMaxY)) ); } ArPosition ClientPos = pClient->GetPos(); ArPosition PointPos = ArPosition(vMoveInfo[0].GetX(), vMoveInfo[0].GetY()); bool bIsPathClear = true; for (std::map::const_iterator it = CollisionMap.begin(); it != CollisionMap.end(); ++it) { if (PositionCube::LineIntersectsRect(ClientPos, PointPos, it->second)) { bIsPathClear = false; } } if (bIsPathClear) { for (size_t i = 0; i + 1 < vMoveInfo.size(); ++i) { ArPosition p1 = ArPosition(vMoveInfo[i].GetX(), vMoveInfo[i].GetY() ); ArPosition p2 = ArPosition(vMoveInfo[i + 1].GetX(), vMoveInfo[i + 1].GetY() ); for (std::map::const_iterator it = CollisionMap.begin(); it != CollisionMap.end(); ++it) { if (PositionCube::LineIntersectsRect(p1, p2, it->second)) bIsPathClear = false; } } } if (!bIsPathClear) { vMoveInfo.clear(); vMoveInfo.push_back( pMObj->GetCurrentPosition( t ) ); SendResult(pClient, pMsg->id, RESULT_ACCESS_DENIED); } } // What the fuck is this shit?! // Fraun commented this cringe 9/15/2025 //if( pClient->IsInInstanceDungeon() ) //{ // int nInstanceID = InstanceDungeonManager::Instance().GetInstanceDungeonID( pMObj->GetCurrentPosition( GetArTime() ) ); // // for( std::vector< ArPosition >::iterator it = vMoveInfo.begin(); it != vMoveInfo.end(); ++it ) // { // std::string strScript, strReturn; // // XStringUtil::Format( strScript, "return dungeon_instance_check_moveable( %d, %f, %f )", nInstanceID, (*it).x, (*it).y ); // LUA()->RunString( strScript.c_str(), &strReturn ); // // if( !_stricmp( strReturn.c_str(), "false" ) ) // { // start_pos = start_pos; // // vMoveInfo.clear(); // vMoveInfo.push_back( pMObj->GetCurrentPosition( t ) ); // break; // } // } //} // What the fuck is this shit?! // Fraun commented this cringe 8/16/2025 //if (pClient->IsInWorld()) //{ // for (std::vector< ArPosition >::iterator it = vMoveInfo.begin(); it != vMoveInfo.end(); ++it) // { // std::string strScript, strReturn; // // XStringUtil::Format(strScript, "return fz_check_moveable( %f, %f )", (*it).x, (*it).y); // LUA()->RunString(strScript.c_str(), &strReturn); // // if (_stricmp(strReturn.c_str(), "true")) //if (!_stricmp(strReturn.c_str(), "false")) // { // start_pos = start_pos; // vMoveInfo.clear(); // vMoveInfo.push_back(pMObj->GetCurrentPosition(t)); // break; // } // } //} if( pMObj->GetMovableTime() > t ) { if( !pMObj->SetPendingMove( vMoveInfo, moveSpeed ) ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); } return; } if( !pMObj->IsActable() || !pMObj->IsInWorld() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } if( pClient == pMObj && pClient->IsSitDown() ) { pClient->StandUp(); BroadcastStatusMessage( pClient ); } if( !pMObj->IsMovable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } ArPosition pos = pMObj->GetCurrentPosition( GetArTime() ); ArPosition targetPos = vMoveInfo.back(); extern float g_fMapLength; // 이동 거리가 맵의 1/5 이상이며, 무저갱에서부터 출발했을 경우는 이동만 금지시킴. if( pMObj == pClient && pos.GetDistance( start_pos ) > ( g_fMapLength / 5 ) ) { if( pClient->IsAutoUsed() && pClient->GetLocationId() == StructWorldLocation::LOCATION_ID_ABADON ) return; } if( vMoveInfo.size() && pMObj->GetCurrentPosition( t ).GetDistance( targetPos ) > g_fMapLength ) { return; } // _oprint( "%s CS( %f %f ) CC( %f %f ) -> ( %f %f )\n", pClient->GetName(), pMObj->GetCurrentPosition( t ).x, pMObj->GetCurrentPosition( t ).y, pMsg->x, pMsg->y, targetPos.x, targetPos.y ); if( pMObj->HasPendingMove() ) { pMObj->ReleasePendingMove(); } if( pMObj == pClient && pClient->IsWarpEnded() ) { pClient->SetWarpEnded( false ); } ArcadiaServer::Instance().SetMultipleMove( pMObj, start_pos, vMoveInfo, moveSpeed , true, t ); if( pMObj->IsPlayer() ) { pClient->SetMoveReq( true ); } if( pMObj->IsPlayer() && static_cast< StructPlayer * >( pMObj )->IsRiding() ) { ArcadiaServer::Instance().SetMultipleMove( static_cast< StructPlayer * >( pMObj )->GetRideObject(), start_pos, vMoveInfo, moveSpeed , true, t ); } if( pMObj->IsSummon() ) { ArcadiaServer::Instance().SetObjectPriority( pMObj, ArSchedulerObject::UPDATE_PRIORITY_NORMAL ); } } void onTimeSync( StructPlayer * pClient, TS_TIMESYNC* pMsg ) { #ifdef TIME_DEBUG _cprint( "RCV : TM_TIMESYNC (%d)\n", pMsg->time ); #endif pClient->m_TS.onEcho( GetArTime() - pMsg->time ); if( pClient->m_TS.GetTestCount() < 4 ) { SendTimeSync( pClient ); return; } #ifdef TIME_DEBUG _cprint( " AVERAGE TIME GAP : %d\n", pClient->m_TS.GetInterval() ); #endif TS_SET_TIME msg; msg.gap = pClient->m_TS.GetInterval(); PendMessage( pClient, &msg ); } void onGameTime( StructPlayer * pClient ) { SendGameTime( pClient ); } void onPutonCard( StructPlayer * pClient, TS_CS_PUTON_CARD* pMsg ) { if( !pClient->IsItemWearable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } StructItem * pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem ) { FILELOG( "onPutonItem() : item not found (%d)", pMsg->item_handle ); _cprint( "onPutonItem() : item not found (%d)\n", pMsg->item_handle ); SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } if( !pClient->FindItem( pItem->GetItemUID() ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( !pClient->PutOnBelt( pMsg->position, pItem ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } pClient->CalculateStat(); SendBeltSlotInfo( pClient ); } void onPutoffCard( StructPlayer * pClient, TS_CS_PUTOFF_CARD* pMsg ) { if( !pClient->IsItemWearable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } StructItem *pItem = pClient->GetBeltSlotCardAt( pMsg->position ); if( pItem && pClient->GetMainSummon() ) { if( pItem->GetItemCode() == 307040 || pItem->GetItemCode() == 307041 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->PutOffBelt( pMsg->position ); pClient->CalculateStat(); SendBeltSlotInfo( pClient ); } void onPutonItemSet( StructPlayer * pClient, TS_CS_PUTON_ITEM_SET* pMsg ) { if( !pClient->IsItemWearable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); for( int i = 0; i < ItemBase::MAX_ITEM_WEAR; ++i ) { pClient->Putoff( static_cast< ItemBase::ItemWearType >( i ) ); if( pMsg->handle[i] ) { StructItem *pItem = StructItem::FindItem( pMsg->handle[i] ); if( pItem ) { pClient->Puton( static_cast< ItemBase::ItemWearType >( i ), pItem ); //아래꺼 보내면 두번 날리는 셈. //SendItemWearInfoMessage( pClient, pClient, pItem ); } } } pClient->CalculateStat(); // 방송 TS_WEAR_INFO msg; GetWearMsg( pClient, msg ); ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &msg ); } void onPutonItem( StructPlayer * pClient, TS_CS_PUTON_ITEM* pMsg ) { if( !pClient->IsItemWearable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } StructItem * pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem ) { FILELOG( "onPutonItem() : item not found (%d)", pMsg->item_handle ); _cprint( "onPutonItem() : item not found (%d)\n", pMsg->item_handle ); SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } if( !pItem->IsWearable() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } StructCreature::iterator it = StructCreature::get( pMsg->target_handle ); StructCreature *pTarget = *it; ArcadiaLock lock( -1 ); bool bLocked = false; if( !pTarget ) { pTarget = pClient; } else if( pTarget->IsInWorld() ) { lock = ArcadiaServer::Instance().LockObjects( pClient, pTarget ); bLocked = true; // 락을 건 이후에 한 번 더 확인하여 월드에서 사라졌으면 락을 해제한다. if( !pTarget->IsInWorld() ) { ArcadiaServer::Instance().UnLock( &lock ); bLocked = false; } } // 최종적으로 걸린 락이 없다면 pTarget이 존재하지 않거나 월드에 없는 경우이다. // pTarget이 월드에 없더라도 클라이언트에서 온 핸들을 검증하기 위해 최소한의 락이 필요하다. // 따라서 두 경우 모두 pClient 중심으로 락을 건다. if( !bLocked ) { lock = ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ); bLocked = true; } ARCADIA_LOCK( lock ); if( !pClient->FindItem( pItem->GetItemUID() ) || ( pClient != pTarget && !pClient->GetSummon( pMsg->target_handle ) ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } unsigned short nResult = pTarget->Puton( static_cast< ItemBase::ItemWearType >( pMsg->position ), pItem ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nResult ); return; } pTarget->CalculateStat(); // 소환수의 경우 주인 무게 스텟 관련 메시지 전달이 위에서 안되므로 따로 주인의 스탯 정보만 전달해 준다. (무게메시지) if( pTarget->IsSummon() ) { SendStatInfo( pClient, pClient ); } SendResult( pClient, pMsg->id, RESULT_SUCCESS ); if( pTarget->IsPlayer() ) { // 방송 TS_WEAR_INFO msg; GetWearMsg( pClient, msg ); ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &msg ); } //Puton안에서 모두 처리 //SendItemWearInfoMessage( pClient, pTarget, pItem ); } void onPutoffItem( StructPlayer * pClient, TS_CS_PUTOFF_ITEM* pMsg ) { if( !pClient->IsItemWearable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } StructCreature::iterator it = StructCreature::get( pMsg->target_handle ); StructCreature *pTarget = *it; ArcadiaLock lock( -1 ); bool bLocked = false; if( !pTarget ) { pTarget = pClient; } else if( pTarget->IsInWorld() ) { lock = ArcadiaServer::Instance().LockObjects( pClient, pTarget ); bLocked = true; // 락을 건 이후에 한 번 더 확인하여 월드에서 사라졌으면 락을 해제한다. if( !pTarget->IsInWorld() ) { ArcadiaServer::Instance().UnLock( &lock ); bLocked = false; } } // 최종적으로 걸린 락이 없다면 pTarget이 존재하지 않거나 월드에 없는 경우이다. // pTarget이 월드에 없더라도 클라이언트에서 온 핸들을 검증하기 위해 최소한의 락이 필요하다. // 따라서 두 경우 모두 pClient 중심으로 락을 건다. if( !bLocked ) { lock = ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ); bLocked = true; } ARCADIA_LOCK( lock ); if( pClient != pTarget && !pClient->GetSummon( pMsg->target_handle ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } StructItem *pItem = pTarget->GetWearedItemByAbsolutePos( static_cast< ItemBase::ItemWearType >( pMsg->position ) ); if( !pItem ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } unsigned short nRet = RESULT_SUCCESS; if( pTarget->IsSummon() ) { // 크리처 전용 아이템은 같은 그룹에 두 개 이상의 아이템을 장비할 수 있으며 장착 위치가 고정되어있다. if( pItem->GetWearType() == ItemBase::WEAR_SUMMON_ONLY ) nRet = pTarget->Putoff( static_cast< ItemBase::ItemWearType >( pMsg->position ) ); else nRet = pTarget->Putoff( pItem->GetWearType() ); } else { nRet = pTarget->Putoff( static_cast< ItemBase::ItemWearType >( pMsg->position ) ); } pTarget->CalculateStat(); // 소환수의 경우 주인 무게 스텟 관련 메시지 전달이 위에서 안되므로 따로 주인의 스탯 정보만 전달해 준다. (무게메시지) if( pTarget->IsSummon() ) { SendStatInfo( pClient, pClient ); } SendResult( pClient, pMsg->id, nRet ); // 스왑용 슬롯에 임의로 장비하는 것은 불가능하나, 인벤토리의 아이템을 클릭하여 해제하는 것은 가능하다. 다만, 장비 방송은 필요없다. if( nRet == RESULT_SUCCESS && pTarget->IsPlayer() && pMsg->position < ItemBase::MAX_ITEM_WEAR ) { // 벗은거 방송 TS_WEAR_INFO msg; GetWearMsg( pClient, msg ); ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &msg ); } //Putoff안에서 모두 처리 //SendItemWearInfoMessage( pClient, pTarget, pItem ); } void onSwapEquip( StructPlayer * pClient, TS_CS_SWAP_EQUIP * pMsg ) { if( !pClient->IsItemWearable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructItem *pWeapon = pClient->GetWearedItem( ItemBase::WEAR_WEAPON ); StructItem *pShield = pClient->GetWearedItem( ItemBase::WEAR_SHIELD ); StructItem *pDecoWeapon = pClient->GetWearedItem( ItemBase::WEAR_DECO_WEAPON ); StructItem *pDecoShield = pClient->GetWearedItem( ItemBase::WEAR_DECO_SHIELD ); StructItem *pSpareWeapon = pClient->GetWearedItem( ItemBase::WEAR_SPARE_WEAPON ); StructItem *pSpareShield = pClient->GetWearedItem( ItemBase::WEAR_SPARE_SHIELD ); StructItem *pSpareDecoWeapon = pClient->GetWearedItem( ItemBase::WEAR_SPARE_DECO_WEAPON ); StructItem *pSpareDecoShield = pClient->GetWearedItem( ItemBase::WEAR_SPARE_DECO_SHIELD ); // Equipment must be unequipped in reverse order, and equipping should be done in the opposite order pClient->Putoff( ItemBase::WEAR_DECO_SHIELD ); pClient->Putoff( ItemBase::WEAR_DECO_WEAPON ); pClient->Putoff( ItemBase::WEAR_SHIELD ); pClient->Putoff( ItemBase::WEAR_WEAPON ); pClient->Putoff( ItemBase::WEAR_SPARE_DECO_SHIELD ); pClient->Putoff( ItemBase::WEAR_SPARE_DECO_WEAPON ); pClient->Putoff( ItemBase::WEAR_SPARE_SHIELD ); pClient->Putoff( ItemBase::WEAR_SPARE_WEAPON ); if( pWeapon ) pClient->Puton( ItemBase::WEAR_SPARE_WEAPON, pWeapon ); if( pShield ) pClient->Puton( ItemBase::WEAR_SPARE_SHIELD, pShield ); if( pDecoWeapon ) pClient->Puton( ItemBase::WEAR_SPARE_DECO_WEAPON, pDecoWeapon ); if( pDecoShield ) pClient->Puton( ItemBase::WEAR_SPARE_DECO_SHIELD, pDecoShield ); if( pSpareWeapon ) pClient->Puton( ItemBase::WEAR_WEAPON, pSpareWeapon ); if( pSpareShield ) pClient->Puton( ItemBase::WEAR_SHIELD, pSpareShield ); if( pSpareDecoWeapon ) pClient->Puton( ItemBase::WEAR_DECO_WEAPON, pSpareDecoWeapon ); if( pSpareDecoShield ) pClient->Puton( ItemBase::WEAR_DECO_SHIELD, pSpareDecoShield ); pClient->CalculateStat(); // Broadcast TS_WEAR_INFO msg; GetWearMsg( pClient, msg ); ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &msg ); } void onRegionUpdate( StructPlayer * pClient, TS_REGION_UPDATE* pMsg ) { #ifdef MOVE_DEBUG _cprint( "RCV : TM_CS_REGION_UPDATE\n" ); #endif pClient->DecRegionUpdateNeedCount(); AR_TIME t = GetArTime(); ArPosition pos = pClient->GetCurrentPosition( t ); ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithSpecificRegion( pClient, GetRegionX( pMsg->x ), GetRegionY( pMsg->y ) ) ); if( !pClient->IsInWorld() ) return; if( pClient->GetRideObject() ) { ArcadiaServer::Instance().onRegionChange( pClient->GetRideObject(), pMsg->update_time, pMsg->bIsStopMessage ); } //_oprint( "%s RegionUpdate %d %d\n", pClient->GetName(), pMsg->update_time, pMsg->bIsStopMessage ); if( pMsg->bIsStopMessage ) { pClient->SetMoveReq( false ); //_oprint( "%s Arrived.\n", pClient->GetName() ); } ArcadiaServer::Instance().onRegionChange( pClient, pMsg->update_time, pMsg->bIsStopMessage ); } void onAttackRequest( StructPlayer * pClient, TS_ATTACK_REQUEST* pMsg ) { if( pClient->IsDead() ) return; StructCreature *pAttacker = pClient; //AziaMafia log GS //_cprint("onAttackRequest : %s\n", pAttacker->GetName()); if( pMsg->handle != pClient->GetHandle() ) pAttacker = pClient->GetSummon( pMsg->handle ); if( !pAttacker ) { SendCantAttackMsg( pClient, pMsg->handle, pMsg->target_handle, RESULT_NOT_OWN ); return; } { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // _oprint( "%s ATTACK (%s) (%d) (%d)\n", pMsg->target_handle ? "START" : "END", pClient->GetName(), pMsg->target_handle, GetArTime() ); if( !pMsg->target_handle ) { if( pAttacker->IsAttacking() ) pAttacker->CancelAttack(); return; } StructCreature::iterator itTarget = StructCreature::get( pMsg->target_handle ); if( !(*itTarget) ) { if( pAttacker->IsAttacking() ) pAttacker->EndAttack(); else SendCantAttackMsg( pClient, pMsg->handle, pMsg->target_handle, RESULT_NOT_EXIST ); return; } if( !pAttacker->IsEnemy( (*itTarget) ) ) { if( pAttacker->IsAttacking() ) pAttacker->EndAttack(); else SendCantAttackMsg( pClient, pMsg->handle, pMsg->target_handle, RESULT_NOT_ACTABLE ); return; } #ifdef _alucard if( ( pAttacker->IsUsingBow() || pAttacker->IsUsingCrossBow() ) && pAttacker->IsPlayer() ) #else if( pAttacker->IsUsingBow() && pAttacker->IsPlayer() ) #endif { if( pAttacker->GetBulletCount() < 1 ) { SendCantAttackMsg( pClient, pMsg->handle, pMsg->target_handle, RESULT_NOT_ENOUGH_BULLET ); return; } } if( pClient == pAttacker && pClient->IsSitDown() ) { pClient->StandUp(); BroadcastStatusMessage( pClient ); } if( !pAttacker->IsAttackable() ) { SendCantAttackMsg( pClient, pMsg->handle, pMsg->target_handle, RESULT_ACCESS_DENIED ); return; } // AziaMafia FIX ZENTRIX if (pAttacker->IsSummon()) { StructPlayer* pPlayer = static_cast(pAttacker)->GetMaster(); StructState::StateCode nCode = static_cast(201085); if (pPlayer->GetState(nCode)) { SendCantAttackMsg(pClient, pMsg->handle, pMsg->target_handle, RESULT_ACCESS_DENIED); return; } if (pPlayer->IsRiding() || pPlayer->HasRidingState() ) // || !pPlayer->IsMountable(true) { SendCantAttackMsg(pClient, pMsg->handle, pMsg->target_handle, RESULT_ACCESS_DENIED); return; } } // AziaMafia FIN FIX ZENTRIX } pAttacker->StartAttack( pMsg->target_handle ); return; } void onDropItem( StructPlayer * pClient, TS_CS_DROP_ITEM* pMsg ) { __int64 count = pMsg->count; // 행동불가라면 KIN if( !pClient->IsActable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } if( !count ) { if( GameRule::bUseAutoJail ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->AddState( StructState::NEMESIS_FOR_AUTO, pClient->GetHandle(), 12, GetArTime(), GetArTime() + 864000000 ); pClient->SetAutoUsed(); pClient->Save( true ); } LOG::Log11N4S( LM_AUTO_USER_CHECKED, pClient->GetAccountID(), pClient->GetSID(), pClient->GetLevel(), pClient->GetJobLevel(), pClient->GetJobId(), 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::ZERO_ITEM_DROP_ATTEMPTION, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); return; } // lock ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructItem *pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem ) { //_cprint( "onDropItem() : 없는 아이템을 드랍하려 했음. %s (%d)\n", pClient->GetName(), pMsg->item_handle ); SendDropResult( pClient, pMsg->item_handle, false ); return; } if( !pClient->IsDropable( pItem ) || pItem->GetCount() < count ) { SendDropResult( pClient, pMsg->item_handle, false ); return; } // 소환수 카드는 일단 못떨어트림 if( pItem->IsSummonCard() && pItem->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_SUMMON ) ) { SendDropResult( pClient, pMsg->item_handle, false ); return; } if( count < 0 ) count = pItem->GetCount(); if( count > pItem->GetCount() ) count = pItem->GetCount(); StructItem * pNewItem = pClient->DropItem( pItem, count ); SendDropResult( pClient, pMsg->item_handle, !!pNewItem ); } const AR_HANDLE procAddItem( StructPlayer * pClient, StructItem *pItem, bool bIsPartyProcess = true ) { ItemBase::ItemCode code = pItem->GetItemCode(); if( !code && pClient->GetGold() + pItem->GetCount() > GameRule::MAX_GOLD_FOR_INVENTORY ) return 0; pItem->SetIdx( 0 ); StructItem *pNewItem = pClient->PushItem( pItem, pItem->GetCount() ); // event item notice if( pItem->GetEventDropFlag() ) { pItem->SetEventDropFlag( false ); int nFlag = 0; pItem->GetInstanceFlag().CopyTo( &nFlag, sizeof( nFlag ) ); FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "Event", "%20s\t%20u\t%20d\t%20u", pClient->GetName(), pClient->GetSID(), code, nFlag ); if( GameRule::bBroadcastEventItemPickup ) { PrintfGlobalChatMessage( CHAT_NOTICE, "@NOTICE", "@955\v#@picker_name@#\v%s\v#@item_name@#\v@%d", pClient->GetName(), pItem->GetItemBase().nNameId ); } } ItemUID uid = pNewItem ? pNewItem->GetItemUID() : 0; AR_HANDLE item_handle = pNewItem ? pNewItem->GetHandle() : pItem->GetHandle(); const char * szType = "PICK"; if( bIsPartyProcess ) { szType = "PPICK"; } int x = pItem->GetX(); int y = pItem->GetY(); if( pItem->IsGold() ) { LOG::Log11N4S( LM_ITEM_TAKE, pClient->GetAccountID(), pClient->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), code, 0, 0, pItem->GetCount(), pClient->GetGold().GetRawData(), x, y, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, szType, LOG::STR_NTS ); } else { //LOG::Log11N4S( LM_ITEM_TAKE, pClient->GetAccountID(), pClient->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), code, pItem->GetCount(), pNewItem ? pNewItem->GetCount() : 0, 0, 0, x, y, uid, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, szType, LOG::STR_NTS ); // AziaMafia Bonus item procAddItem( name , item_id , quantity ) //std::string new_item; //XStringUtil::Format(new_item, "procAddItem( '%s' , '%d' , '%d' )", pClient->GetName() , code , pItem->GetCount() ); //LUA()->RunString(new_item.c_str()); } if( pNewItem && pNewItem != pItem ) { StructItem::PendFreeItem( pItem ); } //SendResult( pClient, TM_CS_TAKE_ITEM, RESULT_SUCCESS, item_handle ); return item_handle; } bool addPartyItem( StructPlayer * pClient, StructItem *pItem ) { ItemBase::ItemCode nItemCode = pItem->GetItemCode(); int nItemLevel = pItem->GetItemLevel(); int nItemEnhance = pItem->GetItemEnhance(); __int64 nItemCount = pItem->GetCount(); AR_HANDLE hItemHandle = pItem->GetHandle(); AR_HANDLE hResultItem = procAddItem( pClient, pItem ); if( !hResultItem ) { SendResult( pClient, TM_CS_TAKE_ITEM, RESULT_ACCESS_DENIED, hItemHandle ); return false; } SendResult( pClient, TM_CS_TAKE_ITEM, RESULT_SUCCESS, hResultItem ); PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, pClient->GetPartyID(), "TAKE_ITEM|%s|%d|%d|%d|%I64d|", pClient->GetAlias(), nItemCode, nItemLevel, nItemEnhance, nItemCount ); return true; } // 루피 습득은 이 쪽으로 들어오면 최대 루피 소지 한도가 적용되지 않음 void procPartyShare( StructPlayer * pClient, StructItem *pItem ) { // 몹이 떨어트린 아이템만.. if( !pItem->GetOwnerUID() ) { // 파티 분배형 퀘스트 아이템일 경우 시야 내 파티원 전원이 습득 if( pItem->IsQuestItem() && pItem->IsQuestDistributeItem() ) { std::vector< StructPlayer * > vList; PartyManager::GetInstance().GetNearMember( pClient, 400, vList ); std::vector< StructPlayer * >::iterator it; for( it = vList.begin() ; it != vList.end() ; ++it ) { if( !(*it)->IsTakeableQuestItem( pItem->GetItemCode() ) ) { continue; } StructItem *pNewItem = StructItem::AllocItem( 0, pItem->GetItemCode(), 1, ItemInstance::BY_MONSTER ); pNewItem->CopyFrom( pItem ); addPartyItem( (*it), pNewItem ); } StructItem::PendFreeItem( pItem ); return; } PartyManager::_ITEM_SHARE_MODE eShareMode = PartyManager::GetInstance().GetShareMode( pClient->GetPartyID() ); switch( eShareMode ) { case PartyManager::ITEM_SHARE_MONOPOLY: // 각자분배(습득자 독점) addPartyItem( pClient, pItem ); break; case PartyManager::ITEM_SHARE_LINEAR: case PartyManager::ITEM_SHARE_RANDOM: // 순차/랜덤분배(둘 다 주변 플레이어 목록 긁어야 하므로 일단 같이 시작) { std::vector< StructPlayer * > vList; PartyManager::GetInstance().GetNearMember( pClient, GameRule::PARTY_ITEM_SHARE_RANGE, vList ); // 무게 남는 사람만 줍게 해야함 std::vector< StructPlayer * >::iterator it; for( it = vList.begin(); it != vList.end(); ) { if( (*it)->GetMaxWeight() - (*it)->GetWeight() < pItem->GetCount() * pItem->GetItemBase().fWeight ) { it = vList.erase( it ); continue; } if( pItem->IsQuestItem() && !(*it)->IsTakeableQuestItem( pItem->GetItemCode() ) ) { it = vList.erase( it ); continue; } ++it; } if( !vList.empty() ) { if( pItem->IsQuestItem() ) { addPartyItem( PartyManager::GetInstance().GetNextItemAcquirer( pClient, vList ), pItem ); } else { // 랜덤 분배일 경우 우선권을 일괄지급하고 아이템 먹은 플레이어에게서 우선권을 뺌으로써 // 한 명이 다른 플레이어보다 많은 아이템을 먹는 것을 방지 if( eShareMode == PartyManager::ITEM_SHARE_RANDOM ) { std::vector< StructPlayer * > vPlayerList( vList ); for( it = vList.begin(); it != vList.end(); ) { if( !(*it)->HasItemPriority() ) { it = vList.erase( it ); continue; } ++it; } if( vList.empty() ) { for( it = vPlayerList.begin(); it != vPlayerList.end(); ++it ) { (*it)->SetItemPriority(); } vList = vPlayerList; } } StructPlayer * pAcquirer = PartyManager::GetInstance().GetNextItemAcquirer( pClient, vList ); addPartyItem( pAcquirer, pItem ); pAcquirer->ReleaseItemPriority(); } } else { // 사실 여기까진 오면 안되지만 ; addPartyItem( pClient, pItem ); } } break; } } else { addPartyItem( pClient, pItem ); } } void onTakeItem( StructPlayer * pClient, TS_CS_TAKE_ITEM* pMsg ) { ArcadiaLock __lock = ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ); // 아이템 자동 줍기가 가능한 펫이 소환되어 있는 경우 플레이어와 펫을 포함하는 영역에 락 걸기 StructCreature *pTaker = pClient; StructPet *pPet = pClient->GetSummonedPet(); if( pPet && pPet->GetHandle() == pMsg->taker_handle && pPet->IsItemCollectable() && pPet->IsInWorld() ) { // 지역 락 풀기 전에 pPet의 iterator 인스턴스를 만들어서 m_nRefCount를 증가시킴으로써 메모리에서 delete되는 걸 방지 StructPet::iterator itPet = pPet; ArcadiaServer::Instance().UnLock( &__lock ); __lock = ArcadiaServer::Instance().LockObjects( pClient, pPet ); // 락을 건 이후에도 펫이 월드에 남아 있다면 펫이 아이템을 집음(없으면 유저가 시도하는 걸로 처리됨) if( pPet->IsInWorld() ) pTaker = pPet; } ARCADIA_LOCK( __lock ); if( pTaker != pClient && pTaker != pPet ) { SendResult( pClient, pMsg->id, RESULT_NOT_OWN, pMsg->item_handle ); return; } if( pTaker == pClient && !pClient->IsItemPickable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } StructItem *pItem = NULL; StructItem::iterator it = StructItem::get( pMsg->item_handle ); if( (*it) && (*it)->IsItem() ) { pItem = static_cast< StructItem* >( *it ); } if( !pItem ) { //_cprint( "onTakeItem() : 없는 아이템을 주우려 했음. %s (%d)\n", pClient->GetName(), pMsg->item_handle ); SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->item_handle ); return; } // 버그 있음 if( pItem->GetWeight() > pClient->GetMaxWeight() - pClient->GetWeight() ) { // _cprint( "onTakeItem() : 넘 무겁다. %s (%d)\n", pClient->GetName(), pMsg->item_handle ); SendResult( pClient, pMsg->id, RESULT_TOO_HEAVY, pMsg->item_handle ); return; } if( !pItem->IsInWorld() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->item_handle ); return; } if( pItem->GetOwnerHandle() > 0 ) { FILELOG( "onTakeItem() : this item has owner. %s (%d)", pClient->GetName(), pMsg->item_handle ); _cprint( "onTakeItem() : this item has owner. %s (%d)\n", pClient->GetName(), pMsg->item_handle ); SendResult( pClient, pMsg->id, RESULT_NOT_OWN, pMsg->item_handle ); return; } if( pItem->GetMv().GetDistance( pTaker->GetCurrentPosition( GetArTime() ) ) > (GameRule::GetPickableRange() + (pTaker->GetUnitSize()/2)) ) { // _cprint( "onTakeItem() : too far. %s (%d)\n", pClient->GetName(), pMsg->item_handle ); SendResult( pClient, pMsg->id, RESULT_TOO_FAR, pMsg->item_handle ); return; } if( pItem->IsQuestItem() && !pClient->IsTakeableQuestItem( pItem->GetItemCode() ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED, pMsg->item_handle ); return; } // { 먹자 방지 const StructItem::ITEM_PICKUP_ORDER & ItemPickupOrder = pItem->GetPickupOrder(); AR_TIME drop_duration = GetArTime() - pItem->GetDropTime(); for( int i = 0; i < 3; ++i ) { if( !ItemPickupOrder.hPlayer[i] && ItemPickupOrder.nPartyID[i] == 0 ) { // 드랍그룹 지정 안되어 있음 break; } if( (ItemPickupOrder.nPartyID[i] > 0 && ItemPickupOrder.nPartyID[i] == pClient->GetPartyID()) || ItemPickupOrder.hPlayer[i] == pClient->GetHandle() ) { // 해당 그룹이시면 드실 수 있슴당. break; } // 해당 그룹 아니신 분들은 시간 안되시면 못드심당 if( drop_duration < GameRule::GetPickupOrderTime( i ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED, pMsg->item_handle ); return; } } // } // 소지금 한도 체크(파티일 때는 일단 없애버리고 보므로 체크 불필요) if( pItem->IsGold() && !pClient->IsInParty() && pClient->GetGold() + pItem->GetCount() > GameRule::MAX_GOLD_FOR_INVENTORY ) { SendResult( pClient, pMsg->id, RESULT_TOO_MUCH_MONEY, pMsg->item_handle ); return; } TS_SC_TAKE_ITEM_RESULT resultMsg; resultMsg.item_handle = pMsg->item_handle; resultMsg.item_taker = pTaker->GetHandle(); ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &resultMsg ); // 월드에서 제거(ItemCollector::onProcess에서 삭제 중인 아이템이었을 경우 실패함) if( !RemoveItemFromWorld( pItem ) ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->item_handle ); return; } // 파티 소속일 경우 if( pClient->IsInParty() ) { if( pItem->IsGold() ) { // 파티원에게 균등분배 std::vector< StructPlayer * > vList; PartyManager::GetInstance().GetNearMember( pClient, 400, vList ); StructGold incGold( pItem->GetCount() / (float)vList.size() ); const char * szType = "PICK"; if( vList.size() == 1 ) szType = "PPICK"; for( std::vector< StructPlayer * >::iterator it = vList.begin(); it != vList.end(); ++it ) { StructGold nNewGold = (*it)->GetGold() + incGold; if( (*it)->ChangeGold( nNewGold ) != RESULT_SUCCESS ) { PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", (*it), "@578" ); LOG::Log11N4S( LM_ITEM_TAKE, (*it)->GetAccountID(), (*it)->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), pItem->GetItemCode(), 0, 0, 0, nNewGold.GetRawData(), pClient->GetX(), pClient->GetY(), 0, (*it)->GetAccountName(), LOG::STR_NTS, (*it)->GetName(), LOG::STR_NTS, "", 0, szType, LOG::STR_NTS ); continue; } LOG::Log11N4S( LM_ITEM_TAKE, (*it)->GetAccountID(), (*it)->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), pItem->GetItemCode(), 0, 0, incGold.GetRawData(), nNewGold.GetRawData(), pClient->GetX(), pClient->GetY(), 0, (*it)->GetAccountName(), LOG::STR_NTS, (*it)->GetName(), LOG::STR_NTS, "", 0, szType, LOG::STR_NTS ); } // 아이템 삭제 // _oprint( "W:" ); StructItem::PendFreeItem( pItem ); } else { procPartyShare( pClient, pItem ); } } else { AR_HANDLE hResultItem = procAddItem( pClient, pItem, false ); if( !hResultItem ) { // 절대로 습득에 성공했어야 하지만 습득을 못했을 경우 월드에서도 이미 사라졌고 // 습득도 실패했으므로 로그 남기고 날려버린다... Orz... assert( 0 ); StructItem::PendFreeItem( pItem ); SendResult( pClient, TM_CS_TAKE_ITEM, RESULT_ACCESS_DENIED, pMsg->item_handle ); return; } SendResult( pClient, TM_CS_TAKE_ITEM, RESULT_SUCCESS, hResultItem ); } } void onEraseItem( StructPlayer *pClient, TS_CS_ERASE_ITEM* pMsg ) { if( pMsg->item_count > GameRule::MAX_ERASE_ITEM_COUNT || pMsg->size != sizeof( TS_CS_ERASE_ITEM ) + ( pMsg->item_count * sizeof( TS_CS_ERASE_ITEM::EraseItemInfo ) ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } if( !pClient->IsActable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } // 부적형 아이템 등으로 인해 아이템 목록에 변화가 생긴다면 시야 범위에 락을 건다. ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); TS_CS_ERASE_ITEM::EraseItemInfo *pEraseItemInfoList = reinterpret_cast< TS_CS_ERASE_ITEM::EraseItemInfo * >( pMsg + 1 ); StructItem *pEraseItemList[ GameRule::MAX_ERASE_ITEM_COUNT ]; memset( pEraseItemList, 0, sizeof( pEraseItemList ) ); char szBuffer[ SENDMSG_BUFFER_SIZE ]; TS_SC_ERASE_ITEM msg; msg.item_count = pMsg->item_count; msg.size += sizeof(TS_SC_ERASE_ITEM::EraseItemInfo) * msg.item_count; s_memcpy( szBuffer, sizeof( szBuffer ), &msg, sizeof(msg) ); for( unsigned char i = 0; i < pMsg->item_count; ++i ) { TS_SC_ERASE_ITEM::EraseItemInfo *pInfo = reinterpret_cast< TS_SC_ERASE_ITEM::EraseItemInfo * >( szBuffer + sizeof(msg) + i * sizeof(TS_SC_ERASE_ITEM::EraseItemInfo) ); StructItem *pItem = StructItem::FindItem( pEraseItemInfoList[i].item_handle ); if( !pItem ) { pInfo->item_handle = 0; pInfo->count = 0; continue; } int nItemEnhance = pItem->GetItemEnhance(); int nItemLevel = pItem->GetItemLevel(); AR_HANDLE hHandle = pItem->GetHandle(); __int64 nItemCount = pItem->GetCount(); ItemUID nItemUID = pItem->GetItemUID(); bool result; const char * szType = "DESTORY_INVENTORY"; if( pEraseItemInfoList[i].is_in_storage ) { result = pClient->EraseItemFromStorage( pItem, pEraseItemInfoList[i].count ); szType = "DESTORY_STORAGE"; } else { result = pClient->EraseItem( pItem, pEraseItemInfoList[i].count ); } if( result ) { LOG::Log11N4S( LM_ITEM_DELETE, pClient->GetAccountID(), pClient->GetSID(), nItemEnhance * 100 + nItemLevel, pItem->GetItemCode(), nItemCount, pEraseItemInfoList[i].count, pClient->GetGold().GetRawData(), 0, pClient->GetX(), pClient->GetY(), nItemUID, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "SUCS", LOG::STR_NTS, szType, LOG::STR_NTS ); pInfo->item_handle = hHandle; pInfo->count = pEraseItemInfoList[i].count; } else { LOG::Log11N4S( LM_ITEM_DELETE, pClient->GetAccountID(), pClient->GetSID(), nItemEnhance * 100 + nItemLevel, pItem->GetItemCode(), nItemCount, pEraseItemInfoList[i].count, pClient->GetGold().GetRawData(), 0, pClient->GetX(), pClient->GetY(), nItemUID, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "FAIL", LOG::STR_NTS, szType, LOG::STR_NTS ); pInfo->item_handle = 0; pInfo->count = 0; } } PendStream( pClient, szBuffer, msg.size ); } static bool isCommand( const char *pString, const char *pCmd ) { if( strlen(pString) < strlen(pCmd) ) return false; // by Testors; if( pString[strlen(pCmd)] && pString[strlen(pCmd)] != ' ' ) return false; // by Testors; return !_strnicmp( pString, pCmd, strlen(pCmd ) ); } void onCheatBlockChat( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { // C 등급은 사용할 수 없다. int nPermission = pPlayer->GetPermission(); if( nPermission == GameRule::PERMISSION_FOR_C_GRADE ) { return; } if( vToken.size() < 2 || vToken.size() > 3 ) { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); return; } AR_HANDLE hTarget = StructPlayer::FindPlayer( vToken[1].c_str() ); if( !hTarget ) { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); return; } StructPlayer::iterator it = StructPlayer::get( hTarget ); StructPlayer * pTarget = (*it); if( !pTarget ) { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); return; } if( vToken.size() == 3 ) { char *endPtr; int nBlockTime = (int) strtol( vToken[2].c_str(), &endPtr, 10 ); if( *endPtr != NULL || nBlockTime < 0 || nBlockTime > GameRule::MAX_BLOCK_TIME ) { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832"); return; } char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "set_chat_block_time( \"%s\", %d )", pTarget->GetName(), nBlockTime ); LUA()->RunString( szScript ); PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@1653\v#@player_name@#\v%s\v#@block_time@#\v%d", pTarget->GetName(), nBlockTime ); } else if( vToken.size() == 2 ) { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@1654\v#@player_name@#\v%s\v#@remaining_time@#\v%d", pTarget->GetName(), pTarget->GetChatBlockTime() ); } } void onRequestGuildIconInfo( StructPlayer* pClient, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; int nGuildID = atoi( vToken.back().c_str() ); int nGuildIconSize = GuildManager::GetInstance().GetGuildIconSize( nGuildID ); std::string strGuildIconURL; std::string strGuildName; if( nGuildIconSize ) { strGuildIconURL = ENV().GetString( "game.guild_icon_base_url" ); strGuildIconURL += GuildManager::GetInstance().GetGuildIconFileName( nGuildID ); } strGuildName = GuildManager::GetInstance().GetGuildName( nGuildID ); PrintfChatMessage( true, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "GICON|%d|%s|%s|%d", nGuildID, strGuildName.c_str(), strGuildIconURL.c_str(), nGuildIconSize ); } void onRequestGuildBannerInfo( StructPlayer* pClient, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; int nGuildID = atoi( vToken.back().c_str() ); int nGuildBannerSize = GuildManager::GetInstance().GetGuildBannerSize( nGuildID ); std::string strGuildBannerURL; std::string strGuildName; if( nGuildBannerSize ) { strGuildBannerURL = ENV().GetString( "game.guild_banner_base_url" ); strGuildBannerURL += GuildManager::GetInstance().GetGuildBannerFileName( nGuildID ); } strGuildName = GuildManager::GetInstance().GetGuildName( nGuildID ); PrintfChatMessage( true, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "GBANNER|%d|%s|%s|%d", nGuildID, strGuildName.c_str(), strGuildBannerURL.c_str(), nGuildBannerSize ); } void onCheatWalk( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); if( !_stricmp( vToken.back().c_str(), "on" ) ) pPlayer->SetWalk( true ); else pPlayer->SetWalk( false ); BroadcastStatusMessage( pPlayer ); } void onCheatRebirth( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "rebirth( \"%s\" )", pPlayer->GetName() ); LUA()->RunString( szScript ); } void onCheatSetAutoUser( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 2 ) { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ); StructPlayer* pAutoPlayer = *it; if( pAutoPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "set_auto_user( 1, \"%s\" )", pAutoPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } void onCheatUnsetAutoUser( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 2 ) { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ); StructPlayer* pAutoPlayer = *it; if( pAutoPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "set_auto_user( 0, \"%s\" )", pAutoPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } void onCheatCheckAutoUser( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 2 ) { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ); StructPlayer* pAutoPlayer = *it; if( pAutoPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "ev( \"auto_user\", \"%s\" )", pAutoPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } void onCheatKick( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 2 ) { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ); StructPlayer* pKickPlayer = *it; if( pKickPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "kick( \"%s\" )", pKickPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } unsigned int GetStatusCode( StructCreature* pCreature, const struct StructPlayer* pClient ) { unsigned status = 0; if( pCreature->IsBattleMode() ) status |= TS_ENTER::CreatureInfo::FLAG_BATTLE_MODE; if( pCreature->IsInvisible() ) status |= TS_ENTER::CreatureInfo::FLAG_INVISIBLE; if( pCreature->IsPlayer() ) { StructPlayer * pPlayer = static_cast< StructPlayer * >( pCreature ); if( pPlayer->IsSitDown() ) status |= TS_ENTER::PlayerInfo::FLAG_SITDOWN; if( pPlayer->GetBoothStatus() == StructPlayer::BUY_BOOTH ) status |= TS_ENTER::PlayerInfo::FLAG_BUY_BOOTH; if( pPlayer->GetBoothStatus() == StructPlayer::SELL_BOOTH ) status |= TS_ENTER::PlayerInfo::FLAG_SELL_BOOTH; if( pPlayer->IsPKOn() ) status |= TS_ENTER::PlayerInfo::FLAG_PK_ON; if( pPlayer->IsBloodyCharacter() ) status |= TS_ENTER::PlayerInfo::FLAG_BLOODY; if( pPlayer->IsDemoniacCharacter() ) status |= TS_ENTER::PlayerInfo::FLAG_DEMONIAC; if( pPlayer->GetPermission() >= GameRule::PERMISSION_FOR_GM ) status |= TS_ENTER::PlayerInfo::FLAG_GM; if( pPlayer->IsDungeonOriginalOwner() ) status |= TS_ENTER::PlayerInfo::FLAG_DUNGEON_ORIGINAL_OWNER; if( pPlayer->IsDungeonOriginalSieger() ) status |= TS_ENTER::PlayerInfo::FLAG_DUNGEON_ORIGINAL_SIEGER; if( pPlayer->IsWalking() ) status |= TS_ENTER::PlayerInfo::FLAG_WALKING; if( pPlayer->IsInStartedCompete( true ) ) status |= TS_ENTER::PlayerInfo::FLAG_COMPETING; // pPlayer->IsInBattleArena 함수를 사용하면 WarpEnd 함수 내에서 ChangeLocation을 호출하기 전에 TS_ENTER 패킷 방송이 이루어지기 때문에 // 정확한 검사가 이루어지지 않는 객체가 발생하게 됨(워프와 동시에 TS_ENTER 패킷을 받는 유저들이 있기 때문) // 따라서 아래와 같이 GetBattleArenaInstanceNo() 가 0 이 아닌 경우에 팀 번호로 체크를 하게 되면 경기장에 입장하기 전부터 // 서로 플래그가 세팅된 상대로 status를 받게 되지만, 클라이언트에서는 이 플래그에 의한 서로 공격 가능 체크를 아레나 영역 안에서만 하고, // 서버에서는 피아 판정(StructPlayer::IsEnemy)에서 팀 번호가 아닌 아레나 내부일 경우에만 BattleArenaManager::IsOpponent를 사용하므로 // 문제없이 사용이 가능하게 될 것으로 예상됨 if( pPlayer->GetBattleArenaInstanceNo() ) { int nTeamNo = BattleArenaManager::Instance().GetTeamNo( pPlayer ); if( nTeamNo == 0 ) status |= TS_ENTER::PlayerInfo::FLAG_BATTLE_ARENA_TEAM_0; else if( nTeamNo == 1 ) status |= TS_ENTER::PlayerInfo::FLAG_BATTLE_ARENA_TEAM_1; } } else if( pCreature->IsNPC() ) { StructNPC * pNPC = static_cast< StructNPC* >( pCreature ); if( pNPC->HasFinishableQuest( pClient ) ) { status |= TS_ENTER::NPCInfo::FLAG_HAS_FINISHABLE_QUEST; } else if( pNPC->HasStartableQuest( pClient ) ) { status |= TS_ENTER::NPCInfo::FLAG_HAS_STARTABLE_QUEST; } else if( pNPC->HasInProgressQuest( pClient ) ) { status |= TS_ENTER::NPCInfo::FLAG_HAS_IN_PROGRESS_QUEST; } } else if( pCreature->IsMonster() ) { StructMonster * pMonster = static_cast< StructMonster * >( pCreature ); if( pMonster->IsDead() ) status |= TS_ENTER::MonsterInfo::FLAG_DEAD; if( pMonster->IsOriginalDungeonOwnerGuardian() ) status |= TS_ENTER::MonsterInfo::FLAG_DUNGEON_ORIGINAL_OWNER; if( pMonster->IsOriginalDungeonSiegerGuardian() ) status |= TS_ENTER::MonsterInfo::FLAG_DUNGEON_ORIGINAL_SIEGER; } else if( pCreature->IsPet() ) { StructPet * pPet = static_cast< StructPet * >( pCreature ); if( pPet->IsUsingSkill() && pPet->GetCastSkill()->GetSkillId() == StructSkill::SKILL_SHOVELING ) { switch( pPet->GetShovelingStatus() ) { case StructPet::SHOVELING_STATUS_SEARCH: status |= TS_ENTER::PlayerInfo::FLAG_SHOVELING_SEARCH; break; case StructPet::SHOVELING_STATUS_APPROACH: status |= TS_ENTER::PlayerInfo::FLAG_SHOVELING_APPROACH; break; case StructPet::SHOVELING_STATUS_DIG: status |= TS_ENTER::PlayerInfo::FLAG_SHOVELING_DIG; break; } } } return status; } void onBattleMode( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() < 2 ) return; char * strstopper; AR_HANDLE handle = strtoul( vToken[1].c_str(), &strstopper, 10 ); StructCreature::iterator it = StructCreature::get( handle ); StructCreature * pCreature = (*it); if( !pCreature ) return; if( pCreature->IsPlayer() && pPlayer == static_cast< StructPlayer * >( pCreature ) ) { pPlayer->SetBattleModeOn(); } else if( pCreature->IsSummon() ) { StructSummon * pSummon = pPlayer->GetSummon( pCreature->GetHandle() ); if( pSummon ) { pSummon->SetBattleModeOn(); } } else { return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pCreature ) ); BroadcastStatusMessage( pCreature ); } void onNormalMode( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() < 2 ) return; char * strstopper; AR_HANDLE handle = strtoul( vToken[1].c_str(), &strstopper, 10 ); StructCreature::iterator it = StructCreature::get( handle ); StructCreature * pCreature = (*it); if( !pCreature ) return; if( pCreature->IsPlayer() && pPlayer == static_cast< StructPlayer * >( pCreature ) ) { // pPlayer->Save(); pPlayer->SetBattleModeOff(); } else if( pCreature->IsSummon() ) { StructSummon * pSummon = pPlayer->GetSummon( pCreature->GetHandle() ); if( pSummon ) { pSummon->SetBattleModeOff(); } } else { return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pCreature ) ); BroadcastStatusMessage( pCreature ); } void onCheatSitdown( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); if( pPlayer->IsMoving( GetArTime() ) || !pPlayer->IsActable() || !pPlayer->IsSitdownable() ) return; if( pPlayer->IsFormChanged() ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@440" ); return; } if( pPlayer->IsAttacking() ) { pPlayer->CancelAttack(); } pPlayer->SitDown(); BroadcastStatusMessage( pPlayer ); } void onCheatRide( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() < 1 ) return; if( !pPlayer->IsMountable( false ) ) return; char * stopchar; AR_HANDLE handle = strtoul( vToken.back().c_str(), &stopchar, 10 ); pPlayer->MountSummon( handle ); } void onCheatUnRide( StructPlayer* pPlayer ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); pPlayer->UnMount(); } void onCheatStandUp( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); if( !pPlayer->GetBoothStatus() == StructPlayer::IS_NOT_BOOTH ) return; pPlayer->StandUp(); BroadcastStatusMessage( pPlayer ); } void onChangeGuildName( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; // 길드가 아니면 리턴 if( !pPlayer->IsInGuild() ) return; // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsLeader( pPlayer->GetGuildID(), pPlayer->GetPlayerUID() ) ) return; const char * szNewGuildName = vToken.back().c_str(); if( (int)strlen( szNewGuildName ) > 16 ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@128" ); return; } int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, szNewGuildName, (int)strlen( szNewGuildName ) + 1, 1, 16 ) || GameContent::IsBannedWord( code_page, szNewGuildName ) ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@128" ); return; } GuildManager::GetInstance().ChangeGuildName( pPlayer->GetGuildID(), vToken.back().c_str(), pPlayer ); } void onChangeAllianceName( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; // 길드가 아니면 리턴 int guild_id = pPlayer->GetGuildID(); if( !guild_id ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@863" ); return; } // 연합에 소속되어 있지 않은 길드면 리턴 int alliance_id = GuildManager::GetInstance().GetAllianceID( guild_id ); if( !alliance_id ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@863" ); return; } // 연합장 길드 소속이 아니면 리턴 if( GuildManager::GetInstance().GetAllianceLeaderGuildID( alliance_id ) != guild_id ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@871" ); return; } // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsLeader( pPlayer->GetGuildID(), pPlayer->GetPlayerUID() ) ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@637" ); return; } const char * szNewAllianceName = vToken.back().c_str(); int nNewAllianceNameLength = static_cast< int >( vToken.back().length() ); if( nNewAllianceNameLength > 16 ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@128" ); return; } int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, szNewAllianceName, nNewAllianceNameLength + 1, 1, 16 ) || GameContent::IsBannedWord( code_page, szNewAllianceName ) ) { SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@128" ); return; } GuildManager::GetInstance().ChangeAllianceName( alliance_id, szNewAllianceName, pPlayer ); } void onAddFriend( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; if( vToken.back() == pPlayer->GetName() ) { SendChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@482" ); // 자신을 등록할 수 없습니다. return; } // US request: Allow friend adding even if the character name is normally invalid, as long as it contains certain exception words. // Example: GM / QA -> [VGM] / [VQA] // Character names with these prefixes are not valid, but still need to be eligible for friend adding. std::string szAllowedWords = ENV().GetString( "game.allowed_names_as_friend" ); bool bIsAllowedName = false; if( !szAllowedWords.empty() ) { std::vector< std::string > vAllowedNames; XStringUtil::Split( szAllowedWords.c_str(), vAllowedNames, ";", false ); std::vector< std::string >::iterator it; for( it = vAllowedNames.begin(); it != vAllowedNames.end(); ++it ) { if( nsl::wildcmp( (*it).c_str(), vToken.back().c_str() ) ) { bIsAllowedName = true; break; } } } if( !bIsAllowedName && !GameRule::IsValidName( ENV().GetInt( "CodePage", CP_ACP ), vToken.back().c_str(), static_cast< int >( vToken.back().length() ) + 1, 4, 18 ) ) { SendChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@481" ); // 존재하지 않는 캐릭터입니다. return; } if( pPlayer->GetFriendCount() >= 25 ) { SendChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@478" ); // 더 이상 등록이 불가능합니다. return; } pPlayer->AddFriend( vToken.back().c_str() ); } void onDelFriend( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; if( pPlayer->IsFriend( vToken.back().c_str() ) ) { pPlayer->DelFriend( vToken.back().c_str() ); } else { PrintfChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@471\v#@friend_name@#\v%s", vToken.back().c_str() ); // XXXX 님은 친구 목록에 존재하지 않습니다. } } void onAddDenial( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; if( vToken.back() == pPlayer->GetName() ) { SendChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@482" ); // 자신을 등록할 수 없습니다. return; } if( !GameRule::IsValidName( ENV().GetInt( "CodePage", CP_ACP ), vToken.back().c_str(), static_cast< int >( vToken.back().length() ) + 1, 4, 18 ) ) { SendChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@481" ); // 존재하지 않는 캐릭터입니다. return; } if( pPlayer->GetDenialCount() >= 25 ) { SendChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@478" ); // 더 이상 등록이 불가능합니다. return; } pPlayer->AddDenial( vToken.back().c_str() ); } void onDelDenial( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; if( pPlayer->IsDenial( vToken.back().c_str() ) ) { pPlayer->DelDenial( vToken.back().c_str() ); } else { PrintfChatMessage( false, CHAT_FRIEND, "@FRIEND", pPlayer, "@475\v#@denial_name@#\v%s", vToken.back().c_str() ); // XXXX 님은 차단 목록에 존재하지 않습니다. } } void onChangeName( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); pPlayer->ChangeName( vToken.back().c_str(), true, false, NULL ); } void onCheatWarp( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 2 ) { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ); StructPlayer* pToPlayer = *it; if( pToPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "warp( %f, %f, %d, \"%s\" )", pToPlayer->GetX(), pToPlayer->GetY(), pToPlayer->GetLayer(), pPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else if( vToken.size() == 3 ) { AR_UNIT x = atoi( vToken[1].c_str() ); AR_UNIT y = atoi( vToken[2].c_str() ); char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "warp( %f, %f, \"%s\" )", x, y, pPlayer->GetName() ); LUA()->RunString( szScript ); } else if( vToken.size() == 4 ) { AR_UNIT x = atoi( vToken[1].c_str() ); AR_UNIT y = atoi( vToken[2].c_str() ); StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[3].c_str() ) ); StructPlayer* pWarpPlayer = *it; if( pWarpPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "warp( %f, %f, \"%s\" )", x, y, pWarpPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } void onCheatWarp2( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 3 ) { AR_TIME t = GetArTime(); AR_UNIT x = atoi( vToken[1].c_str() ); AR_UNIT y = atoi( vToken[2].c_str() ); if( pPlayer ) { std::vector< ArPosition > vMoveInfo; ArPosition start_pos = pPlayer->GetCurrentPosition( t ); ArPosition end_pos = ArPosition( x, y ); unsigned char moveSpeed = static_cast< unsigned char >( UCHAR_MAX ); vMoveInfo.push_back( start_pos ); vMoveInfo.push_back( end_pos ); ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithSpecificRegion( pPlayer, ::GetRegionX( start_pos.x ), ::GetRegionY( start_pos.y ) ) ); if( pPlayer->IsAttacking() ) { pPlayer->CancelAttack(); } if( pPlayer->IsSitDown() ) { pPlayer->StandUp(); BroadcastStatusMessage( pPlayer ); } if( !pPlayer->IsMovable() || !pPlayer->IsInWorld() ) { return; } if( pPlayer->HasPendingMove() ) { pPlayer->ReleasePendingMove(); } ArcadiaServer::Instance().SetMultipleMove( pPlayer, start_pos, vMoveInfo, moveSpeed , true, t ); pPlayer->SetMoveReq( true ); } } } void onCheatForceWarp( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() == 2 ) { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ); StructPlayer* pToPlayer = *it; if( pToPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "force_warp( %f, %f, %d, \"%s\" )", pToPlayer->GetX(), pToPlayer->GetY(), pToPlayer->GetLayer(), pPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else if( vToken.size() == 3 ) { AR_UNIT x = atoi( vToken[1].c_str() ); AR_UNIT y = atoi( vToken[2].c_str() ); char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "force_warp( %f, %f, \"%s\" )", x, y, pPlayer->GetName() ); LUA()->RunString( szScript ); } else if( vToken.size() == 4 ) { AR_UNIT x = atoi( vToken[1].c_str() ); AR_UNIT y = atoi( vToken[2].c_str() ); StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( vToken[3].c_str() ) ); StructPlayer* pWarpPlayer = *it; if( pWarpPlayer != NULL ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "force_warp( %f, %f, \"%s\" )", x, y, pWarpPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@280" ); } } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } void onCheatInvisible( StructPlayer * pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() < 2 ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); std::string strChat; XStringUtil::Format( strChat, "Invisible : %s", pPlayer->IsInvisible() ? "true" : "false" ); SendChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, strChat.c_str(), static_cast< unsigned int >( strChat.size() ) ); return; } int mode = atoi( vToken.back().c_str() ); if( mode == 1 || mode == 2 ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "set_invisible( %d, \"%s\" )", mode, pPlayer->GetName() ); LUA()->RunString( szScript ); } } void onCheatNotice( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() < 2 ) { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); return; } std::string strNotice; for( size_t i = 1; i < vToken.size(); ++i ) { strNotice += vToken[i]; if( i < (vToken.size()-1) ) { strNotice += " "; } } char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "notice( \"%s\" )", strNotice.c_str() ); LUA()->RunString( szScript ); } void onCheatRegenerate( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { int nMonID = 0; int nCount = 1; if( vToken.size() >= 2 ) { nMonID = atoi( vToken[1].c_str() ); } if( vToken.size() >= 3 ) { nCount = atoi( vToken[2].c_str() ); } if( nMonID > 0 && nCount >= 1 ) { char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "add_npc( %f, %f, %d, %d, \"%s\" )", pPlayer->GetX(), pPlayer->GetY(), nMonID, nCount, pPlayer->GetName() ); LUA()->RunString( szScript ); } else { PrintfChatMessage( false, CHAT_DEBUG, "@SCRIPT", pPlayer, "@832" ); } } void onCheatLevel( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; int new_level = atoi( vToken.back().c_str() ); if( new_level < 1 || new_level > GameRule::MAX_LEVEL ) return; char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "set_level( %d, \"%s\" )", new_level, pPlayer->GetName() ); LUA()->RunString( szScript ); } void onCheatHeal( StructPlayer* pPlayer, std::vector< std::string > & vToken ) { if( vToken.size() != 2 ) return; char szScript[1024] = { 0, }; s_sprintf( szScript, _countof( szScript ), "heal( %d, \"%s\" )", atoi( vToken.back().c_str() ), pPlayer->GetName() ); LUA()->RunString( szScript ); } void onCheatScript( const char *pScript ) { int nQuote = 0, nBigQuote = 0, nBracket = 0; for( const char *p = pScript; *p; ++p ) { if( *p == '(' ) nBracket++; if( *p == ')' ) nBracket--; if( *p == '\'' ) nQuote++; if( *p == '\"' ) nBigQuote++; } if( nBracket == 0 && nQuote%2 == 0 && nBigQuote%2 == 0 ) LUA()->RunString( pScript ); } void onPartyAssist( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 이미 파티에 가입되어 있으면.. if( !pClient->IsInParty() ) return; char * stopchar; AR_HANDLE handle = strtoul( vToken[1].c_str(), &stopchar, 10 ); if( !handle ) return; StructPlayer::iterator it = StructPlayer::get( handle ); if( !(*it) ) return; if( (*it)->GetPartyID() != pClient->GetPartyID() ) return; if( !(*it)->IsPlayer() ) return; PrintfChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ASSIST|%u|", (*it)->GetTarget() ); } void onAttackTeamJoin( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 헌터홀릭 던전 및 대기실 안에 있으면 공대 가입 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_HUNTAHOLIC" ); return; } // 인스턴스 던전 안에 있으면 공대 가입 불가 if( pClient->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_INSTANCE_DUNGEON" ); return; } // 데스매치 맵에 있으면 공대 가입 불가 if( pClient->IsInDeathmatch() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_DEATHMATCH" ); return; } // 아레나 대기열 또는 경기에 참여 중이면 가입 불가 if( pClient->GetBattleArenaID() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_BATTLE_ARENA" ); return; } int nPartyID = atoi( vToken[1].c_str() ); std::string strPartyName = PartyManager::GetInstance().GetPartyName( nPartyID ); // 이미 파티에 가입되어 있으면.. if( pClient->IsInParty() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_ALREADY_PARTY_MEMBER" ); return; } if( strPartyName.empty() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_INVALID_PARTY" ); return; } if( !PartyManager::GetInstance().IsAttackTeamParty( nPartyID ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_INVALID_PARTY" ); return; } // 암호가 없거나 인자 수가 부족하면 거절한 것 if( vToken.size() <= 2 ) return; // 암호가 다르면 if( atoi( vToken[2].c_str() ) != PartyManager::GetInstance().GetLinkedPartyPassword( nPartyID ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "HAS_NO_AUTHORITY" ); return; } if( !PartyManager::GetInstance().IsJoinableLinkedParty( nPartyID ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_MAX" ); return; } char buf[1024]; PartyManager::_PARTY_TYPE partyType = PartyManager::GetInstance().GetPartyType( nPartyID ); int nAttackTeamPartyID = 0; for( int i = 0; i < 100; ++i ) { s_sprintf( buf, _countof( buf ), "%s_%02d", strPartyName.c_str(), i ); // 파티 만들고 nAttackTeamPartyID = PartyManager::GetInstance().MakeParty( buf, pClient->GetPlayerUID(), partyType ); if( nAttackTeamPartyID > 0 ) { break; } } if( nAttackTeamPartyID <= 0 ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_MAX" ); return; } // 리더를 참여시킨다. if( !PartyManager::GetInstance().JoinParty( nAttackTeamPartyID, pClient ) ) { PartyManager::GetInstance().DestroyParty( nAttackTeamPartyID ); SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "CANT_JOIN" ); return; } // 초대한 길드 ID를 얻어야 함 int nInvitorGuildID = PartyManager::GetInstance().GetAttackTeamGuildID( nPartyID ); int nNewPartyID = pClient->GetPartyID(); if( !PartyManager::GetInstance().JoinAttackTeam( nInvitorGuildID, nNewPartyID ) ) { PartyManager::GetInstance().DestroyParty( nNewPartyID ); SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ALREADY_EXIST" ); return; } PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nNewPartyID, "CREATE|%s|%s|%d|", buf, pClient->GetName(), partyType ); SendPartyInfo( pClient ); SendLinkedPartyInfo( pClient ); BroadcastPartyMemberInfo( nNewPartyID, pClient ); BroadcastLinkedPartyMemberInfo( nNewPartyID, pClient ); // 소환수 정보는 파티 타입에 관계없이 동일한 규칙에 의해 방송되므로 별도 처리한다. SendSummonInfo( pClient ); BroadcastSummonInfo( nNewPartyID, pClient ); LOG::Log11N4S( LM_PARTY_CREATE, pClient->GetAccountID(), pClient->GetSID(), nNewPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 2, partyType, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, buf, LOG::STR_NTS, "", 0 ); // 파티장이 비정상 종료 되도 공대는 유지되도록 파티장을 저장함 pClient->Save( true ); } void onAttackTeamInvite( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 if( !pClient->IsInParty() ) return; int nPartyID = pClient->GetPartyID(); int nGuildID = pClient->GetGuildID(); std::string partyName( PartyManager::GetInstance().GetPartyName( nPartyID ) ); // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; if( !PartyManager::GetInstance().IsAttackTeamParty( nPartyID ) ) return; StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) return; // 대상이 헌터홀릭 던전 및 대기실 안에 있으면 초대 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pTarget->GetPos() ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_TARGET_IN_HUNTAHOLIC" ); return; } // 대상이 인스턴스 던전 안에 있으면 초대 불가 if( pTarget->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_TARGET_IN_INSTANCE_DUNGEON" ); return; } // 데스매치 맵에 있으면 초대 불가 if( pClient->IsInDeathmatch() ) return; if( pTarget->IsInDeathmatch() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_DEATHMATCH" ); return; } // 대상이 아레나 대기열 또는 경기에 참여 중이면 초대 불가 if( pTarget->GetBattleArenaID() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_TARGET_IN_BATTLE_ARENA" ); return; } // 파티에 가입 되어 있으면 if( pTarget->IsInParty() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_TARGET_ANOTHER" ); return; } if( !nGuildID || ( nGuildID != pTarget->GetGuildID() && ( !GuildManager::GetInstance().GetAllianceID( nGuildID ) || GuildManager::GetInstance().GetAllianceID( nGuildID ) != GuildManager::GetInstance().GetAllianceID( pTarget->GetGuildID() ) ) ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_TARGET_ANOTHER" ); return; } // 공대 파티가 도전 중인 던전의 입장 레벨 제한 체크 if( DungeonManager::Instance().IsRestrictedToEnter( GuildManager::GetInstance().GetRaidDungeonID( nGuildID ), pTarget ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_PROPER_LEVEL_FOR_DUNGEON" ); return; } if( !PartyManager::GetInstance().IsJoinableLinkedParty( nPartyID ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_MAX" ); return; } LOG::Log11N4S( LM_PARTY_INVITE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget->GetAccountID(), pTarget->GetSID(), pTarget->GetX(), pTarget->GetY(), pTarget->GetLayer(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget->GetAccountName(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); PrintfChatMessage( true, CHAT_RAID_SYSTEM, "@RAID", pTarget, "RAID_GUILD_INVITE|%s|%s|%d|%d|", pClient->GetName(), partyName.c_str(), nPartyID, PartyManager::GetInstance().GetLinkedPartyPassword( nPartyID ) ); } void onAttackTeamCreate( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; const char *pPartyName = vToken[1].c_str(); if( !pPartyName ) return; size_t partyLen = strlen( pPartyName ); if( partyLen > 30 ) return; int nPartyID = pClient->GetPartyID(); if( nPartyID ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_ALREADY_PARTY_MEMBER" ); return; } int nGuildID = pClient->GetGuildID(); if( !nGuildID ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_GUILD_LEADER" ); return; } // 헌터홀릭 던전 및 대기실 안에 있으면 파티 생성 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_HUNTAHOLIC" ); return; } // 인스턴스 던전 안에 있으면 파티 생성 불가 if( InstanceDungeonManager::Instance().GetInstanceDungeonID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_INSTANCE_DUNGEON" ); return; } // 데스매치 맵에 있으면 파티 생성 불가 if( pClient->IsInDeathmatch() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_DEATHMATCH" ); return; } // 아레나 대기열 또는 경기에 참여 중이면 파티 생성 불가 if( pClient->GetBattleArenaID() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_BATTLE_ARENA" ); return; } //if( nAllianceID && !GuildManager::GetInstance().IsAllianceLeader( nAllianceID, nGuildID ) ) //{ // SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_GUILD_LEADER" ); // return; //} int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); // 연합 리더 길드 ID로 nGuildID를 변경(전투 대장은 연합 마스터 길드 정보에 세팅됨) if( nAllianceID ) nGuildID = GuildManager::GetInstance().GetAllianceLeaderGuildID( nAllianceID ); int raid_dungeon_id = GuildManager::GetInstance().GetRaidDungeonID( nGuildID ); if( !raid_dungeon_id ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_RAID_GUILD" ); return; } // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_ATTACK_TEAM_CREATE ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_GUILD_LEADER" ); return; } // 레이드 기간 중이어도 해당 날짜 중에 레이드 완료 기록이 있으면 공대파티 생성 불가 // (레이드 한 번 끝났으면 그 날은 시즈공대 못만들어야 함) time_t raid_end_time = DungeonManager::Instance().GetLastRaidEndTime( raid_dungeon_id, nGuildID ); if( DungeonManager::Instance().IsDungeonRaidTime( raid_dungeon_id ) && raid_end_time && raid_end_time >= GetDayBeginTime() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_ALREADY_COMPLETE_RAID" ); return; } // 이미 공대가 결성되어 있으면 불가 if( PartyManager::GetInstance().IsExistAttackTeam( nGuildID ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_ALREADY_PARTY_MEMBER" ); return; } // 나쁜 파티 이름 int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, pPartyName, static_cast< int >( partyLen ) + 1, 1, 30 ) || GameContent::IsBannedWord( code_page, pPartyName ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "INVALID_PARTY_NAME" ); return; } int nMaxGuildParty = 0; bool bRaidParty = false; if( DungeonManager::Instance().IsDungeonRaidTime( raid_dungeon_id ) ) { if( nGuildID == DungeonManager::Instance().GetOwnGuildID( raid_dungeon_id ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_SIEGE_TIME" ); return; } nMaxGuildParty = DungeonManager::Instance().GetMaxRaidParty( raid_dungeon_id ); bRaidParty = true; } else { if( !DungeonManager::Instance().IsDungeonSiegeAttacteamMakingPeriod( raid_dungeon_id ) ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_SIEGE_TIME" ); return; } if( DungeonManager::Instance().GetOwnGuildID( raid_dungeon_id ) != nGuildID && DungeonManager::Instance().GetRaidGuildID( raid_dungeon_id ) != nGuildID ) { assert( 0 ); // 시즈 시간인데 공격자 길드도, 주인 길드도 아닌 길드가 도전 상태임 SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_NOT_SIEGE_TIME" ); return; } nMaxGuildParty = DungeonManager::Instance().GetMaxGuildParty( raid_dungeon_id ); } PartyManager::_PARTY_TYPE partyType = bRaidParty ? PartyManager::TYPE_RAID_ATTACKTEAM : PartyManager::TYPE_SIEGE_ATTACKTEAM; // 파티 만들고 int nAttackTeamPartyID = PartyManager::GetInstance().MakeParty( pPartyName, pClient->GetPlayerUID(), partyType ); if( nAttackTeamPartyID <= 0 ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ALREADY_EXIST" ); return; } // 리더를 참여시킨다. if( !PartyManager::GetInstance().JoinParty( nAttackTeamPartyID, pClient ) ) { PartyManager::GetInstance().DestroyParty( nAttackTeamPartyID ); SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "CANT_JOIN" ); return; } if( !PartyManager::GetInstance().MakeAttackTeam( nAttackTeamPartyID, nGuildID, nMaxGuildParty, bRaidParty ) ) { PartyManager::GetInstance().DestroyParty( nAttackTeamPartyID ); SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ALREADY_EXIST" ); return; } PrintfChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "CREATE|%s|%s|%d|", pPartyName, pClient->GetName(), partyType ); PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "@645\v#@guild_name@#\v%s\v#@user_name@#\v%s", GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), pClient->GetName() ); SendPartyInfo( pClient ); SendLinkedPartyInfo( pClient ); // 소환수 정보는 파티 타입에 관계없이 동일한 규칙에 의해 방송되므로 별도 처리한다. SendSummonInfo( pClient ); BroadcastSummonInfo( nPartyID, pClient ); LOG::Log11N4S( LM_PARTY_CREATE, pClient->GetAccountID(), pClient->GetSID(), pClient->GetPartyID(), pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 1, partyType, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pPartyName, LOG::STR_NTS, "", 0 ); // 파티장이 비정상 종료 되도 공대는 유지되도록 공대장을 저장함 pClient->Save( true ); } void onBattleArenaExerciseOpponentInvite( StructPlayer * pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 if( !pClient->IsInParty() ) return; int nPartyID = pClient->GetPartyID(); // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; if( PartyManager::GetInstance().GetPartyType( nPartyID ) != PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM ) return; // 상대팀이 이미 있으면 초대 불가 if( BattleArenaManager::Instance().HasOpponent( pClient ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_ALREADY_EXIST" ); return; } // 레디를 한 이후에는 초대 불가 if( BattleArenaManager::Instance().IsReadyExerciseTeam( nPartyID ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_READY" ); return; } StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) return; // 대상이 헌터홀릭 던전 및 대기실 안에 있으면 초대 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pTarget->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_HUNTAHOLIC" ); return; } // 대상이 인스턴스 던전 안에 있으면 초대 불가 if( pTarget->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_INSTANCE_DUNGEON" ); return; } if( pTarget->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_DEATHMATCH" ); return; } // 파티에 가입 되어 있거나 배틀아레나 중이라면 if( pTarget->IsInParty() || pTarget->GetBattleArenaID() != 0 ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_ANOTHER" ); return; } LOG::Log11N4S( LM_PARTY_INVITE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget->GetAccountID(), pTarget->GetSID(), pTarget->GetX(), pTarget->GetY(), pTarget->GetLayer(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget->GetAccountName(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pTarget, "ARENA_OPPONENT_INVITE|%s|%s|%d|%d|", pClient->GetName(), PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), nPartyID, PartyManager::GetInstance().GetLinkedPartyPassword( nPartyID ) ); } void onBattleArenaExerciseOpponentJoin( StructPlayer * pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; int nPartyID = atoi( vToken[1].c_str() ); if( PartyManager::GetInstance().GetPartyType( nPartyID ) != PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_INVALID_PARTY" ); return; } StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( PartyManager::GetInstance().GetLeaderName( nPartyID ).c_str() ) ) ); // 초대한 사람이 오프라인이면 없을 수도 있지만 이하 코드에서는 if( pInviter ) 떡칠로 대응은 되어 있으니 패스... StructPlayer * pInviter = (*pit); // 암호가 없거나 인자 수가 부족하면 거절한 것 if( vToken.size() < 3 ) { if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_INVITATION_DENIED|%s|", pClient->GetName() ); return; } // 암호가 다르면 if( atoi( vToken[2].c_str() ) != PartyManager::GetInstance().GetLinkedPartyPassword( nPartyID ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "HAS_NO_AUTHORITY" ); return; } // 헌터홀릭 던전 및 대기실 안에 있으면 연습 경기 참여 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_HUNTAHOLIC" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_IN_HUNTAHOLIC|%s|", pClient->GetName() ); return; } // 인스턴스 던전 안에 있으면 연습 경기 참여 불가 if( pClient->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_INSTANCE_DUNGEON" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_IN_INSTANCE_DUNGEON|%s|", pClient->GetName() ); return; } // 데스매치 맵에 있으면 연습 경기 참여 불가 if( pClient->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_IN_DEATHMATCH|%s|", pClient->GetName() ); return; } // 이미 파티에 가입되어 있거나 배틀아레나 중이라면 if( pClient->IsInParty() || pClient->GetBattleArenaID() != 0 ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_ALREADY_PARTY_MEMBER" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_ANOTHER|%s|", pClient->GetName() ); return; } // JoinExerciseGameOpponentLeader 안에서 팀의 레디 상태 체크를 가입 처리 타이밍에 정확하게 해주기 때문에 // 여기서는 따로 IsReadyExerciseTeam 으로 체크하지 않음 unsigned short nResult = BattleArenaManager::Instance().JoinExerciseGameOpponentLeader( pClient, nPartyID ); if( nResult != RESULT_SUCCESS ) { switch( nResult ) { case RESULT_NOT_EXIST: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_INVALID_PARTY" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_FAIL|%s|", pClient->GetName() ); break; case RESULT_ALREADY_EXIST: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_MAX" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_MAX|%s|", pClient->GetName() ); break; case RESULT_NOT_ACTABLE: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_NOT_ACTABLE" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_NOT_ACTABLE|%s|", pClient->GetName() ); break; default: PrintfChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_UNKNOWN|%d|", nResult ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_UNKNOWN|%s|%d|", pClient->GetName(), nResult ); break; } } } void onBattleArenaExerciseMemberInvite( StructPlayer * pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 if( !pClient->IsInParty() ) return; int nPartyID = pClient->GetPartyID(); // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; if( PartyManager::GetInstance().GetPartyType( nPartyID ) != PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM ) return; // 레디를 한 이후에는 초대 불가 if( BattleArenaManager::Instance().IsReadyExerciseTeam( nPartyID ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_READY" ); return; } // 인원 초과 체크 if( !BattleArenaManager::Instance().IsMoreMemberInvitable( pClient ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_LIMIT_MAX" ); return; } StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) return; // 대상이 헌터홀릭 던전 및 대기실 안에 있으면 초대 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pTarget->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_HUNTAHOLIC" ); return; } // 대상이 인스턴스 던전 안에 있으면 초대 불가 if( pTarget->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_INSTANCE_DUNGEON" ); return; } if( pTarget->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_DEATHMATCH" ); return; } // 파티에 가입 되어 있거나 배틀아레나 중이라면(에러가 모호하다) if( pTarget->IsInParty() || pTarget->GetBattleArenaID() != 0 ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_ANOTHER" ); return; } LOG::Log11N4S( LM_PARTY_INVITE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget->GetAccountID(), pTarget->GetSID(), pTarget->GetX(), pTarget->GetY(), pTarget->GetLayer(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget->GetAccountName(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pTarget, "ARENA_MEMBER_INVITE|%s|%s|%d|%d|", pClient->GetName(), PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), nPartyID, PartyManager::GetInstance().GetPartyPassword( nPartyID ) ); } void onBattleArenaExerciseMemberJoin( StructPlayer * pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; int nPartyID = atoi( vToken[1].c_str() ); if( PartyManager::GetInstance().GetPartyType( nPartyID ) != PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_INVALID_PARTY" ); return; } StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( PartyManager::GetInstance().GetLeaderName( nPartyID ).c_str() ) ) ); // 초대한 사람이 오프라인이면 없을 수도 있지만 이하 코드에서는 if( pInviter ) 떡칠로 대응은 되어 있으니 패스... StructPlayer * pInviter = (*pit); // 암호가 없거나 인자 수가 부족하면 거절한 것 if( vToken.size() < 3 ) { if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_INVITATION_DENIED|%s|", pClient->GetName() ); return; } // 암호가 다르면 if( atoi( vToken[2].c_str() ) != PartyManager::GetInstance().GetPartyPassword( nPartyID ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "HAS_NO_AUTHORITY" ); return; } // 헌터홀릭 던전 및 대기실 안에 있으면 연습 경기 참여 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_HUNTAHOLIC" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_IN_HUNTAHOLIC|%s|", pClient->GetName() ); return; } // 인스턴스 던전 안에 있으면 연습 경기 참여 불가 if( pClient->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_INSTANCE_DUNGEON" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_IN_INSTANCE_DUNGEON|%s|", pClient->GetName() ); return; } // 데스매치 맵에 있으면 연습 경기 참여 불가 if( pClient->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_IN_DEATHMATCH|%s|", pClient->GetName() ); return; } // 이미 파티에 가입되어 있거나 배틀아레나 중이라면 if( pClient->IsInParty() || pClient->GetBattleArenaID() != 0 ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_ALREADY_PARTY_MEMBER" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_ANOTHER|%s|", pClient->GetName() ); return; } // JoinExerciseGameMember 안에서 팀의 레디 상태 체크를 가입 처리 타이밍에 정확하게 해주기 때문에 // 여기서는 따로 IsReadyExerciseTeam 으로 체크하지 않음 unsigned short nResult = BattleArenaManager::Instance().JoinExerciseGameMember( pClient, nPartyID ); switch( nResult ) { case RESULT_SUCCESS: // 성공 시에는 따로 방송을 할 필요 없이 BattleArenaManager::JoinExerciseGameMember 안에서 필요한 방송들이 모두 처리됨 LOG::Log11N4S( LM_PARTY_JOIN, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 ); break; case RESULT_LIMIT_MAX: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_LIMIT_MAX" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_LIMIT|%s|", pClient->GetName() ); break; case RESULT_NOT_EXIST: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_INVALID_PARTY" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_FAIL|%s|", pClient->GetName() ); break; case RESULT_ALREADY_EXIST: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_MAX" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_MAX|%s|", pClient->GetName() ); break; case RESULT_NOT_ACTABLE: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_NOT_ACTABLE" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_NOT_ACTABLE|%s|", pClient->GetName() ); break; case RESULT_ACCESS_DENIED: SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_READY" ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_READY|%s|", pClient->GetName() ); break; default: PrintfChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_UNKNOWN|%d|", nResult ); if( pInviter ) PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pInviter, "ERROR_JOIN_TARGET_UNKNOWN|%s|%d|", pClient->GetName(), nResult ); break; } } void onBattleArenaExerciseMemberKick( StructPlayer * pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 if( !pClient->IsInParty() ) return; int nPartyID = pClient->GetPartyID(); // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; const char *szTargetName = vToken[1].c_str(); // 그런사람 없으면 리턴(가명 기능은 연습 경기에서는 사용되지 않기 때문에 캐릭터 이름으로 명령어 처리 가능) PlayerUID nPlayerUID = PartyManager::GetInstance().GetMemberUID( nPartyID, szTargetName ); if( !nPlayerUID ) return; // 리더는 스스로를 추방 불가 if( PartyManager::GetInstance().IsLeader( nPartyID, nPlayerUID ) ) return; // 배틀 아레나 연습 경기용 파티가 아니라면 추방 불가 PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID ); if( ePartyType != PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM ) return; AR_HANDLE hTargetPlayer = StructPlayer::FindPlayer( szTargetName ); StructPlayer::iterator it = StructPlayer::get( hTargetPlayer ); StructPlayer *pTarget = (*it); unsigned short nResult = RESULT_SUCCESS; if( pTarget ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pTarget ) ); nResult = BattleArenaManager::Instance().QuitGame( pTarget, true, false, ALT_EXERCISE_PARTY_KICK ); } else nResult = BattleArenaManager::Instance().QuitGame( nPartyID, nPlayerUID, true, ALT_EXERCISE_PARTY_KICK ); // nResult == RESULT_SUCCESS 라면 이미 파티 이탈 관련 패킷들이 방송되었을 거라서 그 외의 경우에만 방송 처리 if( nResult != RESULT_SUCCESS ) { //SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_NOT_EXIST" ); return; } std::string partyName( PartyManager::GetInstance().GetPartyName( nPartyID ) ); PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient->GetPartyID(), "KICK|%s|%s|", partyName.c_str(), szTargetName ); LOG::Log11N4S( LM_PARTY_KICK, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget ? pTarget->GetAccountID() : 0, pTarget ? pTarget->GetSID() : 0, pTarget ? pTarget->GetX() : 0, pTarget ? pTarget->GetY() : 0, pTarget ? pTarget->GetLayer() : 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget ? pTarget->GetAccountName() : "", pTarget ? LOG::STR_NTS : 0, szTargetName, LOG::STR_NTS ); } void onPartyCreate( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; XStringUtil::Trim( vToken[1] ); const char *pPartyName = vToken[1].c_str(); if( !pPartyName ) return; size_t partyLen = strlen( pPartyName ); if( partyLen > 30 ) return; // 헌터홀릭 던전 및 대기실 안에 있으면 파티 생성 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_HUNTAHOLIC" ); return; } // 인스턴스 던전 안에 있으면 파티 생성 불가 if( pClient->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_INSTANCE_DUNGEON" ); return; } #ifndef _alucard // 데스매치 맵에 있으면 파티 생성 불가 if( pClient->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); return; } #endif // 아레나 대기열 또는 경기에 참여 중이면 파티 생성 불가 if( pClient->GetBattleArenaID() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_BATTLE_ARENA" ); return; } // 이미 파티에 가입되어 있으면.. if( pClient->IsInParty() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_ALREADY_PARTY_MEMBER" ); return; } // 나쁜 파티 이름 int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, pPartyName, static_cast< int >( partyLen ) + 1, 1, 30 ) || GameContent::IsBannedWord( code_page, pPartyName ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "INVALID_PARTY_NAME" ); return; } // 파티 만들고 int nPartyID = PartyManager::GetInstance().MakeParty( pPartyName, pClient->GetPlayerUID(), PartyManager::TYPE_NORMAL_PARTY ); if( nPartyID <= 0 ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ALREADY_EXIST" ); return; } // 리더를 참여시킨다. if( !PartyManager::GetInstance().JoinParty( nPartyID, pClient ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "CANT_JOIN" ); return; } PrintfChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "CREATE|%s|%s|%d|", pPartyName, pClient->GetName(), PartyManager::TYPE_NORMAL_PARTY ); SendPartyInfo( pClient ); // 소환수 정보는 파티 타입에 관계없이 동일한 규칙에 의해 방송되므로 별도 처리한다. SendSummonInfo( pClient ); LOG::Log11N4S( LM_PARTY_CREATE, pClient->GetAccountID(), pClient->GetSID(), pClient->GetPartyID(), pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, PartyManager::TYPE_NORMAL_PARTY, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pPartyName, LOG::STR_NTS, "", 0 ); } void onPartyShareMode( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; int nFlag = 0; int nPartyID = pClient->GetPartyID(); // 파티 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; #ifndef _alucard // 헌터홀릭 파티면 파티나 배틀 아레나 파티 설정 변경 불가 if( PartyManager::GetInstance().GetPartyType( nPartyID ) == PartyManager::TYPE_HUNTAHOLIC_PARTY || PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) return; // 데스매치 맵에 있으면 파티 설정 변경 불가 if( pClient->IsInDeathmatch() ) return; #else // 헌터홀릭 파티면 파티나 배틀 아레나 파티 설정 변경 불가 if( PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) return; #endif if( vToken[1] == "monopoly" ) { nFlag = 0; PartyManager::GetInstance().SetShareMode( nPartyID, PartyManager::ITEM_SHARE_MONOPOLY ); } else if( vToken[1] == "random" ) { nFlag = 1; PartyManager::GetInstance().SetShareMode( nPartyID, PartyManager::ITEM_SHARE_RANDOM ); } else if( vToken[1] == "linear" ) { nFlag = 2; PartyManager::GetInstance().SetShareMode( nPartyID, PartyManager::ITEM_SHARE_LINEAR ); } PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, nPartyID, "MODE|%d|", nFlag ); } void onPartyDestroy( StructPlayer* pClient, std::vector< std::string > & vToken ) { int nPartyID = pClient->GetPartyID(); // 파티가 아니면 리턴 if( !pClient->IsInParty() ) return; // 파티 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( pClient->GetPartyID(), pClient->GetPlayerUID() ) ) return; int nLeaderPartyID = PartyManager::GetInstance().GetLinkedPartyLeadPartyID( nPartyID ); if( PartyManager::GetInstance().IsSomeoneInSiegeOrRaidDungeon( nPartyID ) || ( nLeaderPartyID == nPartyID && DungeonManager::Instance().IsRaidBegin( GuildManager::GetInstance().GetRaidDungeonID( pClient->GetGuildID() ), pClient->GetGuildID() ) ) ) return; // 헌터홀릭 파티나 배틀 아레나 파티면 명령어에 의한 파티 해산 불가 if( PartyManager::GetInstance().GetPartyType( nPartyID ) == PartyManager::TYPE_HUNTAHOLIC_PARTY || PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) return; // 인스턴스 던전 안에 있으면 파티 해산 불가 if( pClient->IsInInstanceDungeon() ) return; // 인스턴스 던전 안에 파티원이 남아 있으면 파티 해산 불가 // 파장이 인던에 유저가 있을 때 파티에서 나가고 싶으면 아무한테나 파장 인계하고 탈퇴로 나가야 함 struct MemberInInstanceDungeonChecker : public PartyManager::PartyFunctor { MemberInInstanceDungeonChecker() : bSomeoneInInstanceDungeon( false ) {} virtual bool operator()( AR_HANDLE handle ) { // 한 명이라도 인던 내부에 누가 있었던 게 확인됐다면 바로 패스 if( bSomeoneInInstanceDungeon ) return true; StructPlayer * pPlayer = static_cast< StructPlayer * >( GameObject::raw_get( handle ) ); if( !pPlayer ) return true; bSomeoneInInstanceDungeon = pPlayer->IsInInstanceDungeon(); return true; } bool bSomeoneInInstanceDungeon; } foChecker; PartyManager::GetInstance().DoEachMember( nPartyID, foChecker ); if( foChecker.bSomeoneInInstanceDungeon ) return; #ifndef _alucard // 데스매치 맵에 있으면 파티 해산 불가 if( pClient->IsInDeathmatch() ) return; #endif LOG::Log11N4S( LM_PARTY_DESTROY, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 ); BroadcastPartyDestroy( nPartyID ); PartyManager::GetInstance().DestroyParty( nPartyID ); } void onPartyInvite( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 int nPartyID = pClient->GetPartyID(); if( !nPartyID ) return; std::string partyName( PartyManager::GetInstance().GetPartyName( nPartyID ) ); // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) return; // 헌터홀릭용 파티나 배틀 아레나용 파티면 명령어에 의한 초대 불가 PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID ); if( ePartyType == PartyManager::TYPE_HUNTAHOLIC_PARTY || PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) return; // 대상이 헌터홀릭 던전 및 대기실 안에 있으면 초대 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pTarget->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_HUNTAHOLIC" ); return; } // 대상이 인스턴스 던전 안에 있으면 초대 불가, // 파티장이 인스턴스 던전 내부에서 초대가 가능 if( pTarget->IsInInstanceDungeon() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_INSTANCE_DUNGEON" ); return; } #ifndef _alucard // 파티장 또는 대상이 데스매치 맵에 있으면 초대 불가 if( pClient->IsInDeathmatch() ) return; if( pTarget->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); return; } #endif // 대상이 아레나 대기열 또는 경기에 참여 중이면 초대 불가 if( pTarget->GetBattleArenaID() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_TARGET_IN_BATTLE_ARENA" ); return; } // PK 룰 체크 if( !pClient->IsPartyInvitable( pTarget ) ) { SendChatMessage(false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_PKRULE"); return; } // 파티 쪽수 제한 if( PartyManager::GetInstance().GetMemberCount( nPartyID ) >= PartyManager::MAX_PARTY_MEMBER ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_MAX" ); return; } // 파티에 가입 되어 있으면 if( pTarget->IsInParty() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_ANOTHER" ); return; } if( PartyManager::GetInstance().IsAttackTeamParty( nPartyID ) ) { int nAttackTeamGuildID = PartyManager::GetInstance().GetAttackTeamGuildID( nPartyID ); if( !pTarget->IsInGuild() || ( pTarget->GetGuildID() != nAttackTeamGuildID && ( !GuildManager::GetInstance().GetAllianceID( pTarget->GetGuildID() ) || GuildManager::GetInstance().GetAllianceID( pTarget->GetGuildID() ) != GuildManager::GetInstance().GetAllianceID( nAttackTeamGuildID ) ) ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_NOT_GUILD_MEMBER" ); return; } } LOG::Log11N4S( LM_PARTY_INVITE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget->GetAccountID(), pTarget->GetSID(), pTarget->GetX(), pTarget->GetY(), pTarget->GetLayer(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget->GetAccountName(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); PrintfChatMessage( true, CHAT_PARTY_SYSTEM, "@PARTY", pTarget, "INVITE|%s|%s|%d|%d|", pClient->GetName(), partyName.c_str(), nPartyID, PartyManager::GetInstance().GetPartyPassword( nPartyID ) ); } void onPartyJoin( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티에 가입 되어 있으면 if( pClient->IsInParty() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_YOU_CAN_JOIN_ONLY_ONE_PARTY" ); return; } int nPartyID = atoi( vToken[1].c_str() ) ; std::string partyName( PartyManager::GetInstance().GetPartyName( nPartyID ) ); // 없는 파티이면 if( partyName.empty() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_INVALID_PARTY" ); return; } // 헌터홀릭 던전 및 대기실 안에 있으면 파티 가입 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_HUNTAHOLIC" ); return; } // 헌터홀릭용 파티나 배틀 아레나용 파티면 초대에 의한 가입 불가 PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID ); if( ePartyType == PartyManager::TYPE_HUNTAHOLIC_PARTY || PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) { return; } // 인스턴스 던전 안에 있으면 파티 가입 불가 if( InstanceDungeonManager::Instance().GetInstanceDungeonID( pClient->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_INSTANCE_DUNGEON" ); return; } #ifndef _Alucard // 데스매치 맵에 있으면 파티 가입 불가 if( pClient->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); return; } #endif // 아레나 대기열 또는 경기에 참여 중이면 가입 불가 if( pClient->GetBattleArenaID() ) { SendChatMessage( false, CHAT_RAID_SYSTEM, "@RAID", pClient, "ERROR_IN_BATTLE_ARENA" ); return; } // 수락 거부 메세지를 마스터에게 보내야 함 if( vToken.size() <= 2 ) { return; } // 암호가 다르면 if( atoi( vToken[2].c_str() ) != PartyManager::GetInstance().GetPartyPassword( nPartyID ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "HAS_NO_AUTHORITY" ); return; } if( PartyManager::GetInstance().IsAttackTeamParty( nPartyID ) ) { int nAttackTeamGuildID = PartyManager::GetInstance().GetAttackTeamGuildID( nPartyID ); if( !pClient->IsInGuild() || ( pClient->GetGuildID() != nAttackTeamGuildID && ( !GuildManager::GetInstance().GetAllianceID( pClient->GetGuildID() ) || GuildManager::GetInstance().GetAllianceID( pClient->GetGuildID() ) != GuildManager::GetInstance().GetAllianceID( nAttackTeamGuildID ) ) ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_NOT_GUILD_MEMBER" ); return; } } // 파티 쪽수 제한 if( PartyManager::GetInstance().GetMemberCount( pClient->GetPartyID() ) >= PartyManager::MAX_PARTY_MEMBER ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_MAX" ); return; } if( !PartyManager::GetInstance().JoinParty( nPartyID, pClient ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "CANT_JOIN" ); return; } PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, nPartyID, "NEW|%s|", pClient->GetName() ); PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nPartyID, "JOIN|%s|%s|", partyName.c_str(), pClient->GetName() ); SendPartyInfo( pClient ); SendLinkedPartyInfo( pClient ); BroadcastPartyMemberInfo( nPartyID, pClient ); BroadcastLinkedPartyMemberInfo( pClient->GetPartyID(), pClient ); SendSummonInfo( pClient ); BroadcastSummonInfo( pClient->GetPartyID(), pClient ); LOG::Log11N4S( LM_PARTY_JOIN, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, partyName.c_str(), LOG::STR_NTS, "", 0 ); } void onPartyKick( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 int nPartyID = pClient->GetPartyID(); if( !nPartyID ) return; // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; const char *szTargetName = vToken[1].c_str(); // 그런사람 없으면 리턴 PlayerUID nPlayerUID = PartyManager::GetInstance().GetMemberUID( nPartyID, szTargetName ); if( !nPlayerUID ) return; // 리더는 스스로를 추방 불가 if( PartyManager::GetInstance().IsLeader( nPartyID, nPlayerUID ) ) return; // 레이드 파티이면서 던전 레이드/시즈 안에 누군가 있으면 킥 불가능 if( PartyManager::GetInstance().IsSomeoneInSiegeOrRaidDungeon( nPartyID ) ) return; std::string partyName( PartyManager::GetInstance().GetPartyName( nPartyID ) ); AR_HANDLE hTargetPlayer = StructPlayer::FindPlayer( szTargetName ); StructPlayer::iterator it = StructPlayer::get( hTargetPlayer ); StructPlayer *pTarget = (*it); if( pTarget && pTarget->IsInSiegeOrRaidDungeon() ) { return; } // 헌터홀릭용 파티나 배틀 아레나용 파티면 파티 추방 불가 PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID ); if( ePartyType == PartyManager::TYPE_HUNTAHOLIC_PARTY || PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) return; // 대상이 인스턴스 던전 안에 있으면 추방 불가 if( pTarget && pTarget->IsInInstanceDungeon() ) return; #ifndef _Alucard // 데스매치 맵에 있으면 파티 추방 불가 if( pClient->IsInDeathmatch() ) return; if( pTarget && pTarget->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); return; } #endif PrintfLinkedPartyChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", nPartyID, "KICK|%s|%s|", partyName.c_str(), szTargetName ); PartyManager::GetInstance().LeaveParty( nPartyID, nPlayerUID ); LOG::Log11N4S( LM_PARTY_KICK, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget ? pTarget->GetAccountID() : 0, pTarget ? pTarget->GetSID() : 0, pTarget ? pTarget->GetX() : 0, pTarget ? pTarget->GetY() : 0, pTarget ? pTarget->GetLayer() : 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget ? pTarget->GetAccountName() : "", pTarget ? LOG::STR_NTS : 0, szTargetName, LOG::STR_NTS ); } void onPartyLeave( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 파티가 아니면 리턴 if( !pClient->IsInParty() ) return; int nPartyID = pClient->GetPartyID(); // 리더라면 불가 if( PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; // 레이드 파티이면서 던전 레이드/시즈 안에 누군가 있으면 탈퇴 불가능 if( PartyManager::GetInstance().IsSomeoneInSiegeOrRaidDungeon( nPartyID ) ) return; ArPosition pos = pClient->GetPos(); if( pClient->IsInSiegeOrRaidDungeon() ) return; // 헌터홀릭 파티나 배틀 아레나 파티면 명령어에 의한 파티 탈퇴 불가 PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID ); if( ePartyType == PartyManager::TYPE_HUNTAHOLIC_PARTY || PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ) return; // 인스턴스 던전 안에 있으면 파티 탈퇴 불가 if( pClient->IsInInstanceDungeon() ) return; #ifndef _Alucard // 데스매치 맵에 있으면 파티 탈퇴 불가 if( pClient->IsInDeathmatch() ) return; #endif BroadcastPartyLeave( pClient ); std::string partyName( PartyManager::GetInstance().GetPartyName( nPartyID ) ); PartyManager::GetInstance().LeaveParty( nPartyID, pClient->GetPlayerUID() ); LOG::Log11N4S( LM_PARTY_LEAVE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, partyName.c_str(), LOG::STR_NTS, "", 0 ); } void onPartyPromote( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 파티가 아니면 리턴 int nPartyID = pClient->GetPartyID(); if( !nPartyID ) return; // 리더가 아니면 리턴 if( !PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) return; // 시즈/레이드 공대나 헌터홀릭 파티나 배틀 아레나 파티면 파티 인계 불가 PartyManager::_PARTY_TYPE ePartyType = PartyManager::GetInstance().GetPartyType( nPartyID ); if( PartyManager::GetInstance().IsAttackTeamParty( nPartyID ) || ePartyType == PartyManager::TYPE_HUNTAHOLIC_PARTY || ePartyType == PartyManager::TYPE_BATTLE_ARENA_TEAM ) return; // 배틀 아레나 연습 경기용 파티라면 팀이 레디 상태면 인계 불가 if( ePartyType == PartyManager::TYPE_BATTLE_ARENA_EXERCISE_TEAM && BattleArenaManager::Instance().IsReadyExerciseTeam( nPartyID ) ) return; StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 대상이 파티원이 아니면 리턴 if( pTarget->GetPartyID() != nPartyID ) return; // 대상이 헌터홀릭 던전 및 로비에 있으면 파티 인계 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pTarget->GetPos() ) ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_TARGET_IN_HUNTAHOLIC" ); return; } #ifndef _Alucard // 데스매치 맵에 있으면 파티 인계 불가 if( pClient->IsInDeathmatch() ) return; if( pTarget->IsInDeathmatch() ) { SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, "ERROR_IN_DEATHMATCH" ); return; } #endif if( PartyManager::GetInstance().Promote( nPartyID, pTarget ) == false ) { return; } PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, nPartyID, "PROMOTE|%d|%s|", nPartyID, pTarget->GetName() ); LOG::Log11N4S( LM_PARTY_PROMOTE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), pTarget->GetAccountID(), pTarget->GetSID(), pTarget->GetX(), pTarget->GetY(), pTarget->GetLayer(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget->GetAccountName(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); } void onPartyList( StructPlayer* pClient, std::vector< std::string > & vToken ) { struct myPartyFunctor : PartyManager::PartyFunctor { virtual bool operator()( AR_HANDLE handle ) { StructPlayer *pPlayer = static_cast< StructPlayer * >( GameObject::raw_get( handle ) ); if( !pPlayer ) return true; char buf[256]; s_sprintf( buf, _countof( buf ), "%s|%d|%d|", pPlayer->GetName(), pPlayer->GetHPPercentage(), pPlayer->GetMPPercentage() ); strList += buf; return true; } std::string strList; } _fo; PartyManager::GetInstance().DoEachMember( pClient->GetPartyID(), _fo ); char buf[2048]; s_sprintf( buf, _countof( buf ), "LIST|%s|%d|%d|%d|%s", PartyManager::GetInstance().GetPartyName( pClient->GetPartyID() ).c_str(), PartyManager::GetInstance().GetShareMode( pClient->GetPartyID() ), PartyManager::GetInstance().GetMaxLevel( pClient->GetPartyID() ), PartyManager::GetInstance().GetMinLevel( pClient->GetPartyID() ), _fo.strList.c_str() ); SendChatMessage( false, CHAT_PARTY_SYSTEM, "@PARTY", pClient, buf, (unsigned)strlen(buf) ); } void onAllianceInvite( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 길드에 소속되어 있지 않으면 if( !pClient->IsInGuild() ) return; int nAllianceID = GuildManager::GetInstance().GetAllianceID( pClient->GetGuildID() ); if( !nAllianceID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_IN_ALLIANCE" ); return; } std::string allianceName( GuildManager::GetInstance().GetAllianceName( nAllianceID ) ); // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( pClient->GetGuildID(), pClient->GetGuildPermission(), GuildManager::PRA_ALLIANCE_MANAGEMENT ) ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_GUILD_LEADER" ); return; } if( !GuildManager::GetInstance().IsAllianceLeader( nAllianceID, pClient->GetGuildID() ) ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_ALLIANCE_LEADER" ); return; } // 연합 최대 길드 수 제한 체크 if( GuildManager::GetInstance().GetMaxAllianceCount( nAllianceID ) <= GuildManager::GetInstance().GetAllianceMemberGuildCount( nAllianceID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_LIMIT_EXCEED" ); return; } // 던전 소유 중인 연합은 초대 불가 int nMasterGuildID = GuildManager::GetInstance().GetAllianceLeaderGuildID( nAllianceID ); if( DungeonManager::Instance().GetOwnGuildID( GuildManager::GetInstance().GetRaidDungeonID( nMasterGuildID ) ) == nMasterGuildID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_RAID" ); return; } // 유효하지 않은 길드는 초대 불가 int nGuildId = GuildManager::GetInstance().GetGuildID( vToken[1].c_str() ); if( !nGuildId ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_INVALID_GUILD" ); return; } // 던전 레이드 신청 상태 또는 던전 소유 중인 길드를 초대하는 것은 불가 int nDungeonID = GuildManager::GetInstance().GetRaidDungeonID( nGuildId ); if( nDungeonID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_RAID" ); return; } // 다른 연합에 소속되어 있는 길드를 초대하는 것은 불가 if( GuildManager::GetInstance().GetAllianceID( nGuildId ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_ALREADY_IN_ALLIANCE" ); return; } // 다른 연합에서 탈퇴/해산한 지 7일이 지나지 않은 길드를 초대하는 것은 불가 if( GuildManager::GetInstance().IsInGuildAllianceBlockTime( nGuildId ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_ALLIANCE_BLOCK_TIME_LIMIT" ); return; } std::string strTargetLeaderName = StructPlayer::GetPlayerName( GuildManager::GetInstance().GetLeaderSID( nGuildId ) ); StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( strTargetLeaderName.c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_ONLINE" ); return; } PrintfChatMessage( true, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pTarget, "INVITE|%s|%s|%d|%d|", pClient->GetName(), allianceName.c_str(), nAllianceID, GuildManager::GetInstance().GetAlliancePassword( nAllianceID ) ); } void onAllianceKick( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 길드가 아니면 리턴 int nGuildID = pClient->GetGuildID(); if( !nGuildID ) return; int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); if( !nAllianceID ) return; std::string allianceName( GuildManager::GetInstance().GetAllianceName( nAllianceID ) ); // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_ALLIANCE_MANAGEMENT ) ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_GUILD_LEADER" ); return; } if( !GuildManager::GetInstance().IsAllianceLeader( nAllianceID, nGuildID ) ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_ALLIANCE_LEADER_NOT_LEAVABLE" ); return; } // 던전 소유 연합은 멤버 길드 추방 불가 int nDungeonID = GuildManager::GetInstance().GetRaidDungeonID( nGuildID ); if( nDungeonID && DungeonManager::Instance().GetOwnGuildID( nDungeonID ) == nGuildID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_RAID" ); return; } const char *szGuildName = vToken[1].c_str(); int nTargetGuildID = GuildManager::GetInstance().GetGuildID( szGuildName ); if( !nTargetGuildID || nAllianceID != GuildManager::GetInstance().GetAllianceID( nTargetGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_IN_ALLIANCE" ); return; } if( nTargetGuildID == nGuildID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_GUILD_MASTER" ); return; } // 탈퇴할 길드가 공대 파티를 결성했으면 탈퇴 불가(한 명이라도 공대 파티에 소속이 되어있는지와 동일) if( PartyManager::GetInstance().IsExistAttackTeam( nTargetGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "IS_IN_RAID_PARTY" ); return; } // 탈퇴할 길드의 길드원 중 한 명이라도 던전 시즈 또는 레이드에 입장해 있으면 탈퇴 불가 struct GuildFunctorIsInSiegeOrRaid : GuildManager::GuildFunctor { GuildFunctorIsInSiegeOrRaid() : bSomeoneInSiegeOrRaid( false ) {} virtual bool operator()( AR_HANDLE handle ) { if( bSomeoneInSiegeOrRaid ) return false; StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( pPlayer && pPlayer->IsInWorld() && pPlayer->IsInSiegeOrRaidDungeon() ) { bSomeoneInSiegeOrRaid = true; return false; } return true; } bool bSomeoneInSiegeOrRaid; } _foIsInSiegeOrRaid; GuildManager::GetInstance().DoEachMember( nTargetGuildID, _foIsInSiegeOrRaid ); if( _foIsInSiegeOrRaid.bSomeoneInSiegeOrRaid ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IN_SIEGE_OR_RAID" ); return; } if( !GuildManager::GetInstance().LeaveAlliance( nTargetGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_CANT_KICK" ); return; } PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", nAllianceID, "KICK|%s|%s|", allianceName.c_str(), szGuildName ); PrintfGuildChatMessage( CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", nTargetGuildID, "KICK|%s|%s|", allianceName.c_str(), szGuildName ); BroadcastAllianceInfo( nAllianceID ); LOG::Log11N4S( LM_ALLIANCE_KICK, pClient->GetAccountID(), pClient->GetSID(), 0, nTargetGuildID, nAllianceID, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, szGuildName, LOG::STR_NTS, allianceName.c_str(), LOG::STR_NTS ); } void onAllianceLeave( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 길드가 아니면 리턴 int nGuildID = pClient->GetGuildID(); if( !nGuildID ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_IN_GUILD" ); return; } int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); if( !nAllianceID ) return; int nLeadGuildID = GuildManager::GetInstance().GetAllianceLeaderGuildID( nAllianceID ); // 연합 리더는 탈퇴 불가 if( nGuildID == nLeadGuildID ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_ALLIANCE_LEADER_NOT_LEAVABLE" ); return; } // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_ALLIANCE_LEAVE ) ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_GUILD_LEADER" ); return; } // 던전 주인인 연합에서는 탈퇴 불가 int nDungeonID = GuildManager::GetInstance().GetRaidDungeonID( nLeadGuildID ); if( nDungeonID && DungeonManager::Instance().GetOwnGuildID( nDungeonID ) == nLeadGuildID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_RAID" ); return; } // 탈퇴할 길드가 공대 파티를 결성했으면 탈퇴 불가(한 명이라도 공대 파티에 소속이 되어있는지와 동일) if( PartyManager::GetInstance().IsExistAttackTeam( nGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "IS_IN_RAID_PARTY" ); return; } // 탈퇴할 길드의 길드원 중 한 명이라도 던전 시즈 또는 레이드에 입장해 있으면 탈퇴 불가 struct GuildFunctorIsInSiegeOrRaid : GuildManager::GuildFunctor { GuildFunctorIsInSiegeOrRaid() : bSomeoneInSiegeOrRaid( false ) {} virtual bool operator()( AR_HANDLE handle ) { if( bSomeoneInSiegeOrRaid ) return false; StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( pPlayer && pPlayer->IsInWorld() && pPlayer->IsInSiegeOrRaidDungeon() ) { bSomeoneInSiegeOrRaid = true; return false; } return true; } bool bSomeoneInSiegeOrRaid; } _foIsInSiegeOrRaid; GuildManager::GetInstance().DoEachMember( nGuildID, _foIsInSiegeOrRaid ); if( _foIsInSiegeOrRaid.bSomeoneInSiegeOrRaid ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IN_SIEGE_OR_RAID" ); return; } std::string strGuildName = GuildManager::GetInstance().GetGuildName( nGuildID ); std::string strAllianceName( GuildManager::GetInstance().GetAllianceName( nAllianceID ) ); if( !GuildManager::GetInstance().LeaveAlliance( nGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_CANT_LEAVE" ); return; } PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", nAllianceID, "LEAVE|%s|%s|", strAllianceName.c_str(), strGuildName.c_str() ); PrintfGuildChatMessage( CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", nGuildID, "LEAVE|%s|%s|", strAllianceName.c_str(), strGuildName.c_str() ); BroadcastAllianceInfo( nAllianceID ); LOG::Log11N4S( LM_ALLIANCE_LEAVE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, nAllianceID, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, strGuildName.c_str(), LOG::STR_NTS, strAllianceName.c_str(), LOG::STR_NTS ); } void onAllianceJoin( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 길드에 가입 되어 있지 않으면 int nGuildID = pClient->GetGuildID(); if( !nGuildID ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_NOT_IN_GUILD" ); return; } // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_ALLIANCE_JOIN ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_PERMISSION" ); return; } // 던전 레이드 신청 상태 또는 던전 소유 중이면 가입 불가 int nDungeonID = GuildManager::GetInstance().GetRaidDungeonID( nGuildID ); if( nDungeonID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_RAID" ); return; } // 다른 연합에 소속되어 있으면 가입 불가 if( GuildManager::GetInstance().GetAllianceID( nGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_ALREADY_IN_ALLIANCE" ); return; } // 다른 연합에서 탈퇴/해산한 지 7일이 지나지 않았으면 가입 불가 if( GuildManager::GetInstance().IsInGuildAllianceBlockTime( nGuildID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_ALLIANCE_BLOCK_TIME_LIMIT" ); return; } // 던전 소유권을 포기한 지 7일이 지나지 않았으면 가입 불가 if( GuildManager::GetInstance().GetDungeonBlockTime( nGuildID ) ) { //SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_DUNGEON_BLOCK_TIME_LIMIT" ); return; } int nAllianceID = atoi( vToken[1].c_str() ) ; std::string allianceName( GuildManager::GetInstance().GetAllianceName( nAllianceID ) ); std::string guildName( GuildManager::GetInstance().GetGuildName( nGuildID ) ); // 유효하지 않은 연합에 가입 불가 if( allianceName.empty() ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_INVALID_ALLIANCE" ); return; } // 연합 최대 길드 수 제한 체크 if( GuildManager::GetInstance().GetMaxAllianceCount( nAllianceID ) <= GuildManager::GetInstance().GetAllianceMemberGuildCount( nAllianceID ) ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_LIMIT_EXCEED" ); return; } // 던전 소유 중인 연합에 가입 불가 int nMasterGuildID = GuildManager::GetInstance().GetAllianceLeaderGuildID( nAllianceID ); if( DungeonManager::Instance().GetOwnGuildID( GuildManager::GetInstance().GetRaidDungeonID( nMasterGuildID ) ) == nMasterGuildID ) { SendChatMessage( false, CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", pClient, "ERROR_IS_RAID" ); return; } if( vToken.size() > 2 ) { // 암호가 다르면 if( atoi( vToken[2].c_str() ) != GuildManager::GetInstance().GetAlliancePassword( nAllianceID ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_PERMISSION_DENIED" ); return; } if( !GuildManager::GetInstance().AddToAlliance( nAllianceID, nGuildID ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_CANT_JOIN_ALLIANCE" ); return; } PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, "@ALLIANCE", nAllianceID, "JOIN|%s|%s|", allianceName.c_str(), guildName.c_str() ); BroadcastAllianceInfo( nAllianceID ); } else { // TODO : 수락 거부 메세지를 마스터에게 보내야함. } LOG::Log11N4S( LM_ALLIANCE_JOIN, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, nAllianceID, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, guildName.c_str(), LOG::STR_NTS, allianceName.c_str(), LOG::STR_NTS ); } void onAllianceCreate( StructPlayer* pClient, std::vector< std::string > & vToken ) { if( !pClient->GetLastContactLong( "alliance" ) ) return; // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // Lua injection을 방지하기 위해서 몇 가지 제어 문자가 포함된 경우를 먼저 체크하고 그런 경우는 접속을 끊음 if( vToken[1].find( '\'' ) != std::string::npos || vToken[1].find( '\\' ) != std::string::npos ) { _cprint( "Protecting from lua injection(%s). [%s/%s]\n", vToken[1].c_str(), pClient->GetAccountName(), pClient->GetName() ); FILELOG( "Protecting from lua injection(%s). [%s/%s]", vToken[1].c_str(), pClient->GetAccountName(), pClient->GetName() ); pClient->pConnection->Close(); return; } const char *pAllianceName = vToken[1].c_str(); if( !pAllianceName ) return; size_t allianceLen = strlen( pAllianceName ); if( allianceLen > 30 ) return; // 연합 결성 불가 룰 체크는 SCRIPT_CreateAlliance 함수에서 해야 함 // NPC를 통해서 오류 메시지를 출력해야 하기 때문 std::string strCreateGuildFunction = "on_create_alliance( '"; strCreateGuildFunction += pAllianceName; strCreateGuildFunction += "' )"; int nPrevChaos = pClient->GetChaos(); StructGold nPrevGold( pClient->GetGold() ); LUA()->RunString( strCreateGuildFunction.c_str() ); pClient->SetLastContact( "alliance", 0 ); StructNPC * pNPC = NULL; StructCreature::iterator itNPC = StructCreature::get( pClient->GetContactNPCHandle() ); if( (*itNPC) && (*itNPC)->IsNPC() ) pNPC = static_cast< StructNPC * >( *itNPC ); LOG::Log11N4S( LM_NPC_PROCESS, pClient->GetAccountID(), pClient->GetSID(), 0, ( pNPC ) ? pNPC->GetNPCID() : 0, nPrevChaos, nPrevGold.GetRawData(), pClient->GetChaos(), pClient->GetGold().GetRawData(), pClient->GetX(), pClient->GetY(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, strCreateGuildFunction.c_str(), LOG::STR_NTS ); pClient->Save( true ); } void onGuildBuff( StructPlayer* pClient, std::vector< std::string >& vToken ) { if( vToken.size() < 2 ) return; int nGuildID = pClient->GetGuildID(); if( !nGuildID || !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_BUFF ) ) { SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@767" ); return; } int nPoint = atol( vToken[ 1 ].c_str() ); if( nPoint == 0 ) return; if( !GuildManager::GetInstance().Buff( nGuildID, nPoint, pClient ) ) return; SendChatMessage(false, CHAT_NOTICE, "@NOTICE", pClient, "GUILD BUFF"); int nGuildGrade = GuildManager::GetInstance().GetGuildGrade( nGuildID ); int nGuildPoint = GuildManager::GetInstance().GetGuildPoint( nGuildID ); PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "GBUFF|%d|%d|%s|%d|%d|%d", nGuildGrade, nGuildPoint, pClient->GetName(), pClient->GetGuildPoint(), pClient->GetGuildTotalPoint(), nPoint ); LOG::Log11N4S( LM_GUILD_BUFF, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, nGuildGrade, nGuildPoint, pClient->GetGuildPoint(), pClient->GetGuildTotalPoint(), nPoint, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, "", LOG::STR_NTS ); } void onGuildCreate( StructPlayer* pClient, std::vector< std::string > & vToken ) { if( !pClient->GetLastContactLong( "guild" ) ) return; // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // Lua injection을 방지하기 위해서 몇 가지 제어 문자가 포함된 경우를 먼저 체크하고 그런 경우는 접속을 끊음 if( vToken[1].find( '\'' ) != std::string::npos || vToken[1].find( '\\' ) != std::string::npos ) { _cprint( "Protecting from lua injection(%s). [%s/%s]\n", vToken[1].c_str(), pClient->GetAccountName(), pClient->GetName() ); FILELOG( "Protecting from lua injection(%s). [%s/%s]", vToken[1].c_str(), pClient->GetAccountName(), pClient->GetName() ); pClient->pConnection->Close(); return; } const char *pGuildName = vToken[1].c_str(); size_t guildLen = vToken[1].length(); if( guildLen > 30 ) return; // 길드 결성 불가 룰 체크는 SCRIPT_CreateGuild 함수에서 해야 함 // NPC를 통해서 오류 메시지를 출력해야 하기 때문 std::string strCreateGuildFunction = "on_create_guild( '"; strCreateGuildFunction += pGuildName; strCreateGuildFunction += "' )"; int nPrevChaos = pClient->GetChaos(); StructGold nPrevGold( pClient->GetGold() ); LUA()->RunString( strCreateGuildFunction.c_str() ); pClient->SetLastContact( "guild", 0 ); StructNPC * pNPC = NULL; StructCreature::iterator itNPC = StructCreature::get( pClient->GetContactNPCHandle() ); if( (*itNPC) && (*itNPC)->IsNPC() ) pNPC = static_cast< StructNPC * >( *itNPC ); LOG::Log11N4S( LM_NPC_PROCESS, pClient->GetAccountID(), pClient->GetSID(), 0, ( pNPC ) ? pNPC->GetNPCID() : 0, nPrevChaos, nPrevGold.GetRawData(), pClient->GetChaos(), pClient->GetGold().GetRawData(), pClient->GetX(), pClient->GetY(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, strCreateGuildFunction.c_str(), LOG::STR_NTS ); // 길드장이 비정상 종료 되도 길드는 유지되도록 길드장을 저장함 pClient->Save( true ); } void onGuildDestroy( StructPlayer* pClient, std::vector< std::string > & vToken ) { int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) { return; } // 길드 리더가 아니면 리턴 if( !GuildManager::GetInstance().IsLeader( nGuildID, pClient->GetPlayerUID() ) ) { return; } if( GuildManager::GetInstance().GetAllianceID( nGuildID ) ) { SendChatMessage(false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_DESTROY_IN_ALLIANCE"); return; } if( PartyManager::GetInstance().IsExistAttackTeam( nGuildID ) ) { SendChatMessage(false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_DESTROY_IN_ATTACK_TEAM"); return; } int nDungeonID = GuildManager::GetInstance().GetRaidDungeonID( nGuildID ); if( nDungeonID ) { if( nGuildID != DungeonManager::Instance().GetOwnGuildID( nDungeonID ) ) { SendChatMessage(false, CHAT_NOTICE, "@NOTICE", pClient, "@643"); return; } else { SendChatMessage(false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_DESTROY_DUNGEON_OWNER"); return; } } struct myGuildFunctor : GuildManager::GuildFunctor { virtual bool operator()( AR_HANDLE handle ) { StructPlayer *pPlayer = static_cast< StructPlayer * >( GameObject::raw_get( handle ) ); if( !pPlayer ) return true; PrintfChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pPlayer, "DESTROY|%s|", GuildManager::GetInstance().GetGuildName( pPlayer->GetGuildID() ).c_str() ); return true; } } _fo; LOG::Log11N4S( LM_GUILD_DESTROY, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, 0, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, "", 0 ); GuildManager::GetInstance().DoEachMember( nGuildID, _fo ); GuildManager::GetInstance().DestroyGuild( nGuildID ); char szBuf[255]; s_sprintf( szBuf, _countof( szBuf ), "GDESTROY|%d", nGuildID ); SendGlobalChatMessage( CHAT_GUILD_SYSTEM, "@GUILD", szBuf, static_cast< int >( strlen( szBuf ) ) ); } void onGuildDonate( StructPlayer* pClient, std::vector< std::string >& vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; int nGuildID = pClient->GetGuildID(); if( !nGuildID ) return; int nPoint = atol( vToken[ 1 ].c_str() ); if( nPoint == 0 ) return; if( !GuildManager::GetInstance().Donate( nGuildID, nPoint, pClient ) ) return; int nGuildPoint = GuildManager::GetInstance().GetGuildPoint( nGuildID ); int nGuildGrade = GuildManager::GetInstance().GetGuildGrade( nGuildID ); PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "GDONATE|%d|%d|%s|%d|%d|%d", nGuildGrade, nGuildPoint, pClient->GetName(), pClient->GetGuildPoint(), pClient->GetGuildTotalPoint(), nPoint ); LOG::Log11N4S( LM_GUILD_DONATE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, nGuildGrade, nGuildPoint, pClient->GetGuildPoint(), pClient->GetGuildTotalPoint(), nPoint, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, "", LOG::STR_NTS ); } void onGuildInvite( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 길드가 아니면 리턴 if( !pClient->IsInGuild() ) return; std::string guildName( GuildManager::GetInstance().GetGuildName( pClient->GetGuildID() ) ); // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( pClient->GetGuildID(), pClient->GetGuildPermission(), GuildManager::PRA_MEMBER_INVITE ) ) return; StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_NOT_EXIST" ); return; } // 길드에 가입 되어 있으면 if( pTarget->IsInGuild() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_TARGET_ANOTHER" ); return; } if( pTarget->GetPrevGuildID() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_GUILD_BLOCK_TIME_LIMIT" ); return; } // 길드 쪽수 제한 if( GuildManager::GetInstance().GetMemberCount( pClient->GetGuildID() ) >= GuildManager::MAX_GUILD_MEMBER ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_MAX" ); return; } // PK 룰 체크 if( !pClient->IsGuildInvitable( pTarget ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_PKRULE" ); return; } PrintfChatMessage( true, CHAT_GUILD_SYSTEM, "@GUILD", pTarget, "INVITE|%s|%s|%d|%d|", pClient->GetName(), guildName.c_str(), pClient->GetGuildID(), GuildManager::GetInstance().GetGuildPassword( pClient->GetGuildID() ) ); LOG::Log11N4S( LM_GUILD_INVITE, pClient->GetAccountID(), pClient->GetSID(), 0, pClient->GetGuildID(), pTarget->GetAccountID(), pTarget->GetSID(), 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, guildName.c_str(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); } void onGuildJoin( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 길드에 가입 되어 있으면 if( pClient->IsInGuild() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_YOU_CAN_JOIN_ONLY_ONE_GUILD" ); return; } int nGuildID = atoi( vToken[1].c_str() ) ; // 탈퇴한지 7일이 안지났으면 못 드가 if( pClient->GetPrevGuildID() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_GUILD_BLOCK_TIME_LIMIT" ); return; } std::string guildName( GuildManager::GetInstance().GetGuildName( nGuildID ) ); if( guildName.empty() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_INVALID_GUILD" ); return; } bool bJoined = false; if( vToken.size() > 2 ) { // 암호가 다르면 if( atoi( vToken[2].c_str() ) != GuildManager::GetInstance().GetGuildPassword( nGuildID ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "HAS_NO_AUTHORITY" ); return; } // 길드 쪽수 제한 int nMemberCount = GuildManager::GetInstance().GetMemberCount( pClient->GetGuildID() ); if( nMemberCount >= GuildManager::MAX_GUILD_MEMBER ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_MAX" ); return; } if( GuildManager::GetInstance().JoinGuild( nGuildID, pClient ) ) { bJoined = true; PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "NEW|%s|", pClient->GetName() ); PrintfChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "JOIN|%s|", guildName.c_str() ); SendGuildInfo( pClient ); SendGuildNotify( pClient ); BroadcastGuildMemberInfo( nGuildID, pClient ); int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); if( nAllianceID ) { PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, nAllianceID, "@ALLIANCE", "GMEMBER_CHANGE|%d|%d|", nGuildID, nMemberCount + 1 ); } // 던전 내에서 길드에 가입하였고 그 길드가 던전 소유 길드라면 pClient는 던전 소유 길드원으로서 다시 입장 처리를 해줘야 한다. if( !pClient->GetLayer() ) { int nDungeonID = DungeonManager::Instance().GetDungeonID( pClient->GetPos().x, pClient->GetPos().y ); if( nDungeonID ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); DungeonManager::Instance().OnEnterDungeon( nDungeonID, pClient, pClient->GetLayer() ); } } } } else { // TODO : 수락 거부 메세지를 마스터에게 보내야함. } LOG::Log11N4S( LM_GUILD_JOIN, pClient->GetAccountID(), pClient->GetSID(), 0, pClient->GetGuildID(), ( bJoined ) ? 1 : 2, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, guildName.c_str(), LOG::STR_NTS, "", 0 ); } void onGuildKick( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; // 길드가 아니면 리턴 int nGuildID = pClient->GetGuildID(); if( !nGuildID ) return; // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_MEMBER_KICK ) ) return; const char *szTargetName = vToken[1].c_str(); // 그런 사람 없으면 리턴(권한 검사를 위해 다시 길드 멤버 목록을 돌지 않도록 멤버 IsMember함수를 사용하지 않고 멤버 정보를 얻음) GuildManager::GuildMemberTag gmt( 0, 0, 0, GuildManager::PERMISSION_NONE, "", 0, 0 ); if( !GuildManager::GetInstance().GetMemberInfo( nGuildID, szTargetName, &gmt ) ) return; // 권한이 없으면 리턴(권한 등급 비교) if( gmt.nPermission >= pClient->GetGuildPermission() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_PERMISSION" ); return; } // 길드 리더 스스로 추방 불가 if( !_stricmp( pClient->GetName(), szTargetName ) ) return; StructPlayer::iterator pit = StructPlayer::get( StructPlayer::FindPlayer( szTargetName ) ); StructPlayer *pPtr = *pit; if( pPtr && pPtr->IsInSiegeOrRaidDungeon() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_IN_SIEGE_OR_RAID" ); return; } if( pPtr && pPtr->IsInParty() && PartyManager::GetInstance().IsAttackTeamParty( pPtr->GetPartyID() ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_IN_ATTACK_TEAM" ); return; } std::string guildName( GuildManager::GetInstance().GetGuildName( nGuildID ) ); bool bResult = GuildManager::GetInstance().LeaveGuild( nGuildID, szTargetName ); if( bResult ) { PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "KICK|%s|%s|", guildName.c_str(), szTargetName ); int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); if( nAllianceID ) { PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, nAllianceID, "@ALLIANCE", "GMEMBER_CHANGE|%d|%d|", nGuildID, GuildManager::GetInstance().GetMemberCount( nGuildID ) ); } if( pPtr ) { PrintfChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pPtr, "KICK|%s|%s|", guildName.c_str(), szTargetName ); SendGuildNotify( pPtr ); // 던전 내에서 추방 당했고 추방 당하기 전 소속 길드가 던전 소유 길드였다면 pClient는 던전 소유 길드원으로서 퇴장 처리를 해줘야 한다. if( !pPtr->GetLayer() ) { int nDungeonID = DungeonManager::Instance().GetDungeonID( pPtr->GetPos().x, pPtr->GetPos().y ); if( nDungeonID ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPtr ) ); DungeonManager::Instance().OnExitDungeon( nDungeonID, pPtr, nGuildID, pPtr->GetLayer() ); } } } } LOG::Log11N4S( LM_GUILD_KICK, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, (pPtr) ? pPtr->GetSID() : -1, bResult, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, guildName.c_str(), LOG::STR_NTS, szTargetName, LOG::STR_NTS ); } void onGuildLeave( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 길드가 아니면 리턴 if( !pClient->IsInGuild() ) return; int nGuildID = pClient->GetGuildID(); // 리더라면 불가 if( GuildManager::GetInstance().IsLeader( pClient->GetGuildID(), pClient->GetPlayerUID() ) ) return; std::string guildName( GuildManager::GetInstance().GetGuildName( pClient->GetGuildID() ) ); if( pClient->IsInSiegeOrRaidDungeon() ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "@2802" ); return; } if( pClient->IsInParty() && PartyManager::GetInstance().IsAttackTeamParty( pClient->GetPartyID() ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "@2802" ); return; } bool bResult = GuildManager::GetInstance().LeaveGuild( nGuildID, pClient->GetName() ); if( bResult ) { PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "LEAVE|%s|", pClient->GetName() ); PrintfChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "LEAVE|%s|", pClient->GetName() ); int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); if( nAllianceID ) { PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, nAllianceID, "@ALLIANCE", "GMEMBER_CHANGE|%d|%d|", nGuildID, GuildManager::GetInstance().GetMemberCount( nGuildID ) ); } SendGuildNotify( pClient ); // 던전 내에서 길드 탈퇴를 하였고 탈퇴하기 전 소속 길드가 던전 소유 길드였다면 pClient는 던전 소유 길드원으로서 퇴장 처리를 해줘야 한다. if( !pClient->GetLayer() ) { int nDungeonID = DungeonManager::Instance().GetDungeonID( pClient->GetPos().x, pClient->GetPos().y ); if( nDungeonID ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); DungeonManager::Instance().OnExitDungeon( nDungeonID, pClient, nGuildID, pClient->GetLayer() ); } } } LOG::Log11N4S( LM_GUILD_LEAVE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, 0, bResult, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, guildName.c_str(), LOG::STR_NTS, "", 0 ); } void onGuildPromote( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 2 ) return; int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // 리더가 아니면 리턴 if( !GuildManager::GetInstance().IsLeader( nGuildID, pClient->GetPlayerUID() ) ) return; int dungeon_id = GuildManager::GetInstance().GetRaidDungeonID( nGuildID ); if( dungeon_id ) { // 레이드 기간일 때는 신청 기록 혹은 완료 기록이 있으면 불가능 if( DungeonManager::Instance().IsDungeonRaidTime( dungeon_id ) ) { // 던전 주인이면 신청 기록이 아니므로 가능. 그 외에는 리턴 if( DungeonManager::Instance().GetOwnGuildID( dungeon_id ) != nGuildID ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "@2802" ); return; } } // 레이드 기간이 아니라면 해당 던전의 주인 혹은 시즈 도전자 이므로 해당 던전에 도전자가 있으면 안됨.(시즈 종료 전임) else if( DungeonManager::Instance().GetRaidGuildID( dungeon_id ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "@2802" ); return; } } // 시즈공대 파티가 결성되어있으면 리턴 if( PartyManager::GetInstance().GetAttackTeamLeadPartyIDByGuildID( nGuildID ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "@2802" ); return; } StructPlayer::iterator pit( StructPlayer::get( StructPlayer::FindPlayer( vToken[1].c_str() ) ) ); StructPlayer *pTarget = *pit; // 대상이 없으면 리턴 if( !pTarget ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 대상이 길드원이 아니면 리턴 if( pTarget->GetGuildID() != nGuildID ) return; // 자신에게 인계하려는 거면 리턴 if( pTarget == pClient ) return; if( PartyManager::GetInstance().IsExistAttackTeam( nGuildID ) ) return; bool bResult = GuildManager::GetInstance().Promote( nGuildID, pTarget ); if( bResult ) { SendGuildMemberInfo( pTarget ); SendGuildMemberInfo( pClient ); PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "PROMOTE||%s", pTarget->GetName() ); int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildID ); if( nAllianceID ) { PrintfAllianceChatMessage( CHAT_ALLIANCE_SYSTEM, nAllianceID, "@ALLIANCE", "GLEADER_CHANGE|%d|%s|", nGuildID, pTarget->GetName() ); } } LOG::Log11N4S( LM_GUILD_PROMOTE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, pTarget->GetSID(), 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( pClient->GetGuildID() ).c_str(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS ); } void onGuildPermission( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 3 ) return; int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_GRANT_REVOKE_PERMISSION ) ) return; // 자신의 권한을 변경하려고 하는 거면 리턴 if( !_stricmp( vToken[ 1 ].c_str(), pClient->GetName() ) ) return; char nPermission = static_cast< char >( atoi( vToken[ 2 ].c_str() ) ); // 자신보다 높은 권한으로 대상의 권한을 변경하려고 하면 리턴 if( nPermission > pClient->GetGuildPermission() ) return; // 그런 사람 없으면 리턴(권한 검사를 위해 다시 길드 멤버 목록을 돌지 않도록 멤버 IsMember함수를 사용하지 않고 멤버 정보를 얻음, 로그에 캐릭터 sid도 사용해야 함) GuildManager::GuildMemberTag gmt( 0, 0, 0, GuildManager::PERMISSION_NONE, "", 0, 0 ); if( !GuildManager::GetInstance().GetMemberInfo( nGuildID, vToken[ 1 ].c_str(), &gmt ) ) return; // 대상이 자신과 같거나 더 높은 권한을 가지고 있을 경우에는 리턴 if( gmt.nPermission >= pClient->GetGuildPermission() ) return; if( !GuildManager::GetInstance().SetPermission( nGuildID, vToken[ 1 ].c_str(), nPermission ) ) return; PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "CHANGE_PERMISSION|%s|%d|", vToken[ 1 ].c_str(), nPermission ); LOG::Log11N4S( LM_GUILD_PERMISSION, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, gmt.sid, gmt.nPermission, nPermission, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, vToken[ 1 ].c_str(), LOG::STR_NTS ); } void onGuildPermissionSet( StructPlayer* pClient, std::vector< std::string > & vToken ) { // 인자가 없으면 리턴 if( vToken.size() < 3 ) return; int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // 권한이 없으면 리턴(길마만 가능) if( GuildManager::GetInstance().GetLeaderSID( nGuildID ) != pClient->GetSID() ) return; char nPermission = static_cast< char >( atoi( vToken[ 1 ].c_str() ) ); // 자신의 등급이나 자신보다 높은 등급의 권한을 수정하려고 하면 리턴 if( nPermission >= pClient->GetGuildPermission() ) return; int nPermissionSet = atoi( vToken[ 2 ].c_str() ); // 길마가 아닐 경우 자신이 가지고 있는 권한에 대해서만 변경 가능(기존 설정과 다르게 변경되는 비트가 자신이 보유하고 있는 권한에 포함되는지 체크) if( pClient->GetGuildPermission() != GuildManager::PERMISSION_LEADER ) { int nDifference = nPermissionSet ^ GuildManager::GetInstance().GetPermissionSet( nGuildID, nPermissionSet ); if( ( nDifference & GuildManager::GetInstance().GetPermissionSet( nGuildID, pClient->GetGuildPermission() ) ) != nDifference ) return; } GuildManager::GetInstance().SetPermissionSet( nGuildID, nPermission, nPermissionSet ); PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "CHANGE_PERMISSION_SET|%d|%d|", nPermission, nPermissionSet ); LOG::Log11N4S( LM_GUILD_PERMISSION_SET, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, nPermission, nPermissionSet, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, "", 0 ); } void onGuildPermissionName( StructPlayer* pClient, const std::string & strMessage ) { std::vector< std::string > vToken; XStringUtil::Split( strMessage.c_str(), vToken, " ", false ); // 인자가 없으면 리턴(전체 스트링에서 띄어쓰기를 여러 개 쓴 경우에 대해서도 별도 체크, /gpermissionname) if( vToken.size() < 3 || strMessage[ 16 ] != ' ' || strMessage[ 18 ] != ' ' ) return; int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_SET_PERMISSION_NAME ) ) return; char nPermission = static_cast< char >( atoi( vToken[ 1 ].c_str() ) ); // 길이 체크 const char * pszPermissionName = &( strMessage[ 19 ] ); size_t nLen = strlen( pszPermissionName ); if( nLen < 4 ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_LIMIT_MIN" ); return; } if( nLen > 32 ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_LIMIT_MAX" ); return; } // 금칙어 체크 int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( GameContent::IsBannedWord( code_page, pszPermissionName ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_INVALID_TEXT" ); return; } // 특정 제어문자(|) 포함 여부 체크(유니코드 변환 후 체크해야 함) wchar_t wszPermissionName[ 33 ]; MultiByteToWideChar( code_page, 0, pszPermissionName, -1, wszPermissionName, _countof( wszPermissionName ) ); if( wcschr( wszPermissionName, L'|' ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_INVALID_TEXT" ); return; } GuildManager::GetInstance().SetPermissionName( nGuildID, nPermission, pszPermissionName ); PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "CHANGE_PERMISSION_NAME|%d|%s|", nPermission, pszPermissionName ); LOG::Log11N4S( LM_GUILD_PERMISSION_NAME, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, nPermission, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, pszPermissionName, LOG::STR_NTS ); } void onGuildNotice( StructPlayer* pClient, const std::string & strMessage ) { int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // gnotice 뒤로 공지 내용이 포함되어 있지 않은 길이이거나 공지가 너무 길다면 리턴 if( strMessage.size() < 9 || strMessage.size() > 127 ) return; // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_NOTICE ) ) return; const char * pszNotice = strMessage.c_str() + 9; if( !GuildManager::GetInstance().SetGuildNotice( nGuildID, pszNotice ) ) return; PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "NOTICE|%s", pszNotice ); LOG::Log11N4S( LM_GUILD_NOTICE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, 0, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, pszNotice, LOG::STR_NTS ); } void onGuildURL( StructPlayer* pClient, const std::string & strMessage ) { int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // gurl 뒤로 URL 내용이 포함되어 있지 않은 길이이거나 URL이 너무 길다면 리턴 if( strMessage.size() <= 6 || strMessage.size() > 127 ) return; // 권한이 없으면 리턴 if( !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_NOTICE ) ) return; const char * pszURL = strMessage.c_str() + 6; if( !GuildManager::GetInstance().SetGuildURL( nGuildID, pszURL ) ) return; PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "URL|%s", pszURL ); LOG::Log11N4S( LM_GUILD_NOTICE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, 1, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, pszURL, LOG::STR_NTS ); } void onGuildUpgrade( StructPlayer* pClient, std::vector< std::string >& vToken ) { int nGuildID = pClient->GetGuildID(); if( !nGuildID ) return; int nGuildGrade = GuildManager::GetInstance().GetGuildGrade( nGuildID ); int nGuildPoint = GuildManager::GetInstance().GetGuildPoint( nGuildID ); int nGrade = 1; int nTargetGrade = nGuildGrade + nGrade; int pGrade[11] = { 0, 5000, 10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, -1 }; // 리더가 아니면 리턴 if( !GuildManager::GetInstance().IsLeader( nGuildID, pClient->GetPlayerUID() ) ) return; int nPoint = pGrade[ nGuildGrade ]; if( nPoint == -1 ) return; if( nGuildPoint < nPoint ) { SendChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@764" ); return; } if( !GuildManager::GetInstance().SetGuildGradePoint( nGuildID, nTargetGrade, nGuildPoint - nPoint ) ) return; PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "GUPGRADE|%d|%d|%s|%d|%d|%d", GuildManager::GetInstance().GetGuildGrade( nGuildID ), GuildManager::GetInstance().GetGuildPoint( nGuildID ), pClient->GetName(), pClient->GetGuildPoint(), pClient->GetGuildTotalPoint(), nGrade ); LOG::Log11N4S( LM_GUILD_UPGRADE, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, 0, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, GuildManager::GetInstance().GetGuildName( nGuildID ).c_str(), LOG::STR_NTS, "", LOG::STR_NTS ); } void onGuildMemberMemo( StructPlayer* pClient, const std::string & strMessage ) { int nGuildID = pClient->GetGuildID(); // 길드가 아니면 리턴 if( !nGuildID ) return; // gmemo 뒤의 띄어쓰기 이후에 있는 띄어쓰기를 첫 번째 토큰으로 사용 size_t nTokenIndex = strMessage.find( ' ', 7 ); if( nTokenIndex == std::string::npos || nTokenIndex == 7 ) return; std::string strTargetName = strMessage.substr( 7, nTokenIndex - 7 ); const char * pszMemo = strMessage.c_str() + nTokenIndex + 1; // 권한이 없으면 리턴(자신의 메모는 권한 없이도 변경 가능) if( _stricmp( pClient->GetName(), strTargetName.c_str() ) && !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_MEMBER_MEMO ) ) return; // 길이 체크 size_t nLen = strlen( pszMemo ); if( nLen > 127 ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_LIMIT_MAX" ); return; } // 금칙어 체크 int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( GameContent::IsBannedWord( code_page, pszMemo ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_INVALID_TEXT" ); return; } // 특정 제어문자(|) 포함 여부 체크(유니코드 변환 후 체크해야 함) wchar_t wszMemo[ 129 ]; MultiByteToWideChar( code_page, 0, pszMemo, -1, wszMemo, _countof( wszMemo ) ); if( wcschr( wszMemo, L'|' ) ) { SendChatMessage( false, CHAT_GUILD_SYSTEM, "@GUILD", pClient, "ERROR_INVALID_TEXT" ); return; } if( !GuildManager::GetInstance().SetMemberMemo( nGuildID, strTargetName.c_str(), pszMemo ) ) return; PrintfGuildChatMessage( CHAT_GUILD_SYSTEM, nGuildID, "MEMO|%s|%s|", strTargetName.c_str(), pszMemo ); LOG::Log11N4S( LM_GUILD_MEMO, pClient->GetAccountID(), pClient->GetSID(), 0, nGuildID, 0, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, strTargetName.c_str(), LOG::STR_NTS, pszMemo, LOG::STR_NTS ); } void onGuildRaidSiegeTip( StructPlayer * pClient ) { int nGuildID = pClient->GetGuildID(); if( !nGuildID ) return; SendGuildDungeonRaidSiegeTip( pClient ); } // AziaMafia Perm 0 CMD (actions buttons) void onCheatPlayerbuff(StructPlayer* pClient) { LUA()->RunString("buff()"); } void onCheatPlayerEvent(StructPlayer* pClient) { LUA()->RunString("eventinfo()"); } void onCheatPlayerloot(StructPlayer* pClient) { LUA()->RunString("loot()"); } void onCheatSelectSkin(StructPlayer* pClient) { LUA()->RunString("SelectSkin()"); } void onCheatPlayertame(StructPlayer* pClient) { LUA()->RunString("tame()"); } void onCheatPlayeridk(StructPlayer* pClient) { LUA()->RunString("idk()"); } void onCheatPlayerTeleport(StructPlayer* pClient, std::vector< std::string >& vToken) { if (vToken.size() < 2) { LUA()->RunString("help_cmdp_tp()"); return; } std::string strscript; std::string strscript1 = vToken[1].c_str(); if (strscript1 == "0" || strscript1 == "home") { XStringUtil::Format(strscript, "cmdp_tp( \"%s\" , 0 )", pClient->GetName()); } else if (strscript1 == "1" || strscript1 == "gaia" ) { XStringUtil::Format(strscript, "cmdp_tp( \"%s\" , 1 )", pClient->GetName()); } else if (strscript1 == "2" || strscript1 == "asura") { XStringUtil::Format(strscript, "cmdp_tp( \"%s\" , 2 )", pClient->GetName()); } else if (strscript1 == "3" || strscript1 == "deva") { XStringUtil::Format(strscript, "cmdp_tp( \"%s\" , 3 )", pClient->GetName()); } else if (strscript1 == "5" || strscript1 == "ullr") { XStringUtil::Format(strscript, "cmdp_tp( \"%s\" , 5 )", pClient->GetName()); } else if (strscript1 == "6" || strscript1 == "pet") { XStringUtil::Format(strscript, "cmdp_tp( \"%s\" , 6 )", pClient->GetName()); } else { LUA()->RunString("help_cmdp_tp()"); } LUA()->RunString(strscript.c_str()); return; } void onCheatCodeAntiAfk(StructPlayer* pClient, std::vector< std::string >& vToken) { if (vToken.size() < 2) { LUA()->RunString("cmdp_code_reseend()"); return; } std::string strscript; std::string strscript1 = vToken[1].c_str(); XStringUtil::Format(strscript, "cmdp_code( \"%s\" , \"%s\" )", pClient->GetName(), strscript1 ); LUA()->RunString(strscript.c_str()); return; } void onCheatAfkBonus(StructPlayer* pClient, std::vector< std::string >& vToken) { if (vToken.size() < 2) { LUA()->RunString("cmdp_afk_reseend()"); return; } std::string strscript; std::string strscript1 = vToken[1].c_str(); XStringUtil::Format(strscript, "cmdp_afk( \"%s\" , \"%s\" )", pClient->GetName(), strscript1); LUA()->RunString(strscript.c_str()); return; } void onCheatPosition( StructPlayer* pClient ) { ArPosition pos = pClient->GetCurrentPosition( GetArTime() ); char buf[100] = { 0, }; if( pClient->IsInInstanceDungeon() ) { int nInstanceDungeonID = InstanceDungeonManager::Instance().GetInstanceDungeonID( pos ); int nDifficulty = InstanceDungeonManager::Instance().GetInstanceDungeonDifficulty( nInstanceDungeonID, pClient->GetLayer() ); // 유저 입장에서 단계는 0이 1단계이므로 +1 s_sprintf( buf, _countof( buf ), "
X: %d, Y: %d, Layer: %d, Difficulty: %d
", (int)pos.x, (int)pos.y, pClient->GetLayer(), nDifficulty + 1 ); } else { s_sprintf( buf, _countof( buf ), "
X: %d, Y: %d, Layer: %d
", (int)pos.x, (int)pos.y, pClient->GetLayer() ); } SendChatMessage( false, CHAT_EXP, "@SYSTEM", pClient, buf, (unsigned)strlen(buf) ); } void onTakeoutCommercialItem( StructPlayer * pClient, TS_CS_TAKEOUT_COMMERCIAL_ITEM * pMsg ) { if( GameRule::bIsCashUsableServer ) { if( pMsg->count > 0 ) { pClient->DBQuery( new ::DB_TakeOutCommercialItem( pClient, pMsg->commercial_item_uid, pMsg->count ) ); } } } void onOpenCommercialItemStorage( StructPlayer * pClient ) { if( GameRule::bIsCashUsableServer ) pClient->DBQuery( new DB_ReadCommercialStorageList( pClient ) ); } void onOpenItemShop( StructPlayer * pClient ) { TS_SC_OPEN_ITEM_SHOP msg; msg.client_id = pClient->GetSID(); msg.account_id = pClient->GetAccountID(); msg.one_time_password = XRandom() % 0x00ffffff; s_strcpy( msg.raw_server_name, _countof( msg.raw_server_name ), ENV().GetString( "app.name", "Unknown" ).c_str() ); UpdateOTPCF_PendMessage * pFo = new UpdateOTPCF_PendMessage( pClient, &msg ); DB().Push( new DB_UpdateOneTimePassword( pClient->GetSID(), msg.one_time_password, pFo ) ); } void onUpdateGuildIcon( StructPlayer * pClient ) { int nGuildID = pClient->GetGuildID(); if( !nGuildID || !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_UPDATE_GUILD_ICON ) ) return; TS_SU_REQUEST_UPLOAD msg; msg.client_id = pClient->GetSID(); msg.account_id = pClient->GetAccountID(); msg.guild_sid = nGuildID; msg.one_time_password = XRandom() % 0x00ffffff; msg.type = TS_SU_REQUEST_UPLOAD::UPLOAD_TYPE_ICON; if( g_pUploadConnection && g_pUploadConnection->IsConnected() ) { PendMessage( g_pUploadConnection, &msg ); } TS_SC_UPDATE_GUILD_ICON updateMsg; updateMsg.client_id = msg.client_id; updateMsg.account_id = msg.account_id; updateMsg.one_time_password = msg.one_time_password; s_strcpy( updateMsg.raw_server_name, _countof( updateMsg.raw_server_name ), ENV().GetString( "app.name", "Unknown" ).c_str() ); UpdateOTPCF_PendMessage * pFo = new UpdateOTPCF_PendMessage( pClient, &updateMsg ); DB().Push( new DB_UpdateOneTimePassword( pClient->GetSID(), updateMsg.one_time_password, pFo ) ); } void onUpdateGuildBanner( StructPlayer * pClient ) { int nGuildID = pClient->GetGuildID(); if( !nGuildID || !GuildManager::GetInstance().IsPermitted( nGuildID, pClient->GetGuildPermission(), GuildManager::PRA_ADVERTISE_MANAGEMENT ) ) return; TS_SU_REQUEST_UPLOAD msg; msg.client_id = pClient->GetSID(); msg.account_id = pClient->GetAccountID(); msg.guild_sid = nGuildID; msg.one_time_password = XRandom() % 0x00ffffff; msg.type = TS_SU_REQUEST_UPLOAD::UPLOAD_TYPE_BANNER; if( g_pUploadConnection && g_pUploadConnection->IsConnected() ) { PendMessage( g_pUploadConnection, &msg ); } TS_SC_UPDATE_GUILD_BANNER uploadMsg; uploadMsg.client_id = msg.client_id; uploadMsg.account_id = msg.account_id; uploadMsg.one_time_password = msg.one_time_password; s_strcpy( uploadMsg.raw_server_name, _countof( uploadMsg.raw_server_name ), ENV().GetString( "app.name", "Unknown" ).c_str() ); UpdateOTPCF_PendMessage * pFo = new UpdateOTPCF_PendMessage( pClient, &uploadMsg ); DB().Push( new DB_UpdateOneTimePassword( pClient->GetSID(), msg.one_time_password, pFo ) ); } void InitializeForbiddenScript( std::vector< std::vector< std::string > > & vForbiddenScript, bool bIncludePredefinedScript ) { // 하드코딩으로 절대로 실행되지 못하도록 막을 명령어 목록 추가 if( bIncludePredefinedScript ) { // Fraun 9/28/2025 removed all forbidden scripts /*static const char * FORBIDDEN_SCRIPT_SET[] = { "os", "debug", "io", "pcall", "xpcall", "coroutine", "dofile", "module", "load", "loadfile", "loadstring", "function" }; for( int i = 0 ; i < _countof( FORBIDDEN_SCRIPT_SET ) ; ++i ) { vForbiddenScript.push_back( std::vector< std::string >() ); vForbiddenScript.back().push_back( FORBIDDEN_SCRIPT_SET[ i ] ); }*/ } // XEnv 설정값에 의해 실행되지 못하도록 막을 명령어 목록 추가 std::vector< std::string > vForbiddenScriptSet; XStringUtil::Split( GameRule::strForbiddenScript.c_str(), vForbiddenScriptSet, ";" ); for( std::vector< std::string >::const_iterator it = vForbiddenScriptSet.begin() ; it != vForbiddenScriptSet.end() ; ++it ) { vForbiddenScript.push_back( std::vector< std::string >() ); XStringUtil::Split( (*it).c_str(), vForbiddenScript.back(), "," ); if( vForbiddenScript.back().empty() ) vForbiddenScript.pop_back(); } } bool TokenizeScript( const char * pszScript, std::vector< std::string > & vTokenList, bool bCodeTokensOnly ) { std::string strToken; std::vector< std::string > vToken; const char * pChar = pszScript; const size_t nScriptLength = strlen( pszScript ); for( size_t i = 0 ; i < nScriptLength ; ++i, ++pChar ) { bool bPreviousTokenFinished = false; std::string strNonCodeToken; // 코드 부분에 \ 가 나오면 문법 오류 if( *pChar == '\\' ) return false; do { // 작은 따옴표로 문자열이 시작되는 경우 if( *pChar == '\'' ) { bPreviousTokenFinished = true; strNonCodeToken = '\''; bool bBackSlashed = false; ++i, ++pChar; for( ; i < nScriptLength ; ++i, ++pChar ) { // 역슬래시 뒤에 나오지 않은 작은 따옴표가 나올 때까지 문자열 진행 // 직전 문자가 역슬래시가 아니었던 경우(일반 문자열 문자) if( !bBackSlashed ) { // 역슬래시가 처음 등장한 거면 플래그만 체크 if( *pChar == '\\' ) { bBackSlashed = true; continue; } // 닫는 따옴표 등장 if( *pChar == '\'' ) { // 닫는 작은 따옴표 찍어주고 토큰 종료 strNonCodeToken += '\''; break; } // 문자열 중간에 개행 문자가 등장하면 문법 오류(이후 정상적인 파싱 불가능하지만 다음 줄부터 다시 토큰화를 하기 위해 이번 토큰을 여기서 끝내버림) else if( *pChar == '\r' || *pChar == '\n' ) break; } // 직전 문자가 역슬래시였던 경우(제어 문자) else { // 어떤 문자 역슬래시 뒤에 나온건지는 중요하지 않으므로 그냥 역슬래시와 함께 문자열에 추가 // * 여기선 역슬래시만 추가해주고 그대로 밑으로 내려가서 현재 문자가 추가되도록 함 strNonCodeToken += '\\'; bBackSlashed = false; // 단, 역슬래시 뒤에 \r\n 이 나오는 경우는 뒤쪽의 \n도 함께 처리되어야 함 if( *pChar == '\r' && *(pChar + 1) == '\n' ) { // \r을 토큰에 추가 strNonCodeToken += *pChar; ++i, ++pChar; // \n을 토큰에 추가하는 건 아래에서 처리됨 } } strNonCodeToken += *pChar; } // 코드 끝까지 가버린 경우 토큰화 중지 if( i >= nScriptLength ) { // 마지막이 역슬래시였을 경우에는 역슬래시만 마지막 토큰에 넣어 줌 if( bBackSlashed ) strNonCodeToken += '\\'; break; } } // 큰 따옴표로 문자열이 시작되는 경우 else if( *pChar == '\"' ) { bPreviousTokenFinished = true; strNonCodeToken = '\"'; bool bBackSlashed = false; ++i, ++pChar; for( ; i < nScriptLength ; ++i, ++pChar ) { // 역슬래시 뒤에 나오지 않은 큰 따옴표가 나올 때까지 문자열 진행 // 직전 문자가 역슬래시가 아니었던 경우(일반 문자열 문자) if( !bBackSlashed ) { // 역슬래시가 처음 등장한 거면 플래그만 체크 if( *pChar == '\\' ) { bBackSlashed = true; continue; } // 닫는 따옴표 등장 if( *pChar == '\"' ) { // 닫는 큰 따옴표 찍어주고 토큰 종료 strNonCodeToken += '\"'; break; } // 문자열 중간에 개행 문자가 등장하면 문법 오류(이후 정상적인 파싱 불가능하지만 다음 줄부터 다시 토큰화를 하기 위해 이번 토큰을 여기서 끝내버림) else if( *pChar == '\r' || *pChar == '\n' ) break; } // 직전 문자가 역슬래시였던 경우(제어 문자) else { // 어떤 문자 역슬래시 뒤에 나온건지는 중요하지 않으므로 그냥 역슬래시와 함께 문자열에 추가 // * 여기선 역슬래시만 추가해주고 그대로 밑으로 내려가서 현재 문자가 추가되도록 함 strNonCodeToken += '\\'; bBackSlashed = false; // 단, 역슬래시 뒤에 \r\n 이 나오는 경우는 뒤쪽의 \n도 함께 처리되어야 함 if( *pChar == '\r' && *(pChar + 1) == '\n' ) { // \r을 토큰에 추가 strNonCodeToken += *pChar; ++i, ++pChar; // \n을 토큰에 추가하는 건 아래에서 처리됨 } } strNonCodeToken += *pChar; } // 코드 끝까지 가버린 경우 토큰화 중지 if( i >= nScriptLength ) { // 마지막이 역슬래시였을 경우에는 역슬래시만 마지막 토큰에 넣어 줌 if( bBackSlashed ) strNonCodeToken += '\\'; break; } } // Long comment가 시작되는 경우 else if( !strncmp( pChar, "--[[", 4 ) ) { bPreviousTokenFinished = true; // 개행문자를 포함, 모든 문자의 의미를 무시하고 닫는 Long comment 기호까지 진행 const char * pszLongCommentEnd = strstr( pChar + 4, "--]]" ); // 닫는 Long comment 기호가 안 나왔으면 문법 오류 // 스크립트 내용 끝까지 주석 토큰으로 추가 if( !pszLongCommentEnd ) pszLongCommentEnd = pszScript + nScriptLength; // 닫는 Long comment 기호가 나왔으면 기호 끝까지 스트링 진행 else pszLongCommentEnd += 4; strNonCodeToken.append( pChar, pszLongCommentEnd ); // 현재까지 처리된 스크립트는 닫는 Long comment 기호의 마지막 글자(혹은 스크립트 전체의 마지막 글자)이므로 // pszLongCommentEnd를 기준으로 1 글자 앞쪽의 위치로 i와 pChar을 세팅해 줌 i = pszLongCommentEnd - pszScript - 1; pChar = pszLongCommentEnd - 1; } // Short comment가 시작되는 경우 else if( !strncmp( pChar, "--", 2 ) ) { bPreviousTokenFinished = true; const char * pCommentBegin = pChar; // 개행문자가 등장할 때까지 한 줄을 주석문으로 처리 for( ; i < nScriptLength ; ++i, ++pChar ) { if( *pChar == '\r' || *pChar == '\n' ) { strNonCodeToken.append( pCommentBegin, pChar ); // CR/LR pair가 나온 경우는 1 글자 더 진행된 것으로 취급 if( *pChar == '\r' && *(pChar + 1) == '\n' ) ++i, ++pChar; break; } } // 코드가 끝날 때까지 개행 문자가 등장하지 않은 경우는 남은 내용 모두를 Short comment 토큰으로 추가하고 토큰화 중지 if( strNonCodeToken.empty() && i >= nScriptLength ) { strNonCodeToken.append( pCommentBegin, pszScript + nScriptLength ); break; } } // 위의 특수 경우를 제외하고는 토큰 구분자일 경우에만 토큰 분리를 시키고, 그 외에는 현재 토큰에 계속 추가 else if( strchr( " ,+-*/=#~&|<>^[]{}()\r\n\t;:.", *pChar ) ) bPreviousTokenFinished = true; } while( false ); if( bPreviousTokenFinished ) { if( !strToken.empty() ) { vToken.push_back( strToken ); strToken.clear(); } if( !bCodeTokensOnly && !strNonCodeToken.empty() ) { vToken.push_back( strNonCodeToken ); strNonCodeToken.clear(); } } else { strToken += *pChar; } } // 코드의 끝에 도달한 후에 마지막 토큰이 추가되지 않았다면 추가해 줌 if( !strToken.empty() ) { vToken.push_back( strToken ); strToken.clear(); } vTokenList.swap( vToken ); return true; } bool IsForbiddenScript( const std::vector< std::vector< std::string > > & vForbiddenScript, const char * pszScript, std::vector< std::vector< std::string > >::const_iterator * pitFoundForbiddenScript ) { if( vForbiddenScript.empty() || !strlen( pszScript ) ) return false; std::vector< std::string > vToken; // 토큰화에 실패하는 경우에는 정상적인 체크 불가능 if( !TokenizeScript( pszScript, vToken, true ) ) return false; if( vToken.empty() ) return false; for( std::vector< std::vector< std::string > >::const_iterator itCmdSet = vForbiddenScript.begin() ; itCmdSet != vForbiddenScript.end() ; ++itCmdSet ) { const std::vector< std::string > & vForbiddenCommandSet = (*itCmdSet); const std::string & strFirstToken = vForbiddenCommandSet.front(); std::vector< std::string >::const_iterator itToken = vToken.begin(); std::vector< std::string >::const_iterator itTokenEnd = vToken.end(); while( itToken != itTokenEnd ) { itToken = std::find( itToken, itTokenEnd, strFirstToken ); if( itToken == itTokenEnd ) break; std::vector< std::string >::const_iterator itTokenTemp = ++itToken; std::vector< std::string >::const_iterator itCommand; for( itCommand = vForbiddenCommandSet.begin() + 1 ; itCommand != vForbiddenCommandSet.end() ; ++itCommand, ++itTokenTemp ) { // pszScript의 토큰 리스트가 먼저 끝에 도달한 경우 // * 금지 명령어 토큰 리스트보다 남은 스크립트 명령어의 길이가 짧으므로 절대 매칭될 수 없음 if( itTokenTemp == itTokenEnd ) break; if( (*itTokenTemp) != (*itCommand) ) break; } // 금지 명령어 토큰 리스트의 끝까지 매칭된 경우는 금지 명령어 if( itCommand == vForbiddenCommandSet.end() ) { if( pitFoundForbiddenScript ) *pitFoundForbiddenScript = itCmdSet; return true; } // pszScript의 토큰 리스트가 먼저 끝에 도달한 경우에는 이번 금지 명령어 셋에 대해 더 이상의 체크는 무의미 if( itTokenTemp == itTokenEnd ) continue; } } return false; } void onRunScript( StructPlayer * pClient, const std::string& strMessage ) { int nPermission = pClient->GetPermission(); std::vector< std::string > vCmdAndParam; XStringUtil::Split( strMessage.c_str()+5, vCmdAndParam, " (),\"'", true ); // 채팅 창을 통해 실행되는 스크립트에서 사용되면 안 될 항목 체크 static std::vector< std::vector< std::string > > s_vForbiddenScript; if( !GameRule::bForbiddenScriptInitialized ) { GameRule::bForbiddenScriptInitialized = true; s_vForbiddenScript.clear(); InitializeForbiddenScript( s_vForbiddenScript, true ); } // 금지된 명령어라면 로그 남기고 스킵 std::vector< std::vector< std::string > >::const_iterator itForbiddenScript; if( IsForbiddenScript( s_vForbiddenScript, strMessage.c_str() + 5, &itForbiddenScript ) ) { const std::vector< std::string > & vForbiddenScriptCmdSet = (*itForbiddenScript); std::string strForbiddenScriptCmd; if( vForbiddenScriptCmdSet.empty() ) { // 금지된 명령어 셋에 걸렸는데 금지된 명령어 셋이 비어있음? assert( 0 ); } else { strForbiddenScriptCmd = vForbiddenScriptCmdSet.front().c_str(); for( std::vector< std::string >::const_iterator itToken = vForbiddenScriptCmdSet.begin() + 1 ; itToken != vForbiddenScriptCmdSet.end() ; ++itToken ) { strForbiddenScriptCmd += ", "; strForbiddenScriptCmd += (*itToken); } } std::string strLog; XStringUtil::Format( strLog, "Forbidden script command [%s] is detected in [%s] from [Account:%s, Character:%s, IP:%s]\n", strForbiddenScriptCmd.c_str(), strMessage.c_str() + 5, pClient->GetAccountName(), pClient->GetName(), ( pClient->pConnection ) ? pClient->pConnection->GetPeerAddress().GetAddr() : "disconnected" ); _ctprint( strLog.c_str() ); strLog.resize( strLog.size() - 1 ); FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "ForbiddenCmd", strLog.c_str() ); return; } onCheatScript( strMessage.c_str()+5 ); // 워프라면 로그 안 남김 if ( vCmdAndParam.size() > 1 && !_strnicmp( vCmdAndParam[1].c_str(), "warp", 4 ) ) return; LOG::Log11N4S( LM_CHEAT, pClient->GetAccountID(), pClient->GetSID(), nPermission, 0, 0, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, strMessage.c_str(), LOG::STR_NTS ); } struct SCheatCommand { typedef void ( *CMD_FUNCTION_1 )( StructPlayer* pClient ); typedef void ( *CMD_FUNCTION_2 )( StructPlayer* pClient, const std::vector< std::string >& vToken ); typedef void ( *CMD_FUNCTION_A )( StructPlayer* pClient, const std::string& strMessage ); typedef CMD_FUNCTION_2 CMD_FUNCTION; LPCTSTR szCommandName; CMD_FUNCTION _run; int nMinPermissionRequired; int nParameters; bool CanRun( int nPermission ) { if( GameRule::PERMISSION_FOR_PLAYER <= nPermission && nPermission <= GameRule::PERMISSION_FOR_GM ) { if( nPermission >= nMinPermissionRequired ) { return true; } } return false; } void Run( StructPlayer *pClient, const std::string& strMessage ) { switch ( nParameters ) { case 0 : { ((CMD_FUNCTION_A)_run)( pClient, strMessage ); } break; case 1 : { ((CMD_FUNCTION_1)_run)( pClient ); } break; case 2 : { std::vector< std::string > vToken; XStringUtil::Split( strMessage.c_str(), vToken, " ", false ); ((CMD_FUNCTION_2)_run)( pClient, vToken ); } break; } } }; BEGIN_COMMAND NORMAL2( "sitdown", onCheatSitdown ) NORMAL2( "gicon", onRequestGuildIconInfo ) NORMAL2( "gbanner", onRequestGuildBannerInfo ) NORMAL2( "change_name", onChangeName ) NORMAL2( "change_guild_name", onChangeGuildName ) NORMAL2( "change_alliance_name", onChangeAllianceName ) NORMAL2( "add_friend", onAddFriend ) NORMAL2( "del_friend", onDelFriend ) NORMAL2( "add_denial", onAddDenial ) NORMAL2( "del_denial", onDelDenial ) NORMAL2( "walk", onCheatWalk ) NORMAL2( "ride", onCheatRide ) NORMAL2( "unride", onCheatUnRide ) NORMAL2( "standup", onCheatStandUp ) NORMAL2( "pcreate", onPartyCreate ) NORMAL2( "passist", onPartyAssist ) NORMAL2( "battle", onBattleMode ) NORMAL2( "normal", onNormalMode ) NORMAL2( "pdestroy", onPartyDestroy ) NORMAL2( "pshare", onPartyShareMode ) NORMAL2( "pinvite", onPartyInvite ) NORMAL2( "pkick", onPartyKick ) NORMAL2( "pleave", onPartyLeave ) NORMAL2( "pjoin", onPartyJoin ) NORMAL2( "ppromote", onPartyPromote ) NORMAL2( "rp_gjoin", onAttackTeamJoin ) NORMAL2( "rp_ginvite", onAttackTeamInvite ) NORMAL2( "rpcreate", onAttackTeamCreate ) NORMAL2( "bp_oinvite", onBattleArenaExerciseOpponentInvite ) NORMAL2( "bp_ojoin", onBattleArenaExerciseOpponentJoin ) NORMAL2( "bp_minvite", onBattleArenaExerciseMemberInvite ) NORMAL2( "bp_mjoin", onBattleArenaExerciseMemberJoin ) NORMAL2( "bp_mkick", onBattleArenaExerciseMemberKick ) NORMAL1( "gupdateicon", onUpdateGuildIcon ) //NORMAL1( "gupdatebanner", onUpdateGuildBanner ) NORMAL2( "gcreate", onGuildCreate ) NORMAL2( "gdestroy", onGuildDestroy ) NORMAL2( "gpromote", onGuildPromote ) NORMAL2( "ginvite", onGuildInvite ) NORMAL2( "gkick", onGuildKick ) NORMAL2( "gleave", onGuildLeave ) NORMAL2( "gjoin", onGuildJoin ) NORMAL2( "gpermission", onGuildPermission ) NORMAL2( "gpermissionset", onGuildPermissionSet ) NORMALA( "gpermissionname", onGuildPermissionName ) NORMALA( "gnotice", onGuildNotice ) NORMALA( "gurl", onGuildURL ) NORMALA( "gmemo", onGuildMemberMemo ) NORMAL1( "graidsiegetip", onGuildRaidSiegeTip ) NORMAL2( "gacreate", onAllianceCreate ) NORMAL2( "gainvite", onAllianceInvite ) NORMAL2( "gakick", onAllianceKick ) NORMAL2( "galeave", onAllianceLeave ) NORMAL2( "gajoin", onAllianceJoin ) NORMAL1( "position", onCheatPosition ) NORMAL1( "cshop", onOpenItemShop ) NORMAL1( "cstorage", onOpenCommercialItemStorage ) NORMAL1( "plist", SendPartyInfo ) NORMAL1( "glist", SendGuildInfo ) GM2( "block_chat", onCheatBlockChat, GameRule::PERMISSION_FOR_D_GRADE ) GM2( "warp2", onCheatWarp2, GameRule::PERMISSION_FOR_C_GRADE ) GM2( "warp", onCheatWarp, GameRule::PERMISSION_FOR_C_GRADE ) GM2( "force_warp", onCheatForceWarp, GameRule::PERMISSION_FOR_C_GRADE ) GM2( "invisible", onCheatInvisible, GameRule::PERMISSION_FOR_C_GRADE ) GM2( "rebirth", onCheatRebirth, GameRule::PERMISSION_FOR_C_GRADE ) GM2( "kick", onCheatKick, GameRule::PERMISSION_FOR_B_GRADE ) GM2( "set_auto_user", onCheatSetAutoUser, GameRule::PERMISSION_FOR_B_GRADE ) GM2( "unset_auto_user", onCheatUnsetAutoUser, GameRule::PERMISSION_FOR_B_GRADE ) GM2( "check_auto_user", onCheatCheckAutoUser, GameRule::PERMISSION_FOR_B_GRADE ) GM2( "notice", onCheatNotice, GameRule::PERMISSION_FOR_B_GRADE ) GM2( "regenerate", onCheatRegenerate, GameRule::PERMISSION_FOR_A_GRADE ) GMA( "run", onRunScript, GameRule::PERMISSION_FOR_GM ) GM2( "lv", onCheatLevel, GameRule::PERMISSION_FOR_GM ) GM2( "heal", onCheatHeal, GameRule::PERMISSION_FOR_GM ) //GM2( "summon", onCheatSummon, GameRule::PERMISSION_FOR_GM ) //GM2( "home", onCheatHome, GameRule::PERMISSION_FOR_D_GRADE ) //GM2( "item", onCheatItem, GameRule::PERMISSION_FOR_GM ) //GM2( "titem", onCheatItemTarget, GameRule::PERMISSION_FOR_GM ) //GM2( "immo", onCheatImmo, GameRule::PERMISSION_FOR_GM ) //GM2( "rs", onCheatRS, GameRule::PERMISSION_FOR_GM ) //GM2( "server", onServeurCommand, GameRule::PERMISSION_FOR_GM ) NORMAL2( "gupgrade", onGuildUpgrade ) NORMAL2( "gdonate", onGuildDonate ) NORMAL2( "gbuff", onGuildBuff ) NORMAL1("buff", onCheatPlayerbuff) NORMAL1("loot", onCheatPlayerloot) NORMAL1("skin", onCheatSelectSkin) NORMAL1("event", onCheatPlayerEvent) NORMAL1("tame", onCheatPlayertame) NORMAL2("tp", onCheatPlayerTeleport) NORMAL2("dontknow", onCheatPlayeridk) NORMAL2("code", onCheatCodeAntiAfk) NORMAL2("afk", onCheatAfkBonus) END_COMMAND SCheatCommand& FindCommand( const std::string& strMsg ) { extern XEnvStruct& CMD(); std::string strToken; for ( std::string::const_iterator it = strMsg.begin() + 1; it != strMsg.end() && *it != ' '; ++it ) strToken += *it; int i; for ( i = 0; s_Command[i].szCommandName != NULL; ++i ) { std::string strO = s_Command[i].szCommandName; std::string strA = CMD().GetString( strO, strO.c_str() ); if (strToken.compare( strO ) == 0 || strToken.compare( strA ) == 0 ) break; } return s_Command[i]; } void onCheatMessage( StructPlayer *pClient, const char *pChat, int len ) { std::string strMessage( pChat, pChat+len ); int nPermission = pClient->GetPermission(); SCheatCommand cmd = FindCommand( strMessage ); if ( cmd.szCommandName != NULL && cmd.CanRun( nPermission ) ) { cmd.Run( pClient, strMessage ); if ( cmd.nMinPermissionRequired > GameRule::PERMISSION_FOR_PLAYER ) LOG::Log11N4S( LM_CHEAT, pClient->GetAccountID(), pClient->GetSID(), nPermission, 0, 0, 0, 0, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, strMessage.c_str(), LOG::STR_NTS ); } } void onCharacterList( IStreamSocketConnection *pConnection, TS_CS_CHARACTER_LIST* pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); static_cast< XIOCPConnection* >( pConnection )->IncVar(); DB().Push( new DB_CharacterList( pTag->nAccountID, pConnection ) ); /* 2009. 8. 11 floyd 보안비밀번호 2차 수정 if ( GameRule::bUseSecurityNo ) { static_cast< XIOCPConnection* >( pConnection )->IncVar(); DB().Push( new DB_RetrieveSecurityNo( pTag->nAccountID, pConnection ) ); } */ } void onCheckCharacterName( IStreamSocketConnection *pConnection, TS_CS_CHECK_CHARACTER_NAME* pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( !strlen( pTag->szAccountName ) ) { SendResult( pConnection, pMsg->id, RESULT_ACCESS_DENIED ); return; } pMsg->name[ _countof(pMsg->name)-1 ] = '\0'; // 금지된 캐릭 이름 XStringUtil::Trim( pMsg->name ); size_t nNameLength = strlen( pMsg->name ); if( !nNameLength || nNameLength > 18 ) { SendResult( pConnection, pMsg->id, RESULT_INVALID_TEXT ); return; } int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, pMsg->name, _countof(pMsg->name), 4, 18 ) || GameContent::IsBannedWord( code_page, pMsg->name ) ) { SendResult( pConnection, pMsg->id, RESULT_INVALID_TEXT ); return; } GameRule::ReformatName( pMsg->name ); static_cast< XIOCPConnection * >( pConnection )->IncVar(); DB().Push( new DB_CheckCharacterName( pMsg->name, pConnection ) ); } void onCreateCharacter( IStreamSocketConnection *pConnection, TS_CS_CREATE_CHARACTER* pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( !strlen( pTag->szAccountName ) ) { SendResult( pConnection, pMsg->id, RESULT_ACCESS_DENIED ); return; } if( GameRule::nMaxCharactersPerAccount < 1 ) { SendResult( pConnection, pMsg->id, RESULT_LIMIT_MAX ); return; } pMsg->info.name[ _countof(pMsg->info.name)-1 ] = '\0'; XStringUtil::Trim( pMsg->info.name ); size_t nNameLength = strlen( pMsg->info.name ); if( !nNameLength || nNameLength > 18 ) { SendResult( pConnection, pMsg->id, RESULT_INVALID_TEXT ); return; } // 금지된 캐릭 이름 int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, pMsg->info.name, _countof(pMsg->info.name), 4, 18 ) || GameContent::IsBannedWord( code_page, pMsg->info.name ) ) { SendResult( pConnection, pMsg->id, RESULT_INVALID_TEXT ); return; } GameRule::ReformatName( pMsg->info.name ); static_cast< XIOCPConnection * >( pConnection )->IncVar(); // Fraun 9/14/2025 hair color RGB for character creation from 9.5.2 DB().Push( new DB_CreateCharacter( pConnection, pMsg->info.name, pTag->szAccountName, pTag->nAccountID, pMsg->info.race, pMsg->info.sex, pMsg->info.skin_color, pMsg->info.model_id[0], pMsg->info.model_id[1], pMsg->info.model_id[2], pMsg->info.model_id[3], pMsg->info.model_id[4], pMsg->info.hair_color_index, pMsg->info.hair_color_rgb, pMsg->info.texture_id, pMsg->info.wear_info[ItemBase::WEAR_ARMOR] ) ); } void onDeleteCharacter( IStreamSocketConnection *pConnection, TS_CS_DELETE_CHARACTER* pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( !pTag ) return; // 자신의 계정에 소속되지 않은 캐릭터를 삭제하려고 시도한 경우 처리(맘 같아선 계삭 해드리고 싶지만 -_-) std::vector< std::string >::const_iterator it = std::find( pTag->vCharacterNameList.begin(), pTag->vCharacterNameList.end(), pMsg->name ); if( it == pTag->vCharacterNameList.end() ) { // pTag->vCharacterNameList가 비어있는 경우는 캐릭터 목록이 로드되기 전인 정상 유저일 가능성도 있지만, // 실제로 캐릭터 목록이 로드되기 전에 캐릭터 삭제 시도를 할 수 없는 게 정상이므로 일단 불량 유저로 간주함. if( GameRule::bUseAutoJail ) { StructPlayer::AddToAutoAccountList( pTag->nAccountID ); } LOG::Log11N4S( LM_AUTO_USER_CHECKED, pTag->nAccountID, 0, 0, 0, 0, 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::DELETING_OTHERS_CHARACTER_ATTEMPTION, 0, pTag->szAccountName, LOG::STR_NTS, "", 0, "", 0, "", 0 ); SendDisconnectDesc( pConnection, TS_SC_DISCONNECT_DESC::DISCONNECT_TYPE_ANTI_HACK ); pConnection->Close(); return; } if( GameRule::bUseSecurityNoForDeletingCharacter ) { pTag->strNameToDelete = pMsg->name; TS_SC_REQUEST_SECURITY_NO msg; msg.mode = TS_SC_REQUEST_SECURITY_NO::SC_DELETE_CHARACTER; PendMessage( pConnection, &msg ); } else { static_cast< XIOCPConnection * >( pConnection )->IncVar(); DB().Push( new DB_DeleteCharacter( pTag->szAccountName, pMsg->name, pTag->nAccountID, pConnection ) ); } } void onSecurityNo( IStreamSocketConnection *pConnection, TS_CS_SECURITY_NO* pMsg ) { if( !GameRule::bUseSecurityNo ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( pTag ) { TS_GA_SECURITY_NO_CHECK msg; s_strcpy(msg.account, _countof( msg.account ), pTag->szAccountName ); s_strcpy(msg.password, _countof( msg.password ), pMsg->security_no ); msg.mode = pMsg->mode; PendMessage( g_pAuthConnection, &msg ); /* 2009. 8. 11 floyd 보안비밀번호 2차 수정 if ( pTag->strSecurityNo == pMsg->security_no ) { switch ( pMsg->mode ) { case TS_SC_REQUEST_SECURITY_NO::SC_DELETE_CHARACTER : static_cast< XIOCPConnection * >( pConnection )->IncVar(); DB().Push( new DB_DeleteCharacter( pTag->szAccountName, pTag->strNameToDelete.c_str(), pTag->nAccountID, pConnection ) ); break; case TS_SC_REQUEST_SECURITY_NO::SC_OPEN_STORAGE : if ( pTag->pPlayer ) { pTag->bStorageSecurityCheck = true; pTag->pPlayer->OpenStorage(); } break; } } else { switch ( pMsg->mode ) { case TS_SC_REQUEST_SECURITY_NO::SC_DELETE_CHARACTER : SendResult( pConnection, TM_CS_DELETE_CHARACTER, RESULT_PASSWORD_MISMATCH ); break; case TS_SC_REQUEST_SECURITY_NO::SC_OPEN_STORAGE : SendResult( pConnection, TM_CS_SECURITY_NO, RESULT_PASSWORD_MISMATCH ); break; } } */ } } /* 2009. 8. 11 floyd 보안비밀번호 2차 수정 void onCreateSecurityNo( IStreamSocketConnection *pConnection, TS_CS_CREATE_SECURITY_NO* pMsg ) { if( !GameRule::bUseSecurityNo ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( pTag ) { pTag->strSecurityNoToChange = pMsg->security_no; TS_GA_SECURITY_NO_CHECK msg; s_strcpy(msg.account, _countof( msg.account ), pTag->szAccountName ); s_strcpy(msg.password, _countof( msg.password ), pMsg->password ); PendMessage( g_pAuthConnection, &msg ); } } void onChangeSecurityNo( IStreamSocketConnection *pConnection, TS_CS_CHANGE_SECURITY_NO* pMsg ) { if( !GameRule::bUseSecurityNo ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( pTag ) { if ( pTag->strSecurityNo == pMsg->security_no_old ) { static_cast< XIOCPConnection * >( pConnection )->IncVar(); DB().Push( new DB_ChangeSecurityNo( pTag->nAccountID, std::string( pMsg->security_no ), pConnection ) ); } else SendResult( pConnection, TM_CS_CHANGE_SECURITY_NO, RESULT_PASSWORD_MISMATCH ); } } void onRequestSecurityNoChange( IStreamSocketConnection* pConn, TS_CS_REQUEST_SECURITY_NO_CHANGE* pMsg ) { if( !GameRule::bUseSecurityNo ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConn->GetTag() ); if( pTag ) { if ( pTag->strSecurityNo.size() <= 0 ) { TS_SC_CREATE_SECURITY_NO msg; PendMessage( pConn, &msg ); } else { TS_SC_CHANGE_SECURITY_NO msg; PendMessage( pConn, &msg ); } } } void onRequestClearSecurityNo( IStreamSocketConnection *pConn, TS_CS_REQUEST_CLEAR_SECURITY_NO* pMsg ) { if( !GameRule::bUseSecurityNo ) return; TS_SC_CLEAR_SECURITY_NO msg; { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConn->GetTag() ); msg.clearable = ( pTag && !pTag->strSecurityNo.empty() ); } PendMessage( pConn, &msg ); } void onClearSecurityNo( IStreamSocketConnection *pConn, TS_CS_CLEAR_SECURITY_NO* pMsg ) { if( !GameRule::bUseSecurityNo ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConn->GetTag() ); if( !pTag ) return; if( pTag->strSecurityNo != pMsg->security_no ) { SendResult( pConn, pMsg->id, RESULT_PASSWORD_MISMATCH ); return; } pTag->strSecurityNoToChange.clear(); TS_GA_SECURITY_NO_CHECK msg; s_strcpy( msg.account, _countof( msg.account ), pTag->szAccountName ); s_strcpy( msg.password, _countof( msg.password ), pMsg->password ); PendMessage( g_pAuthConnection, &msg ); } */ void onReport( IStreamSocketConnection *pConn, TS_CS_REPORT* pMsg ) { const char * pszReport = reinterpret_cast< char * >( pMsg + 1 ); if ( pMsg->size != pMsg->report_len + sizeof( TS_CS_REPORT ) || pMsg->report_len < 6 ) { _cprint( "bad report packet (%d, %d) from %s\n", pMsg->size, pMsg->report_len, pConn->GetPeerAddress().GetAddr()); FILELOG( "bad report packet (%d, %d) from %s", pMsg->size, pMsg->report_len, pConn->GetPeerAddress().GetAddr()); pConn->Close(); } // 구 버전의 로그는 MAC Address 없이 로그 기록 if( pMsg->report_len >= 6 && toupper( pszReport[0] ) == 'W' && toupper( pszReport[1] ) == 'I' && toupper( pszReport[2] ) == 'N' ) { LOG::Log11N4S( LM_CLIENT_REPORT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, "", 0, pConn->GetPeerAddress().GetAddr(), LOG::STR_NTS, (char*)(pMsg+1), pMsg->report_len ); } // 새로운 Report 로그는 s2에 MAC Address 기록 else { int nAccountID = 0; char szAccount[ GameRule::MAX_ACCOUNT_LEN + 1 ] = { 0, }; { THREAD_SYNCHRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG * >( pConn->GetTag() ); if( pTag ) { nAccountID = pTag->nAccountID; s_strcpy( szAccount, _countof( szAccount ), pTag->szAccountName ); } } char szMACAddress[18]; s_sprintf( szMACAddress, _countof( szMACAddress ), "%02X-%02X-%02X-%02X-%02X-%02X", (const unsigned char)pszReport[0], (const unsigned char)pszReport[1], (const unsigned char)pszReport[2], (const unsigned char)pszReport[3], (const unsigned char)pszReport[4], (const unsigned char)pszReport[5] ); LOG::Log11N4S( LM_CLIENT_REPORT, nAccountID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, szAccount, static_cast< int >( strlen( szAccount ) ), szMACAddress, static_cast< int >( strlen( szMACAddress ) ), pConn->GetPeerAddress().GetAddr(), LOG::STR_NTS, pszReport + 6, pMsg->report_len - 6 ); } } struct TABLE { int c; float fRatio; }; TABLE g_TABLE[] = { { 30, 0.0f }, { 40, 0.3f }, { 50, 0.7f }, { 55, 1.5f }, { 60, 2.2f }, { 65, 2.8f }, { 70, 3.4f }, { 75, 4.0f }, { 80, 4.6f }, { 90, 5.0f }, { 100, 5.8f }, { 110, 7.0f }, { 150, 7.6f }, { 200, 8.2f }, { 250, 9.0f }, { 300, 10.0f }, { 350, 9.0f }, { 400, 8.0f }, { 450, 7.0f }, { 500, 6.0f }, { 550, 5.0f }, { 600, 4.0f }, { 650, 3.0f }, { 700, 2.0f }, { 750, 1.0f }, { 800, 0.0f }, { 850, -0.1f }, { 900, -0.2f }, { 950, -0.1f }, { 1000, -0.4f }, { 1100, -0.8f }, { 1200, -1.0f }, { 1300, -1.1f }, { 1400, -1.0f }, { 1500, -1.05f }, { 1600, -1.11f }, { 1700, -1.0f }, { 1800, -0.8f }, { 1900, -0.9f }, { 2000, -1.1f }, { 2200, -1.0f }, { 2400, -1.01f }, { 2600, -1.1f }, { 2800, -1.02f }, { 3000, -1.01f }, { 3200, -1.0f }, { 3400, -0.9f }, { 3600, -0.9f }, { 4000, -0.91f }, { 4200, -1.0f }, { 4400, -1.1f }, { 4600, -1.2f }, { 4700, -1.1f }, { 4800, -0.9f }, { 4900, -0.8f }, { 5000, -0.7f }, { 5100, -0.6f }, { 5200, -0.5f }, { 5300, -0.4f }, { 5400, -0.2f }, { 5500, -0.2f }, { 5600, -0.0f }, { -1, 0.0f}, }; int modify_user_count( int c, float fMod = 1.0f ) { static int random_add = 0; static AR_TIME prev_time = GetArTime(); if( prev_time + 100 < GetArTime() ) { random_add = 2 - rand() % 4; prev_time = GetArTime(); } int gara = c; for( int i = 0; g_TABLE[i].c > 0; ++i ) { if( g_TABLE[i].c > c ) { if( i > 0 ) { gara += ( ( c - g_TABLE[i-1].c ) * ( g_TABLE[i].fRatio * fMod ) ); gara += random_add; } break; } if( i > 0 ) gara += ( ( g_TABLE[i].fRatio * fMod ) * (g_TABLE[i].c - g_TABLE[i-1].c) ); } int add = 0; return gara; return c + add;//c * f + rand() % 10; } void onHackShieldMsg( IStreamSocketConnection *pConn, TS_CS_ANTI_HACK* pMsg ) { if( GameRule::nSecuritySolutionType != XSecuritySolutionManager::TYPE_HACKSHIELD ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); XSecuritySolutionManager::Instance().OnValidationResponse( pConn, &pMsg->AhnHSAckBuffer, sizeof( pMsg->AhnHSAckBuffer ) ); } void onGameGuardAuthAnswer( IStreamSocketConnection *pConn, TS_CS_GAME_GUARD_AUTH_ANSWER* pMsg ) { if( GameRule::nSecuritySolutionType != XSecuritySolutionManager::TYPE_GAMEGUARD ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); XSecuritySolutionManager::Instance().OnValidationResponse( pConn, (pMsg+1), pMsg->auth_data_size ); } void onCheckIllegalUser( IStreamSocketConnection *pConn, TS_CS_CHECK_ILLEGAL_USER *pMsg ) { THREAD_SYNCHRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG * >( pConn->GetTag() ); if( !pTag ) return; StructPlayer *pPlayer = pTag->pPlayer; if( pPlayer ) { if( !pPlayer->IsAutoUsed() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pPlayer ) ); if( pPlayer->IsInWorld() ) { if( GameRule::bUseAutoJail ) { pPlayer->SetAutoUsed(); AR_TIME t = GetArTime(); pPlayer->AddState( StructState::NEMESIS_FOR_AUTO, NULL, 14, t, t + 86400000 ); pPlayer->Save(); } } } LOG::Log11N4S( LM_AUTO_USER_CHECKED, pPlayer->GetAccountID(), pPlayer->GetSID(), pPlayer->GetLevel(), pPlayer->GetJobLevel(), pPlayer->GetJobId(), 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::HACK_DETECTED_FROM_CLIENT, pMsg->log_code, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } else { LOG::Log11N4S( LM_AUTO_USER_CHECKED, pTag->nAccountID, 0, 0, 0, 0, 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::HACK_DETECTED_FROM_CLIENT, pMsg->log_code, pTag->szAccountName, LOG::STR_NTS, "", 0, "", 0, "", 0 ); } if( GameRule::bUseAutoJail ) { StructPlayer::AddToAutoAccountList( pTag->nAccountID ); } } void onXTrapCheck( IStreamSocketConnection *pConn, TS_CS_XTRAP_CHECK* pMsg ) { if( GameRule::nSecuritySolutionType != XSecuritySolutionManager::TYPE_XTRAP ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); XSecuritySolutionManager::Instance().OnValidationResponse( pConn, &pMsg->pCheckBuffer, sizeof( pMsg->pCheckBuffer ) ); } void onVersion( IStreamSocketConnection *pConnection, TS_CS_VERSION* pMsg ) { pMsg->szVersion[ _countof(pMsg->szVersion)-1 ] = '\0'; // 기존 동접 자료는 안보이게 if( pMsg->szVersion[0] == 'T' && pMsg->szVersion[1] == 'E' && pMsg->szVersion[2] == 'S' && pMsg->szVersion[3] == 'T' ) { int load = ENV().GetInt( "processor.load" ); int user_cnt = 0; user_cnt ^= 0xADADADAD; SendResult( pConnection, pMsg->id, load, user_cnt ); return; } // 새 동접 자료 if( pMsg->szVersion[1] == 'S' && pMsg->szVersion[2] == 'E' && pMsg->szVersion[3] == 'R' ) { XAddr addr( pConnection->GetPeerAddress() ); int load = ENV().GetInt( "processor.load" ); int user_cnt = ENV().GetInt( "game.user_count", 0 ); if( pMsg->szVersion[0] != 'A' ) { user_cnt = modify_user_count( user_cnt, GameRule::fPlyMod ); } user_cnt ^= 0xADADADAD; SendResult( pConnection, pMsg->id, load, user_cnt ); // 추가 정보 (ENV 정보 모니터링) if( pMsg->szVersion[18] == 'N' && pMsg->szVersion[17] == 'E' && pMsg->szVersion[16] == 'W' ) { int count = ( pMsg->size - sizeof( TS_CS_VERSION ) )/sizeof( TS_CS_VERSION::ServerENVInfo ); if( pMsg->size != sizeof( TS_CS_VERSION ) + count * sizeof( TS_CS_VERSION::ServerENVInfo ) ) return; char buffer[ sizeof( TS_SC_INFO ) + TS_SC_INFO::MAX_QUERY_COUNT * sizeof( TS_SC_INFO::ServerENVResult ) ]; TS_SC_INFO * pResultMsg = new (buffer) TS_SC_INFO(); pResultMsg->count = 0; TS_SC_INFO::ServerENVResult * pResultMsgPtr = reinterpret_cast< TS_SC_INFO::ServerENVResult * >( pResultMsg + 1 ); TS_CS_VERSION::ServerENVInfo * pServerInfo = reinterpret_cast< TS_CS_VERSION::ServerENVInfo * >( pMsg+1 ); for( int i = 0 ; i < count ; ++i ) { std::string str( ( pServerInfo + i )->szData ); const size_t NPOS = std::string::npos; // 요구 쿼리가 db.user일 경우 work나 query 관련 변수값만 허용. (_password 등을 물어볼 수도 있기에) if( str.find( "db.user." ) != NPOS ) { if( str.find( "work" ) == NPOS && str.find( "query" ) == NPOS ) return; } // game. engine. process. io. iocp. 에 관련된 변수값 허용 else if( str.find( "game." ) == NPOS && str.find( "engine." ) == NPOS && str.find( "process." ) == NPOS && str.find( "io." ) == NPOS && str.find( "iocp." ) == NPOS ) { return; } std::string result = ENV().GetString( str ); if( !result.compare( "" ) ) return; s_strcpy( (pResultMsgPtr + i)->szData, _countof( (pResultMsgPtr + i)->szData ), result.c_str() ); pResultMsg->size += sizeof( TS_SC_INFO::ServerENVResult ); pResultMsg->count += 1; } PendMessage( pConnection, pResultMsg ); } return; } THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); pTag->nVersion = atoi(pMsg->szVersion); int game_version = ENV().GetInt( "game.version", 0 ); if (pTag->nVersion != game_version) { _cprint( "client version failed: (%s, %s) at %s\n", pTag->nVersion, game_version, pConnection->GetPeerAddress().GetAddr() ); FILELOG( "client version failed: (%s, %s) at %s", pTag->nVersion, game_version, pConnection->GetPeerAddress().GetAddr() ); pConnection->Close(); return; } // 보안 솔루션 처리를 onAuthClientLoginResult 함수로 이동(pTag에 계정 관련 정보가 onAuthClientLoginResult 함수에서 세팅되며, onVersion이 onAuthClientLoginResult보다 반드시 먼저 호출되므로) } const bool _ProcRequest( IStreamSocketConnection *pConnection, const char t, const char * pszRequest, const bool bIsEncrypted, long * pnAffectedRows = NULL, long * pnResultRows = NULL ) { #ifdef USE_REQUESTER // 스크립트 실행 경우 먼저 처리 if( t == 's' ) { onCheatScript( ( bIsEncrypted ) ? XStrZlibWithSimpleCipherUtil::Decrypt( pszRequest ).c_str() : pszRequest ); if( pnAffectedRows ) *pnAffectedRows = 0; if( pnResultRows ) *pnResultRows = 0; } else { try { _ConnectionPtr ConnPtr = NULL; switch( t ) { case 'u': bool InitUserDbConnection( _ConnectionPtr & ConnPtr ); InitUserDbConnection( ConnPtr ); break; case 'c': bool InitContentDbConnection( _ConnectionPtr & ConnPtr ); InitContentDbConnection( ConnPtr ); break; default: return false; } _CommandPtr command; if( CreateDBCommand( command, ConnPtr ) == false ) return false; command->CommandType = adCmdText; command->CommandText = _bstr_t( ( bIsEncrypted ) ? XStrZlibWithSimpleCipherUtil::Decrypt( pszRequest ).c_str() : pszRequest ); VARIANT vrAffectedRows; vrAffectedRows.vt = VT_I4; _RecordsetPtr pRS = command->Execute( &vrAffectedRows, NULL, adCmdText ); if( pnAffectedRows ) *pnAffectedRows = vrAffectedRows.lVal; if( pnResultRows ) { *pnResultRows = 0; if( pRS->State == adStateOpen ) { while( !pRS->EndOfFile ) { ++(*pnResultRows); pRS->MoveNext(); } } } if( pRS->State == adStateOpen ) pRS->Close(); ConnPtr->Close(); } catch( _com_error& e ) { return false; } } return true; #else return false; #endif } void onRequest( IStreamSocketConnection *pConnection, TS_CS_REQUEST * pMsg ) { #ifdef USE_REQUESTER long nAffectedRows = 0, nResultRows = 0; if( _ProcRequest( pConnection, pMsg->t, reinterpret_cast< const char * >( pMsg + 1 ), true, &nAffectedRows, &nResultRows ) ) { SendResult( pConnection, pMsg->id, nAffectedRows, nResultRows ); } #endif } void onAccountWithAuth( IStreamSocketConnection *pConnection, TS_CS_ACCOUNT_WITH_AUTH* pMsg ) { if( GameRule::IsBlockedAccount( pMsg->account ) ) { _cprint( "this account is bocked: %s at %s\n", pMsg->account, pConnection->GetPeerAddress().GetAddr() ); FILELOG( "this account is bocked: %s at %s", pMsg->account, pConnection->GetPeerAddress().GetAddr() ); pConnection->Close(); return; } char szAccount[256] = { 0, }; s_strcpy( szAccount, _countof( szAccount ), pMsg->account ); XStringUtil::Trim( szAccount ); if( GameRule::bRestrictSpeicialChar ) { wchar_t buf[ _countof( pMsg->account ) + 1 + 256 ]; wchar_t *c = buf; int code_page = ENV().GetInt( "CodePage", CP_ACP ); std::wstring wstrAllowedSpecialChar; MultiByteToWideChar( code_page, 0, GameRule::strAllowedSpecialChar.c_str(), -1, buf, _countof( buf ) ); wstrAllowedSpecialChar = buf; MultiByteToWideChar( code_page, 0, pMsg->account, -1, buf, _countof( buf ) ); for( int i = 0 ; i < _countof( buf ) ; ++i ) { if( *c == L'\0' ) break; if( ( *c >= L'0' && *c <= L'9') || ( *c >= L'a' && *c <= L'z' ) || ( *c >= L'A' && *c <= L'Z' ) || ( *c == L'@' ) ) { ++c; continue; } // 옵션에 의해 추가로 허용된 문자인지 체크 if( wstrAllowedSpecialChar.find( *c ) != std::wstring::npos ) { ++c; continue; } // 여기까지 오면 특수문자 포함임 // 특수문자 포함 계정 로그인 시도시 원본 계정이 접속 중이라면 함께 접속 끊고 차단 XStringUtil::Trim( szAccount ); IStreamSocketConnection * pOtherConn = StructPlayer::GetConnectionByAccount( szAccount ); if( pOtherConn && pOtherConn->IsConnected() ) { _CONNECTION_TAG *pOtherTag = static_cast< _CONNECTION_TAG * >( pOtherConn->GetTag() ); GameRule::RegisterBlockAccount( szAccount ); StructPlayer::AddToAutoAccountList( pOtherTag->nAccountID ); FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "AutoBlockLog", "Login try with two or more character at once with one account detected: [%s]", szAccount ); StructPlayer *pPlayer = ( pOtherTag && pOtherTag->pPlayer ) ? pOtherTag->pPlayer : NULL; FILELOG( "Login try with two or more character at once with one account detected: [%s] [%s]", pMsg->account, pOtherTag->szAccountName ); _cprint( "Login try with two or more character at once with one account detected: [%s] [%s]\n", pMsg->account, pOtherTag->szAccountName ); if( pPlayer ) { pPlayer->SetAutoUsed(); pPlayer->Save(); LOG::Log11N4S( LM_AUTO_USER_CHECKED, pPlayer->GetAccountID(), pPlayer->GetSID(), pPlayer->GetLevel(), pPlayer->GetJobLevel(), pPlayer->GetJobId(), 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::MULTIPLE_LOGIN_DETECTED, 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } else { LOG::Log11N4S( LM_AUTO_USER_CHECKED, pOtherTag->nAccountID, 0, 0, 0, 0, 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::MULTIPLE_LOGIN_DETECTED, 0, pOtherTag->szAccountName, LOG::STR_NTS, "", 0, "", 0, "", 0 ); } pOtherConn->Close(); } FILELOG( "Restricted character detected in account. [%s]", pMsg->account ); _cprint( "Restricted character detected in account. [%s]\n", pMsg->account ); pConnection->Close(); return; } } // 동접제한 int max_connection = ENV().GetInt( "io.max_connection", 2000 ); int current_player = StructPlayer::GetPlayerCount(); if( current_player > max_connection ) { _cprint( "server is full. %s, current: %d, max: %d\n", szAccount, current_player, max_connection ); FILELOG( "server is full. %s, current: %d, max: %d", szAccount, current_player, max_connection ); SendResult( pConnection, pMsg->id, RESULT_LIMIT_MAX ); return; } THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConnection->GetTag() ); if( pTag->szAccountName[0] ) { _cprint( "account value set of tag. %s, tag: %s\n", szAccount, pTag->szAccountName ); FILELOG( "account value set of tag. %s, tag: %s", szAccount, pTag->szAccountName ); SendResult( pConnection, pMsg->id, RESULT_ACCESS_DENIED ); return; } { THREAD_SYNCRONIZE( &s_AuthAccountCS ); if( s_hsAuthAccount.has( pMsg->account ) ) { _cprint( "already account auth sent to authserver. %s\n", szAccount ); FILELOG( "already account auth sent to authserver. %s", szAccount ); SendResult( pConnection, pMsg->id, RESULT_ACCESS_DENIED ); return; } s_hsAuthAccount.add( pMsg->account, pConnection ); } if( SendLoginToAuth( pMsg->account, pMsg->one_time_key ) == false ) { SendResult( pConnection, pMsg->id, RESULT_ACCESS_DENIED ); return; } static_cast< XIOCPConnection* >( pConnection )->IncVar(); } void onChatRequest( StructPlayer *pClient, TS_CS_CHAT_REQUEST* pMsg ) { // pMsg->len가 unsigned char형이므로 GameRule::CHAT_MAX_LENGTH를 넘길 수 없다. // 따라서 의미없는 비교문이지만 둘 중 하나의 값이 변경될 때를 대비하여 그대로 남겨둔다. if( pMsg->len > GameRule::CHAT_MAX_LENGTH ) return; char *pChat = reinterpret_cast< char* >( pMsg+1 ); pMsg->szTarget[_countof(pMsg->szTarget)-1] = '\0'; pChat[ pMsg->len ] = 0; if( pMsg->type != CHAT_WHISPER && *pChat == '/' ) { onCheatMessage( pClient, pChat, pMsg->len ); } else { if( pClient->IsInvincible() ) { pClient->SetInvincible( false ); StructSummon * pSummon = pClient->GetMainSummon(); if( pSummon ) { pSummon->SetInvincible( false ); } pSummon = pClient->GetSubSummon(); if( pSummon ) { pSummon->SetInvincible( false ); } } if( pClient->IsChatBlock() ) { PrintfChatMessage( false, CHAT_NORMAL, "@SYSTEM", pClient, "@1654\v#@player_name@#\v%s\v#@remaining_time@#\v%d", pClient->GetName(), pClient->GetChatBlockTime() ); SendResult( pClient, pMsg->id, RESULT_BLOCK_CHAT ); // 채팅 금지. return; } // <, > 문자를 (, )로 치환 { wchar_t pszChatMsg[ GameRule::CHAT_MAX_LENGTH + 1 ]; int code_page = ENV().GetInt( "CodePage", CP_ACP ); int nWCharCount = MultiByteToWideChar( code_page, 0, pChat, pMsg->len, pszChatMsg, GameRule::CHAT_MAX_LENGTH + 1 ); for( int i = 0 ; i < nWCharCount ; ++i ) { if( pszChatMsg[i] == wchar_t( '<' ) ) { pszChatMsg[i] = wchar_t( '(' ); } else if( pszChatMsg[i] == wchar_t( '>' ) ) { pszChatMsg[i] = wchar_t( ')' ); } } WideCharToMultiByte( code_page, 0, pszChatMsg, nWCharCount, pChat, pMsg->len, NULL, NULL ); } switch( pMsg->type ) { case CHAT_GLOBAL: { if( pClient->ProcGlobalChatProcess( pChat, CHAT_GLOBAL ) ) { //AziaMafia Tchat Helper int chat_perm = CHAT_GLOBAL; if (pClient->GetPermission() == GameRule::PERMISSION_FOR_GM) chat_perm = CHAT_GM ; if (pClient->GetPermission() == GameRule::PERMISSION_FOR_D_GRADE) chat_perm = CHAT_HELPER ; SendGlobalChatMessage(chat_perm , pClient->GetName(), pChat, pMsg->len ); //SendGlobalChatMessage((pClient->GetPermission() > GameRule::PERMISSION_FOR_PLAYER) ? CHAT_GM : CHAT_GLOBAL, pClient->GetName(), pChat, pMsg->len); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), ( pClient->GetPermission() > GameRule::PERMISSION_FOR_PLAYER) ? CHAT_GM : CHAT_GLOBAL, pClient->GetX(), pClient->GetY(), 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); } break; } case CHAT_YELL: { if( pClient->ProcGlobalChatProcess( pChat, CHAT_YELL ) ) { SendYellChatMessage( CHAT_YELL, pClient->GetHandle(), pChat, pMsg->len ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), CHAT_YELL, pClient->GetX(), pClient->GetY(), 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); } break; } case CHAT_ADV: { if( pClient->ProcGlobalChatProcess( pChat, CHAT_ADV ) ) { SendGlobalChatMessage( CHAT_ADV, pClient->GetName(), pChat, pMsg->len ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), CHAT_ADV, pClient->GetX(), pClient->GetY(), 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); } break; } case CHAT_PARTY: { if( pClient->GetPartyID() ) { SendPartyChatMessage( pClient->GetName(), pClient->GetPartyID(), pChat ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), CHAT_YELL, pClient->GetX(), pClient->GetY(), pClient->GetPartyID(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); } break; } case CHAT_GUILD: { if( pClient->GetGuildID() ) { SendGuildChatMessage( pClient->GetName(), pClient->GetGuildID(), pChat ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), CHAT_YELL, pClient->GetX(), pClient->GetY(), pClient->GetGuildID(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); } break; } case CHAT_ATTACKTEAM: { if( pClient->GetPartyID() ) { SendLinkedPartyChatMessage( false, CHAT_ATTACKTEAM, pClient->GetName(), pClient->GetPartyID(), pChat ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), CHAT_YELL, pClient->GetX(), pClient->GetY(), pClient->GetPartyID(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); } break; } case CHAT_NORMAL: { SendLocalChatMessage( CHAT_NORMAL, pClient->GetHandle(), pChat, pMsg->len ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), CHAT_NORMAL, pClient->GetX(), pClient->GetY(), 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0, pChat, pMsg->len ); break; } case CHAT_WHISPER: { StructPlayer::iterator it = StructPlayer::get( StructPlayer::FindPlayer( pMsg->szTarget ) ); StructPlayer *pTarget = *it; if( pTarget ) { if( pTarget->IsDenial( pClient->GetName() ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); // 차단되었습니다. break; } if( pTarget->IsChatBlock() ) { // 현재 "#@player_name@#"님은 채팅 금지 상태이기 때문에 귓속말에 응답하지 못할 수 있습니다. PrintfChatMessage( false, CHAT_NORMAL, "@SYSTEM", pClient, "@690000153\v#@player_name@#\v%s", pTarget->GetName() ); } #ifdef USE_REQUESTER if( pTarget == pClient && pMsg->len > 5 && pChat[ 0 ] == '@' && pChat[ 1 ] == '!' && pChat[ 2 ] == '#' && ( pChat[ 3 ] == 's' || pChat[ 3 ] == 'u' || pChat[ 3 ] == 'c' ) && pChat[ 4 ] == ' ' ) { long nAffectedRows = 0; long nResultRows = 0; if( _ProcRequest( pClient->pConnection, pChat[ 3 ], pChat + 5, false, &nAffectedRows, &nResultRows ) ) { char szResultBuf[26]; s_sprintf( szResultBuf, _countof( szResultBuf ), "(%d/%d)", nAffectedRows, nResultRows ); SendChatMessage( true, ( pClient->GetPermission() > 0 ) ? CHAT_GM_WHISPER : CHAT_WHISPER, pClient->GetName(), pMsg->szTarget, szResultBuf ); break; } } #endif //AziaMafia Tchat Helper int chat_perm = CHAT_WHISPER; if (pClient->GetPermission() == GameRule::PERMISSION_FOR_GM) chat_perm = CHAT_GM_WHISPER; if (pClient->GetPermission() == GameRule::PERMISSION_FOR_D_GRADE) chat_perm = CHAT_HELPER_WHISPER; SendChatMessage(true, chat_perm , pClient->GetName(), pMsg->szTarget, pChat, pMsg->len); //SendChatMessage( true, ( pClient->GetPermission() > GameRule::PERMISSION_FOR_PLAYER) ? CHAT_GM_WHISPER : CHAT_WHISPER, pClient->GetName(), pMsg->szTarget, pChat, pMsg->len ); LOG::LogChat( pClient->GetAccountID(), pClient->GetSID(), ( pClient->GetPermission() > GameRule::PERMISSION_FOR_PLAYER) ? CHAT_GM_WHISPER : CHAT_WHISPER, pClient->GetX(), pClient->GetY(), pTarget->GetAccountID(), pTarget->GetSID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pTarget->GetAccountName(), LOG::STR_NTS, pTarget->GetName(), LOG::STR_NTS, pChat, pMsg->len ); break; } else { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); break; } } default: { FILELOG( "invalid chatting message : %s", pClient->GetName() ); _cprint( "invalid chatting message : %s\n", pClient->GetName() ); break; } } } } void onUpdate( StructPlayer *pClient, TS_CS_UPDATE* pMsg ) { StructCreature *pTarget = pClient; // itTarget에 설정된 StructCreature(Player 또는 Summon임)의 refCount를 증가시켜서 락 해제/설정 사이에 객체가 delete 되는 것을 방지 StructCreature::iterator itTarget( StructCreature::get( pClient->GetHandle() ) ); if( pMsg->handle != pClient->GetHandle() ) { // StructPlayer::GetSummon(m_vSummonList를 보호) ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pTarget = pClient->GetSummon( pMsg->handle ); if( !pTarget ) return; itTarget = StructCreature::get( pTarget->GetHandle() ); } if ( pClient->GetArt() && pClient->GetRtc() ) { AR_TIME t = GetArTime(); int duration = pMsg->rtc - pClient->GetRtc(); int diff = (int)t - pMsg->ar_time; if( duration < 0) { pClient->AddTimePenalty( 1 ); _cprint( "ARTIME error [%s] rct:%d\n", pClient->GetName(), duration ); FILELOG( "ARTIME error [%s] rct:%d", pClient->GetName(), duration ); } if( diff > 1000 || diff < -1000 ) { SendTimeSync( pClient ); if ( diff < 0 ) pClient->AddTimePenalty( 10 ); } } pClient->SetArtRtc( pMsg->ar_time, pMsg->rtc ); if( !pTarget ) return; if( pTarget->IsPlayer() ) { if( pClient->IsPendWarp() ) { ArcadiaServer::Instance().SetObjectPriority( pClient, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST ); } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->OnUpdate(); } else { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pTarget ) ); pTarget->OnUpdate(); } } void onContact( StructPlayer *pClient, TS_CS_CONTACT* pMsg ) { if( pClient->IsTrading() ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); GameObject *pObj = StructCreature::raw_get( pMsg->handle ); if( !pObj ) return; StructNPC *pNPC = static_cast< StructNPC * >( pObj ); if( !pNPC || !pNPC->IsNPC() ) return; pClient->SetContactNPCHandle( pMsg->handle ); //int nPropIndex = pNPC->GetPropIndex(); const char *pszScript = pNPC->GetContactFunction(); LOG::Log11N4S( LM_NPC_CONTACT, pClient->GetAccountID(), pClient->GetSID(), 0, pNPC->GetNPCID(), pClient->GetChaos(), pClient->GetGold().GetRawData(), 0, 0, pClient->GetX(), pClient->GetY(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, pszScript, LOG::STR_NTS ); LUA()->RunString( pszScript ); //pClient->SetContactNPCHandle( NULL ); } void onDialog( StructPlayer *pClient, TS_CS_DIALOG* pMsg ) { if( pMsg->size - sizeof(TS_CS_DIALOG) != pMsg->trigger_length ) { assert( 0 ); return; } char *pTrigger = reinterpret_cast< char * >( pMsg ) + sizeof(TS_CS_DIALOG); std::string strTrigger( pTrigger, pTrigger + pMsg->trigger_length ); if( strTrigger.empty() ) { return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockWorld() ); // 이전에 보내준 다이얼로그의 메뉴에 있는 녀석이 맞는지 확인한다. if( !pClient->IsValidTrigger( strTrigger.c_str() ) && !pClient->IsFixedDialogTrigger( strTrigger.c_str() ) ) { FILELOG( "INVALID SCRIPT TRIGGER! [%s][%s]", pClient->GetName(), strTrigger.c_str() ); _cprint( "INVALID SCRIPT TRIGGER! [%s][%s]\n", pClient->GetName(), strTrigger.c_str() ); //assert( 0 ); pClient->IncInvalidScriptTriggerCount(); int nInvalidScriptTriggerCount = pClient->GetInvalidScriptTriggerCount(); // 잘못된 스크립트 트리거가 10번째 날아왔을 경우 처리(한 번 접속에 대해 1회만 걸림) if( nInvalidScriptTriggerCount == 10 ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "InvalidScript", "%d Invalid script triggers sent from\t%20s\t%20s\t%20s", nInvalidScriptTriggerCount, pClient->GetName(), pClient->GetAccountName(), pClient->pConnection->GetPeerAddress().GetAddr() ); } return; } char buf[30]; // s_sprintf( buf, _countof( buf ), "game.script.%02d.trigger", GetThreadId( GetCurrentThread() ) ); s_sprintf( buf, _countof( buf ), "game.script.%02d.trigger", GetCurrentThreadId( ) ); ENV().Set( buf, strTrigger.c_str() ); // s_sprintf( buf, _countof( buf ), "game.script.%02d.time", GetThreadId( GetCurrentThread() ) ); s_sprintf( buf, _countof( buf ), "game.script.%02d.time", GetCurrentThreadId( ) ); ENV().Set( buf, (int)GetArTime() ); GameObject *pObj = StructCreature::raw_get( pClient->GetContactNPCHandle() ); if( !pObj ) { if( pClient->IsNonNPCDialog() ) { pClient->SetNonNPCDialog( false ); } else { return; } } //GameObject* pObj = StructCreature::raw_get(pClient->GetContactNPCHandle()); //if (!pObj) //{ // if (pClient->IsNonNPCDialog()) // { // pClient->SetNonNPCDialog(false); // } // else // { // // return; // } //} if( pClient->IsFixedDialogTrigger( strTrigger.c_str() ) ) { pClient->ClearFixedDialogTrigger(); } else { if( pClient->IsSpecialDialogMenu( strTrigger.c_str() ) ) { pClient->ClearSpecialDialogMenu(); } pClient->ClearDialogMenu(); } // _oprint( "contact begin : %s(%s)\n", pClient->GetName(), strTrigger.c_str() ); int nPrevChaos = pClient->GetChaos(); StructGold nPrevGold( pClient->GetGold() ); LUA()->RunString( strTrigger.c_str() ); StructNPC *pNPC = static_cast< StructNPC * >( pObj ); LOG::Log11N4S( LM_NPC_PROCESS, pClient->GetAccountID(), pClient->GetSID(), 0, ( pNPC ) ? pNPC->GetNPCID() : 0, nPrevChaos, nPrevGold.GetRawData(), pClient->GetChaos(), pClient->GetGold().GetRawData(), pClient->GetX(), pClient->GetY(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, strTrigger.c_str(), LOG::STR_NTS ); // 스크립트가 다이얼로그 출력을 빠트렸다면.. if( pClient->HasDialog() ) pClient->ShowDialog(); // _oprint( "contact end : %s(%s)\n", pClient->GetName(), strTrigger.c_str() ); } void onBuyItem( StructPlayer *pClient, TS_CS_BUY_ITEM* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); std::string strMarketName = pClient->GetLastContactMarket(); if( pMsg->buy_count <= 0 ) { SendResult( pClient, pMsg->id, RESULT_UNKNOWN ); return; } // 상점 정보 얻어온다 _MARKET_INFO *pInfo = NULL; if( !(pInfo = GameContent::GetMarketInfo( strMarketName.c_str() )) ) { assert( 0 ); return; } std::vector< _MARKET_INFO::_MARKET_TAG >::iterator it; for( it = pInfo->vItemList.begin(); it != pInfo->vItemList.end(); ++it ) { _MARKET_INFO::_MARKET_TAG & mt = (*it); if( mt.code == pMsg->item_code ) { bool bJoinable = StructItem::GetItemBase( pMsg->item_code ).Flag.IsOn( ItemBase::FLAG_JOIN ); if( !bJoinable && pMsg->buy_count != 1 ) pMsg->buy_count = 1; // 루피 가격 계산 오버플로우 체크, 소지금 체크 StructGold nTotalPrice = mt.price * pMsg->buy_count; if( nTotalPrice.GetRawData() / pMsg->buy_count != mt.price.GetRawData() || pClient->GetGold() < nTotalPrice ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY ); return; } // 헌터홀릭 포인트 가격 계산 오버플로우 체크, 소지량 체크 int nTotalHuntaholicPoint = mt.huntaholic_point * pMsg->buy_count; if( nTotalHuntaholicPoint / pMsg->buy_count != mt.huntaholic_point || pClient->GetHuntaholicPoint() < nTotalHuntaholicPoint ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_HUNTAHOLIC_POINT ); return; } // 아레나 포인트 가격 계산 오버플로우 체크, 소지량 체크 int nTotalArenaPoint = mt.arena_point * pMsg->buy_count; if( nTotalArenaPoint / pMsg->buy_count != mt.arena_point || pClient->GetBattleArenaPoint() < nTotalArenaPoint ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_ARENA_POINT ); return; } if( StructItem::GetItemBase( mt.code ).fWeight * pMsg->buy_count > pClient->GetMaxWeight() - pClient->GetWeight() ) { // _cprint( "onBuyItem() : 넘 무겁다. %s\n", pClient->GetName() ); SendResult( pClient, pMsg->id, RESULT_TOO_HEAVY ); return; } ItemUID uid = 0; if( pClient->ChangeGold( pClient->GetGold() - ( mt.price * pMsg->buy_count ) ) != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY, pMsg->item_code ); return; } if(nTotalHuntaholicPoint) pClient->SetHuntaholicPoint( pClient->GetHuntaholicPoint() - nTotalHuntaholicPoint ); if( nTotalArenaPoint ) { int nPrevArenaPoint = pClient->GetBattleArenaPoint(); pClient->AddBattleArenaPoint( -1 * nTotalArenaPoint ); LOG::Log11N4S( LM_BATTLE_ARENA_POINT_USE, pClient->GetAccountID(), pClient->GetSID(), nTotalArenaPoint, nPrevArenaPoint, pClient->GetBattleArenaPoint(), pMsg->item_code, pMsg->buy_count, 0, pClient->GetContactNPCHandle(), 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, strMarketName.c_str(), LOG::STR_NTS, "", LOG::STR_NTS ); } if( bJoinable ) { StructItem *pItem = StructItem::AllocItem( 0, pMsg->item_code, pMsg->buy_count, ItemInstance::BY_MARKET ); StructItem *pNewItem = pClient->PushItem( pItem, pItem->GetCount() ); assert( pNewItem ); uid = pNewItem ? pNewItem->GetItemUID() : 0; // Credits to AziaMafia: OnItemBuy std::string onbuyitemstring; XStringUtil::Format(onbuyitemstring, "MarketSystem:OnItemBuy( %d , %d )", pNewItem->GetItemCode(), pNewItem->GetHandle()); LUA()->RunString(onbuyitemstring.c_str()); LOG::Log11N4S( LM_ITEM_BUY, pClient->GetAccountID(), pClient->GetSID(), pNewItem->GetItemEnhance() * 100 + pNewItem->GetItemLevel(), pMsg->item_code, pMsg->buy_count, pNewItem->GetCount(), pMsg->buy_count * mt.price.GetRawData(), pClient->GetGold().GetRawData(), pClient->GetContactNPCHandle(), pMsg->buy_count * mt.huntaholic_point, uid, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, strMarketName.c_str(), LOG::STR_NTS, "", 0 ); if( pNewItem != pItem ) { StructItem::PendFreeItem( pItem ); } } else { for( int i = 0; i < pMsg->buy_count; ++i ) { StructItem *pItem = StructItem::AllocItem( 0, pMsg->item_code, 1, ItemInstance::BY_MARKET ); StructItem *pNewItem = pClient->PushItem( pItem, pItem->GetCount() ); assert( pNewItem ); uid = pNewItem ? pNewItem->GetItemUID() : 0; // Credits to AziaMafia: OnItemBuy std::string onbuyitemstring; XStringUtil::Format(onbuyitemstring, "MarketSystem:OnItemBuy( %d , %d )", pNewItem->GetItemCode(), pNewItem->GetHandle() ); LUA()->RunString(onbuyitemstring.c_str()); LOG::Log11N4S( LM_ITEM_BUY, pClient->GetAccountID(), pClient->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), pMsg->item_code, pMsg->buy_count, pNewItem->GetCount(), pMsg->buy_count * mt.price.GetRawData(), pClient->GetGold().GetRawData(), pClient->GetContactNPCHandle(), pMsg->buy_count * mt.huntaholic_point, uid, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, strMarketName.c_str(), LOG::STR_NTS, "", 0 ); if( pNewItem != pItem ) { // _oprint( "b:" ); StructItem::PendFreeItem( pItem ); } } } SendResult( pClient, pMsg->id, RESULT_SUCCESS, pMsg->item_code ); // 시스템 메시지 출력을 위해 필요한 정보 추가 송신 TS_SC_NPC_TRADE_INFO trade_info_msg; trade_info_msg.is_sell = false; trade_info_msg.code = pMsg->item_code; trade_info_msg.count = pMsg->buy_count; trade_info_msg.price = mt.price.GetRawData(); trade_info_msg.huntaholic_point = mt.huntaholic_point; trade_info_msg.arena_point = mt.arena_point; trade_info_msg.target = pClient->GetContactNPCHandle(); PendMessage( pClient, &trade_info_msg ); // Fraun 9/28/2025 attempt to fix dupe bug SendGoldChaosUpdateMsg(pClient); break; } } return; } void onSellItem( StructPlayer *pClient, TS_CS_SELL_ITEM* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructItem *pItem = StructItem::FindItem( pMsg->handle ); if( !pItem || ( pItem->GetOwnerHandle() && pItem->GetOwnerHandle() != pClient->GetHandle() ) || !pItem->IsInInventory() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } if( pMsg->sell_count <= 0 ) { SendResult( pClient, pMsg->id, RESULT_UNKNOWN ); return; } if( !pClient->IsSellable( pItem ) || pItem->GetCount() < pMsg->sell_count ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } ItemBase::ItemCode code = pItem->GetItemCode(); ItemUID uid = pItem->GetItemUID(); // 아이템을 NPC가 매입하는 가격을 602700 ~ 602799 사이의 ID에 대해서는 절반 룰 적용 안 함(환전용 아이템 처리) StructGold nPrice( GameRule::GetItemSellPrice( pItem->GetItemBase().nPrice, pItem->GetItemRank(), pItem->GetItemLevel(), ( pItem->GetItemCode() >= 602700 && pItem->GetItemCode() <= 602799 ), pItem->GetMaxEtherealDurability() && !pItem->GetCurrentEtherealDurability(), pItem->IsEquipment() ).GetRawData() ); __int64 nResultCount = pItem->GetCount() - pMsg->sell_count; int nEnhanceLevel = pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(); if( nResultCount < 0 ) { SendResult( pClient, TM_CS_SELL_ITEM, RESULT_NOT_EXIST, code ); return; } StructGold nPrevGold = pClient->GetGold(); if( nPrevGold + ( nPrice * pMsg->sell_count ) > GameRule::MAX_GOLD_FOR_INVENTORY ) { SendResult( pClient, TM_CS_SELL_ITEM, RESULT_TOO_MUCH_MONEY, code ); return; } if( !pClient->EraseItem( pItem, pMsg->sell_count ) ) { SendResult( pClient, TM_CS_SELL_ITEM, RESULT_NOT_ACTABLE, code ); return; } if( pClient->ChangeGold( nPrevGold + ( nPrice * pMsg->sell_count ) ) != RESULT_SUCCESS ) { LOG::Log11N4S( LM_ITEM_SELL, pClient->GetAccountID(), pClient->GetSID(), nEnhanceLevel, code, pMsg->sell_count, nResultCount, pMsg->sell_count * nPrice.GetRawData(), pClient->GetGold().GetRawData(), pClient->GetContactNPCHandle(), nPrevGold.GetRawData(), uid, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); SendResult( pClient, TM_CS_SELL_ITEM, RESULT_TOO_MUCH_MONEY, code ); return; } LOG::Log11N4S( LM_ITEM_SELL, pClient->GetAccountID(), pClient->GetSID(), nEnhanceLevel, code, pMsg->sell_count, nResultCount, pMsg->sell_count * nPrice.GetRawData(), pClient->GetGold().GetRawData(), pClient->GetContactNPCHandle(), nPrevGold.GetRawData(), uid, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); SendResult( pClient, TM_CS_SELL_ITEM, RESULT_SUCCESS, code ); // 시스템 메시지 출력을 위해 필요한 정보 추가 송신 TS_SC_NPC_TRADE_INFO trade_info_msg; trade_info_msg.is_sell = true; trade_info_msg.code = code; trade_info_msg.count = pMsg->sell_count; trade_info_msg.price = nPrice.GetRawData() * pMsg->sell_count; trade_info_msg.target = pClient->GetContactNPCHandle(); PendMessage( pClient, &trade_info_msg ); } void onDonateItem( StructPlayer *pClient, TS_CS_DONATE_ITEM* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( ( !pMsg->gold && !pMsg->item_count )//&& !pMsg->jp ) || pMsg->item_count > GameRule::MAX_DONATE_ITEM_COUNT || pMsg->size != sizeof( TS_CS_DONATE_ITEM ) + ( pMsg->item_count * sizeof( TS_CS_DONATE_ITEM::DonateItemInfo ) ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } c_fixed10 fPrevImmoral = pClient->GetImmoralPoint(); c_fixed10 fNewImmoral = fPrevImmoral; // 기부 금액 체크 StructGold nDonateGold( pMsg->gold ); if( ( nDonateGold != 0 && nDonateGold < GameRule::MIN_DONATE_GOLD ) || pClient->GetGold() < nDonateGold ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "BugReport", "Donate Gold Bug [%s:%s]: Try[%d] / Owning[%d]", pClient->GetAccountName(), pClient->GetName(), pMsg->gold, pClient->GetGold().GetRawData() ); SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY ); return; } // 기부 아이템 확인 및 포인터 보관 TS_CS_DONATE_ITEM::DonateItemInfo *pDonateItemInfoList = reinterpret_cast< TS_CS_DONATE_ITEM::DonateItemInfo * >( pMsg + 1 ); StructItem * pDonateItemList[ GameRule::MAX_DONATE_ITEM_COUNT ]; memset( pDonateItemList, 0, sizeof( pDonateItemList ) ); for( unsigned char i = 0 ; i < pMsg->item_count ; ++i ) { StructItem *pItem = StructItem::FindItem( pDonateItemInfoList[i].handle ); if( !pItem || ( pItem->GetOwnerHandle() && pItem->GetOwnerHandle() != pClient->GetHandle() ) || !pItem->IsInInventory() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } // 기부 불가 플래그 적용된 아이템은 기부 불가 if( !pItem->IsDonatable() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } // 효과 적용 중인 가방은 기부 불가 if( pClient->GetWearedItem( ItemBase::WEAR_BAG_SLOT ) == pItem ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } // 테이밍 중인 소환수 카드는 기부 불가 if( pItem->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_TAMING ) ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } // 편성되어 있거나 벨트 장착 중인 소환수 카드는 기부 불가 for( int nSummonIdx = 0 ; nSummonIdx < 6 ; ++nSummonIdx ) { if( pClient->GetSummonCardAt( nSummonIdx ) == pItem ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } } for( int nBeltIdx = 0; nBeltIdx < 8; ++nBeltIdx ) { if( pClient->GetBeltSlotCardAt( nBeltIdx ) == pItem ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } } if( pItem->GetCount() < pDonateItemInfoList[i].count || pDonateItemInfoList[i].count < 1 ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "BugReport", "Donate Item Bug [%s:%s]", pClient->GetAccountName(), pClient->GetName() ); GameRule::RegisterBlockAccount( pClient->GetAccountName() ); SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); if( pClient->pConnection && pClient->pConnection->IsConnected() ) { pClient->pConnection->Close(); return; } } pDonateItemList[i] = pItem; } /* 기부 잡포인트 확인 int nDonateJP = pMsg->jp; if( ( nDonateJP != 0 && nDonateJP < GameRule::MIN_DONATE_JP ) || pClient->GetJobPoint() < nDonateJP ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "BugReport", "Donate JP Bug [%s:%s]: Try[%d] / Owning[%d]", pClient->GetAccountName(), pClient->GetName(), pMsg->jp, pClient->GetJobPoint() ); SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_JP ); return; }*/ c_fixed10 nReward; // 돈 기부에 대한 보상 지급 if( nDonateGold >= GameRule::MIN_DONATE_GOLD ) { // 이모럴이니까 보상수치(양수)를 빼야 감소함(모럴 증가) StructGold nPrevGold( pClient->GetGold() ); bool bSuccess = ( pClient->ChangeGold( nPrevGold - nDonateGold ) == RESULT_SUCCESS ); if( bSuccess ) { nReward = GameRule::GetDonationRewardMoralPoint( nDonateGold.GetRawData() ); fNewImmoral -= nReward; } LOG::Log11N4S( LM_DONATE_ITEM, pClient->GetAccountID(), pClient->GetPlayerUID(), 0, nPrevGold.GetRawData(), nDonateGold.GetRawData(), pClient->GetGold().GetRawData(), fPrevImmoral.get(), nReward.get(), fNewImmoral.get(), bSuccess, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS ); } // 각 기부 아이템에 대한 보상 지급 for( unsigned char i = 0 ; i < pMsg->item_count ; ++i ) { // 이모럴이니까 보상수치(양수)를 빼야 감소함(모럴 증가) nReward = GameRule::GetDonationRewardMoralPoint( pDonateItemList[i]->GetItemBase().nPrice.GetRawData() ) * pDonateItemInfoList[i].count; ItemBase::ItemCode nItemCode = pDonateItemList[i]->GetItemCode(); __int64 nPrevItemCount = pDonateItemList[i]->GetCount(); __int64 nDonateItemCount = pDonateItemInfoList[i].count; ItemUID nItemUID = pDonateItemList[i]->GetItemUID(); bool bSuccess = true; if( !pClient->EraseItem( pDonateItemList[i], pDonateItemInfoList[i].count ) ) { nReward = 0; bSuccess = false; } fNewImmoral -= nReward; LOG::Log11N4S( LM_DONATE_ITEM, pClient->GetAccountID(), pClient->GetPlayerUID(), nItemCode, nPrevItemCount, nDonateItemCount, ( !bSuccess || nPrevItemCount > nDonateItemCount ) ? pDonateItemList[i]->GetCount() : 0, fPrevImmoral.get(), nReward.get(), fNewImmoral.get(), bSuccess, nItemUID, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS ); } /* JP 기부에 대한 보상 지급 if( nDonateJP >= GameRule::MIN_DONATE_JP ) { int nPrevJP = pClient->GetJobPoint(); bool bSuccess = (nPrevJP >= nDonateJP) ? true : false; if( bSuccess ) { // 이모럴이니까 보상수치(양수)를 빼야 감소함(모럴 증가) pClient->SetJP( nPrevJP - nDonateJP ); nReward = GameRule::GetDonationRewardMoralPoint( GameRule::DONATE_GOLD_PER_JP * nDonateJP ); fNewImmoral -= nReward; } LOG::Log11N4S( LM_DONATE_JP, pClient->GetAccountID(), pClient->GetPlayerUID(), 0, nPrevJP, nDonateJP, pClient->GetJobPoint(), fPrevImmoral.get(), nReward.get(), fNewImmoral.get(), bSuccess, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS ); }*/ // 길드 단위 누적 기부 포인트 기록 if( GameRule::bUseGuildDonationPoint && pClient->GetGuildID() ) { GuildManager::GetInstance().AddGuildDonationPoint( pClient->GetGuildID(), fPrevImmoral - fNewImmoral ); } // 개인별 기부 포인트 랭킹 데이터 추가 RankingManager::Instance().AddRankingScore( RankingManager::RANKING_TYPE_DONATION, pClient->GetPlayerUID(), pClient->GetName(), fPrevImmoral - fNewImmoral, true ); pClient->SetImmoralPoint( fNewImmoral ); // 블러디 풀렸을 경우 방송 if( fPrevImmoral >= GameRule::MORAL_LIMIT && !pClient->IsBloodyCharacter() ) BroadcastStatusMessage( pClient ); SendResult( pClient, pMsg->id, RESULT_SUCCESS ); } void onDonateReward( StructPlayer *pClient, TS_CS_DONATE_REWARD* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); c_fixed10 fPrevMoralPoint = pClient->GetImmoralPoint() * -1; c_fixed10 fCurrentMoralPoint = fPrevMoralPoint; if( !pMsg->reward_count || pMsg->reward_count > GameRule::DONATION_MAX_REWARD_ITEM_TYPE || pMsg->size != sizeof( TS_CS_DONATE_REWARD ) + sizeof( TS_CS_DONATE_REWARD::RewardInfo ) * pMsg->reward_count ) { return; } TS_CS_DONATE_REWARD::RewardInfo * pRewardInfo = reinterpret_cast< TS_CS_DONATE_REWARD::RewardInfo * >( pMsg + 1 ); // fCostMoralPoint는 오버 플로우가 발생하지 않음. DONATION_POINT_FOR_REWARD_ITEM의 최대값 * 65535 * c_fixed10::FACTOR * 4 해도 64비트 이내에서 끝남. c_fixed10 fCostMoralPoint; //c_fixed10 for( unsigned char i = 0 ; i < pMsg->reward_count ; ++i, ++pRewardInfo ) { fCostMoralPoint += GameRule::DONATION_POINT_FOR_REWARD_ITEM[ pRewardInfo->reward_type ] * pRewardInfo->count; } if( fCostMoralPoint > fCurrentMoralPoint ) { // 모럴 포인트가 필요 수치 미만이면 223번 스트링(해당 아이템의 소유권이 없습니다) 출력 SendResult( pClient, pMsg->id, RESULT_NOT_OWN ); return; } pRewardInfo = reinterpret_cast< TS_CS_DONATE_REWARD::RewardInfo * >( pMsg + 1 ); for( unsigned char i = 0 ; i < pMsg->reward_count ; ++i, ++pRewardInfo ) { c_fixed10 fCost = GameRule::DONATION_POINT_FOR_REWARD_ITEM[ pRewardInfo->reward_type ] * pRewardInfo->count; if( fCurrentMoralPoint < fCost ) { // 위에서 미리 전체 필요 모럴 포인트를 계산했으므로 여기서 모자라면 뭔가 이상한 것 assert( 0 ); break; } fCurrentMoralPoint -= fCost; StructItem *pItem = StructItem::AllocItem( 0, GameRule::DONATION_REWARD_ITEM_CODE[ pRewardInfo->reward_type ], pRewardInfo->count, ItemInstance::BY_DONATION_REWARD ); if( !pItem ) { break; } StructItem *pNewItem = pClient->PushItem( pItem, pItem->GetCount() ); if( !pNewItem ) { continue; } LOG::Log11N4S( LM_ITEM_TAKE, pClient->GetAccountID(), pClient->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), pItem->GetItemCode(), pItem->GetCount(), pNewItem->GetCount(), pClient->GetGold().GetRawData(), pClient->GetGold().GetRawData(), pClient->GetX(), pClient->GetY(), pNewItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "DONATION_REWARD", LOG::STR_NTS ); if( pNewItem != pItem ) { StructItem::PendFreeItem( pItem ); } } // 모럴 수치가 소모 수치 이상일 때만 감소 연산이 일어나므로 Status를 방송할 필요는 없음(모럴 상태에서 보상 받고 데모니악/블러디가 될 일이 없음) if( fPrevMoralPoint != fCurrentMoralPoint ) { pClient->SetImmoralPoint( fCurrentMoralPoint * -1 ); } } void onRankingTopRecord( StructPlayer *pClient, TS_CS_RANKING_TOP_RECORD* pMsg ) { RankingManager::_RANKING_TYPE eRankingType = static_cast< RankingManager::_RANKING_TYPE >( pMsg->ranking_type ); // 랭킹 스코어 데이터가 아닌 타입의 경우 상위 랭킹 목록을 요청받아도 응답을 주지 않아야 함 if( eRankingType == RankingManager::RANKING_TYPE_DONATION_REWARD ) { assert( 0 ); return; } static const size_t MAX_RANKING_RECORD_COUNT = 10; char szBuffer[ sizeof( TS_SC_RANKING_TOP_RECORD ) + sizeof( TS_SC_RANKING_TOP_RECORD::RANKING_RECORD ) * MAX_RANKING_RECORD_COUNT ]; memset( szBuffer, 0, sizeof( szBuffer ) ); TS_SC_RANKING_TOP_RECORD * pTopRecordMsg = new ( szBuffer ) TS_SC_RANKING_TOP_RECORD(); pTopRecordMsg->ranking_type = pMsg->ranking_type; pTopRecordMsg->requester_rank = static_cast< unsigned short >( RankingManager::Instance().GetRank( eRankingType, pClient->GetPlayerUID() ) ); pTopRecordMsg->requester_score = RankingManager::Instance().GetRankingScore( eRankingType, pClient->GetPlayerUID() ).get(); std::vector< RankingManager::RankingRecord > vTopRecord; RankingManager::Instance().GetRankingTopRecord( eRankingType, vTopRecord ); pTopRecordMsg->record_count = static_cast< unsigned short >( vTopRecord.size() ); pTopRecordMsg->size += sizeof( TS_SC_RANKING_TOP_RECORD::RANKING_RECORD ) * pTopRecordMsg->record_count; TS_SC_RANKING_TOP_RECORD::RANKING_RECORD * pRankingRecord = reinterpret_cast< TS_SC_RANKING_TOP_RECORD::RANKING_RECORD * >( pTopRecordMsg + 1 ); unsigned short nRank = 0; for( std::vector< RankingManager::RankingRecord >::const_iterator it = vTopRecord.begin() ; it != vTopRecord.end() ; ++it, ++pRankingRecord ) { pRankingRecord->rank = ++nRank; s_strcpy( pRankingRecord->ranker_name, _countof( pRankingRecord->ranker_name ), StructPlayer::GetPlayerName( (*it).first ) ); pRankingRecord->score = (*it).second.get(); } assert( nRank == pTopRecordMsg->record_count ); PendMessage( pClient, pTopRecordMsg ); } void onArrangeItem( StructPlayer *pClient, TS_CS_ARRANGE_ITEM* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); unsigned short nRet = pClient->ArrangeItem( pMsg->bIsStorage ); SendResult( pClient, pMsg->id, nRet, pMsg->bIsStorage ); if( nRet == RESULT_SUCCESS ) SendItemList( pClient, pMsg->bIsStorage ); } void onChangeSummonName( StructPlayer *pClient, TS_CS_CHANGE_SUMMON_NAME* pMsg ) { // 락은 소환수 기준으로 걸어야 하지만 소환수의 포인터를 얻기 위해서는 // 플레이어 기준의 락도 필요함. 결국 플레이어와 소환수의 공용 락이 // 필요한데, 소환수의 포인터를 얻기 이전에는 소환수의 정보도 얻을 수 없으므로 // 전역 락을 사용한다(크리처 이름 변경은 방송을 동반한다) ARCADIA_LOCK( ArcadiaServer::Instance().LockWorld() ); if( !pClient->GetLastContactLong( "creature_name_change" ) ) return; pClient->SetLastContact( "creature_name_change", 0 ); int index = pClient->GetNameChangeTarget(); if( !index ) return; StructSummon *pSummon = pClient->GetSummon( index ); pClient->SetNameChangeTarget( 0 ); if( !pSummon ) return; // 유효한 이름인지 체크 int code_page = ENV().GetInt( "CodePage", CP_ACP ); pMsg->szName[ _countof( pMsg->szName ) - 1 ] = '\0'; if( strlen( pMsg->szName ) < 4 ) { PrintfChatMessage( false, CHAT_SUMMON, "@SYSTEM", pClient, "@117" ); return; } // 같은 이름 사용 불가 else if( !strncmp( pMsg->szName, pSummon->GetName(), std::max( strlen( pMsg->szName ), strlen( pSummon->GetName() ) ) ) ) { PrintfChatMessage( false, CHAT_SUMMON, "@SYSTEM", pClient, "@118" ); return; } else if( strlen( pMsg->szName ) == 0 || strlen( pMsg->szName ) > 18 || !_stricmp( pSummon->GetName(), pMsg->szName ) || !GameRule::IsValidName( code_page, pMsg->szName, (int)strlen( pMsg->szName ) + 1, 4, 18 ) || // IsValidName에서 소환수 이름은 최대 길이 19 Byte이므로 버퍼 제한 18로 고정 GameContent::IsBannedWord( code_page, pMsg->szName ) ) { PrintfChatMessage( false, CHAT_SUMMON, "@SYSTEM", pClient, "@17" ); return; } // 소환수 이름 변경 프로세스를 스크립트로 요청 std::string szChangeSummonNameFunction = " "; XStringUtil::Format( szChangeSummonNameFunction, "change_summon_name( %d, '%s' )", index, pMsg->szName ); LUA()->RunString( szChangeSummonNameFunction.c_str() ); // { 변경된 이름 및 성공/실패 방송 // 변경에 성공했는지 체크 if( strncmp( pMsg->szName, pSummon->GetName(), std::max( strlen( pMsg->szName ), strlen( pSummon->GetName() ) ) ) ) { // 실패 메시지 PrintfChatMessage( false, CHAT_SUMMON, "@SYSTEM", pClient, "@129" ); return; } // 성공 메시지(클라에서 TS_SC_CHANGE_NAME 메시지 받으면 출력하게 변경되었음) //PrintfChatMessage( false, CHAT_SUMMON, "@SYSTEM", pClient, "@132\v#@creature_name@#\v%s", pSummon->GetName() ); // 각종 UI에 이름 변경을 알려주기 위함 SendPropertyMessage( pClient, pSummon->GetHandle(), "name", pSummon->GetName() ); // 주변 사람들에게 바뀐 이름 보이게 하기 위함 TS_SC_CHANGE_NAME msg; msg.handle = pSummon->GetHandle(); s_strcpy( msg.name, _countof( msg.name ), pSummon->GetName() ); ArcadiaServer::Instance().Broadcast( pSummon->GetRX(), pSummon->GetRY(), pSummon->GetLayer(), &msg ); // } 방송 끝 // 소환수 스텟 재계산(필요는 없지만 -_-;) if( pSummon->IsInWorld() ) pSummon->SetNeedCalculateStat(); else pSummon->CalculateStat(); // 변경된 정보 저장 pClient->Save(); } void onGetSummonSetupInfo( StructPlayer *pClient, TS_CS_GET_SUMMON_SETUP_INFO* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); SendCreatureEquipMessage( pClient, pMsg->show_dialog ); } void onSetPetName( StructPlayer *pClient, TS_CS_SET_PET_NAME* pMsg ) { // 소환된 펫은 이름이 바뀔 수 없음(소환되기 전에 이름을 바꾸게 되므로) if( !pMsg->handle ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 지정된 펫이 존재하는지, 월드에 나와 있는지(소환되기 전에 이름을 바꾸므로 월드에 없어야 함), // 이름을 바꿀 수 있는 레어 펫인지 체크 StructCreature::iterator itPet = StructCreature::get( pMsg->handle ); StructCreature *pTarget = (*itPet); if( !pTarget || !pTarget->IsPet() ) return; StructPet *pPet = static_cast< StructPet * >( pTarget ); if( pPet->IsInWorld() || !pPet->IsRare() || pPet->IsNameChanged() ) return; // 소유권 체크(펫 주인 + 펫 우리 소유권) StructItem *pCage = pPet->GetParentCage(); if( !pCage || pCage->GetOwnerHandle() != pClient->GetHandle() || pPet->GetMaster() != pClient ) return; // NULL 문자 없어서 거지되는 걸 막음 pMsg->name[ _countof( pMsg->name ) - 1 ] = '\0'; unsigned short nResult = pPet->ChangeName( pMsg->name, false ); // 시스템 메시지 출력 switch( nResult ) { case RESULT_ACCESS_DENIED: case RESULT_LIMIT_MAX: PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@1106" ); break; case RESULT_LIMIT_MIN: PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@1105" ); break; case RESULT_ALREADY_EXIST: PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@118" ); break; } SendResult( pClient, pMsg->id, nResult, pMsg->handle ); } void onCancelAction( StructPlayer *pClient, TS_CS_CANCEL_ACTION* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructCreature * pCreature = pClient; if( pClient->GetHandle() != pMsg->handle ) { if( pCreature = pClient->GetSummon( pMsg->handle ) ) { if( !pCreature->IsInWorld() ) return; } else { return; } } if( pCreature->IsUsingSkill() ) { pCreature->CancelSkill(); } else if( pCreature->IsAttacking() ) { pCreature->CancelAttack(); } } void onSkill( StructPlayer *pClient, TS_CS_SKILL* pMsg ) { if( pClient->IsDead() ) return; AR_TIME casting_time = 0; StructCreature *pCaster = pClient; //AziaMafia log GS //_cprint("onSkill : %s\n", pCaster->GetName()); if( pMsg->caster != pClient->GetHandle() ) pCaster = pClient->GetSummon( pMsg->caster ); // 활동불가 체크 if( !pCaster || !pCaster->IsInWorld() ) { StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_EXIST ); return; } // AziaMafia FIX ZENTRIX if (pCaster->IsSummon()) { StructPlayer* pPlayer = static_cast(pCaster)->GetMaster(); StructState::StateCode nCode = static_cast(201085); if (pPlayer->GetState(nCode)) { return; } if (pPlayer->IsRiding() || pPlayer->HasRidingState()) // || !pPlayer->IsMountable(true) { return; } } // AziaMafia FIN FIX ZENTRIX SkillBase *pBase = GameContent::GetSkillBase( pMsg->skill_id ); if( !pBase || !pBase->IsValid() || pBase->IsSystemSkill() ) { StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_ACCESS_DENIED ); return; } if( pClient == pCaster ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsSitDown() ) { pClient->StandUp(); BroadcastStatusMessage( pClient ); } } // 캐스팅 가능한지 검사 if( ( pBase->IsPhysicalSkill() && !pCaster->IsSkillCastable() ) || ( !pBase->IsPhysicalSkill() && !pCaster->IsMagicCastable() ) ) { //AziaMafia log GS //_cprint( "impossible spam 1: %s\n", pCaster->GetName() ); StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_ACTABLE ); return; } // 스킬이 발동한 region 좌표 unsigned rx, ry; rx = pMsg->x / g_nRegionSize; ry = pMsg->y / g_nRegionSize; // 만약 타겟 대상이라면 region 좌표를 타겟의 위치로 수정 GameObject * pTarget = NULL; GameObject::iterator itTarget; if( pMsg->target ) { itTarget = GameObject::get( pMsg->target ); pTarget = (*itTarget); if( !pTarget ) { // _cprint( "시전실패 (대상없음) : %s\n", pCaster->GetName() ); StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_EXIST ); return; } if( pTarget->IsCreature() ) { if( !isValidTarget( pClient, pTarget ) ) { SendLeaveMsg( pClient, pTarget ); StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_EXIST ); return; } rx = pTarget->GetRX(); ry = pTarget->GetRY(); } else { // 이 부분이 무슨 의미가 있을지 잘 모르겠다;; 아이템이나 프랍 핸들을 타겟으로 보내는 경우도 있나;? rx = pCaster->GetRX(); ry = pCaster->GetRY(); pTarget = NULL; } } ArcadiaLock __lock = -1; // 유효한 타겟이 있다면 시전자와 타겟을 포함하는 락을 걸고, 아니라면 시전자와 패킷에 지정된 영역을 포함하는 락을 걸어 줌 if( pTarget ) __lock = ArcadiaServer::Instance().LockObjects( pCaster, pTarget ); else __lock = ArcadiaServer::Instance().LockObjectWithSpecificRegion( pCaster, rx, ry ); ARCADIA_LOCK( __lock ); if( pCaster->IsMoving( GetArTime() ) ) { StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_ACTABLE ); return; } // one more time~!! if( ( pBase->IsPhysicalSkill() && !pCaster->IsSkillCastable() ) || ( !pBase->IsPhysicalSkill() && !pCaster->IsMagicCastable() ) ) { //AziaMafia log GS //_cprint("impossible spam 2: %s\n", pCaster->GetName()); StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_ACTABLE ); return; } const StructSkill * pSkill = pCaster->GetSkill( pMsg->skill_id ); if( !pSkill || pSkill && pSkill->GetSkillUID() == StructSkill::SKILL_UID_ITEM_SKILL ) return; if( pSkill->GetSkillUID() == StructSkill::SKILL_UID_BOOSTER_SKILL ) { StructItem * pItem = pClient->GetWearedItem( ItemBase::WEAR_BOOSTER ); if( !pItem ) { StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_ACCESS_DENIED ); return; } if( pClient->IsUseableItem( pItem, NULL ) == RESULT_SUCCESS ) { if( pItem->GetItemBase().fOptVar1[0] != pMsg->skill_id || pItem->GetItemLevel() < pMsg->skill_level ) { StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_NOT_ACTABLE ); return; } int nResult = pCaster->CastSkill(pMsg->skill_id, pMsg->skill_level, pMsg->target, ArPosition(pMsg->x, pMsg->y, pMsg->z), pCaster->GetLayer() ); if (nResult != RESULT_SUCCESS) { StructSkill::SendSkillCastFailMessage(pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition(pMsg->x, pMsg->y, pMsg->z), nResult); return; } int nCoolTime = pSkill->GetCoolTime( pMsg->skill_level, 0 ); int nCoolTimeGroup = pItem->GetCoolTimeGroup(); pClient->SetItemCoolTime( nCoolTimeGroup, GetArTime() + nCoolTime ); } SendItemCoolTimeInfo( pClient ); return; } // 디버프로 인해 현재 스킬이 음수인 경우 무슨 일이 있더라도 발동이 되지 않아야 한다. if( pSkill->GetCurrentSkillLevel() <= 0 ) { StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), RESULT_ACCESS_DENIED ); return; } // 사용하려는 스킬 레벨이 보유 스킬 레벨보다 높거나 음수일 경우 제한 if( pMsg->skill_level <= 0 || pMsg->skill_level > pSkill->GetCurrentSkillLevel() ) { pMsg->skill_level = pSkill->GetCurrentSkillLevel(); } // 캐스팅 시작 int nResult = pCaster->CastSkill( pMsg->skill_id, pMsg->skill_level, pMsg->target, ArPosition( pMsg->x, pMsg->y, pMsg->z ), pCaster->GetLayer() ); if( nResult != RESULT_SUCCESS ) { //AziaMafia log GS //_cprint("onskill nResult != RESULT_SUCCESS : %s and nResult = %d\n", pCaster->GetName() , nResult); StructSkill::SendSkillCastFailMessage( pClient, pMsg->caster, pMsg->target, pMsg->skill_id, pMsg->skill_level, ArPosition( pMsg->x, pMsg->y, pMsg->z ), nResult ); } } void onEnterEventArea( StructPlayer * pClient, TS_CS_ENTER_EVENT_AREA* pMsg ) { // 지역락 걸고~(스크립트 실행 시에는 지역락 걸어놓고 실행하기) // 단, 지역 락의 영역을 일반 시야 범위의 두 배 반경으로 설정함. // 이렇게 해야 시야 범위 내에서 어떤 변화가 생겨서 변화 지점으로부터 시야 범위 내의 영역에 방송을 해도 락 범위 내부가 됨 ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithSpecificRange( pClient, VISIBLE_REGION_RANGE * 2 ) ); if( !GameContent::IsActivatableEventArea( pClient, pMsg->event_area_id, pMsg->area_index ) ) return; if( !pClient->IsInWorld() || !pClient->IsActable() ) return; // 스크립트 실행 LUA()->RunString( GameContent::GetEventAreaEnterHandler( pMsg->event_area_id ) ); pClient->InsertEventAreaID( pMsg->event_area_id ); // 이벤트 영역 진입 횟수 제한 영역일 경우 진입했던 횟수 증가 if( GameContent::GetEnterCountLimitOfEventArea( pMsg->event_area_id ) ) { pClient->IncEventAreaEnterCount( pMsg->event_area_id ); } } void onLeaveEventArea( StructPlayer * pClient, TS_CS_LEAVE_EVENT_AREA* pMsg ) { // 지역락 걸고~(스크립트 실행 시에는 지역락 걸어놓고 실행하기) // 단, 지역 락의 영역을 일반 시야 범위의 두 배 반경으로 설정함. // 이렇게 해야 시야 범위 내에서 어떤 변화가 생겨서 변화 지점으로부터 시야 범위 내의 영역에 방송을 해도 락 범위 내부가 됨 ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithSpecificRange( pClient, VISIBLE_REGION_RANGE * 2 ) ); if( !pClient->IsInWorld() || !pClient->IsActable() ) return; // 기존에 진입되어 있던 영역이 아니라면 떠나기 핸들러 호출도 실패 if( !pClient->DeleteEventAreaID( pMsg->event_area_id ) ) return; // 스크립트 실행 LUA()->RunString( GameContent::GetEventAreaLeaveHandler( pMsg->event_area_id ) ); } void onUseItem( StructPlayer * pClient, TS_CS_USE_ITEM* pMsg ) { if( !pClient->IsItemUseable() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } // 아이템 없으면 KIN StructItem *pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem || pItem->GetOwnerHandle() != pClient->GetHandle() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->item_handle ); return; } // 인벤토리에 있는 아이템이 아닌 경우 사용 불가(시스템 메시지 출력 안 함) if( !pItem->IsInInventory() ) { return; } if( !pItem->IsSupplyItem() && !pItem->IsUsingItem() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED, pMsg->item_handle ); return; } if( ( pItem->IsWarpItem() || !pItem->IsUsableMoving() ) && pClient->IsMoving( GetArTime() ) ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } if( !pItem->IsUsableSit() && pClient->IsSitDown() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } if( pItem->IsUsableOnOnlySit() && !pClient->IsSitDown() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_ON_STAND_UP, pMsg->item_handle ); return; } if( !pItem->IsUsableInSecroute() && pClient->IsInSecroute() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_SECROUTE, pMsg->item_handle ); return; } if( !pItem->IsUsableInSiegeOrRaid() && pClient->IsInSiegeOrRaidDungeon() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_SIEGE_OR_RAID, pMsg->item_handle ); return; } if( !pItem->IsUsableInEventmap() && pClient->IsInEventmap() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_EVENTMAP, pMsg->item_handle ); return; } if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { if( !pItem->IsUsableInHuntaholic() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_HUNTAHOLIC, pMsg->item_handle ); return; } } else if( pItem->IsUsableInOnlyHuntaholic() ) { SendResult( pClient, pMsg->id, RESULT_ACTABLE_IN_ONLY_HUNTAHOLIC, pMsg->item_handle ); return; } if( pClient->IsInDeathmatch() ) { if( !pItem->IsUsableInDeathmatch() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_DEATHMATCH, pMsg->item_handle ); return; } } else if( pItem->IsUsableInOnlyDeathmatch() ) { SendResult( pClient, pMsg->id, RESULT_ACTABLE_IN_ONLY_DEATHMATCH, pMsg->item_handle ); return; } if( !pItem->IsUsableInSecretDungeon() && pClient->IsInSecretDungeon() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_SECRET_DUNGEON, pMsg->item_handle ); return; } if( !pItem->IsUsableInBattleArena() && pClient->IsInBattleArena() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_BATTLE_ARENA, pMsg->item_handle ); return; } if( pItem->IsExpireItem() && pItem->GetExpireTime() < time( NULL ) ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } int nResult = pClient->IsUseableItem( pItem, NULL ); if( nResult != RESULT_SUCCESS ) { // 아이템 쿨타임이 덜 됐는데 사용 요청이 왔다면 클라 쿨타임 정보 갱신 if( nResult == RESULT_COOL_TIME ) SendItemCoolTimeInfo( pClient ); SendResult( pClient, pMsg->id, nResult, pMsg->item_handle ); return; } if( pClient->GetBoothStatus() != StructPlayer::IS_NOT_BOOTH ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } int nTargetType = 0; int nTargetID = 0; if( pItem->IsNeedTarget() ) { do { GameObject *pObj = GameObject::raw_get( pMsg->target_handle ); if( !pObj ) break; if( pObj->IsPlayer() ) nTargetType = 1; // 플레이어 else if( pObj->IsMonster() ) nTargetType = 2; // 몬스터 else if( pObj->IsSummon() ) nTargetType = 3; // 소환수 else if( pObj->IsNPC() ) nTargetType = 4; // NPC else if( pObj->IsCreature() ) nTargetType = 5; // 그 외 생물체 else nTargetType = 6; // 생물도 아닌 것 if( nTargetType != 6 ) nTargetID = static_cast< StructCreature * >(pObj)->GetSID(); } while( false ); } pMsg->szParameter[ _countof( pMsg->szParameter ) - 1 ] = '\0'; const ItemUID nItemUID = pItem->GetItemUID(); const __int64 nItemEnhanceAndLevel = pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(); const ItemBase::ItemCode nItemCode = pItem->GetItemCode(); const __int64 nItemCount = pItem->GetCount(); const bool bDecreaseCount = pItem->GetItemBase().nType != ItemBase::TYPE_USE; if( pItem->IsNeedTarget() ) { StructCreature::iterator it = StructCreature::get( pMsg->target_handle ); StructCreature *pCreature = *it; if( !pCreature || !pCreature->IsCreature() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->item_handle ); return; } nResult = pClient->IsUseableItem( pItem, pCreature ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nResult, pMsg->item_handle ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( pClient, pCreature ) ); // 스크립트가 있으면서 사용형일 경우, 따로 TS_SC_RESULT를 보내주진 않는다. (스크립트 내에서 알아서 하라고) unsigned short nRet = pClient->UseItem( pItem, pCreature, pMsg->szParameter ); if( nRet != RESULT_SUCCESS_WITHOUT_NOTICE ) { SendResult( pClient, pMsg->id, nRet, pMsg->item_handle ); } if( nRet != RESULT_SUCCESS ) return; } else { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 스크립트가 있으면서 사용형일 경우, 따로 TS_SC_RESULT를 보내주진 않는다. (스크립트 내에서 알아서 하라고) unsigned short nRet = pClient->UseItem( pItem, NULL, pMsg->szParameter ); if( nRet != RESULT_SUCCESS_WITHOUT_NOTICE ) { SendResult( pClient, pMsg->id, nRet, pMsg->item_handle ); } if( nRet != RESULT_SUCCESS && nRet != RESULT_SUCCESS_WITHOUT_NOTICE ) return; } LOG::Log11N4S( LM_ITEM_USE, pClient->GetAccountID(), pClient->GetSID(), nItemEnhanceAndLevel, nItemCode, 1, ( bDecreaseCount ) ? nItemCount - 1 : nItemCount, nTargetType, nTargetID, 0, 0, nItemUID, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, pMsg->szParameter, LOG::STR_NTS, "", 0 ); TS_SC_USE_ITEM_RESULT msg; msg.item_handle = pMsg->item_handle; msg.target_handle = pMsg->target_handle; PendMessage( pClient, &msg ); return; } void onSetProperty( StructPlayer * pClient, TS_CS_SET_PROPERTY* pMsg ) { // 데이터가 잘리더라도 업데이트 해주도록 변경 ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); char szName[_countof(pMsg->name)]; s_strcpy( szName, _countof( szName ), pMsg->name ); // 혹시나 유저에 의해 업데이트 된 클라 저장 정보가 잘리는 지 (4096Bytes 이상) 검사: 업데이트는 해주고 로그만 남김 if( pMsg->size > sizeof( *pMsg ) + 4096 ) { _cprint( "Client Saving Information has exceeced its allowed size(4096Bytes): [%s:%s]\n", pClient->GetName(), szName ); FILELOG( "Client Saving Information has exceeced its allowed size(4096Bytes): [%s:%s]", pClient->GetName(), szName ); } if( !_stricmp( szName, "client_info" ) ) { // client_info 갱신 char* pClientInfo = reinterpret_cast< char* >( pMsg+1 ); char szValue[4096] = ""; s_memcpy( szValue, sizeof( szValue ), pClientInfo, sizeof( szValue ) - sizeof( szValue[0] ) ); szValue[ _countof( szValue ) - 1 ] = '\0'; if( strcmp( pClient->GetClientInfo(), szValue ) != 0 ) { pClient->SetClientInfo( szValue ); pClient->DBQuery( new DB_UpdateClientInfo( pClient, pMsg+1 ) ); } } else if( !_stricmp( szName, "quick_slot" ) ) { // quick_slot 갱신 char* pClientInfo = reinterpret_cast< char* >( pMsg+1 ); char szValue[4096] = ""; s_memcpy( szValue, sizeof( szValue ), pClientInfo, sizeof( szValue ) - sizeof( szValue[0] ) ); szValue[ _countof( szValue ) - 1 ] = '\0'; if( strcmp( pClient->GetQuickSlot(), szValue ) != 0 ) { pClient->SetQuickSlot( szValue ); pClient->DBQuery( new DB_UpdateQuickSlot( pClient, pMsg+1 ) ); } } else if( !_stricmp( szName, "current_key" ) ) { // current_key 갱신 char* pClientInfo = reinterpret_cast< char* >( pMsg+1 ); char szValue[4096] = ""; s_memcpy( szValue, sizeof( szValue ), pClientInfo, sizeof( szValue ) - sizeof( szValue[0] ) ); szValue[ _countof( szValue ) - 1 ] = '\0'; if( strcmp( pClient->GetCurrentKey(), szValue ) != 0 ) { pClient->SetCurrentKey( szValue ); pClient->DBQuery( new DB_UpdateCurrentKey( pClient, pMsg+1 ) ); } } else if( !_stricmp( szName, "saved_key" ) ) { // saved_key 갱신 char* pClientInfo = reinterpret_cast< char* >( pMsg+1 ); char szValue[4096] = ""; s_memcpy( szValue, sizeof( szValue ), pClientInfo, sizeof( szValue ) - sizeof( szValue[0] ) ); szValue[ _countof( szValue ) - 1 ] = '\0'; if( strcmp( pClient->GetSavedKey(), szValue ) != 0 ) { pClient->SetSavedKey( szValue ); pClient->DBQuery( new DB_UpdateSavedKey( pClient, pMsg+1 ) ); } } } StructItem * check_mixable_item( StructPlayer * pPlayer, AR_HANDLE hItem, const __int64 & nItemCount ) { StructItem::iterator it = StructItem::get( hItem ); if( !it.get() ) return NULL; if( !it.get()->IsItem() ) { SendResult( pPlayer, TM_CS_MIX, RESULT_ACCESS_DENIED ); return NULL; } StructItem *pItem = static_cast< StructItem* >( it.get() ); if( !pItem ) { SendResult( pPlayer, TM_CS_MIX, RESULT_NOT_EXIST ); return NULL; } if( pItem->GetCount() < nItemCount ) { SendResult( pPlayer, TM_CS_MIX, RESULT_NOT_EXIST ); return NULL; } if( pItem->GetBaseFlag().IsOn( ItemBase::FLAG_CANT_ENHANCE ) ) { SendResult( pPlayer, TM_CS_MIX, RESULT_ACCESS_DENIED ); return NULL; } if( !pPlayer->IsMixable( pItem ) ) { SendResult( pPlayer, TM_CS_MIX, RESULT_NOT_EXIST ); return NULL; } return pItem; } void onMix( StructPlayer *pClient, TS_CS_MIX *pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pMsg->count > 9 || pMsg->size != sizeof( TS_CS_MIX ) + sizeof( TS_CS_MIX::MIX_INFO ) * pMsg->count ) { if( GameRule::bUseAutoJail ) { pClient->AddState( StructState::NEMESIS_FOR_AUTO, pClient->GetHandle(), 12, GetArTime(), GetArTime() + 864000000 ); pClient->SetAutoUsed(); pClient->Save( true ); } LOG::Log11N4S( LM_AUTO_USER_CHECKED, pClient->GetAccountID(), pClient->GetSID(), pClient->GetLevel(), pClient->GetJobLevel(), pClient->GetJobId(), 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::MIX_BUFFER_OVERRUN_ATTEMPTION, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); pClient->pConnection->Close(); return; } StructItem * pMainItem = NULL; StructItem * pSubItem[ MixBase::MAX_SUB_MATERIAL_COUNT ]; unsigned short pCountList[ MixBase::MAX_SUB_MATERIAL_COUNT ]; memset( pSubItem, 0, sizeof( pSubItem ) ); memset( pCountList, 0, sizeof( pCountList ) ); pMainItem = check_mixable_item( pClient, pMsg->main_item.handle, 1 ); if( pMsg->main_item.handle && !pMainItem ) return; TS_CS_MIX::MIX_INFO *pMixInfo = reinterpret_cast< TS_CS_MIX::MIX_INFO* >( pMsg+1 ); for( int i = 0; i < pMsg->count; ++i, ++pMixInfo ) { pSubItem[i] = check_mixable_item( pClient, pMixInfo->handle, pMixInfo->count ); if( !pSubItem[i] ) return; pCountList[i] = pMixInfo->count; } const MixBase * pMixBase = MixManager::GetProperMixInfoAndArrangeSubMaterials( pMainItem, pMsg->count, pSubItem, pCountList ); if( !pMixBase ) { SendResult( pClient, pMsg->id, RESULT_INVALID_ARGUMENT ); return; } ////////////////////////////////////////////////////////////////////////// // 미확인 아이템 식별이 아니면 if( pMixBase->type != MixBase::MIX_ITEM_IDENTIFY_FOR_RANDOM_OPTION ) { if( pMainItem != NULL ) { // 미확인 아이템이면 강화 조합 불가 if( pMainItem->IsRandomizable() == true && pMainItem->IsIdentified() == false ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } } } ////////////////////////////////////////////////////////////////////////// std::vector< std::pair< StructItem *, __int64 > > vCreatedItem; switch( pMixBase->type ) { case MixBase::MIX_ENHANCE: case MixBase::MIX_ENHANCE_WITHOUT_FAIL: MixManager::EnhanceItem( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_ENHANCE_SKILL_CARD: case MixBase::MIX_ENHANCE_SKILL_CARD_WITHOUT_FAIL: MixManager::EnhanceSkillCard( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_ENHANCE_CREATURE_CARD: case MixBase::MIX_ENHANCE_CREATURE_CARD_WITH_JOKER: MixManager::EnhanceCreatureCard( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_ULTIMATE_ENHANCE: MixManager::UltimateEnhance( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_SET_LEVEL: case MixBase::MIX_SET_LEVEL_CREATE_ITEM: case MixBase::MIX_SET_LEVEL_SET_FLAG: case MixBase::MIX_SET_LEVEL_SET_FLAG_CREATE_ITEM: case MixBase::MIX_SET_LEVEL_SET_FLAG_CREATE_ITEM_WITH_MAIN_MATERIAL_LEVEL: case MixBase::MIX_SET_LEVEL_ON_SUB_MATERIAL_LEVEL_SET_FLAG: case MixBase::MIX_SET_LEVEL_SET_FLAG_CREATE_ITEM_WITH_MAIN_MATERIAL_LEVEL_SET_ZERO: case MixBase::MIX_ADD_LEVEL: case MixBase::MIX_ADD_LEVEL_CREATE_ITEM: case MixBase::MIX_SET_LEVEL_WITH_FAIL: case MixBase::MIX_ADD_LEVEL_SET_FLAG: case MixBase::MIX_ADD_LEVEL_SET_FLAG_CREATE_ITEM: MixManager::MixItemLevel( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_RECYCLE: case MixBase::MIX_RECYCLE_ENHANCE: MixManager::RecycleItem( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_RESTORE_ENHANCE_SET_FLAG: MixManager::RestoreEnhance( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_CREATE_ITEM: case MixBase::MIX_CREATE_ITEM_IN_MASS: MixManager::CreateItem( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList, &vCreatedItem ); break; case MixBase::MIX_CHANGE_APPEARANCE_CODE: case MixBase::MIX_REMOVE_APPEARANCE_CODE: MixManager::ChangeAppearanceCode( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_REPLACE_WITH: case MixBase::MIX_REPLACE_WITH_RANDOM: case MixBase::MIX_CHANGE_CODE_ADD: MixManager::ChangeItemCode( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_ADD_ADDITIONAL_ITEMEFFECT: // Fraun Sky Accessories 7/12/2025 MixManager::ChangeAdditionalItemEffect( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_CHANGE_USABLE_PERIOD: MixManager::ChangeItemUsablePeriod( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_SET_ELEMENTAL_EFFECT: MixManager::SetElementalEffect( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_SET_ELEMENTAL_EFFECT_PARAMETER: MixManager::SetElementalEffectParameter( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_SET_SOCKET: MixManager::SetSocket( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_REPLACE_SOCKET_WITH: MixManager::ReplaceSocketWith( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_SACRIFICE_ITEM_FOR_ETHEREAL_DURABILITY_WITH_MESSAGE: case MixBase::MIX_SACRIFICE_ITEM_FOR_ETHEREAL_DURABILITY: MixManager::SacrificeItemForEtherealDurability( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_TRANSMIT_ETHEREAL_DURABILITY: MixManager::TransmitEtherealDurability( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_RECOVER_EXHAUSTED_ETHEREAL_DURABILITY: MixManager::RecoverExhaustedEtherealDurability( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_SACRIFICE_ITEM_FOR_ETHEREAL_STONE_DURABILITY: MixManager::SacrificeItemForEtherealStoneDurability( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_TRANSMIT_ETHEREAL_DURABILITY_FROM_ETHEREAL_STONE: MixManager::TransmitEtherealDurabilityFromEtherealStone( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_AWAKEN_ITEM: MixManager::InsertAwakenItem( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_DELETE_AWAKEN_OPTION: MixManager::DeleteAwakenOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; case MixBase::MIX_ITEM_IDENTIFY_FOR_RANDOM_OPTION: MixManager::IdentifyItemForRandomOption( pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList ); break; // AziaMafia New mix case MixBase::MIX_RE_AWAKEN_OPTION: MixManager::ReAwakenOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; case MixBase::MIX_DELETE_RANDOM_OPTION: MixManager::DeleteRandomOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; case MixBase::MIX_ITEM_RE_IDENTIFY_FOR_RANDOM_OPTION: MixManager::ReIdentifyItemForRandomOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; case MixBase::MIX_RANDOM_SOCKET_SCRIPT: MixManager::rdmSocketRandomOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; case MixBase::MIX_AWAKEN_ITEM_BY_RANDOM_OPTION: MixManager::InsertAwakenItembyRandomOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; case MixBase::MIX_REAWAKEN_ITEM_BY_RANDOM_OPTION: MixManager::ReAwakenItembyRandomOption(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; case MixBase::MIX_SCRIPT_LUA: MixManager::MixWithLuaScript(pMixBase, pClient, pMainItem, pMsg->count, pSubItem, pCountList); break; default: SendResult( pClient, pMsg->id, RESULT_INVALID_ARGUMENT ); break; } pClient->UpdateTitleConditionByItemCreateByMixing( vCreatedItem ); } void onDecompose( StructPlayer *pClient, TS_CS_DECOMPOSE *pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 메시지 유효성 검사 if( pMsg->count > GameRule::MAX_DECOMPOSE_ITEM_COUNT || pMsg->size != sizeof( TS_CS_DECOMPOSE ) + sizeof( TS_CS_DECOMPOSE::DECOMPOSE_INFO ) * pMsg->count ) { // 유저가 잘못 된 패킷을 보냈을 경우 패킷 조작에 의한 프로그램 해킹 시도라고 간주하고 AutoJail에 가두는 코드(?)/ 확인 요망 if( GameRule::bUseAutoJail ) { pClient->AddState( StructState::NEMESIS_FOR_AUTO, pClient->GetHandle(), 12, GetArTime(), GetArTime() + 864000000 ); pClient->SetAutoUsed(); pClient->Save( true ); } LOG::Log11N4S( LM_AUTO_USER_CHECKED, pClient->GetAccountID(), pClient->GetSID(), pClient->GetLevel(), pClient->GetJobLevel(), pClient->GetJobId(), 0, 0, 0, 0, AUTO_USER_CHECK_TYPE::DECOMPOSE_BUFFER_OVERRUN_ATTEMPTION, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); pClient->pConnection->Close(); return; } if( !pMsg->count ) { SendResult( pClient, TM_CS_DECOMPOSE, RESULT_NOT_EXIST ); return; } std::vector< std::pair< StructItem*, __int64 > > vCreatedItem; // 호칭 처리용 TS_CS_DECOMPOSE::DECOMPOSE_INFO *pDecomposeInfo = reinterpret_cast< TS_CS_DECOMPOSE::DECOMPOSE_INFO* >( pMsg+1 ); //AR_HANDLE *pItemHandle = reinterpret_cast< AR_HANDLE* >( pMsg+1 ); for( unsigned int i = 0; i < pMsg->count; ++i, ++pDecomposeInfo ) { MixManager::FindAndDecompose( pClient, &vCreatedItem, pDecomposeInfo->handle, pDecomposeInfo->count ); } // 분해 유형 중 아이템을 생성했을 때에는 중첩가능의 경우 한번에 Update Query를 여기서 일괄적으로 날려준다. unsigned int resultCount = 0; unsigned int nResult = RESULT_INVALID_ARGUMENT; std::vector< TS_SC_DECOMPOSE_RESULT::DECOMPOSE_INFO > vResultItems( vCreatedItem.size() ); // 결과 패킷 처리용 벡터 for( std::vector< std::pair< StructItem*, __int64 > >::iterator it = vCreatedItem.begin() ; it != vCreatedItem.end() ; ++it, ++resultCount ) { StructItem* pItem = it->first; // UpdateQuery를 여러번 날리지 않기 위해 위에서 분해결과 신규 아이템이 아니면서 중첩 가능한 경우 DB 업데이트를 여기까지 유보한다. if( pItem->IsJoinable() ) pItem->DBQuery( new DB_UpdateItem( pItem ) ); vResultItems[ resultCount ] = TS_SC_DECOMPOSE_RESULT::DECOMPOSE_INFO( it->first->GetHandle(), it->second ); } if( resultCount != 0 ) { SendDecomposeResult( pClient, &vResultItems[0], resultCount ); nResult = RESULT_SUCCESS; } SendResult( pClient, TM_CS_DECOMPOSE, nResult ); return; } void onLearnSkill( StructPlayer *pClient, TS_CS_LEARN_SKILL* pMsg ) { StructCreature::iterator it; StructCreature *pCreature = pClient; // 소환수에게 시도한 경우 if( pClient->GetHandle() != pMsg->target ) { it = StructCreature::get( pMsg->target ); // 소환수가 아니면 리턴 if( !(*it) || !(*it)->IsSummon() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } // 내 소환수가 아니면 리턴 if( static_cast< StructSummon* >( *it )->GetMaster() != pClient ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } pCreature = *it; } int nSkillid = pMsg->skill_id; int nRegisterSkillid = pMsg->skill_id; int nBaseSkillLevel = 0; int nGroupID = 0; // nGroupID가 나오는 경우 // 1. 랜덤형 스킬을 처음 찍을 때 (skill_id = 랜덤원본, original_id = 0) // 2. 랜덤형 스킬을 레벨업 할 때 (skill_id = 랜덤찍힌스킬, original_id = 랜덤원본) if( pMsg->original_skill_id ) nGroupID = GameContent::GetSkillTreeGroupID( pCreature, pMsg->original_skill_id ); else nGroupID = GameContent::GetSkillTreeGroupID( pCreature, pMsg->skill_id ); if( nGroupID ) { // nSkillid가 검사할 스킬의 아이디가 된다. // (랜덤형 스킬의 경우 그 실제 스킬을 검사하면 안되고 랜덤 원본 스킬을 검사해야함) if( pMsg->original_skill_id ) { nSkillid = pMsg->original_skill_id; } if( !( pMsg->skill_level -1 ) ) // 처음 배우는 스킬 { if( !pMsg->original_skill_id ) nRegisterSkillid = GameContent::GetSummonRandomSkillID( nGroupID ); if( !nRegisterSkillid ) { SendResult( pClient, pMsg->id, RESULT_UNKNOWN ); return; } // 같은 그룹 ID를 쓰는 스킬은 하나라도 배워서는 안된다. 단 레벨 0은 허용 for( int i = 0 ; i < GameRule::MAX_RANDOM_SKILL_COUNT ; ++i ) { StructSkill * pSkill = pCreature->GetSkill( GameContent::GetRandomSkillIDFromGroupID( nGroupID, i ) ); if( pSkill ) { if( pSkill->GetBaseSkillLevel() || !pMsg->original_skill_id ) { // 이미 같은 랜덤그룹의 스킬을 배운 상태라 ERROR SendResult( pClient, pMsg->id, RESULT_UNKNOWN ); return; } } } } else { StructSkill *pSkill = pCreature->GetSkill( pMsg->skill_id ); if( NULL == pSkill ) { SendResult( pClient, pMsg->id, RESULT_UNKNOWN ); return; } } } nBaseSkillLevel = pCreature->GetBaseSkillLevel( nRegisterSkillid ); // 클라에서 스킬 습득 메시지가 여러 번 오면 해당 상황에서 배워서는 안되는 // 스킬 레벨까지 올라가 버리는 문제점을 막기 위해 습득 요청 레벨 체크 // UI 마구 클릭한 것에 의한 것일 수 있으므로 출력 메시지는 없음 if( pMsg->skill_level != nBaseSkillLevel + 1 ) return; int nSkillTreeID = 0; // 배울수 없으면 KIN int nErrorCode = pCreature->IsLearnableSkill( nSkillid, nBaseSkillLevel+1, &nSkillTreeID ); if( nErrorCode != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nErrorCode ); return; } // 스킬 등록 { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pCreature ) ); // 등록 SkillID는 위에서 결정된 skill로 넘긴다. pCreature->RegisterSkill( nRegisterSkillid, nBaseSkillLevel+1, 0, nSkillTreeID ); pClient->Save( true ); } SendResult( pClient, pMsg->id, nErrorCode, nRegisterSkillid ); } void onJobLevelUp( StructPlayer *pClient, TS_CS_JOB_LEVEL_UP *pMsg ) { StructCreature::iterator it = StructCreature::get( pMsg->target ); StructCreature *pCreature = *it; if( !pCreature ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->target ); return; } if( pCreature->IsPlayer() && pCreature != pClient ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED, pMsg->target ); return; } if( pCreature->IsSummon() && static_cast< StructSummon* >( pCreature )->GetMaster() != pClient ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED, pMsg->target ); return; } __int64 jp = GameContent::GetNeedJpForJobLevelUp( pCreature->GetJobLevel(), pCreature->GetJobDepth() ); if( pCreature->GetJobPoint() < jp ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_JP, pMsg->target ); return; } if( !jp ) { SendResult( pClient, pMsg->id, RESULT_LIMIT_MAX, pMsg->target ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pCreature ) ); // jp 감소 pCreature->SetJP( pCreature->GetJobPoint() - jp ); // jlv 증가 pCreature->SetJobLevel( pCreature->GetJobLevel() + 1 ); pCreature->CalculateStat(); if( pCreature->IsPlayer() ) { StructPlayer * pPlayer = static_cast< StructPlayer * >( pCreature ); pPlayer->Save(); LOG::Log11N4S( LM_CHARACTER_JOB_LEVEL_UP, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, pPlayer->GetJobId(), pPlayer->GetJobLevel(), jp, pPlayer->GetJobPoint(), 0, 0, 0, 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); // Fraun changed original on_player_level_joblevel_guide to updated with job points and job points spent std::string strJobLevelHook = "on_player_joblevel_up('"; strJobLevelHook += std::to_string(jp); strJobLevelHook += "', '"; strJobLevelHook += std::to_string(pCreature->GetJobPoint()); strJobLevelHook += "', '"; strJobLevelHook += std::to_string( static_cast(pCreature->GetJobLevel()) ); strJobLevelHook += "')"; LUA()->RunString(strJobLevelHook.c_str()); } else if( pCreature->IsSummon() ) { StructSummon * pSummon = static_cast< StructSummon * >( pCreature ); StructPlayer * pPlayer = pSummon->GetMaster(); LOG::Log11N4S( LM_CHARACTER_JOB_LEVEL_UP, pPlayer->GetAccountID(), pPlayer->GetSID(), pSummon->GetSID(), pSummon->GetJobId(), pSummon->GetJobLevel(), jp, pSummon->GetJobPoint(), 0, 0, 0, 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } pClient->Save( true ); SendPropertyMessage( pClient, pCreature->GetHandle(), "job_level", pCreature->GetJobLevel() ); SendResult( pClient, pMsg->id, RESULT_SUCCESS, pMsg->target ); } void onEquipSummon( StructPlayer *pClient, TS_EQUIP_SUMMON* pMsg ) { if( !pClient->IsItemUseable() ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->EquipSummon( pMsg->card_handle ); } void onSummon( StructPlayer *pClient, TS_CS_SUMMON* pMsg ) { } /* struct TS_CS_BIND_SKILLCARD : TS_MESSAGE { TS_CS_BIND_SKILLCARD() _INIT(TM_CS_BIND_SKILLCARD) AR_HANDLE item_handle; unsigned char idx; // 0이면 자기자신, 1+ 이라면 차례대로 소환수 슬롯에 있는 녀석들 }; struct TS_CS_UNBIND_SKILLCARD : TS_MESSAGE { TS_CS_UNBIND_SKILLCARD() _INIT(TM_CS_UNBIND_SKILLCARD) AR_HANDLE item_handle; }; */ void onBindSkillCard( StructPlayer *pClient, TS_CS_BIND_SKILLCARD* pMsg ) { // 아이템 없으면 KIN StructItem *pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem || pItem->GetOwnerHandle() != pClient->GetHandle() || !pItem->IsSkillCard() || !pItem->IsInInventory() ) return; StructCreature *pTarget = NULL; // 이미 바인드 되어 있으면 해제 if( pItem->GetBindedCreatureHandle() ) { StructCreature::iterator it = StructCreature::get( pItem->GetBindedCreatureHandle() ); if( (*it) ) (*it)->UnBindSkillCard( pItem ); } if( IsPlayer( pMsg->target_handle ) ) { if( pMsg->target_handle == pClient->GetHandle() ) pTarget = pClient; } else if( IsSummon( pMsg->target_handle ) ) { StructCreature::iterator it = StructCreature::get( pMsg->target_handle ); pTarget = *it; if( pTarget && static_cast< StructSummon* >( pTarget )->GetMaster() != pClient ) pTarget = NULL; } if( !pTarget ) return; const StructSkill *pSkill = pTarget->GetSkill( pItem->GetSkillId() ); if( !pSkill ) return; // 해당 스킬에 다른 카드가 이미 장착되어있음 if( pSkill->GetEnhance() ) return; pTarget->BindSkillCard( pItem ); } void onUnBindSkillCard( StructPlayer *pClient, TS_CS_UNBIND_SKILLCARD* pMsg ) { // 아이템 없으면 KIN StructItem *pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem || pItem->GetOwnerHandle() != pClient->GetHandle() || !pItem->IsSkillCard() || !pItem->IsInInventory() ) return; StructCreature *pTarget = NULL; if( IsPlayer( pMsg->target_handle ) ) { if( pMsg->target_handle == pClient->GetHandle() ) pTarget = pClient; } else if( IsSummon( pMsg->target_handle ) ) { StructCreature::iterator it = StructCreature::get( pMsg->target_handle ); pTarget = *it; if( pTarget && static_cast< StructSummon* >( pTarget )->GetMaster() != pClient ) pTarget = NULL; } if( pItem->GetBindedCreatureHandle() == 0 ) return; if( pTarget ) { assert( pTarget->GetHandle() == pItem->GetBindedCreatureHandle() ); pTarget->UnBindSkillCard( pItem ); } } void onQuery( StructPlayer *pClient, TS_CS_QUERY *pMsg ) { GameObject::iterator it = GameObject::get( pMsg->handle ); if( *it && (*it)->IsInWorld() ) { if( (*it)->GetLayer() != pClient->GetLayer() ) return; if( !IsVisibleRegion( (*it)->GetRX(), (*it)->GetRY(), pClient->GetRX(), pClient->GetRY() ) ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( (*it) ) ); SendEnterMsg( pClient, *it ); } } void onTargeting( StructPlayer *pClient, TS_CS_TARGETING* pMsg ) { // TODO : 자신의 소환수 클릭시에는 보내줄 필요가 없음. (별도 관리 되어야함) if( pClient->GetTarget() ) { StructCreature::iterator it = StructCreature::get( pClient->GetTarget() ); StructCreature *pObj = *it; if( pObj ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pObj ) ); pObj->RemoveAimer( pClient->GetHandle() ); } pClient->SetTarget( 0 ); } // 타게팅 취소 메세지거나 자기 자신이라면 KIN if( !pMsg->target || pMsg->target == pClient->GetHandle() ) return; StructCreature::iterator it = StructCreature::get( pMsg->target ); StructCreature *pObj = *it; if( !pObj ) return; { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pObj ) ); pObj->AddAimer( pClient->GetHandle() ); pClient->SetTarget( pObj->GetHandle() ); } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( pClient, pObj ) ); if( !isValidTarget( pClient, pObj ) ) { SendLeaveMsg( pClient, pObj ); return; } } void onTrade( StructPlayer *pClient, TS_TRADE *pMsg ); // OnTrade.cpp 에 있음. void onStorage( StructPlayer *pClient, TS_CS_STORAGE *pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); if( pMsg->mode == TS_CS_STORAGE::CLOSE && (pClient->IsUsingStorage() || pClient->IsLoadingStorage()) ) { pClient->CloseStorage(); return; } if( !pClient->IsUsingStorage() || !pClient->IsActable() || pClient->IsTrading() || pClient->IsUsingSkill() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE, pMsg->item_handle ); return; } // 아템 이동일경우 if( pMsg->mode == TS_CS_STORAGE::ITEM_INVENTORY_TO_STORAGE || pMsg->mode == TS_CS_STORAGE::ITEM_STORAGE_TO_INVENTORY ) { if( pMsg->count <= 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY, pMsg->item_handle ); return; } // 아이템 없으면 KIN StructItem *pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem || pItem->GetOwnerHandle() != pClient->GetHandle() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST, pMsg->item_handle ); return; } if( pItem->GetOwnerHandle() != pClient->GetHandle() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED, pMsg->item_handle ); return; } if( pItem->IsInInventory() && pMsg->mode == TS_CS_STORAGE::ITEM_INVENTORY_TO_STORAGE ) { if( pClient->GetStorageItemCount() >= static_cast< size_t >( GameRule::nMaxStorageItemCount ) && ( !pItem->IsJoinable() || !pClient->FindStorageItem( pItem->GetItemCode() ) || !pClient->FindStorageItem( pItem->GetItemCode() )->IsJoinable() ) ) { SendResult( pClient, pMsg->id, RESULT_TOO_HEAVY, pMsg->item_handle ); return; } pClient->MoveInventoryToStorage( pItem, pMsg->count ); } if( pItem->IsInStorage() && pMsg->mode == TS_CS_STORAGE::ITEM_STORAGE_TO_INVENTORY ) { pClient->MoveStorageToInventory( pItem, pMsg->count ); } pClient->Save( true ); return; } // 돈일경우 else if( pMsg->mode == TS_CS_STORAGE::GOLD_INVENTORY_TO_STORAGE ) { StructGold nGold = pClient->GetGold(); StructGold nStorageGold = pClient->GetStorageGold(); if( pMsg->count <= 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY, pMsg->item_handle ); return; } if( nGold < pMsg->count ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY, pMsg->item_handle ); return; } if( nStorageGold + pMsg->count > GameRule::MAX_GOLD_FOR_STORAGE ) { // TM_CS_STORAGE에 대해서는 TS_SC_RESULT 메시지 클라에서 사용 안 함 // 그래서 서버에서 그냥 오류 메시지 출력 ㄱㄱ std::string strGold; XStringUtil::itosc64( strGold, pMsg->count ); PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@575\v#@gold@#\v%s", strGold.c_str() ); SendResult( pClient, pMsg->id, RESULT_TOO_MUCH_MONEY, pMsg->item_handle ); return; } if( pClient->ChangeGold( nGold - pMsg->count ) != RESULT_SUCCESS || pClient->ChangeStorageGold( nStorageGold + pMsg->count ) != RESULT_SUCCESS ) { // 돈 빼거나 넣는 중에 오류 발생하면 원래대로 되돌리고 오류 메시지 송신 // 오류 발생시에는 변경되었을 가능성이 있는 데이터는 유저 소지금 뿐임 if( pClient->ChangeGold( nGold ) != RESULT_SUCCESS ) { // 원래대로 되돌리는 것도 실패하면 어쩌자는 거냐 -_ - _cprint( "ChangeGold/ChangeStorageGold Failed: Case[0], Player[%s], Info[Inven(%I64d), Storage(%I64d), Move(%I64d)]\n", pClient->GetName(), nGold.GetRawData(), nStorageGold.GetRawData(), pMsg->count ); FILELOG( "ChangeGold/ChangeStorageGold Failed: Case[0], Player[%s], Info[Inven(%I64d), Storage(%I64d), Move(%I64d)]", pClient->GetName(), nGold.GetRawData(), nStorageGold.GetRawData(), pMsg->count ); } SendResult( pClient, pMsg->id, RESULT_TOO_MUCH_MONEY, pMsg->item_handle ); return; } pClient->Save( true ); LOG::Log11N4S( LM_BANK, pClient->GetAccountID(), pClient->GetSID(), 0, 0, 0, 0, 0 - pMsg->count, pClient->GetGold().GetRawData(), 0, pClient->GetStorageGold().GetRawData(), 0, pClient->GetAccountName(),LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "GOLD_INVENTORY_TO_STORAGE", LOG::STR_NTS ); return; } else if( pMsg->mode == TS_CS_STORAGE::GOLD_STORAGE_TO_INVENTORY ) { StructGold nGold = pClient->GetGold(); StructGold nStorageGold = pClient->GetStorageGold(); if( pMsg->count <= 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY, pMsg->item_handle ); return; } if( nStorageGold < pMsg->count ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY, pMsg->item_handle ); return; } if( nGold + pMsg->count > GameRule::MAX_GOLD_FOR_INVENTORY ) { // TM_CS_STORAGE에 대해서는 TS_SC_RESULT 메시지 클라에서 사용 안 함 // 그래서 서버에서 그냥 오류 메시지 출력 ㄱㄱ std::string strGold; XStringUtil::itosc64( strGold, pMsg->count ); PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pClient, "@576\v#@gold@#\v%s", strGold.c_str() ); SendResult( pClient, pMsg->id, RESULT_TOO_MUCH_MONEY, pMsg->item_handle ); return; } if( pClient->ChangeStorageGold( nStorageGold - pMsg->count ) != RESULT_SUCCESS || pClient->ChangeGold( nGold + pMsg->count ) != RESULT_SUCCESS ) { // 돈 빼거나 넣는 중에 오류 발생하면 원래대로 되돌리고 오류 메시지 송신 // 오류 발생시에는 변경되었을 가능성이 있는 데이터는 창고 보관액 뿐임 if( pClient->ChangeStorageGold( nStorageGold ) != RESULT_SUCCESS ) { // 원래대로 되돌리는 것도 실패하면 어쩌자는 거냐 -_ - _cprint( "ChangeGold/ChangeStorageGold Failed: Case[1], Player[%s], Info[Inven(%I64d), Storage(%I64d), Move(%I64d)]\n", pClient->GetName(), nGold.GetRawData(), nStorageGold.GetRawData(), pMsg->count ); FILELOG( "ChangeGold/ChangeStorageGold Failed: Case[1], Player[%s], Info[Inven(%I64d), Storage(%I64d), Move(%I64d)]", pClient->GetName(), nGold.GetRawData(), nStorageGold.GetRawData(), pMsg->count ); } SendResult( pClient, pMsg->id, RESULT_TOO_MUCH_MONEY, pMsg->item_handle ); return; } pClient->Save( true ); LOG::Log11N4S( LM_BANK, pClient->GetAccountID(), pClient->GetSID(), 0, 0, 0, 0, pMsg->count, pClient->GetGold().GetRawData(), 0, pClient->GetStorageGold().GetRawData(), 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "GOLD_STORAGE_TO_INVENTORY", LOG::STR_NTS ); // TODO : 플레이어 돈 즉시 업데 해야함 (시간차를 두면 섭따로 인한 창고 돈복사 일어남) return; } } void onReturnLobby( IStreamSocketConnection * pConn, StructPlayer *pClient ) { // 즉시 로그아웃 불가능할 경우 로그아웃 불가 if( !pClient->CanLogoutNow() ) return; { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG * pTag = reinterpret_cast< _CONNECTION_TAG * > ( pConn->GetTag() ); if( pTag && pTag->pPlayer ) { pTag->nConnId = pTag->pPlayer->SetReturnLobbyConnection( static_cast< XIOCPConnection * >( pConn ) ); pTag->nContinuousPlayTime = pTag->pPlayer->GetContinuousPlayTime(); pTag->nContinuousLogoutTime = pTag->pPlayer->GetContinuousLogoutTime(); pTag->pPlayer = NULL; } } if( pClient->GetBattleArenaID() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 특정 경기에 참여 상태일 경우에는 경기 이탈 if( pClient->GetBattleArenaID() ) BattleArenaManager::Instance().QuitGame( pClient, false, false, ALT_USER_REQUEST ); assert( !pClient->GetBattleArenaID() ); } pClient->LogoutNow( 2 ); } void checkBoothStartable( StructPlayer * pClient, unsigned short & nResult, int & nErrorValue ) { if( pClient->GetLevel() < GameRule::nMinBoothStartableLevel ) { nResult = RESULT_NOT_ENOUGH_LEVEL; nErrorValue = GameRule::nMinBoothStartableLevel; } else if( ( GameRule::bLimitBoothOpenableLayerToZero && pClient->GetLayer() > 0 ) || GameRule::bDisableBooth ) { nResult = RESULT_NOT_ACTABLE_HERE; nErrorValue = 0; } else { nResult = pClient->IsBoothOpenable(); nErrorValue = 0; } } void onCheckBoothStartable( StructPlayer *pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); unsigned short nResult = RESULT_SUCCESS; int nErrorValue = 0; checkBoothStartable( pClient, nResult, nErrorValue ); SendResult( pClient, TM_CS_CHECK_BOOTH_STARTABLE, nResult, nErrorValue ); } void onStartBooth( StructPlayer *pClient, TS_CS_START_BOOTH* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); unsigned short nResult = RESULT_SUCCESS; int nErrorValue = 0; checkBoothStartable( pClient, nResult, nErrorValue ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, TM_CS_START_BOOTH, nResult, nErrorValue ); return; } pMsg->name[ _countof( pMsg->name ) - 1 ] = '\0'; int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( strlen( pMsg->name ) < 1 || ( GameRule::bRestrictBanWordForBooth && GameContent::IsBannedWord( code_page, pMsg->name ) ) ) { SendResult( pClient, TM_CS_START_BOOTH, RESULT_INVALID_TEXT ); return; } std::vector< StructPlayer::BOOTH_OPEN_ITEM_INFO > vItemList; bool bIsBuyBooth = (pMsg->type == 2); if( bIsBuyBooth && GameRule::bDisableBuyBooth ) { SendResult( pClient, TM_CS_START_BOOTH, RESULT_NOT_ACTABLE ); return; } if( pMsg->cnt > GameRule::MAX_BOOTH_ITEM_COUNT ) { SendResult( pClient, TM_CS_START_BOOTH, RESULT_LIMIT_MAX ); return; } TS_CS_START_BOOTH::TS_BOOTH_OPEN_ITEM_INFO* pItemInfo = reinterpret_cast< TS_CS_START_BOOTH::TS_BOOTH_OPEN_ITEM_INFO* > ( pMsg + 1 ); for( int i = 0; i < pMsg->cnt; ++i ) { StructPlayer::BOOTH_OPEN_ITEM_INFO ItemInfo; ItemInfo.handle = pItemInfo->item_handle; ItemInfo.cnt = pItemInfo->cnt; ItemInfo.gold = pItemInfo->gold; vItemList.push_back( ItemInfo ); ++pItemInfo; } bool result = false; { THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); result = pClient->OpenBooth( pMsg->name, vItemList, bIsBuyBooth ); } if( !result ) { SendResult( pClient, TM_CS_START_BOOTH, RESULT_NOT_ACTABLE ); return; } if( !pClient->IsSitDown() ) pClient->SitDown(); BroadcastStatusMessage( pClient ); SendResult( pClient, TM_CS_START_BOOTH, RESULT_SUCCESS ); // Credits to AziaMafia: On booth open if (pClient->IsBoothOpen()) { std::stringstream oss; oss << "{"; for (size_t i = 0; i < vItemList.size(); ++i) { if (i > 0) oss << ","; oss << "{ handle=" << vItemList[i].handle << ", cnt=" << vItemList[i].cnt << ", gold=" << vItemList[i].gold << " }"; } oss << "}"; std::string luaItemTable = oss.str(); std::string strBoothOpen; XStringUtil::Format(strBoothOpen, "MarketSystem:OnBoothOpen( %d, %s)", (int)pMsg->type, luaItemTable.c_str()); LUA()->RunString(strBoothOpen.c_str()); } BroadcastStatusMessage( pClient ); } void onStopBooth( StructPlayer *pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); bool result = false; THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); if( !pClient->IsBoothOpen() ) return; result = pClient->CloseBooth(); if( !result ) { SendResult( pClient, TM_CS_STOP_BOOTH, RESULT_NOT_ACTABLE ); return; } LUA()->RunString( "MarketSystem:OnBoothClose() "); // 일어서자 pClient->StandUp(); BroadcastStatusMessage( pClient ); SendResult( pClient, TM_CS_STOP_BOOTH, RESULT_SUCCESS ); BroadcastStatusMessage( pClient ); } void onWatchBooth( StructPlayer *pClient, TS_CS_WATCH_BOOTH* pMsg ) { THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); StructCreature::iterator it = StructCreature::get( pMsg->target ); StructPlayer * pTarget = static_cast< StructPlayer * >( *it ); if( !pTarget ) return; if( !( pTarget->IsPlayer() ) ) return; if( pClient->GetBoothStatus() != StructPlayer::IS_NOT_BOOTH ) { return; } pClient->WatchBooth( pTarget ); SendBoothInfo( pClient, pTarget ); } void onStopWatchBooth( StructPlayer * pClient, TS_CS_STOP_WATCH_BOOTH* pMsg ) { THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); pClient->StopWatchBooth(); } void onBuyFromBooth( StructPlayer * pClient, TS_CS_BUY_FROM_BOOTH* pMsg ) { StructCreature::iterator itPlayer = StructCreature::get( pMsg->target ); StructPlayer * pTarget = static_cast< StructPlayer * >( *itPlayer ); if( !pTarget ) return; if( !( pTarget->IsPlayer() ) ) return; // 락하고~ ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( pClient, pTarget ) ); THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); // 판매 부스 아니면 KIN if( pTarget->GetBoothStatus() != StructPlayer::SELL_BOOTH ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ACTABLE ); return; } // 카운트 검사 if( pMsg->cnt > GameRule::MAX_BOOTH_ITEM_COUNT ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ACTABLE ); return; } const std::vector< StructPlayer::BOOTH_ITEM_INFO > & rvItemList = pTarget->GetBoothItemList(); std::vector< StructPlayer::BOOTH_ITEM_INFO >::const_iterator it; std::vector< StructPlayer::BOOTH_ITEM_BUY_INFO > vBuyInfo; TS_ITEM_BASE_INFO * pItemInfo = reinterpret_cast< TS_ITEM_BASE_INFO * >( pMsg + 1 ); StructPlayer::BOOTH_ITEM_BUY_INFO ItemBuyInfo; // 결과 송신 메시지 버퍼 미리 준비: 루프 돌면서 가격 정보 채워야 함. char buf[ sizeof( TS_SC_BOOTH_TRADE_INFO ) + GameRule::MAX_BOOTH_ITEM_COUNT * sizeof( TS_BOOTH_TRADE_ITEM_INFO ) ]; TS_SC_BOOTH_TRADE_INFO * pResultMsg = reinterpret_cast< TS_SC_BOOTH_TRADE_INFO * >( buf ); pResultMsg->id = TM_SC_BOOTH_TRADE_INFO; pResultMsg->size = sizeof( TS_SC_BOOTH_TRADE_INFO ) + pMsg->cnt * sizeof( TS_BOOTH_TRADE_ITEM_INFO ); pResultMsg->cnt = pMsg->cnt; TS_BOOTH_TRADE_ITEM_INFO *pTradeResultInfo = reinterpret_cast< TS_BOOTH_TRADE_ITEM_INFO * >( pResultMsg + 1 ); int remain_buf_len = sizeof( buf ) - sizeof( TS_SC_BOOTH_TRADE_INFO ); StructGold nTotalCost( 0 ); int nTotalWeight = 0; // { 여기서부터, 아이템 존내 검증. 판매자나 구매자가 사기쳤는지 확인 for( int i = 0; i < pMsg->cnt; ++i ) { int nItemIdx = -1; for( it = rvItemList.begin(); it != rvItemList.end(); ++it ) { if( (*it).pItem->GetHandle() == pItemInfo->handle ) break; } // 아이템이 없음 if( it == rvItemList.end() ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_EXIST ); return; } // 구매 수량 변수 오버플로우 체크(실제 개수는 int 타입 한계치여야 하는데 TS_ITEM_BASE_INFO::count가 __int64여서 받을 수 있음) if( pItemInfo->count > 0x7FFFFFFF || pItemInfo->count <= 0 ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_EXIST ); return; } // 틀린 아이템 if( pItemInfo->Code != (*it).pItem->GetItemCode() || pItemInfo->count > (*it).cnt || pItemInfo->count > (*it).pItem->GetCount() || pItemInfo->endurance != (*it).pItem->GetCurrentEndurance() || pItemInfo->enhance != (*it).pItem->GetItemEnhance() || pItemInfo->Flag != *((*it).pItem->GetInstanceFlag().GetRawData()) || pItemInfo->level != (*it).pItem->GetItemLevel() || pItemInfo->elemental_effect_type != (*it).pItem->GetElementalEffectType() || pItemInfo->elemental_effect_attack_point != (*it).pItem->GetElementalEffectAttackPoint() || pItemInfo->elemental_effect_magic_point != (*it).pItem->GetElementalEffectMagicPoint() || pItemInfo->appearance_code != (*it).pItem->GetAppearanceCode() || pItemInfo->summon_code != (*it).pItem->GetSummonCode() ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_EXIST ); return; } // 소켓이 다름 // 소환수 카드의 경우 소켓 정보에 크리처 진화 렙 정보를 포함시켜줘야 함 int ItemSocket[ ItemBase::MAX_SOCKET_NUMBER ]; StructSummon *pSummon = (*it).pItem->GetSummonStruct(); for( unsigned i = 0; i < ItemBase::MAX_SOCKET_NUMBER; ++i ) ItemSocket[i] = (*it).pItem->GetSocketCode( i ); if( (*it).pItem->IsSummonCard() && pSummon ) { int depth = pSummon->GetTransformLevel(); int i; for( i = 0; i < depth - 1; ++i ) { ItemSocket[i + 1] = pSummon->GetPrevJobLevel( i ); } ItemSocket[i + 1] = pSummon->GetLevel(); } // 소켓에 필요한 정보는 ItemSocket 배열에 모드 들어갔음. for( unsigned i = 0; i < ItemBase::MAX_SOCKET_NUMBER; ++i ) { //if( pItemInfo->socket[i] != (*it).pItem->GetSocketCode( i ) ) if( pItemInfo->socket[i] != ItemSocket[i] ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_EXIST ); return; } } // 거래 불가능 상태 if( !pTarget->IsTradable( (*it).pItem ) ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ACTABLE ); return; } nItemIdx = it - rvItemList.begin(); ItemBuyInfo.nIdx = nItemIdx; ItemBuyInfo.nCount = pItemInfo->count; nTotalCost += StructGold( (*it).gold * pItemInfo->count ); nTotalWeight += (*it).pItem->GetItemBase().fWeight * pItemInfo->count; s_memcpy( pTradeResultInfo, remain_buf_len, pItemInfo, sizeof( TS_ITEM_BASE_INFO ) ); remain_buf_len -= sizeof( TS_ITEM_BASE_INFO ); pTradeResultInfo->price = (*it).gold * pItemInfo->count; if( (*it).gold * pItemInfo->count < 0 ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "BugReport", "Buy Booth Bug [%s:%s]", pTarget->GetAccountName(), pTarget->GetName() ); GameRule::RegisterBlockAccount( pTarget->GetAccountName() ); SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ENOUGH_MONEY ); if( pClient->pConnection && pClient->pConnection->IsConnected() ) pClient->pConnection->Close(); return; } if( pClient->GetGold() < StructGold( (*it).gold * pItemInfo->count ) ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ENOUGH_MONEY ); return; } vBuyInfo.push_back( ItemBuyInfo ); ++pItemInfo; ++pTradeResultInfo; } // } if( nTotalCost < StructGold( 0 ) ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "BugReport", "Buy Booth Bug [%s:%s]", pTarget->GetAccountName(), pTarget->GetName() ); GameRule::RegisterBlockAccount( pTarget->GetAccountName() ); SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ENOUGH_MONEY ); if( pClient->pConnection && pClient->pConnection->IsConnected() ) pClient->pConnection->Close(); return; } if( nTotalCost > pClient->GetGold() ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_ENOUGH_MONEY ); return; } // 판매자 루피 소지 한도 체크 if( pTarget->GetGold() + nTotalCost > GameRule::MAX_GOLD_FOR_INVENTORY ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_TOO_MUCH_MONEY, pTarget->GetHandle() ); PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pTarget, "@565" ); return; } if( nTotalWeight + pClient->GetWeight() > pClient->GetMaxWeight() * 0.9f ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_TOO_HEAVY ); return; } if( !pTarget->BuyFromBooth( pClient, vBuyInfo ) ) { SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_NOT_EXIST ); return; } SendResult( pClient, TM_CS_BUY_FROM_BOOTH, RESULT_SUCCESS ); // 거래 결과 메시지 송신 pResultMsg->target = pClient->GetHandle(); pResultMsg->is_sell = true; PendStream( pTarget, pResultMsg, pResultMsg->size ); pResultMsg->target = pTarget->GetHandle(); pResultMsg->is_sell = false; PendStream( pClient, pResultMsg, pResultMsg->size ); // AziaMafia Save Shop pClient->Save(); pTarget->Save(); } void onSellToBooth( StructPlayer * pClient, TS_CS_SELL_TO_BOOTH* pMsg ) { StructCreature::iterator itPlayer = StructCreature::get( pMsg->target ); StructPlayer * pTarget = static_cast< StructPlayer * >( *itPlayer ); if( !pTarget ) return; if( !( pTarget->IsPlayer() ) ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( pClient, pTarget ) ); THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); if( pTarget->GetBoothStatus() != StructPlayer::BUY_BOOTH ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_ACTABLE ); return; } StructItem * pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_ACTABLE ); return; } if( pItem->GetOwnerHandle() != pClient->GetHandle() || !pItem->IsInInventory() ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_EXIST ); return; } if( !pClient->IsTradable( pItem ) ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_ACTABLE ); return; } if( pMsg->cnt <= 0 ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_EXIST ); return; } const std::vector< StructPlayer::BOOTH_ITEM_INFO > & rvItemList = pTarget->GetBoothItemList(); std::vector< StructPlayer::BOOTH_ITEM_INFO >::const_iterator it; int nItemIdx = -1; StructGold nTotalCost( 0 ); int nWeight = 0; AR_HANDLE hBuyHandle = 0; // { 아이템 찾기 for( it = rvItemList.begin(); it != rvItemList.end(); ++it ) { if( pItem->GetItemCode() != (*it).pItem->GetItemCode() || pMsg->cnt > (*it).cnt || pItem->GetCount() < pMsg->cnt || pItem->GetCurrentEndurance() != (*it).pItem->GetCurrentEndurance() || pItem->GetItemEnhance() != (*it).pItem->GetItemEnhance() || *(pItem->GetInstanceFlag().GetRawData()) != *((*it).pItem->GetInstanceFlag().GetRawData()) || pItem->GetItemLevel() != (*it).pItem->GetItemLevel() || pItem->GetSummonCode() != (*it).pItem->GetSummonCode() ) { continue; } bool bSocketVerify = true; for( unsigned i = 0; i < ItemBase::MAX_SOCKET_NUMBER; ++i ) { if( pItem->GetSocketCode( i ) != (*it).pItem->GetSocketCode( i ) ) { bSocketVerify = false; break; } } if( !bSocketVerify ) continue; nTotalCost += (*it).gold * pMsg->cnt; nWeight = (*it).pItem->GetItemBase().fWeight * pMsg->cnt; nItemIdx = it - rvItemList.begin(); hBuyHandle = (*it).pItem->GetHandle(); break; } if( nItemIdx == -1 ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_EXIST ); return; } if( pTarget->GetWeight() + nWeight > pTarget->GetMaxWeight() * 0.9f ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_TOO_HEAVY ); return; } if( nTotalCost < StructGold( 0 ) ) { FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "BugReport", "Sell Booth Bug [%s:%s]", pTarget->GetAccountName(), pTarget->GetName() ); GameRule::RegisterBlockAccount( pTarget->GetAccountName() ); SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_ENOUGH_MONEY ); if( pClient->pConnection && pClient->pConnection->IsConnected() ) pClient->pConnection->Close(); return; } if( pTarget->GetGold() < nTotalCost ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_ENOUGH_MONEY ); return; } // 판매자 루피 소지 한도 체크 if( pClient->GetGold() + nTotalCost > GameRule::MAX_GOLD_FOR_INVENTORY ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_TOO_MUCH_MONEY, pTarget->GetHandle() ); PrintfChatMessage( false, CHAT_NOTICE, "@SYSTEM", pTarget, "@573\v#@target_name@#\v%s", pClient->GetName() ); return; } TS_ITEM_BASE_INFO ItemInfo; fillItemBaseInfo( &ItemInfo, pItem ); ItemInfo.count = pMsg->cnt; ItemInfo.handle = hBuyHandle; if( !pTarget->SellToBooth( pClient, pMsg->item_handle, nItemIdx, pMsg->cnt ) ) { SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_NOT_EXIST ); return; } SendResult( pClient, TM_CS_SELL_TO_BOOTH, RESULT_SUCCESS ); char buf[ sizeof( TS_SC_BOOTH_TRADE_INFO ) + sizeof( TS_BOOTH_TRADE_ITEM_INFO ) ]; TS_SC_BOOTH_TRADE_INFO * pResultMsg = reinterpret_cast< TS_SC_BOOTH_TRADE_INFO * >( buf ); pResultMsg->id = TM_SC_BOOTH_TRADE_INFO; pResultMsg->size = sizeof( TS_SC_BOOTH_TRADE_INFO ) + sizeof( TS_BOOTH_TRADE_ITEM_INFO ); pResultMsg->cnt = 1; s_memcpy( pResultMsg + 1, sizeof( buf ) - sizeof( TS_SC_BOOTH_TRADE_INFO ), &ItemInfo, sizeof( TS_ITEM_BASE_INFO ) ); reinterpret_cast< TS_BOOTH_TRADE_ITEM_INFO * >( pResultMsg + 1 )->price = nTotalCost.GetRawData(); pResultMsg->target = pClient->GetHandle(); pResultMsg->is_sell = false; PendStream( pTarget, pResultMsg, pResultMsg->size ); pResultMsg->target = pTarget->GetHandle(); pResultMsg->is_sell = true; PendStream( pClient, pResultMsg, pResultMsg->size ); // AziaMafia Save Shop pClient->Save(); pTarget->Save(); } void onSoulStoneCraft( StructPlayer* pClient, TS_CS_SOULSTONE_CRAFT* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( !pClient->GetLastContactLong( "SoulStoneCraft" ) ) return; StructItem *pCraftItem = StructItem::FindItem( pMsg->craft_item_handle ); StructItem *pSoulStoneList[ TS_CS_SOULSTONE_CRAFT::MAX_SOULSTONE_NUM ]; memset( pSoulStoneList, 0, sizeof( pSoulStoneList ) ); // 아이템 존재/소지 여부 체크 if( !pCraftItem || pCraftItem->GetOwnerHandle() != pClient->GetHandle() || !pCraftItem->IsInInventory() ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } // 강화 실패작은 세공 불가능 if( pCraftItem->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_FAILED ) ) { return; } // 소울스톤 세공 가능한 아이템인지 체크, 기획상으로 벨트는 세공이 불가능 int nSocketCount = pCraftItem->GetMaxSocketCount(); if( pCraftItem->IsBelt() || !nSocketCount || nSocketCount > TS_CS_SOULSTONE_CRAFT::MAX_SOULSTONE_NUM ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } LOG::Log11N4S( LM_ITEM_SOCKET_INFO, pClient->GetAccountID(), pClient->GetSID(), pCraftItem->GetCurrentEndurance(), pCraftItem->GetMaxEndurance(), pCraftItem->GetSocketCode( 0 ), pCraftItem->GetSocketCode( 1 ), pCraftItem->GetSocketCode( 2 ), pCraftItem->GetSocketCode( 3 ), pCraftItem->GetSocketCode( 4 ), pCraftItem->GetSocketCode( 5 ), pCraftItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); // 최대 중복 가능 소울스톤 종류 수 한정(소켓이 3개 이상인 건 2개까지 가능) int nMaxReplicatableCount = ( pCraftItem->GetMaxSocketCount() >= 3 ) ? 2 : 1; int nReplicatedCount = 0; StructGold nPrevGold = pClient->GetGold(); // 세공에 소요되는 루피 StructGold nCraftCost( 0 ); // 1개라도 새로 세공되는 소울스톤이 있는지(없으면 오류) bool bValidCraft = false; bool bNeedUpdate = false; int nErrorCode = RESULT_SUCCESS; for( int i = 0 ; i < nSocketCount ; ++i ) { if( !pMsg->soulstone_handle[i] ) { pSoulStoneList[i] = NULL; continue; } pSoulStoneList[i] = StructItem::FindItem( pMsg->soulstone_handle[i] ); // 아이템 존재 여부와 소유 여부 체크 if( !pSoulStoneList[i] || pSoulStoneList[i]->GetOwnerHandle() != pClient->GetHandle() || !pSoulStoneList[i]->IsInInventory() ) { nErrorCode = RESULT_ACCESS_DENIED; break; } // 소울스톤 분류인지 체크 추가 필요 if( pSoulStoneList[i]->GetItemBase().nType != ItemBase::TYPE_SOULSTONE || pSoulStoneList[i]->GetItemBase().nGroup != ItemBase::GROUP_SOULSTONE || pSoulStoneList[i]->GetItemBase().nClass != ItemBase::CLASS_SOULSTONE ) { nErrorCode = RESULT_NOT_ACTABLE; break; } static const size_t nTypeSize = sizeof( short ) * ItemBase::MAX_OPTION_NUMBER; static const size_t nBaseVarSize = sizeof( double ) * ItemBase::MAX_OPTION_NUMBER; static const size_t nOptVarSize = sizeof( double ) * ItemBase::MAX_OPTION_NUMBER; // 같은 효과의 소울스톤이 제한 개수보다 많이 세공되어 있으면 사용 불가 nReplicatedCount = 0; for( int k = 0 ; k < pCraftItem->GetMaxSocketCount() ; ++k ) { ItemBase::ItemCode nSoulStoneCode = pCraftItem->GetSocketCode( k ); if( !nSoulStoneCode || k == i ) continue; if( !memcmp( StructItem::GetItemBase( nSoulStoneCode ).nBaseType, pSoulStoneList[i]->GetItemBase().nBaseType, nTypeSize ) && !memcmp( StructItem::GetItemBase( nSoulStoneCode ).fBaseVar1, pSoulStoneList[i]->GetItemBase().fBaseVar1, nBaseVarSize ) && !memcmp( StructItem::GetItemBase( nSoulStoneCode ).nOptType, pSoulStoneList[i]->GetItemBase().nOptType, nTypeSize ) && !memcmp( StructItem::GetItemBase( nSoulStoneCode ).fOptVar1, pSoulStoneList[i]->GetItemBase().fOptVar1, nOptVarSize ) ) { ++nReplicatedCount; if( nReplicatedCount >= nMaxReplicatableCount ) { nErrorCode = RESULT_ALREADY_EXIST; break; } } } if( nErrorCode != RESULT_SUCCESS ) break; // 같은 효과의 소울스톤이 함께 세공되는 경우 불가 for( int k = 0 ; k < i ; ++k ) { if( !pSoulStoneList[k] ) continue; if( !memcmp( pSoulStoneList[k]->GetItemBase().nBaseType, pSoulStoneList[i]->GetItemBase().nBaseType, nTypeSize ) && !memcmp( pSoulStoneList[k]->GetItemBase().fBaseVar1, pSoulStoneList[i]->GetItemBase().fBaseVar1, nBaseVarSize ) && !memcmp( pSoulStoneList[k]->GetItemBase().nOptType, pSoulStoneList[i]->GetItemBase().nOptType, nTypeSize ) && !memcmp( pSoulStoneList[k]->GetItemBase().fOptVar1, pSoulStoneList[i]->GetItemBase().fOptVar1, nOptVarSize ) ) { ++nReplicatedCount; if( nReplicatedCount > nMaxReplicatableCount ) { nErrorCode = RESULT_ALREADY_EXIST; break; } } } if( nErrorCode != RESULT_SUCCESS ) break; // 세공 비용 처리 nCraftCost = pSoulStoneList[i]->GetItemBase().nPrice / StructGold( 10 ); // 기존 세공 자리에 세공하는데 필요 비용 if( pClient->ChangeGold( pClient->GetGold() - nCraftCost ) != RESULT_SUCCESS ) { nErrorCode = RESULT_NOT_ENOUGH_MONEY; break; } // 소켓에 세공 처리하고 소울스톤 삭제 bNeedUpdate = true; pCraftItem->SetSocketCode( i, pSoulStoneList[i]->GetItemCode() ); pClient->EraseItem( pSoulStoneList[i], 1 ); pCraftItem->SetCurrentEndurance( pCraftItem->GetCurrentEndurance() + pSoulStoneList[i]->GetCurrentEndurance() ); LOG::Log11N4S( LM_ITEM_SOULSTONE_CRAFT, pClient->GetAccountID(), pClient->GetSID(), pCraftItem->GetItemEnhance() * 100 + pCraftItem->GetItemLevel(), pCraftItem->GetItemCode(), ( pSoulStoneList[0] ) ? pSoulStoneList[0]->GetItemCode() : 0, ( pSoulStoneList[1] ) ? pSoulStoneList[1]->GetItemCode() : 0, ( pSoulStoneList[2] ) ? pSoulStoneList[2]->GetItemCode() : 0, ( pSoulStoneList[3] ) ? pSoulStoneList[3]->GetItemCode() : 0, nPrevGold.GetRawData(), pClient->GetGold().GetRawData() - nCraftCost.GetRawData(), pCraftItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "SUCS", 0 ); } if( bNeedUpdate ) { pCraftItem->DBQuery( new DB_UpdateItem( pCraftItem ) ); LOG::Log11N4S( LM_ITEM_SOCKET_INFO, pClient->GetAccountID(), pClient->GetSID(), pCraftItem->GetCurrentEndurance(), pCraftItem->GetMaxEndurance(), pCraftItem->GetSocketCode( 0 ), pCraftItem->GetSocketCode( 1 ), pCraftItem->GetSocketCode( 2 ), pCraftItem->GetSocketCode( 3 ), pCraftItem->GetSocketCode( 4 ), pCraftItem->GetSocketCode( 5 ), pCraftItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); pClient->SetLastContact( "SoulStoneCraft", 0 ); SendItemMessage( pClient, pCraftItem ); pClient->CalculateStat(); } SendResult( pClient, pMsg->id, nErrorCode ); } void onRepairSoulStone( StructPlayer* pClient, TS_CS_REPAIR_SOULSTONE* pMsg ) { // 시간제 아이템이 중간에 사라지는 것을 방지해야 함 ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( !pClient->GetLastContactLong( "RepairSoulStone" ) ) return; pClient->SetLastContact( "RepairSoulStone", 0 ); typedef std::pair< StructItem *, int > REPAIR_INFO; std::vector< REPAIR_INFO > vItemList; int nTotalRepair = 0; for( int i = 0 ; i < TS_CS_REPAIR_SOULSTONE::MAX_REPAIR_COUNT ; ++i ) { if( !pMsg->item_handle[i] ) continue; StructItem *pItem = StructItem::FindItem( pMsg->item_handle[i] ); // 아이템 존재/소유 여부 체크 if( !pItem || pItem->GetOwnerHandle() != pClient->GetHandle() || !pItem->IsInInventory() ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } // 강화 실패작은 세공 수리 불가 if( pItem->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_FAILED ) ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } // 소울스톤 세공 여부 체크 if( pItem->GetUsingSocketCount() == 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } int nDamagedEndurance = ( pItem->GetMaxEndurance() / 100000 ) - ( pItem->GetCurrentEndurance() + 99999 ) / 100000; // 내구도 소모 여부 체크 if( nDamagedEndurance == 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } // 수리 수치 총합 계산(소모 라크 계산 용) nTotalRepair += nDamagedEndurance; vItemList.push_back( REPAIR_INFO( pItem, nDamagedEndurance ) ); } // 총 소모 라크는 수리 대상 아이템을 모두 합친 거 기준으로 올림 처리(각각 올림 처리 X) int nCostChaos = ( nTotalRepair * GameRule::COST_CHAOS_PER_PEPAIR_POINT ) + 0.9; if( vItemList.empty() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } // 라크가 모자라~ if( pClient->GetChaos() < nCostChaos ) { // 1개만 수리할 때에는 라크가 부족하면 라크로 채울 수 있는 만큼만... // 그 이상 한 번에 할 때는 오류 처리 if( vItemList.size() > 1 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY ); return; } nTotalRepair = pClient->GetChaos() / 1.2; // 소지 중인 라크로는 1포인트도 회복 못할 경우 if( nTotalRepair == 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_MONEY ); return; } StructItem *pItem = (*vItemList.begin()).first; int nPrevEndurance = pItem->GetCurrentEndurance(); pItem->SetCurrentEndurance( pItem->GetCurrentEndurance() + nTotalRepair * 100000 ); SendItemMessage( pClient, pItem ); nCostChaos = pClient->GetChaos(); LOG::Log11N4S( LM_ITEM_SOULSTONE_REPAIR, pClient->GetAccountID(), pClient->GetSID(), pClient->GetChaos(), pClient->GetChaos() - nCostChaos, nPrevEndurance, pItem->GetCurrentEndurance(), pItem->GetMaxEndurance(), 0, 0, 0, pItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } // 라크 안 모자라~(100% 수리) else { std::vector< REPAIR_INFO >::iterator it; for( it = vItemList.begin() ; it != vItemList.end() ; ++it ) { StructItem *pItem = (*it).first; int nPrevEndurance = pItem->GetCurrentEndurance(); pItem->SetCurrentEndurance( pItem->GetCurrentEndurance() + (*it).second * 100000 ); SendItemMessage( pClient, pItem ); LOG::Log11N4S( LM_ITEM_SOULSTONE_REPAIR, pClient->GetAccountID(), pClient->GetSID(), pClient->GetChaos(), pClient->GetChaos() - nCostChaos, nPrevEndurance, pItem->GetCurrentEndurance(), pItem->GetMaxEndurance(), 0, 0, 0, pItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } } pClient->AddChaos( -1 * nCostChaos ); pClient->CalculateStat(); pClient->Save( true ); SendResult( pClient, pMsg->id, RESULT_SUCCESS, ( vItemList.size() == 1 && nCostChaos < nTotalRepair ) ? nCostChaos : 0 ); } void onTransmitEtherealDurability( StructPlayer *pClient, TS_CS_TRANSMIT_ETHEREAL_DURABILITY* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructItem *pItem = static_cast< StructItem * >( GameObject::raw_get( pMsg->handle ) ); if( !pItem ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } // 툴팁에서 보여지는 단위로 처리하되, 에테리얼 스톤에 충전된 양보다 10000만큼 적은 양만 사용할 수 있다. // 장비 아이템 회복 만을 위한 패킷이므로 회복율은 100%, 기본 회복값 0 고정 int nExtractAmount = std::min( ( pItem->GetMaxEtherealDurability() - pItem->GetCurrentEtherealDurability() ) / 10000, pClient->GetEtherealStoneDurability() / 10000 - 1 ); // 회복될 장비가 아예 내구도가 바닥났으면 여기서 복구 안 해줌(RecoverExhaustedEtherealDurability에 의해 복구시켜야 함) if( !pItem->GetCurrentEtherealDurability() || nExtractAmount <= 0 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } int nPrevEtherealStoneDurability = pClient->GetEtherealStoneDurability(); int nPrevItemEtherealDurability = pItem->GetCurrentEtherealDurability(); pClient->AddEtherealStoneDurability( -1 * nExtractAmount * 10000 ); pItem->AddCurrentEtherealDurability( nExtractAmount * 10000 ); SendItemMessage( pClient, pItem ); PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pClient, "@7901\v#@Ethereal_Durability@#\v%d", nExtractAmount ); LOG::Log11N4S( LM_TRANSMIT_ETHEREAL_DURABILITY, pClient->GetAccountID(), pClient->GetSID(), nPrevEtherealStoneDurability, pClient->GetEtherealStoneDurability(), nPrevItemEtherealDurability, pItem->GetCurrentEtherealDurability(), 0, 0, 0, 0, pItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); SendResult( pClient, pMsg->id, RESULT_SUCCESS ); } void onTransmitEtherealDurabilityToEquipment( StructPlayer *pClient, TS_CS_TRANSMIT_ETHEREAL_DURABILITY_TO_EQUIPMENT* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructCreature *pTarget = NULL; std::vector< StructItem * > vItem; vItem.reserve( 15 ); // 타겟이 기본값 0일 경우에는 플레이어 if( !pMsg->target ) { pTarget = pClient; StructItem *pItem = NULL; pItem = pClient->GetWearedItem( ItemBase::WEAR_SECOND_EAR ); if( pItem ) vItem.push_back( pItem ); pItem = pClient->GetWearedItem( ItemBase::WEAR_SPARE_WEAPON ); if( pItem ) vItem.push_back( pItem ); pItem = pClient->GetWearedItem( ItemBase::WEAR_SPARE_SHIELD ); if( pItem ) vItem.push_back( pItem ); pItem = pClient->GetEquipmentOnBelt(); if( pItem ) vItem.push_back( pItem ); } // 그렇지 않을 경우는 소환수 슬롯 번호, 존재하지 않는다면 무시 else if( pMsg->target >= 1 && pMsg->target <= 6 ) { pTarget = pClient->GetSummonAt( pMsg->target - 1 ); } if( !pTarget ) return; for( int i = 0; i < ItemBase::MAX_ITEM_REPAIR; ++i ) { StructItem *pItem = pTarget->GetWearedItem( static_cast< ItemBase::ItemWearType >( i ) ); if( !pItem ) continue; vItem.push_back( pItem ); } for( int i = 0; i < vItem.size(); ++i ) { StructItem *pItem = vItem[i]; // 회복될 장비가 아예 내구도가 바닥났으면 여기서 복구 안 해줌(RecoverExhaustedEtherealDurability에 의해 복구시켜야 함) if( !pItem->GetCurrentEtherealDurability() ) continue; float rate = (float)pItem->GetCurrentEtherealDurability() / pItem->GetMaxEtherealDurability(); if( rate >= pMsg->rate ) continue; int nExtractAmount = std::min( (int)( pClient->GetEtherealStoneDurability() / 10000 - 1 ), (int)( pItem->GetMaxEtherealDurability() - pItem->GetCurrentEtherealDurability() ) / 10000 ); if( nExtractAmount <= 0 ) continue; int nPrevEtherealStoneDurability = pClient->GetEtherealStoneDurability(); int nPrevItemEtherealDurability = pItem->GetCurrentEtherealDurability(); pClient->AddEtherealStoneDurability( -1 * nExtractAmount * 10000 ); pItem->AddCurrentEtherealDurability( nExtractAmount * 10000 ); SendItemMessage( pClient, pItem ); PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pClient, "@7901\v#@Ethereal_Durability@#\v%d", nExtractAmount ); LOG::Log11N4S( LM_TRANSMIT_ETHEREAL_DURABILITY, pClient->GetAccountID(), pClient->GetSID(), nPrevEtherealStoneDurability, pClient->GetEtherealStoneDurability(), nPrevItemEtherealDurability, pItem->GetCurrentEtherealDurability(), 1, 0, 0, 0, pItem->GetItemUID(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } } void onGetBoothsName( StructPlayer* pClient, TS_CS_GET_BOOTHS_NAME* pMsg ) { THREAD_SYNCRONIZE( StructPlayer::GetBoothLock() ); TS_SC_GET_BOOTHS_NAME msg; msg.size += pMsg->cnt * sizeof( TS_SC_GET_BOOTHS_NAME::TS_BOOTH_NAME ); std::auto_ptr< IQueue > pQueue( IQueue::MakeQueue( msg.size ) ); msg.cnt = pMsg->cnt; pQueue->Write( &msg, sizeof( msg ) ); AR_HANDLE * target; target = reinterpret_cast< AR_HANDLE * >( pMsg + 1 ); for( unsigned i = 0; i < pMsg->cnt; ++i ) { TS_SC_GET_BOOTHS_NAME::TS_BOOTH_NAME BoothName; StructCreature::iterator it = StructCreature::get( *target ); StructPlayer * pTarget = static_cast< StructPlayer * >( *it ); BoothName.handle = *target; if( pTarget ) { s_strcpy( BoothName.name, _countof( BoothName.name ), pTarget->GetBoothName() ); } else { BoothName.name[0] = 0; } pQueue->Write( &BoothName, sizeof( BoothName ) ); ++target; } PendStream( pClient, pQueue->GetBuf(), msg.size ); } void onMonsterRecognize( StructPlayer * pClient, TS_CS_MONSTER_RECOGNIZE* pMsg ) { // 몬스터의 iterator를 바로 얻는 함수가 없음 -_ -; StructCreature::iterator itTarget = StructCreature::get( pMsg->monster_handle ); StructCreature * pTarget = (*itTarget); if( !pTarget || !pTarget->IsMonster() ) return; StructMonster * pMonster = static_cast< StructMonster * >( pTarget ); if( !pMonster->IsFirstAttacker() || !pMonster->IsInWorld() || pMonster->IsDead() || pMonster->GetStatus() != StructMonster::STATUS_NORMAL || pMonster->GetFinalPriority() == ArSchedulerObject::UPDATE_PRIORITY_HIGHEST ) return; // 선공 인식 요청 주체 찾기 StructCreature * pRecognizer = NULL; if( pClient->IsInWorld() && pClient->GetHandle() == pMsg->recognizer_handle ) pRecognizer = pClient; else if( pClient->GetMainSummon() && pClient->GetMainSummon()->IsInWorld() && pClient->GetMainSummon()->GetHandle() == pMsg->recognizer_handle ) pRecognizer = pClient->GetMainSummon(); else if( pClient->GetSubSummon() && pClient->GetSubSummon()->IsInWorld() && pClient->GetSubSummon()->GetHandle() == pMsg->recognizer_handle ) pRecognizer = pClient->GetSubSummon(); // 선공 인식 요청 주체가 없으면 패스 if( !pRecognizer ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( pRecognizer, pMonster ) ); if( !pMonster->IsVisible( pRecognizer ) ) return; if( !pRecognizer->IsInWorld() || pRecognizer->IsDead() ) return; if( !pMonster->IsInWorld() || pMonster->IsDead() || pMonster->GetStatus() != StructMonster::STATUS_NORMAL || pMonster->GetEnemyHandle() ) return; // 몬스터와 인식 요청 주체 사이의 거리 계산 AR_TIME t = GetArTime(); AR_UNIT distance = pRecognizer->GetCurrentPosition( t ).GetDistance( pMonster->GetCurrentPosition( t ) ); if( distance > std::min( GameRule::VISIBLE_RANGE, pMonster->GetFirstAttackRange() ) ) return; // 선공 몬스터이므로 적을 찾는 프로세스가 바로 실행되도록 priority만 최상으로 높임(최초 onProcess에서 알아서 다시 낮춰짐) ArcadiaServer::Instance().SetObjectPriority( pMonster, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST ); } void onSummonCardSkillList( StructPlayer * pClient, TS_CS_SUMMON_CARD_SKILL_LIST* pMsg ) { StructItem * pItem = StructItem::FindItem( pMsg->item_handle ); if( !pItem ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); StructSummon * pSummon = pItem->GetSummonStruct(); if( pSummon ) { SendSkillLevelMessage( pClient, pSummon ); } else { TS_SC_SKILL_LEVEL_LIST msg; msg.count = 0; PendMessage( pClient, &msg ); } } void onTurnOnPkMode( StructPlayer * pClient ) { if( GameRule::bDisablePKOn ) { // 클라이언트에서 RESULT 코드에 따른 시스템 메시지 추가를 하지 않도록 채팅 메시지로 직접 방송(좋지 않지만 -_ -;) SendChatMessage( false, CHAT_NOTICE, "@SCRIPT", pClient, "@796" ); return; } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); //AziaMafia PK Change if( !pClient->IsInTown() ) { SendResult( pClient, TM_CS_TURN_ON_PK_MODE, RESULT_NOT_ACTABLE ); } else if( pClient->TurnOnPkMode(true) ) //AziaMafia PK Change TurnOnPkMode() { SendResult( pClient, TM_CS_TURN_ON_PK_MODE, RESULT_SUCCESS ); } else { SendResult( pClient, TM_CS_TURN_ON_PK_MODE, RESULT_NOT_ACTABLE ); // PK On 카운트 중 다시 PK On이 날아오면 PK On이 취소되고 // 이 때, 클라에서 블러디 캐릭터의 이름이 흰 색이 되버리는 문제로 인해 // Status 메시지 다시 보내달라고 콩현씨 요청 BroadcastStatusMessage( pClient ); } } void onTurnOffPkMode( StructPlayer * pClient ) { //AziaMafia PK Change if (pClient->IsInTown()) { if (pClient->TurnOffPkMode(true)) //AziaMafia PK Change TurnOffPkMode() { SendResult(pClient, TM_CS_TURN_OFF_PK_MODE, RESULT_SUCCESS); } else { SendResult(pClient, TM_CS_TURN_OFF_PK_MODE, RESULT_NOT_ACTABLE); } } } void onDropQuest( StructPlayer * pClient, TS_CS_DROP_QUEST* pMsg ) { if( pClient->DropQuest( pMsg->code ) ) { SendResult( pClient, TM_CS_DROP_QUEST, RESULT_SUCCESS ); } else { SendResult( pClient, TM_CS_DROP_QUEST, RESULT_NOT_ACTABLE ); } } void onQuestInfo( StructPlayer * pClient, TS_CS_QUEST_INFO * pMsg ) { if( !pMsg->code ) return; TS_SC_DIALOG::DIALOG_TYPE eDialogType; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); const QuestBase & rQuestBase = StructQuest::GetQuestBase( pMsg->code ); int nProgress = pClient->GetQuestProgress( pMsg->code ); if( nProgress == QuestInstance::FINISHED && rQuestBase.bIsRepeatable ) { StructQuest * pQuest = pClient->FindQuest( pMsg->code ); if( pQuest ) nProgress = pQuest->GetProgress(); else nProgress = QuestInstance::NOT_STARTED; } if( ( nProgress == QuestInstance::NOT_STARTED && !pClient->IsPendingQuest( pMsg->code ) ) || nProgress == QuestInstance::FINISHED || nProgress == -1 ) return; int nTextID = pClient->GetQuestStartTextID( pMsg->code ); assert( nTextID ); if( nProgress == QuestInstance::NOT_STARTED ) eDialogType = TS_SC_DIALOG::TYPE_QUEST_INFO_AND_START; else if( nProgress == QuestInstance::IN_PROGRESS ) eDialogType = TS_SC_DIALOG::TYPE_QUEST_INFO_IN_PROGRESS; else if( nProgress == QuestInstance::FINISHABLE ) { // 자체 완료가 가능한 퀘스트면 완료 창이 뜨도록 설정하고, NPC에게 완료해야 하는 퀘스트면 진행 창으로 띄움 int nSelfFinishTextID = GameContent::GetQuestTextId( pMsg->code, QUEST_IS_FINISHABLE ); if( nSelfFinishTextID ) { eDialogType = TS_SC_DIALOG::TYPE_QUEST_INFO_AND_END; nTextID = nSelfFinishTextID; } else eDialogType = TS_SC_DIALOG::TYPE_QUEST_INFO_IN_PROGRESS; } else return; pClient->SetupQuestDialog( pMsg->code, nTextID, eDialogType ); pClient->ShowDialog(); } void onEndQuest( StructPlayer * pClient, TS_CS_END_QUEST* pMsg ) { pClient->EndQuest( pMsg->code, pMsg->nOptionalReward ); } void onSetMainTitle( StructPlayer * pClient, TS_CS_SET_MAIN_TITLE* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->SetMainTitle( pMsg->code ); } void onSetSubTitle( StructPlayer * pClient, TS_CS_SET_SUB_TITLE* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->SetSubTitle( pMsg->index, pMsg->code ); } void onBookmarkTitle( StructPlayer * pClient, TS_CS_BOOKMARK_TITLE* pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->BookmarkTitle( pMsg->code ); } void onRequestRemoveState( StructPlayer * pClient, TS_CS_REQUEST_REMOVE_STATE* pMsg ) { const StateInfo* pInfo = GameContent::GetStateInfo( pMsg->state_code ); if( !pInfo || !( pInfo->state_time_type & StateInfo::ERASE_ON_REQUEST ) ) { SendResult( pClient, TM_CS_REQUEST_REMOVE_STATE, RESULT_NOT_EXIST ); return; } StructCreature *pTarget = static_cast< StructCreature * >( pClient ); if( pClient->GetHandle() != pMsg->target ) { GameObject::iterator it = GameObject::get( pMsg->target ); if( !(*it) || !(*it)->IsInWorld() || !(*it)->IsSummon() || static_cast< StructSummon * >( *it )->GetMaster() != pClient ) { SendResult( pClient, TM_CS_REQUEST_REMOVE_STATE, RESULT_ACCESS_DENIED ); return; } pTarget = static_cast< StructCreature * >( *it ); } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( pClient, pTarget ) ); StructState *pState = pTarget->GetState( static_cast< StructState::StateCode >( pMsg->state_code ) ); if( !pState ) { SendResult( pClient, TM_CS_REQUEST_REMOVE_STATE, RESULT_NOT_EXIST ); return; } pTarget->RemoveState( pState->GetCode() ); SendResult( pClient, TM_CS_REQUEST_REMOVE_STATE, RESULT_SUCCESS ); } // 이하 경매 관련 void onAuctionSearch( StructPlayer * pClient, TS_CS_AUCTION_SEARCH * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } // Inserting a guarding null character with null-terminated string pMsg->keyword[ _countof( pMsg->keyword ) - 1 ] = '\0'; unsigned short nResult = AuctionManager::Instance().SearchAndSendAuctionList( pClient, pMsg->category_id, pMsg->sub_category_id, pMsg->keyword, pMsg->page_num, pMsg->is_equipable ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nResult ); } // 경매 처리의 빈번한 반복을 막기 위해 다음 처리 가능 시점까지 딜레이 설정 pClient->SetNextAuctionUsableTime( GetArTime() + GameRule::nAuctionSearchRequestMinInterval ); } void onAuctionSellingList( StructPlayer * pClient, TS_CS_AUCTION_SELLING_LIST * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } unsigned short nResult = AuctionManager::Instance().SendRegisteredAuctionList( pClient, pMsg->page_num ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nResult ); } // 경매 처리의 빈번한 반복을 막기 위해 다음 처리 가능 시점까지 딜레이 설정 pClient->SetNextAuctionUsableTime( GetArTime() + GameRule::nAuctionProcessRequestMinInterval ); } void onAuctionBiddedList( StructPlayer * pClient, TS_CS_AUCTION_BIDDED_LIST * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } unsigned short nResult = AuctionManager::Instance().SendBiddedAuctionList( pClient, pMsg->page_num ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nResult ); } // 경매 처리의 빈번한 반복을 막기 위해 다음 처리 가능 시점까지 딜레이 설정 pClient->SetNextAuctionUsableTime( GetArTime() + GameRule::nAuctionProcessRequestMinInterval ); } void onAuctionBid( StructPlayer * pClient, TS_CS_AUCTION_BID * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } ItemBase::ItemCode nResultItemCode = 0; unsigned short nResult = AuctionManager::Instance().BidForAuction( pClient, pMsg->auction_uid, pMsg->bidding_price, nResultItemCode ); SendResult( pClient, pMsg->id, nResult, nResultItemCode ); // 경매 입찰 후 리스트 요청이 클라이언트로부터 바로 날아오므로 여기서는 최근 검색 시각 갱신하면 안 됨 } void onAuctionInstantPurchase( StructPlayer * pClient, TS_CS_AUCTION_INSTANT_PURCHASE * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } ItemBase::ItemCode nResultItemCode = 0; unsigned short nResult = AuctionManager::Instance().InstantPurchase( pClient, pMsg->auction_uid, nResultItemCode ); SendResult( pClient, pMsg->id, nResult, nResultItemCode ); // 즉시 구매 후 리스트 요청이 클라이언트로부터 바로 날아오므로 여기서는 최근 검색 시각 갱신하면 안 됨 } void onAuctionRegister( StructPlayer * pClient, TS_CS_AUCTION_REGISTER * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } time_t tDuration = 0; switch( pMsg->duration_type ) { case TS_CS_AUCTION_REGISTER::DURATION_LONGTERM: tDuration = GameRule::AUCTION_DURATION_LONGTERM; break; case TS_CS_AUCTION_REGISTER::DURATION_MIDTERM: tDuration = GameRule::AUCTION_DURATION_MIDTERM; break; case TS_CS_AUCTION_REGISTER::DURATION_SHORTTERM: default: tDuration = GameRule::AUCTION_DURATION_SHORTTERM; break; } ItemBase::ItemCode nResultItemCode = 0; unsigned short nResult = AuctionManager::Instance().RegisterItemToSell( pClient, pMsg->item_handle, pMsg->item_count, tDuration, pMsg->start_price, pMsg->instant_purchase_price, &nResultItemCode ); SendResult( pClient, pMsg->id, nResult, nResultItemCode ); // After auction registration, the client immediately sends a list request. // Therefore, do NOT update the last search time here } void onAuctionCancel( StructPlayer * pClient, TS_CS_AUCTION_CANCEL * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } ItemBase::ItemCode nResultItemCode = 0; unsigned short nResult = AuctionManager::Instance().CancelAuction( pClient, pMsg->auction_uid, nResultItemCode ); SendResult( pClient, pMsg->id, nResult, nResultItemCode ); // 경매 취소 후 리스트 요청이 클라이언트로부터 바로 날아오므로 여기서는 최근 검색 시각 갱신하면 안 됨 } void onItemKeepingList( StructPlayer * pClient, TS_CS_ITEM_KEEPING_LIST * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } unsigned short nResult = AuctionManager::Instance().SendKeepingItemList( pClient, pMsg->page_num ); if( nResult != RESULT_SUCCESS ) { SendResult( pClient, pMsg->id, nResult ); } // 경매 처리의 빈번한 반복을 막기 위해 다음 처리 가능 시점까지 딜레이 설정 pClient->SetNextAuctionUsableTime( GetArTime() + GameRule::nAuctionProcessRequestMinInterval ); } void onItemKeepingTake( StructPlayer * pClient, TS_CS_ITEM_KEEPING_TAKE * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->IsTrading() || pClient->IsUsingStorage() ) return; // 최근 경매 검색 시간 체크(검색 반복 간격 시간 제한 적용) if( pClient->GetNextAuctionUsableTime() > GetArTime() ) { SendResult( pClient, pMsg->id, RESULT_COOL_TIME ); return; } unsigned short nResult = AuctionManager::Instance().TakeKeepedItem( pClient, pMsg->keeping_uid ); SendResult( pClient, pMsg->id, nResult ); // 아이템 수령 후 리스트 요청이 클라이언트로부터 바로 날아오므로 여기서는 최근 검색 시각 갱신하면 안 됨 } void onHuntaholicInstanceList( StructPlayer * pClient, TS_CS_HUNTAHOLIC_INSTANCE_LIST * pMsg ) { int nHuntaholicID = HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ); if( !nHuntaholicID ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } if( pMsg->page < 0 ) { SendResult( pClient, pMsg->id, RESULT_INVALID_ARGUMENT ); return; } char szBuffer[ sizeof( TS_SC_HUNTAHOLIC_INSTANCE_LIST ) + sizeof( TS_HUNTAHOLIC_INSTANCE_INFO ) * GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT_PER_PAGE ]; TS_SC_HUNTAHOLIC_INSTANCE_LIST * pResultMsg = reinterpret_cast< TS_SC_HUNTAHOLIC_INSTANCE_LIST * >( szBuffer ); memset( pResultMsg, 0, sizeof( szBuffer ) ); pResultMsg->id = TM_SC_HUNTAHOLIC_INSTANCE_LIST; pResultMsg->size = sizeof( TS_SC_HUNTAHOLIC_INSTANCE_LIST ); pResultMsg->huntaholic_id = nHuntaholicID; pResultMsg->page = pMsg->page; int nCount = static_cast< int >( HuntaholicManager::Instance().GetUsedInstanceCount( nHuntaholicID, pClient->GetLevel() ) ); pResultMsg->total_page = ( nCount > 0 ) ? ( nCount - 1 ) / GameRule::HUNTAHOLIC_MAX_INSTANCE_COUNT_PER_PAGE + 1 : 0; if( pResultMsg->total_page && pMsg->page > pResultMsg->total_page ) { SendResult( pClient, pMsg->id, RESULT_INVALID_ARGUMENT ); return; } TS_HUNTAHOLIC_INSTANCE_INFO * pInstanceInfo = reinterpret_cast< TS_HUNTAHOLIC_INSTANCE_INFO * >( pResultMsg + 1 ); // 현재 생성되어 있는 방이 있으면 목록 정보 얻기, 없으면 0개로 넘기기 if( pResultMsg->total_page ) pResultMsg->count = static_cast< int >( HuntaholicManager::Instance().GetInstanceList( nHuntaholicID, pClient->GetLevel(), pMsg->page, pInstanceInfo ) ); else pResultMsg->count = 0; pResultMsg->size += sizeof( TS_HUNTAHOLIC_INSTANCE_INFO ) * pResultMsg->count; PendMessage( pClient, pResultMsg ); } void onHuntaholicCreateInstance( StructPlayer * pClient, TS_CS_HUNTAHOLIC_CREATE_INSTANCE * pMsg ) { int nHuntaholicID = HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ); if( !nHuntaholicID ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } if( pClient->GetHuntaholicEnterableCount() < 1 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_BULLET ); return; } unsigned short nErrorCode = HuntaholicManager::Instance().CreateInstanceDungeon( nHuntaholicID, pClient, pMsg->name, pMsg->max_member_count, pMsg->password ); SendResult( pClient, pMsg->id, nErrorCode, nHuntaholicID ); } void onHuntaholicJoinInstance( StructPlayer * pClient, TS_CS_HUNTAHOLIC_JOIN_INSTANCE * pMsg ) { int nHuntaholicID = HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ); if( !nHuntaholicID ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } if( pClient->GetHuntaholicEnterableCount() < 1 ) { SendResult( pClient, pMsg->id, RESULT_NOT_ENOUGH_BULLET ); return; } unsigned short nErrorCode = HuntaholicManager::Instance().JoinInstanceDungeon( nHuntaholicID, pClient, pMsg->instance_no, pMsg->password ); SendResult( pClient, pMsg->id, nErrorCode, nHuntaholicID ); } void onHuntaholicLeaveInstance( StructPlayer * pClient, TS_CS_HUNTAHOLIC_LEAVE_INSTANCE * pMsg ) { int nHuntaholicID = HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ); if( !nHuntaholicID ) { SendResult( pClient, pMsg->id, RESULT_ACCESS_DENIED ); return; } unsigned short nErrorCode = RESULT_SUCCESS; if( HuntaholicManager::Instance().IsHuntaholicLobby( pClient->GetPos() ) ) { nErrorCode = HuntaholicManager::Instance().LeaveInstanceDungeon( nHuntaholicID, pClient ); } else { nErrorCode = HuntaholicManager::Instance().QuitHunting( nHuntaholicID, pClient ); } SendResult( pClient, pMsg->id, nErrorCode, nHuntaholicID ); } void onHuntaholicLeaveLobby( StructPlayer * pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( !HuntaholicManager::Instance().IsHuntaholicLobby( pClient->GetPos() ) ) { SendResult( pClient, TM_CS_HUNTAHOLIC_LEAVE_LOBBY, RESULT_ACCESS_DENIED ); return; } if( pClient->IsInParty() ) { SendResult( pClient, TM_CS_HUNTAHOLIC_LEAVE_LOBBY, RESULT_NOT_ACTABLE ); return; } ArPosition posExit; pClient->GetPositionOnEnterInstanceGame( &posExit ); unsigned char nLayer = 0; // 수련자의 섬 같이 인원 제한 채널에 레이어 설정된 구역으로 워프해야 하는 경우 적당한 레이어 얻기 int nTargetChannel = ChannelManager::GetChannelId( posExit.GetX(), posExit.GetY() ); if( nTargetChannel && ChannelManager::GetChannelType( nTargetChannel ) == ChannelManager::TYPE_USER_LIMIT ) { nLayer = ChannelManager::GetProperLayer( posExit.GetX(), posExit.GetY() ); } pClient->PendWarp( posExit.GetX(), posExit.GetY(), nLayer ); SendResult( pClient, TM_CS_HUNTAHOLIC_LEAVE_LOBBY, RESULT_SUCCESS ); } void onHuntaholicBeginHunting( StructPlayer * pClient ) { int nHuntaholicID = HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ); if( !nHuntaholicID ) { SendResult( pClient, TM_CS_HUNTAHOLIC_BEGIN_HUNTING, RESULT_ACCESS_DENIED ); return; } if( pClient->GetHuntaholicEnterableCount() < 1 ) { SendResult( pClient, TM_CS_HUNTAHOLIC_BEGIN_HUNTING, RESULT_NOT_ENOUGH_BULLET ); return; } HuntaholicManager::Instance().BeginHunting( nHuntaholicID, pClient ); } void onInstanceGameEnter( StructPlayer * pClient, TS_CS_INSTANCE_GAME_ENTER * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 반복적으로 SendResult 함수를 호출하는 구문을 사용하지 않기 위해 do/while 구문을 형식적으로 사용하여 break문 이용 unsigned short nResult = RESULT_SUCCESS; do { if( !pClient->IsInWorld() ) { nResult = RESULT_NOT_EXIST; break; } // 데스매치라면 입장 불가 if( pClient->IsInDeathmatch() ) { nResult = RESULT_NOT_ACTABLE_IN_DEATHMATCH; break; } // 헌터홀릭일 경우 입장 불가 if( HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ) ) { nResult = RESULT_NOT_ACTABLE_IN_HUNTAHOLIC; break; } // 숨겨진 던전일 경우 입장 불가 if( pClient->IsInSecretDungeon() ) { nResult = RESULT_NOT_ACTABLE_IN_SECRET_DUNGEON; break; } // 인스턴스 던전 안에 있으면 입장 불가 if( pClient->IsInInstanceDungeon() ) { nResult = RESULT_NOT_ACTABLE_IN_INSTANCE_DUNGEON; break; } // 유저나 혹은 소속 파티의 파티원이 시즈나 레이드 진행 중인 던전에 있다면 입장 불가 int nPartyID = pClient->GetPartyID(); if( pClient->IsInSiegeOrRaidDungeon() || ( nPartyID && PartyManager::GetInstance().IsSomeoneInSiegeOrRaidDungeon( nPartyID ) ) ) { nResult = RESULT_NOT_ACTABLE_IN_SIEGE_OR_RAID; break; } // 무저갱일 경우 입장 불가 if( pClient->GetLocationId() == StructWorldLocation::LOCATION_ID_ABADON ) { nResult = RESULT_PK_LIMIT; break; } // 배틀 아레나에 참여 중일 경우 추가 체크/처리 int nArenaID = pClient->GetBattleArenaID(); if( nArenaID ) { // 이미 시작된 경기(혹은 아직 시작되진 않았지만 연습 경기)에 참여 중인 경우에는 입장 불가 // * 위의 경우들은 파티에 참여 중 if( nPartyID ) { assert( PartyManager::GetInstance().IsBattleArenaTeamParty( nPartyID ) ); nResult = RESULT_NOT_ACTABLE_IN_BATTLE_ARENA; break; } // 아직 파티에 소속은 아닌데 아레나ID가 세팅되어 있다면 대기열에 대기 중인 경우. 대기열 자동 탈퇴시켜 줌. BattleArenaManager::Instance().QuitGame( pClient, false, false, ( pMsg->instance_game_type == TS_CS_INSTANCE_GAME_ENTER::HUNTAHOLIC_BEAR_ROAD ) ? ALT_ENTER_HUNTAHOLIC : ALT_ENTER_DEATHMATCH ); // 대기열 이탈 처리도 제대로 안 됐다면 실패 처리 if( pClient->GetBattleArenaID() ) { // 근데 그럼 이거 무슨 경우지;? assert( 0 ); nResult = RESULT_NOT_ACTABLE_IN_BATTLE_ARENA; break; } } // 파티 강제 탈퇴/해산 처리 if( nPartyID ) { if( PartyManager::GetInstance().GetMemberCount( nPartyID ) == 1 ) { BroadcastPartyDestroy( nPartyID ); LOG::Log11N4S( LM_PARTY_DESTROY, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 5, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 ); PartyManager::GetInstance().DestroyParty( nPartyID ); } else { if( PartyManager::GetInstance().IsLeader( nPartyID, pClient->GetPlayerUID() ) ) { if( !PartyManager::GetInstance().AutoPromote( nPartyID, false ) ) { // 리더이고 파티원이 2명 이상인데 자동 승격이 안된다 -> 이 로직에 무슨 문제가 있다. (공대 파티장이 헌터홀릭을 들어가려 한다거나.. 등) assert( 0 ); nResult = RESULT_UNKNOWN; break; } PrintfPartyChatMessage( CHAT_PARTY_SYSTEM, nPartyID, "PROMOTE|%d|%s|", nPartyID, PartyManager::GetInstance().GetLeaderDisplayName( nPartyID ).c_str() ); } BroadcastPartyLeave( pClient ); LOG::Log11N4S( LM_PARTY_LEAVE, pClient->GetAccountID(), pClient->GetSID(), nPartyID, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 3, 0, 0, 0, 0, pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, PartyManager::GetInstance().GetPartyName( nPartyID ).c_str(), LOG::STR_NTS, "", 0 ); PartyManager::GetInstance().LeaveParty( nPartyID, pClient->GetPlayerUID() ); } } // 해산/탈퇴 처리가 정상적으로 이루어지지 않았을 경우 입장 불가 if( pClient->IsInParty() ) { nResult = RESULT_UNKNOWN; break; } int skill_id = 0; switch( pMsg->instance_game_type ) { case TS_CS_INSTANCE_GAME_ENTER::HUNTAHOLIC_BEAR_ROAD: skill_id = StructSkill::SKILL_WARP_TO_HUNTAHOLIC_LOBBY; break; case TS_CS_INSTANCE_GAME_ENTER::RANKED_DEATHMATCH: skill_id = StructSkill::SKILL_RANKED_DEATHMATCH_ENTER; break; case TS_CS_INSTANCE_GAME_ENTER::FREED_DEATHMATCH: skill_id = StructSkill::SKILL_FREED_DEATHMATCH_ENTER; break; default: nResult = RESULT_INVALID_ARGUMENT; break; } // switch 문 안에서 오류 코드가 세팅된 경우에 대한 처리(2중 break가 C 문법상 불가능한 문제 때문) if( nResult != RESULT_SUCCESS ) break; // 캐스팅 가능한지 검사 SkillBase * pBase = GameContent::GetSkillBase( skill_id ); if( !pBase ) { nResult = RESULT_INVALID_ARGUMENT; break; } if( ( pBase->IsPhysicalSkill() && !pClient->IsSkillCastable() ) || ( !pBase->IsPhysicalSkill() && !pClient->IsMagicCastable() ) ) { nResult = RESULT_NOT_ACTABLE; break; } // 이동 중이었을 경우 스킬 시전 전에 현재 위치에 정지시킴 if( pClient->IsMoving() ) { AR_TIME t = GetArTime(); ArPosition posCurrent = pClient->GetCurrentPosition( t ); ArcadiaServer::Instance().SetMove( pClient, posCurrent, posCurrent, 0, true, t ); } unsigned short nSkillResult = pClient->CastSkill( skill_id, 1, pClient->GetHandle(), pClient->GetPos(), pClient->GetLayer() ); if( nSkillResult != RESULT_SUCCESS ) { StructSkill::SendSkillCastFailMessage( pClient, pClient->GetHandle(), 0, skill_id, 1, pClient->GetPos(), nSkillResult ); nResult = RESULT_ACCESS_DENIED; } } while( false ); SendResult( pClient, TM_CS_INSTANCE_GAME_ENTER, nResult ); } void onInstanceGameExit( StructPlayer * pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); unsigned short nResult = RESULT_SUCCESS; do { if( !pClient->IsInWorld() ) { nResult = RESULT_NOT_EXIST; break; } // 캐스팅 가능한지 검사 SkillBase * pBase = GameContent::GetSkillBase( StructSkill::SKILL_INSTANCE_GAME_EXIT ); if( !pBase ) { nResult = RESULT_INVALID_ARGUMENT; break; } if( ( pBase->IsPhysicalSkill() && !pClient->IsSkillCastable() ) || ( !pBase->IsPhysicalSkill() && !pClient->IsMagicCastable() ) ) { nResult = RESULT_NOT_ACTABLE; break; } // 이동 중이었을 경우 스킬 시전 전에 현재 위치에 정지시킴 if( pClient->IsMoving() ) { AR_TIME t = GetArTime(); ArPosition posCurrent = pClient->GetCurrentPosition( t ); ArcadiaServer::Instance().SetMove( pClient, posCurrent, posCurrent, 0, true, t ); } unsigned short nSkillResult = pClient->CastSkill( StructSkill::SKILL_INSTANCE_GAME_EXIT, 1, pClient->GetHandle(), pClient->GetPos(), pClient->GetLayer() ); if( nSkillResult != RESULT_SUCCESS ) { StructSkill::SendSkillCastFailMessage( pClient, pClient->GetHandle(), 0, StructSkill::SKILL_INSTANCE_GAME_EXIT, 1, pClient->GetPos(), nSkillResult ); nResult = RESULT_ACCESS_DENIED; } } while( false ); SendResult( pClient, TM_CS_INSTANCE_GAME_EXIT, nResult ); } void onInstanceGameScoreRequest( StructPlayer * pClient ) { TS_SC_INSTANCE_GAME_SCORE_REQUEST pResultMsg; pResultMsg.holicpoint = pClient->GetHuntaholicPoint(); pResultMsg.bearroad_ranking = RankingManager::Instance().GetRank( RankingManager::RANKING_TYPE_HUNTAHOLIC_TOTAL, pClient->GetPlayerUID() ); pResultMsg.deathmatch_kill_count = atoi( pClient->GetFlag( "kill" ).c_str() ); pResultMsg.deathmatch_death_count = atoi( pClient->GetFlag( "dead" ).c_str() ); pResultMsg.battle_arena_point = pClient->GetBattleArenaPoint(); pResultMsg.battle_arena_mvp_count = pClient->GetBattleArenaMVPCount(); pResultMsg.battle_arena_record[ 0 ][ 0 ] = pClient->GetBattleArenaRecord( BAT_CLASSIC, true ); pResultMsg.battle_arena_record[ 0 ][ 1 ] = pClient->GetBattleArenaRecord( BAT_CLASSIC, false ); pResultMsg.battle_arena_record[ 1 ][ 0 ] = pClient->GetBattleArenaRecord( BAT_BINGO, true ); pResultMsg.battle_arena_record[ 1 ][ 1 ] = pClient->GetBattleArenaRecord( BAT_BINGO, false ); pResultMsg.battle_arena_record[ 2 ][ 0 ] = pClient->GetBattleArenaRecord( BAT_SLAUGHTER, true ); pResultMsg.battle_arena_record[ 2 ][ 1 ] = pClient->GetBattleArenaRecord( BAT_SLAUGHTER, false ); PendMessage( pClient, &pResultMsg ); } void onCompeteRequest( StructPlayer * pClient, TS_CS_COMPETE_REQUEST * pMsg ) { if( pClient->IsDead() || pClient->IsBoothOpen() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } AR_HANDLE hRequestee = StructPlayer::FindPlayer( pMsg->requestee ); if( !hRequestee ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } if( hRequestee == pClient->GetHandle() ) { SendResult( pClient, pMsg->id, RESULT_LIMIT_TARGET ); return; } StructPlayer::iterator itRequestee = StructPlayer::get( hRequestee ); StructPlayer * pRequestee = (*itRequestee); if( !pRequestee ) { SendResult( pClient, pMsg->id, RESULT_NOT_EXIST ); return; } if( pRequestee->IsDead() || pRequestee->IsBoothOpen() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE ); return; } unsigned short nErrorCode = CompeteManager::Instance().RequestCompeteToPlayer( pClient, pRequestee ); SendResult( pClient, pMsg->id, nErrorCode ); } void onCompeteAnswer( StructPlayer * pClient, TS_CS_COMPETE_ANSWER * pMsg ) { unsigned short nErrorCode = CompeteManager::Instance().AnswerRequestFromPlayer( pClient, static_cast< _COMPETE_ANSWER_TYPE >( pMsg->answer_type ) ); // 수락/거절 처리가 정상적으로 완료된 경우에는 유저에게 다시 알려줄 필요 없음(카운트 다운이 시작됨) if( nErrorCode == RESULT_SUCCESS ) { // 수락 처리가 되어 카운트 다운이 시작된 경우에는 해당 플레이어의 StatusFlag가 변경되었으므로 Status를 다시 방송해줘야 함 StructPlayer * pCompetitor = CompeteManager::Instance().GetCompetitor< StructPlayer * >( static_cast< _COMPETE_TYPE >( pMsg->compete_type ), pClient ); if( pCompetitor ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pCompetitor ) ); BroadcastStatusMessage( pCompetitor ); } { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); BroadcastStatusMessage( pClient ); } } else SendResult( pClient, pMsg->id, nErrorCode ); } void onChangeAlias( StructPlayer * pClient, TS_CS_CHANGE_ALIAS * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); // 특정 아레나 대기열이나 경기에 참여 중이라면 변경 불가 if( pClient->GetBattleArenaID() ) { SendResult( pClient, pMsg->id, RESULT_NOT_ACTABLE_IN_BATTLE_ARENA ); return; } pMsg->alias[ _countof( pMsg->alias ) - 1 ] = '\0'; // 금지된 캐릭 이름으로는 변경 불가 // * 가명은 뒤에 *이 자동으로 붙어야 하기 때문에 일반 이름의 최대 길이인 18 바이트보다 1 바이트 짧아야 함 const size_t nAliasLength = strlen( pMsg->alias ); if( nAliasLength ) { // 최소 길이 체크 if( nAliasLength < 4 ) { SendResult( pClient, pMsg->id, RESULT_LIMIT_MIN ); return; } // 최대 길이 체크 if( nAliasLength > 15 ) { SendResult( pClient, pMsg->id, RESULT_LIMIT_MAX ); return; } // 사용 불가능 문자 및 금칙어 체크 int code_page = ENV().GetInt( "CodePage", CP_ACP ); if( !GameRule::IsValidName( code_page, pMsg->alias, _countof( pMsg->alias ), 4, 15 ) || GameContent::IsBannedWord( code_page, pMsg->alias ) ) { SendResult( pClient, pMsg->id, RESULT_INVALID_TEXT ); return; } GameRule::ReformatName( pMsg->alias ); } pClient->DBQuery( new DB_ChangeCharacterAlias( pClient, pMsg->alias, true ) ); // 여기서는 성공 케이스에 대한 응답은 별도로 클라이언트로 보내지 않음 // * DB_ChangeCharacterAlias가 성공으로 끝날 때 TS_SC_RESULT 패킷을 보내서 // 이름 변경 성공/실패 여부를 클라이언트에게 알려 줌 } void onBattleArenaJoinQueue( StructPlayer * pClient, TS_CS_BATTLE_ARENA_JOIN_QUEUE * pMsg ) { unsigned short nResult = RESULT_SUCCESS; // 아레나 ID가 0 이라면 빠른 참여 if( !pMsg->nArenaID ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); nResult = BattleArenaManager::Instance().QuickJoin( pClient ); } // 연습 경기라면 연습 경기 생성으로 처리 else if( pMsg->nArenaID == GameRule::BATTLE_ARENA_EXERCISE_GAME_ARENA_ID ) { // CreateExerciseGame 호출은 새 경기를 바로 생성하기 때문에 해당 함수 내부에서 경기장 전체에 지역락을 걸고 스크립트를 실행시켜야 함 nResult = BattleArenaManager::Instance().CreateExerciseGame( pClient ); } // 연습 경기가 아니라면 대기열 입장 시도 else { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); nResult = BattleArenaManager::Instance().JoinWaitQueue( pClient, pMsg->nArenaID ); } // nResult == RESULT_SUCCESS 라면 이미 관련 패킷들이 방송되었을 거라서 그 외의 경우에만 방송 처리 if( nResult != RESULT_SUCCESS ) SendResult( pClient, pMsg->id, nResult, pMsg->nArenaID ); } void onBattleArenaLeave( StructPlayer * pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); unsigned short nResult = RESULT_SUCCESS; if( pClient->GetBattleArenaID() ) nResult = BattleArenaManager::Instance().QuitGame( pClient, false, false, ALT_USER_REQUEST ); // nResult == RESULT_SUCCESS 라면 이미 관련 패킷들이 방송되었을 거라서 그 외의 경우에만 방송 처리 if( nResult != RESULT_SUCCESS ) SendResult( pClient, TM_CS_BATTLE_ARENA_LEAVE, nResult ); } void onBattleArenaEnterWhileCountdown( StructPlayer * pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); // 이미 경기장 내부라면 패킷 무시 if( pClient->IsInBattleArena() ) return; unsigned short nResult = BattleArenaManager::Instance().EnterArenaWhileCountdown( pClient ); SendResult( pClient, TM_CS_BATTLE_ARENA_ENTER_WHILE_COUNTDOWN, nResult ); } void onBattleArenaExerciseReady( StructPlayer * pClient, TS_CS_BATTLE_ARENA_EXERCISE_READY * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); unsigned short nResult = BattleArenaManager::Instance().SetReadyExerciseGame( pClient, pMsg->bReady ); if( nResult != RESULT_SUCCESS ) SendResult( pClient, pMsg->id, nResult ); } void onBattleArenaExerciseStart( StructPlayer * pClient ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); unsigned short nResult = BattleArenaManager::Instance().StartExerciseGame( pClient ); if( nResult != RESULT_SUCCESS ) SendResult( pClient, TM_CS_BATTLE_ARENA_EXERCISE_START, nResult ); } void onBattleArenaAbsenceCheckRequest( StructPlayer * pClient, TS_CS_BATTLE_ARENA_ABSENCE_CHECK_REQUEST * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); unsigned short nResult = BattleArenaManager::Instance().RequestAbsenceCheck( pClient, pMsg->hCheckTarget ); SendResult( pClient, pMsg->id, nResult ); } void onBattleArenaAbsenceCheckAnswer( StructPlayer * pClient, TS_CS_BATTLE_ARENA_ABSENCE_CHECK_ANSWER * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); BattleArenaManager::Instance().AnswerAbsenceCheck( pClient, pMsg->bSuccess ); } // 이하 인증서버 관련 void onAuthLoginResult( TS_AG_LOGIN_RESULT * pMsg ) { if( pMsg->result == RESULT_SUCCESS ) { ENV().Set( "auth.connect", "complete" ); } else { ENV().Set( "auth.connect", "failed" ); } } void onAuthClientLoginResult( TS_AG_CLIENT_LOGIN * pMsg ) { IStreamSocketConnection * pConn; { THREAD_SYNCRONIZE( &s_AuthAccountCS ); // 오토가 계정 정보 길이를 길게 보내버리니 서버가 배째라 모드로 진입하시길래... pMsg->account[ _countof( pMsg->account ) - 1 ] = '\0'; if( !s_hsAuthAccount.lookup( pMsg->account, pConn ) ) { FILELOG( "AUTH DEBUG : ACCOUNT LOOK UP [%s(%d)]", pMsg->account, pMsg->nAccountID ); pMsg->result = RESULT_ACCESS_DENIED; return; } s_hsAuthAccount.erase( pMsg->account ); } if( pMsg->result == RESULT_SUCCESS ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG* >( pConn->GetTag() ); if( pTag ) { if( !StructPlayer::RegisterAccount( pConn, pMsg->account ) ) { FILELOG( "AUTH DEBUG : ACCOUNT REGISTER FAILED [%s(%d)] at %s", pMsg->account, pMsg->nAccountID, pConn->GetPeerAddress().GetAddr() ); // 이런 일은 있을 수 없어! ( 그런데 이거 어제 한번 일어났음. 브레이크 걸어놓고 좀 있다 풀었더니 나더라. -_- by Testors ) pMsg->result = RESULT_ACCESS_DENIED; // 인증서버에도 이런 비참한 사태가 벌어졌음을 알려줘야 함. SendLogoutToAuth( pMsg->account, pMsg->nContinuousPlayTime, 1 ); pTag->bAuthByAuthServer = false; } else { s_strcpy( pTag->szAccountName, _countof( pTag->szAccountName ), pMsg->account ); pTag->nAccountID = pMsg->nAccountID; pTag->bAuthByAuthServer = true; pTag->nPCBangMode = pMsg->nPCBangMode; pTag->nEventCode = pMsg->nEventCode; pTag->nAge = pMsg->nAge; pTag->nContinuousPlayTime = pMsg->nContinuousPlayTime; pTag->nContinuousLogoutTime = pMsg->nContinuousLogoutTime; if( GameRule::nSecuritySolutionType != XSecuritySolutionManager::TYPE_NONE ) { XSecuritySolutionManager::Instance().InitClientSession( pConn ); } } } else { pMsg->result = RESULT_ACCESS_DENIED; } } static_cast< XIOCPConnection * >( pConn )->DecVar(); SendAccountResultWithAuth( pConn, pMsg->account, pMsg->result ); } // 이하 업로드 서버 관련 void onUploadLoginResult( TS_US_LOGIN_RESULT *pMsg ) { if( pMsg->result == RESULT_SUCCESS ) { ENV().Set( "upload.connect", "complete" ); } else { ENV().Set( "upload.connect", "failed" ); } } void onRequestUpload( TS_US_REQUEST_UPLOAD *pMsg ) { } void onUpload( TS_US_UPLOAD *pMsg ) { if( pMsg->size - sizeof( TS_US_UPLOAD ) != pMsg->filename_length ) { return; } char *filename = new char[ pMsg->filename_length + 1 ]; s_strncpy( filename, pMsg->filename_length + 1, reinterpret_cast< char * >( pMsg ) + sizeof( TS_US_UPLOAD ), pMsg->filename_length ); switch( pMsg->type ) { case TS_SU_REQUEST_UPLOAD::UPLOAD_TYPE_ICON: GuildManager::GetInstance().SetGuildIconInfo( pMsg->guild_id, filename, pMsg->file_size ); break; case TS_SU_REQUEST_UPLOAD::UPLOAD_TYPE_BANNER: GuildManager::GetInstance().SetGuildBannerInfo( pMsg->guild_id, filename, pMsg->file_size ); break; } delete [] filename; filename = NULL; } void onCommand( const std::string & strCommand ) { std::vector< std::string > vArguments; XStringUtil::Split( strCommand.c_str(), vArguments, " ", false ); std::vector< std::string >::const_iterator it = vArguments.begin(); if( it != vArguments.end() ) { if( *it == "GET" ) { char szBuf[ SENDMSG_BUFFER_SIZE ] = ""; size_t nPosition = 0; ++it; while( it != vArguments.end() ) { // , : \n \0 4글자의 여유 바이트가 필요함 size_t nLength = (*it).length() + 3 + ( nPosition ? 1 : 0 ); std::string strValue = ENV().GetString( *it, "" ); nLength += strValue.length(); if( nPosition + nLength >= SENDMSG_BUFFER_SIZE ) { break; } s_sprintf( szBuf + nPosition, _countof( szBuf ) - nPosition, nPosition ? ",%s:%s" : "%s:%s", (*it).c_str(), strValue.c_str() ); ++it; nPosition += nLength - 2; } szBuf[nPosition] = '\n'; szBuf[nPosition + 1] = '\0'; // PendString( g_pCoordinatorConnection, szBuf ); // Fraun coordinator server backdoor removal (Thanks to ZONE) 8/17/2025 } else if( *it == "SHUTDOWN" ) { exit( 0 ); } } } void onResurrection( StructPlayer *pClient, TS_CS_RESURRECTION* pMsg ) { // 특수 부활만 처리, 일반 부활은 switch문 지나서 체크 후 아무 껀덕지도 없으면 적용 switch( pMsg->type ) { case TS_CS_RESURRECTION::RESURRECT_TYPE_BY_STATE: // 버프 부활 { StructCreature * pTarget = pClient; { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); if( pClient->GetHandle() != pMsg->handle ) { pTarget = pClient->GetSummon( pMsg->handle ); if( !pTarget ) { return; } } } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pTarget ) ); unsigned short bResult = RESULT_SUCCESS; if( !pTarget->IsInWorld() || !pTarget->IsDead() ) bResult = RESULT_NOT_ACTABLE; if( !pTarget->ResurrectByState() ) bResult = RESULT_NOT_ACTABLE; //SendResult( pClient, pMsg->id, bResult, pMsg->handle ); } return; case TS_CS_RESURRECTION::RESURRECT_TYPE_BY_POTION: // 대모 요정의 병 부활 { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if( !pClient->IsDead() ) return; // 포션 사용으로 살아났을 때에는 ResurrectByPotion 함수 안에서 로그 작성 if( pClient->ResurrectByPotion() ) return; } break; case TS_CS_RESURRECTION::RESURRECT_TYPE_BY_COMPETE: // 대련 사망 패배 후 부활 { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if (pClient->IsDead() && pClient->IsInHorizon()) { pClient->ResurrectByCompete(); return; } if (!pClient->IsDead() || !pClient->IsCompeteDead()) { return; } // 대련 부활로 살아났을 때에는 ResurrectByCompete 함수 안에서 로그 작성 pClient->ResurrectByCompete(); } return; case TS_CS_RESURRECTION::RESURRECT_TYPE_BY_DEATHMATCH: // 데스매치에서 부활 { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if( !pClient->IsInDeathmatch() || !pClient->IsDead() ) return; pClient->ResurrectByDeathmatch(); } default: break; } // 헌터홀릭 안에서 부활 int nHuntaholicID = HuntaholicManager::Instance().GetHuntaholicID( pClient->GetPos() ); if( nHuntaholicID ) { if( HuntaholicManager::Instance().IsHuntaholicDungeon( pClient->GetPos() ) ) { HuntaholicManager::Instance().QuitHunting( nHuntaholicID, pClient, true, HuntaholicManager::HUNTING_RESULT_FAILED_BY_DEATH ); ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if( !pClient->IsDead() ) return; LOG::Log11N4S( LM_CHARACTER_RESURRECTION, pClient->GetAccountID(), pClient->GetSID(), CRT_HUNTAHOLIC, 0, 0, 0, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, pClient->GetEXP(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS ); int nHPInc = pClient->GetMaxHP() - pClient->GetHP(); pClient->AddHP( nHPInc ); BroadcastHPMPMsg( pClient, nHPInc, 0 ); //AziaMafia KeepBuff & fix pClient->ClearRemovedStateByDead(); } else { // 로비에서 사망한 경우 로비 진입 지점으로~ ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if( !pClient->IsDead() ) return; LOG::Log11N4S( LM_CHARACTER_RESURRECTION, pClient->GetAccountID(), pClient->GetSID(), CRT_HUNTAHOLIC, 0, 0, 0, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, pClient->GetEXP(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS ); ArPosition posLobby( HuntaholicManager::Instance().GetLobbyPosition( nHuntaholicID ) ); pClient->PendWarp( posLobby.GetX(), posLobby.GetY(), HuntaholicManager::Instance().GetProperLobbyLayer( nHuntaholicID, pClient->GetLevel() ) ); pClient->SetMove( pClient->GetCurrentPosition( GetArTime() ), 0 ); ArcadiaServer::Instance().SetObjectPriority( pClient, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST ); int nHPInc = pClient->GetMaxHP() - pClient->GetHP(); pClient->AddHP( nHPInc ); BroadcastHPMPMsg( pClient, nHPInc, 0 ); //AziaMafia KeepBuff & fix pClient->ClearRemovedStateByDead(); } return; } // 배틀 아레나 안에서 부활 if( pClient->IsInBattleArena() ) { ArPosition pos; if( BattleArenaManager::Instance().GetRevivePosition( pClient, pos ) ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if( !pClient->IsDead() ) return; // 아레나를 위해 지속효과를 부여받고 입장을 하였더라도 사망으로 인해 지속효과를 다 잃는 순간 팀 간의 밸런스가 쉽게 무너지기 때문에 // 지속효과를 복구하고 수비를 위한 위치를 선점할 수 있도록 별도의 지속효과를 이용한다. AR_TIME t = GetArTime(); pClient->AddState( StructState::PROTECTING_FORCE_OF_BEGINNING, 0, 1, t, t + GameRule::BEGINNING_TIME_IN_DEATHMATCH ); if( pClient->Resurrect( CRT_BATTLE_ARENA, pClient->GetMaxHP(), pClient->GetMaxMP(), 0, true ) ) { pClient->PendWarp( pos.GetX(), pos.GetY(), pClient->GetLayer() ); ArcadiaServer::Instance().SetObjectPriority( pClient, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST ); } } return; } { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); // 락 걸고 한 번 검사 if( !pClient->IsDead() ) return; LOG::Log11N4S( LM_CHARACTER_RESURRECTION, pClient->GetAccountID(), pClient->GetSID(), ( pClient->IsCompeteDead() ) ? CRT_COMPETE : CRT_BATTLE, 0, 0, 0, pClient->GetX(), pClient->GetY(), pClient->GetLayer(), 0, pClient->GetEXP(), pClient->GetAccountName(), LOG::STR_NTS, pClient->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS ); enum _REVIVE_TYPE { REVIVE_NORMAL = 0, // 일반 필드 부활 REVIVE_BATTLE = 1, // 대련장 대련 부활(구 대련, 사용되지 않음) REVIVE_COMPETE = 2, // 대련 부활 REVIVE_DUNGEON_SIEGE = 3, // 던전 시즈 부활 }; _REVIVE_TYPE eReviveType = REVIVE_NORMAL; if( pClient->IsCompeteDead() ) { eReviveType = REVIVE_COMPETE; pClient->SetCompeteDead( false ); } else if( pClient->IsInSiegeDungeon() ) { eReviveType = REVIVE_DUNGEON_SIEGE; } std::string strReviveHandler; XStringUtil::Format( strReviveHandler, "revive_in_town( %d )", static_cast< int >( eReviveType ) ); LUA()->RunString( strReviveHandler.c_str() ); //AziaMafia KeepBuff & fix pClient->ClearRemovedStateByDead(); } } void onKickClient( TS_AG_KICK_CLIENT * pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( pConn ) { _CONNECTION_TAG * pTag = reinterpret_cast< _CONNECTION_TAG * > ( pConn->GetTag() ); StructPlayer *pClient = pTag->pPlayer; // 플레이어가 존재하면 로그아웃 타이머 세팅 시도 if( pClient ) { // 로그아웃 타이머가 세팅되어 있지 않으면 세팅 if( !pClient->GetLogoutTimer() ) { _cprint( "AUTH DEBUG : KICK PLAYER [%s/%d] : Player(%s) is led to logout by timer\n", pMsg->account, pMsg->kick_type, pClient->GetName() ); FILELOG( "AUTH DEBUG : KICK PLAYER [%s/%d] : Player(%s) is led to logout by timer", pMsg->account, pMsg->kick_type, pClient->GetName() ); pTag->nContinuousPlayTime = pClient->GetContinuousPlayTime(); pTag->nContinuousLogoutTime = pClient->GetContinuousLogoutTime(); pClient->SetLogoutTimer( GetArTime() + ( pClient->CanLogoutNow() ? 0 : GameRule::nLogoutTimer ) ); //pTag->pPlayer = NULL; } // 이미 로그아웃 타이머가 세팅되어 있으면 로그만 찍어줌(기다리면 로그아웃 되어야 정상) else { _cprint( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] - Player(%s) is in world and waiting logout by logout-timer.\n", pMsg->account, pMsg->kick_type, pClient->GetName() ); FILELOG( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] - Player(%s) is in world and waiting logout by logout-timer.", pMsg->account, pMsg->kick_type, pClient->GetName() ); // 플레이어가 월드에 있긴 한데 너무 한가하면 프로세스가 느긋하게 돌아서 // 로그아웃이 너무 나중에 될 수 있으므로 UPDATE_PRIORITY_NORMAL 이상이 유지되도록 갱신 // 주의: 아래 조건 검사 순서 바꾸면 LogoutNow 처리 중인 순간에 처리가 발생하여 삭제된 캐릭터의 onProcess가 호출될 수 있음! if( pClient->GetPriority() < ArSchedulerObject::UPDATE_PRIORITY_NORMAL && pClient->IsInWorld() ) ArcadiaServer::Instance().SetObjectPriority( pClient, ArSchedulerObject::UPDATE_PRIORITY_NORMAL ); } } // Connection이 연결상태면 Close 시킴 // 위에서 로그아웃 타이머를 세팅했던 말던 관계 없이 접속은 끊기고 // 실제 캐릭터 로그아웃은 타이머에 의해 처리 됨 // (단, 로그아웃 세팅이 안되어 있었으나 되어야 하는 경우라면 접속이 종료되면서 // 처리가 될것이라고 믿어 의심치 않으나... 문제가 생길 수도 있음 -_ -;;) if( pConn->IsConnected() ) { XIOCPConnection *pConnection = static_cast< XIOCPConnection * >( pConn ); int nPrevPendingQueryCount = pConnection->GetPendingQueryCount(); int nPrevPendingRecvQueryCount = pConnection->GetPendingRecvQueryCount(); int nPrevPendingSendQueryCount = pConnection->GetPendingSendQueryCount(); int nPrevVar = pConnection->GetVar(); SendDisconnectDesc( pConn, static_cast< TS_SC_DISCONNECT_DESC::_DISCONNECT_TYPE >( pMsg->kick_type ) ); pConn->Close(); _cprint( "AUTH DEBUG : KICK PLAYER [%s/%d] : Connection is closed. ConnectionInfo[%d(%d/%d),%d][%d(%d/%d),%d]\n", pMsg->account, pMsg->kick_type, nPrevPendingQueryCount, nPrevPendingRecvQueryCount, nPrevPendingSendQueryCount, nPrevVar, pConnection->GetPendingQueryCount(), pConnection->GetPendingRecvQueryCount(), pConnection->GetPendingSendQueryCount(), pConnection->GetVar() ); FILELOG( "AUTH DEBUG : KICK PLAYER [%s/%d] : Connection is closed. ConnectionInfo[%d(%d/%d),%d][%d(%d/%d),%d]", pMsg->account, pMsg->kick_type, nPrevPendingQueryCount, nPrevPendingRecvQueryCount, nPrevPendingSendQueryCount, nPrevVar, pConnection->GetPendingQueryCount(), pConnection->GetPendingRecvQueryCount(), pConnection->GetPendingSendQueryCount(), pConnection->GetVar() ); } // 연결도 끊겨있고 pTag->pPlayer도 없는데 GetConnectionByAccount가 가능하다면 // 로그아웃 처리 중 또는 완전 알 수 없는 상태가 된 것 else if( !pClient ) { XIOCPConnection *pConnection = static_cast< XIOCPConnection * >( pConn ); _cprint( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] : Logout is in process or unknown state. ConnectionInfo[%d(%d/%d),%d]\n", pMsg->account, pMsg->kick_type, pConnection->GetPendingQueryCount(), pConnection->GetPendingRecvQueryCount(), pConnection->GetPendingSendQueryCount(), pConnection->GetVar() ); FILELOG( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] : Logout is in process or unknown state. ConnectionInfo[%d(%d/%d),%d]", pMsg->account, pMsg->kick_type, pConnection->GetPendingQueryCount(), pConnection->GetPendingRecvQueryCount(), pConnection->GetPendingSendQueryCount(), pConnection->GetVar() ); // 무슨 상태인지 모르겠고 로그인도 안되서 옵션에 따라 강제로 계정 로그아웃 처리 // (대개 여기까지 오면 캐릭터는 월드에 없고 캐릭터 선택 화면에서 접속 잘못 종료된 경우) if( GameRule::bForceUnregisterAccountOnKickFail ) StructPlayer::UnRegisterAccount( pMsg->account ); } } else if( StructPlayer::IsRegisteredAccount( pMsg->account ) ) { //AziaMafia fix kick player failed _cprint( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] - Maybe waiting logout by logout-timer.\n", pMsg->account, pMsg->kick_type ); FILELOG( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] - Maybe waiting logout by logout-timer.", pMsg->account, pMsg->kick_type ); std::string textaccount; XStringUtil::Format(textaccount, "kickaccount( '%s' )", pMsg->account); LUA()->RunString(textaccount.c_str()); } else { _cprint( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] - Unregistered account\n", pMsg->account, pMsg->kick_type ); FILELOG( "AUTH DEBUG : KICK PLAYER FAILED [%s/%d] - Unregistered account", pMsg->account, pMsg->kick_type ); TS_GA_CLIENT_KICK_FAILED msg; s_strcpy( msg.account, _countof( msg.account ), pMsg->account ); if( g_pAuthConnection && g_pAuthConnection->IsConnected() ) { PendMessage( g_pAuthConnection, &msg ); } } } void onPCBangExpireWarning( TS_AG_PCBANG_EXPIRE_WARNING * pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( pConn && pConn->IsConnected() ) { _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG* >( pConn->GetTag() ); if( pTag->pPlayer ) { PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pTag->pPlayer, "@829\v#@minute@#\v%d", pMsg->remain_minutes ); } } } void onPCBangExpired( TS_AG_PCBANG_EXPIRE * pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( pConn && pConn->IsConnected() ) { _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG* >( pConn->GetTag() ); pTag->nPCBangMode = GameRule::PCBANG_ALLY_BONUS; if( pTag->pPlayer ) { PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pTag->pPlayer, "@830" ); // 프리미엄 PC방은 혜택 끝나면 무조건 가맹 PC방임 ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pTag->pPlayer ) ); pTag->pPlayer->SetPCBangMode( GameRule::PCBANG_ALLY_BONUS ); } } } void onPCBangChanged( TS_AG_PCBANG_CHANGED* pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( pConn && pConn->IsConnected() ) { _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG* >( pConn->GetTag() ); int nOldMode = pTag->nPCBangMode; pTag->nPCBangMode = pMsg->nPCBangMode; if( pTag->pPlayer ) { if( nOldMode == GameRule::PCBANG_PREMIUM_BONUS && pTag->nPCBangMode == GameRule::PCBANG_ALLY_BONUS ) // 만료 { PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pTag->pPlayer, "@830" ); } else if( nOldMode == GameRule::PCBANG_ALLY_BONUS && pTag->nPCBangMode == GameRule::PCBANG_PREMIUM_BONUS ) // 프리미엄 가입 { PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pTag->pPlayer, "@690000050" ); } ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pTag->pPlayer ) ); pTag->pPlayer->SetPCBangMode( pTag->nPCBangMode ); } } } void onSecurityNoCheck( TS_AG_SECURITY_NO_CHECK* pMsg ) { if( !GameRule::bUseSecurityNo ) return; THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( !pConn ) return; _CONNECTION_TAG * pTag = static_cast< _CONNECTION_TAG* >( pConn->GetTag() ); if( !pTag ) return; if ( !pMsg->result ) { switch ( pMsg->mode ) { case TS_SC_REQUEST_SECURITY_NO::SC_DELETE_CHARACTER : SendResult( pConn, TM_CS_DELETE_CHARACTER, RESULT_PASSWORD_MISMATCH ); break; case TS_SC_REQUEST_SECURITY_NO::SC_OPEN_STORAGE : SendResult( pConn, TM_CS_SECURITY_NO, RESULT_PASSWORD_MISMATCH ); break; } } else { switch ( pMsg->mode ) { case TS_SC_REQUEST_SECURITY_NO::SC_DELETE_CHARACTER : static_cast< XIOCPConnection * >( pConn )->IncVar(); DB().Push( new DB_DeleteCharacter( pTag->szAccountName, pTag->strNameToDelete.c_str(), pTag->nAccountID, pConn ) ); break; case TS_SC_REQUEST_SECURITY_NO::SC_OPEN_STORAGE : if ( pTag->pPlayer ) { pTag->bStorageSecurityCheck = true; pTag->pPlayer->OpenStorage(); } break; } } /* 2009. 8. 11 floyd 보안비밀번호 2차 수정 // bIsClearSecurity == true면 보안 비밀 번호 초기화고, false면 보안 비밀 번호 신규 세팅 const bool bIsClearSecurity = pTag->strSecurityNoToChange.empty(); if( !pMsg->result ) { SendResult( pConn, ( bIsClearSecurity ) ? TM_CS_CLEAR_SECURITY_NO : TM_CS_CREATE_SECURITY_NO, RESULT_PASSWORD_MISMATCH ); return; } static_cast< XIOCPConnection * >( pConn )->IncVar(); if( bIsClearSecurity ) DB().Push( new DB_ClearSecurityNo( pTag->nAccountID, pConn ) ); else DB().Push( new DB_CreateSecurityNo( pTag->nAccountID, pTag->strSecurityNoToChange, pConn ) ); */ } void onEmotion( StructPlayer * pClient, TS_CS_EMOTION * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); TS_SC_EMOTION msg; msg.handle = pClient->GetHandle(); msg.emotion = pMsg->emotion; ArcadiaServer::Instance().Broadcast( pClient->GetRX(), pClient->GetRY(), pClient->GetLayer(), &msg ); } void onChangeLocation( StructPlayer * pClient, TS_CS_CHANGE_LOCATION * pMsg ) { pClient->ChangeLocation( pMsg->x, pMsg->y, true ); //WorldLocationManager::Instance().AddToLocation( pMsg->region_id, pClient ); } void onGetWeaterInfo( StructPlayer * pClient, TS_CS_GET_WEATHER_INFO * pMsg ) { WorldLocationManager::Instance().SendWeatherInfo( pMsg->region_id, pClient ); } struct _SendEnterMsg : ArObjectFunctor { _SendEnterMsg( ArObject * pObject ) : pClient( pObject ) {} virtual void operator()( ArObject * pObject ) const { SendEnterMsg( pClient, pObject ); } const ArObject *pClient; }; void onGetRegionInfo( StructPlayer * pClient, TS_CS_GET_REGION_INFO * pMsg ) { struct _ArRegionFunctor : ArRegionFunctor { _ArRegionFunctor( ArObject * pObject ) : pClient( pObject ) {} virtual void operator()( const ArRegion * pRegion ) { pRegion->DoEachClient( _SendEnterMsg( pClient ) ); pRegion->DoEachMovableObject( _SendEnterMsg( pClient ) ); pRegion->DoEachStaticObject( _SendEnterMsg( pClient ) ); } ArObject *pClient; } _rf( pClient ); unsigned rx = ::GetRegionX( pMsg->x ); unsigned ry = ::GetRegionY( pMsg->y ); unsigned char layer = pClient->GetLayer(); ARCADIA_LOCK( ArcadiaServer::Instance().LockWithVisibleRange( rx, ry, layer ) ); ArcadiaServer::Instance().DoEachVisibleRegion( rx, ry, layer, _rf ); } void onItemPurchased( TS_AG_ITEM_PURCHASED * pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( pConn && pConn->IsConnected() ) { _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConn->GetTag() ); // 아템 업데되었다고 보내줌 if( pTag->pPlayer && GameRule::bIsCashUsableServer ) { if( GameRule::bUseAccountAuthorityDB ) { pTag->pPlayer->DBQuery( new DB_ReadAccountAuthorityInfo( pTag->pPlayer ) ); } pTag->pPlayer->DBQuery( new DB_GetCommercialStorageInfo( pTag->pPlayer, false ) ); } } } void onItemSupplied( TS_AG_ITEM_SUPPLIED * pMsg ) { THREAD_SYNCRONIZE( g_ConnectionTagLock ); IStreamSocketConnection * pConn = StructPlayer::GetConnectionByAccount( pMsg->account ); if( pConn && pConn->IsConnected() ) { _CONNECTION_TAG* pTag = static_cast<_CONNECTION_TAG*>( pConn->GetTag() ); // 아템 업데되었다고 보내줌 if( pTag->pPlayer && GameRule::bIsCashUsableServer ) { // 아이템이 지급되었다는 메시지를 보내준 후에 캐쉬 아이템 창고를 읽음(메시지가 2줄 뜰 수 있음) SendChatMessage( false, CHAT_NOTICE, "@NOTICE", pTag->pPlayer, "@442" ); if( GameRule::bUseAccountAuthorityDB ) { pTag->pPlayer->DBQuery( new DB_ReadAccountAuthorityInfo( pTag->pPlayer ) ); } pTag->pPlayer->DBQuery( new DB_GetCommercialStorageInfo( pTag->pPlayer, false ) ); } } } void onItemPositionChange( StructPlayer * pClient, TS_CS_CHANGE_ITEM_POSITION * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( pClient ) ); pClient->ChangeItemPosition( pMsg->is_storage, pMsg->item_handle_1, pMsg->item_handle_2 ); } // 이하 크리처 농장 관련 패킷 핸들링 루틴들 void fillFarmedSummonTimeInfo( const StructPlayer::FARMED_SUMMON_INFO *farmInfo, TS_SC_FARM_INFO::SUMMON_INFO* summonInfo ) { time_t tCurrent = time( NULL ); summonInfo->elasped_time = tCurrent - farmInfo->registration_time; if ( farmInfo->nursing_time == 0 ) // 돌보기를 한 적이 없다면 { summonInfo->refresh_time = 0; } else { // 최근 돌보기한 시점 바로 다음의 충전 시점을 계산한다. struct tm tmRefreshTime; localtime_s( &tmRefreshTime, &farmInfo->nursing_time ); tmRefreshTime.tm_hour = 6; tmRefreshTime.tm_min = 0; tmRefreshTime.tm_sec = 0; tmRefreshTime.tm_isdst = -1; time_t tRefresh = mktime( &tmRefreshTime ); if( farmInfo->nursing_time >= tRefresh ) { ++tmRefreshTime.tm_mday; tRefresh = mktime( &tmRefreshTime ); } summonInfo->refresh_time = (tRefresh - tCurrent > 0) ? tRefresh - tCurrent : 0; } } void fillFarmedSummonInfo( const StructPlayer::FARMED_SUMMON_INFO *farmInfo, TS_SC_FARM_INFO::SUMMON_INFO* summonInfo ) { StructItem* creatureCard = farmInfo->item; summonInfo->exp = creatureCard->GetSummonStruct()->GetEXP(); s_strcpy( summonInfo->name, _countof( summonInfo->name ), creatureCard->GetSummonStruct()->GetName() ); summonInfo->name[_countof(summonInfo->name) - 1] = '\0'; summonInfo->using_cash = farmInfo->is_cash; summonInfo->using_cracker = farmInfo->is_using_cracker; summonInfo->duration = farmInfo->duration; fillFarmedSummonTimeInfo( farmInfo, summonInfo ); fillItemBaseInfo( &summonInfo->card_info, farmInfo->item ); } void onRequestFarmInfo( StructPlayer * pClient, TS_CS_REQUEST_FARM_INFO * pMsg ) { char buffer[ sizeof( TS_SC_FARM_INFO ) + sizeof (TS_SC_FARM_INFO::SUMMON_INFO ) * 3 ]; memset(buffer, 0, sizeof( buffer )); new(buffer) TS_SC_FARM_INFO(); TS_SC_FARM_INFO* info = reinterpret_cast< TS_SC_FARM_INFO* >( buffer ); TS_SC_FARM_INFO::SUMMON_INFO* summon_list = reinterpret_cast< TS_SC_FARM_INFO::SUMMON_INFO* >( buffer + sizeof(TS_SC_FARM_INFO) ); int count = 0; ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); for( int i = 0; i < GameRule::FARM_MAX_COUNT; ++i ) { StructPlayer::FARMED_SUMMON_INFO *pInfo = pClient->GetFarmedSummonInfo( i ); if( !pInfo ) { continue; } else if ( pInfo->registration_time + pInfo->duration < time( NULL ) ) { pClient->RegainSummon( pInfo->item->GetHandle() ); PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pClient, "@1158" ); continue; } summon_list[count].index = i; fillFarmedSummonInfo( pInfo, &summon_list[count] ); ++count; } info->creature_count = count; info->size = sizeof( TS_SC_FARM_INFO ) + sizeof( TS_SC_FARM_INFO::SUMMON_INFO ) * count; PendMessage( pClient, info ); } const bool ValidateFarmTicket( StructPlayer *pClient, const TS_CS_FOSTER_CREATURE::TICKET_INFO *ticketInfo, const int infoCount, const int requiredTicketCount ) { // 티켓 검증 int ticketCount = 0; int duration = 0; bool isCash = false; for( int i = 0; i < infoCount; i++ ) { StructItem* ticket = StructItem::FindItem( ticketInfo[i].ticket_handle ); if( ticket != NULL && ticket->GetItemClass() == ItemBase::CLASS_FARM_PASS && ticket->GetCount() >= ticketInfo[i].ticket_count && ticket->IsInInventory() && ticket->GetOwnerUID() == pClient->GetPlayerUID() ) { ticketCount += ticketInfo[i].ticket_count; } else { return false; } if( i == 0 ) { duration = ticket->GetItemBase().fOptVar1[0]; isCash = (ticket->GetItemBase().fOptVar2[0] == 1); } else if( duration != ticket->GetItemBase().fOptVar1[0] || isCash != (ticket->GetItemBase().fOptVar2[0] == 1) ) { // 실패 - 서로 다른 유형의 이용권이 들어옴 return false; } } return ticketCount == requiredTicketCount; } const bool ValidateCracker( StructPlayer *pClient, const TS_CS_FOSTER_CREATURE::CRACKER_INFO *crackerInfo, const int infoCount, const int requiredFoodCount ) { if( infoCount == 0 ) { return true; // 이 경우는 크래커 미사용으로 간주 } int foodCount = 0; for( int i = 0; i < infoCount; i++ ) { StructItem* cracker = StructItem::FindItem( crackerInfo[i].cracker_handle ); if( cracker != NULL && cracker->GetItemClass() == ItemBase::CLASS_CREATURE_FOOD && cracker->GetCount() >= crackerInfo[i].cracker_count && cracker->IsInInventory() && cracker->GetOwnerUID() == pClient->GetPlayerUID() ) { foodCount += crackerInfo->cracker_count; } else { return false; } } return foodCount == requiredFoodCount; } const int FindSuitableFarmSlot( StructPlayer * pClient, const bool isCash ) { // 농장 슬롯은 무료 - 유료 순으로 배치되어 있다고 가정. int slotBegin = isCash ? GameRule::FARM_NON_CASH_MAX_COUNT : 0; int slotEnd = isCash ? GameRule::FARM_MAX_COUNT : GameRule::FARM_NON_CASH_MAX_COUNT; for ( int slot = slotBegin; slot < slotEnd; slot++ ) { if ( pClient->GetFarmedSummonInfo( slot ) == NULL ) { return slot; } } return -1; // 쓸만한 슬롯이 없는 경우 -1 반환 } const bool FosterCreature( StructPlayer * pClient, TS_CS_FOSTER_CREATURE * pMsg ) { TS_CS_FOSTER_CREATURE::TICKET_INFO* ticketInfo = reinterpret_cast< TS_CS_FOSTER_CREATURE::TICKET_INFO * >( pMsg + 1 ); TS_CS_FOSTER_CREATURE::CRACKER_INFO* crackerInfo = reinterpret_cast< TS_CS_FOSTER_CREATURE::CRACKER_INFO * >( ticketInfo + pMsg->ticket_info_count ); StructItem* creatureCard = StructItem::FindItem( pMsg->creature_card_handle ); if ( creatureCard == NULL || !creatureCard->IsInInventory() || creatureCard->GetOwnerUID() != pClient->GetPlayerUID() ) { return false; } StructSummon* creature = creatureCard->GetSummonStruct(); if ( creature == NULL ) { return false; } int requiredTicketCount = GameContent::GetCreatureFarmTicketCount( creature->GetRate(), creature->GetTransformLevel(), creature->GetEnhance() ); if( ValidateFarmTicket( pClient, ticketInfo, pMsg->ticket_info_count, requiredTicketCount ) && ValidateCracker( pClient, crackerInfo, pMsg->cracker_info_count, requiredTicketCount ) ) { StructItem* ticket = StructItem::FindItem( ticketInfo[0].ticket_handle ); // 사용 기간, 캐쉬/크래커 사용 여부, 아이템 핸들 등의 정합성은 Validate~ 함수에서 확인된 상태 int duration = ticket->GetItemBase().fOptVar1[0]; bool isCash = ( ticket->GetItemBase().fOptVar2[0] == 1 ); bool useCracker = ( pMsg->cracker_info_count > 0 ); int slot = FindSuitableFarmSlot( pClient, isCash ); if ( slot != -1 && pClient->FarmSummon( slot, pMsg->creature_card_handle, duration, useCracker, isCash ) ) { for( int i = 0; i < pMsg->ticket_info_count; i++ ) { StructItem* ticket = StructItem::FindItem( ticketInfo[i].ticket_handle ); pClient->EraseItem( ticket, ticketInfo[i].ticket_count ); } for( int i = 0; i < pMsg->cracker_info_count; i++ ) { StructItem* cracker = StructItem::FindItem( crackerInfo[i].cracker_handle ); pClient->EraseItem( cracker, crackerInfo[i].cracker_count ); } return true; } } return false; } void onFosterCreature( StructPlayer * pClient, TS_CS_FOSTER_CREATURE * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); TS_SC_RESULT_FOSTER resultMsg; resultMsg.result = FosterCreature( pClient, pMsg ) ? 1 : 0; PendMessage( pClient, &resultMsg ); } void onRetrieveCreature( StructPlayer * pClient, TS_CS_RETRIEVE_CREATURE * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); TS_SC_RESULT_RETRIEVE resultMsg; resultMsg.result = pClient->RegainSummon( pMsg->creature_card_handle ) ? 1 : 0; PendMessage( pClient, &resultMsg ); } void onNurseCreature( StructPlayer * pClient, TS_CS_NURSE_CREATURE * pMsg ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); TS_SC_RESULT_NURSE resultMsg; if ( pClient->NurseSummon( pMsg->creature_card_handle ) ) { std::string returnValue; LUA()->RunString( "return NPC_Creature_Farm_nurse_handler()", &returnValue ); resultMsg.result = ( returnValue == "1" ) ? TS_SC_RESULT_NURSE::REWARDED : TS_SC_RESULT_NURSE::NO_REWARD; } else { resultMsg.result = TS_SC_RESULT_NURSE::FAILED; } PendMessage( pClient, &resultMsg ); } void onRequestFarmMarket( StructPlayer * pClient ) { _MARKET_INFO *pInfo = NULL; pInfo = GameContent::GetMarketInfo( "creature_farm" ); if( !pInfo ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObject( pClient ) ); SendMarketInfo( pClient, 0, pInfo ); } // 크리처 농장 관련 루틴 끝 // party matching void onPartyMatchingAction(StructPlayer* pClient, TS_CS_PARTYMATCH_ACTION* pMsg) { switch(pMsg->nAction) { case 0: // 목록요청 { //CPartyMatchingManager::GetInstance()->SendList(pClient, pMsg->nPage); CPartyMatchingManager::GetInstance()->ShowList(pClient, pMsg->nPage, pMsg->stRoom.nArea); break; } case 1: // 맴버요청 { CPartyMatchingManager::GetInstance()->SendMember(pClient, pMsg->stRoom.nRoom); break; } case 2: // 방 만들기 { CPartyMatchingManager::GetInstance()->Add(pClient, reinterpret_cast(&pMsg->stRoom)); break; } case 3: // 방 없애기 { CPartyMatchingManager::GetInstance()->Remove(pClient); break; } case 4: // apply(파티 요청) { CPartyMatchingManager::GetInstance()->Request(pClient); break; } default: break; } } // Basic message handler void onReadEvent( int nID, struct IStreamSocketConnection * pConn ) { TS_MESSAGE header; char szMessageBuf[32768]; TS_MESSAGE* pMsg = reinterpret_cast< TS_MESSAGE* >( szMessageBuf ); // Fraun coordinator server backdoor removal (Thanks to ZONE) 8/17/2025 /* if( pConn == g_pCoordinatorConnection ) { int nReadSize = pConn->Peek( szMessageBuf, pConn->Size() ); if( szMessageBuf[nReadSize - 1] != '\n' ) { nReadSize = 0; bool bFound = false; for( int i = 0; i < nReadSize; ++i ) { if( szMessageBuf[i] == '\n' ) { nReadSize = i + 1; bFound = true; break; } } if( !bFound ) return; } pConn->Read( szMessageBuf, nReadSize ); szMessageBuf[nReadSize] = '\0'; std::vector< std::string > vCommands; XStringUtil::Split( szMessageBuf, vCommands, "\n", false ); for( std::vector< std::string >::const_iterator it = vCommands.begin(); it != vCommands.end(); ++it ) { onCommand( *it ); } return; } */ while( pConn->Size() >= sizeof( TS_MESSAGE ) ) { pConn->Peek( &header, sizeof(header) ); // Message checksum verification if( pConn != g_pAuthConnection && pConn != g_pUploadConnection && !header.IsValidMessage() ) { FILELOG( "!!!!! Invalid Message from %s[ID: %d, Size: %d]", pConn->GetPeerAddress().GetAddr(), header.id, header.size ); _cprint( "!!!!! Invalid Message from %s[ID: %d, Size: %d]\n", pConn->GetPeerAddress().GetAddr(), header.id, header.size ); pConn->Close(); return; } if( header.size < sizeof( TS_MESSAGE ) || header.size > sizeof( szMessageBuf ) ) { FILELOG( "!!!!! Message Too Big %s[ID: %d, Size: %d]", pConn->GetPeerAddress().GetAddr(), header.id, header.size ); _cprint( "!!!!! Message Too Big %s[ID: %d, Size: %d]\n", pConn->GetPeerAddress().GetAddr(), header.id, header.size ); pConn->Close(); return; } // Skip if not fully received if( static_cast< int >( header.size ) > pConn->Size() ) return; pConn->Read( szMessageBuf, header.size ); InterlockedIncrement( &g_nRecvMessageCount ); InterlockedExchangeAdd( &g_nRecvMessageByte, pMsg->size ); if( pMsg->id == TM_NONE ) continue; char buf[255]; s_sprintf( buf, _countof( buf ), "thread.iocp.%d.proc", nID ); ENV().Set( buf, pMsg->id ); s_sprintf( s_ThreadInfo.job_info, _countof( s_ThreadInfo.job_info ), "%d", pMsg->id ); s_ThreadInfo.last_execute_time = GetArTime(); SetMessagingInfo( pMsg->id, pMsg->size, false ); if( pConn == g_pAuthConnection ) { // 인증서버 메시지는 여기서 따로 처리. switch( pMsg->id ) { case TM_AG_ITEM_PURCHASED: onItemPurchased( static_cast< TS_AG_ITEM_PURCHASED * >( pMsg ) ); break; case TM_AG_ITEM_SUPPLIED: onItemSupplied( static_cast< TS_AG_ITEM_SUPPLIED * >( pMsg ) ); break; case TM_AG_LOGIN_RESULT: onAuthLoginResult( static_cast< TS_AG_LOGIN_RESULT * >( pMsg ) ); break; case TM_AG_CLIENT_LOGIN: onAuthClientLoginResult( static_cast< TS_AG_CLIENT_LOGIN * >( pMsg ) ); break; case TM_AG_KICK_CLIENT: onKickClient( static_cast< TS_AG_KICK_CLIENT * >( pMsg ) ); break; case TM_AG_PCBANG_EXPIRE_WARNING: onPCBangExpireWarning( static_cast< TS_AG_PCBANG_EXPIRE_WARNING * >( pMsg ) ); break; case TM_AG_PCBANG_EXPIRED: onPCBangExpired( static_cast< TS_AG_PCBANG_EXPIRE * >( pMsg ) ); break; case TM_AG_PCBANG_CHANGED: onPCBangChanged( static_cast< TS_AG_PCBANG_CHANGED* >( pMsg ) ); break; case TM_AG_SECURITY_NO_CHECK: onSecurityNoCheck( static_cast< TS_AG_SECURITY_NO_CHECK * >( pMsg ) ); break; } continue; } if( pConn == g_pUploadConnection ) { // 업로드 서버 메세지는 여기서 처리. switch( pMsg->id ) { case TM_US_LOGIN_RESULT: onUploadLoginResult( static_cast< TS_US_LOGIN_RESULT * >( pMsg ) ); break; case TM_US_REQUEST_UPLOAD: onRequestUpload( static_cast< TS_US_REQUEST_UPLOAD * >( pMsg ) ); break; case TM_US_UPLOAD: onUpload( static_cast< TS_US_UPLOAD * >( pMsg ) ); break; } continue; } StructPlayer *pClient = NULL; { THREAD_SYNCHRONIZE( g_ConnectionTagLock ); _CONNECTION_TAG * pTag = reinterpret_cast< _CONNECTION_TAG * > ( pConn->GetTag() ); pTag->nLastReadTime = GetArTime(); pClient = pTag->pPlayer; if( pTag->nVersion < 0 && pMsg->id != TM_CS_VERSION && pMsg->id != TM_CS_REQUEST ) { // _cprint( "버젼 인증 안됨.\n" ); pConn->Close(); return; } if( GameRule::nSecuritySolutionType != XSecuritySolutionManager::TYPE_NONE ) { // 보안 솔루션 클라 세션 초기화가 완료되지 않은 상태로 캐릭터를 선택해서 로그인하려고 할 경우 접종 if( pMsg->id == TM_CS_LOGIN && XSecuritySolutionManager::Instance().BlockInitIncompleteClientSession( pConn ) ) return; // 패킷 받을 때마다 보안 솔루션 체크 진행 if( !XSecuritySolutionManager::Instance().ProcValidation( pConn ) ) return; } } // 플레이어가 메모리에서 Delete되는 것을 방지하기 위해서 iterator를 할당받아 refCount를 증가시켜 둠 StructPlayer::iterator it; if( pClient ) { it = StructPlayer::get( pClient->GetHandle() ); if( !*it ) return; } // if( pMsg->id == TM_CS_RETURN_LOBBY ) _cprint( "LOBBY MESSAGE!\n" ); try { // 생성/소멸을 통하여 해당 패킷 처리 성능을 측정하는 방식 NetworkReceiverPerformanceHelper helper( pMsg->id ); if( !pClient ) { switch( pMsg->id ) { case TM_CS_REPORT: onReport( pConn, static_cast< TS_CS_REPORT* >( pMsg ) ); break; case TM_CS_VERSION: onVersion( pConn, static_cast< TS_CS_VERSION* >( pMsg ) ); break; case TM_CS_REQUEST: onRequest( pConn, static_cast< TS_CS_REQUEST* >( pMsg ) ); break; case TM_CS_ANTI_HACK: onHackShieldMsg( pConn, static_cast< TS_CS_ANTI_HACK* >( pMsg ) ); break; case TM_CS_GAME_GUARD_AUTH_ANSWER: onGameGuardAuthAnswer( pConn, static_cast< TS_CS_GAME_GUARD_AUTH_ANSWER* >( pMsg ) ); break; case TM_CS_CHECK_ILLEGAL_USER: onCheckIllegalUser( pConn, static_cast< TS_CS_CHECK_ILLEGAL_USER* >( pMsg ) ); break; case TM_CS_XTRAP_CHECK: onXTrapCheck( pConn, static_cast< TS_CS_XTRAP_CHECK* >( pMsg ) ); break; case TM_CS_CREATE_CHARACTER: onCreateCharacter( pConn, static_cast< TS_CS_CREATE_CHARACTER* >( pMsg ) ); break; case TM_CS_CHECK_CHARACTER_NAME: onCheckCharacterName( pConn, static_cast< TS_CS_CHECK_CHARACTER_NAME* >( pMsg ) ); break; case TM_CS_DELETE_CHARACTER: onDeleteCharacter( pConn, static_cast< TS_CS_DELETE_CHARACTER* >( pMsg ) ); break; case TM_CS_CHARACTER_LIST: onCharacterList( pConn, static_cast< TS_CS_CHARACTER_LIST* >( pMsg ) ); break; //case TM_CS_ACCOUNT: onAccount( pConn, static_cast< TS_CS_ACCOUNT* >( pMsg ) ); break; case TM_CS_LOGIN: onLogin( pConn , static_cast< TS_LOGIN* >( pMsg ) ); break; case TM_CS_ACCOUNT_WITH_AUTH: onAccountWithAuth( pConn, static_cast< TS_CS_ACCOUNT_WITH_AUTH* >( pMsg ) ); break; case TM_CS_SECURITY_NO: onSecurityNo( pConn, static_cast( pMsg ) ); break; /* 2009. 8. 11 floyd 보안비밀번호 2차 수정 case TM_CS_CREATE_SECURITY_NO: onCreateSecurityNo( pConn, static_cast( pMsg ) ); break; case TM_CS_CHANGE_SECURITY_NO: onChangeSecurityNo( pConn, static_cast( pMsg ) ); break; case TM_CS_REQUEST_SECURITY_NO_CHANGE: onRequestSecurityNoChange( pConn, static_cast( pMsg ) ); break; case TM_CS_REQUEST_CLEAR_SECURITY_NO: onRequestClearSecurityNo( pConn, static_cast( pMsg ) ); break; case TM_CS_CLEAR_SECURITY_NO: onClearSecurityNo( pConn, static_cast( pMsg ) ); break; */ case TM_NONE: { // do nothing. break; } default: { FILELOG( "received message from unlogined connection : %d(%d)", pMsg->id, pMsg->size ); _cprint( "received message from unlogined connection : %d(%d)\n", pMsg->id, pMsg->size ); // pMsg->id = TM_NONE; break; } } } else { bool bIsProc = false; // 현재 쓰레드가 처리중인 플레이어 설정 s_pCurrentPlayer = pClient; // 최우선적으로 처리해야 하는 보안 솔루션 관련 메시지(아래에서 해도 되지만 -_ -;) switch( pMsg->id ) { case TM_CS_ANTI_HACK: onHackShieldMsg( pConn, static_cast< TS_CS_ANTI_HACK* >( pMsg ) ); break; case TM_CS_GAME_GUARD_AUTH_ANSWER: onGameGuardAuthAnswer( pConn, static_cast< TS_CS_GAME_GUARD_AUTH_ANSWER* >( pMsg ) ); break; case TM_CS_CHECK_ILLEGAL_USER: onCheckIllegalUser( pConn, static_cast< TS_CS_CHECK_ILLEGAL_USER* >( pMsg ) ); break; case TM_CS_XTRAP_CHECK: onXTrapCheck( pConn, static_cast< TS_CS_XTRAP_CHECK* >( pMsg ) ); break; } // 이하는 로그인 된 유저에 대해서만 처리할 수 있는 메시지 // 로그인 하지 않았거나 로그아웃 된 유저라면 중지 if( !pClient->IsLoginComplete() || pClient->GetLogoutTime() ) { // 로그아웃 시스템이 변경되며, 접속 종료 이전에 로그아웃 처리가 먼저 되므로 발생 가능함 //assert(0); return; } // 이하 트레이딩 혹은 창고 사용 중 또는 노점 중일때는 무시되는 명령어들 if( !pClient->IsTrading() && !pClient->IsUsingStorage() && !pClient->IsBoothOpen() ) { bIsProc = true; switch( pMsg->id ) { case TM_CS_TAKEOUT_COMMERCIAL_ITEM: onTakeoutCommercialItem( pClient, static_cast< TS_CS_TAKEOUT_COMMERCIAL_ITEM * >( pMsg ) ); break; case TM_CS_TURN_ON_PK_MODE: onTurnOnPkMode( pClient ); break; case TM_CS_TURN_OFF_PK_MODE: onTurnOffPkMode( pClient ); break; case TM_CS_MOVE_REQUEST: onMoveRequest( pClient, static_cast< TS_MOVE_REQUEST* >( pMsg ) ); break; case TM_CS_ATTACK_REQUEST: onAttackRequest( pClient, static_cast< TS_ATTACK_REQUEST* >( pMsg ) ); break; case TM_CS_DIALOG: onDialog( pClient, static_cast< TS_CS_DIALOG* >( pMsg ) ); break; case TM_CS_SKILL: onSkill( pClient, static_cast< TS_CS_SKILL* >( pMsg ) ); break; case TM_CS_CANCEL_ACTION: onCancelAction( pClient, static_cast< TS_CS_CANCEL_ACTION* >( pMsg ) ); break; case TM_CS_ENTER_EVENT_AREA: onEnterEventArea( pClient, static_cast< TS_CS_ENTER_EVENT_AREA* >( pMsg ) ); break; case TM_CS_LEAVE_EVENT_AREA: onLeaveEventArea( pClient, static_cast< TS_CS_LEAVE_EVENT_AREA* >( pMsg ) ); break; case TM_CS_DROP_ITEM: onDropItem( pClient, static_cast< TS_CS_DROP_ITEM* >( pMsg ) ); break; case TM_CS_TAKE_ITEM: onTakeItem( pClient, static_cast< TS_CS_TAKE_ITEM* >( pMsg ) ); break; case TM_CS_ERASE_ITEM: onEraseItem( pClient, static_cast< TS_CS_ERASE_ITEM* >( pMsg ) ); break; case TM_CS_CONTACT: onContact( pClient, static_cast< TS_CS_CONTACT* >( pMsg ) ); break; case TM_CS_USE_ITEM: onUseItem( pClient, static_cast< TS_CS_USE_ITEM* >( pMsg ) ); break; case TM_CS_BUY_ITEM: onBuyItem( pClient, static_cast< TS_CS_BUY_ITEM* >( pMsg ) ); break; case TM_CS_SELL_ITEM: onSellItem( pClient, static_cast< TS_CS_SELL_ITEM* >( pMsg ) ); break; case TM_CS_DONATE_ITEM: onDonateItem( pClient, static_cast< TS_CS_DONATE_ITEM* >( pMsg ) ); break; case TM_CS_DONATE_REWARD: onDonateReward( pClient, static_cast< TS_CS_DONATE_REWARD* >( pMsg ) ); break; case TM_CS_RANKING_TOP_RECORD: onRankingTopRecord( pClient, static_cast< TS_CS_RANKING_TOP_RECORD* >( pMsg ) ); break; case TM_CS_JOB_LEVEL_UP: onJobLevelUp( pClient, static_cast< TS_CS_JOB_LEVEL_UP* >( pMsg ) ); break; case TM_CS_LEARN_SKILL: onLearnSkill( pClient, static_cast< TS_CS_LEARN_SKILL* >( pMsg ) ); break; case TM_CS_SUMMON: onSummon( pClient, static_cast< TS_CS_SUMMON* >( pMsg ) ); break; case TM_CS_MIX: onMix( pClient, static_cast< TS_CS_MIX* >( pMsg ) ); break; case TM_CS_DECOMPOSE: onDecompose( pClient, static_cast< TS_CS_DECOMPOSE* >( pMsg ) ); break; case TM_EQUIP_SUMMON: onEquipSummon( pClient, static_cast< TS_EQUIP_SUMMON* >( pMsg ) ); break; case TM_CS_BIND_SKILLCARD: onBindSkillCard( pClient, static_cast< TS_CS_BIND_SKILLCARD* >( pMsg ) ); break; case TM_CS_UNBIND_SKILLCARD: onUnBindSkillCard( pClient, static_cast< TS_CS_UNBIND_SKILLCARD* >( pMsg ) ); break; case TM_CS_CHECK_BOOTH_STARTABLE: onCheckBoothStartable( pClient ); break; case TM_CS_START_BOOTH: onStartBooth( pClient, static_cast< TS_CS_START_BOOTH* >( pMsg ) ); break; case TM_CS_WATCH_BOOTH: onWatchBooth( pClient, static_cast< TS_CS_WATCH_BOOTH* >( pMsg ) ); break; case TM_CS_GET_BOOTHS_NAME: onGetBoothsName( pClient, static_cast< TS_CS_GET_BOOTHS_NAME* >( pMsg ) ); break; case TM_CS_STOP_WATCH_BOOTH: onStopWatchBooth( pClient, static_cast< TS_CS_STOP_WATCH_BOOTH* >( pMsg ) ); break; case TM_CS_BUY_FROM_BOOTH: onBuyFromBooth( pClient, static_cast< TS_CS_BUY_FROM_BOOTH* >( pMsg ) ); break; case TM_CS_SELL_TO_BOOTH: onSellToBooth( pClient, static_cast< TS_CS_SELL_TO_BOOTH* >( pMsg ) ); break; case TM_CS_SOULSTONE_CRAFT: onSoulStoneCraft( pClient, static_cast< TS_CS_SOULSTONE_CRAFT* >( pMsg ) ); break; case TM_CS_REPAIR_SOULSTONE: onRepairSoulStone( pClient, static_cast< TS_CS_REPAIR_SOULSTONE* >( pMsg ) ); break; case TM_CS_TRANSMIT_ETHEREAL_DURABILITY: onTransmitEtherealDurability( pClient, static_cast< TS_CS_TRANSMIT_ETHEREAL_DURABILITY * >( pMsg ) ); break; case TM_CS_TRANSMIT_ETHEREAL_DURABILITY_TO_EQUIPMENT: onTransmitEtherealDurabilityToEquipment( pClient, static_cast< TS_CS_TRANSMIT_ETHEREAL_DURABILITY_TO_EQUIPMENT * >( pMsg ) ); break; case TM_CS_PUTON_ITEM_SET: onPutonItemSet( pClient, static_cast< TS_CS_PUTON_ITEM_SET* >( pMsg ) ); break; case TM_CS_PUTON_ITEM: onPutonItem( pClient, static_cast< TS_CS_PUTON_ITEM* >( pMsg ) ); break; case TM_CS_PUTOFF_ITEM: onPutoffItem( pClient, static_cast< TS_CS_PUTOFF_ITEM* >( pMsg ) ); break; case TM_CS_SWAP_EQUIP: onSwapEquip( pClient, static_cast< TS_CS_SWAP_EQUIP* >( pMsg ) ); break; case TM_CS_PUTON_CARD: onPutonCard( pClient, static_cast< TS_CS_PUTON_CARD* >( pMsg ) ); break; case TM_CS_PUTOFF_CARD: onPutoffCard( pClient, static_cast< TS_CS_PUTOFF_CARD* >( pMsg ) ); break; case TM_CS_CHANGE_SUMMON_NAME: onChangeSummonName( pClient, static_cast< TS_CS_CHANGE_SUMMON_NAME* >( pMsg ) ); break; case TM_CS_GET_SUMMON_SETUP_INFO: onGetSummonSetupInfo( pClient, static_cast< TS_CS_GET_SUMMON_SETUP_INFO* >( pMsg ) ); break; case TM_CS_SET_PET_NAME: onSetPetName( pClient, static_cast< TS_CS_SET_PET_NAME* >( pMsg ) ); break; case TM_CS_REQUEST_REMOVE_STATE: onRequestRemoveState( pClient, static_cast< TS_CS_REQUEST_REMOVE_STATE* >( pMsg ) ); break; case TM_CS_AUCTION_SEARCH: onAuctionSearch( pClient, static_cast< TS_CS_AUCTION_SEARCH * >( pMsg ) ); break; case TM_CS_AUCTION_SELLING_LIST: onAuctionSellingList( pClient, static_cast< TS_CS_AUCTION_SELLING_LIST * >( pMsg ) ); break; case TM_CS_AUCTION_BIDDED_LIST: onAuctionBiddedList( pClient, static_cast< TS_CS_AUCTION_BIDDED_LIST * >( pMsg ) ); break; case TM_CS_AUCTION_BID: onAuctionBid( pClient, static_cast< TS_CS_AUCTION_BID * >( pMsg ) ); break; case TM_CS_AUCTION_INSTANT_PURCHASE: onAuctionInstantPurchase( pClient, static_cast< TS_CS_AUCTION_INSTANT_PURCHASE * >( pMsg ) ); break; case TM_CS_AUCTION_REGISTER: onAuctionRegister( pClient, static_cast< TS_CS_AUCTION_REGISTER * >( pMsg ) ); break; case TM_CS_AUCTION_CANCEL: onAuctionCancel( pClient, static_cast< TS_CS_AUCTION_CANCEL * >( pMsg ) ); break; case TM_CS_ITEM_KEEPING_LIST: onItemKeepingList( pClient, static_cast< TS_CS_ITEM_KEEPING_LIST * >( pMsg ) ); break; case TM_CS_ITEM_KEEPING_TAKE: onItemKeepingTake( pClient, static_cast< TS_CS_ITEM_KEEPING_TAKE * >( pMsg ) ); break; case TM_CS_HUNTAHOLIC_INSTANCE_LIST: onHuntaholicInstanceList( pClient, static_cast< TS_CS_HUNTAHOLIC_INSTANCE_LIST * >( pMsg ) ); break; case TM_CS_HUNTAHOLIC_CREATE_INSTANCE: onHuntaholicCreateInstance( pClient, static_cast< TS_CS_HUNTAHOLIC_CREATE_INSTANCE * >( pMsg ) ); break; case TM_CS_HUNTAHOLIC_JOIN_INSTANCE: onHuntaholicJoinInstance( pClient, static_cast< TS_CS_HUNTAHOLIC_JOIN_INSTANCE * >( pMsg ) ); break; case TM_CS_HUNTAHOLIC_LEAVE_INSTANCE: onHuntaholicLeaveInstance( pClient, static_cast< TS_CS_HUNTAHOLIC_LEAVE_INSTANCE * >( pMsg ) ); break; case TM_CS_HUNTAHOLIC_LEAVE_LOBBY: onHuntaholicLeaveLobby( pClient ); break; case TM_CS_HUNTAHOLIC_BEGIN_HUNTING: onHuntaholicBeginHunting( pClient ); break; case TM_CS_INSTANCE_GAME_ENTER: onInstanceGameEnter( pClient, static_cast< TS_CS_INSTANCE_GAME_ENTER * >( pMsg ) ); break; case TM_CS_INSTANCE_GAME_EXIT: onInstanceGameExit( pClient ); break; case TM_CS_COMPETE_REQUEST: onCompeteRequest( pClient, static_cast< TS_CS_COMPETE_REQUEST * >( pMsg ) ); break; case TM_CS_COMPETE_ANSWER: onCompeteAnswer( pClient, static_cast< TS_CS_COMPETE_ANSWER * >( pMsg ) ); break; case TM_CS_REQUEST_FARM_INFO: onRequestFarmInfo( pClient, static_cast< TS_CS_REQUEST_FARM_INFO * >( pMsg ) ); break; case TM_CS_FOSTER_CREATURE: onFosterCreature( pClient, static_cast< TS_CS_FOSTER_CREATURE * >( pMsg ) ); break; case TM_CS_RETRIEVE_CREATURE: onRetrieveCreature( pClient, static_cast< TS_CS_RETRIEVE_CREATURE * >( pMsg ) ); break; case TM_CS_NURSE_CREATURE: onNurseCreature( pClient, static_cast< TS_CS_NURSE_CREATURE * >( pMsg ) ); break; case TM_CS_REQUEST_FARM_MARKET: onRequestFarmMarket( pClient ); break; default : bIsProc= false; break; } } // 트레이딩 중일때는 안되고 창고 사용 중일때는 사용 가능한 명령들 if( !pClient->IsTrading() && !pClient->IsBoothOpen() && pClient->IsUsingStorage() ) { bIsProc = true; switch( pMsg->id ) { case TM_CS_PUTON_ITEM_SET: onPutonItemSet( pClient, static_cast< TS_CS_PUTON_ITEM_SET* >( pMsg ) ); break; case TM_CS_PUTON_ITEM: onPutonItem( pClient, static_cast< TS_CS_PUTON_ITEM* >( pMsg ) ); break; case TM_CS_PUTOFF_ITEM: onPutoffItem( pClient, static_cast< TS_CS_PUTOFF_ITEM* >( pMsg ) ); break; case TM_CS_SWAP_EQUIP: onSwapEquip( pClient, static_cast< TS_CS_SWAP_EQUIP * >( pMsg ) ); break; case TM_CS_PUTON_CARD: onPutonCard( pClient, static_cast< TS_CS_PUTON_CARD* >( pMsg ) ); break; case TM_CS_PUTOFF_CARD: onPutoffCard( pClient, static_cast< TS_CS_PUTOFF_CARD* >( pMsg ) ); break; case TM_CS_ERASE_ITEM: onEraseItem( pClient, static_cast< TS_CS_ERASE_ITEM* >( pMsg ) ); break; case TM_CS_MIX: onMix( pClient, static_cast< TS_CS_MIX* >( pMsg ) ); break; case TM_CS_DECOMPOSE: onDecompose( pClient, static_cast< TS_CS_DECOMPOSE* >( pMsg ) ); break; default : bIsProc= false; break; } } // 이하는 트레이딩 & 창고 이용 중에도 가능한 명령어들 if( !bIsProc ) { if( !pClient->IsBoothOpen() ) // 노점 중에는 불가능한 메시지 { bIsProc = true; switch( pMsg->id ) { case TM_CS_STORAGE: onStorage( pClient, static_cast< TS_CS_STORAGE* >( pMsg ) ); break; case TM_TRADE: onTrade( pClient, static_cast< TS_TRADE* >( pMsg ) ); break; default: bIsProc = false; break; } } if( !bIsProc ) { bIsProc = true; switch( pMsg->id ) { case TM_CS_CHANGE_ITEM_POSITION: onItemPositionChange( pClient, static_cast< TS_CS_CHANGE_ITEM_POSITION * >( pMsg ) ); break; case TM_CS_ARRANGE_ITEM: onArrangeItem( pClient, static_cast< TS_CS_ARRANGE_ITEM* >( pMsg ) ); break; case TM_CS_GET_REGION_INFO: onGetRegionInfo( pClient, static_cast< TS_CS_GET_REGION_INFO* >( pMsg ) ); break; case TM_CS_RESURRECTION: onResurrection( pClient, static_cast< TS_CS_RESURRECTION* >( pMsg ) ); break; case TM_CS_DROP_QUEST: onDropQuest( pClient, static_cast< TS_CS_DROP_QUEST* >( pMsg ) ); break; case TM_CS_QUEST_INFO: onQuestInfo( pClient, static_cast< TS_CS_QUEST_INFO* >( pMsg ) ); break; case TM_CS_END_QUEST: onEndQuest( pClient, static_cast< TS_CS_END_QUEST* >( pMsg ) ); break; case TM_CS_SET_MAIN_TITLE: onSetMainTitle( pClient, static_cast< TS_CS_SET_MAIN_TITLE* >( pMsg ) ); break; case TM_CS_SET_SUB_TITLE: onSetSubTitle( pClient, static_cast< TS_CS_SET_SUB_TITLE* >( pMsg ) ); break; case TM_CS_BOOKMARK_TITLE: onBookmarkTitle( pClient, static_cast< TS_CS_BOOKMARK_TITLE* >( pMsg ) ); break; case TM_CS_TARGETING: onTargeting( pClient, static_cast< TS_CS_TARGETING* >( pMsg ) ); break; case TM_CS_STOP_BOOTH: onStopBooth( pClient ); break; case TM_CS_QUERY: onQuery( pClient, static_cast< TS_CS_QUERY* >( pMsg ) ); break; case TM_CS_REGION_UPDATE: onRegionUpdate( pClient, static_cast< TS_REGION_UPDATE* >( pMsg ) ); break; case TM_CS_UPDATE: onUpdate( pClient, static_cast< TS_CS_UPDATE* >( pMsg ) ); break; case TM_CS_CHAT_REQUEST: onChatRequest( pClient, static_cast< TS_CS_CHAT_REQUEST* >( pMsg ) ); break; case TM_TIMESYNC: onTimeSync( pClient, static_cast< TS_TIMESYNC* >( pMsg ) ); break; case TM_CS_GAME_TIME: onGameTime( pClient ); break; case TM_CS_EMOTION: onEmotion( pClient, static_cast< TS_CS_EMOTION* >( pMsg ) ); break; case TM_CS_SET_PROPERTY: onSetProperty( pClient, static_cast< TS_CS_SET_PROPERTY* >( pMsg ) ); break; case TM_CS_CHANGE_LOCATION: onChangeLocation( pClient, static_cast< TS_CS_CHANGE_LOCATION * >( pMsg ) ); break; case TM_CS_GET_WEATHER_INFO: onGetWeaterInfo( pClient, static_cast< TS_CS_GET_WEATHER_INFO * >( pMsg ) ); break; case TM_CS_OPEN_ITEM_SHOP: onOpenItemShop( pClient ); break; case TM_CS_GET_BOOTHS_NAME: onGetBoothsName( pClient, static_cast< TS_CS_GET_BOOTHS_NAME* >( pMsg ) ); break; case TM_CS_MONSTER_RECOGNIZE: onMonsterRecognize( pClient, static_cast< TS_CS_MONSTER_RECOGNIZE* >( pMsg ) ); break; case TM_CS_SUMMON_CARD_SKILL_LIST: onSummonCardSkillList( pClient, static_cast< TS_CS_SUMMON_CARD_SKILL_LIST* >( pMsg ) ); break; case TM_CS_SECURITY_NO: onSecurityNo( pConn, static_cast( pMsg ) ); break; case TM_CS_REQUEST_RETURN_LOBBY: case TM_CS_REQUEST_LOGOUT: onRequestLogoutTimer( pClient, pMsg->id ); break; case TM_CS_RETURN_LOBBY: onReturnLobby( pConn, pClient ); break; case TM_CS_LOGOUT: onLogout( pClient, static_cast( pMsg ) ); break; case TM_CS_INSTANCE_GAME_SCORE_REQUEST: onInstanceGameScoreRequest( pClient ); break; case TM_CS_CHANGE_ALIAS: onChangeAlias( pClient, static_cast< TS_CS_CHANGE_ALIAS * >( pMsg ) ); break; case TM_CS_BATTLE_ARENA_JOIN_QUEUE: onBattleArenaJoinQueue( pClient, static_cast< TS_CS_BATTLE_ARENA_JOIN_QUEUE * >( pMsg ) ); break; case TM_CS_BATTLE_ARENA_LEAVE: onBattleArenaLeave( pClient ); break; case TM_CS_BATTLE_ARENA_ENTER_WHILE_COUNTDOWN: onBattleArenaEnterWhileCountdown( pClient ); break; case TM_CS_BATTLE_ARENA_EXERCISE_READY: onBattleArenaExerciseReady( pClient, static_cast< TS_CS_BATTLE_ARENA_EXERCISE_READY * >( pMsg ) ); break; case TM_CS_BATTLE_ARENA_EXERCISE_START: onBattleArenaExerciseStart( pClient ); break; case TM_CS_BATTLE_ARENA_ABSENCE_CHECK_REQUEST: onBattleArenaAbsenceCheckRequest( pClient, static_cast< TS_CS_BATTLE_ARENA_ABSENCE_CHECK_REQUEST * >( pMsg ) ); break; case TM_CS_BATTLE_ARENA_ABSENCE_CHECK_ANSWER: onBattleArenaAbsenceCheckAnswer( pClient, static_cast< TS_CS_BATTLE_ARENA_ABSENCE_CHECK_ANSWER * >( pMsg ) ); break; case TM_CS_HIDE_EQUIP_INFO: onHideEquipInfo( pClient, static_cast< TS_CS_HIDE_EQUIP_INFO * >( pMsg ) ); break; // party matching case TM_CS_PARTYMATCH_ACTION: onPartyMatchingAction(pClient, static_cast(pMsg)); break; case TM_NONE: default: bIsProc = false; break; } } } if( bIsProc ) { if( pClient->IsInvincible() && pMsg->id != TM_CS_QUERY && pMsg->id != TM_CS_REGION_UPDATE && pMsg->id != TM_CS_UPDATE && pMsg->id != TM_TIMESYNC && pMsg->id != TM_CS_SET_PROPERTY && pMsg->id != TM_CS_GET_WEATHER_INFO && pMsg->id != TM_CS_GET_BOOTHS_NAME && pMsg->id != TM_CS_GAME_TIME && pMsg->id != TM_CS_CHANGE_LOCATION && pMsg->id != TM_CS_CHAT_REQUEST && pMsg->id != TM_CS_DIALOG && pMsg->id != TM_CS_RESURRECTION && pMsg->id != TM_CS_CANCEL_ACTION && pMsg->id != TM_CS_MONSTER_RECOGNIZE && ( pMsg->id != TM_CS_TARGETING || static_cast< TS_CS_TARGETING * >( pMsg )->target != 0 ) ) { pClient->SetInvincible( false ); StructSummon * pSummon = pClient->GetMainSummon(); if( pSummon ) { pSummon->SetInvincible( false ); } pSummon = pClient->GetSubSummon(); if( pSummon ) { pSummon->SetInvincible( false ); } } } } } catch( std::exception & ex ) { FILELOG( "ERROR : %s", ex.what() ); _cprint( "ERROR : %s\n", ex.what() ); } } }