#include "StdAfx.h" #include "DnEscapeBlow.h" #include "DnCantMoveBlow.h" #include "DnCantActionBlow.h" #include "DnTableDB.h" #include "DnFrameStopBlow.h" #include "DnMonsterActor.h" #ifdef _GAMESERVER #include "DnGameRoom.h" #include "DnGameDataManager.h" #include "DnUserSession.h" #else #include "DnEtcObject.h" #include "DnLocalPlayerActor.h" #include "DnInterface.h" #include "navigationmesh.h" #include "InputWrapper.h" #endif #if !defined( USE_BOOST_MEMPOOL ) #ifdef _DEBUG #define new new(_NORMAL_BLOCK,__FILE__,__LINE__) #endif #endif // #if !defined( USE_BOOST_MEMPOOL ) const int ICE_DAMAGE_ADD_PROBABILITY = 30; const LOCAL_TIME SHAKE_TIME = 3000; CDnEscapeBlow::CDnEscapeBlow( DnActorHandle hActor, const char* szValue ) : CDnBlow( hActor ), m_pCantMoveBlow( new CDnCantMoveBlow(hActor, NULL ) ), m_pCantActionBlow( new CDnCantActionBlow(hActor, NULL) ), m_pFrameStopBlow( new CDnFrameStopBlow(hActor, NULL) ), //m_iDurabilityCount( 1 ), m_dwOriSpeedFrame( 0 ), m_bNestMap( false ) #ifndef _GAMESERVER ,m_ComboCalc( CDnComboCalculator::CIRCULAR_CHECK ) ,m_bShake( false ) ,m_ShakeStartTime( 0 ) #endif ,m_bPlayerCharacter( false ) ,m_bAllowShakeControl(false) ,m_IntervalChecker( hActor, GetMySmartPtr() ) ,m_isAbleIntervalChecker(false) { static int sValueType = 1; m_StateBlow.emBlowIndex = STATE_BLOW::BLOW_218; SetValue( szValue ); std::string str = szValue;//"확률;내구도;(0/1)흔들림여부;피격액션"; std::vector tokens; std::string delimiters = ";"; bool bValidValues = false; //1. 상태효과 인덱스 리스트와 적용 레벨 구분 TokenizeA(str, tokens, delimiters); if (tokens.size() >= 3) { m_fValue = (float)atof(tokens[0].c_str()); // m_iDurabilityCount = atoi(tokens[1].c_str()); m_bAllowShakeControl = atoi(tokens[1].c_str()) == 0; //0 : 흔들림, 1 : 흔들리지 않음 //마지막 피격 액션은 옵션 사항 if (tokens.size() == 3) { m_strHitedAction = tokens[2]; } bValidValues = true; } else { m_fValue = 0.0f; // m_iDurabilityCount = 2; bValidValues = false; } if (bValidValues) { #ifndef _GAMESERVER std::vector vlKeysToCheck; vlKeysToCheck.push_back( g_WrappingKeyData[IW_MOVELEFT] ); vlKeysToCheck.push_back( g_WrappingKeyData[IW_MOVERIGHT] ); vlKeysToCheck.push_back( g_WrappingKeyData[IW_MOVELEFT] ); vlKeysToCheck.push_back( g_WrappingKeyData[IW_MOVERIGHT] ); vlKeysToCheck.push_back( g_WrappingKeyData[IW_MOVELEFT] ); vlKeysToCheck.push_back( g_WrappingKeyData[IW_MOVERIGHT] ); m_ComboCalc.SetKeysToCheck( vlKeysToCheck ); vlKeysToCheck.clear(); vlKeysToCheck.push_back( IW_MOVELEFT ); vlKeysToCheck.push_back( IW_MOVELEFT ); vlKeysToCheck.push_back( IW_MOVELEFT ); vlKeysToCheck.push_back( IW_MOVERIGHT ); vlKeysToCheck.push_back( IW_MOVERIGHT ); vlKeysToCheck.push_back( IW_MOVERIGHT ); vlKeysToCheck.push_back( IW_MOVEBACK ); vlKeysToCheck.push_back( IW_MOVEBACK ); vlKeysToCheck.push_back( IW_MOVEBACK ); vlKeysToCheck.push_back( IW_MOVEFRONT ); vlKeysToCheck.push_back( IW_MOVEFRONT ); vlKeysToCheck.push_back( IW_MOVEFRONT ); m_ComboCalc.SetPadsToCheck( vlKeysToCheck, 3.0f ); UseTableDefinedGraphicEffect( false ); if (m_pCantMoveBlow) m_pCantMoveBlow->UseTableDefinedGraphicEffect( false ); m_bPlayerCharacter = dynamic_cast(m_hActor.GetPointer()) ? true : false; #endif //AddCallBackType( SB_ONCALCDAMAGE ); //피격 액션이 없을때는 기존 Freezing과 동일하게.. if (m_pCantActionBlow) { //노멀 상태(isMoveable)에서도 피격 액션 설정을 위해(m_strHitedAction.empty() ? false : true) m_pCantActionBlow->SetAvailableNormalState(!m_strHitedAction.empty()); m_pCantActionBlow->SetActionWhenCancelAttack( m_strHitedAction.empty() ? "Freezing" : m_strHitedAction.c_str() ); } } #ifdef _GAMESERVER CDNGameRoom* pGameRoom = static_cast(m_hActor->GetRoom()); if( false == pGameRoom->bIsPvPRoom() ) { UINT uiSessionID = 0; pGameRoom->GetLeaderSessionID( uiSessionID ); CDNUserSession *pUserSession = pGameRoom ? pGameRoom->GetUserSession(uiSessionID) : NULL; if( pUserSession ) { const TMapInfo* pMapData = g_pDataManager->GetMapInfo( pUserSession->GetMapIndex() ); if( pMapData ) #if defined(PRE_ADD_DRAGON_FELLOWSHIP) m_bNestMap = CDnBlow::CheckEffectIgnoreMapType(pMapData->MapType, pMapData->MapSubType); #else // #if defined(PRE_ADD_DRAGON_FELLOWSHIP) m_bNestMap = (GlobalEnum::MAP_DUNGEON == pMapData->MapType) && ((GlobalEnum::MAPSUB_NEST == pMapData->MapSubType) ||(GlobalEnum::MAPSUB_NESTNORMAL == pMapData->MapSubType)); #endif // #if defined(PRE_ADD_DRAGON_FELLOWSHIP) } } #else #if defined(PRE_ADD_DRAGON_FELLOWSHIP) m_bNestMap = CDnBlow::CheckEffectIgnoreMapType(); #else // #if defined(PRE_ADD_DRAGON_FELLOWSHIP) CDnWorld* pWorld = CDnWorld::GetInstancePtr(); m_bNestMap = (CDnWorld::MapTypeEnum::MapTypeDungeon == pWorld->GetMapType()) && ((CDnWorld::MapSubTypeEnum::MapSubTypeNest == pWorld->GetMapSubType() || CDnWorld::MapSubTypeEnum::MapSubTypeNestNormal == pWorld->GetMapSubType())); #endif // #if defined(PRE_ADD_DRAGON_FELLOWSHIP) #endif CheckBossMonster(); m_fOrigWight = 0.0f; m_iOrigPressLevel = 0; // 상태효과가 상태를 가지고 있는 경우 계속 유지를 위해서 if (m_pCantMoveBlow) m_pCantMoveBlow->SetPermanent(true); if (m_pCantActionBlow) m_pCantActionBlow->SetPermanent(true); if (m_pFrameStopBlow) m_pFrameStopBlow->SetPermanent(true); } CDnEscapeBlow::~CDnEscapeBlow(void) { SAFE_DELETE( m_pCantMoveBlow ); SAFE_DELETE( m_pCantActionBlow ); SAFE_DELETE( m_pFrameStopBlow ); } #ifdef _GAMESERVER bool CDnEscapeBlow::CanBegin( void ) { bool bResult = true; int iProb = int(m_fValue * 10000.0f); if( _rand(GetRoom())%10000 > iProb ) { // 호출한 쪽에서 CanBegin 호출하고 실패시 즉시 상태효과 삭제토록 변경. //SetState( STATE_BLOW::STATE_END ); OutputDebug( "%s - Freezing Fail\n", __FUNCTION__ ); bResult = false; } return bResult; } #endif // 처음에 결빙이 걸릴 확률이 Value 로 셋팅되므로 확률에 맞지 않는다면 그대로 끝내버림 void CDnEscapeBlow::OnBegin( LOCAL_TIME LocalTime, float fDelta ) { // 결빙지속시간 = <효과지속시간> * (1 – 물속성내성*상태보정weight값) float fGlobalStateEffectWeight = CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::StateRevision ); float fIceResistance = m_hActor->GetElementDefense(CDnState::Ice); // 지속시간을 내성에 따라 줄여준다. m_StateBlow.fDurationTime = m_StateBlow.fDurationTime * (1.0f - fIceResistance*fGlobalStateEffectWeight); // 행동불가, 이동불가 if( m_pCantMoveBlow ) m_pCantMoveBlow->OnBegin( LocalTime, fDelta ); if( m_pCantActionBlow ) m_pCantActionBlow->OnBegin( LocalTime, fDelta ); // TODO: 이펙트 출력.. 클라에서만 해당됨 #ifndef _GAMESERVER m_vlBreakActions.clear(); _AttachGraphicEffect(); m_vlBreakActions.push_back( "Break" ); // 플레이어가 걸렸을 경우에 버튼 연타 UI 보여줌 if( m_bPlayerCharacter ) { GetInterface().ShowStickAniDialog( true ); } #else // 몬스터 액터라면 이펙트 테이블에 정의되어있는 액션이 클라로부터 패킷이 오지 않기 때문에 (CmdAction 은 클라에서 패킷 안보냄) // 여기서 따로 처리해준다. if( m_hActor->IsMonsterActor() ) { if (false == m_bIgnoreEffectAction) { if( false == m_bIgnoreEffectAction ) { DNTableFileFormat* pStateEffectTable = CDnTableDB::GetInstancePtr()->GetTable( CDnTableDB::TSTATEEFFECT ); std::vector infoTokens; std::string delimiters = ";"; TokenizeA(m_ParentSkillInfo.szEffectOutputIDs, infoTokens, delimiters); int nTokenSize = (int)infoTokens.size(); for (int i = 0; i < nTokenSize; ++i) { int nEffectOutputID = atoi(infoTokens[i].c_str()); if (IsMatchStateEffectIndex(nEffectOutputID)) { string strActionName = pStateEffectTable->GetFieldFromLablePtr( nEffectOutputID, "_ActorActionName" )->GetString(); m_hActor->SetActionQueue( strActionName.c_str() ); } } } } } // 클라이언트에게도 이 상태효과의 지속시간을 방금 업데이트 된 것으로 바꿔준다. // PVP 같은 보정테이블 값을 참조받는 경우 , 서버와 클라이언트의 동기가 틀어지는경우가 생긴다. m_hActor->CmdModifyStateEffect( GetBlowID(), m_StateBlow ); #endif //시작될때 Weight/PressLevel을 저장해 놓는다. m_fOrigWight = m_hActor->GetWeight(); m_iOrigPressLevel = m_hActor->GetPressLevel(); ////캐릭터 밀림을 방지 하기 위해 설정. //m_hActor->SetWeight(0.0f); //m_hActor->SetPressLevel(-1); //피격 액션이 있으면 피격 액션이 끝나는 시점에서 멈추도록.. if (!m_strHitedAction.empty()) { //SAFE_DELETE(m_pFrameStopBlow); CEtActionBase::ActionElementStruct* pStruct = m_hActor->GetElement( m_strHitedAction.c_str() ); if( pStruct ) { m_isAbleIntervalChecker = true; //60frame기준으로 float fFrameTime = 0.01666666666666666666666666666667f * 1000.0f; m_IntervalChecker.OnBegin( LocalTime, (DWORD)(pStruct->dwLength * fFrameTime)); } } else if( m_pFrameStopBlow ) m_pFrameStopBlow->OnBegin( LocalTime, fDelta ); OutputDebug( "%s - BlowID:%d, Ice Resistance: %2.2f, Result DurationTime: %2.2f\n", __FUNCTION__, GetBlowID(), fIceResistance, GetDurationTime() ); } #ifndef _GAMESERVER void CDnEscapeBlow::_ProcessShake( LOCAL_TIME LocalTime, float fDelta ) { LOCAL_TIME TimeGap = LocalTime - m_ShakeStartTime; if( TimeGap > SHAKE_TIME ) { m_bShake = false; return; } float fShakeDelta = 0.0f; float fStartRatio = 0.2f; float fEndRatio = 0.2f; float fShakeValue = 3.0f; if( LocalTime < m_ShakeStartTime + ( TimeGap * fStartRatio ) ) { fShakeDelta = 1.f / ( SHAKE_TIME * fStartRatio ) * ( TimeGap ); fShakeDelta *= fShakeValue; } else if( LocalTime < m_ShakeStartTime + ( SHAKE_TIME * fEndRatio ) ) { fShakeDelta = fShakeValue; } else { fShakeDelta = 1.f / ( SHAKE_TIME * fEndRatio ) * ( ( m_ShakeStartTime + SHAKE_TIME ) - LocalTime ); fShakeDelta *= fShakeValue; } if( fShakeDelta > 0.1f ) { //MatrixEx *pCross = m_hActor->GetMatEx(); MatrixEx CrossMove = *m_hActor->GetMatEx(); EtVector3 vPrevPos = CrossMove.m_vPosition; CrossMove.m_vPosition.x += -fShakeDelta + ( ( _rand()%(int)(fShakeDelta*20) ) / 10.f ); CrossMove.m_vPosition.y += -fShakeDelta + ( ( _rand()%(int)(fShakeDelta*20) ) / 10.f ); CrossMove.m_vPosition.z += -fShakeDelta + ( ( _rand()%(int)(fShakeDelta*20) ) / 10.f ); NavigationMesh *pNavMesh = INSTANCE(CDnWorld).GetNavMesh( CrossMove.m_vPosition ); if( !pNavMesh ) { if( CDnWorld::GetInstance().GetAttribute( CrossMove.m_vPosition ) != 0 ) { CrossMove.m_vPosition = vPrevPos; } CrossMove.m_vPosition.y = CDnWorld::GetInstance().GetHeight( CrossMove.m_vPosition ); } else { NavigationCell* pCurCell = NULL; if( pCurCell == NULL ) { pCurCell = pNavMesh->FindClosestCell( CrossMove.m_vPosition ); if( ( pCurCell == NULL ) || ( !pCurCell->IsPointInCellCollumn( CrossMove.m_vPosition ) ) ) { pCurCell = NULL; //EtVector3 vPrevPos = Cross.m_vPosition; if( CDnWorld::GetInstance().GetAttribute( CrossMove.m_vPosition ) != 0 ) { CrossMove.m_vPosition = vPrevPos; } CrossMove.m_vPosition.y = CDnWorld::GetInstance().GetHeight( CrossMove.m_vPosition ); } } if( pCurCell ) { //EtVector3 vPrevPos = Cross.m_vPosition; //Cross.MoveLocalZAxis( fValue ); int nSide = -1; NavigationCell *pLastCell = NULL; pCurCell->FindLastCollision( vPrevPos, CrossMove.m_vPosition, &pLastCell, nSide ); if( nSide != -1 ) { if( pLastCell->Link( nSide ) == NULL ) { EtVector2 vMoveDir2D( CrossMove.m_vPosition.x - vPrevPos.x, CrossMove.m_vPosition.z - vPrevPos.z ); float fMoveLength = EtVec2Length( &vMoveDir2D ); vMoveDir2D /= fMoveLength; EtVector2 vWallDir2D = pLastCell->Side( nSide )->EndPointB() - pLastCell->Side( nSide )->EndPointA(); EtVec2Normalize( &vWallDir2D, &vWallDir2D ); fMoveLength *= EtVec2Dot( &vWallDir2D, &vMoveDir2D ); CrossMove.m_vPosition.x = vPrevPos.x + fMoveLength * vWallDir2D.x; CrossMove.m_vPosition.z = vPrevPos.z + fMoveLength * vWallDir2D.y; int nNewSide = -1; pCurCell->FindLastCollision( vPrevPos, CrossMove.m_vPosition, &pLastCell, nNewSide ); if( nNewSide != -1 ) { if( pLastCell->Link( nNewSide ) ) { NavigationCell *pNewCell; pNewCell = pLastCell->Link( nNewSide ); if( pNewCell->IsPointInCellCollumn( CrossMove.m_vPosition ) ) { pCurCell = pNewCell; } else { CrossMove.m_vPosition.x = vPrevPos.x; CrossMove.m_vPosition.z = vPrevPos.z; } } else if( !pCurCell->IsPointInCellCollumn( CrossMove.m_vPosition ) ) { CrossMove.m_vPosition.x = vPrevPos.x; CrossMove.m_vPosition.z = vPrevPos.z; } } } else { pCurCell = pLastCell->Link( nSide ); } } if( pCurCell->GetType() == NavigationCell::CT_PROP ) CrossMove.m_vPosition.y = pCurCell->GetPlane()->SolveForY( CrossMove.m_vPosition.x, CrossMove.m_vPosition.z ); else CrossMove.m_vPosition.y = CDnWorld::GetInstance().GetHeight( CrossMove.m_vPosition ); } } m_hActor->ProcessCollision( EtVector3(CrossMove.m_vPosition - vPrevPos) ); } } #endif // 결빙은 버튼 연타로 풀어낼 수 있다. void CDnEscapeBlow::Process( LOCAL_TIME LocalTime, float fDelta ) { CDnBlow::Process( LocalTime, fDelta ); //피격 동작이 설정 되어 있으면 피격 동작 끝날때 FrameStop상태효과를 활성화를 위해 time체크.. if (m_isAbleIntervalChecker) m_IntervalChecker.Process( LocalTime, fDelta ); #ifndef _GAMESERVER if( CDnLocalPlayerActor::s_hLocalActor == m_hActor ) { // TODO: 좌우 입력 받을 때 마다 시간 줄여줌. 결과적으로 서버로도 보내줘야 함... int iNumComboCount = 0; bool bComboSatisfy = m_ComboCalc.Process( LocalTime, fDelta, &iNumComboCount ); for( int i = 0; i < iNumComboCount; ++i ) m_StateBlow.fDurationTime -= 1.0f; if( bComboSatisfy ) { m_bShake = true; m_ShakeStartTime = LocalTime; OutputDebug( "[위기탈출] %d 회 콤보 성공, 시간 %2.2f 로 줄어듬 \n", iNumComboCount, m_StateBlow.fDurationTime ); } } if( m_bAllowShakeControl && m_bShake ) _ProcessShake( LocalTime, fDelta ); // 자기 자신의 플레이어 캐릭터만 위기 탈출로 시간을 줄일 수 있으므로 체크해서 서버로 상태효과 끝낸다고 보냄. if( CDnLocalPlayerActor::s_hLocalActor == m_hActor ) { if( m_StateBlow.fDurationTime < 0.2f ) { CDnLocalPlayerActor* pLocalPlayerActor = static_cast(m_hActor.GetPointer()); if (pLocalPlayerActor) pLocalPlayerActor->CmdRemoveStateEffectByServerBlowID( GetServerBlowID() ); } } #endif if( m_pCantMoveBlow ) m_pCantMoveBlow->Process( LocalTime, fDelta ); if( m_pCantActionBlow ) m_pCantActionBlow->Process( LocalTime, fDelta ); } bool CDnEscapeBlow::OnCustomIntervalProcess( void ) { m_IntervalChecker.OnEnd(0, 0.0f); if (m_pFrameStopBlow) m_pFrameStopBlow->OnBegin(m_LastLocalTime, 0.0f); m_isAbleIntervalChecker = false; return true; } void CDnEscapeBlow::OnEnd( LOCAL_TIME LocalTime, float fDelta ) { if( m_pCantMoveBlow ) m_pCantMoveBlow->OnEnd( LocalTime, fDelta ); if( m_pCantActionBlow ) m_pCantActionBlow->OnEnd( LocalTime, fDelta ); if( m_pFrameStopBlow ) m_pFrameStopBlow->OnEnd( LocalTime, fDelta ); ////////////////////////////////////////////////////////////////////////// // #36294 // 아이스 스피어 스킬 사용시 상태효과 중복 처리때문에 OnBegin되기 전에 // OnEnd로 들어 오는 경우가 있음. 이때 OnBegin되기 전에 m_bIgnoreEffectAction가 false // 상태여서 중복처리 될때마다 OnEnd로 들어와서 SetActionQueue("Stand")호출됨. // CheckBossMonster()로 함수 빼내서 생성자에서 호출되도록 수정해서 // m_bIgnoreEffectAction 값 정상적으로 설정 되도록 수정함. ////////////////////////////////////////////////////////////////////////// // 다운 상태가 아니라면 풀어준다. if( false == m_bIgnoreEffectAction ) { // 이펙트에 지정된 액션을 계속 실행하고 있을 경우에 Stand 로 풀어주도록 한다. #ifdef _GAMESERVER if( false == m_hActor->IsDown() ) #else // #37065 이펙트 정보에 액션 이름이 없거나 (퍼니셔 브라스의 묶기) // 이펙트 정보에 정의된 액션을 취하고 있는 경우 결빙 종료시에 Stand 액션 실행. if( NULL == m_pEffectOutputInfo || m_pEffectOutputInfo->strActorActionName.empty() || m_pEffectOutputInfo->strActorActionName == m_hActor->GetCurrentAction() ) #endif { #if defined(PRE_FIX_50007) //이미 죽은 경우는 동작 바뀌지 않도록.. const char *szCurrentAction = m_hActor->GetCurrentAction(); //Die동작인 경우는 Stand로 전환 안되도록 한다. if (strstr(szCurrentAction, "Die") == NULL && strstr(szCurrentAction, "Relieve") == NULL) //#53900 #endif // PRE_FIX_50007 m_hActor->SetActionQueue( "Stand" ); } } #ifndef _GAMESERVER // 플레이어가 걸렸을 경우에 버튼 연타 UI 보여줌 if( dynamic_cast(m_hActor.GetPointer()) ) { GetInterface().ShowStickAniDialog( false ); } if( m_pEffectOutputInfo ) { // #30527 결빙 이펙트도 각 본에 붙게 되었으므로 본에 붙어있는 이펙트들을 모두 찾아서 break 액션을 시켜준다. if( EffectOutputInfo::ATTACH == m_pEffectOutputInfo->iOutputType ) { if( !m_vlBreakActions.empty() && m_hEtcObjectEffect ) m_hEtcObjectEffect->SetActionQueue( m_vlBreakActions.at( _rand() % (int)m_vlBreakActions.size() ).c_str() ); // 깨지는 액션 실행될 때는 링크 끊어줌 TSmartPtrSignalImp::SmartPtrSignalImpStruct *pStruct = (TSmartPtrSignalImp::SmartPtrSignalImpStruct *)m_hActor->TSmartPtrSignalImp::GetStruct( STATEBLOWEFFECT_ETCOFFSET + m_StateBlow.emBlowIndex, GetBlowID() ); if( pStruct ) pStruct->bLinkObject = false; } else if( EffectOutputInfo::DUMMY_BONE_ATTACH == m_pEffectOutputInfo->iOutputType ) { int nNumBone = (int)m_pEffectOutputInfo->vlDummyBoneIndices.size(); for( int nBone = 0; nBone < nNumBone; ++nBone ) { if( m_hActor->TSmartPtrSignalImp::IsExistSignalHandle( STATEBLOWEFFECT_ETCOFFSET + m_StateBlow.emBlowIndex, (m_nBlowID*100)+nBone ) ) { // 깨지는 액션 실행될 때는 링크 끊어줌 TSmartPtrSignalImp::SmartPtrSignalImpStruct *pStruct = (TSmartPtrSignalImp::SmartPtrSignalImpStruct *)m_hActor->TSmartPtrSignalImp::GetStruct( STATEBLOWEFFECT_ETCOFFSET + m_StateBlow.emBlowIndex, (m_nBlowID*100)+nBone ); if( pStruct ) pStruct->bLinkObject = false; DnEtcHandle hEffectHandle = m_hActor->TSmartPtrSignalImp::GetSignalHandle( STATEBLOWEFFECT_ETCOFFSET + m_StateBlow.emBlowIndex, (m_nBlowID*100)+nBone ); if( hEffectHandle ) { if( hEffectHandle->IsExistAction( "Break" ) ) hEffectHandle->SetActionQueue( "Break" ); // Destroy 시그널 반드시 존재해야 객체 소멸됨 else m_hActor->TSmartPtrSignalImp::RemoveSignalHandle( STATEBLOWEFFECT_ETCOFFSET + m_StateBlow.emBlowIndex, (m_nBlowID*100)+nBone ); } else m_hActor->TSmartPtrSignalImp::RemoveSignalHandle( STATEBLOWEFFECT_ETCOFFSET + m_StateBlow.emBlowIndex, (m_nBlowID*100)+nBone ); } } } } // 효과 삭제... 실제로 이펙트를 없애는 건 아니고, 이펙트 자체는 destroy 시그널로 알아서 죽는다. // 하지만 상태효과는 이 시점에서 끝이므로 이펙트를 추가할 때 판단 근거가 되는 스킬 정보를 여기서 지워줘야 한다. // 그렇지 않으면 스킬 정보 계속 남아있어서 이펙트가 추가가 안됨. m_hActor->DetachSEEffectSkillInfo( m_ParentSkillInfo ); _SetDiffuse( 1.0f, 1.0f, 1.0f, 1.0f ); //m_hActor->DetachSEEffect( m_ParentSkillInfo, m_StateBlow.emBlowIndex, GetBlowID(), m_pEffectOutputInfo ); #else #endif ////상태가 끝날때 기존에 저장해놓은 값으로 복원 //m_hActor->SetWeight(m_fOrigWight); //m_hActor->SetPressLevel(m_iOrigPressLevel); OutputDebug( "%s BlowID: %d\n", __FUNCTION__, GetBlowID() ); } // #ifdef _GAMESERVER // float CDnEscapeBlow::OnCalcDamage( float fOriginalDamage, CDnDamageBase::SHitParam& HitParam ) // { // float fResult = 0.0f; // // --m_iDurabilityCount; // // // 결빙상태에서는 데미지를 1.5배로 먹는다. // fResult = fOriginalDamage / 2.0f; // // // 결빙상태에서는 Freezing 이던 Stand 액션을 하고 있던 간에 맞았다고 hit 류 액션으로 바꾸지 않는다. // // hit 류의 CanHit 가 false 이면 계속 맞지 않는 경우가 생긴다. #15675 // HitParam.szActionName.clear(); // // // 막타라면 상태효과 종료. // if( m_iDurabilityCount <= 0 ) // { // SetState( STATE_BLOW::STATE_END ); // OutputDebug( "%s - Freezing Broken by Attack!\n", __FUNCTION__ ); // } // // return fResult; // } // #else // float CDnEscapeBlow::OnCalcDamage( float fOriginalDamage, CDnDamageBase::SHitParam& HitParam ) // { // --m_iDurabilityCount; // // // 마지막 타격일 경우엔 데미지를 두 배로 먹는다... 클라에서는 아이스 볼트 맞았을 때도 // // 이쪽으로 들어오게 됨.. // // 클라에서는 처음에 상태효과 걸렸을 때도 데미지를 먹기 때문에 이쪽으로 호출됨.. // // 따라서 2회 맞으면 부서진다고 했을 때 총 3회로 해주어야 결빙 걸리고 두 번 맞고 깨지게 된다. // if( m_iDurabilityCount <= 0 ) // { // SetState( STATE_BLOW::STATE_END ); // OutputDebug( "%s - Freezing Broken by Attack!\n", __FUNCTION__ ); // } // // return 0.0f; // } // #endif void CDnEscapeBlow::OnDuplicate( const STATE_BLOW& StateBlowInfo ) { // 결빙 상태효과에서는 중복처리할 때 아무것도 하지 않는다. // 추후 정확한 요구사항이 오면 반영토록 한다. } #if defined(PRE_ADD_PREFIX_SYSTE_RENEW) void CDnEscapeBlow::AddStateEffectValue(const char* szOrigValue, const char* szAddValue, std::string& szNewValue) { char szBuff[128] = {0, }; //파싱에 필요한 변수 선언 std::vector vlTokens[2]; string strArgument[2]; //필요한 값 변수 float fValue[2] = { 0.0f, }; int nCountValue[2] = { 0, }; int nAllowShakeControl[2] = {0, }; std::string strHitedAction[2]; ////////////////////////////////////////////////////////////////////////// //첫번째 문자열 파싱. strArgument[0] = szOrigValue; TokenizeA( strArgument[0], vlTokens[0], ";" ); if( vlTokens[0].size() >= 3 ) { fValue[0] = (float)atof( vlTokens[0][0].c_str() ); nCountValue[0] = atoi( vlTokens[0][1].c_str() ); nAllowShakeControl[0] = atoi(vlTokens[0][2].c_str()); if (vlTokens[0].size() == 4) strHitedAction[0] = vlTokens[0][3]; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //두번째 문자열 파싱 strArgument[1] = szAddValue; TokenizeA( strArgument[1], vlTokens[1], ";" ); if( vlTokens[1].size() == 2 ) { fValue[1] = (float)atof( vlTokens[1][0].c_str() ); nCountValue[1] = atoi( vlTokens[1][1].c_str() ); nAllowShakeControl[1] = atoi(vlTokens[1][2].c_str()); if (vlTokens[1].size() == 4) strHitedAction[1] = vlTokens[1][3]; } ////////////////////////////////////////////////////////////////////////// //두 값을 더한다. float fResultValue = fValue[0] + fValue[1]; int nResultCount = (nCountValue[0] + nCountValue[1]); int nResultAllowShakeControl = (nAllowShakeControl[0] == 0 || nAllowShakeControl[1] == 0) ? 0 : 1; std::string strResultAction = strHitedAction[0].empty() ? (strHitedAction[1].empty() ? "" : strHitedAction[1]) : strHitedAction[0]; sprintf_s(szBuff, "%f;%d;%d;%s", fResultValue, nResultCount, nResultAllowShakeControl, strResultAction.c_str()); szNewValue = szBuff; } void CDnEscapeBlow::RemoveStateEffectValue(const char* szOrigValue, const char* szAddValue, std::string& szNewValue) { char szBuff[128] = {0, }; //파싱에 필요한 변수 선언 std::vector vlTokens[2]; string strArgument[2]; //필요한 값 변수 float fValue[2] = { 0.0f, }; int nCountValue[2] = { 0, }; int nAllowShakeControl[2] = {0, }; std::string strHitedAction[2]; ////////////////////////////////////////////////////////////////////////// //첫번째 문자열 파싱. strArgument[0] = szOrigValue; TokenizeA( strArgument[0], vlTokens[0], ";" ); if( vlTokens[0].size() >= 3 ) { fValue[0] = (float)atof( vlTokens[0][0].c_str() ); nCountValue[0] = atoi( vlTokens[0][1].c_str() ); nAllowShakeControl[0] = atoi(vlTokens[0][2].c_str()); if (vlTokens[0].size() == 4) strHitedAction[0] = vlTokens[0][3]; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //두번째 문자열 파싱 strArgument[1] = szAddValue; TokenizeA( strArgument[1], vlTokens[1], ";" ); if( vlTokens[1].size() == 2 ) { fValue[1] = (float)atof( vlTokens[1][0].c_str() ); nCountValue[1] = atoi( vlTokens[1][1].c_str() ); nAllowShakeControl[1] = atoi(vlTokens[1][2].c_str()); if (vlTokens[1].size() == 4) strHitedAction[1] = vlTokens[1][3]; } ////////////////////////////////////////////////////////////////////////// //두 값을 더한다. float fResultValue = fValue[0] - fValue[1]; int nResultCount = (nCountValue[0] - nCountValue[1]); int nResultAllowShakeControl = (nAllowShakeControl[0] == 0 || nAllowShakeControl[1] == 0) ? 0 : 1; std::string strResultAction = strHitedAction[0].empty() ? (strHitedAction[1].empty() ? "" : strHitedAction[1]) : strHitedAction[0]; sprintf_s(szBuff, "%f;%d;%d;%s", fResultValue, nResultCount, nResultAllowShakeControl, strResultAction.c_str()); szNewValue = szBuff; } #endif // PRE_ADD_PREFIX_SYSTE_RENEW void CDnEscapeBlow::CheckBossMonster() { // #27679 네임드, 보스, 8인 네스트보스에게는 효과는 적용되나 실제로 escape 의 부가효과가 적용되진 않는다. // #28385 네스트에서만 적용됨. if( m_hActor->IsMonsterActor() ) { if( m_bNestMap ) { CDnMonsterActor* pMonsterActor = static_cast(m_hActor.GetPointer()); if( (CDnMonsterState::Boss == pMonsterActor->GetGrade() || CDnMonsterState::BossHP4 == pMonsterActor->GetGrade() || CDnMonsterState::NestBoss == pMonsterActor->GetGrade() || CDnMonsterState::NestBoss8 == pMonsterActor->GetGrade()) ) { SAFE_DELETE( m_pCantMoveBlow ); SAFE_DELETE( m_pCantActionBlow ); SAFE_DELETE( m_pFrameStopBlow ); // 이래야 보스급에선 freezing 액션 실행 안됨. m_bIgnoreEffectAction = true; } } // #51048 상태효과가 있는 경우 네스트 네임드급 몬스터들과 동일한 취급을 받는다. // 독립적으로 상태효과가 박히는 것이므로 맵과 관계 없다. if( m_hActor->IsAppliedThisStateBlow(STATE_BLOW::BLOW_235) ) { m_bIgnoreEffectAction = true; } } } #if defined(PRE_FIX_51048) void CDnEscapeBlow::RemoveDebufAction(LOCAL_TIME LocalTime, float fDelta) { if( m_pCantMoveBlow ) m_pCantMoveBlow->OnEnd( LocalTime, fDelta ); if( m_pCantActionBlow ) m_pCantActionBlow->OnEnd( LocalTime, fDelta ); if( m_pFrameStopBlow ) m_pFrameStopBlow->OnEnd( LocalTime, fDelta ); SAFE_DELETE( m_pCantMoveBlow ); SAFE_DELETE( m_pCantActionBlow ); SAFE_DELETE( m_pFrameStopBlow ); m_bIgnoreEffectAction = true; } #endif // PRE_FIX_51048