#include "StdAfx.h" #include "DnPlayerActor.h" #include "EtMatrixEx.h" #include "DnWorld.h" #include "MAMovementBase.h" #include "DnWeapon.h" #include "DnProjectile.h" #include "DnGameTask.h" #include "TaskManager.h" #include "DnPartyTask.h" #include "DnSkill.h" #include "PerfCheck.h" #include "DnDropItem.h" #include "GameSendPacket.h" #include "DnMonsterActor.h" #include "DnStateBlow.h" #include "MAAiScript.h" #include "DnBlow.h" #include "DnItemTask.h" #include "DNLogConnection.h" #include "DNGameDataManager.h" #include "ScoreSystem.h" #include "DNMissionSystem.h" #include "DNDBConnectionManager.h" #include "DnPlayerSpeedHackChecker.h" #include "DnPlayerDoNotEnterChecker.h" #include "DnPlayerPickupChecker.h" #include "DnPlayerSkillChecker.h" #include "DnPlayerActionChecker.h" #include "DnPlayAniProcess.h" #include "DNPvPPlayerAggroSystem.h" #include "DNMonsterAggroSystem.h" #include "DnChangeActionSetBlow.h" #include "DnChangeActionStrProcessor.h" #include "DNDBConnection.h" #include "DnChangeStandActionBlow.h" #include "DnActionSpecificInfo.h" #include "MasterRewardSystem.h" #if defined( PRE_ADD_SECONDARY_SKILL ) #include "SecondarySkillRepositoryServer.h" #endif // #if defined( PRE_ADD_SECONDARY_SKILL ) #include "DnCannonMonsterActor.h" #include "DNPvPGameRoom.h" #include "DnApplySEWhenTargetNormalHitProcessor.h" #include "DnBubbleSystem.h" #include "DnObserverEventMessage.h" #include "DnBlockBlow.h" #include "DnParryBlow.h" #include "DnCooltimeParryBlow.h" #include "PvPZombieMode.h" #include "DNGameServerScriptAPI.h" #include "DnOrderMySummonedMonsterBlow.h" #include "IDnSkillUsableChecker.h" #include "DnTransformBlow.h" #include "DnBasicBlow.h" #include "DnCreateBlow.h" #ifdef PRE_ADD_EXPORT_DPS_INFORMATION #include "DnDPSReporter.h" #endif #if defined(PRE_ADD_SKILL_LEVELUP_LIMIT_BY_SP) #include "DnSkillTask.h" #endif // PRE_ADD_SKILL_LEVELUP_LIMIT_BY_SP #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) #include "TotalLevelSkillSystem.h" #endif // PRE_ADD_TOTAL_LEVEL_SKILL #ifdef _DEBUG #define new new(_NORMAL_BLOCK,__FILE__,__LINE__) #endif float CDnPlayerActor::s_fRecoverySPTime = 5.f; CDnPlayerActor::CDnPlayerActor( CMultiRoom *pRoom, int nClassID ) : CDnActor( pRoom, nClassID ) { m_pSession = NULL; CDnPlayerState::Initialize( m_nClassID ); CDnActionBase::Initialize( this ); MACP::Initialize( this ); m_cMovePushKeyFlag = 0; m_bBattleMode = true; m_nWorldLevel = 0; m_nComboDelay = 0; m_nComboCount = 0; m_nTotalComboCount = 0; m_fRecoverySPDelta = 0.f; m_bCompleteCutScene = false; m_bCheckCompleteCutScene = false; m_bSkipCutScene = false; m_bGhost = false; m_uiStateBlowProcessAfterBit = 0; m_afLastEquipItemSkillDelayTime = 0.0f; m_afLastEquipItemSkillRemainTime = 0.0f; m_dwSyncDatumTick = 0; m_dwSyncDatumSendTick = 0; m_nVoiceChannelID = 0; m_pPlayerSpeedHackChecker = new CDnPlayerSpeedHackChecker( this ); m_pPlayerDoNotEnterChecker = new CDnPlayerDoNotEnterChecker( this ); m_pPlayerPickupChecker = new CDnPlayerPickupChecker( this ); m_pPlayerSkillChecker = new CDnPlayerSkillChecker( this ); m_pPlayerActionChecker = new CDnPlayerActionChecker( this ); memset( m_bCashSelfDeleteWeapon, 0, sizeof(m_bCashSelfDeleteWeapon) ); memset( m_bWeaponViewOrder, 0, sizeof(m_bWeaponViewOrder) ); m_iNowMaxProjectileCount = 0; m_iReservedProjectileCount = 0; m_LastEscapeTime = 0; m_dwLastBasicShootActionTime = 0; m_dwLastBasicShootCoolTime = 0; m_dwLastChargeShootTime = 0; m_bCheckProjectileSignalTerm = true; m_bUpdatedProjectileInfoFromCmdAction = false; m_fFrameSpeed = 1.0f; m_bPlayerCannonMode = false; m_nSwapSingleSkinActorID = -1; m_nMonsterMutationTableID = -1; m_hSwapOriginalHandle.Identity(); m_pSwapOriginalAction = NULL; m_nDeathCount = 0; m_pBubbleSystem = new BubbleSystem::CDnBubbleSystem; this->RegisterObserver( m_pBubbleSystem ); m_bTempSkillAdded = false; m_iTempChangedJob = 0; m_nInvalidPlayerCheckCounter = 0; m_bForceEnableRideByTrigger = true; m_nVehicleEffectIndex = 0; m_bVehicleMode = false; m_vecEventEffectList.clear(); for( int i=0; i( m_hCannonMonsterActor.GetPointer() )->OnMasterPlayerActorDie(); } //문장 스킬효과 리셋 DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TGLYPHSKILL ); if( pSox ) { for( int itr = 0; itr < CDnGlyph::GlyphSlotEnum_Amount; ++itr ) { if( m_hGlyph[itr] ) { if( CDnGlyph::PassiveSkill == pSox->GetFieldFromLablePtr( m_hGlyph[itr]->GetClassID(), "_GlyphType" )->GetInteger() ) { int nSkillID = pSox->GetFieldFromLablePtr( m_hGlyph[itr]->GetClassID(), "_SkillID" )->GetInteger(); DnSkillHandle hSkill = FindSkill( nSkillID ); if( hSkill ) hSkill->DelGlyphStateEffect( m_hGlyph[itr]->GetClassID() ); } } } } // 오라 스킬 정리. 다른 버프류와는 다르게 오라는 시전했던 캐릭터가 죽으면 다른 캐릭터에게 적용되던 것들이 풀려야 한다. // 오라스킬, 토글스킬이 켜져 있다면 꺼준다. if( IsEnabledToggleSkill() ) OnSkillToggle( m_hToggleSkill, false ); if( IsEnabledAuraSkill() ) OnSkillAura( m_hAuraSkill, false ); for( int i=0; i<2; i++ ) SAFE_RELEASE_SPTR( m_hCashWeapon[i] ); SAFE_RELEASE_SPTR( m_hSwapOriginalHandle ); SAFE_DELETE( m_pSwapOriginalAction ); SAFE_DELETE( m_pPlayerSkillChecker ); SAFE_DELETE( m_pPlayerActionChecker ); SAFE_DELETE( m_pPlayerDoNotEnterChecker ) ; SAFE_DELETE( m_pPlayerPickupChecker ); SAFE_DELETE( m_pPlayerSpeedHackChecker ); SAFE_DELETE( m_pBubbleSystem ); #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) SAFE_DELETE(m_pTotalLevelSkillSystem); #endif // PRE_ADD_TOTAL_LEVEL_SKILL } MAMovementBase* CDnPlayerActor::CreateMovement() { MAMovementBase* pMovement = new IBoostPoolMAWalkMovement(); return pMovement; } bool CDnPlayerActor::Initialize() { bool bResult = CDnActor::Initialize(); m_hObject->SetCollisionGroup( COLLISION_GROUP_DYNAMIC( 1 ) ); m_hObject->SetTargetCollisionGroup( COLLISION_GROUP_STATIC( 1 ) | COLLISION_GROUP_DYNAMIC( 2 ) | COLLISION_GROUP_DYNAMIC( 3 ) ); _ASSERT( m_pAggroSystem == NULL ); if( GetGameRoom()->bIsPvPRoom() ) { m_pAggroSystem = new CDNPvPPlayerAggroSystem( GetActorHandle() ); _ASSERT( m_pAggroSystem != NULL ); } m_mapTumbleHelper.insert( make_pair(GetElementIndex("Tumble_Front"), GetElementIndex("Move_Front")) ); m_mapTumbleHelper.insert( make_pair(GetElementIndex("Tumble_Back"), GetElementIndex("Move_Back")) ); m_mapTumbleHelper.insert( make_pair(GetElementIndex("Tumble_Left"), GetElementIndex("Move_Left")) ); m_mapTumbleHelper.insert( make_pair(GetElementIndex("Tumble_Right"), GetElementIndex("Move_Right")) ); // 소서리스 차지 미사일도 액션툴과 액션시스템만 갖고 구현되어있지 않고 클라에서 처리하는 부분이 있으므로 서버도 맞춰줄 수 밖에 없다. // 시스템과는 약간 다르게 돌아가므로 마찬가지로 액션을 이어주고 액션의 쿨타임은 차지시간으로 설정되어있는 1.5초로 한다. // 대표로 Move_Front 로만 셋팅하도록 해도 된다. 핵 체크에서 예외로 확인되기만 하면 된다.. m_mapTumbleHelper.insert( make_pair(GetElementIndex("ChargeShoot_Stand"), GetElementIndex("Charge_Front")) ); m_pBubbleSystem->Initialize( GetActorHandle() ); m_mapIcyFractionHitted.clear(); #ifdef PRE_FIX_GAMESERVER_PERFOMANCE m_FrameSkipCallSkillProcess.SetFramePerSec( 10.0f ); #endif // #ifdef PRE_FIX_GAMESERVER_PERFOMANCE #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) m_pTotalLevelSkillSystem = new CDnTotalLevelSkillSystem(GetMySmartPtr()); #endif // PRE_ADD_TOTAL_LEVEL_SKILL return bResult; } void CDnPlayerActor::Process( LOCAL_TIME LocalTime, float fDelta ) { EtVector3 vPrevPos = m_Cross.m_vPosition; CDnActor::Process( LocalTime, fDelta ); PROFILE_TIME_TEST_BLOCK_START( "CDnPlayerActor::Process" ); m_pBubbleSystem->Process( LocalTime, fDelta ); for( int i=0; i<2; i++ ) { if( m_hCashWeapon[i] ) m_hCashWeapon[i]->Process( LocalTime, fDelta ); } MAPartsBody::PreProcess( LocalTime, fDelta ); if( 0 < GetCantXZMoveSEReferenceCount() ) m_vAniDistance.x = m_vAniDistance.z = 0.0f; #ifdef PRE_ADD_MONSTER_CATCH if(!IsVehicleMode() && !m_hCatcherMonster ) #else if(!IsVehicleMode()) #endif // #ifdef PRE_ADD_MONSTER_CATCH m_pMovement->Process( LocalTime, fDelta ); MAPartsBody::Process( m_Cross, LocalTime, fDelta ); ProcessCombo( LocalTime, fDelta ); ProcessRecoverySP( LocalTime, fDelta ); m_pPlayerDoNotEnterChecker->Process( LocalTime, fDelta ); m_pPlayerSpeedHackChecker->Process( LocalTime, fDelta ); m_pPlayerPickupChecker->Process( LocalTime, fDelta ); m_pPlayerSkillChecker->Process( LocalTime, fDelta ); m_pPlayerActionChecker->Process( LocalTime, fDelta ); ProcessCompanion( LocalTime, fDelta ); ProcessNonLocalShootModeAction(); if(m_bRefreshTransformMode) RefreshTransformMode(); #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) if (m_pTotalLevelSkillSystem) m_pTotalLevelSkillSystem->Process(LocalTime, fDelta); #endif // PRE_ADD_TOTAL_LEVEL_SKILL PROFILE_TIME_TEST_BLOCK_END(); } void CDnPlayerActor::CmdPickupItem( PickupItemStruct* pStruct, DnDropItemHandle hDropItem/*=CDnDropItem::Identity()*/ ) { #if defined(_CH) if (m_pSession->GetFCMState() != FCMSTATE_NONE){ // 3시간 이상 게임하면 아이템 줏기 못함 090624 // m_pSession->SendPickUp(ERROR_FCMSTATE, -1, NULL, 0); return; } #endif // 옵져버는 PickUp 못함 if( GetActorHandle() && GetActorHandle()->bIsObserver() ) return; // 운영자난입도 PickUp 못함 if( m_pSession && m_pSession->bIsGMTrace() ) return; if( !CDnDropItem::IsActive(GetRoom()) ) return; DnDropItemHandle hResult; if( hDropItem ) hResult = hDropItem; else { _DANGER_POINT(); return; } if( hResult ) { if (hResult->IsReversionItem() && hResult->IsReversionLocked() == false) { DnActorHandle hBeneficiery; if (hResult->GetOwnerUniqueID() != -1) { hBeneficiery = CDnActor::FindActorFromUniqueID( GetRoom(), hResult->GetOwnerUniqueID() ); if (hBeneficiery) CDnItemTask::GetInstance(GetRoom()).PickUpItem( hBeneficiery, hResult, ITEMLOOTRULE_NONE ); return; } CDnItemTask::GetInstance(GetRoom()).PickUpItem( GetMySmartPtr(), hResult, ITEMLOOTRULE_NONE ); return; } if( hResult->GetOwnerUniqueID() != -1 ) { DnActorHandle hOwner = CDnActor::FindActorFromUniqueID( GetRoom(), hResult->GetOwnerUniqueID() ); if( hOwner ) { CDnItemTask::GetInstance(GetRoom()).PickUpItem( hOwner, hResult, ITEMLOOTRULE_NONE ); } return; } else { eItemRank Rank = CDnItem::GetItemRank( hResult->GetItemID() ); eItemTypeEnum Type = CDnItem::GetItemType( hResult->GetItemID() ); TPARTYITEMLOOTRULE LootType = ITEMLOOTRULE_NONE; TITEMRANK LootRank = m_pRoom->GetPartyItemLootRank(); switch( Type ) { case ITEMTYPE_INSTANT: LootType = ITEMLOOTRULE_NONE; break; default: if (LootRank == ITEMRANK_NONE ) LootType = m_pRoom->GetPartyItemLootRule(); else LootType = ( Rank >= LootRank ) ? ITEMLOOTRULE_RANDOM : m_pRoom->GetPartyItemLootRule(); break; } // 돈일경우도 그냥 노말로 획득합니다. if( hDropItem->GetItemID() == 0 ) LootType = ITEMLOOTRULE_NONE; TItemData *pItemData = g_pDataManager->GetItemData(hDropItem->GetItemID()); if (pItemData) { if (pItemData->cReversion == ITEMREVERSION_BELONG) LootType = ITEMLOOTRULE_NONE; } // 구울모드에서는 강제로 LootType 설정 if( GetRoom() && static_cast(GetRoom())->bIsZombieMode() ) LootType = ITEMLOOTRULE_NONE; switch( LootType ) { case ITEMLOOTRULE_NONE: case ITEMLOOTRULE_OWNER: CDnItemTask::GetInstance(GetRoom()).PickUpItem( GetMySmartPtr(), hResult, LootType ); break; case ITEMLOOTRULE_RANDOM: { int nLiveCount = CDnPartyTask::GetInstance(GetRoom()).GetPartyUserCount(CDNGameRoom::ePICKUPITEM); if( nLiveCount == 0 ) return; int nResultOffset = _rand(GetRoom())%nLiveCount; int nOffset = 0; DnActorHandle hResultActor; int nPartyCount = CDnPartyTask::GetInstance(GetRoom()).GetUserCount(); for( int i=0; ibIsGMTrace() ) continue; #if defined _CH if (pSession->GetFCMState() != FCMSTATE_NONE) continue; #endif DnActorHandle hActor = pSession->GetActorHandle(); if( !hActor || hActor->IsDie() ) continue; if( nOffset == nResultOffset ) { hResultActor = hActor; break; } nOffset++; } if( hResultActor ) CDnItemTask::GetInstance(GetRoom()).PickUpItem( hResultActor, hResult, LootType ); } break; case ITEMLOOTRULE_LEADER: { int nPartyCount = CDnPartyTask::GetInstance(GetRoom()).GetUserCount(); DnActorHandle hResultActor; for( int i=0; ibLeader ) { hResultActor = ( pPartyStruct->pSession ) ? pPartyStruct->pSession->GetActorHandle() : CDnActor::Identity(); } } if( hResultActor ) CDnItemTask::GetInstance(GetRoom()).PickUpItem( hResultActor, hResult, LootType ); } break; case ITEMLOOTRULE_INORDER: { CDNUserSession *pSession = CDnPartyTask::GetInstance(GetRoom()).GetUserData(m_pRoom->GetCurrentItemLooterIdx()); if (pSession) { DnActorHandle hActor = pSession->GetActorHandle(); CDnItemTask::GetInstance(GetRoom()).PickUpItem( hActor, hResult, LootType ); } } break; } } } } void CDnPlayerActor::OnSignal( SignalTypeEnum Type, void *pPtr, LOCAL_TIME LocalTime, LOCAL_TIME SignalStartTime, LOCAL_TIME SignalEndTime, int nSignalIndex ) { switch( Type ) { case STE_Hit: { } break; case STE_Jump: { JumpStruct *pStruct = (JumpStruct *)pPtr; EtVector2 vVec( 0.f, 0.f ); if( 0 == GetCantXZMoveSEReferenceCount() ) { if( !pStruct->bIgnoreJumpDir ) { if( m_cMovePushKeyFlag & 0x01 ) vVec.x -= 1.f; if( m_cMovePushKeyFlag & 0x02 ) vVec.x += 1.f; if( m_cMovePushKeyFlag & 0x04 ) vVec.y += 1.f; if( m_cMovePushKeyFlag & 0x08 ) vVec.y -= 1.f; EtVec2Normalize( &vVec, &vVec ); } } // 점프 동기화 [2010/11/09 semozz] // 점프중 재 점프가 될때 기존 Velocity값을 리셋해야 하는경우 // 클라이언트와 같이 리셋이 되어야 점프시간 동기화가 맞아진다. if( pStruct->bResetPrevVelocity ) { SetVelocityY( 0.f ); SetResistanceY( 0.f ); } Jump( pStruct->fJumpVelocity, vVec ); SetResistanceY( pStruct->fJumpResistance ); } return; case STE_CustomAction: { CustomActionStruct *pStruct = (CustomActionStruct *)pPtr; ResetCustomAction(); SetCustomAction( pStruct->szChangeAction, (float)pStruct->nChangeActionFrame ); } return; case STE_PickupItem: { } return; case STE_RebirthAnyPlayer: { // [2011/02/11 semozz] // 씨드래곤맵에서는 파티 부활 방지.. CMultiRoom *pRoom = GetRoom(); if (!pRoom) break; eDragonNestType _dragonNestType = CDnWorld::GetInstance(pRoom).GetDragonNestType(); RebirthAnyPlayerStruct *pStruct = (RebirthAnyPlayerStruct*)pPtr; CDNUserSession *pSession = NULL; float fMinDist = FLT_MAX; for( DWORD i=0; ipSession ) continue; if( pParty->pSession == m_pSession ) continue; if( pParty->pSession->GetState() != SESSION_STATE_GAME_PLAY) continue; DnActorHandle hActor = pParty->pSession->GetActorHandle(); if( !hActor ) continue; CDnPlayerActor *pPlayer = (CDnPlayerActor *)hActor.GetPointer(); if( !pPlayer->IsDie() || !pPlayer->IsGhost() ) continue; if( pParty->nUsableRebirthCoin == 0 ) continue; EtVector3 vVec = *GetPosition() - *hActor->GetPosition(); float fDist = EtVec3LengthSq( &vVec ); if( fDist < fMinDist ) { fMinDist = fDist; pSession = pParty->pSession; } } if( !pSession || fMinDist > (float)( pStruct->nRadius * pStruct->nRadius ) ) break; if( m_pSession->GetStatusData()->wCashRebirthCoin <= 0 ) { GetUserSession()->SendRebirthCoin(ERROR_ITEM_REBIRTH_CASHCOIN_SHORT_FAIL, m_pPartyData->nUsableRebirthCoin, _REBIRTH_SELF, GetUserSession()->GetSessionID()); break; } CDnItemTask::GetInstance(GetRoom()).RequestRebirthCoinUseAnyPlayer( m_pSession, pSession ); UpdateRebirthPlayer(); } break; case STE_CancelChangeStandActionSE: { CDnChangeStandActionBlow::ReleaseStandChangeSkill( GetActorHandle(), false ); } break; case STE_CancelChangeStandEndActionSE: { CancelChangeStandEndActionSEStruct* pStruct = (CancelChangeStandEndActionSEStruct*)pPtr; CDnChangeStandActionBlow::ReleaseStandChangeSkill( GetActorHandle(), false, pStruct->szEndAction ); } break; case STE_TriggerEvent: { TriggerEventStruct *pStruct = (TriggerEventStruct *)pPtr; CDnWorld::GetInstance(GetRoom()).InsertTriggerEventStore( "EventArea", -1 ); CDnWorld::GetInstance(GetRoom()).InsertTriggerEventStore( "ActorHandle", GetSessionID() ); CDnWorld::GetInstance(GetRoom()).InsertTriggerEventStore( "EventID", pStruct->nEventID ); CDnWorld::GetInstance(GetRoom()).OnTriggerEventCallback( "CDnActor::TriggerEvent", LocalTime, 0.f ); } break; case STE_ShootCannon: { ShootCannonStruct* pStruct = (ShootCannonStruct*)pPtr; if( IsCannonMode() ) { // 클라이언트에서 CS_CANNONROTATESYNC 패킷으로 보내준 TargetPosition 으로 발사체를 셋팅한다. m_hCannonMonsterActor->UseSkill( pStruct->CannonMonsterSkillID ); } } break; // #29925 이 시그널에 지정된 상태효과가 없으면 지정된 액션을 실행. case STE_ChangeActionSECheck: { ChangeActionSECheckStruct* pStruct = (ChangeActionSECheckStruct*)pPtr; if( false == IsAppliedThisStateBlow( (STATE_BLOW::emBLOW_INDEX)pStruct->nStateEffectID ) ) { SetActionQueue( pStruct->szChangeAction ); } } break; /* // Note: 패킷보다 시그널이 늦게 처리되어 클라에서 발동된 액션 중 스킬이 씹히는 경우가 있으므로 // 클라쪽에서 패킷으로 보내도록 수정합니다.. case STE_CanUseSkill: { CanUseSkillStruct* pStruct = (CanUseSkillStruct*)pPtr; m_bUseSignalSkillCheck = (pStruct->bUseSignalSkillCheck ? TRUE : FALSE); SetSignalSkillCheck( pStruct->CheckType, m_bUseSignalSkillCheck ); } break; */ case STE_OrderMySummonedMonster: { OrderMySummonedMonsterStruct* pStruct = (OrderMySummonedMonsterStruct*)pPtr; OrderUseSkillToMySummonedMonster(pStruct); } break; } CDnActor::OnSignal( Type, pPtr, LocalTime, SignalStartTime, SignalEndTime, nSignalIndex ); } void CDnPlayerActor::OnDrop( float fCurVelocity ) { if( IsAir() ) { if( !IsHit() ) { ////////////////////////////////////////////////////////////////////////// // #32977 - 탑 스피닝 스킬을 시작 해놓은 상태에서 탑 스피닝 액션의 시그널이 처리 되기전 // OnDrop이 호출 되면 탑 스피닝에 있는 STE_Jump가 처리가 되지 않아서 동작이 이어지지 않게 된다. // 그래서 일단.. // 현재 Frame이 0이고, STE_Jump시그널이 0프레임에 있으면 _Landing동작으로 변경을 막고 // 현재 동작을 계속 유지 하도록 한다. ////////////////////////////////////////////////////////////////////////// float fCurrentFrame = CDnActionBase::GetCurFrame(); float fPrevFrame = CDnActionBase::m_fPrevFrame; ActionElementStruct *pStruct = GetElement(GetCurrentAction()); if (pStruct) { bool hasJumpSignal = false; CEtActionSignal *pSignal = NULL; for (int i = 0; i < (int)pStruct->pVecSignalList.size(); ++i) { pSignal = pStruct->pVecSignalList[i]; if (pSignal && pSignal->GetSignalIndex() == STE_Jump) { hasJumpSignal = true; break; } } if (hasJumpSignal) { //STE_Jump가 있고, actionQueue가 있으면 스킵..(자동으로 다음 액션으로 바뀌겠지?..) if (false == m_szActionQueue.empty()) { return; } //현재 액션이 STE_Jump를 가지고 있고, 0프레임이면 STE_Jump 호출될 수 있도록.. else if (false == m_szAction.empty() && fPrevFrame == 0.0f) { return; } } } ////////////////////////////////////////////////////////////////////////// char szStr[64]; sprintf_s( szStr, "%s_Landing", GetCurrentAction() ); if( IsExistAction( szStr ) ) { SetActionQueue( szStr, 0, 2.f, 0.f, true, false ); // 스킬 사용중일땐 체인액션 셋팅해준다. if( m_hProcessSkill ) { m_hProcessSkill->AddUseActionName( szStr ); m_hProcessSkill->OnChainInput( szStr ); } } else {// 만약에 없을경우에 하늘에서 병신짓하구있어서 넣어놉니다. 일단은 마춰서 넣어주는거임 if( GetVelocity()->y != 0.f ) SetActionQueue( "Stand", 0, 0.f, 0.f, true, false ); } SetMovable( false ); } else { std::string szAction; float fBlendFrame = 2.f; // 떨어지는 속도가 10이상이면 bigBounce로 한번 더 띄어준다. if( fCurVelocity < -6.f && m_HitParam.vVelocity.y != 0.f && abs(m_HitParam.vVelocity.y) > 0.1f ) { if( m_HitParam.vVelocity.y > 0.f ) { m_HitParam.vVelocity.y *= 0.6f; SetVelocityY( m_HitParam.vVelocity.y ); } else { // 가속도가 처음부터 바닥으로 향해있는 경우에는 뒤집어줘야한다. m_HitParam.vVelocity.y *= -0.6f; if( m_HitParam.vResistance.y > 0.f ) m_HitParam.vResistance.y *= -1.f; SetVelocityY( m_HitParam.vVelocity.y ); if( m_HitParam.vVelocity.y > 0 && m_HitParam.vResistance.y <= 0 ) SetResistanceY( -15.0f ); else SetResistanceY( m_HitParam.vResistance.y ); } szAction = "Hit_AirBounce"; } else { szAction = "Down_SmallBounce"; fBlendFrame = 0.f; } SetActionQueue( szAction.c_str(), 0, fBlendFrame, 0.f, true, false ); } } } void CDnPlayerActor::OnStop( EtVector3 &vPosition ) { if( IsProcessSkill() ) return; CmdStop( "Stand" ); } void CDnPlayerActor::OnFall( float fCurVelocity ) { // #31056 Move 이고 Air 이면 공중에서 이동하는 액션이므로 Fall 처리 하지 않는다. (높은 곳에서 떨어질 때 탑스피닝 사용) if( !(IsMove() && IsAir()) && (IsStay() || IsMove()) && ! IsFloorCollision() ) { // 움직여지는 각도도 체크해서 계단등을 내려올때 떨어져보이는것 보정해보아요. EtVector3 vDir = *GetPosition() - *GetPrevPosition(); EtVec3Normalize( &vDir, &vDir ); float fDot = EtVec3Dot( &EtVector3( 0.f, 1.f, 0.f ), &vDir ); float fAngle = EtToDegree( EtAcos( fDot ) ); if( fCurVelocity < -5.f && fAngle > 155.f ) { ActionElementStruct *pStruct = GetElement( "Jump" ); if( pStruct ) { SetActionQueue( "Jump", 0, 6.f, (float)pStruct->dwLength / 2.f ); } } } } void CDnPlayerActor::OnDamage( CDnDamageBase *pHitter, SHitParam &HitParam, HitStruct *pHitStruct ) { #if defined( PRE_ADD_SECONDARY_SKILL ) if( GetUserSession() && GetUserSession()->GetSecondarySkillRepository() ) static_cast(GetUserSession()->GetSecondarySkillRepository())->CancelManufacture(); #endif // #if defined( PRE_ADD_SECONDARY_SKILL ) if( IsCannonMode() ) { HitParam.szActionName.clear(); HitParam.vViewVec = *GetLookDir(); HitParam.vResistance = EtVector3( 0.0f, 0.0f, 0.0f ); HitParam.vVelocity = EtVector3( 0.0f, 0.0f, 0.0f ); } int nSeed = CRandom::Seed(GetRoom()); _srand( GetRoom(), nSeed ); INT64 nTemp = GetHP(); CDnActor::OnDamage( pHitter, HitParam, pHitStruct ); INT64 nDamage = nTemp - GetHP(); INT64 biKillerCharDBID = 0; if( GetGameRoom() ) { DnActorHandle hHitter = pHitter ? pHitter->GetActorHandle() : CDnActor::Identity(); GetGameRoom()->OnDamage( GetActorHandle(), hHitter, nDamage ); if( GetGameRoom()->GetGameTask() ) GetGameRoom()->GetGameTask()->OnDamage( GetActorHandle(), hHitter, nDamage ); if (IsDie() && hHitter && hHitter->IsPlayerActor()){ CDnPlayerActor *pPlayer = (CDnPlayerActor *)hHitter.GetPointer(); biKillerCharDBID = pPlayer->GetUserSession()->GetCharacterDBID(); } } if( m_HitParam.bSuccessNormalDamage ) { m_nComboDelay = 0; m_nComboCount = 0; } UpdateAttackedCPPoint( pHitter, m_HitParam.HitType ); ResetCustomAction(); RequestDamage( pHitter, nSeed, nDamage ); switch( pHitter->GetDamageObjectType() ) { case DamageObjectTypeEnum::Actor: { DnActorHandle hActor = pHitter->GetActorHandle(); if( m_pAggroSystem ) m_pAggroSystem->OnDamageAggro( hActor, HitParam, (int)nDamage ); } break; } } void CDnPlayerActor::CmdMove( EtVector3 &vPos, const char *szActionName, int nLoopCount, float fBlendFrame ) { MovePos( vPos, false ); // #31940 패링은 상태효과가 돌면서 패링액션을 나가게 하는 것이므로 움직이면서 피격되어 패링이 나올 경우 // 클라에서 계속 방향키를 누르고 있어서 이동 패킷이 오는경우 CS_CMDMOVE 패킷이 와서 여기서 Move_ 시리즈로 로 액션이 바뀌므로 예외처리. if( m_szAction == "Skill_Parrying" || m_szActionQueue == "Skill_Parrying" ) return; SetActionQueue( szActionName, nLoopCount, fBlendFrame ); } void CDnPlayerActor::CmdStop( const char *szActionName, int nLoopCount, float fBlendFrame, float fStartFrame ) { MAMovementBase *pMovement = GetMovement(); if( !pMovement ) return; pMovement->ResetMove(); // #31940 패링은 상태효과가 돌면서 패링액션을 나가게 하는 것이므로 움직이면서 피격되어 패링이 나올 경우 // 곧바로 캐릭터가 멈추면서 CS_CMDSTOP 패킷이 와서 여기서 Stand 로 액션이 바뀌므로 예외처리. if( m_szAction == "Skill_Parrying" || m_szActionQueue == "Skill_Parrying" ) return; SetActionQueue( szActionName, nLoopCount, fBlendFrame, fStartFrame ); } void CDnPlayerActor::CmdPassiveSkillAction( int nSkillID, const char *szActionName, int nLoopCount /*= 0*/, float fBlendFrame /*= 3.f*/, float fStartFrame /*= 0.0f*/, bool bChargeKey /*= false*/, bool bCheckOverlapAction /*= true */, bool bOnlyCheck/* = false*/ ) { // Note: 행동불가인 경우 패시브 스킬 나갈 수 없음. // SetAction() 함수에서 액션이 막히기 때문에 어차피 안나가지만 스킬 사용으로 쿨타임이 돌아가므로 시그널 처리를 막는다. if( GetCantActionSEReferenceCount() > 0 ) return; if( false == bOnlyCheck ) { DnSkillHandle hSkill = FindSkill( nSkillID ); if( !hSkill ) return; // Note: 발차기 같은 경우 같은 스킬이 발동 중에 끝 부분에 또 발동 되도록 액션툴에 설정되어있어서 // 같은 스킬일지리도 끊기고 나가도록 합니다. if( m_hProcessSkill /*&& m_hProcessSkill != hSkill*/ ) { if( false == (IsEnabledAuraSkill() && m_hProcessSkill->IsAuraOn()) ) { m_hProcessSkill->OnEnd( CDnActionBase::m_LocalTime, 0.f ); } m_hProcessSkill.Identity(); } // 서버쪽에서도 누르고 있는 키기 ‹š문에 액션 바뀌기 전엔 스킬이 계속 실행되는 것으로 처리. if( bChargeKey ) { hSkill->SetPassiveSkillLength( -1.0f ); } else { // 패시브 스킬 액션에 next 액션까지 있는 경우 감안. // next 액션은 클라이언트쪽에서도 하나만 기준 잡고 있기 때문에 서버에서도 하나만 기준 잡는다. DWORD dwActionLength = 0; ActionElementStruct* pElement = GetElement( szActionName ); if( pElement ) { if( pElement->dwLength <= (DWORD)fStartFrame ) fStartFrame = pElement->dwLength*1.f; dwActionLength = pElement->dwLength - (DWORD)fStartFrame; } else { fStartFrame = 0.0f; } // #25042 Stand 액션이 next 액션인 경우 패시브 스킬의 액션이 아니므로 시간을 포함시키지 않는다. if( pElement->szNextActionName != "Stand" && 0 == strstr( pElement->szNextActionName.c_str(), "_Landing") ) { ActionElementStruct* pNextElement = GetElement( pElement->szNextActionName.c_str() ); if( pNextElement ) dwActionLength += pNextElement->dwLength; } hSkill->SetPassiveSkillLength( (float)dwActionLength / s_fDefaultFps ); // Active Type의 스킬(이글스 디센트) 같은 경우 Passive Type으로 스킬을 사용 할 경우도 있다. // 추후에 _CheckActionWithProcessPassiveActionSkill 에서 ActionLength가 진행 중인지 체크하게 되면 // Passive Type 일 경우 ChaningPassiveSkill을 true로 만드는데 Active Type이라서 false 이다. // 그래서 여기서 ChaningPassiveKill을 true로 만들어 준다. if( hSkill->GetSkillType() == CDnSkill::Active ) // Active 일때만 설정해 줍니다. hSkill->SetChaningPassiveSkill( true ); } // 액티브 스킬이 InputHasPassiveSkill 로 패킷이 왓을 경우 UsableCheck 를 무시해야 한다. if( CDnSkill::Active == hSkill->GetSkillType() ) { m_bUseSignalSkillCheck = true; for( int i = 0; i < 3; ++i ) SetSignalSkillCheck( i, true ); } #ifdef PRE_FIX_GAMESERVER_PERFOMANCE // 서버의 쿨타임 오차를 감안해서 0.5초의 여유를 두고 있지만, // 존이동할때나 기타 0.5초 이상 클라이언트와 벌어지는 다른 새로운 경우들이 생길 수 있으므로 // 최종 스킬 사용한 타임 스탬프를 찍어두어 데이터에 지정된 스킬의 쿨타임보타 간격이 크다면 // 서버의 스킬 객체에 저장되어있는 쿨타임을 초기화 시켜주도록 한다. (#19737) hSkill->UpdateSkillCoolTimeExactly(); #endif // #ifdef PRE_FIX_GAMESERVER_PERFOMANCE CDnSkill::UsingResult eResult = CanExecuteSkill( hSkill ); if( eResult == CDnSkill::UsingResult::Success ) { // m_nLastUsedSkill = hSkill->GetClassID(); // ExecuteSkill( hSkill, CDnActionBase::m_LocalTime, 0.f ); hSkill->FromInputHasPassive(); SetActionQueue( szActionName, nLoopCount, fBlendFrame, fStartFrame, bCheckOverlapAction ); } else { ((CDnPlayerSkillChecker*)m_pPlayerSkillChecker)->OnInvalidUseSkill( nSkillID, eResult ); } // 액티브 스킬이 InputHasPassiveSkill 로 패킷이 왓을 경우 UsableCheck 를 무시한 것 다시 복구. if( CDnSkill::Active == hSkill->GetSkillType() ) { m_bUseSignalSkillCheck = false; for( int i = 0; i < 3; ++i ) SetSignalSkillCheck( i, false ); } // 플레이어가 사용하는 패시브/즉시 스킬을 위해 액션 이름 기입해 줌. hSkill->SetPassiveSkillActionName( szActionName ); } else SetActionQueue( szActionName, nLoopCount, fBlendFrame, fStartFrame, bCheckOverlapAction ); m_fDownDelta = 0.f; } // 이쪽 패킷에서 구조체가 변경될 경우 PvP 난입시 코드도 수정이 필요하니 저에게(김밥) 알려주세요. int CDnPlayerActor::CmdAddStateEffect( const CDnSkill::SkillInfo* pParentSkill, STATE_BLOW::emBLOW_INDEX emBlowIndex, int nDurationTime, const char *szParam, bool bOnPlayerInit/* = false */, bool bCheckCanBegin/* = true*/ , bool bEternity /*= false*/ ) { if( emBlowIndex < STATE_BLOW::BLOW_NONE || emBlowIndex >= STATE_BLOW::BLOW_MAX ) { if( pParentSkill ) g_Log.Log(LogType::_ERROR, L"[CDnActor::CmdAddStateEffect] SkillID:%d, STATE_BLOW:%d\r\n", pParentSkill->iSkillID, emBlowIndex ); else g_Log.Log(LogType::_ERROR, L"[CDnActor::CmdAddStateEffect] STATE_BLOW:%d\r\n", emBlowIndex); return -1; } int iID = CDnActor::CmdAddStateEffect( pParentSkill, emBlowIndex, nDurationTime, szParam, bOnPlayerInit, bCheckCanBegin , bEternity ); if( -1 == iID ) return -1; DnBlowHandle hAddedBlow = m_pStateBlow->GetStateBlowFromID( iID ); const CPacketCompressStream* pPacket = hAddedBlow->GetPacketStream( szParam, bOnPlayerInit ); Send( eActor::SC_CMDADDSTATEEFFECT, const_cast(pPacket) ); if( GetGameRoom() ) GetGameRoom()->OnCmdAddStateEffect( hAddedBlow->GetParentSkillInfo() ); CheckAndRegisterObserverStateBlow( hAddedBlow ); return iID; } void CDnPlayerActor::CmdRemoveStateEffect( STATE_BLOW::emBLOW_INDEX emBlowIndex, bool bRemoveFromServerToo /*= true */ ) { if( m_pStateBlow->IsApplied( emBlowIndex ) ) { if( bRemoveFromServerToo ) CDnActor::CmdRemoveStateEffect( emBlowIndex ); SendRemoveStateEffect( emBlowIndex ); } } void CDnPlayerActor::SendRemoveStateEffect( STATE_BLOW::emBLOW_INDEX emBlowIndex ) { BYTE pBuffer[32]; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &emBlowIndex, sizeof(STATE_BLOW::emBLOW_INDEX) ); Send( eActor::SC_CMDREMOVESTATEEFFECT, &Stream ); } void CDnPlayerActor::SendRemoveStateEffectGraphic( STATE_BLOW::emBLOW_INDEX emBlowIndex ) { BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &emBlowIndex, sizeof(int) ); Send( eActor::SC_BLOW_GRAPHIC_ERASE, &Stream ); } void CDnPlayerActor::CmdToggleBattle( bool bBattleMode ) { if( bBattleMode == true ) { if( !m_hWeapon[0] ) { m_bBattleMode = false; return; } } if( bBattleMode != m_bBattleMode ) { SetBattleMode( bBattleMode ); bool bSkipAction = false; #if defined(PRE_ADD_50907) bool bSkipChangeAction = IsSkipChangeWeaponAction(); if (bSkipChangeAction == true) return; #endif // PRE_ADD_50907 if(m_bShootMode) { if( m_bBattleMode == true ) SetActionQueue( "MOD_PullOut_Weapon" ); else SetActionQueue( "MOD_PutIn_Weapon" ); // ShootMode는 전투상태에만 사용하기때문에 Normal상태에서 무기를 꺼내는 행동은 GetchangeShootaction 에서 설정하지 못하기때문에 여기서 넣는다. bSkipAction = true; } if(!bSkipAction) { if( !IsDie() ) { if( m_bBattleMode == true ) SetActionQueue( "PullOut_Weapon" ); else SetActionQueue( "PutIn_Weapon" ); } } } } void CDnPlayerActor::CmdAddExperience( TExpData &ExpData, int nLogCode, INT64 biFKey ) { int nExp = ExpData.nExperience; int nEventExp = ExpData.nEventExperience; int nPCBangExp = ExpData.nPcBangExperience; int nVIPExp = ExpData.nVIPExperience; int nPromoExp = ExpData.nPromotionExperience; TPlayerCommonLevelTableInfo* pPlayerCommonLevelTableInfo = g_pDataManager->GetPlayerCommonLevelTable(GetLevel()); if( pPlayerCommonLevelTableInfo ) { //여기서 1.5배 증가(한군데에서 해주는게 관리가 용이할듯....dyss) nExp = (int)(nExp * pPlayerCommonLevelTableInfo->fAddGainExp); nEventExp = (int)(nEventExp * pPlayerCommonLevelTableInfo->fAddGainExp); nPCBangExp = (int)(nPCBangExp * pPlayerCommonLevelTableInfo->fAddGainExp); nVIPExp = (int)(nVIPExp * pPlayerCommonLevelTableInfo->fAddGainExp); nPromoExp = (int)(nPromoExp * pPlayerCommonLevelTableInfo->fAddGainExp); } #if defined( PRE_USA_FATIGUE ) int iPwrExp = 0; if( nLogCode == DBDNWorldDef::CharacterExpChangeCode::DungeonMonster ) { nLogCode = DBDNWorldDef::CharacterExpChangeCode::Dungeon; if( m_pSession && m_pSession->bIsNoFatigueEnter() == true ) { int iTemp = nExp+ExpData.nItemExperience; nExp = (iTemp*g_pDataManager->GetNoFatigueExpRate())/100; #if defined( _WORK ) char szBuf[MAX_PATH]; sprintf_s( szBuf, "북미피로도적용 Exp%d->%d", iTemp, nExp ); std::cout << szBuf << std::endl; #endif // #if defined( _WORK ) } else { iPwrExp = ((nExp+ExpData.nItemExperience)*(g_pDataManager->GetFatigueExpRate()-g_pDataManager->GetNoFatigueExpRate()))/100; int iTemp = nExp+ExpData.nItemExperience; nExp = (iTemp*g_pDataManager->GetFatigueExpRate())/100; #if defined( _WORK ) char szBuf[MAX_PATH]; sprintf_s( szBuf, "북미피로도적용 Exp%d->%d", iTemp, nExp ); std::cout << szBuf << std::endl; #endif // #if defined( _WORK ) } } #endif // #if defined( PRE_USA_FATIGUE ) #if defined(_CH) if (m_pSession->GetFCMState() == FCMSTATE_HALF){ nExp = ExpData.nExperience / 2; nEventExp = (ExpData.nEventExperience > 0) ? ExpData.nEventExperience / 2 : 0; nPCBangExp = (ExpData.nPcBangExperience > 0) ? ExpData.nPcBangExperience / 2 : 0; nVIPExp = (ExpData.nVIPExperience > 0) ? ExpData.nVIPExperience / 2 : 0; nPromoExp = (ExpData.nPromotionExperience > 0) ? ExpData.nPromotionExperience / 2 : 0; ExpData.nItemExperience = (ExpData.nItemExperience>0) ? ExpData.nItemExperience/2 : 0; ExpData.nGuildExp = (ExpData.nGuildExp>0) ? ExpData.nGuildExp/2 : 0; } else if (m_pSession->GetFCMState() == FCMSTATE_ZERO){ nExp = 0; nEventExp = 0; nPCBangExp = 0; nVIPExp = 0; nPromoExp = 0; ExpData.nItemExperience = 0; ExpData.nGuildExp = 0; } #endif // _CH nExp += ExpData.nItemExperience; nExp += ExpData.nGuildExp; #if defined(PRE_ADD_WEEKLYEVENT) if (CDnWorld::GetInstance(GetRoom()).GetMapType() != EWorldEnum::MapSubTypeNest){ int nThreadID = GetGameRoom()->GetServerID(); float fEventValue = g_pDataManager->GetWeeklyEventValuef(WeeklyEvent::Player, GetClassID(), WeeklyEvent::Event_5, nThreadID); if (fEventValue != 0) nExp += (int)(nExp * fEventValue); } #endif // #if defined(PRE_ADD_WEEKLYEVENT) int nPrevLevel = GetLevel(); int nTotalExp = nExp + nEventExp + nPCBangExp + nVIPExp + nPromoExp; AddExperience( nTotalExp, nLogCode, biFKey ); //_KR이 아니면 pcbangexp는 항상 0이다. // 레벨업이 되버리면 구지 보낼필요 없지 말입니다. if( GetLevel() == nPrevLevel ) { BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &m_nExperience, sizeof(int) ); Stream.Write( &nExp, sizeof(int) ); Stream.Write( &nEventExp, sizeof(int) ); Stream.Write( &nPCBangExp, sizeof(int) ); #if defined(PRE_ADD_VIP) Stream.Write( &nVIPExp, sizeof(int) ); #endif // #if defined(PRE_ADD_VIP) Stream.Write( &nPromoExp, sizeof(int) ); #if defined( PRE_USA_FATIGUE ) Stream.Write( &iPwrExp, sizeof(int) ); #endif // #if defined( PRE_USA_FATIGUE ) Send( eActor::SC_ADDEXP, GetMySmartPtr(), &Stream ); } } void CDnPlayerActor::CmdAddCoin( INT64 nCoin, int nLogCode, int nFKey, bool bSync ) { INT64 nChangeCoin = nCoin; #if defined( PRE_ADD_TOTAL_LEVEL_SKILL ) if( nLogCode == DBDNWorldDef::CoinChangeCode::PickUp ) { float fIncGoldRate = 0.0f; if (IsAppliedThisStateBlow(STATE_BLOW::BLOW_266)) { DNVector(DnBlowHandle) vlBlows; GatherAppliedStateBlowByBlowIndex(STATE_BLOW::BLOW_266, vlBlows); { int nCount = (int)vlBlows.size(); for (int i = 0; i < nCount; ++i) { DnBlowHandle hBlow = vlBlows[i]; if (hBlow && hBlow->IsEnd() == false) { fIncGoldRate += hBlow->GetFloatValue(); } } } nChangeCoin += (INT64)(nChangeCoin * fIncGoldRate); if( !m_pSession->CheckMaxCoin(nChangeCoin) ) return; } } #endif #if defined(_CH) if (m_pSession->GetFCMState() == FCMSTATE_HALF){ nChangeCoin = nCoin / 2; } else if (m_pSession->GetFCMState() == FCMSTATE_ZERO){ nChangeCoin = 0; } #endif // _CH #if defined( _GAMESERVER ) if( nLogCode != DBDNWorldDef::CoinChangeCode::PickUp ) g_Log.Log(LogType::_ERROR, m_pSession, L"CmdAddCoin PrevCoin=%I64d ChangeCoin=%I64d PickUpCoint=%I64d LogType=%d\r\n", m_pSession->GetCoin(), nChangeCoin, m_pSession->GetPickUpCoin(), nLogCode); #endif if( nLogCode == DBDNWorldDef::CoinChangeCode::PickUp ) { m_pSession->AddPickUpCoin( nChangeCoin ); #if defined( _WORK ) std::cout << nCoin << " Coin 획득 => 총 " << m_pSession->GetPickUpCoin() << " Coin" << std::endl; #endif // #if defined( _WORK ) nLogCode = 0; // DB에 저장하지 않기 위해 다시 초기화 } // 여기 주의!! bSync 랑 AddCoin 의 bWarehouse 는 틀리지만 쨋든 패킷 보낸다,안보낸다 차이기 때문에 걍 쓴다.ㅋㅋㅋㅋ 우씨 -> 바꿨음 Send로... ㅎㅎㅎ if( m_pSession && m_pSession->GetItem() ){ if (nChangeCoin > 0) m_pSession->AddCoin( nChangeCoin, nLogCode, nFKey, bSync ); else if (nChangeCoin < 0) m_pSession->DelCoin( -nChangeCoin, nLogCode, nFKey, bSync ); } } void CDnPlayerActor::OnDispatchMessage( CDNUserSession *pSession, DWORD dwActorProtocol, BYTE *pPacket ) { switch( dwActorProtocol ) { case eActor::CS_CMDMOVE: { CPacketCompressStream Stream( pPacket, 128 ); int nActionIndex; EtVector3 vPos, vXVec; EtVector2 vZVec, vLook; char cFlag; DWORD dwGap; int nMoveSpeed; Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &nActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &vLook, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &cFlag, sizeof(char) ); Stream.Read( &nMoveSpeed, sizeof(int) ); ActionElementStruct *pStruct = GetElement( nActionIndex ); if( pStruct == NULL ) break; m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); m_pPlayerSpeedHackChecker->OnSyncPosition( vPos ); #ifdef PRE_ADD_CHECK_MOVESPEED_HACK m_pPlayerSpeedHackChecker->OnSyncMoveSpeed( nMoveSpeed ); #endif if( CheckSkillAction(pStruct->szName.c_str() ) == true ) { ((CDnPlayerActionChecker*)m_pPlayerActionChecker)->OnInvalidAction(); break; } Look( vLook, false ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); SetPosition( vPos ); // SetMagnetPosition( vPos ); #ifdef _USE_VOICECHAT m_nVoiceRotate = (int)EtToDegree( acos( EtVec2Dot( &EtVector2( 0.f, 1.f ), &vZVec ) ) ); if( vZVec.x > 0.0f ) m_nVoiceRotate = 360 - m_nVoiceRotate; m_nVoiceRotate = (m_nVoiceRotate + 90) % 360; m_pSession->SetVoicePos((int)vPos.x, (int)vPos.y, (int)vPos.z, m_nVoiceRotate); #endif m_cMovePushKeyFlag = cFlag; float fXSpeed = 0.f, fZSpeed = 0.f; if( cFlag & 0x01 ) fXSpeed = -100000.f; if( cFlag & 0x02 ) fXSpeed = 100000.f; if( cFlag & 0x04 ) fZSpeed = 100000.f; if( cFlag & 0x08 ) fZSpeed = -100000.f; vPos += ( vXVec * fXSpeed ); vPos += ( EtVec2toVec3( vZVec ) * fZSpeed ); CmdMove( vPos, pStruct ? pStruct->szName.c_str() : "", -1, 8.f ); } break; case eActor::CS_CMDSTOP: { CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos; DWORD dwGap; bool bReset, bForce; Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &bReset, sizeof(bool) ); Stream.Read( &bForce, sizeof(bool) ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); m_pPlayerSpeedHackChecker->OnSyncPosition( vPos ); /* std::string szActionName = GetCurrentAction(); if( EtVec2Length( &( EtVector2( vPos.x, vPos.z ) - EtVector2( GetPosition()->x, GetPosition()->z ) ) ) > 100.f ) { if( GetState() != ActorStateEnum::Move ) szActionName = "Move_Front"; } CmdMove( vPos, szActionName.c_str(), -1, CDnActionBase::m_fQueueBlendFrame ); */ if( IsProcessSkill() && bForce ) CancelUsingSkill(); SetPosition( vPos ); OnStop( vPos ); #ifdef _USE_VOICECHAT m_pSession->SetVoicePos((int)vPos.x, (int)vPos.y, (int)vPos.z, m_nVoiceRotate); #endif } break; case eActor::CS_CMDACTION: { CPacketCompressStream Stream( pPacket, 128 ); int nActionIndex, nLoopCount; float fBlendFrame; EtVector3 vXVec, vPos; EtVector2 vLook, vZVec; DWORD dwGap; bool bFromStateBlow = false; bool bSkillChain = false; Stream.Read( &nActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &nLoopCount, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &fBlendFrame, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 10.f ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &vLook, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &m_cMovePushKeyFlag, sizeof(char) ); Stream.Read( &bFromStateBlow, sizeof(bool) ); Stream.Read( &bSkillChain, sizeof(bool) ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); ResetMove(); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); Look( vLook ); SetMagnetPosition( vPos ); ActionElementStruct *pStruct = GetElement( nActionIndex ); if( pStruct == NULL ) break; if( bSkillChain ) { if( false == IsValidSkillChain( GetCurrentActionIndex(), nActionIndex ) ) return; } else { bool bCheckStandAction = CDnChangeStandActionBlow::CheckUsableAction( GetActorHandle(), true, pStruct->szName.c_str() ); if( IsProcessSkill() == true && CheckSkillAction( pStruct->szName.c_str() ) == true || bCheckStandAction == false ) { ActionElementStruct *pCurrentActionElement = GetElement( GetCurrentActionIndex() ); if( pCurrentActionElement ) { bool bCorrectInputAction = false; for( DWORD iSignal = 0; iSignal < pCurrentActionElement->pVecSignalList.size(); ++iSignal ) { CEtActionSignal* pSignal = pCurrentActionElement->pVecSignalList.at( iSignal ); if( STE_Input == pSignal->GetSignalIndex() ) { InputStruct* pInputStruct = (InputStruct*)(pSignal->GetData()); if( strcmp( pInputStruct->szChangeAction, pStruct->szName.c_str() ) == NULL ) { bCorrectInputAction = true; break; } } } if( bCorrectInputAction == false ) { ((CDnPlayerActionChecker*)m_pPlayerActionChecker)->OnInvalidAction(); return; } } } } if( m_pBasicAttackInfo && strstr(pStruct->szName.c_str(), "Attack") ) { DNVector(int) nVecCheckActionList; nVecCheckActionList.push_back( GetCurrentActionIndex() ); if( IsCustomAction() ) nVecCheckActionList.push_back( GetCustomActionIndex() ); // next 액션이 있는 경우엔 10 프레임 정도의 여유를 두고 검증할 리스트에 추가해준다. // 서버 프레임이 느려서 next action 으로 stand 잡아놓고 입력 받게 해도 서버에서 // next 액션 셋팅이 안되어서 핵으로 간주되는 경우가 있다. ActionElementStruct* pCurrentActionElement = GetElement( GetCurrentActionIndex() ); if( pCurrentActionElement ) { if( 0 < pCurrentActionElement->szNextActionName.length() ) { // 클라이언트가 어떻게 조작하느냐에 따라 서버와의 프레임 격차가 커질 수 있어서 일단 프레임체크는 서버에서 할 수 없으므로 뺌. //if( pCurrentActionElement->dwLength - (DWORD)CDnActionBase::m_fFrame < 10 ) { int iNextActionIndex = GetElementIndex( pCurrentActionElement->szNextActionName.c_str() ); if( -1 < iNextActionIndex ) nVecCheckActionList.push_back( iNextActionIndex ); } } } // cmdaction 으로 평타 패킷을 마구 보내는 경우, 전이될 수 있는 모든 액션을 체크함으로써 기본적으로 막힐 수 있으며, // 실제로 타격이 되는 액션의 프레임은 조금 지나야 나오므로 cmdaction 패킷엔 임의의 프레임으로부터 액션을 시작할 수 없기 때문에 // 이 방법이 유효하다. 하지만 바로 아래 mixedaction 은 임의로 액션 시작 프레임을 정할 수 있으므로 해당 부분을 따로 막아야 한다. bool bValid = false; for( DWORD k=0; k >::const_iterator iter = m_pBasicAttackInfo->mapBasicAttackInfo.find( nVecCheckActionList[k] ); if( iter != m_pBasicAttackInfo->mapBasicAttackInfo.end() ) { const vector& vlInputSignals = iter->second; for( int i = 0; i < (int)vlInputSignals.size(); ++i ) { const CDnActionSpecificInfo::S_BASIC_ATTACK_INPUT_SIGNAL_INFO& InputSignalInfo = vlInputSignals.at( i ); if( strstr( pStruct->szName.c_str(), InputSignalInfo.strChangeActionName.c_str() ) ) { bValid = true; break; } } } } if( !bValid ) { ((CDnPlayerActionChecker*)m_pPlayerActionChecker)->OnInvalidAction(); return; } } // 일반 슛 액션이라면 현재 시점에 가능한 것인지 인풋 시그널 간격의 시간과 체크. if( m_pProjectileCountInfo ) { map::const_iterator iterCooltime = m_pProjectileCountInfo->mapBasicShootActionCoolTime.find( nActionIndex ); if( m_pProjectileCountInfo->mapBasicShootActionCoolTime.end() != iterCooltime ) { DWORD dwTerm = timeGetTime() - m_dwLastBasicShootActionTime; DWORD dwCoolTime = DWORD((float)m_dwLastBasicShootCoolTime/m_fFrameSpeed); if( dwTerm < dwCoolTime ) { // 정해진 시간 간격 이하로 일반 공격 패킷이 왔음. 핵입니다.. #ifndef _FINAL_BUILD OutputDebug( "CDnPlayerActor-CS_CMDACTION: 액션툴에서 정해진 시간 간격 이하로 일반 슛 액션이 왔음. 핵으로 판단.\n" ); #endif // #ifndef _FINAL_BUILD return; } m_dwLastBasicShootActionTime = timeGetTime(); m_dwLastBasicShootCoolTime = iterCooltime->second; } } if( IsCustomAction() ) ResetCustomAction(); // 스킬 채인이 아닌 경우라면 현재 cmdaction 패킷이 스킬에서 사용되는 액션인지 체크한다. #26467 // 위에서 스킬 체인에 대한 시그널 데이터 검증이 다 끝나야 이쪽으로 흐름이 오기 때문에 안심하고 확인하면 된다. if( false == bSkillChain ) _CheckProcessSkillActioncChange( pStruct->szName.c_str() ); CmdAction( pStruct->szName.c_str(), nLoopCount, fBlendFrame, false, false, bSkillChain ); // CS_CMDACTION 패킷이 왔을 때 바로 업데이트 해주도록 변경. _UpdateMaxProjectileCount( nActionIndex ); m_bUpdatedProjectileInfoFromCmdAction = true; // 서버나 클라 둘 중에 상태효과쪽에서 발생시킨 액션. if( bFromStateBlow ) m_pStateBlow->OnCmdActionFromPacket( pStruct->szName.c_str() ); // #ifdef _USE_VOICECHAT m_nVoiceRotate = (int)EtToDegree( acos( EtVec2Dot( &EtVector2( 0.f, 1.f ), &vZVec ) ) ); if( vZVec.x > 0.0f ) m_nVoiceRotate = 360 - m_nVoiceRotate; m_nVoiceRotate = (m_nVoiceRotate + 90) % 360; m_pSession->SetVoicePos((int)vPos.x, (int)vPos.y, (int)vPos.z, m_nVoiceRotate); #endif } break; case eActor::CS_CMDMIXEDACTION: { // Note: 액티브 패시브 스킬 사용 중에 일반 액션을 하게 된다면 체크해서 사용중인 스킬을 종료 시킨다. // 상태효과가 남아서 일반 공격에도 영향을 미치는 것을 막기 위해서. if( IsProcessSkill() ) { if( (m_hProcessSkill->GetSkillType() == CDnSkill::Passive || m_hProcessSkill->GetSkillType() == CDnSkill::AutoPassive) && m_hProcessSkill->GetDurationType() == CDnSkill::Instantly ) { m_hProcessSkill->OnEnd( CDnActionBase::m_LocalTime, 0.f ); m_hProcessSkill.Identity(); } } CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos; int nActionIndex, nMaintenanceBone, nActionBone; float fFrame, fBlendFrame; EtVector3 vLook; DWORD dwGap; Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &nActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Stream.Read( &nActionBone, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &nMaintenanceBone, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &fFrame, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 10.f ); Stream.Read( &fBlendFrame, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 10.f ); Stream.Read( &m_cMovePushKeyFlag, sizeof(char) ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); ActionElementStruct *pStruct = GetElement( nActionIndex ); if( pStruct == NULL ) break; // 기본적으로 cmdaction 으로 날아오는 Skill_ 관련 패킷은 잘못된 것이다. (스킬 체인 입력을 제외하고) // 현재 돌아가는 예외 액션 모음에 없다면 무시한다. if( CheckSkillAction(pStruct->szName.c_str()) == true ) { ((CDnPlayerActionChecker*)m_pPlayerActionChecker)->OnInvalidAction(); break; } bool bCheckStandAction = CDnChangeStandActionBlow::CheckUsableAction( GetActorHandle(), true, pStruct->szName.c_str() ); if( bCheckStandAction == false ) { ((CDnPlayerActionChecker*)m_pPlayerActionChecker)->OnInvalidAction(); return; } // 일반 슛 액션이라면 현재 시점에 가능한 것인지 인풋 시그널 간격의 시간과 체크. // 새로 액션을 취하는 경우(0 프레임으로 패킷 온다.)에만 기본 공격 액션 쿨타임을 체크한다. // 만약 핵을 썼다면 0 프레임으로 보내면 액션 쿨타임에 걸리고, 0 프레임 이상을 보낸다면 // 발사체 갯수나 기타 핵관련 인증 정보를 업데이트를 해주지 않도록 한다. 그 뒤로 쏘는 발사체는 무효가 된다.. // cmdaction 쪽은 무조건 0 프레임으로 시작하므로 MIXEDACTION 만 클라에서 보내주는 프레임을 체크하면 된다. bool bActionCoolTimeChecked = false; if( m_pProjectileCountInfo ) { map::const_iterator iterCooltime = m_pProjectileCountInfo->mapBasicShootActionCoolTime.find( nActionIndex ); if( m_pProjectileCountInfo->mapBasicShootActionCoolTime.end() != iterCooltime ) { if( 0.0f == fFrame ) { DWORD dwTerm = timeGetTime() - m_dwLastBasicShootActionTime; DWORD dwCoolTime = DWORD((float)m_dwLastBasicShootCoolTime/m_fFrameSpeed); if( dwTerm < dwCoolTime ) { // 정해진 시간 간격 이하로 일반 공격 패킷이 왔음. 핵입니다.. #ifndef _FINAL_BUILD OutputDebug( "CDnPlayerActor-CS_CMDMIXEDACTION: 액션툴에서 정해진 시간 간격 이하로 일반 슛 액션이 왔음. 핵으로 판단.\n" ); #endif // #ifndef _FINAL_BUILD return; } m_dwLastBasicShootActionTime = timeGetTime(); m_dwLastBasicShootCoolTime = iterCooltime->second; bActionCoolTimeChecked = true; } } } if( false == bActionCoolTimeChecked ) { // 프레임 값이 있는 경우엔 워리어, 클러릭등이 평타 한 번 하고 이동할 때임. 이 패킷을 임의의 평타액션으로 hit 시그널 있는 프레임으로 마구 // 보내 계속 hit 하는 핵이 나올 수 있어 방어한다. 방법은 중간중간 CmdStop 이나 CmdMove 등으로 Stand 액션으로 변경하면서 MixedAction 을 보내면 // 서버로서는 검증할 방법이 없으므로 쿨타임으로 체크를 한다.. LOCAL_TIME MixedActionCoolTime = 500; if( CDnActionBase::m_LocalTime - m_MixedActionTimeStamp < MixedActionCoolTime ) { if( CDnActionBase::m_LocalTime - m_MixedActionTimeStamp < 0 ) m_MixedActionTimeStamp = CDnActionBase::m_LocalTime; // 일정 시간 간격 이상으로 들어오면 핵으로 판단. return; } m_MixedActionTimeStamp = CDnActionBase::m_LocalTime; } std::string szAction = pStruct->szName; m_szActionBoneName = GetBoneName(nActionBone); m_szMaintenanceBoneName = GetBoneName(nMaintenanceBone); if( IsCustomAction() ) ResetCustomAction(); SetCustomAction( szAction.c_str(), fFrame ); // MixedAction 은 OnChangeAction() 함수가 호출이 안되므로 업데이트 해주어야 함. //_UpdateMaxProjectileCount( nActionIndex, true ); // 프레임 값이 0 이 아니면 플레이어의 방향만 바꿔주는 경우다. if( 0.0f == fFrame ) { _UpdateMaxProjectileCount( nActionIndex ); } } break; case eActor::CS_PROJECTILE: { // next 액션이 있고 거의 끝 프레임에 다다랐을 경우엔 액션이 바뀌기 직전이므로 클라에서 // 액션을 바꿔서 발사체를 쏘더라도 서버는 못따라갔을 수 있다. 한발 여유를 준다. // 핀포인트 샷처럼 빠르게 loop 액션을 행하는 경우 문제가 될 수 있다. #ifdef PRE_ADD_LOOP_PROJECTILE ActionElementStruct* pActionElement = GetElement( m_szAction.c_str() ); if( pActionElement && false == pActionElement->szNextActionName.empty() ) { // TODO: 추가적으로 IsCustomAction() 으로 mixed 애니메이션을 체크해서 // 정확하게 발사체 인증 정보를 업데이트 해준다. 지금 이렇게만 해두면 뒤로 가면서 // 화살 쏘면 Move_Back 만 업데이트 되어 발사체 갯수는 0 이 됨. int iNextActionElementIndex = GetElementIndex( pActionElement->szNextActionName.c_str() ); if( GetCurrentActionIndex() == iNextActionElementIndex ) _UpdateMaxProjectileCount( iNextActionElementIndex ); } #endif // #ifdef PRE_ADD_LOOP_PROJECTILE CDnProjectile* pProjectile = CDnProjectile::CreatePlayerProjectileFromClientPacket( GetMySmartPtr(), pPacket ); } break; case eActor::CS_CMDLOOK: { CPacketCompressStream Stream( pPacket, 128 ); EtVector2 vLook, vZVec; EtVector3 vXVec; bool bForce; Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &vLook, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &bForce, sizeof(bool) ); Look( vLook, bForce ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); } break; case eActor::CS_USESKILL: { CPacketCompressStream Stream( pPacket, 128 ); int nSkillTableID = 0; int nEnchantSkillID = 0; // #38294 게임 서버에서는 사용할 일 없다. 빌리지 서버에서 ex 스킬 사용시 정보를 알려주기 위한 용도. BYTE cLevel; EtVector2 vLook, vZVec; EtVector3 vXVec; #ifdef PRE_ADD_POSITION_SYNC_BY_SKILL_USAGE EtVector3 vPos; #endif bool bUseApplySkillItem = false; bool bAutoUseFromServer = false; // 겜서버에선 사용하지 않는 데이터. Stream.Read( &nSkillTableID, sizeof(int) ); Stream.Read( &cLevel, sizeof(char) ); Stream.Read( &bUseApplySkillItem, sizeof(bool) ); Stream.Read( m_abSignalSkillCheck, sizeof(m_abSignalSkillCheck) ); Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &vLook, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &bAutoUseFromServer, sizeof(bool) ); Stream.Read( &nEnchantSkillID, sizeof(int) ); #ifdef PRE_ADD_POSITION_SYNC_BY_SKILL_USAGE Stream.Read(&vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); SetPosition( vPos ); #endif Look( vLook, true ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); // 연속으로 이어서 스킬이 사용되는 경우 CanMove 가 false 인 경우가 있을 수 있는데 // 이런 경우엔 Movable 로 스킬 발동조건을 걸어놓은 경우 씹힐 수 있기 때문에 true 로 바꿔줌. // 이미 클라이언트에선 사용가능 상태라서 패킷이 온 상황이다. (정상적인 경우라면) #14127 if( false == m_bMovable ) m_bMovable = true; //////////////////////////////////////////////////////////////////////////////////////////////////// // Note: 게임서버에서는 ApplySkill이 붙은 아이템 사용을 사용한 클라쪽에서 보내기 때문에 그 시점에 이미 처리합니다.. // 그런고로 여기선 무시. 다른 클라이언트들에게 적용시키기 위한 플래그입니다. if( false == bUseApplySkillItem ) { bool bSkipAirCondition = false; if(CheckSkipAirCondition(nSkillTableID)) { SetSignalSkillCheck(2,true); bSkipAirCondition = true; } // 상태가 Air인경우에 다음액션이 GroundMovable 조건이 있다면 클라와 서버의 갭으로 인해 Air 체크에 걸려서 취소되는 경우가 있다. m_bUseSignalSkillCheck = (m_abSignalSkillCheck[ 0 ] || m_abSignalSkillCheck[ 1 ] || m_abSignalSkillCheck[ 2 ]); if( m_hProcessSkill ) { // #25154 오라 스킬은 오라를 껐을 때 onend 된다. if( false == m_hProcessSkill->IsAuraOn() ) { m_hProcessSkill->OnEnd( CDnActionBase::m_LocalTime, 0.f ); } else { // #26002 오라 스킬이 종료된 거라면 자기 자신에게 적용하는 상태효과 리스트를 비우도록 한다. // 안그러면 다른 스킬을 사용할 때 영향을 미치게 된다. // 원래 ApplyStateEffect 시그널을 사용하는 의도가 중간에 피격시 스킬 액션이 끊기면 적용이 안되게 하는 것이므로, // 유저가 곧바로 다른 스킬을 사용해서 자기 자신에게 적용하는 상태효과 시그널이 돌기 전에 액션이 바뀌면 // 당연히 상태효과가 적용 안되게 되므로 액션 디자인을 할 때 이부분이 고려되어 있다는 전제 하에 이렇게 처리한다. if( IsEnabledAuraSkill() ) ClearSelfStateSignalBlowQueue(); } m_hProcessSkill.Identity(); } if (GetActorHandle() && GetActorHandle()->IsAppliedThisStateBlow(STATE_BLOW::BLOW_345)) //rlkt_mechanicMODE { if (IsExistSkill(nSkillTableID, cLevel) == false) AddSkill(nSkillTableID, cLevel); } if (IsExistSkill(nSkillTableID, cLevel) == false) { ((CDnPlayerSkillChecker*)m_pPlayerSkillChecker)->OnInvalidUseSkill(nSkillTableID, CDnSkill::UsingResult::NoExistSkill); // Hack!!! break; } #if defined( PRE_FIX_CHANGESTAND_HACK ) CDnChangeStandActionBlow::ReleaseStandChangeSkill( GetActorHandle(), true ); #endif CDnSkill::UsingResult eResult = UseSkill( nSkillTableID ); if( eResult != CDnSkill::UsingResult::Success ) { // 쿨타임 부족으로 인해 스킬 사용 실패되면 핵으로 판단. // 나머지는 클라에서 스킬 사용하는 순간 이미 서버에서 맞은 상태로 판단하여 클라이언트로 결과를 통보. // 클라이언트 쪽에서는 쿨타임을 리셋시킨다. ((CDnPlayerSkillChecker*)m_pPlayerSkillChecker)->OnInvalidUseSkill( nSkillTableID, eResult ); switch( eResult ) { case CDnSkill::FailedByCooltime: break; case CDnSkill::FailedByUsableChecker: { #ifdef PRE_FIX_69469 if( GetProcessSkill() ) CancelUsingSkill(); SetAction( "Stand", 0.f, 3.f ); #endif const char* pCurrentAction = GetCurrentAction(); int iCurrentActionIndex = GetElementIndex( pCurrentAction ); BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream LocalStream( pBuffer, 32 ); LocalStream.Write( &nSkillTableID, sizeof(int) ); LocalStream.Write( &eResult, sizeof(CDnSkill::UsingResult) ); LocalStream.Write( &iCurrentActionIndex, sizeof(int) ); Send( eActor::SC_SKILLUSING_FAILED, &LocalStream ); } break; } } if(bSkipAirCondition) SetSignalSkillCheck(2,false); // 다시 해제해준다. // 만약 장비아이템 스킬이라면, if( m_hProcessSkill && m_hProcessSkill->IsEquipItemSkill() ) { m_afLastEquipItemSkillDelayTime = m_hProcessSkill->GetDelayTime(); m_afLastEquipItemSkillRemainTime = m_hProcessSkill->GetElapsedDelayTime(); } m_bUseSignalSkillCheck = false; ZeroMemory( m_abSignalSkillCheck, sizeof(m_abSignalSkillCheck) ); } } break; case eActor::CS_VIEWSYNC: { if( GetGameRoom() && GetGameRoom()->GetGameTask() && !GetGameRoom()->GetGameTask()->IsSyncComplete() ) break; CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos, vXVec; EtVector2 vZVec, vLook; DWORD dwGap; Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &vLook, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); Look( vLook, false ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); m_pPlayerSpeedHackChecker->OnSyncPosition( vPos ); SetPosition( vPos ); // SetMagnetPosition( vPos ); } break; case eActor::CS_CMDTOGGLEBATTLE: { CPacketCompressStream Stream( pPacket, 128 ); bool bBattle; Stream.Read( &bBattle, sizeof(bool) ); CmdToggleBattle( bBattle ); } break; case eActor::CS_CMDPASSIVESKILLACTION: { CPacketCompressStream Stream( pPacket, 128 ); int nActionIndex, nLoopCount, nSkillID; BYTE cLevel; float fBlendFrame; float fStartFrame; EtVector3 vXVec, vPos; EtVector2 vLook, vZVec; bool bChargeKey = false; Stream.Read( &nSkillID, sizeof(int)); Stream.Read( &cLevel, sizeof(char) ); Stream.Read( &nActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Stream.Read( &nLoopCount, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &fBlendFrame, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 10.f ); Stream.Read( &fStartFrame, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 10.0f ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &vLook, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); Stream.Read( &m_cMovePushKeyFlag, sizeof(char) ); Stream.Read( &bChargeKey, sizeof(bool) ); bool bOnlyCheck = false; Stream.Read( &bOnlyCheck, sizeof(bool) ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); ResetMove(); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); Look( vLook ); //SetMagnetPosition( vPos ); SetPosition( vPos ); ActionElementStruct *pStruct = GetElement( nActionIndex ); if( pStruct == NULL ) { // Hack!! break; } // 핵체크를 위해 커스텀 액션 정보도 필요하므로 몇 줄 아래에서 정보 빼내고 리셋합니다. //if( IsCustomAction() ) ResetCustomAction(); // Tumble 류 액션은 서버에서 Move 액션 중이 아니기 때문에 아래 핵체크에 걸려 실행이 안된다. // Tumble 액션 요청이 여기로 왔을 때 서버에서 Stand 액션 상태인 이유는 이 패킷이 오기 전에 // CMDSTOP 패킷이 먼저 오기 ‹š문이다. Tumble 로 전이할 수 있는 시그널은 Move 액션에 있기 때문에 // 핵체크에 걸리게 된다. 현재로썬 서버에서 판단할 수 없는 부분이므로 Tumble 류 액션은 그냥 통과시킨다. // 다만 클라에서 온 skillid 를 믿을 수 없으므로 Tumble 류 액션이 왔을 땐 관련된 패시브 스킬 id 로 바꿔서 // 흘려보내준다. 그러면, 스킬의 쿨타임이나 보유 여부는 CmdInputHasPassiveSkill 에서도 체크해서 임의로 사용하는 경우엔 걸리게 된다. int iCurrentActionIndex = GetCurrentActionIndex(); vector vlCheckActionIndices; vlCheckActionIndices.push_back( iCurrentActionIndex ); // #23245 패링 시리즈들 처럼 상태효과에서 직접 액션을 조작하는 경우 서버쪽에서 액션 큐에만 넣고 프레임 갱신이 // 되기 전에 클라이언에서 이미 바뀐 액션에서의 패시브 스킬 사용 요청이 오는 경우가 있으므로 queue 된 액션까지 감안한다. int iQueuedActionIndex = -1; if( false == m_szActionQueue.empty() ) { iQueuedActionIndex = GetElementIndex( m_szActionQueue.c_str() ); vlCheckActionIndices.push_back( iQueuedActionIndex ); } // mixed action 중이라면 해당 액션까지 포함. if( IsCustomAction() ) { int iCustomActionIndex = GetCustomActionIndex(); vlCheckActionIndices.push_back( iCustomActionIndex ); ResetCustomAction(); } // 현재 액션이 cmdstop 으로 인해 stand 일 때만 tumblehelper 를 사용한다. bool bNowStand = (0 != strstr( m_szAction.c_str(), "Stand" )); // 이동 중인 경우도 방향 관계없이 어떤 덤블링 액션이라도 수행할 수 있도록 Move 상태에서도 tunble helper 를 사용한다. // 클라이언트에서 옆으로 이동하다가 곧바로 shift+앞 방향키로 다른 방향으로 대쉬하도록 여기에 패킷이 오는 경우가 있어서 감안한다. // 안 그러면 현재 방향으로의 move 액션 시그널에 걸리지 않아서 핵으로 간주되어 사용되지 않는다. bool bNowMove = IsMove(); bool bValid = false; for( int k = 0; k < (int)vlCheckActionIndices.size(); ++k ) { int iCheckActionIndex = vlCheckActionIndices.at( k ); if( bNowStand || bNowMove ) { map::iterator iterTumble = m_mapTumbleHelper.find( nActionIndex ); if( m_mapTumbleHelper.end() != iterTumble ) { // 아무 이동 액션에서 Tumble 이 가능하므로 현재 액션을 이걸로 셋팅해주어야 Tumble 이 핵 체크에서 valid 하게 떨어진다. iCheckActionIndex = iterTumble->second; } } if( !m_pPassiveSkillInfo || ( m_pPassiveSkillInfo && m_pPassiveSkillInfo->mapPassiveSkillInfo.empty() ) ) continue; // 우선 패킷으로 온 액션이 현재 액션에서 inputhaspassive 스킬로 전이 될 수 있는지 확인. map >::const_iterator iter = m_pPassiveSkillInfo->mapPassiveSkillInfo.find( iCheckActionIndex ); if( m_pPassiveSkillInfo->mapPassiveSkillInfo.end() != iter ) { const vector& vlPassiveSkillSignalInfos = iter->second; // 먼저 액션이름이 정확히 일치하는 것이 있다면 해당 시그널이 우선이고, 문자열 포함되는 것은 그 다음 순위이다. for( int i = 0; i < (int)vlPassiveSkillSignalInfos.size(); ++i ) { const CDnActionSpecificInfo::S_PASSIVESKILL_SIGNAL_INFO& PassiveSkillInfo = vlPassiveSkillSignalInfos.at( i ); if( PassiveSkillInfo.strChangeActionName == pStruct->szName || PassiveSkillInfo.strEXSkillChangeActionName == pStruct->szName ) { // 클라로부터 온 요청에 있는 액션 이름이 현재 액션에서 전이될 수 있는 패시브 스킬 액션이지만 // 보유하고 있는 패시브 스킬인지 한번 더 체크한다. nSkillID = PassiveSkillInfo.iSkillID; if( IsExistSkill( PassiveSkillInfo.iSkillID ) ) { // 강화 패시브 스킬로 패시브 스킬이 강화가 된 경우. // 강화 패시브 스킬의 액션이 패킷으로 와야 정상. DnSkillHandle hSkill = FindSkill( PassiveSkillInfo.iSkillID ); if( hSkill->IsEnchantedSkill() ) { if( PassiveSkillInfo.strEXSkillChangeActionName == pStruct->szName ) bValid = true; } else bValid = true; // 차지샷인경우엔 액션의 쿨타임을 따로 측정한다. if( strstr( pStruct->szName.c_str(), "ChargeShoot_" ) ) { if( timeGetTime() - m_dwLastChargeShootTime < 1500 ) { // 차지샷 액션을 패시브 스킬 패킷으로 계속 날리는 핵임. bValid = false; } else m_dwLastChargeShootTime = timeGetTime(); } } break; } } // 일치하는 액션 이름을 찾지 못한 경우. if( false == bValid ) { for( int i = 0; i < (int)vlPassiveSkillSignalInfos.size(); ++i ) { const CDnActionSpecificInfo::S_PASSIVESKILL_SIGNAL_INFO& PassiveSkillInfo = vlPassiveSkillSignalInfos.at( i ); if( strstr( pStruct->szName.c_str(), PassiveSkillInfo.strChangeActionName.c_str() ) ) // 소서리스는 _Book, _Orb 이런게 붙기 때문에 포함여부로 체크한다. { // 클라로부터 온 요청에 있는 액션 이름이 현재 액션에서 전이될 수 있는 패시브 스킬 액션이지만 // 보유하고 있는 패시브 스킬인지 한번 더 체크한다. nSkillID = PassiveSkillInfo.iSkillID; if( IsExistSkill( PassiveSkillInfo.iSkillID ) ) { // 강화 패시브 스킬로 패시브 스킬이 강화가 된 경우. // 강화 패시브 스킬의 액션이 패킷으로 와야 정상. DnSkillHandle hSkill = FindSkill( PassiveSkillInfo.iSkillID ); if( hSkill->IsEnchantedSkill() ) { if( PassiveSkillInfo.strEXSkillChangeActionName == pStruct->szName ) bValid = true; } else bValid = true; // 차지샷인경우엔 액션의 쿨타임을 따로 측정한다. if( strstr( pStruct->szName.c_str(), "ChargeShoot_" ) ) { if( timeGetTime() - m_dwLastChargeShootTime < 1500 ) { // 차지샷 액션을 패시브 스킬 패킷으로 계속 날리는 핵임. bValid = false; } else m_dwLastChargeShootTime = timeGetTime(); } } } if( bValid ) break; } } if( bValid ) break; } } #if defined( PRE_FIX_CHANGESTAND_HACK ) CDnChangeStandActionBlow::ReleaseStandChangeSkill( GetActorHandle(), true ); #endif if( false == bValid ) { // Hack!! 현재 액션에서 전이될 수 없는,, 갖고 있는 패시브 스킬 시그널들에 없는 액션을 실행하려 함. // 아니면 보유하고 있지 않은 패시브 스킬의 액션을 실행하려 하거나.. // 핵이다. #ifdef PRE_FIX_SKILL_FAILED_BY_ACTION #ifdef PRE_FIX_69469 if( GetProcessSkill() ) CancelUsingSkill(); SetAction( "Stand", 0.f, 3.f ); #endif const char* pCurrentAction = GetCurrentAction(); int iCurrentActionIndex = GetElementIndex( pCurrentAction ); BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream LocalStream( pBuffer, 32 ); CDnSkill::UsingResult eResult = CDnSkill::FailedByInvailedAction; LocalStream.Write( &nSkillID, sizeof(int) ); LocalStream.Write( &eResult, sizeof(CDnSkill::UsingResult) ); LocalStream.Write( &iCurrentActionIndex, sizeof(int) ); Send( eActor::SC_SKILLUSING_FAILED, &LocalStream ); #endif return; } // 위의 핵 테스트를 통과하면 나머 nLoopCount 나 fBlendFrame 이나 fStartFrame 등등을 그대로 사용하게 되는데 // 이 부분이 문제될 시에는 시그널에 있는 데이터를 그대로 사용토록 한다. CmdPassiveSkillAction( nSkillID, pStruct->szName.c_str(), nLoopCount, fBlendFrame, fStartFrame, bChargeKey, false, bOnlyCheck ); m_pPlayerSpeedHackChecker->OnSyncPosition( vPos ); } break; case eActor::CS_POSREV: { if( GetGameRoom() && GetGameRoom()->GetGameTask() && !GetGameRoom()->GetGameTask()->IsSyncComplete() ) break; CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos; bool bMove; DWORD dwGap; Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &bMove, sizeof(bool) ); _ASSERT( m_pPlayerSpeedHackChecker ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); m_pPlayerSpeedHackChecker->OnSyncPosition( vPos ); SetPosition( vPos ); if( bMove ) { EtVector3 vXVec; EtVector2 vZVec; Stream.Read( &vZVec, sizeof(EtVector2), CPacketCompressStream::VECTOR2_SHORT ); EtVec3Cross( &vXVec, &EtVector3( 0.f, 1.f, 0.f ), &EtVec2toVec3( vZVec ) ); SetMoveVectorX( vXVec ); SetMoveVectorZ( EtVec2toVec3( vZVec ) ); if( EtVec3LengthSq( GetMovePos() ) > 0.f ) { float fXSpeed = 0.f, fZSpeed = 0.f; if( m_cMovePushKeyFlag & 0x01 ) fXSpeed = -100000.f; if( m_cMovePushKeyFlag & 0x02 ) fXSpeed = 100000.f; if( m_cMovePushKeyFlag & 0x04 ) fZSpeed = 100000.f; if( m_cMovePushKeyFlag & 0x08 ) fZSpeed = -100000.f; vPos += ( vXVec * fXSpeed ); vPos += ( EtVec2toVec3( vZVec ) * fZSpeed ); MovePos( vPos, false ); } } } break; case eActor::CS_ONDROP: { CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos; DWORD dwGap; Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &dwGap, sizeof(DWORD) ); _ASSERT( m_pPlayerSpeedHackChecker ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); SetPosition( vPos ); SetJumpMovement( EtVector2( 0.f, 0.f ) ); } break; case eActor::CS_CMDREMOVESTATEEFFECT: { CPacketCompressStream Stream( pPacket, 32 ); STATE_BLOW::emBLOW_INDEX emBlowIndex; Stream.Read( &emBlowIndex, sizeof(STATE_BLOW::emBLOW_INDEX) ); // 다른 클라이언트들에게도 상태효과 제거한 클라에서 요청한 제거 패킷을 알아야 하기 때문에 // 다시 브로드캐스팅 시킨다. 상태효과 제거 요청한 클라에선 이미 제거된 상태. CmdRemoveStateEffect( emBlowIndex ); break; } break; #if defined(PRE_FIX_57706) case eActor::CS_CMDREMOVESTATEEFFECTFROMID: { CPacketCompressStream Stream( pPacket, 32 ); int nServerBlowID = 0; Stream.Read( &nServerBlowID, sizeof(int) ); //클라이언트만 제거 시간 변경을 알고 있기 때문에 클라이언트에서 서버로 패킷을 전송하고, 서버에서 //브로드캐스팅 시겨 다른 클라이언트도 해당 상태효과 제거를 한다. CmdRemoveStateEffectFromID(nServerBlowID); break; } break; #endif // PRE_FIX_57706 case eActor::CS_CMDTOGGLEWEAPONORDER: { CPacketCompressStream Stream( pPacket, 32 ); int nEquipIndex; bool bViewCash; Stream.Read( &nEquipIndex, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &bViewCash, sizeof(bool) ); CmdToggleWeaponViewOrder( nEquipIndex, bViewCash ); } break; case eActor::CS_CMDTOGGLEPARTSORDER: { CPacketCompressStream Stream( pPacket, 32 ); int nEquipIndex; bool bViewCash; Stream.Read( &nEquipIndex, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &bViewCash, sizeof(bool) ); if( nEquipIndex == HIDEHELMET_BITINDEX ) CmdToggleHideHelmet( bViewCash ); else CmdTogglePartsViewOrder( nEquipIndex, bViewCash ); if (GetUserSession() && GetUserSession()->GetPartyID() > 0) { if( nEquipIndex == CASHEQUIP_HELMET || nEquipIndex == CASHEQUIP_EARRING || nEquipIndex == HIDEHELMET_BITINDEX ) { for (DWORD i = 0; i < m_pSession->GetGameRoom()->GetUserCount(); i++) { CDNGameRoom::PartyStruct *pStruct = m_pSession->GetGameRoom()->GetPartyData(i); if (pStruct == NULL) continue; pStruct->pSession->SendPartyMemberPart(m_pSession); } } } } break; case eActor::CS_CMDPICKUPITEM: { if( IsDie() ) break; if( IsInvalidPlayerChecker() ) break; CPacketCompressStream Stream( pPacket, 128 ); DWORD dwUniqueID; int nSignalIndex; EtVector3 vPos; int nRange = 100; Stream.Read( &dwUniqueID, sizeof(DWORD) ); Stream.Read( &nSignalIndex, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); CEtActionSignal *pSignal = GetSignal( m_nActionIndex, nSignalIndex ); if( pSignal && pSignal->GetSignalIndex() == STE_PickupItem ) { PickupItemStruct *pSignalStruct = (PickupItemStruct *)pSignal->GetData(); nRange = pSignalStruct->nRange; } m_pPlayerSpeedHackChecker->OnSyncPosition( vPos ); SetPosition( vPos ); if( GetGameRoom() ) { DnDropItemHandle hDropItem = GetGameRoom()->FindDropItem( dwUniqueID ); if( hDropItem ) { ((CDnPlayerPickupChecker*)m_pPlayerPickupChecker)->OnPickupDist( vPos, hDropItem ); if( m_pSession && m_pSession->GetItem() && m_pSession->GetItem()->GetPetEquip() )//플레이어 아이템 착용 장비중 펫을 소환했다면 { const TVehicle* pPet = m_pSession->GetItem()->GetPetEquip(); // 거리측정(2배 오차 허용) float fDist = EtVec2Length( &(EtVector2(hDropItem->GetPosition()->x,hDropItem->GetPosition()->z)-EtVector2(vPos.x,vPos.z) ) ); if( fDist > pPet->nRange*2.f ) return; } else { // 거리측정(2배 오차 허용) float fDist = EtVec2Length( &(EtVector2(hDropItem->GetPosition()->x,hDropItem->GetPosition()->z)-EtVector2(vPos.x,vPos.z) ) ); if( fDist > nRange*2.f ) return; } PickupItemStruct Struct; Struct.nRange = nRange; CmdPickupItem( &Struct, hDropItem ); } } break; } case eActor::CS_CMDESCAPE: { // 여기서 실제 먹히게할지 한번 검중. if( m_LastEscapeTime > 0 && CDnActionBase::m_LocalTime - m_LastEscapeTime < 1000 * 60 * 1 ) break; CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos; Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); SetPosition( vPos ); SetStaticPosition( vPos ); SetPrevPosition( vPos ); if( m_pPlayerSpeedHackChecker ) m_pPlayerSpeedHackChecker->ResetInvalid(); if( m_pPlayerDoNotEnterChecker ) m_pPlayerDoNotEnterChecker->ResetInvalid(); CmdEscape( vPos ); } break; case eActor::CS_CANNONPOSSESS_REQ: { // 대포를 점유하겠다는 클라로부터의 요청. // 요청의 결과도 보내주도록 한다. CPacketCompressStream Stream( pPacket, 128 ); DWORD dwCannonMonsterActorID = 0; MatrixEx Cross; Stream.Read( &dwCannonMonsterActorID, sizeof(DWORD) ); Stream.Read( &Cross, sizeof(MatrixEx) ); bool bSuccess = false; // 대포 몬스터에게 할당. CDnActor *pActor = CDnActor::FindActorFromUniqueID( GetRoom(), dwCannonMonsterActorID ); if( pActor && pActor->GetActorType() == ActorTypeEnum::Cannon ) { CDnCannonMonsterActor* pCannonActor = static_cast( pActor ); // 클라에서 보내준 대포 몬스터의 근처에 이 캐릭터가 있는지 체크한다. // Press Circle 보다 거리가 멀면 핵일 가능성이 있다.. 약간 관용도 값을 주어도 될 듯. bool bValid = false; float fDistanceSQ = EtVec3LengthSq( &EtVector3(*pCannonActor->GetPosition() - *GetPosition()) ); float fPressCircleDist = float(GetUnitSize() + pCannonActor->GetUnitSize()); float fPressCircleDistSQ = fPressCircleDist * fPressCircleDist; if( fPressCircleDistSQ <= fDistanceSQ + 1000.0f || fPressCircleDistSQ - 1000.f <= fDistanceSQ) { // 이미 점유중인지. if( false == pCannonActor->IsPossessed() ) { m_bPlayerCannonMode = true; pCannonActor->SetMasterPlayerActor( GetMySmartPtr() ); m_hCannonMonsterActor = pCannonActor->GetMySmartPtr(); m_Cross = Cross; bSuccess = true; // 대포 점유 성공.. 필요한 정보들과 함께 응답 보내줌.. char acBuffer[ 64 ] = { 0 }; CPacketCompressStream Result( acBuffer, sizeof(acBuffer) ); Result.Write( &bSuccess, sizeof(bool) ); Result.Write( &dwCannonMonsterActorID, sizeof(DWORD) ); Result.Write( &m_Cross.m_vPosition, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Result.Write( &m_Cross.m_vXAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Result.Write( &m_Cross.m_vYAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Result.Write( &m_Cross.m_vZAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Send( eActor::SC_CANNONPOSSESS_RES, &Result ); // 대포상태에서는 밀리지 않도록 설정합니다. 대포에서 내리는 경우에는 리셋시켜줘야합니다. SetWeight(0.f); SetPressLevel(-1); #ifdef PRE_ADD_MODIFY_PLAYER_CANNON DNTableFileFormat* pTableCannon = GetDNTable( CDnTableDB::TCANNON ); if( pTableCannon->IsExistItem( pCannonActor->GetClassID() ) ) { const char* pCannonActionName = pTableCannon->GetFieldFromLablePtr( pCannonActor->GetClassID(), "_StandName" )->GetString(); CmdAction(pCannonActionName); } else { CmdAction( "Stand_Cannon" ); } #else // 대포 액션으로 변경. CmdAction( "Stand_Cannon" ); #endif } } } // 대포 점유 실패.. 응답 보내줌. if( false == bSuccess ) { char acBuffer[ 64 ] = { 0 }; CPacketCompressStream Result( acBuffer, sizeof(acBuffer) ); Result.Write( &bSuccess, sizeof(bool) ); Send( eActor::SC_CANNONPOSSESS_RES, &Result ); } } break; case eActor::CS_CANNONRELEASE: { DWORD dwCannonMonsterID = 0; MatrixEx Cross; CPacketCompressStream Result( pPacket, 64 ); Result.Read( &dwCannonMonsterID, sizeof(DWORD) ); Result.Read( &Cross.m_vPosition, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Result.Read( &Cross.m_vXAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Result.Read( &Cross.m_vYAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Result.Read( &Cross.m_vZAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); CDnActor* pActor = CDnActor::FindActorFromUniqueID( GetRoom(), dwCannonMonsterID ); if( pActor && pActor->GetActorType() == ActorTypeEnum::Cannon ) { CDnCannonMonsterActor* pCannonMonster = static_cast(pActor); pCannonMonster->ClearMasterPlayerActor(); m_Cross = Cross; EndCannonMode(); } } break; case eActor::CS_CANNONROTATESYNC: { _ASSERT( IsCannonMode() ); if( false == IsCannonMode() ) break; CPacketCompressStream Stream( pPacket, 64 ); DWORD dwCannonMonsterID = 0; EtVector3 vDirection( 0.0f, 0.0f, 0.0f ); Stream.Read( &dwCannonMonsterID, sizeof(DWORD) ); Stream.Read( &vDirection, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); CDnActor* pActor = CDnActor::FindActorFromUniqueID( GetRoom(), dwCannonMonsterID ); if( pActor && pActor->GetActorType() == ActorTypeEnum::Cannon ) { CDnCannonMonsterActor* pCannonMonster = static_cast(pActor); EtVec3Normalize( &vDirection, &vDirection ); // 압축되어 전송된 데이터이므로 정규화 한 번 해줌. pCannonMonster->SetCannonLookDirection(&vDirection); // Rotha - 대포 자체를 돌리는게 아닌 대포의 포신을 돌리도록 설정합니다. 포신은 시점만 관리합니다. m_Cross.m_vZAxis = pCannonMonster->GetMatEx()->m_vZAxis; m_Cross.MakeUpCartesianByZAxis(); // press circle 값만큼 밀어주면 된다. m_Cross.m_vPosition = pCannonMonster->GetMatEx()->m_vPosition - (pCannonMonster->GetMatEx()->m_vZAxis*20.0f); EtVector2 vDir; float vDist = 0.0f; _ASSERT( GetPress() == CDnActorState::Press_Circle && pCannonMonster->GetPress() == CDnActorState::Press_Circle ); if( GetPress() == CDnActorState::Press_Circle && pCannonMonster->GetPress() == CDnActorState::Press_Circle ) { if( CheckPressCircle2Clrcle2( pCannonMonster->GetMySmartPtr(), GetMySmartPtr() , vDir, vDist ) ) { m_Cross.m_vPosition.x += vDir.x*vDist; m_Cross.m_vPosition.z += vDir.y*vDist; } } // 이 타이밍에 다른 어떤 이유로 인해 "Stand" 액션을 캐릭터가 취하고 있으면 // Stand_Cannon 로 바꿔준다. // 감전 액션을 하다가 풀리거나 하는 경우. const char* pCurrentAction = GetCurrentAction(); if( strcmp(pCurrentAction, "Stand") == 0 ) { #ifdef PRE_ADD_MODIFY_PLAYER_CANNON DNTableFileFormat* pTableCannon = GetDNTable( CDnTableDB::TCANNON ); if( pTableCannon->IsExistItem( pCannonMonster->GetClassID() ) ) { const char* pCannonActionName = pTableCannon->GetFieldFromLablePtr( pCannonMonster->GetClassID(), "_StandName" )->GetString(); CmdAction(pCannonActionName); } else { CmdAction( "Stand_Cannon" ); } #else // 대포 액션으로 변경. CmdAction( "Stand_Cannon" ); #endif } } } break; case eActor::CS_CANNONTARGETING: { if( false == IsCannonMode() ) break; DWORD dwCannonMonsterID = 0; EtVector3 vCannonDir; // 클라이언트에서 카메라가 바라보고 있는 방향. 결국 대포의 방향. EtVector3 vShootDir; EtVector3 vCannonGroundHitPos; CPacketCompressStream Stream( pPacket, 64 ); Stream.Read( &dwCannonMonsterID, sizeof(DWORD) ); Stream.Read( &vCannonDir, sizeof(EtVector3) ); Stream.Read( &vShootDir, sizeof(EtVector3) ); Stream.Read( &vCannonGroundHitPos, sizeof(EtVector3) ); CDnActor* pActor = CDnActor::FindActorFromUniqueID( GetRoom(), dwCannonMonsterID ); if( pActor && pActor->GetActorType() == ActorTypeEnum::Cannon ) { CDnCannonMonsterActor* pCannonMonster = static_cast(pActor); pCannonMonster->SetCannonProjectileInfo( vCannonDir, vShootDir, vCannonGroundHitPos ); } } break; case eActor::CS_VEHICLE_RIDE_COMPLETE: { if(!IsVehicleMode() || !GetMyVehicleActor()) return; CPacketCompressStream Stream( pPacket, 128 ); bool bComplete = false; Stream.Read( &bComplete, sizeof(bool) ); if(bComplete) { DNVector( DnBlowHandle ) vlhFrameBlows; DNVector( DnBlowHandle ) vlhMoveSpeedBlows; m_pStateBlow->GetStateBlowFromBlowIndex( STATE_BLOW::BLOW_025, vlhFrameBlows ); m_pStateBlow->GetStateBlowFromBlowIndex( STATE_BLOW::BLOW_076, vlhMoveSpeedBlows ); if(vlhFrameBlows.size() > 0) { for(DWORD i=0 ; iCmdAddStateEffect( vlhFrameBlows[i]->GetParentSkillInfo() , vlhFrameBlows[i]->GetBlowIndex() , (int)(vlhFrameBlows[i]->GetDurationTime() * 1000) , vlhFrameBlows[i]->GetValue() ); } } if(vlhMoveSpeedBlows.size() > 0) { for(DWORD i=0 ; iCmdAddStateEffect( vlhMoveSpeedBlows[i]->GetParentSkillInfo() , vlhMoveSpeedBlows[i]->GetBlowIndex() , (int)(vlhMoveSpeedBlows[i]->GetDurationTime() * 1000) , vlhMoveSpeedBlows[i]->GetValue() ); } } } } break; case eActor::CS_SYNCPRESSEDPOS: { if( GetGameRoom() && GetGameRoom()->GetGameTask() && !GetGameRoom()->GetGameTask()->IsSyncComplete() ) break; CPacketCompressStream Stream( pPacket, 128 ); EtVector3 vPos; DWORD dwGap; Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); m_pPlayerSpeedHackChecker->OnSyncDatumGap( dwGap ); SetPosition( vPos ); // SetMagnetPosition( vPos ); } break; case eActor::CS_UDP_PING: { CPacketCompressStream Stream( pPacket, 128 ); DWORD dwTick = 0; Stream.Read( &dwTick, sizeof(dwTick) ); m_pSession->RecvUdpPing( dwTick ); break; } case eActor::CS_SUMMONOFF: { CPacketCompressStream Stream( pPacket, 64 ); DWORD dwSummonMonsterUniqueID = 0; Stream.Read( &dwSummonMonsterUniqueID, sizeof(DWORD) ); CDnActor* pSummonMonster = CDnActor::FindActorFromUniqueID( GetRoom(), dwSummonMonsterUniqueID ); if (pSummonMonster) pSummonMonster->CmdSuicide(false, false); } break; #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) // case eActor::CS_TOTAL_LEVEL: // { // int nTotalLevel = 0; // // CPacketCompressStream Stream( pPacket, 128 ); // Stream.Read( &nTotalLevel, sizeof(int) ); // // UpdateTotalLevel(nTotalLevel); // } // break; case eActor::CS_ADD_TOTAL_LEVEL_SKILL: { int nSlotIndex = -1; int nSkillID = 0; bool isInitialize = false; CPacketCompressStream Stream( pPacket, 128 ); Stream.Read( &nSlotIndex, sizeof(int) ); Stream.Read( &nSkillID, sizeof(int) ); Stream.Read( &isInitialize, sizeof(bool) ); if( nSlotIndex >= TotalLevelSkill::Common::MAXSLOTCOUNT ) break; if(!m_pSession->AddTotalLevelSkillData(nSlotIndex, nSkillID, isInitialize)) { break; } else AddTotalLevelSkill(nSlotIndex, nSkillID, isInitialize); m_pSession->SendAddTotalLevelSkill(m_pSession->GetSessionID(), nSlotIndex, nSkillID, isInitialize); } break; case eActor::CS_REMOVE_TOTAL_LEVEL_SKILL: { int nSlotIndex = -1; CPacketCompressStream Stream( pPacket, 128 ); Stream.Read( &nSlotIndex, sizeof(int) ); if( nSlotIndex < 0 || nSlotIndex >= TotalLevelSkill::Common::MAXSLOTCOUNT ) break; RemoveTotalLevelSkill(nSlotIndex); m_pSession->AddTotalLevelSkillData(nSlotIndex, 0); m_pSession->SendDelTotalLevelSkill(m_pSession->GetSessionID(), nSlotIndex); } break; // case eActor::CS_TOTAL_LEVEL_SKILL_ACTIVE_LIST: // { // int nCount = 0; // int nSlotIndex = -1; // int nSkillID = 0; // // CPacketCompressStream Stream( pPacket, 128 ); // Stream.Read( &nCount, sizeof(int) ); // // for (int i = 0; i < nCount; ++i) // { // Stream.Read( &nSlotIndex, sizeof(int) ); // Stream.Read( &nSkillID, sizeof(int) ); // // ActivateTotalLevelSkillSlot(nSlotIndex, true); // AddTotalLevelSkill(nSlotIndex, nSkillID); // } // } // break; // case eActor::CS_TOTAL_LEVEL_SKILL_CASHSLOT_ACTIVATE: // { // int nSlotIndex = -1; // bool bActivate = false; // // CPacketCompressStream Stream( pPacket, 128 ); // Stream.Read(&nSlotIndex, sizeof(int)); // Stream.Read(&bActivate, sizeof(bool)); // // ActivateTotalLevelSkillCashSlot(bActivate); // } // break; #endif // PRE_ADD_TOTAL_LEVEL_SKILL #if defined(PRE_FIX_68898) case eActor::CS_SKIP_END_ACTION: { bool isSkipEndAction = false; CPacketCompressStream Stream( pPacket, 128 ); Stream.Read(&isSkipEndAction, sizeof(bool)); SetSkipEndAction(isSkipEndAction); } break; #endif // PRE_FIX_68898 } } void CDnPlayerActor::SyncClassTime( LOCAL_TIME LocalTime ) { MAActorRenderBase::m_LocalTime = LocalTime; CDnActor::SyncClassTime( LocalTime ); } void CDnPlayerActor::ProcessDie( LOCAL_TIME LocalTime, float fDelta ) { // 죽지않아~ if( !IsDie() ) { ToggleGhostMode( false ); return; } if( m_fDieDelta > 0.f ) { m_fDieDelta -= fDelta; if( m_fDieDelta < 0.f ) m_fDieDelta = 0.f; } if( !m_bGhost && m_fDieDelta < m_fMaxDieDelta - 4.f ) { ToggleGhostMode( true ); } if( m_fDieDelta <= 0.f ) { OnFinishProcessDie(); } // 상용 아이템에 스킬 들어갈 수 있고, 추가적으로 문장에 스킬이 들어감. 일단 그냥 악세사리 루프도 돌자. if( m_afLastEquipItemSkillRemainTime != 0.0f ) { m_afLastEquipItemSkillRemainTime -= fDelta; if( m_afLastEquipItemSkillRemainTime < 0.0f ) m_afLastEquipItemSkillRemainTime = 0.0f; } } void CDnPlayerActor::OnKillMonster(DnActorHandle hMonster) { if ( !m_pSession ) return; if ( m_pSession->GetQuest() == NULL ) return; if( hMonster && hMonster->IsMonsterActor() ) { CDnMonsterActor* pMonster = static_cast(hMonster.GetPointer()); if ( pMonster ) { m_pSession->GetQuest()->OnKillMonster(pMonster->GetMonsterClassID()); UpdateKillMonster(); if (pMonster->IsBossKillCheck()) UpdateKillBoss(); } } } void CDnPlayerActor::OnStateBlowProcessAfter() { if( m_uiStateBlowProcessAfterBit&eStateBlowAfterProcessType::eRebirth ) { m_uiStateBlowProcessAfterBit &= ~eStateBlowAfterProcessType::eRebirth; if( !GetGameRoom() ) { _DANGER_POINT(); return; } GetGameRoom()->OnRebirth( GetActorHandle() ); } if( m_uiStateBlowProcessAfterBit & eStateBlowAfterProcessType::eDelZombie ) { m_uiStateBlowProcessAfterBit &= ~eStateBlowAfterProcessType::eDelZombie; if( GetGameRoom() && GetGameRoom()->bIsZombieMode() ) static_cast(static_cast(GetGameRoom())->GetPvPGameMode())->DelZombie( GetActorHandle(), true ); } } void CDnPlayerActor::OnAddStateBlowProcessAfterType( eStateBlowAfterProcessType Type ) { m_uiStateBlowProcessAfterBit |= Type; } void CDnPlayerActor::OnDie( DnActorHandle hHitter ) { #if defined( PRE_ADD_SECONDARY_SKILL ) if( GetUserSession() && GetUserSession()->GetSecondarySkillRepository() ) static_cast(GetUserSession()->GetSecondarySkillRepository())->CancelManufacture(); #endif // #if defined( PRE_ADD_SECONDARY_SKILL ) CDnActor::OnDie( hHitter ); // hitter 쪽 버블 시스템쪽에 타격한 대상이 죽었음을 알린다. ///////////////////////////////////////////// if(hHitter && hHitter->IsPlayerActor()) { CDnPlayerActor *pPlayer = static_cast(hHitter.GetPointer()); boost::shared_ptr pEvent( IDnObserverNotifyEvent::Create( EVENT_PLAYER_KILL_TARGET ) ); pPlayer->Notify( pEvent ); } ////////////////////////////////////////////////////////////////////////// UpdateDead(); AddStageDeathCount(); if(m_pRoom && !m_pRoom->bIsPvPRoom()) { for( DWORD i=0; ipSession ) continue; if( pParty->pSession == m_pSession ) continue; DnActorHandle hActor = pParty->pSession->GetActorHandle(); if( !hActor ) continue; CDnPlayerActor *pPlayer = (CDnPlayerActor *)hActor.GetPointer(); pPlayer->UpdatePartyMemberDead(); } } // 현재 발동중인 스킬을 모두 종료, // 부활하고나면 패시브 스킬은 다시 걸어줘야 함.. if( m_hProcessSkill ) m_hProcessSkill->OnEnd( CDnActionBase::m_LocalTime, 0.0f ); // 발동중인 오토 패시브 스킬이 있다면 종료 EndAutoPassiveSkill( CDnActionBase::m_LocalTime, 0.0f ); // 접두어 스킬 종료 EndPrefixSystemSkill(CDnActionBase::m_LocalTime, 0.0f); // #14340 으로 인해.. 죽었을 때 패시브 스킬은 없애지 않는 것으로 수정됨. // 오라스킬, 토글스킬이 켜져 있다면 꺼준다. if( IsEnabledToggleSkill() ) OnSkillToggle( m_hToggleSkill, false ); if( IsEnabledAuraSkill() ) OnSkillAura( m_hAuraSkill, false ); // 나머지, 다른 플레이어나 몹에 의해 걸려 있는 상태효과 모두 없앤다. RemoveAllBlowExpectPassiveSkill(); // 인벤토리 아이템 쿨타임 초기화 해준다. if( m_pSession && m_pSession->GetItem() ) { //m_pSession->GetItem()->ResetCoolTime(); } SetSP(0); bool bIgnoreDuration = false; if (bIgnoreDuration == false) { // 내구도 감소시켜준다. bool bDecreaseDurability = true; if( m_pRoom ) { if( m_pRoom->bIsPvPRoom() ) bDecreaseDurability = false; else if( m_pRoom->bIsDLRoom() ) bDecreaseDurability = false; else if( m_pRoom->bIsFarmRoom() ) bDecreaseDurability = false; } if( bDecreaseDurability ) OnDecreaseEquipDurability( GetDeadDurabilityRatio(), true ); } if( GetGameRoom() ) { GetGameRoom()->OnDie( GetActorHandle(), hHitter ); if( GetGameRoom()->GetGameTask() ) GetGameRoom()->GetGameTask()->OnDie( GetActorHandle(), hHitter ); } if( IsCannonMode() ) { static_cast(m_hCannonMonsterActor.GetPointer())->OnMasterPlayerActorDie(); EndCannonMode(); } m_pBubbleSystem->Clear(); if( CDnWorld::IsActive(GetRoom()) ) { CDnWorld::GetInstance(GetRoom()).InsertTriggerEventStore( "DieActionPlayer", GetUniqueID() ); CDnWorld::GetInstance(GetRoom()).OnTriggerEventCallback( "CDnPlayerActor::OnDie", CDnActionBase::m_LocalTime, 0.f ); } } void CDnPlayerActor::OnFinishProcessDie() { if( GetGameRoom() ) GetGameRoom()->OnFinishProcessDie( GetActorHandle() ); } void CDnPlayerActor::ResetActor() { CDnActor::ResetActor(); m_cMovePushKeyFlag = 0; m_LastEscapeTime = 0; if( IsProcessSkill() ) CancelUsingSkill(); // #26902 임시로 추가된 스킬이 있다면 삭제. 클라한테는 패킷으로 날라감. if( IsTempSkillAdded() ) { RemoveAllTempSkill(); EndAddTempSkillAndSendRestoreTempJobChange(); } if( m_pPlayerSpeedHackChecker ) m_pPlayerSpeedHackChecker->ResetInvalid(); map::iterator iter = m_mapEnchantSkillFromBubble.begin(); for( iter; iter != m_mapEnchantSkillFromBubble.end(); ) { SAFE_RELEASE_SPTR( iter->second ); iter = m_mapEnchantSkillFromBubble.erase( iter ); } m_MixedActionTimeStamp = 0; } void CDnPlayerActor::GetBoundingSphere( SSphere &Sphere, bool bActorSize/* = false*/ ) { Sphere.Center = m_Cross.m_vPosition; Sphere.Center.y += 50.f; Sphere.fRadius = 50.f; } void CDnPlayerActor::SetBattleMode( bool bEnable ) { m_bBattleMode = bEnable; } bool CDnPlayerActor::IsCanBattleMode() { if( IsSwapSingleSkin() ) return false; if( m_bBattleMode && !m_hWeapon[0] ) return false; if( IsSpectatorMode() ) return false; return m_bBattleMode; } int CDnPlayerActor::GetLevelUpSkillPoint( int nPrevLevel, int nCurLevel ) { if( nPrevLevel == nCurLevel ) return 0; int nSkillPoint = 0; for( int i=nPrevLevel+1; i<=nCurLevel; i++ ) { int nItemID = ( ( GetClassID() - 1 ) * PLAYER_MAX_LEVEL ) + i; nSkillPoint += CPlayerLevelTable::GetInstance().GetValue( GetJobClassID(), i, CPlayerLevelTable::SkillPoint ); } return nSkillPoint; } void CDnPlayerActor::OnBattleToggle( bool bBattle ) { BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &bBattle, sizeof(bool) ); Send( eActor::SC_CMDTOGGLEBATTLE, &Stream ); } void CDnPlayerActor::OnLevelUp( int nLevel, int nLevelUpAmount ) { if( !IsDie() ) { SetHP( GetMaxHP() ); SetSP( GetMaxSP() ); } int nSkillPoint = GetLevelUpSkillPoint( nLevel - nLevelUpAmount, nLevel ); //레벨업시에는 스킬포인트 두군데 모두 증가 m_pSession->ChangeSkillPoint(nSkillPoint, 0, true, DBDNWorldDef::SkillPointCode::LevelUp, DualSkill::Type::Primary); m_pSession->ChangeSkillPoint(nSkillPoint, 0, true, DBDNWorldDef::SkillPointCode::LevelUp, DualSkill::Type::Secondary); m_pSession->SetLevel(m_nLevel, DBDNWorldDef::CharacterLevelChangeCode::Normal, true); m_pSession->SetExp(m_nExperience, DBDNWorldDef::CharacterExpChangeCode::Dungeon, 0, true); BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &nLevel, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Write( &m_nExperience, sizeof(int) ); Send( eActor::SC_LEVELUP, &Stream ); #if !defined(PRE_DELETE_DUNGEONCLEAR) // 던전 저장 관련 데이타 여기서 한번 리플레쉬 해서 필요없는것들 지워주게 하자 m_pSession->RefreshDungeonEnterLevel(); #endif // #if !defined(PRE_DELETE_DUNGEONCLEAR) // Refresh 해보고 게이트의 변경사항이 생겼으면 게이트인포를 보낸다. if( CDnPartyTask::IsActive(GetRoom()) ) CDnPartyTask::GetInstance(GetRoom()).UpdateGateInfo(); // 레벨에 따라 스킬 소모 SP 가 다르기땜시 언제나 리플레쉬 해줘야한다. for( DWORD i=0; iRefreshDecreaseMP(); } m_pSession->NotifyGuildMemberLevelUp(nLevel); m_pSession->GetEventSystem()->OnEvent( EventSystem::OnLevelUp ); // 사제 졸업 if( m_pSession->GetMasterSystemData()->SimpleInfo.iMasterCount > 0 && nLevel >= static_cast(CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::MasterSystem_GraduateLevel )) ) { if( m_pSession->CheckDBConnection() ) m_pSession->GetDBConnection()->QueryMasterSystemGraduate( m_pSession ); } if( CDnWorld::GetInstance(GetRoom()).GetMapType() == EWorldEnum::MapTypeDungeon && m_pSession->GetPeriodExpItemRate() > 0 ) m_pSession->SetPeriodExpItemRate(); #if defined( PRE_ADD_BESTFRIEND ) m_pSession->BestFriendChangeLevel(nLevel, true); #endif #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) UpdateTotalLevelByCharLevel(); #endif // PRE_ADD_TOTAL_LEVEL_SKILL } void CDnPlayerActor::OnAddExperience( int nAddExperience, int nLogCode, INT64 biFKey ) { m_pSession->ChangeExp(nAddExperience, nLogCode, biFKey); // db저장 안에있음 // 경험치는 마출필요 없다. } void CDnPlayerActor::OnBeginStateBlow( DnBlowHandle hBlow ) { // 나중에 고치자.. DNVector(DnActorHandle) hVecList; ScanActor( GetRoom(), m_Cross.m_vPosition, 2000.f, hVecList ); for( DWORD i=0; iGetAggroSystem(); // 일반던젼 플레이시에는 PlayerActor 는 AggroSystem 이 없다. if( !pAggroSystem ) continue; if( !pAggroSystem->bOnCheckPlayerBeginStateBlow( this ) ) continue; pAggroSystem->OnStateBlowAggro( hBlow ); } } void CDnPlayerActor::UnLockSkill( int nSkillID, INT64 nUnlockPrice/*=0*/ ) { // 이미 언락 되어있는 스킬이 또 들어오면 안되고.. vector::iterator iter = find( m_vlUnlockZeroLevelSkills.begin(), m_vlUnlockZeroLevelSkills.end(), nSkillID ); _ASSERT( m_vlUnlockZeroLevelSkills.end() == iter ); if( m_vlUnlockZeroLevelSkills.end() == iter ) { m_vlUnlockZeroLevelSkills.push_back( nSkillID ); CDnSkillTreeSystem::S_POSSESSED_SKILL_INFO SkillInfo; SkillInfo.iSkillID = nSkillID; SkillInfo.iSkillLevel = 0; SkillInfo.bCurrentLock = false; m_vlPossessedSkill.push_back( SkillInfo ); INT64 biCurrentCoin = 0; INT64 biPickUpCoin = 0; if( nUnlockPrice > 0 ) { biCurrentCoin = m_pSession->GetCoin(); biPickUpCoin = m_pSession->GetPickUpCoin(); m_pSession->SelPickUpCoin(0); } m_pSession->GetDBConnection()->QueryAddSkill(m_pSession, nSkillID, SkillInfo.iSkillLevel, 0, DBDNWorldDef::SkillChangeCode::GainByBuy, nUnlockPrice, biCurrentCoin, biPickUpCoin); } } int CDnPlayerActor::CanAcquireSkillIfUnlock( int nSkillID ) { int nRetCode = ERROR_NONE; CDnSkillTreeSystem* pSkillTreeSystem = g_pDataManager->GetSkillTreeSystem(); CDnSkillTreeSystem::S_TRY_ACQUIRE TryAcquire( GetPossessedSkillInfo() ); CDnSkillTreeSystem::S_OUTPUT Output; TryAcquire.iCurrentCharLevel = GetLevel(); TryAcquire.iTryAcquireSkillID = nSkillID; TryAcquire.iHasSkillPoint = GetAvailSkillPointByJob( nSkillID ); DNTableFileFormat* pSkillTable = GetDNTable( CDnTableDB::TSKILL ); int iNeedJob = pSkillTable->GetFieldFromLablePtr( nSkillID, "_NeedJob" )->GetInteger(); if( IsPassJob( iNeedJob ) ) { pSkillTreeSystem->TryAcquireSkill( TryAcquire, &Output ); // #36858 글로벌 스킬로 서로 엮여 있다면 해당 그룹중에 하나만 배워도 부모 스킬 조건없이 배울 수 있다. bool bAlreadyGlobalSkillAcquired = HasSameGlobalIDSkill( nSkillID ); bool bIgnoreParentSkillCondition = ( (CDnSkillTreeSystem::R_DONT_HAVE_PARENT_SKILL == Output.eResult) || (CDnSkillTreeSystem::R_LOCKED_PARENTSKILL == Output.eResult) || (CDnSkillTreeSystem::R_NOT_ENOUGH_PARENT_SKILL_LEVEL == Output.eResult) ) && true == bAlreadyGlobalSkillAcquired; if( CDnSkillTreeSystem::R_SUCCESS != Output.eResult && false == bIgnoreParentSkillCondition ) { switch( Output.eResult ) { // 캐릭터 요구레벨이 모자람. case CDnSkillTreeSystem::R_NOT_ENOUGH_CHAR_LEVEL: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_NOT_ENOUGH_CHAR_LEVEL; break; // 선행(부모) 스킬이 없음. case CDnSkillTreeSystem::R_DONT_HAVE_PARENT_SKILL: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_DONT_HAVE_PARENT_SKILL; break; // 부모 스킬의 레벨이 충족되지 않음. case CDnSkillTreeSystem::R_NOT_ENOUGH_PARENT_SKILL_LEVEL: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_NOT_ENOUGH_PARENT_SKILL_LEVEL; break; // 스킬 포인트가 모자라서 스킬을 획득할 수 없음. case CDnSkillTreeSystem::R_NOT_ENOUGH_SKILLPOINT_TO_ACQUIRE: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_NOT_ENOUGH_SKILLPOINT; break; } } } return nRetCode; } int CDnPlayerActor::AcquireSkill( int nSkillID ) { int nRetCode = ERROR_NONE; vector::iterator iter = find( m_vlUnlockZeroLevelSkills.begin(), m_vlUnlockZeroLevelSkills.end(), nSkillID ); _ASSERT( m_vlUnlockZeroLevelSkills.end() != iter ); if( m_vlUnlockZeroLevelSkills.end() != iter ) { // 서로 못배우게 한 스킬은 못배우게 처리. TSkillData* pSkillData = g_pDataManager->GetSkillData( nSkillID ); if( IsExclusiveSkill( nSkillID, pSkillData->nExclusiveID ) ) { // 클라에서 기본적으로 막기 때문에 여기까지 오면 핵이다. return ERROR_SKILL_ACQUIRE_FAIL_EXCLUSIVE; } CDnSkillTreeSystem* pSkillTreeSystem = g_pDataManager->GetSkillTreeSystem(); CDnSkillTreeSystem::S_TRY_ACQUIRE TryAcquire( GetPossessedSkillInfo() ); CDnSkillTreeSystem::S_OUTPUT Output; TryAcquire.iCurrentCharLevel = GetLevel(); TryAcquire.iTryAcquireSkillID = nSkillID; TryAcquire.iHasSkillPoint = GetAvailSkillPointByJob( nSkillID ); DNTableFileFormat* pSkillTable = GetDNTable( CDnTableDB::TSKILL ); int iNeedJob = pSkillTable->GetFieldFromLablePtr( nSkillID, "_NeedJob" )->GetInteger(); if( IsPassJob( iNeedJob ) ) { //TryAcquire.iJobID = iNeedJob; pSkillTreeSystem->TryAcquireSkill( TryAcquire, &Output ); #if defined(PRE_ADD_SKILL_LEVELUP_LIMIT_BY_SP) std::vector nNeedSPValues; CDnSkillTask* pSkillTask = static_cast(CTaskManager::GetInstance( m_pSession->GetGameRoom() ).GetTask( "SkillTask" )); if (pSkillTask) pSkillTask->GetNeedSPValuesByJob(nSkillID, nNeedSPValues); std::vector jobHistory; GetJobHistory(jobHistory); bool bAvailableSPByJob = false; if (pSkillTask) bAvailableSPByJob = pSkillTask->IsAvailableSPByJob(jobHistory, nNeedSPValues, this); if (bAvailableSPByJob == false) Output.eResult = CDnSkillTreeSystem::R_NOT_ENOUGH_SKILLPOINT_TO_ACQUIRE; #endif // PRE_ADD_SKILL_LEVELUP_LIMIT_BY_SP // #36858 글로벌 스킬로 서로 엮여 있다면 해당 그룹중에 하나만 배워도 부모 스킬 조건없이 배울 수 있다. bool bAlreadyGlobalSkillAcquired = HasSameGlobalIDSkill( nSkillID ); bool bIgnoreParentSkillCondition = ( (CDnSkillTreeSystem::R_DONT_HAVE_PARENT_SKILL == Output.eResult) || (CDnSkillTreeSystem::R_LOCKED_PARENTSKILL == Output.eResult) || (CDnSkillTreeSystem::R_NOT_ENOUGH_PARENT_SKILL_LEVEL == Output.eResult) ) && true == bAlreadyGlobalSkillAcquired; if( CDnSkillTreeSystem::R_SUCCESS == Output.eResult || true == bIgnoreParentSkillCondition ) { // 내부에서 직접 레벨 1로 바꿈. //vector::iterator iter = m_vlPossessedSkill.begin(); //for( iter; m_vlPossessedSkill.end() != iter; ++iter ) //{ // if( iter->iSkillID == nSkillID ) // { // iter->iSkillLevel = 1; // break; // } //} bool bSuccess = AddSkill( nSkillID, 1 ); _ASSERT( bSuccess ); DnSkillHandle hAcquiredSkill = FindSkill( nSkillID ); m_pSession->ChangeSkillPoint( -hAcquiredSkill->GetNowLevelSkillPoint(), nSkillID, false, 0 ); m_pSession->GetDBConnection()->QueryModSkillLevel(m_pSession, nSkillID, 1, 0, -hAcquiredSkill->GetNowLevelSkillPoint(), DBDNWorldDef::SkillChangeCode::GainByBuy); // db저장: 스킬포인트까지 같이 업데이트 } else { switch( Output.eResult ) { // 캐릭터 요구레벨이 모자람. case CDnSkillTreeSystem::R_NOT_ENOUGH_CHAR_LEVEL: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_NOT_ENOUGH_CHAR_LEVEL; break; // 선행(부모) 스킬이 없음. case CDnSkillTreeSystem::R_DONT_HAVE_PARENT_SKILL: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_DONT_HAVE_PARENT_SKILL; break; // 부모 스킬의 레벨이 충족되지 않음. case CDnSkillTreeSystem::R_NOT_ENOUGH_PARENT_SKILL_LEVEL: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_NOT_ENOUGH_PARENT_SKILL_LEVEL; break; // 스킬 포인트가 모자라서 스킬을 획득할 수 없음. case CDnSkillTreeSystem::R_NOT_ENOUGH_SKILLPOINT_TO_ACQUIRE: nRetCode = ERROR_SKILL_ACQUIRE_FAIL_NOT_ENOUGH_SKILLPOINT; break; } } } } return nRetCode; } void CDnPlayerActor::CheckAndRegisterObserverStateBlow( DnBlowHandle hBlow ) { switch( hBlow->GetBlowIndex() ) { // 블록 상태효과 case STATE_BLOW::BLOW_030: { CDnBlockBlow* pObservable = static_cast( hBlow.GetPointer() ); pObservable->RegisterObserver( m_pBubbleSystem ); } break; // 패링 상태효과 case STATE_BLOW::BLOW_031: { CDnParryBlow* pObservable = static_cast( hBlow.GetPointer() ); pObservable->RegisterObserver( m_pBubbleSystem ); } break; // 쿨타임 패링 상태효과. case STATE_BLOW::BLOW_153: { CDnCooltimeParryBlow* pObservable = static_cast( hBlow.GetPointer() ); pObservable->RegisterObserver( m_pBubbleSystem ); } break; } } void CDnPlayerActor::OnApplyPassiveSkillBlow( int iBlowID ) { DnBlowHandle hBlow = m_pStateBlow->GetStateBlowFromID( iBlowID ); if( hBlow ) CheckAndRegisterObserverStateBlow( hBlow ); } bool CDnPlayerActor::CanAddSkill( int nSkillTableID, int nLevel /*= 1*/ ) { TSkillData* pSkillData = g_pDataManager->GetSkillData( nSkillTableID ); if( pSkillData == NULL ) return false; if( GetGameRoom() == NULL ) return false; if( GetGameRoom()->bIsZombieMode() == true ) { if( CDnSkill::Passive == pSkillData->cSkillType && CDnSkill::DurationTypeEnum::Buff == pSkillData->cDurationType ) return false; } return true; } bool CDnPlayerActor::AddSkill( int nSkillTableID, int nLevel /* = 1 */, int iSkillLevelApplyType/* = CDnSkill::PVE*/ ) { bool bSuccess = false; if( 0 < nLevel ) { // 스킬 레벨 데이터를 pve/pvp 인 경우와 나눠서 셋팅해준다. int iSkillLevelDataType = CDnSkill::PVE; if( GetGameRoom()->bIsPvPRoom() ) iSkillLevelDataType = CDnSkill::PVP; bSuccess = MASkillUser::AddSkill( nSkillTableID, nLevel, iSkillLevelDataType ); vector::iterator iter = find( m_vlUnlockZeroLevelSkills.begin(), m_vlUnlockZeroLevelSkills.end(), nSkillTableID ); if( m_vlUnlockZeroLevelSkills.end() != iter ) m_vlUnlockZeroLevelSkills.erase( iter ); } else { // 처음에 DB 로부터 스킬 리스트 받을 때 레벨이 0 이면 이쪽으로 들어온다. // 언락만 되어있고 실제로 갖고있지는 않은 스킬. m_vlUnlockZeroLevelSkills.push_back( nSkillTableID ); bSuccess = true; } //if( bSuccess ) //{ // bool bFinded = false; // int iNumPossessed = (int)m_vlPossessedSkill.size(); // for( int i = 0; i < iNumPossessed; ++i ) // { // CDnSkillTreeSystem::S_POSSESSED_SKILL_INFO& PossessedSkill = m_vlPossessedSkill.at( i ); // if( PossessedSkill.iSkillID == nSkillTableID ) // { // PossessedSkill.iSkillLevel = nLevel; // bFinded = true; // } // } // if( false == bFinded ) // { // // unlock 만 된 레벨 0 짜리 스킬도 감안하기 때문에 같이 리스트에 넣어준다. // CDnSkillTreeSystem::S_POSSESSED_SKILL_INFO SkillInfo; // SkillInfo.iSkillID = nSkillTableID; // SkillInfo.iSkillLevel = nLevel; // SkillInfo.bCurrentLock = false; // m_vlPossessedSkill.push_back( SkillInfo ); // } //} return bSuccess; } bool CDnPlayerActor::ExecuteSkill( DnSkillHandle hSkill, LOCAL_TIME LocalTime, float fDelta ) { ResetCustomAction(); // 오라 스킬이 꺼지는 것으로 토글링 될 땐 m_hAuraSkill 없어짐. // CDnActor::OnSkillExecute( LocalTime, fDelta ); DnSkillHandle hNowSkill; if( m_hAuraSkill ) hNowSkill = m_hAuraSkill; bool bResult = CDnActor::ExecuteSkill( hSkill, LocalTime, fDelta ); if( m_hProcessSkill ) hNowSkill = m_hProcessSkill; else if( m_hAuraSkill ) hNowSkill = m_hAuraSkill; if( bResult && hNowSkill ) { UpdateUseSkill( hNowSkill ); // 일단 인스턴트만 아니면 무조건 이벤트 발생시킨다. 추후에 오오라나 타임토글 같은 경우도 필요없다면 // 이쪽에서 또 걸러준다. switch( hNowSkill->GetDurationType() ) { case CDnSkill::Instantly: break; default: { if( m_pSession && m_pSession->GetMissionSystem() ) { m_pSession->GetEventSystem()->OnEvent( EventSystem::OnSkillUse, 2, EventSystem::SkillID, hNowSkill->GetClassID(), EventSystem::SkillLevel, hNowSkill->GetLevel() ); } } break; } } #ifdef PRE_ADD_EXPORT_DPS_INFORMATION if( bResult && CDnDPSReporter::IsActive() ) { if(CDnDPSReporter::GetInstance().IsEnabledUser( GetCharacterDBID() )) { DNVector(DnActorHandle) hVecList; ScanActor( GetRoom(), *GetPosition() , 500.f , hVecList ); CDnDPSReporter::GetInstance().ReportSkillInfo( hSkill , (int)hVecList.size()); } } #endif return bResult; } void CDnPlayerActor::OnHitSuccess( LOCAL_TIME LocalTime, DnActorHandle hActor, HitStruct *pStruct ) { // #33265 by kalliste // #if defined( PRE_ADD_LOTUSGOLEM ) // if( hActor->GetHitParam()->bIgnoreShowDamage == true ) // { // m_nComboDelay = -1; // return; // } // #endif // #if defined( PRE_ADD_LOTUSGOLEM ) // #12170 콤보 딜레이 값이 0인 hit 는 콤보 판정에 아무런 영향을 주지 않도록 처리. if( m_bAllowCalcCombo && 0 < pStruct->nComboDelay ) { if( m_nComboDelay > 0 ) { #if defined( PRE_ADD_LOTUSGOLEM ) if( hActor->GetHitParam()->bIgnoreShowDamage == false ) m_nComboCount++; #else m_nComboCount++; #endif // #if defined( PRE_ADD_LOTUSGOLEM ) if( m_nComboCount > m_nTotalComboCount ) m_nTotalComboCount = m_nComboCount; UpdateMaxCombo( m_nComboCount ); } else { m_nComboCount = 1; } m_nComboDelay = pStruct->nComboDelay; if( m_nComboDelay == 0 ) { m_nComboCount = 0; } } switch( hActor->GetHitParam()->HitType ) { case CDnWeapon::Critical: UpdateCriticalHit(); break; case CDnWeapon::Stun: UpdateStunHit(); break; } CheckNormalHitSEProcessor( hActor, *hActor->GetHitParam() ); if( hActor && hActor->IsHit() && pStruct->szTargetHitAction != NULL ) { if( IsAppliedThisStateBlow(STATE_BLOW::BLOW_246) ) { DNVector(DnBlowHandle) vlBlows; GatherAppliedStateBlowByBlowIndex( STATE_BLOW::BLOW_246, vlBlows ); for( DWORD i=0; iGetParentSkillInfo(); if( pSkillInfo ) { DnActorHandle hHitter = pSkillInfo->hSkillUser; if( hHitter == hActor ) { vlBlows[i]->SetState(STATE_BLOW::STATE_END); SendRemoveStateEffectFromID( vlBlows[i]->GetBlowID() ); } } } } } } } // 이 액터가 쏜 발사체가 명중되었을 때. void CDnPlayerActor::OnHitProjectile( LOCAL_TIME LocalTime, DnActorHandle hHittedTarget, const CDnDamageBase::SHitParam& HitParam ) { CheckNormalHitSEProcessor( hHittedTarget, HitParam ); } void CDnPlayerActor::CheckNormalHitSEProcessor( DnActorHandle hHittedTarget, const CDnDamageBase::SHitParam& HitParam ) { // #23818 무기에 붙은 아이템 접두어 스킬을 처리하기 위한 "평타" 를 구분하기 위한 코드. // 평타인 경우엔 평타로 대상에게 상태효과를 부여하는 발현타입이 있는지 확인해서 처리. if( hHittedTarget ) { // 내가 스킬을 쓰고 있지 않을 때만. if( ( false == IsProcessSkill() || false == HitParam.bFromProjectileSkill) ) { int iNumApplySEProcessor = (int)m_vlpApplySEWhenNormalHitProcessor.size(); for( int i = 0; i < iNumApplySEProcessor; ++i ) { CDnApplySEWhenTargetNormalHitProcessor* pProcessor = static_cast( m_vlpApplySEWhenNormalHitProcessor.at( i ) ); pProcessor->OnNormalHitSuccess( hHittedTarget ); } } } } void CDnPlayerActor::OnHitFinish( LOCAL_TIME LocalTime, HitStruct *pStruct ) { CDnActor::OnHitFinish( LocalTime, pStruct ); /* if( m_nComboDelay > 0 && pStruct->bFinish ) { for( DWORD i=0; iIsAir() ) { m_nChainCount++; UpdateChainCombo(); char pBuffer[2]; CPacketCompressStream Stream( pBuffer, sizeof(pBuffer) ); Stream.Write( &m_nChainCount, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Send( eActor::SC_CHAIN, &Stream ); break; } } } */ int nDieCount = 0; for( DWORD i=0; iIsDie() ) nDieCount++; } UpdateMissionByMonsterKillCount(nDieCount); } void CDnPlayerActor::UpdateMissionByMonsterKillCount(int nCount) { if( nCount >= 3 ) UpdateGenocide(); if( nCount >= 2 && m_pSession ) { m_pSession->GetEventSystem()->OnEvent( EventSystem::OnKillMonster, 1, EventSystem::GenocideCount, nCount ); } } void CDnPlayerActor::SaveUserData(TUserData &UserData) { #ifndef PRE_FIX_SKILLLIST memset( UserData.Skill.SkillList, 0, sizeof(UserData.Skill.SkillList) ); if( static_cast(GetRoom())->bIsPvPRoom() == false ) { int iIndex = 0; for( UINT i=0 ; iGetClassID(); UserData.Skill.SkillList[iIndex].cSkillLevel = hSkill->GetLevel(); UserData.Skill.SkillList[iIndex].nCoolTime = static_cast(hSkill->GetElapsedDelayTime()*1000); ++iIndex; } } #else memset( UserData.Skill[0].SkillList, 0, sizeof(UserData.Skill[0].SkillList) ); if( static_cast(GetRoom())->bIsPvPRoom() == false ) { int iIndex = 0; DWORD dwNumSkill = GetSkillCount(); for( DWORD i = 0; i < dwNumSkill; ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); if( !hSkill ) continue; UserData.Skill[0].SkillList[iIndex].nSkillID = hSkill->GetClassID(); UserData.Skill[0].SkillList[iIndex].cSkillLevel = hSkill->GetLevel(); UserData.Skill[0].SkillList[iIndex].nCoolTime = static_cast(hSkill->GetElapsedDelayTime()*1000); ++iIndex; } } #endif // #ifndef PRE_FIX_SKILLLIST UserData.Status.nGlyphDelayTime = (int)(m_afLastEquipItemSkillDelayTime * 1000.f); UserData.Status.nGlyphRemainTime = (int)(m_afLastEquipItemSkillRemainTime * 1000.f); if( GetUserSession() && m_nInvalidPlayerCheckCounter > 0 ) { g_Log.Log( LogType::_HACK, GetUserSession(), L"HackChecker(DB) : CharName=%s Counter=%d\n", GetUserSession()->GetCharacterName(), m_nInvalidPlayerCheckCounter ); m_pSession->GetDBConnection()->QueryAddAbuseMonitor( m_pSession, m_nInvalidPlayerCheckCounter, 0 ); m_nInvalidPlayerCheckCounter = 0; } } void CDnPlayerActor::SetUserSession(CDNUserSession * pSession) { m_pSession = pSession; m_pPartyData = pSession->GetGameRoom()->GetPartyData( m_pSession ); // EquipDelayTime, EquipRemainTime 세팅 여기서 그냥 해준다;; m_afLastEquipItemSkillDelayTime = (float)m_pSession->GetGlyphDelayTime() / 1000.f; m_afLastEquipItemSkillRemainTime = (float)m_pSession->GetGlyphRemainTime() / 1000.f; // 현재 장비 슬롯에 매치되는 스킬이 있다면 쿨타임 셋팅해줌. if( m_ahEquipSkill ) m_ahEquipSkill->SetOnceCoolTime( m_afLastEquipItemSkillDelayTime, m_afLastEquipItemSkillRemainTime ); SetAccountLevel(m_pSession->GetAccountLevel()); } void CDnPlayerActor::ProcessCombo( LOCAL_TIME LocalTime, float fDelta ) { if( m_nComboDelay > 0 ) m_nComboDelay -= (int)( fDelta * 1000 ); if( m_nComboDelay < 0 ) { OnComboFinish( m_nComboCount ); m_nComboCount = 0; m_nComboDelay = 0; } } void CDnPlayerActor::OnComboFinish( int nCombo ) { if( nCombo > 1 ) { UpdateCombo( nCombo ); } } bool CDnPlayerActor::IsGMTrace() const { if( m_pSession && m_pSession->bIsGMTrace() ) return true; return false; } UINT CDnPlayerActor::GetSessionID() { return m_pSession ? m_pSession->GetSessionID() : 0; } int CDnPlayerActor::GetMoveSpeed() { int nMoveSpeed = CDnActor::GetMoveSpeed(); if( CDnWorld::IsActive(GetRoom()) && ( CDnWorld::GetInstance(GetRoom()).GetMapType() == EWorldEnum::MapTypeVillage || CDnWorld::GetInstance(GetRoom()).GetMapType() == EWorldEnum::MapTypeWorldMap ) ) nMoveSpeed += GetSafeZoneMoveSpeed(); if( IsTransformMode() ) { nMoveSpeed = g_pDataManager->GetMonsterMutationMoveSpeed( m_nMonsterMutationTableID ); if( m_cMovePushKeyFlag & 0x08 ) nMoveSpeed /= 2; return nMoveSpeed; } if( m_cMovePushKeyFlag & 0x08 ) nMoveSpeed /= 2; // #62481 월드존 이동속도를 마을과 동일하게 변경 if( !IsGhost() && !IsBattleMode() ) nMoveSpeed = (int)( nMoveSpeed * 1.4f ); if( IsDie() && IsGhost() ) nMoveSpeed = (int)( nMoveSpeed * 1.5f ); return nMoveSpeed; } void CDnPlayerActor::OnAddSkill( DnSkillHandle hSkill, bool isInitialize/* = false*/ ) { CDnActor::OnAddSkill( hSkill, isInitialize ); if( hSkill ) { bool bFinded = false; int iNumPossessed = (int)m_vlPossessedSkill.size(); for( int i = 0; i < iNumPossessed; ++i ) { CDnSkillTreeSystem::S_POSSESSED_SKILL_INFO& PossessedSkill = m_vlPossessedSkill.at( i ); if( PossessedSkill.iSkillID == hSkill->GetClassID() ) { PossessedSkill.iSkillLevel = hSkill->GetLevel(); bFinded = true; } } if( false == bFinded ) { // unlock 만 된 레벨 0 짜리 스킬도 감안하기 때문에 같이 리스트에 넣어준다. CDnSkillTreeSystem::S_POSSESSED_SKILL_INFO SkillInfo; SkillInfo.iSkillID = hSkill->GetClassID(); SkillInfo.iSkillLevel = hSkill->GetLevel(); SkillInfo.bCurrentLock = false; m_vlPossessedSkill.push_back( SkillInfo ); } } // 추가된 스킬이 강화 패시브 스킬인 경우.(강화 패시브 스킬을 획득 했거나 다른 플레이어가 레벨업 한 경우) if( hSkill->GetSkillType() == CDnSkill::EnchantPassive ) { int iBaseSkillID = hSkill->GetBaseSkillID(); DnSkillHandle hBaseSkill = FindSkill( iBaseSkillID ); if( hBaseSkill ) { #if defined(PRE_FIX_64312) //소환몬스터용 스킬이 경우 바로 적용 하지 않고 담아 놓고, MAAiSkill에서 UseSkill시점에 적용 한다. bool isSummonMonsterSkill = false; isSummonMonsterSkill = hBaseSkill->IsSummonMonsterSkill(); if (isSummonMonsterSkill == false) hBaseSkill->ApplyEnchantSkill( hSkill ); else hBaseSkill->AddSummonMonsterEnchantSkill(hSkill); #else hBaseSkill->ApplyEnchantSkill( hSkill ); #endif // PRE_FIX_64312 } } else { // 추가된 스킬이 갖고 있는 강화 패시브 스킬의 베이스 스킬인 경우. (다른 플레이어의 강화된 스킬이 레벨업 된 경우) CheckAndApplyEnchantPassiveSkill( hSkill ); } } void CDnPlayerActor::OnRemoveSkill( DnSkillHandle hSkill ) { CDnActor::OnRemoveSkill( hSkill ); if( hSkill ) { vector::iterator iter = m_vlPossessedSkill.begin(); for( iter; iter != m_vlPossessedSkill.end(); ++iter ) { if( iter->iSkillID == hSkill->GetClassID() ) { m_vlPossessedSkill.erase( iter ); break; } } } // 다른 플레이어의 패시브 강화 스킬이 레벨업 되어 이전 레벨의 스킬 객체가 삭제 루틴을 타고 이쪽으로 오는 경우. // 적용되고 있던 베이스 스킬의 강화 상태를 리셋으로 돌린다. if( CDnSkill::EnchantPassive == hSkill->GetSkillType() && 0 < hSkill->GetBaseSkillID() ) { DnSkillHandle hBaseSkill = FindSkill( hSkill->GetBaseSkillID() ); if( hBaseSkill ) { #if defined(PRE_FIX_64312) //소환몬스터용 스킬이 경우 바로 적용 하지 않고 담아 놓고, MAAiSkill에서 UseSkill시점에 적용 한다. bool isSummonMonsterSkill = false; isSummonMonsterSkill = hBaseSkill->IsSummonMonsterSkill(); if (isSummonMonsterSkill == false) hBaseSkill->ReleaseEnchantSkill(); else hBaseSkill->RemoveSummonMonsterEnchantSkill(); #else hBaseSkill->ReleaseEnchantSkill(); #endif // PRE_FIX_64312 } } // 다른 플레이어의 패시브 강화 스킬의 대상이 되는 베이스 스킬이 레벨업 되어 삭제 루틴을 타고 이쪽으로 오는 경우. // 이 경우엔 베이스 스킬 객체를 그냥 삭제하면 되므로 따로 처리할 것은 없다. } void CDnPlayerActor::ProcessRecoverySP( LOCAL_TIME LocalTime, float fDelta ) { if( IsDie() ) { m_fRecoverySPDelta = 0.f; return; } m_fRecoverySPDelta += fDelta; int nValue = GetSP(); if( m_fRecoverySPDelta >= s_fRecoverySPTime ) { m_fRecoverySPDelta -= s_fRecoverySPTime; nValue += GetRecoverySP(); } else return; if( GetSP() >= GetMaxSP() ) return; nValue = min( nValue, GetMaxSP() ); SetSP( nValue ); BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &nValue, sizeof(int) ); Send( eActor::SC_RECOVERYSP, &Stream ); } void CDnPlayerActor::CmdToggleWeaponViewOrder( int nEquipIndex, bool bShowCash ) { SetWeaponViewOrder( nEquipIndex, bShowCash ); RefreshWeaponViewOrder( nEquipIndex ); if( m_pSession ) { m_pSession->SetViewCashEquipBitmap(CASHEQUIP_WEAPON1 + nEquipIndex, bShowCash); } } void CDnPlayerActor::CmdTogglePartsViewOrder( int nEquipIndex, bool bShowCash ) { if ((nEquipIndex < 0) || (nEquipIndex >= _countof(m_bPartsViewOrder))) return; SetPartsViewOrder( nEquipIndex, bShowCash ); RefreshPartsViewOrder( nEquipIndex ); if( m_pSession ) m_pSession->SetViewCashEquipBitmap(nEquipIndex, bShowCash); } void CDnPlayerActor::CmdToggleHideHelmet( bool bHideHelmet ) { if( m_pSession ) m_pSession->SetViewCashEquipBitmap(HIDEHELMET_BITINDEX, bHideHelmet); } void CDnPlayerActor::CmdChangeJob( int nJobID ) { SetJobHistory( nJobID ); OnChangeJob( nJobID ); BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &nJobID, sizeof(int) ); Send( eActor::SC_CHANGEJOB, m_pSession->GetSessionID(), &Stream ); } void CDnPlayerActor::CmdEscape( EtVector3 &vPos ) { BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); Stream.Write( &vPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Send( eActor::SC_CMDESCAPE, &Stream ); } void CDnPlayerActor::ToggleGhostMode( bool bGhost ) { if( bGhost == m_bGhost ) return; m_bGhost = bGhost; if( bGhost == true && GetSP() > 0 ) SetSP(0); #ifdef PRE_FIX_GAMESERVER_USE_GHOST_MODE bool bGhostMode = true; CDnGameTask* pGameTask = m_pSession->GetGameRoom()->GetGameTask(); if( pGameTask && pGameTask->GetGameTaskType() == GameTaskType::PvP ) { bGhostMode = false; CDNGameRoom* pGameRoom = GetGameRoom(); #if defined(PRE_ADD_PVP_TOURNAMENT) if( pGameRoom && pGameRoom->GetPvPGameMode() && (pGameRoom->GetPvPGameMode()->bIsAllKillMode() || pGameRoom->GetPvPGameMode()->bIsTournamentMode())) #else if( pGameRoom && pGameRoom->GetPvPGameMode() && pGameRoom->GetPvPGameMode()->bIsAllKillMode() ) #endif //#if defined(PRE_ADD_PVP_TOURNAMENT) bGhostMode= true; } if( !bGhostMode ) { if( bGhost ) { if( IsMovable() || IsStay() ) { SetAction( "Die", 0.f, 0.f, false ); } } else { std::string szActionName = "Stand"; if(m_bShootMode) szActionName = "MOD_Stand"; if(IsTransformMode()) { if(IsExistAction( "Summon_On" )) szActionName = "Summon_On"; } SetAction( szActionName.c_str(), 0.f, 0.f ); } } else { if( bGhost ) { SwapSingleSkin( 499 + m_nClassID ); SetAction( "Stand", 0.f, 0.f ); } else { SwapSingleSkin( -1 ); SetAction( "Stand", 0.f, 0.f ); } } #endif if( m_bGhost && GetGameRoom() && GetGameRoom()->GetGameTask() ) GetGameRoom()->GetGameTask()->OnGhost( GetActorHandle() ); } void CDnPlayerActor::_CheckProcessSkillActioncChange( const char* pAction ) { // #26467 액션이 셋팅되고 다음 프레임에 갱신되므로 곧바로 스킬 사용중인지 체크해서 스킬 액션이 아니면 스킬을 종료시키도록 한다. if( m_hProcessSkill ) { m_setUseActionName.clear(); m_setUseActionName.insert( pAction ); if( false == m_hProcessSkill->IsUseActionNames( m_setUseActionName ) ) { if( false == (IsEnabledAuraSkill() && m_hProcessSkill->IsAuraOn()) ) { m_hProcessSkill->OnEnd( CDnActionBase::m_LocalTime, 0.f ); } m_hProcessSkill.Identity(); } } } void CDnPlayerActor::_CheckActionWithProcessPassiveActionSkill( const char* szPrevAction ) { // 같은 액션 반복중이면 패스 if( szPrevAction && m_nPrevActionIndex == m_nActionIndex ) return; // if instantly passive skill, then cancel skill. ( ex) archer's spinkick) // because state effect must deactivate when change to another attack action. if( m_hProcessSkill ) { m_setUseActionName.clear(); m_setUseActionName.insert( szPrevAction ); // 현재 액션이 prev 액션의 next 액션이라면 스킬이 이어지는 것으로 본다. // 이전 액션이 현재 진행중인 스킬에서 사용하는 액션이었고 현재 액션이 이전 액션의 next 액션이 아니라면 // 패시브 스킬이 끝난 것으로 판단한다. ActionElementStruct* pElement = GetElement( szPrevAction ); bool bIsNextAction = false; if( pElement ) { // #25154 기본 스탠드 액션은 스킬에서 지정된 next 액션이 이어지는 것으로 보지 않는다. // 오라 스킬 액션이 끝나고 이 함수가 호출되었을 때 현재 액션이 Stand 로 되어있는데 해당 시점에서 // m_hProcessSkill 이 스킬이 끝난 것으로 판단되어서 NULL 로 되어야 한다. // m_hProcessSkill 이 남아있으면 다른 스킬 썼을 때 강제로 onend 될 수 있기 때문에 안됨. // 따라서 bIsNextAction 이 false 가 되고 m_hProcessSkill->IsUseSkillActionNames() 함수 내부에서 // 스킬 액션이 종료된 것으로 판단되어야 한다. bIsNextAction = ((pElement->szNextActionName != "Stand") && (pElement->szNextActionName == GetCurrentAction())); } if( false == bIsNextAction && m_hProcessSkill->IsUseActionNames( m_setUseActionName ) ) { // 액티브 스킬이 패시브 형태로 등록되어 사용되었을때 , GetPassiveSkillLengh() 로 알수있다. if( ( (m_hProcessSkill->GetSkillType() == CDnSkill::Passive || m_hProcessSkill->GetPassiveSkillLength() != 0.f ) || m_hProcessSkill->GetSkillType() == CDnSkill::AutoPassive) && m_hProcessSkill->GetDurationType() == CDnSkill::Instantly ) { // 패시브 스킬이 체인 입력이 들어왔을 때를 체크한다. 한번 체크되는 순간 체인 입력 플래그는 초기화된다. // 체인입력되는 순간 액션의 길이만큼 패시브 스킬 사용 길이가 늘어난다. // 이렇게 플래그와 시간 둘 다 같이 사용해야 패시브 스킬의 연속 체인이 가능해진다. if( false == m_hProcessSkill->CheckChainingPassiveSkill() ) { if( false == m_hProcessSkill->IsChainInputAction( GetCurrentAction() ) ) { m_hProcessSkill->OnEnd( MAActorRenderBase::m_LocalTime, 0.0f ); m_hProcessSkill.Identity(); } } } else if( IsEnabledAuraSkill() && m_hProcessSkill->IsAuraOn() ) { // Note 한기: m_hProcessSkill 스마트 포인터는 오라 스킬 사용하는 액션이 재생되는 동안은 유효해야 // 게임 서버에서 CDnPlayerActor::CmdStop() 쪽에서 걸러지기 때문에 겜 서버에서 해당 액션 시그널이 끝까지 // 처리됨. 따라서 CDnActor::OnChangeAction 쪽에서 ProcessAction 을 Identity 시킴. m_hProcessSkill.Identity(); ClearSelfStateSignalBlowQueue(); // 오라 스킬의 자기 자신에게 적용하는 상태효과 타이밍 시그널에 보내주는 큐 초기화 시킴. 안그럼 다른 스킬에 영향을 준다. } } } } void CDnPlayerActor::OnChangeAction( const char* szPrevAction ) { // 129번 액션 이름 대체 상태효과 활성 비활성 처리. ///////////////////////////////////////////////// if( m_pStateBlow->IsApplied( STATE_BLOW::BLOW_129 ) ) { DNVector(DnBlowHandle) vlhChangeActionSetBlow; m_pStateBlow->GetStateBlowFromBlowIndex( STATE_BLOW::BLOW_129, vlhChangeActionSetBlow ); // 액션 셋 변경 상태효과는 여러개 있을 수 있다. int iNumBlow = (int)vlhChangeActionSetBlow.size(); for( int i = 0; i < iNumBlow; ++i ) { if( !vlhChangeActionSetBlow[i] ) continue; CDnChangeActionSetBlow* pChangeActionSetBlow = static_cast( vlhChangeActionSetBlow.at(i).GetPointer() ); pChangeActionSetBlow->UpdateEnable( szPrevAction, GetCurrentAction() ); // 액션 변경시에 상태효과 적용 발현타입이 있다면 함수 호출해줌. // 클라에서 액션이 이미 변환되어 날아오기 때문에.. CDnChangeActionStrProcessor* pProcessor = pChangeActionSetBlow->GetChangeActionStrProcessor(); // 액션 변경 발현타입이 비활성화 상태일때는 NULL 리턴됨. if( pProcessor && pProcessor->IsChangedActionName( GetCurrentAction() ) ) { pChangeActionSetBlow->OnChangeAction(); } else { // 서버에서 자체적으로 액션이 처리되는 setaction 쪽에서도 호출해줘야함. pChangeActionSetBlow->OnNotChangeAction(); } } } _CheckActionWithProcessPassiveActionSkill( szPrevAction ); //CDnActor::OnChangeAction( szPrevAction ); if( !( szPrevAction && m_nPrevActionIndex == m_nActionIndex ) ) { if( CDnWorld::IsActive(GetRoom()) ) { CDnWorld::GetInstance(GetRoom()).InsertTriggerEventStore( "ChangeActionPlayer", GetUniqueID() ); CDnWorld::GetInstance(GetRoom()).OnTriggerEventCallback( "CDnPlayerActor::OnChangeAction", CDnActionBase::m_LocalTime, 0.f ); } } m_bUseSignalSkillCheck = false; ZeroMemory( m_abSignalSkillCheck, sizeof(m_abSignalSkillCheck) ); // mixed 액션중일 때는 mixed 액션 패킷 왔을 때 이미 업데이트 된 상태. if( false == m_bUpdatedProjectileInfoFromCmdAction ) { if( false == IsCustomAction() ) _UpdateMaxProjectileCount( m_nActionIndex ); } else m_bUpdatedProjectileInfoFromCmdAction = false; // 이 다음에 OnChangeAction() 호출되면 업데이트 해주어야 하기 때문에 플래그를 꺼준다. // 액션이 바뀌면 텀 체크하도록 원상 복구 시켜줌. m_bCheckProjectileSignalTerm = true; if( false == m_mapIcyFractionHitted.empty() ) m_mapIcyFractionHitted.clear(); // 버블 시스템쪽에 알려줌. 아카데믹 쪽에서 쓰는 경우가.. boost::shared_ptr pEvent( IDnObserverNotifyEvent::Create( EVENT_ONCHANGEACTION ) ); Notify( pEvent ); } void CDnPlayerActor::OnBreakSkillSuperAmmor( int nIndex, int nOriginalSupperAmmor, int nDescreaseSupperAmmor ) { if( nOriginalSupperAmmor >= 200 ) { UpdateSuperAmmorBreak(); } } void CDnPlayerActor::OnAirCombo( int nComboCount ) { if( nComboCount >= 2 ) { UpdateAirCombo(); } } void CDnPlayerActor::PushSummonMonster( DnMonsterActorHandle hMonster, const SummonMonsterStruct* pSummonMonsterStruct, bool bReCreateFollowStageMonster/* = false*/ ) { CDnActor::PushSummonMonster( hMonster, pSummonMonsterStruct, bReCreateFollowStageMonster ); // 스테이지 이동으로 재생성 되는 소환몹인지 여부와 관계 없이 (bReCreateFollowStageMonster) // 해당 그룹 아이디로 넣어준다. 스테이지 이동하면서 CDnActor::bIsCanSummonMonster() 함수에서 유효하지 않은 // 몬스터 액터 객체 핸들은 정리된다. if( 0 < pSummonMonsterStruct->nGroupID ) { m_mapSummonMonsterByGroup[ pSummonMonsterStruct->nGroupID ].push_back( hMonster ); hMonster->SetSummonGroupID( pSummonMonsterStruct->nGroupID ); } if( bReCreateFollowStageMonster ) { // 관리되고 있는 스테이지 따라가는 몬스터가 새 스테이지에서 다시 생성되는 경우 핸들 교체. DWORD dwMonsterClassID = hMonster->GetMonsterClassID(); std::list::iterator iter = m_listSummonedMonstersFollowStageInfos.begin(); for( iter; iter != m_listSummonedMonstersFollowStageInfos.end(); ++iter ) { if( false == iter->bReCreatedFollowStageMonster && dwMonsterClassID == iter->dwMonsterClassID ) { iter->hMonster = hMonster; iter->hMonster->SetActionQueue( "Stand" ); // 새로 소환되는 것이 아니므로 곧바로 stand 액션. iter->hMonster->CmdWarp( *GetPosition(), EtVec3toVec2( *GetLookDir() ) ); iter->bReCreatedFollowStageMonster = true; break; } } } else { // 추가적으로 플레이어인 경우 스테이지 이동이나 존 이동(CmdWarp) 를 하는 경우 따라가도록 설정된 소환체는 // 또 따로 갖고 있도록 한다. if( pSummonMonsterStruct->bFollowSummonerStage ) { S_FOLLOWSTAGE_SUMMONED_MONSTER_INFO Info; Info.hMonster = hMonster; Info.dwMonsterClassID = hMonster->GetMonsterClassID(); #ifdef PRE_FIX_MEMOPT_SIGNALH CopyShallow_SummonMonsterStruct(Info.SummonMonsterSignalData, pSummonMonsterStruct); #else Info.SummonMonsterSignalData = *pSummonMonsterStruct; #endif Info.iRemainDestroyTime = pSummonMonsterStruct->nLifeTime; m_listSummonedMonstersFollowStageInfos.push_back( Info ); } } } // 소환된 몬스터 객체가 제한 시간이 다 되었거나 HP 가 0 이 되어 죽는 경우 호출됨. // 이 두가지 경우에 대해서는 스테이지를 따라가는 소환 몬스터들의 관리 리스트에서 제거해준다. // 스테이지 이동시 해당 몬스터 객체가 파괴될 때는 호출되지 않는다. void CDnPlayerActor::OnDieSummonedMonster( DnMonsterActorHandle hSummonedMonster ) { std::list::iterator iter = m_listSummonedMonstersFollowStageInfos.begin(); for( iter; iter != m_listSummonedMonstersFollowStageInfos.end(); ++iter ) { if( hSummonedMonster == iter->hMonster ) { m_listSummonedMonstersFollowStageInfos.erase( iter ); break; } } int iGroupID = hSummonedMonster->GetSummonGroupID(); if( 0 < m_mapSummonMonsterByGroup.count( iGroupID ) ) { list& listSummonedMonsters = m_mapSummonMonsterByGroup[ iGroupID ]; list::iterator iter = find( listSummonedMonsters.begin(), listSummonedMonsters.end(), hSummonedMonster ); if( listSummonedMonsters.end() != iter ) listSummonedMonsters.erase( iter ); // 해당 그룹의 리스트가 비었으면 맵에서 제거. if( listSummonedMonsters.empty() ) m_mapSummonMonsterByGroup.erase( iGroupID ); } #if defined(PRE_ADD_65808) if (hSummonedMonster) { CDnMonsterActor* pMonsterActor = static_cast(hSummonedMonster.GetPointer()); if (pMonsterActor) { RemoveSummonMonsterGlyphStateEffects(pMonsterActor->GetMonsterClassID()); } } #endif // PRE_ADD_65808 } // 스테이지에 있는 몬스터들이 삭제되기 직전에 호출된다. // 필요한 정보를 여기서 뽑아내서 갖고 있는다. void CDnPlayerActor::OnBeforeDestroyStageMonsters( void ) { std::list::iterator iter = m_listSummonedMonstersFollowStageInfos.begin(); for( iter; iter != m_listSummonedMonstersFollowStageInfos.end(); ) { // 리스트에 갖고 있는 몬스터 핸들이 invalid 한 경우엔 스테이지 이동시 버리고 간다. if( iter->hMonster ) { iter->iRemainDestroyTime = iter->hMonster->GetRemainDestroyTime(); iter->bReCreatedFollowStageMonster = false; // TODO: 추가적으로 받아둘 내용이 있으면 받아둔다. ++iter; } else { iter = m_listSummonedMonstersFollowStageInfos.erase( iter ); } } } void CDnPlayerActor::OnInitializeNextStageFinished( void ) { // 소환된 몬스터 제거된 것들 새로 생성한다. STE_SummonMonster 시그널 그대로 돌림. CDnGameTask *pTask = (CDnGameTask *)CTaskManager::GetInstance(GetRoom()).GetTask( "GameTask" ); std::list::iterator iter = m_listSummonedMonstersFollowStageInfos.begin(); for( iter; iter != m_listSummonedMonstersFollowStageInfos.end(); ++iter ) { // 스테이지 이동에 따라 다시 생성하는 몬스터이기 때문에 PushMonster() 함수에서 액터 핸들 갱신만 이루어진다. // 다시 소환하는 몬스터의 지속시간을 남은 지속시간으로 셋팅해줌. iter->SummonMonsterSignalData.nLifeTime = (int)iter->iRemainDestroyTime; pTask->RequestSummonMonster( GetMySmartPtr(), &(iter->SummonMonsterSignalData), true ); } #ifdef PRE_ADD_WEEKLYEVENT RemoveEventStateBlow(); ApplyEventStateBlow(); #endif #if defined( PRE_FIX_70618 ) if( IsAppliedThisStateBlow(STATE_BLOW::BLOW_078) ) CmdRemoveStateEffect(STATE_BLOW::BLOW_078); #endif // #if defined( PRE_FIX_70618 ) } bool CDnPlayerActor::_bIsMasterSystemDurabilityReward() { if( GetGameRoom() && GetGameRoom()->GetMasterRewardSystem() ) { if( GetGameRoom()->GetMasterRewardSystem()->bIsDurabilityReward( m_pSession ) ) { #if defined( _WORK ) WCHAR wszBuf[MAX_PATH]; wsprintf( wszBuf, L"[사제시스템] 내구도 보상 적용으로 깎이지 않음" ); m_pSession->SendDebugChat( wszBuf ); #endif // #if defined( _WORK ) return true; } } return false; } void CDnPlayerActor::OnRepairEquipDurability( bool bDBSave, INT64 nPriceCoin ) { bool bRefreshStatus = false; std::vector VecSerialList; std::vector VecDurList; VecSerialList.clear(); VecDurList.clear(); for( int i=CDnParts::Helmet; i<=CDnParts::Ring2; i++ ) { DnPartsHandle hParts = GetParts( (CDnParts::PartsTypeEnum)i ); if( !hParts ) continue; if( hParts->IsInfinityDurability() ) continue; if( hParts->GetDurability() == hParts->GetMaxDurability() ) continue; int nTemp = hParts->GetMaxDurability(); hParts->SetDurability( nTemp ); bRefreshStatus = true; m_pSession->GetItem()->SetEquipItemDurability( i, nTemp, true ); if (bDBSave && m_pSession->GetItem()->GetEquip(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetEquip(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetEquip(i)->wDur); } } for( int i=0; i<2; i++ ) { DnWeaponHandle hWeapon = m_hWeapon[i]; if( !hWeapon ) continue; if( hWeapon->IsInfinityDurability() ) continue; if( hWeapon->GetDurability() == hWeapon->GetMaxDurability() ) continue; int nTemp = hWeapon->GetMaxDurability(); hWeapon->SetDurability( nTemp ); bRefreshStatus = true; m_pSession->GetItem()->SetEquipItemDurability( EQUIP_WEAPON1 + i, nTemp, true ); if (bDBSave && m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)){ VecSerialList.push_back(m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)->wDur); } } if( bRefreshStatus ) { RefreshState( RefreshEquip, ST_All ); } if (bDBSave) { INT64 biCurrentCoin = 0; INT64 biPickUpCoin = 0; if( nPriceCoin > 0 ) { biCurrentCoin = m_pSession->GetCoin(); biPickUpCoin = m_pSession->GetPickUpCoin(); m_pSession->SelPickUpCoin(0); } m_pSession->GetDBConnection()->QueryModItemDurability(m_pSession, nPriceCoin, VecSerialList, VecDurList, biCurrentCoin, biPickUpCoin); } } void CDnPlayerActor::OnDecreaseEquipDurability( int nValue, bool bDBSave ) { if( _bIsMasterSystemDurabilityReward() ) return; bool bRefreshStatus = false; std::vector VecSerialList; std::vector VecDurList; VecSerialList.clear(); VecDurList.clear(); for( int i=CDnParts::Helmet; i<=CDnParts::Ring2; i++ ) { DnPartsHandle hParts = GetParts( (CDnParts::PartsTypeEnum)i ); if( !hParts ) continue; if( hParts->IsInfinityDurability() ) continue; if( hParts->GetDurability() == 0 ) continue; int nTemp = hParts->GetDurability() - nValue; if( nTemp <= 0 ) { nTemp = 0; bRefreshStatus = true; } hParts->SetDurability( nTemp ); m_pSession->GetItem()->SetEquipItemDurability( i, nTemp ); if (bDBSave && m_pSession->GetItem()->GetEquip(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetEquip(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetEquip(i)->wDur); } } for( int i=0; i<2; i++ ) { DnWeaponHandle hWeapon = m_hWeapon[i]; if( !hWeapon ) continue; if( hWeapon->IsInfinityDurability() ) continue; if( hWeapon->GetDurability() == 0 ) continue; int nTemp = hWeapon->GetDurability() - nValue; if( nTemp <= 0 ) { nTemp = 0; bRefreshStatus = true; } hWeapon->SetDurability( nTemp ); m_pSession->GetItem()->SetEquipItemDurability( EQUIP_WEAPON1 + i, nTemp ); if (bDBSave && m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)){ VecSerialList.push_back(m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)->wDur); } } if( bRefreshStatus ) { RefreshState( RefreshEquip, ST_All ); } if (bDBSave) m_pSession->GetDBConnection()->QueryModItemDurability(m_pSession, 0, VecSerialList, VecDurList); } void CDnPlayerActor::OnDecreaseEquipDurability( float fValue, bool bDBSave ) { if( _bIsMasterSystemDurabilityReward() ) return; bool bRefreshStatus = false; std::vector VecSerialList; std::vector VecDurList; VecSerialList.clear(); VecDurList.clear(); #if defined( PRE_ADD_TOTAL_LEVEL_SKILL ) float fTotalLevelValue = 0.0f; if ( m_pRoom->bIsPvPRoom() == false && IsAppliedThisStateBlow(STATE_BLOW::BLOW_259)) { DNVector(DnBlowHandle) vlBlows; GatherAppliedStateBlowByBlowIndex(STATE_BLOW::BLOW_259, vlBlows); { int nCount = (int)vlBlows.size(); for (int i = 0; i < nCount; ++i) { DnBlowHandle hBlow = vlBlows[i]; if (hBlow && hBlow->IsEnd() == false) { fTotalLevelValue += hBlow->GetFloatValue(); } } } } #endif for( int i=CDnParts::Helmet; i<=CDnParts::Ring2; i++ ) { DnPartsHandle hParts = GetParts( (CDnParts::PartsTypeEnum)i ); if( !hParts ) continue; if( hParts->IsInfinityDurability() ) continue; if( hParts->GetDurability() == 0 ) continue; #if defined( PRE_ADD_TOTAL_LEVEL_SKILL ) int nTemp = (int)( hParts->GetDurability() - (( hParts->GetMaxDurability() * fValue ) * (1 - fTotalLevelValue)) ); #else int nTemp = (int)( hParts->GetDurability() - ( hParts->GetMaxDurability() * fValue ) ); #endif if( nTemp <= 0 ) { nTemp = 0; bRefreshStatus = true; } hParts->SetDurability( nTemp ); m_pSession->GetItem()->SetEquipItemDurability( i, nTemp ); if (bDBSave && m_pSession->GetItem()->GetEquip(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetEquip(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetEquip(i)->wDur); } } for( int i=0; i<2; i++ ) { DnWeaponHandle hWeapon = m_hWeapon[i]; if( !hWeapon ) continue; if( hWeapon->IsInfinityDurability() ) continue; if( hWeapon->GetDurability() == 0 ) continue; #if defined( PRE_ADD_TOTAL_LEVEL_SKILL ) int nTemp = (int)( hWeapon->GetDurability() - (( hWeapon->GetMaxDurability() * fValue ) * (1 - fTotalLevelValue)) ); #else int nTemp = (int)( hWeapon->GetDurability() - ( hWeapon->GetMaxDurability() * fValue ) ); #endif if( nTemp <= 0 ) { nTemp = 0; bRefreshStatus = true; } hWeapon->SetDurability( nTemp ); m_pSession->GetItem()->SetEquipItemDurability( EQUIP_WEAPON1 + i, nTemp ); if (bDBSave && m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)){ VecSerialList.push_back(m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetEquip(EQUIP_WEAPON1 + i)->wDur); } } if( bRefreshStatus ) { RefreshState( RefreshEquip, ST_All ); } if (bDBSave) m_pSession->GetDBConnection()->QueryModItemDurability(m_pSession, 0, VecSerialList, VecDurList); } void CDnPlayerActor::OnDecreaseInvenDurability( int nValue, bool bDBSave ) { if( _bIsMasterSystemDurabilityReward() ) return; std::vector VecSerialList; std::vector VecDurList; VecSerialList.clear(); VecDurList.clear(); for( int i=0; iGetItem()->GetInventory(i); if( !pItem ) continue; switch( g_pDataManager->GetItemMainType( pItem->nItemID ) ) { case ITEMTYPE_WEAPON: { CDnWeapon *pWeapon = static_cast(m_pPartyData->pInventory[i]); if( !pWeapon ) continue; if( pWeapon->IsInfinityDurability() ) continue; if( pWeapon->GetDurability() == 0 ) continue; int nTemp = pWeapon->GetDurability() - nValue; if( nTemp <= 0 ) nTemp = 0; pWeapon->SetDurability( nTemp ); m_pSession->GetItem()->SetInvenItemDurability( i, nTemp ); if (bDBSave && m_pSession->GetItem()->GetInventory(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetInventory(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetInventory(i)->wDur); } } break; case ITEMTYPE_PARTS: { CDnParts *pParts = static_cast(m_pPartyData->pInventory[i]); if( pParts->IsInfinityDurability() ) continue; if( pParts->GetDurability() == 0 ) continue; int nTemp = pParts->GetDurability() - nValue; if( nTemp <= 0 ) nTemp = 0; pParts->SetDurability( nTemp ); m_pSession->GetItem()->SetInvenItemDurability( i, nTemp ); if (bDBSave && m_pSession->GetItem()->GetInventory(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetInventory(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetInventory(i)->wDur); } } break; default: continue; } } if (bDBSave) m_pSession->GetDBConnection()->QueryModItemDurability(m_pSession, 0, VecSerialList, VecDurList); } void CDnPlayerActor::OnDecreaseInvenDurability( float fValue, bool bDBSave ) { if( _bIsMasterSystemDurabilityReward() ) return; std::vector VecSerialList; std::vector VecDurList; VecSerialList.clear(); VecDurList.clear(); for( int i=0; iGetItem()->GetInventory(i); if( !pItem ) continue; switch( g_pDataManager->GetItemMainType( pItem->nItemID ) ) { case ITEMTYPE_WEAPON: { CDnWeapon *pWeapon = static_cast(m_pPartyData->pInventory[i]); if( !pWeapon ) continue; if( pWeapon->IsInfinityDurability() ) continue; if( pWeapon->GetDurability() == 0 ) continue; int nTemp = (int)( pWeapon->GetDurability() - ( pWeapon->GetMaxDurability() * fValue ) ); if( nTemp <= 0 ) nTemp = 0; pWeapon->SetDurability( nTemp ); m_pSession->GetItem()->SetInvenItemDurability( i, nTemp ); if (m_pSession->GetItem()->GetInventory(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetInventory(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetInventory(i)->wDur); } } break; case ITEMTYPE_PARTS: { CDnParts *pParts = static_cast(m_pPartyData->pInventory[i]); if( !pParts ) continue; if( pParts->IsInfinityDurability() ) continue; if( pParts->GetDurability() == 0 ) continue; int nTemp = (int)( pParts->GetDurability() - ( pParts->GetMaxDurability() * fValue ) ); if( nTemp <= 0 ) nTemp = 0; pParts->SetDurability( nTemp ); m_pSession->GetItem()->SetInvenItemDurability( i, nTemp ); if (m_pSession->GetItem()->GetInventory(i)){ VecSerialList.push_back(m_pSession->GetItem()->GetInventory(i)->nSerial); VecDurList.push_back(m_pSession->GetItem()->GetInventory(i)->wDur); } } break; default: continue; } } m_pSession->GetDBConnection()->QueryModItemDurability(m_pSession, 0, VecSerialList, VecDurList); } bool CDnPlayerActor::IsPenaltyStageGiveUp() { if (!m_pRoom) return false; switch( m_pRoom->GetGameTaskType() ) { case GameTaskType::PvP: return false; case GameTaskType::DarkLair: return false; case GameTaskType::Farm: return false; default: break; } if (IsDie()) return false; if (CDnWorld::IsActive(GetRoom())) { EWorldEnum::MapTypeEnum mapType = CDnWorld::GetInstance(GetRoom()).GetMapType(); if (mapType == EWorldEnum::MapTypeDungeon) { CDnGameTask *pTask = (CDnGameTask *)CTaskManager::GetInstance(GetRoom()).GetTask( "GameTask" ); if (pTask->GetDungeonClearState() == CDnGameTask::DCS_WarpStandBy) return false; } else { return false; } } return true; } // 내구도 감소가 되었으면 return true; 그렇지 않으면 return false; bool CDnPlayerActor::OnStageGiveUp() { if (IsPenaltyStageGiveUp() == false) return false; DNTableFileFormat *pDungeonSox = GetDNTable( CDnTableDB::TDUNGEONENTER ); if( pDungeonSox->GetFieldFromLablePtr( GetGameRoom()->GetGameTask()->GetDungeonEnterTableID(), "_StageOutDurability" )->GetInteger() == 0) return false; OnDecreaseEquipDurability( CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::StageGiveupDurabilityPenalty ), true ); OnDecreaseInvenDurability( CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::StageGiveupDurabilityPenalty ), true ); // 포기하고 가면 Equip 이야 알아서 갱신되지만 인벤토리는 그렇지 않습니다. 그래서 알려줘야 합니다. if( m_pSession ) { float fRatio = CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::StageGiveupDurabilityPenalty ); m_pSession->SendDecreaseDurabilityInventory( 1, (void*)&fRatio ); } return true; } void CDnPlayerActor::RecvPartyRefreshGateInfo( const EtVector3& Pos ) { SetPosition( Pos ); SetStaticPosition( Pos ); } bool CDnPlayerActor::AttachParts( DnPartsHandle hParts, CDnParts::PartsTypeEnum Index, bool bDelete ) { if( !hParts ) return false; bool bResult = MAPartsBody::AttachParts( hParts, Index, bDelete ); #if defined(_GAMESERVER) int nSkillID = -1; int nSkillLevel = 0; if (hParts->HasPrefixSkill(nSkillID, nSkillLevel)) { DnSkillHandle hSkill = CDnSkill::CreateSkill(GetMySmartPtr(), nSkillID, nSkillLevel); if (!hSkill) { OutputDebug("%s ==> SkillID %d, SkillLevel %d... CreateSkill Failed!!!!\n", __FUNCTION__, nSkillID, nSkillLevel); } else { #if defined(PRE_ADD_PREFIXSKILL_PVP) // 스킬 레벨 데이터를 pve/pvp 인 경우와 나눠서 셋팅해준다. int iSkillLevelDataType = CDnSkill::PVE; if( GetGameRoom()->bIsPvPRoom() ) iSkillLevelDataType = CDnSkill::PVP; hSkill->SelectLevelDataType( iSkillLevelDataType ); #endif // PRE_ADD_PREFIXSKILL_PVP if (!MASkillUser::AddPreFixSystemDefenceSkill(Index, hSkill)) { SAFE_RELEASE_SPTR(hSkill); OutputDebug("MASkillUser::AddPreFixSystemDeffenceSkill ===> slotIndex %d, ItemID %d, SkillID %d, SkillLevel %d AddPreFixSystem Deffence skill Failed !!!\n", Index, hParts->GetClassID(), nSkillID, nSkillLevel); } } } #endif // _GAMESERVER int nLevelUpSkillID = -1; int nLevelUpSkillLevelValue = 0; int nLevelUpItemSkillUsingType = 0; if (hParts->HasLevelUpInfo(nLevelUpSkillID, nLevelUpSkillLevelValue, nLevelUpItemSkillUsingType)) { if (CDnItem::ItemSkillApplyType::SkillLevelUp == nLevelUpItemSkillUsingType) AddSkillLevelUpInfo(Index, nLevelUpSkillID, nLevelUpSkillLevelValue); } return bResult; } bool CDnPlayerActor::DetachParts( CDnParts::PartsTypeEnum Index ) { #if defined(_GAMESERVER) MASkillUser::RemovePreFixSystemDefenceSkill(Index); #endif // _GAMESERVER RemoveSkillLevelUpInfo(Index); // Note: 파츠 분리에 실패한 경우에도 스킬은 사라질 수 있음. bool bResult = MAPartsBody::DetachParts( Index ); return bResult; } bool CDnPlayerActor::AttachCashParts( DnPartsHandle hParts, CDnParts::PartsTypeEnum Index, bool bDelete ) { if( !hParts ) return false; bool bResult = MAPartsBody::AttachCashParts( hParts, Index, bDelete ); int nLevelUpSkillID = -1; int nLevelUpSkillLevelValue = 0; int nLevelUpItemSkillUsingType = 0; if (hParts->HasLevelUpInfo(nLevelUpSkillID, nLevelUpSkillLevelValue, nLevelUpItemSkillUsingType)) { if (CDnItem::ItemSkillApplyType::SkillLevelUp == nLevelUpItemSkillUsingType) AddSkillLevelUpInfoByCashItem(Index, nLevelUpSkillID, nLevelUpSkillLevelValue); } return bResult; } bool CDnPlayerActor::DetachCashParts( CDnParts::PartsTypeEnum Index ) { RemoveSkillLevelUpInfoByCashItem(Index); // Note: 파츠 분리에 실패한 경우에도 스킬은 사라질 수 있음. bool bResult = MAPartsBody::DetachCashParts( Index ); return bResult; } void CDnPlayerActor::ReplacementGlyph( DnSkillHandle hNewSkill ) { DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TGLYPHSKILL ); if( !pSox ) { g_Log.Log( LogType::_FILELOG, L"GlyphSkillTable.ext failed\r\n"); return; } int iSkillID = hNewSkill->GetClassID(); for( int itr = 0; itr < CDnGlyph::GlyphSlotEnum_Amount; ++itr ) { if( m_hGlyph[itr] ) { int eType = pSox->GetFieldFromLablePtr( m_hGlyph[itr]->GetClassID(), "_GlyphType" )->GetInteger(); int iGlyphSkillID = pSox->GetFieldFromLablePtr( m_hGlyph[itr]->GetClassID(), "_SkillID" )->GetInteger(); if( CDnGlyph::PassiveSkill == eType && iSkillID == iGlyphSkillID ) hNewSkill->AddGlyphStateEffect( m_hGlyph[itr]->GetClassID() ); } } } bool CDnPlayerActor::AttachGlyph( DnGlyphHandle hGlyph, CDnGlyph::GlyphSlotEnum Index, bool bDelete /* = false */ ) { if( !MAPlateUser::AttachGlyph( hGlyph, Index, bDelete ) ) return false; DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TGLYPHSKILL ); if( !pSox ) { g_Log.Log( LogType::_FILELOG, L"GlyphSkillTable.ext failed\r\n"); return false; } int eType = pSox->GetFieldFromLablePtr( hGlyph->GetClassID(), "_GlyphType" )->GetInteger(); // 문장에 스킬이 존재 할 경우 문장이 스킬추가 인지 스킬효과추가 인지 알아 낸다. if( CDnItem::TemperedSkill == eType ) { DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TGLYPHSKILL ); if( !pSox ) { g_Log.Log( LogType::_FILELOG, L"GlyphSkillTable.ext failed\r\n"); return false; } int nSkillID = pSox->GetFieldFromLablePtr( hGlyph->GetClassID(), "_SkillID" )->GetInteger(); DnSkillHandle hSkill = FindSkill( nSkillID ); if( !hSkill ) return false; //CDnSkill에 SkillEffect를 추가해야함 hSkill->AddGlyphStateEffect( hGlyph->GetClassID() ); } else if( CDnItem::AddSKill == eType && 0 != hGlyph->GetSkillID() && 0 != hGlyph->GetSkillLevel() ) { AddSkill( hGlyph->GetSkillID(), hGlyph->GetSkillLevel() ); DnSkillHandle hSkill = FindSkill( hGlyph->GetSkillID() ); if( !hSkill ) return false; hSkill->AsEquipItemSkill(); hSkill->SetEquipIndex( Index ); m_ahEquipSkill = hSkill; if( 0.0f != m_afLastEquipItemSkillDelayTime ) { hSkill->SetOnceCoolTime( m_afLastEquipItemSkillDelayTime, m_afLastEquipItemSkillRemainTime ); } } return true; } bool CDnPlayerActor::DetachGlyph( CDnGlyph::GlyphSlotEnum Index ) { DnGlyphHandle hGlyph = m_hGlyph[Index]; if( !hGlyph ) return false; DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TGLYPHSKILL ); if( !pSox ) { g_Log.Log( LogType::_FILELOG, L"GlyphSkillTable.ext failed\r\n"); return false; } int eType = pSox->GetFieldFromLablePtr( hGlyph->GetClassID(), "_GlyphType")->GetInteger(); if( CDnItem::TemperedSkill == eType ) { int nSkillID = pSox->GetFieldFromLablePtr( hGlyph->GetClassID(), "_SkillID")->GetInteger(); DnSkillHandle hSkill = FindSkill( nSkillID ); if( !hSkill ) return false; hSkill->DelGlyphStateEffect( hGlyph->GetClassID() ); } else if( CDnItem::AddSKill == eType && 0 != hGlyph->GetSkillID() && 0 != hGlyph->GetSkillLevel() ) { DnSkillHandle hSkill = FindSkill( hGlyph->GetSkillID() ); m_afLastEquipItemSkillDelayTime = hSkill->GetDelayTime(); m_afLastEquipItemSkillRemainTime = hSkill->GetElapsedDelayTime(); RemoveSkill( hGlyph->GetSkillID() ); } return true; } void CDnPlayerActor::AttachWeapon( DnWeaponHandle hWeapon, int nEquipIndex /*= 0*/, bool bDelete/* = false */) { CDnActor::AttachWeapon( hWeapon, nEquipIndex, bDelete ); #if defined(PRE_ADD_50907) if (IsSkipOnAttatchDetachWeapon() == true) return; #endif // PRE_ADD_50907 RefreshWeaponViewOrder( nEquipIndex ); if( m_hCashWeapon[nEquipIndex] ) { m_hCashWeapon[nEquipIndex]->RecreateCashWeapon( GetMySmartPtr(), nEquipIndex ); LinkCashWeapon( nEquipIndex ); } #if defined(_GAMESERVER) int nSkillID = -1; int nSkillLevel = 0; if (hWeapon->HasPrefixSkill(nSkillID, nSkillLevel)) { DnSkillHandle hSkill = CDnSkill::CreateSkill(GetMySmartPtr(), nSkillID, nSkillLevel); if (!hSkill) { OutputDebug("%s ==> SkillID %d, SkillLevel %d... CreateSkill Failed!!!!\n", __FUNCTION__, nSkillID, nSkillLevel); } else { #if defined(PRE_ADD_PREFIXSKILL_PVP) // 스킬 레벨 데이터를 pve/pvp 인 경우와 나눠서 셋팅해준다. int iSkillLevelDataType = CDnSkill::PVE; if( GetGameRoom()->bIsPvPRoom() ) iSkillLevelDataType = CDnSkill::PVP; hSkill->SelectLevelDataType( iSkillLevelDataType ); #endif // PRE_ADD_PREFIXSKILL_PVP if (!MASkillUser::AddPreFixSystemOffenceSkill(nEquipIndex, hSkill)) { SAFE_RELEASE_SPTR(hSkill); OutputDebug("MASkillUser::AddPreFixSystemOffenceSkill ===> slotIndex %d, ItemID %d, SkillID %d, SkillLevel %d AddPreFixSystem Deffence skill Failed !!!\n", nEquipIndex, hWeapon->GetClassID(), nSkillID, nSkillLevel); } } } #endif // _GAMESERVER int nLevelUpSkillID = -1; int nLevelUpSkillLevelValue = 0; int nLevelUpItemSkillUsingType = 0; if (hWeapon->HasLevelUpInfo(nLevelUpSkillID, nLevelUpSkillLevelValue, nLevelUpItemSkillUsingType)) { if (CDnItem::ItemSkillApplyType::SkillLevelUp == nLevelUpItemSkillUsingType) AddSkillLevelUpInfo(CDnParts::PartsTypeEnum::PartsTypeEnum_Amount+nEquipIndex, nLevelUpSkillID, nLevelUpSkillLevelValue); } } void CDnPlayerActor::DetachWeapon( int nEquipIndex/* = 0*/ ) { CDnActor::DetachWeapon( nEquipIndex ); #if defined(PRE_ADD_50907) if (IsSkipOnAttatchDetachWeapon() == true) return; #endif // PRE_ADD_50907 RefreshWeaponViewOrder( nEquipIndex ); #if defined(_GAMESERVER) MASkillUser::RemovePreFixSystemOffenceSkill(nEquipIndex); #endif // _GAMESERVER RemoveSkillLevelUpInfo(CDnParts::PartsTypeEnum::PartsTypeEnum_Amount+nEquipIndex); } void CDnPlayerActor::AttachCashWeapon( DnWeaponHandle hWeapon, int nEquipIndex, bool bDelete ) { if( m_hCashWeapon[nEquipIndex] ) { DetachCashWeapon( nEquipIndex ); } m_hCashWeapon[nEquipIndex] = hWeapon; m_bCashSelfDeleteWeapon[nEquipIndex] = bDelete; if( !m_hCashWeapon[nEquipIndex] ) return; m_hCashWeapon[nEquipIndex]->CreateObject(); RefreshWeaponViewOrder( nEquipIndex ); m_hCashWeapon[nEquipIndex]->RecreateCashWeapon( GetMySmartPtr(), nEquipIndex ); LinkCashWeapon( nEquipIndex ); int nLevelUpSkillID = -1; int nLevelUpSkillLevelValue = 0; int nLevelUpItemSkillUsingType = 0; if (hWeapon->HasLevelUpInfo(nLevelUpSkillID, nLevelUpSkillLevelValue, nLevelUpItemSkillUsingType)) { if (CDnItem::ItemSkillApplyType::SkillLevelUp == nLevelUpItemSkillUsingType) AddSkillLevelUpInfoByCashItem(CASHEQUIP_WEAPON1+nEquipIndex, nLevelUpSkillID, nLevelUpSkillLevelValue); } } void CDnPlayerActor::LinkCashWeapon( int nEquipIndex ) { switch( m_hCashWeapon[nEquipIndex]->GetEquipType() ) { case CDnWeapon::Sword: case CDnWeapon::Axe: case CDnWeapon::Hammer: case CDnWeapon::SmallBow: case CDnWeapon::BigBow: case CDnWeapon::CrossBow: case CDnWeapon::Staff: case CDnWeapon::Book: case CDnWeapon::Orb: case CDnWeapon::Puppet: case CDnWeapon::Mace: case CDnWeapon::Flail: case CDnWeapon::Wand: case CDnWeapon::Shield: case CDnWeapon::Gauntlet: m_hCashWeapon[nEquipIndex]->LinkWeapon( GetMySmartPtr(), nEquipIndex ); break; case CDnWeapon::Arrow: m_hCashWeapon[nEquipIndex]->LinkWeapon( GetMySmartPtr(), m_hCashWeapon[0] ); break; } } void CDnPlayerActor::DetachCashWeapon( int nEquipIndex ) { if( !m_hCashWeapon[nEquipIndex] ) return; m_hCashWeapon[nEquipIndex]->FreeObject(); m_hCashWeapon[nEquipIndex]->UnlinkWeapon(); if( m_bCashSelfDeleteWeapon[nEquipIndex] ) { SAFE_RELEASE_SPTR( m_hCashWeapon[nEquipIndex] ); m_bCashSelfDeleteWeapon[nEquipIndex] = false; } m_hCashWeapon[nEquipIndex].Identity(); RefreshWeaponViewOrder( nEquipIndex ); RemoveSkillLevelUpInfoByCashItem(CASHEQUIP_WEAPON1+nEquipIndex); } void CDnPlayerActor::ShowCashWeapon( int nEquipIndex, bool bShow ) { if( m_hCashWeapon[nEquipIndex] ) m_hCashWeapon[nEquipIndex]->ShowWeapon( bShow ); } void CDnPlayerActor::SetWeaponViewOrder( int nEquipIndex, bool bShowCash ) { if( nEquipIndex < 0 || nEquipIndex >= 2 ) return; m_bWeaponViewOrder[nEquipIndex] = bShowCash; } void CDnPlayerActor::RefreshWeaponViewOrder( int nEquipIndex ) { if( nEquipIndex < 0 || nEquipIndex >= 2 ) return; if( m_hWeapon[nEquipIndex] && m_hCashWeapon[nEquipIndex] ) { if( m_bWeaponViewOrder[nEquipIndex] ) { if( !m_hCashWeapon[nEquipIndex]->IsCreateObject() ) { m_hCashWeapon[nEquipIndex]->CreateObject(); m_hCashWeapon[nEquipIndex]->RecreateCashWeapon( GetMySmartPtr(), nEquipIndex ); LinkCashWeapon( nEquipIndex ); m_hCashWeapon[nEquipIndex]->ShowWeapon( true ); } if( m_hWeapon[nEquipIndex]->IsCreateObject() ) { m_hWeapon[nEquipIndex]->FreeObject(); m_hWeapon[nEquipIndex]->ShowWeapon( false ); } } else { if( !m_hWeapon[nEquipIndex]->IsCreateObject() ) { m_hWeapon[nEquipIndex]->CreateObject(); LinkWeapon( nEquipIndex ); m_hWeapon[nEquipIndex]->ShowWeapon( true ); } if( m_hCashWeapon[nEquipIndex]->IsCreateObject() ) { m_hCashWeapon[nEquipIndex]->FreeObject(); m_hCashWeapon[nEquipIndex]->ShowWeapon( false ); } } } else if( m_hWeapon[nEquipIndex] && !m_hCashWeapon[nEquipIndex] ) { if( !m_hWeapon[nEquipIndex]->IsCreateObject() ) { m_hWeapon[nEquipIndex]->CreateObject(); LinkWeapon( nEquipIndex ); m_hWeapon[nEquipIndex]->ShowWeapon( true ); } } else { if( nEquipIndex == 0 ) { if( m_hCashWeapon[nEquipIndex] && m_hCashWeapon[nEquipIndex]->IsCreateObject() ) { m_hCashWeapon[nEquipIndex]->FreeObject(); m_hCashWeapon[nEquipIndex]->ShowWeapon( false ); } } } SetBattleMode( IsBattleMode() ); } void CDnPlayerActor::OnEventCP( CPTypeEnum Type, int nResult ) { #if defined( PRE_ADD_CP_RENEWAL ) MACP_Renewal::OnEventCP( Type, nResult ); #else // #if defined( PRE_ADD_CP_RENEWAL ) MACP::OnEventCP( Type, nResult ); #endif // #if defined( PRE_ADD_CP_RENEWAL ) switch( Type ) { case MACP::MaxComboCount: case MACP::KillBossCount: case MACP::SuperAmmorBreakScore: case MACP::GenocideScore: case MACP::AirComboScore: case MACP::RebirthPlayerScore: case MACP::ComboScore: case MACP::PartyComboScore: case MACP::AssistMonsterScore: { BYTE pBuffer[32]; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &Type, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Write( &nResult, sizeof(int) ); Send( eActor::SC_CP, &Stream ); } break; } } void CDnPlayerActor::UpdateAttackedCPPoint( CDnDamageBase *pHitter , CDnWeapon::HitTypeEnum eHitType ) { bool bIsSameTeam = false; if( pHitter && pHitter->GetActorHandle() ) { if( GetTeam() == pHitter->GetActorHandle()->GetTeam() ) { bIsSameTeam = true; } } if( bIsSameTeam == false ) { switch( eHitType ) { case CDnWeapon::Normal: case CDnWeapon::CriticalRes: { UpdateAttackedHit(); } break; case CDnWeapon::Critical: { UpdateAttackedCriticalHit(); } break; case CDnWeapon::Stun: { UpdateAttackedStunHit(); } break; } } } DnWeaponHandle CDnPlayerActor::GetActiveWeapon( int nEquipIndex ) { if( m_bWeaponViewOrder[nEquipIndex] && m_hCashWeapon[nEquipIndex] ) return m_hCashWeapon[nEquipIndex]; return CDnActor::GetActiveWeapon( nEquipIndex ); } float CDnPlayerActor::PreCalcDamage( CDnDamageBase *pHitter, SHitParam &HitParam, const float fDefenseRate, float fStateEffectAttackM ) { float fResult = CDnActor::PreCalcDamage( pHitter, HitParam, fDefenseRate, fStateEffectAttackM ); return fResult; } bool CDnPlayerActor::IsDie() { bool bResult = CDnPlayerState::IsDie(); if( bResult ) return true; // PvP 에서는 return true 할 필요가 없습니다. if( IsGMTrace() && GetGameRoom() && !GetGameRoom()->bIsPvPRoom() ) return true; return false; } bool CDnPlayerActor::SetActionQueue( const char *szActionName, int nLoopCount, float fBlendFrame , float fStartFrame , bool bCheck , bool bCheckStateEffect ) { if(m_bShootMode && !m_bTransformMode && m_bBattleMode) szActionName = GetChangeShootActionName(szActionName); #if defined(PRE_FIX_63219) //#63219 //블럭이 발동된 시점에 좌우 이동키를 누르면 MAWalkMovement에서 OnStop이 호출 되고 //이때 CmdStop으로 "Stand"동작이 설정되어 버린다. //클라이언트에서 이동중 block 발동 되고 특수공격키 눌러 패시브 스킬을 사용하면 //서버에서는 Stand동작으로 변경되 서버에서는 스킬이 동작 하지 않게 됨. //일단 Block동작에 Stand로 변경은 무시 if ((strstr(m_szAction.c_str(), "Block") != NULL || strstr(m_szActionQueue.c_str(), "Block") != NULL)&& strstr(szActionName, "Stand") != NULL) { //#68376 스탠스 오브 페이스 오류 //Skill_StandOfFaith_EX_Block 이런 동작에서 Stand로는 전환이 되어야 한다... //다른 Block이 있을 수 있어서 "StandOfFaith"이 있으면 전환 하도록 수정. bool isSkillAction = (strstr(m_szAction.c_str(), "StandOfFaith") != NULL || strstr(m_szActionQueue.c_str(), "StandOfFaith")); if (isSkillAction == false) return false; } #endif // PRE_FIX_63219 return CDnActor::SetActionQueue( szActionName , nLoopCount, fBlendFrame, fStartFrame, bCheck, bCheckStateEffect ); } void CDnPlayerActor::_UpdateMaxProjectileCount( int nActionIndex, bool bUpdateReservedCount/* = false*/ ) { if( NULL == m_pProjectileCountInfo ) return; // 현재 액션에서 최대한 발사할 수 있는 발사체 갯수. m_iNowMaxProjectileCount = 0; map::const_iterator iterProj = m_pProjectileCountInfo->mapMaxProjectileCountInAction.find( nActionIndex ); if( m_pProjectileCountInfo->mapMaxProjectileCountInAction.end() != iterProj ) m_iNowMaxProjectileCount = iterProj->second; // 발사체 시그널 순서대로 사용하는 무기 테이블 인덱스 // 보통 발사체 시그널과 무기 SendAction 이 섞여있지 않다면 덱이 리셋되지 않아서 아래쪽 무기 체크하는 루프에서 계속 쌓이게 되므로 // 여기서 한번 클리어 해준다. m_setWeaponIDUsingProjectileSignal.clear(); map >::const_iterator iterWeaponIDs = m_pProjectileCountInfo->mapUsingProjectileWeaponTableIDs.find( nActionIndex ); if( m_pProjectileCountInfo->mapUsingProjectileWeaponTableIDs.end() != iterWeaponIDs ) m_setWeaponIDUsingProjectileSignal = iterWeaponIDs->second; // 현재 액션에서 발사체가 나가는 프레임들 모음. // 보통 발사체 시그널과 무기 SendAction 이 섞여있지 않다면 덱이 리셋되지 않아서 아래쪽 무기 체크하는 루프에서 계속 쌓이게 되므로 // 여기서 한번 클리어 해준다. m_dqProjectileSignalOffset.clear(); map >::const_iterator iterProjOffset = m_pProjectileCountInfo->mapProjectileSignalFrameOffset.find( nActionIndex ); if( m_pProjectileCountInfo->mapProjectileSignalFrameOffset.end() != iterProjOffset ) m_dqProjectileSignalOffset = iterProjOffset->second; sort( m_dqProjectileSignalOffset.begin(), m_dqProjectileSignalOffset.end() ); map >::const_iterator iterWeapon = m_pProjectileCountInfo->mapSendActionWeapon.find( nActionIndex ); if( m_pProjectileCountInfo->mapSendActionWeapon.end() != iterWeapon ) { const vector& vlWeaponInfo = iterWeapon->second; for( int i = 0; i < (int)vlWeaponInfo.size(); ++i ) { const CDnActionSpecificInfo::S_WEAPONACTION_INFO& Struct = vlWeaponInfo.at( i ); // 그냥 호출하면 TDnPlayer~~::GetActiveWeapon() 함수가 호출되어 발차기 중 쏘는 액션으로 바뀔 때 발 무기가 얻어와지므로 핵체크에 걸려서 CDnPlayerActor::GetActiveWeapon() 을 호출 하돌고 변경. DnWeaponHandle hWeapon = CDnPlayerActor::GetActiveWeapon( Struct.iWeaponIndex ); if( hWeapon ) { if( false == Struct.strActionName.empty() ) { // 플레이어 액터인 경우현재 액션에서 쏠 수 있는 발사체 갯수에 무기의 발사체 갯수를 더해준다. if( hWeapon->IsExistAction( Struct.strActionName.c_str() ) ) { hWeapon->SetActionQueue( Struct.strActionName.c_str() ); if( m_ActorType <= Reserved6 ) { int nWeaponActionIndex = hWeapon->GetElementIndex( Struct.strActionName.c_str() ); int nAddCount = hWeapon->GetMaxProjectileCountInAction( nWeaponActionIndex ); if( false == bUpdateReservedCount ) m_iNowMaxProjectileCount += nAddCount; else m_iReservedProjectileCount += nAddCount; // 무기에서 쏘는 발사체에서 사용하는 무기 테이블 정보 추가. hWeapon->AddUsingProjectileWeaponTableIDs( nWeaponActionIndex, m_setWeaponIDUsingProjectileSignal ); // 무기에서 쏘는 발사체 프레임 정보 추가. // 마지막 프레임 같은 곳에 무기에게 shoot 액션을 하라는 시그널을 박던가하면 이 핵 체크 루틴에 걸릴 수 있습니다. // 현재로썬 그럴 가능성이 없기 때문에 일단 패스. hWeapon->AddProjectileSignalOffset( nWeaponActionIndex, Struct.iFrame, m_dqProjectileSignalOffset ); // 결과로 얻어온 것은 정렬로 가지고 있도록 합니다. // 프레임 순서대로 정렬해서 갖고 있다가 비교할때 사용함. sort( m_dqProjectileSignalOffset.begin(), m_dqProjectileSignalOffset.end() ); } } } } } } } bool CDnPlayerActor::UseAndCheckAvailProjectileCount( void ) { if( m_iNowMaxProjectileCount <= 0 ) { // 발사체에서 발사체 쏘는 경우처럼 예약된 발사체 갯수도 없는 경우 핵으로 판단. if( m_iReservedProjectileCount <= 0 ) { // 핵으로 발사체를 마구 날리고 있음. OutputDebug( "CS_PROJECTILE: 현재 액션의 최대 갯수를 넘는 발사체 요청. 핵으로 판단됨.\n" ); return false; } --m_iReservedProjectileCount; } if( 0 < m_iNowMaxProjectileCount ) --m_iNowMaxProjectileCount; return true; } bool CDnPlayerActor::IsExclusiveSkill( int iSkillID, int iExclusiveID ) { #ifndef PRE_FIX_SKILLLIST // 배운 스킬 중에 같은 스킬 못 배우게 하는 id 가 있으면 true. int iNumSkill = (int)m_vlhSkillList.size(); for( int iSkill = 0; iSkill < iNumSkill; ++iSkill ) { DnSkillHandle hSkill = m_vlhSkillList.at( iSkill ); // 자신의 스킬은 제외하고. if( hSkill->GetClassID() == iSkillID ) continue; if( 0 == hSkill->GetExclusiveID() ) continue; if( iExclusiveID == hSkill->GetExclusiveID() ) { return true; } } #else // 배운 스킬 중에 같은 스킬 못 배우게 하는 id 가 있으면 true. DWORD dwNumSkill = GetSkillCount(); for( DWORD i = 0; i < dwNumSkill; ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); // 자신의 스킬은 제외하고. if( hSkill->GetClassID() == iSkillID ) continue; if( 0 == hSkill->GetExclusiveID() ) continue; if( iExclusiveID == hSkill->GetExclusiveID() ) { return true; } } #endif // #ifndef PRE_FIX_SKILLLIST return false; } int CDnPlayerActor::GetAvailSkillPointByJob( int iSkillID ) { // 현재 직업 차수 int iJobDegree = g_pDataManager->GetJobNumber( GetJobClassID() ); int iWholeSP = GetLevelUpSkillPoint( 1, GetLevel() ); int iWholeAvailSPByJob = int(iWholeSP * m_pSession->GetAvailSkillPointRatioByJob( iSkillID )); // 스킬에 필요한 직업에 해당되는 사용한 SP 를 모은다. int iUsedSPByJob = 0; const TSkillData* pSkillDataForNeedJobID = g_pDataManager->GetSkillData( iSkillID ); #ifndef PRE_FIX_SKILLLIST for( int i = 0; i < (int)m_vlhSkillList.size(); ++i ) { DnSkillHandle hSkill = m_vlhSkillList.at( i ); #else DWORD dwNumSkill = GetSkillCount(); for( DWORD i = 0; i < dwNumSkill; ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); #endif // #ifndef PRE_FIX_SKILLLIST if( hSkill->GetNeedJobClassID() == pSkillDataForNeedJobID->nNeedJobID ) { int iLevel = hSkill->GetLevel(); for( int k = 0; k < iLevel; ++k ) { const TSkillData* pSkillData = g_pDataManager->GetSkillData( hSkill->GetClassID() ); iUsedSPByJob += pSkillData->vLevelDataList.at( k ).nNeedSkillPoint; } } } // 전체 사용가능 SP 보다 직업 SP 가 남은 것이 많으면 전체 사용가능 SP 가 진짜이므로 해당 포인트로 리턴. int iAvailPoint = iWholeAvailSPByJob - iUsedSPByJob; if( m_pSession->GetSkillPoint() < iAvailPoint ) iAvailPoint = m_pSession->GetSkillPoint(); return iAvailPoint; } int CDnPlayerActor::GetUsedSkillPointInThisJob( const int nJobID ) { int iUsedSPByJob = 0; #ifndef PRE_FIX_SKILLLIST for( int i = 0; i < (int)m_vlhSkillList.size(); ++i ) { DnSkillHandle hSkill = m_vlhSkillList.at( i ); #else DWORD dwNumSkill = GetSkillCount(); for( DWORD i = 0; i < dwNumSkill; ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); #endif // #ifndef PRE_FIX_SKILLLIST if( hSkill->GetNeedJobClassID() == nJobID ) { int iLevel = hSkill->GetLevel(); for( int k = 0; k < iLevel; ++k ) { const TSkillData* pSkillData = g_pDataManager->GetSkillData( hSkill->GetClassID() ); iUsedSPByJob += pSkillData->vLevelDataList.at( k ).nNeedSkillPoint; } } } return iUsedSPByJob; } void CDnPlayerActor::OnChangePlaySpeed( DWORD dwFrame, float fSpeed ) { // 맞거나 해서 슈퍼아머 때문에 플레이 시간이 늘어난 경우 시그널 텀 프레임 값을 // 같이 업데이트 해줘야 핵에 안걸린다. // 프레임 스피드 변경된 가운데 액션을 시작할 수도 있으므로 값을 받아놓고 체크할 때 적용하는 것으로 변경. //for( int i = 0; i < (int)m_dqProjectileSignalOffset.size(); ++i ) //{ // int& iSignalOffsetFrame = m_dqProjectileSignalOffset.at( i ); // iSignalOffsetFrame = (int)((float)iSignalOffsetFrame * fSpeed ); //} // 슈퍼아머로 아예 프레임이 순간적으로 일정 시간 정지되는 경우 if( dwFrame <= 200 && fSpeed < 0.05f) m_bCheckProjectileSignalTerm = false; m_fFrameSpeed = fSpeed; } void CDnPlayerActor::ProcessCompanion( LOCAL_TIME LocalTime, float fDelta ) { CheckPetSatietyPercent(); } void CDnPlayerActor::CheckPetSatietyPercent() { if( !IsCanPetMode() ) return; const TVehicle* pEquipPet = GetUserSession()->GetItem()->GetPetEquip(); if( pEquipPet && (pEquipPet->nType & Pet::Type::ePETTYPE_SATIETY) && pEquipPet->Vehicle[Pet::Slot::Body].nItemID > 0 ) { if( GetUserSession()->GetItem()->GetPetSatietyPercent() < 50.f ) // 여기에 Limit 체크 해야 합니다. { DnSkillHandle hFirstSkill = FindSkill(pEquipPet->nSkillID1); if(hFirstSkill && hFirstSkill->GetSkillType() == CDnSkill::Passive ) { CmdForceRemoveSkill(pEquipPet->nSkillID1); m_bDeletedPetSkill[Pet::Skill::Primary] = true; } DnSkillHandle hSecondSkill = FindSkill(pEquipPet->nSkillID2); if(hSecondSkill && hSecondSkill->GetSkillType() == CDnSkill::Passive ) { CmdForceRemoveSkill(pEquipPet->nSkillID2); m_bDeletedPetSkill[Pet::Skill::Secondary] = true; } } else { if( m_bDeletedPetSkill[Pet::Skill::Primary] == true ) { if( !IsExistSkill( pEquipPet->nSkillID1 ) ) CmdForceAddSkill( pEquipPet->nSkillID1 ); m_bDeletedPetSkill[Pet::Skill::Primary] = false; } if( m_bDeletedPetSkill[Pet::Skill::Secondary] == true ) { if( !IsExistSkill( pEquipPet->nSkillID2 ) ) CmdForceAddSkill( pEquipPet->nSkillID2 ); m_bDeletedPetSkill[Pet::Skill::Secondary] = false; } } } } CDnVehicleActor *CDnPlayerActor::GetMyVehicleActor() { if(m_hVehicleActor) { return static_cast(m_hVehicleActor.GetPointer()); } return NULL; } bool CDnPlayerActor::IsCanVehicleMode() { if( IsDie() || IsGhost() || m_nTeam == PvPCommon::Team::Observer ) return false; if( IsSpectatorMode() ) return false; int nCurrentMapID = 0; CDnGameTask *pGameTask = (CDnGameTask *)CTaskManager::GetInstance(GetRoom()).GetTask( "GameTask" ); if( pGameTask ) // 게임테스크일때의 검출 nCurrentMapID = pGameTask->GetMapTableID(); else nCurrentMapID = m_pSession->GetMapIndex(); bool bIsCanVehicleMode = g_pDataManager->IsVehicleMode(nCurrentMapID); #if defined( PRE_ADD_FORCE_RIDE_ENABLE_TRIGGER ) bIsCanVehicleMode = bIsCanVehicleMode && m_bForceEnableRideByTrigger; #endif // #if defined( PRE_ADD_FORCE_RIDE_ENABLE_TRIGGER ) return bIsCanVehicleMode; } bool CDnPlayerActor::IsCanPetMode() { int nCurrentMapID = 0; CDnGameTask *pGameTask = (CDnGameTask *)CTaskManager::GetInstance(GetRoom()).GetTask( "GameTask" ); if( pGameTask ) // 게임테스크일때의 검출 nCurrentMapID = pGameTask->GetMapTableID(); else nCurrentMapID = m_pSession->GetMapIndex(); return g_pDataManager->IsPetMode( nCurrentMapID ); } void CDnPlayerActor::RideVehicle(TVehicle *pInfo) { if( !IsPlayerActor() || IsVehicleMode() || !pInfo ) return; if( !IsCanVehicleMode()) return; if(IsBattleMode()) SetBattleMode(false); int nVehicleActorTableID = g_pDataManager->GetVehicleActorID(pInfo->Vehicle[Vehicle::Slot::Body].nItemID); DnActorHandle hVehicle; hVehicle = CreateActor( GetRoom(), nVehicleActorTableID ); if( !hVehicle || !hVehicle->GetObjectHandle()) return; CDnVehicleActor* pVehicle = (CDnVehicleActor *)hVehicle.GetPointer(); if(!pVehicle) return; pVehicle->SetMyPlayerActor(GetActorHandle()); pVehicle->Show( false ); float fLandHeight = INSTANCE(CDnWorld).GetHeight( GetMatEx()->m_vPosition ); pVehicle->SetPosition(GetMatEx()->m_vPosition); pVehicle->SetPrevPosition(GetMatEx()->m_vPosition); pVehicle->SetAddHeight( GetMatEx()->m_vPosition.y - fLandHeight ); pVehicle->GetMatEx()->CopyRotationFromThis(GetMatEx()); pVehicle->SetItemID(pInfo->Vehicle[Vehicle::Slot::Body].nItemID); // 자신의 아이템 아이디를 가지고 있습니다. #ifdef PRE_ADD_VEHICLE_ACTION_STRING DNTableFileFormat* pVehicleTable = GetDNTable( CDnTableDB::TVEHICLE ); std::string strVehicleAction = pVehicleTable->GetFieldFromLablePtr( pInfo->Vehicle[Vehicle::Slot::Body].nItemID , "_RiderString" )->GetString(); if(!strVehicleAction.empty() || strstr(strVehicleAction.c_str() , "Vehicle_") != NULL ) pVehicle->SetVehicleActionString(strVehicleAction.c_str()); #endif // 부위별 장착 // 파츠 정보를 함유하고 들어오는 경우는 장착을 시켜줍니다. for (int i= Vehicle::Slot::Saddle; iVehicle[i].nItemID != 0 && pInfo->Vehicle[i].nSerial != 0) { pVehicle->EquipItem(pInfo->Vehicle[i]); } } //////////////// if(pInfo->dwPartsColor1 != 0 && pInfo->dwPartsColor1 != -1) // 설정된 색이 있는경우에는 색 설정을 해줍니다. { pVehicle->ChangeHairColor(pInfo->dwPartsColor1); // 기본 테이블에 정의된 색지정. } SetMyVehicleActor(pVehicle->GetActorHandle()); pVehicle->InitializeRoom((CDNGameRoom*)GetRoom()); pVehicle->Initialize(); pVehicle->SetUniqueID( m_pSession->GetVehicleObjectID() ); pVehicle->SetVehicleClassID(pInfo->Vehicle[Vehicle::Slot::Body].nItemID); pVehicle->RefreshState(); pVehicle->Show(true); pVehicle->SetAttachToPlayer(true); pVehicle->SetProcess(true); SetVehicleMode(true); pVehicle->SetActionQueue( "Stand" ); pVehicle->SetTeam(GetTeam()); } void CDnPlayerActor::UnRideVehicle(bool bForce) { if(!IsVehicleMode()) return; if(GetMyVehicleActor() && !GetMyVehicleActor()->IsDestroy()) { float fLandHeight = INSTANCE(CDnWorld).GetHeight( GetMyVehicleActor()->GetMatEx()->m_vPosition ); SetPosition(GetMyVehicleActor()->GetMatEx()->m_vPosition); SetPrevPosition(GetMyVehicleActor()->GetMatEx()->m_vPosition); SetAddHeight( GetMyVehicleActor()->GetMatEx()->m_vPosition.y - fLandHeight ); GetMyVehicleActor()->SetAttachToPlayer(false); GetMyVehicleActor()->Show(false); GetMyVehicleActor()->SetUniqueID(GetMyVehicleActor()->GetUniqueID() +1 ); // SetDestroy가 설정되고 프로세스 이후에 액터가 삭제되는 사이에 새로운 말을 타게되면 유니크 아이디가 겹치는 문제가 발생된다 // < 신규 유니크아이디를 이후 프로세스에서 삭제하는경우가 생김 > 그러므로 디스트로이 하기전에 유니크아이디를 변경시켜주자. GetMyVehicleActor()->SetDestroy(); } SetVehicleMode(false); Show(true); SetActionQueue("Stand"); return; } void CDnPlayerActor::ForceUnRideVehicle() { if(!IsVehicleMode()) return; CDNGameRoom* pRoom = GetGameRoom(); if (!pRoom) return; api_trigger_UnRideVehicle( pRoom, GetSessionID() ); } void CDnPlayerActor::RemoveVehicleStateEffectImmediately(int nBlowIndex ) { if(IsVehicleMode() && GetMyVehicleActor()) { GetMyVehicleActor()->CmdRemoveStateEffectImmediately( (STATE_BLOW::emBLOW_INDEX)nBlowIndex ); GetMyVehicleActor()->SendRemoveStateEffect( (STATE_BLOW::emBLOW_INDEX)nBlowIndex ); } } void CDnPlayerActor::SetForceEnableRide( const bool bForceEnableRide ) { if( false == bForceEnableRide ) ForceUnRideVehicle(); m_bForceEnableRideByTrigger = bForceEnableRide; m_pSession->SendTriggerForceEnableRide( GetSessionID(), bForceEnableRide ); } void CDnPlayerActor::OnCannonMonsterDie( void ) { // 대포 모드 해제 EndCannonMode(); SetActionQueue( "Stand" ); } void CDnPlayerActor::EndCannonMode() { m_bPlayerCannonMode = false; m_hCannonMonsterActor.Identity(); DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TACTOR ); if( pSox && pSox->IsExistItem(GetClassID())) { float fWeight = pSox->GetFieldFromLablePtr( GetClassID(), "_Weight" )->GetFloat(); int fPressLevel = pSox->GetFieldFromLablePtr( GetClassID(), "_PressLevel" )->GetInteger(); SetWeight(fWeight); SetPressLevel(fPressLevel); } } void CDnPlayerActor::ProcessNonLocalShootModeAction() { if(!IsLocalActor()) return; if( strcmp( GetCurrentAction(), "MOD_Stand" ) == NULL ) { if( strstr( m_szCustomAction.c_str(), "MOD_Shoot" ) ) { float fFrame = ( ( CDnActionBase::m_LocalTime - m_CustomActionTime ) / 1000.f ) * CDnActionBase::m_fFPS; CmdStop( "MOD_Shoot_Stand", 0, 8.f, fFrame ); ResetCustomAction(); } } } const char *CDnPlayerActor::GetChangeShootActionName(const char *szActionName) // 작업중. { if(strcmp(szActionName,"Stand") == NULL) { szActionName = "MOD_Stand"; } if(strcmp(szActionName,"Move_Front") == NULL) { szActionName = "MOD_Move_Front"; } if(strcmp(szActionName,"Move_Back") == NULL) { szActionName = "MOD_Move_Back"; } if(strcmp(szActionName,"Move_Left") == NULL) { szActionName = "MOD_Move_Left"; } if(strcmp(szActionName,"Move_Right") == NULL) { szActionName = "MOD_Move_Right"; } if(strcmp(szActionName,"Jump") == NULL) { szActionName = "MOD_Jump"; } if(strstr(szActionName,"Attack1")) { szActionName = "MOD_Shoot_Stand"; } return szActionName; } bool CDnPlayerActor::IsTransformSkill( int nSkillID ) { for( DWORD i=0; iIsApplied(STATE_BLOW::BLOW_232)) { DNVector(DnBlowHandle) vlBlows; GatherAppliedStateBlowByBlowIndex(STATE_BLOW::BLOW_232, vlBlows); for (DWORD i = 0; i < vlBlows.size(); i++) { if( vlBlows[i] ) { CDnTransformBlow *pTransformBlow = static_cast( vlBlows[i].GetPointer() ); if( pTransformBlow && pTransformBlow->GetParentSkillInfo()->iSkillID == m_hProcessSkill->GetClassID() ) { bCancelSkill = false; break; } } } } if( bCancelSkill == true ) { CancelUsingSkill(); SetAction("Stand",0.f,0.f); } } else { SetAction("Stand",0.f,0.f); } if(IsTransformMode() == true) { int nActorIndex = pMonsterSox->IsExistItem(m_nMonsterMutationTableID) ? pMonsterSox->GetFieldFromLablePtr( m_nMonsterMutationTableID , "_ActorTableID" )->GetInteger() : 0; int nSkillTableIndex = pMonsterSox->IsExistItem(m_nMonsterMutationTableID) ? pMonsterSox->GetFieldFromLablePtr( m_nMonsterMutationTableID , "_SkillTable" )->GetInteger() : 0; if(GetStateBlow()->IsApplied(STATE_BLOW::BLOW_176)) { CmdRemoveStateEffect(STATE_BLOW::BLOW_176); GetStateBlow()->Process( 0, 0.f ); } DNTableFileFormat* pActorSox = GetDNTable( CDnTableDB::TACTOR ); if(!pActorSox || !pActorSox->IsExistItem(nActorIndex)) return; if(nActorIndex > 0) { SwapSingleSkin( nActorIndex ); ResetCustomAction(); SetAction( "Stand", 0.f, 0.f ); } else return; if( pMonsterSkillSox && pMonsterSkillSox->IsExistItem(nSkillTableIndex)) { if(!m_vecTransformSkillList.empty()) // 이미 적용된 스킬이있을때는 지워준다. < 변신했다가 또변신하는경우 > { for (int i=0; i<(int)m_vecTransformSkillList.size(); i++) { if(m_vecTransformSkillList[i] != -1 ) RemoveSkill(m_vecTransformSkillList[i]); } m_vecTransformSkillList.clear(); } for (int i=0; iGetFieldFromLablePtr( nSkillTableIndex, szStr )->GetInteger(); sprintf_s( szStr, "_SkillLevel%d", i+1 ); nSkillLevel = pMonsterSkillSox->GetFieldFromLablePtr( nSkillTableIndex, szStr )->GetInteger(); if(nSkillIndex != -1 && nSkillLevel != -1) { m_vecTransformSkillList.push_back(nSkillIndex); AddSkill(nSkillIndex,nSkillLevel); } else break; } std::string strSkillVec = ""; for(DWORD i=0; i 0) { CmdRemoveStateEffectFromID(m_nAllowedSkill); m_nAllowedSkill = 0; } m_vecTransformSkillList.clear(); } SetBattleMode(true); RefreshState(); } void CDnPlayerActor::ToggleTransformMode( bool bTrue,int nMonsterMutatorTableID , bool bForce, const char* strEndAction ) { if( m_bTransformMode == bTrue && !bForce ) return; m_bTransformMode = bTrue; m_nMonsterMutationTableID = nMonsterMutatorTableID; m_bRefreshTransformMode = true; m_strTransformEndAction = strEndAction; } void CDnPlayerActor::CmdShootMode(bool bTrue) { m_bShootMode = bTrue; if(m_bShootMode) CmdStop("MOD_Shoot_Stand"); else CmdStop("Stand"); BYTE pBuffer[128]; CPacketCompressStream Stream( pBuffer, 128 ); DWORD dwUniqueID = GetUniqueID(); Stream.Write( &dwUniqueID, sizeof(dwUniqueID) ); Stream.Write( &m_bShootMode, sizeof(bool) ); Send( eActor::SC_CMDSHOOTMODE, &Stream ); } void CDnPlayerActor::CmdWarp( EtVector3 &vPos, EtVector2 &vLook, CDNUserSession* pGameSession, bool bCheckPlayerFollowSummonedMonster/*=false*/ ) { if(IsVehicleMode() && GetMyVehicleActor() ) GetMyVehicleActor()->CmdWarp(vPos, vLook, pGameSession); CDnActor::CmdWarp( vPos, vLook, pGameSession, bCheckPlayerFollowSummonedMonster ); if( m_pPlayerSpeedHackChecker ) m_pPlayerSpeedHackChecker->ResetInvalid(); // #32426 소환체 컨트롤 기능 - 스테이지 이동 혹은 존 이동(CmdWarp)를 할 때 따라가야 되는 // 이 플레이어가 소환한 몬스터 객체들 체크해서 처리. if( bCheckPlayerFollowSummonedMonster ) { list::iterator iter = m_listSummonedMonstersFollowStageInfos.begin(); for( iter; iter != m_listSummonedMonstersFollowStageInfos.end(); ) { DnMonsterActorHandle hMonsterActor = iter->hMonster; if( hMonsterActor && false == hMonsterActor->IsDie() ) { hMonsterActor->CmdWarp( vPos, vLook, pGameSession, false ); hMonsterActor->ResetAggro(); hMonsterActor->CmdAction( "Stand" ); ++iter; } else { iter = m_listSummonedMonstersFollowStageInfos.erase( iter ); } } } } void CDnPlayerActor::RequestCooltimeParrySuccess( int iSkillID ) { BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &iSkillID, sizeof(int) ); Send( eActor::CS_COOLTIMEPARRY_SUCCESS, &Stream ); } bool CDnPlayerActor::IsInvalidPlayerChecker() { if( !m_pSession ) return false; if( m_pSession->GetHackPlayRestraintValue() <= 0 ) return false; if( m_pSession->GetHackAbuseDBValue() + m_nInvalidPlayerCheckCounter >= m_pSession->GetHackPlayRestraintValue() ) return true; return false; } void CDnPlayerActor::SwapSingleSkin( int nChangeActorTableID ) { if( m_nSwapSingleSkinActorID == nChangeActorTableID ) return; m_nSwapSingleSkinActorID = nChangeActorTableID; // FreeAction(); #ifdef PRE_FIX_MEMOPT_EXT if (g_pDataManager == NULL) { _ASSERT(0); return; } #endif DNTableFileFormat* pSox = GetDNTable( CDnTableDB::TACTOR ); if( m_nSwapSingleSkinActorID == -1 ) { #ifdef PRE_FIX_MEMOPT_EXT std::string szSkinName, szAniName, szActName; g_pDataManager->GetFileNameFromFileEXT(szSkinName, pSox, m_nClassID, "_SkinName"); g_pDataManager->GetFileNameFromFileEXT(szAniName, pSox, m_nClassID, "_AniName"); g_pDataManager->GetFileNameFromFileEXT(szActName, pSox, m_nClassID, "_ActName"); #else std::string szSkinName = pSox->GetFieldFromLablePtr( m_nClassID, "_SkinName" )->GetString(); std::string szAniName = pSox->GetFieldFromLablePtr( m_nClassID, "_AniName" )->GetString(); std::string szActName = pSox->GetFieldFromLablePtr( m_nClassID, "_ActName" )->GetString(); #endif SAFE_RELEASE_SPTR( GetObjectHandle() ); FreeAction(); LoadSkin( CEtResourceMng::GetInstance().GetFullName( szSkinName ).c_str(), CEtResourceMng::GetInstance().GetFullName( szAniName ).c_str() ); LoadAction( CEtResourceMng::GetInstance().GetFullName( szActName ).c_str() ); SetAction( "Stand", 0.f, 0.f ); for( int i=0; iGetFileNameFromFileEXT(szSkinName, pSox, m_nSwapSingleSkinActorID, "_SkinName"); g_pDataManager->GetFileNameFromFileEXT(szAniName, pSox, m_nSwapSingleSkinActorID, "_AniName"); g_pDataManager->GetFileNameFromFileEXT(szActName, pSox, m_nSwapSingleSkinActorID, "_ActName"); #else std::string szSkinName = pSox->GetFieldFromLablePtr( m_nSwapSingleSkinActorID, "_SkinName" )->GetString(); std::string szAniName = pSox->GetFieldFromLablePtr( m_nSwapSingleSkinActorID, "_AniName" )->GetString(); std::string szActName = pSox->GetFieldFromLablePtr( m_nSwapSingleSkinActorID, "_ActName" )->GetString(); #endif if( !m_hSwapOriginalHandle && m_hObject ) { m_hSwapOriginalHandle = EternityEngine::CreateAniObject( GetRoom(), CEtResourceMng::GetInstance().GetFullName( m_hObject->GetSkinFileName() ).c_str(), CEtResourceMng::GetInstance().GetFullName( m_hObject->GetAniHandle()->GetFileName() ).c_str() ); } SAFE_RELEASE_SPTR( GetObjectHandle() ); if( !m_pSwapOriginalAction ) { m_pSwapOriginalAction = new CDnActionBase; m_pSwapOriginalAction->LoadAction( CEtResourceMng::GetInstance().GetFullName( CDnActionBase::m_szFileName ).c_str() ); } SAFE_RELEASE_SPTR( GetObjectHandle() ); FreeAction(); LoadSkin( CEtResourceMng::GetInstance().GetFullName( szSkinName ).c_str(), CEtResourceMng::GetInstance().GetFullName( szAniName ).c_str() ); LoadAction( CEtResourceMng::GetInstance().GetFullName( szActName ).c_str() ); SetAction( "Stand", 0.f, 0.f ); } if( m_hObject ) { m_hObject->SetCollisionGroup( COLLISION_GROUP_DYNAMIC( 1 ) ); m_hObject->SetTargetCollisionGroup( COLLISION_GROUP_STATIC( 1 ) | COLLISION_GROUP_DYNAMIC( 2 ) | COLLISION_GROUP_DYNAMIC( 3 ) ); } if(m_pBubbleSystem) m_pBubbleSystem->Clear(); } void CDnPlayerActor::InitializeEnchantPassiveSkills( void ) { // 강화 패시브 타입의 스킬을 찾아서 베이스 스킬에 수치를 적용하도록 한다. #ifndef PRE_FIX_SKILLLIST for( DWORD i = 0; i < m_vlhSkillList.size(); i++ ) { DnSkillHandle hSkill = m_vlhSkillList[ i ]; #else for( DWORD i = 0; i < GetSkillCount(); ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); #endif // #ifndef PRE_FIX_SKILLLIST if( CDnSkill::EnchantPassive == hSkill->GetSkillType() && 0 < hSkill->GetBaseSkillID() ) { DnSkillHandle hEnchantPassiveSkill = hSkill; int iBaseSkillID = hEnchantPassiveSkill->GetBaseSkillID(); DnSkillHandle hBaseSkill = FindSkill( iBaseSkillID ); // 임시로 방어코드 넣음. 스킬리셋 캐쉬템이 제대로 올라가면 없애야 함. if( hBaseSkill ) hBaseSkill->ApplyEnchantSkill( hEnchantPassiveSkill ); } } } void CDnPlayerActor::OnReplacementSkill( DnSkillHandle hLegacySkill, DnSkillHandle hNewSkill ) { MASkillUser::OnReplacementSkill( hLegacySkill, hNewSkill ); // 로컬 플레이어의 패시브 강화 스킬이 레벨업 되어 이전 레벨의 스킬 객체의 교체 루틴을 타고 이쪽으로 오는 경우. // 적용되고 있던 베이스 스킬의 강화 상태를 리셋으로 돌리고 레벨업된 새로운 강화 스킬의 것을 적용시킨다. if( CDnSkill::EnchantPassive == hLegacySkill->GetSkillType() && 0 < hLegacySkill->GetBaseSkillID() ) { DnSkillHandle hBaseSkill = FindSkill( hLegacySkill->GetBaseSkillID() ); if( hBaseSkill ) { _ASSERT( hLegacySkill->GetBaseSkillID() == hNewSkill->GetBaseSkillID() ); hBaseSkill->ReleaseEnchantSkill(); hBaseSkill->ApplyEnchantSkill( hNewSkill ); } } else { // 로컬 플레이어의 패시브 강화 스킬의 대상이 되는 베이스 스킬이 레벨업 되어 교체 루틴을 타고 이쪽으로 오는 경우. // 이 경우엔 베이스 스킬 객체는 SkillTask 에서 이 루틴이 끝난 후 그냥 삭제될 것이므로 놔두고 // 새로 레벨업 된 베이스 스킬에 강화 스킬을 적용시켜 주면 된다. CheckAndApplyEnchantPassiveSkill( hNewSkill ); } } void CDnPlayerActor::CheckAndApplyEnchantPassiveSkill( DnSkillHandle hBaseSkill ) { if( hBaseSkill ) { DnSkillHandle hEnchantPassiveSkill; #ifndef PRE_FIX_SKILLLIST for( DWORD i = 0; i < m_vlhSkillList.size(); i++ ) { DnSkillHandle hSkill = m_vlhSkillList[ i ]; #else for( DWORD i = 0; i < GetSkillCount(); ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); #endif // #ifndef PRE_FIX_SKILLLIST if( hSkill->GetBaseSkillID() == hBaseSkill->GetClassID() ) { hEnchantPassiveSkill = hSkill; break; } } if( hEnchantPassiveSkill ) hBaseSkill->ApplyEnchantSkill( hEnchantPassiveSkill ); } } // 현재 엘리멘탈로드의 아이시 프랙션에서만 예외적으로 2가지의 상태효과 필터링을 걸고 있는데 // 둘 중에 하나만 hit 되길 원하기 때문에 먼저 체크된 것은 다음 필터링에 걸리지 않도록 한다. #28747 void CDnPlayerActor::OnHitSignalStateEffectFilterException( DWORD dwTargetActorUniqueID, int iBlowIndex ) { if( STATE_BLOW::BLOW_041 == (STATE_BLOW::emBLOW_INDEX)iBlowIndex || STATE_BLOW::BLOW_144 == (STATE_BLOW::emBLOW_INDEX)iBlowIndex ) { m_mapIcyFractionHitted[ dwTargetActorUniqueID ] = true; } } bool CDnPlayerActor::CheckHitSignalStateEffectFilterException( DWORD dwTargetActorUniqueID, int iBlowIndex ) { bool bResult = true; if( STATE_BLOW::BLOW_041 == (STATE_BLOW::emBLOW_INDEX)iBlowIndex || STATE_BLOW::BLOW_144 == (STATE_BLOW::emBLOW_INDEX)iBlowIndex ) { if( m_mapIcyFractionHitted.end() != m_mapIcyFractionHitted.find( dwTargetActorUniqueID ) ) { bResult = false; } } return bResult; } bool CDnPlayerActor::IsMyRelicMonster( DnActorHandle hActor ) { bool bResult = false; if( hActor && hActor->IsMonsterActor() ) { CDnMonsterActor* pMonsterActor = static_cast(hActor.GetPointer()); if( pMonsterActor->IsClericRelicMonster() ) { if( GetMySmartPtr() == pMonsterActor->GetSummonerPlayerActor() ) bResult = true; } } return bResult; } INT64 CDnPlayerActor::GetCharacterDBID() { return m_pSession ? m_pSession->GetCharacterDBID() : 0; } // #26902 // 임시로 클라이언트에게 이 직업으로 전직할 것을 명령. // 이 시점부터는 스킬 레벨업 및 각종 조작이 불가하다. // 게임 서버 쪽에서도 스킬 레벨업 등의 패킷이 오면 무시하도록 한다. // 스테이지 이동시 곧바로 리셋시킨다. bool CDnPlayerActor::CanChangeJob( int iJobID ) { // 게임서버에서는 실제 전직하지 않는다. // 현재 직업에서 전직이 가능한 2차 직업인지 인증만해서 클라로 보내준다. DNTableFileFormat* pJobTable = GetDNTable( CDnTableDB::TJOB ); // 현재 직업의 단계값과 루트 직업을 얻어옴. int iNowJob = m_pSession->GetUserJob(); int iNowJobDeep = 0; int iNowRootJob = 0; for( int i = 0; i < pJobTable->GetItemCount(); ++i ) { int iItemID = pJobTable->GetItemID( i ); if( iItemID == iNowJob ) { iNowJobDeep = pJobTable->GetFieldFromLablePtr( iItemID, "_JobNumber" )->GetInteger(); iNowRootJob = pJobTable->GetFieldFromLablePtr( iItemID, "_BaseClass" )->GetInteger(); break; } } int iJobIDToChange = iJobID; // 바꾸기 원하는 직업과 단계가 같거나 큰지 확인. bool bResult = false; map mapRootJob; for( int i = 0; i < pJobTable->GetItemCount(); ++i ) { int iItemID = pJobTable->GetItemID( i ); if( iItemID == iJobIDToChange ) { int iJobRootToChange = pJobTable->GetFieldFromLablePtr( iItemID, "_BaseClass" )->GetInteger(); if( iNowRootJob == iJobRootToChange ) { int iJobDeepToChange = pJobTable->GetFieldFromLablePtr( iItemID, "_JobNumber" )->GetInteger(); if( iNowJobDeep < iJobDeepToChange ) { // 부모 직업도 맞아야 함. int iParentJobID = pJobTable->GetFieldFromLablePtr( iItemID, "_ParentJob" )->GetInteger(); if( iParentJobID == iNowJob ) { bResult = true; } else { // 바꾸고자 하는 직업의 부모 직업이 현재 직업이 아님. wstring wszString = FormatW(L"현재 직업에선 전직 할 수 없는 직업입니다.!!\r\n"); m_pSession->SendChat(CHATTYPE_NORMAL, (int)wszString.size()*sizeof(WCHAR), L"", (WCHAR*)wszString.c_str()); } } else { // 바꾸고자하는 직업이 아래 단계임. 못바꿈. wstring wszString = FormatW(L"같거나 낮은 단계의 직업으로 바꿀 수 없습니다!!\r\n"); m_pSession->SendChat(CHATTYPE_NORMAL, (int)wszString.size()*sizeof(WCHAR), L"", (WCHAR*)wszString.c_str()); } } else { // 바꾸고자하는 직업이 다른 클래스임. 못바꿈. wstring wszString = FormatW(L"다른 클래스의 직업으로 바꿀 수 없습니다!!\r\n"); m_pSession->SendChat(CHATTYPE_NORMAL, (int)wszString.size()*sizeof(WCHAR), L"", (WCHAR*)wszString.c_str()); } } } if( false == bResult ) { wstring wszString = FormatW(L"잘못된 Job ID 입니다..\r\n"); m_pSession->SendChat(CHATTYPE_NORMAL, (int)wszString.size()*sizeof(WCHAR), L"", (WCHAR*)wszString.c_str()); } return bResult; } void CDnPlayerActor::SendTempJobChange( int iJobID ) { BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &iJobID, sizeof(int) ); Send( eActor::SC_DO_TEMP_JOBCHANGE, &Stream ); m_iTempChangedJob = iJobID; } // 임시 전직을 원래대로 복구할 것을 명령. // 이 시점부터는 스킬 레벨업 및 각종 조작이 가능하다. void CDnPlayerActor::EndAddTempSkillAndSendRestoreTempJobChange( void ) { BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &m_iTempChangedJob, sizeof(int) ); Send( eActor::SC_RESTORE_TEMP_JOBCHANGE, &Stream ); m_iTempChangedJob = 0; } void CDnPlayerActor::AddTempSkill( int iSkillID ) { // 임시로 만들려고 하는 스킬이 강화 패시브 스킬인 경우 // 베이스가 되는 스킬을 갖고 있는지 찾아서 없다면 역시 임시 스킬로 베이스 스킬로 추가한다. DNTableFileFormat* pSkillTable = GetDNTable( CDnTableDB::TSKILL ); int iNeedBaseSkillID = 0; if( pSkillTable->IsExistItem( iSkillID ) ) iNeedBaseSkillID = pSkillTable->GetFieldFromLablePtr( iSkillID, "_BaseSkillID" )->GetInteger(); DNVector(int) vlSkillsToAdd; if( 0 < iNeedBaseSkillID ) { // 강화 대상 스킬을 갖고 있지 않은 상태라면 이것도 새로 생성하도록 벡터에 넣어줌. if( false == IsExistSkill( iNeedBaseSkillID ) ) vlSkillsToAdd.push_back( iNeedBaseSkillID ); } vlSkillsToAdd.push_back( iSkillID ); for( int i = 0; i < (int)vlSkillsToAdd.size(); ++i ) { int iSkillIDToAdd = vlSkillsToAdd.at( i ); bool bSuccess = AddSkill( iSkillIDToAdd, 1, CDnSkill::PVE ); if( bSuccess ) { DnSkillHandle hSkill = FindSkill( iSkillIDToAdd ); if( hSkill ) { hSkill->AsTempSkill(); // 2차 전직스킬이라서 현재 사용할 수 없는 스킬이라면, // 현재 캐릭터 레벨 및 직업에 맞게 객체 값을 바꿔준다. if( GetLevel() < hSkill->GetLevelLimit() ) hSkill->SetLevelLimit( GetLevel() ); if( false == IsPassJob( hSkill->GetNeedJobClassID() ) ) hSkill->SetNeedJobClassID( GetJobClassID() ); // 클라로 임시 스킬 추가 보냄. BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &iSkillID, sizeof(int) ); Send( eActor::SC_ADD_TEMP_SKILL, &Stream ); m_bTempSkillAdded = true; } } } } void CDnPlayerActor::RemoveAllTempSkill( void ) { #ifndef PRE_FIX_SKILLLIST DNVector(DnSkillHandle)::iterator iter = m_vlhSkillList.begin(); for( iter; iter != m_vlhSkillList.end(); ) { DnSkillHandle hSkill = *iter; if( hSkill->IsTempSkill() ) { // 2차전직을 위한 임시적인 기능이므로 MASkillUser 가 아닌 여기서 직접 // 스킬 객체 삭제 처리를 함. 추후에 이쪽관련 추가 요청이 있는 경우 // MASkillUser::RemoveSkill() 를 사용해야할 수도 있음. int iSkillID = hSkill->GetClassID(); OnRemoveSkill( hSkill ); iter = m_vlhSkillList.erase( iter ); // 클라로 임시 스킬 제거 보냄. BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &iSkillID, sizeof(int) ); Send( eActor::SC_REMOVE_TEMP_SKILL, &Stream ); } else ++iter; } #else vector vlTempSkills; for( DWORD i = 0; i < GetSkillCount(); ++i ) { DnSkillHandle hSkill = GetSkillFromIndex( i ); if( hSkill->IsTempSkill() ) { vlTempSkills.push_back( hSkill->GetClassID() ); } } for( int i = 0; i < (int)vlTempSkills.size(); ++i ) { int iSkillID = vlTempSkills.at( i ); RemoveSkill( iSkillID ); // 클라로 임시 스킬 제거 보냄. BYTE pBuffer[ 32 ] = { 0 }; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &iSkillID, sizeof(int) ); Send( eActor::SC_REMOVE_TEMP_SKILL, &Stream ); } #endif // #ifndef PRE_FIX_SKILLLIST m_bTempSkillAdded = false; } #ifdef PRE_ADD_48714 #include "DNMailSender.h" #endif //#ifdef PRE_ADD_48714 void CDnPlayerActor::OnInvalidPlayerChecker( int nValue ) { m_nInvalidPlayerCheckCounter += nValue; OutputDebug( "OnInvalidPlayerChecker : %d, (%d)\n", m_nInvalidPlayerCheckCounter, nValue ); if( m_pSession && m_pSession->GetHackPlayRestraintValue() > 0 ) { #if defined (PRE_ADD_ABUSE_ACCOUNT_RESTRAINT) //제재가 들어갈경우 끊는 판단에 *2 제외 if( m_nInvalidPlayerCheckCounter >= m_pSession->GetHackPlayRestraintValue() ) { //일단은 한국에만 처리되어진다 if (m_pSession->GetHackCharacterCntWithoutMe() > 0) { //지금현재 캐릭터를 제외한 캐릭터가 하나 있는데 하나 더 걸렸다 36080이슈에 의하여 제재처리한다. #if defined(PRE_ADD_MULTILANGUAGE) std::wstring wszRestraintReason = GetEtUIXML().GetUIString(CEtUIXML::idCategory1, 100070, m_pSession->m_eSelectedLanguage); std::wstring wszRestraintReasonForDolis = GetEtUIXML().GetUIString(CEtUIXML::idCategory1, 100071, m_pSession->m_eSelectedLanguage); #else //#if defined(PRE_ADD_MULTILANGUAGE) std::wstring wszRestraintReason = GetEtUIXML().GetUIString(CEtUIXML::idCategory1, 100070); std::wstring wszRestraintReasonForDolis = GetEtUIXML().GetUIString(CEtUIXML::idCategory1, 100071); #endif //#if defined(PRE_ADD_MULTILANGUAGE) m_pSession->GetDBConnection()->QueryAddRestraint(m_pSession, DBDNWorldDef::RestraintTargetCode::Account, DBDNWorldDef::RestraintTypeCode::ConnectRestraint, wszRestraintReason.c_str(), wszRestraintReasonForDolis.c_str(), 9999, DBDNWorldDef::RestraintDolisReasonCode::AbuseRestraintCode); } m_pSession->DetachConnection( L"InvalidPlayerCheckCounter" ); } #else #ifdef PRE_ADD_48714 if(m_nInvalidPlayerCheckCounter >= m_pSession->GetHackPlayRestraintValue()) { if (m_pSession->GetDBConnection()) { #if defined (_TW) WCHAR wszBuf[100]; wsprintf( wszBuf, L"Invalidcount Reached limit Value"); m_pSession->GetDBConnection()->QueryAddAbuseLog(m_pSession, ABUSE_TWN_EXTENDLOG, wszBuf); #if defined(PRE_ADD_MULTILANGUAGE) std::wstring wszRestraintReason = GetEtUIXML().GetUIString(CEtUIXML::idCategory1, 109049, m_pSession->m_eSelectedLanguage); #else //#if defined(PRE_ADD_MULTILANGUAGE) std::wstring wszRestraintReason = GetEtUIXML().GetUIString(CEtUIXML::idCategory1, 109049); #endif //#if defined(PRE_ADD_MULTILANGUAGE) m_pSession->GetDBConnection()->QueryAddRestraint(m_pSession, DBDNWorldDef::RestraintTargetCode::Character, DBDNWorldDef::RestraintTypeCode::TradeRestraint, wszRestraintReason.c_str(), wszRestraintReason.c_str(), 9999, DBDNWorldDef::RestraintDolisReasonCode::AbuseTradeRestraintCode); #endif //#if defined (_TW) CDNMailSender::Process(m_pSession, AbuseLog::Common::AbuseLog_Reached_MailID); } } #endif //#ifdef PRE_ADD_48714 if( m_nInvalidPlayerCheckCounter >= m_pSession->GetHackPlayRestraintValue()*2 ) { m_pSession->DetachConnection( L"InvalidPlayerCheckCounter" ); } #endif //#if defined (PRE_ADD_ABUSE_ACCOUNT_RESTRAINT) } } void CDnPlayerActor::OrderUseSkillToMySummonedMonster( OrderMySummonedMonsterStruct* pStruct ) { if( IsAppliedThisStateBlow( STATE_BLOW::BLOW_215 ) ) { DNVector( DnBlowHandle ) vlhBlows; GatherAppliedStateBlowByBlowIndex( STATE_BLOW::BLOW_215, vlhBlows ); _ASSERT( 1 == (int)vlhBlows.size() ); if( false == vlhBlows.empty() ) { // 정의된 반경 안에 내가 소환한 몬스터가 있는지 확인. float fRangeSQ = pStruct->fOrderRange * pStruct->fOrderRange; DNVector( DnMonsterActorHandle ) vlhMonsters; for( list::const_iterator iter = m_listSummonMonster.begin(); iter != m_listSummonMonster.end(); ++iter ) { DnMonsterActorHandle hMonster = *iter; if( hMonster && false == hMonster->IsDie() ) { float fDistanceWithThisMonsterSQ = EtVec3LengthSq( &EtVector3(*GetPosition() - *hMonster->GetPosition()) ); if( fDistanceWithThisMonsterSQ < fRangeSQ ) { vlhMonsters.push_back( hMonster ); } } } map >::iterator iterMap = m_mapSummonMonsterByGroup.begin(); for( iterMap; iterMap != m_mapSummonMonsterByGroup.end(); ++iterMap ) { const list& listSummonMonster = iterMap->second; for( list::const_iterator iterList = listSummonMonster.begin(); iterList != listSummonMonster.end(); ++iterList ) { DnMonsterActorHandle hMonster = *iterList; if( hMonster && false == hMonster->IsDie() ) { float fDistanceWithThisMonsterSQ = EtVec3LengthSq( &EtVector3(*GetPosition() - *hMonster->GetPosition()) ); if( fDistanceWithThisMonsterSQ < fRangeSQ ) { vlhMonsters.push_back( hMonster ); } } } } if( false == vlhMonsters.empty() ) { CDnOrderMySummonedMonsterBlow* pBlow = static_cast( vlhBlows.front().GetPointer() ); int iSkillID = pBlow->GetSkillID(); for( int i = 0; i < (int)vlhMonsters.size(); ++i ) { DnMonsterActorHandle hMonster = vlhMonsters.at( i ); if( hMonster->GetMonsterClassID() == pStruct->nMonsterID ) { // TODO: AI 쪽에 문의해서 스킬 사용 예약을 해둬야 함. if( hMonster->IsExistSkill( iSkillID ) ) { MAAiScript* pScript = static_cast(hMonster->GetAIBase()); if( hMonster->GetAggroTarget() ) pScript->GetMonsterSkillAI()->AddWaitOrderCount( hMonster, iSkillID ); } } } } } } } bool CDnPlayerActor::CheckSkipAirCondition(int iSkill) { if(IsAir()) { bool bHaveGroundCheck = false; DnSkillHandle hSkill = FindSkill(iSkill); if(hSkill) { if(hSkill->IsExistUsableChecker(IDnSkillUsableChecker::GROUNDMOVABLE_CHECKER)) bHaveGroundCheck = true; } if(bHaveGroundCheck) { return true; } } return false; } bool CDnPlayerActor::HasSameGlobalIDSkill( int iSkillID ) { bool bResult = false; DNTableFileFormat* pSkillTable = GetDNTable( CDnTableDB::TSKILL ); int iGlobalSkillGroupID = pSkillTable->GetFieldFromLablePtr( iSkillID, "_GlobalSkillGroup" )->GetInteger(); if( 0 < iGlobalSkillGroupID ) { for( DWORD i = 0; i < GetSkillCount(); ++i ) { DnSkillHandle hExistSkill = GetSkillFromIndex( i ); if( hExistSkill && hExistSkill->GetGlobalSkillGroupID() == iGlobalSkillGroupID ) { bResult = true; break; } } } return bResult; } #ifdef PRE_FIX_GAMESERVER_PERFOMANCE bool CDnPlayerActor::IsAllowCallSkillProcess( float fDelta ) { return m_FrameSkipCallSkillProcess.Update( fDelta ); } #endif // #ifdef PRE_FIX_GAMESERVER_PERFOMANCE void CDnPlayerActor::RemoveAllBubbles( bool bRemoveEvent ) { if( m_pBubbleSystem ) m_pBubbleSystem->RemoveAllBubbles( bRemoveEvent ); } void CDnPlayerActor::ApplyEnchantSkillOnceFromBubble( int iTargetSkillID, int iEnchantSkillID ) { DnSkillHandle hEnchantSkill; map::iterator iter = m_mapEnchantSkillFromBubble.find( iEnchantSkillID ); if( m_mapEnchantSkillFromBubble.end() != iter ) { hEnchantSkill = iter->second; } else { // pvp/pve 모두 데이터가 있어야 제대로 생성됨. hEnchantSkill = CDnSkill::CreateSkill( GetMySmartPtr(), iEnchantSkillID, 1 ); m_mapEnchantSkillFromBubble.insert( make_pair(iEnchantSkillID, hEnchantSkill) ); } DnSkillHandle hTargetSkill = FindSkill( iTargetSkillID ); _ASSERT( hTargetSkill ); _ASSERT( hEnchantSkill ); if( hTargetSkill && hEnchantSkill ) { // 이미 강화된 스킬이 대상이 될 수는 없음. bool bIsEnchantedAlready = hTargetSkill->IsEnchantedSkill(); _ASSERT( false == bIsEnchantedAlready ); if( false == bIsEnchantedAlready ) { hTargetSkill->ApplyEnchantSkillOnceFromBubble( hEnchantSkill ); } } } bool CDnPlayerActor::OnApplySpectator(bool bEnable) { CDNGameRoom* pGameRoom = GetGameRoom(); if( pGameRoom == NULL || pGameRoom->bIsPvPRoom() == false || pGameRoom->GetPvPGameMode()->bIsAllKillMode() == false ) return false; if( bEnable ) { if( IsProcessSkill() ) CancelUsingSkill(); if(IsBattleMode()) SetBattleMode(false); } return true; } bool CDnPlayerActor::IsSpectatorMode() { return IsAppliedThisStateBlow(STATE_BLOW::BLOW_230); } void CDnPlayerActor::ChangeSkillLevelUp(int nSkillID, int nOrigLevel) { if( IsProcessSkill() ) return; DnSkillHandle hSkill = FindSkill(nSkillID); if( hSkill ) { if (hSkill->GetElapsedDelayTime() > 0.0f || hSkill->IsToggleOn()) return; } __super::ChangeSkillLevelUp(nSkillID, nOrigLevel); DnSkillHandle hChangedSkill = FindSkill( nSkillID ); if( hChangedSkill ) { ReplacementGlyph( hChangedSkill ); } } #if defined(PRE_ADD_TOTAL_LEVEL_SKILL) void CDnPlayerActor::UpdateTotalLevel(int nLevel) { if (m_pTotalLevelSkillSystem) { m_pTotalLevelSkillSystem->SetTotalLevel(nLevel); } } void CDnPlayerActor::UpdateTotalLevelByCharLevel() { if (m_pTotalLevelSkillSystem) m_pTotalLevelSkillSystem->UpdateTotalLevel(); } void CDnPlayerActor::AddTotalLevelSkill(int nSlotIndex, int nSkillID, bool isInitialize/* = false*/) { if (m_pTotalLevelSkillSystem) { DnSkillHandle hSkill = m_pTotalLevelSkillSystem->FindTotalLevelSkill(nSkillID); if (!hSkill) { RemoveTotalLevelSkill(nSlotIndex); return; } //만약 해당 슬롯이 활성화가 되지 않은 경우는 추가 하지 않도록 한다.. bool isActivateSlot = m_pTotalLevelSkillSystem->IsActivateSlot(nSlotIndex); if (isActivateSlot == false) return; //PVE/PVP설정.. // 스킬 레벨 데이터를 pve/pvp 인 경우와 나눠서 셋팅해준다. int iSkillLevelDataType = CDnSkill::PVE; if( GetGameRoom()->bIsPvPRoom() ) iSkillLevelDataType = CDnSkill::PVP; hSkill->SelectLevelDataType( iSkillLevelDataType ); m_pTotalLevelSkillSystem->AddTotalLevelSkill(nSlotIndex, hSkill, isInitialize); } } void CDnPlayerActor::RemoveTotalLevelSkill(int nSlotIndex) { if (m_pTotalLevelSkillSystem) { m_pTotalLevelSkillSystem->RemoveTotallevelSkill(nSlotIndex); } } void CDnPlayerActor::ActivateTotalLevelSkillSlot(int nSlotIndex, bool bActivate) { if (m_pTotalLevelSkillSystem) m_pTotalLevelSkillSystem->ActivateTotalLevelSkillSlot(nSlotIndex, bActivate); } void CDnPlayerActor::ActivateTotalLevelSkillCashSlot(int nSlotIndex, bool bActivate, __time64_t tExpireDate) { if (m_pTotalLevelSkillSystem) m_pTotalLevelSkillSystem->ActivateTotalLevelSkillCashSlot(nSlotIndex, bActivate, tExpireDate); } void CDnPlayerActor::OnLevelChange() { UpdateTotalLevelByCharLevel(); } #endif // PRE_ADD_TOTAL_LEVEL_SKILL void CDnPlayerActor::ApplyEventStateBlow() { int nAddStateEffectIndex = -1; DnBlowHandle hBlow; #ifdef PRE_ADD_WEEKLYEVENT if (CDnWorld::GetInstance(GetRoom()).GetMapSubType() != EWorldEnum::MapSubTypeNest && !GetGameRoom()->bIsPvPRoom() ) { bool bRfreshHP = false; float fCurrentHpRatio = (float)GetHP() / (float)GetMaxHP(); int nThreadID = GetGameRoom()->GetServerID(); float fEventHp = g_pDataManager->GetWeeklyEventValuef(WeeklyEvent::Player, GetClassID(), WeeklyEvent::Event_1, nThreadID); if (fEventHp != 0.f) { std::string strValue; strValue.append(boost::lexical_cast(fEventHp)); nAddStateEffectIndex = CmdAddStateEffect(NULL, STATE_BLOW::BLOW_058, -1, strValue.c_str(), false, true , true ); m_vecEventEffectList.push_back( nAddStateEffectIndex ); bRfreshHP = true; } float fEventAttack = g_pDataManager->GetWeeklyEventValuef(WeeklyEvent::Player, GetClassID(), WeeklyEvent::Event_2, nThreadID); if (fEventAttack != 0.f) { std::string strValue; strValue.append(boost::lexical_cast(fEventAttack)); nAddStateEffectIndex = CmdAddStateEffect(NULL, STATE_BLOW::BLOW_002, -1, strValue.c_str(), false, true , true ); m_vecEventEffectList.push_back( nAddStateEffectIndex ); nAddStateEffectIndex = CmdAddStateEffect(NULL, STATE_BLOW::BLOW_029, -1, strValue.c_str(), false, true , true ); m_vecEventEffectList.push_back( nAddStateEffectIndex ); } float fEventDefense = g_pDataManager->GetWeeklyEventValuef(WeeklyEvent::Player, GetClassID(), WeeklyEvent::Event_3, nThreadID); if (fEventDefense != 0.f) { std::string strValue; strValue.append(boost::lexical_cast(fEventDefense)); nAddStateEffectIndex = CmdAddStateEffect(NULL, STATE_BLOW::BLOW_004, -1, strValue.c_str(), false, true , true ); m_vecEventEffectList.push_back( nAddStateEffectIndex ); nAddStateEffectIndex = CmdAddStateEffect(NULL, STATE_BLOW::BLOW_094, -1, strValue.c_str(), false, true , true ); m_vecEventEffectList.push_back( nAddStateEffectIndex ); } if( bRfreshHP ) { GetStateBlow()->Process( 0 , 0 ); CmdRefreshHPSP( (INT64)(GetMaxHP() * fCurrentHpRatio) , GetSP() ); } } #endif } void CDnPlayerActor::RemoveEventStateBlow() { for( DWORD index = 0; index < m_vecEventEffectList.size() ; ++index ) { CmdRemoveStateEffectFromID( m_vecEventEffectList[index] ); } m_vecEventEffectList.clear(); } bool CDnPlayerActor::CheckSkillAction( const char *szActionName ) { if( strstr( szActionName, "Skill_" ) || strstr( szActionName, "ChargeShoot_" ) || strstr( szActionName, "GuildSkill_" ) || strstr( szActionName, "Throw_" ) || strstr( szActionName, "Tumble_" ) || strstr( szActionName, "AerialEvasion" ) ) { return true; } return false; } void CDnPlayerActor::MakeEquipAndPassiveState( CDnState &State ) { State.ResetState(); State = MakeEquipState(); CDnState BUFF_STATE; BUFF_STATE.ResetState(); if( m_pStateBlow ) { int nSize = m_pStateBlow->GetNumStateBlow(); for ( int i = 0 ; i < nSize ; i++ ) { DnBlowHandle hBlow = m_pStateBlow->GetStateBlow(i); if( hBlow && CDnCreateBlow::IsBasicBlow( hBlow->GetBlowIndex() ) == true && hBlow->GetParentSkillInfo() && hBlow->GetParentSkillInfo()->eSkillType == CDnSkill::SkillTypeEnum::Passive && hBlow->GetParentSkillInfo()->eDurationType == CDnSkill::DurationTypeEnum::Buff ) { CDnBasicBlow *pBlow = static_cast(hBlow.GetPointer()); if( pBlow ) { CDnState *pState = const_cast(pBlow->GetState()); BUFF_STATE.MergeState( *pState, pState->GetValueType() ); } } } } State.MergeState( BUFF_STATE, ValueTypeAbsolute ); State.CalculateRatioValue( BUFF_STATE ); } #ifdef PRE_ADD_COSTUME_SKILL void CDnPlayerActor::RefreshCostumeSkill( int nSkillIndex, int nSkillLevel ) { if( m_nCostumeSkillID == nSkillIndex ) return; if( m_nCostumeSkillID > 0 ) RemoveSkill( m_nCostumeSkillID ); if( nSkillIndex > 0 ) { AddSkill( nSkillIndex, nSkillLevel ); DnSkillHandle hSkill = FindSkill( nSkillIndex ); if( hSkill ) { if( hSkill->GetSkillType() != CDnSkill::SkillTypeEnum::Active ) { RemoveSkill( nSkillIndex ); return; } hSkill->OnBeginCoolTime(); } } m_nCostumeSkillID = nSkillIndex; } #endif // PRE_ADD_COSTUME_SKILL #ifdef PRE_ADD_VEHICLE_SPECIAL_ACTION void CDnPlayerActor::ReportInvalidAction() { if( m_pPlayerActionChecker ) ((CDnPlayerActionChecker*)m_pPlayerActionChecker)->OnInvalidAction(); } #endif