#include "StdAfx.h" #include "DnProjectile.h" #include "DnWeapon.h" #include "MAActorRenderBase.h" #include "VelocityFunc.h" #include "EtActionSignal.h" #include "DnWorld.h" #include "DnMonsterActor.h" #include "MAAiBase.h" #include "DnWorldBrokenProp.h" #include "DNGameServerManager.h" #include "DnChainAttackBlow.h" #include "DnPingpongBlow.h" #include "MAWalkMovement.h" #include "DnPlayerActor.h" #include "TaskManager.h" #include "DnGameTask.h" #include "IDnOrbitProcessor.h" #include "DNMissionSystem.h" #include "DnPartsMonsterActor.h" #include "DnComboDamageLimitBlow.h" #include "DnStateBlow.h" #include "DnCurseBlow.h" #include "DnChangeStandActionBlow.h" int CDnProjectile::s_iHitUniqueID = 0; #define SHOOT_ACTION_NAME "Shoot" #define HIT_ACTION_NAME "Hit" #define DESTROY_ACTION_NAME "Destroy" // 서버에서는 이 액션을 실행시키지 않는다. // 게임서버와 클라이언트의 DnProjectile 클래스 파일이 따로 있으므로 수정할 경우 각각 수정해주어야 함. const int PROJECTILE_PACKET_BUFFER_SIZE = 128; CDnProjectile::CDnProjectile( CMultiRoom *pRoom, DnActorHandle hActor, bool bProcess, bool bIncreaseUniqueID ) : CDnWeapon( pRoom, bProcess, bIncreaseUniqueID ) , m_vPrevPos( 0.f, 0.f, 0.f ) , m_vStartPos( 0.f, 0.f, 0.f ) , m_vTargetPosition( 0.f, 0.f, 0.f ) , m_vHitPos( 0.f, 0.f, 0.f ) , m_bOnCollisionCalled( false ) , m_bPierce( false ) , m_LastHitSignalEndTime( 0 ) , m_iHitUniqueID( -1 ) , m_bHasHitAction( false ) , m_bHasHitSignalInHitAction( false ) , m_bHasHitSignalInShootAction( false ) , m_nLastHitSignalIndex( 0 ) , m_fElapsedTime( 0.0f ) , m_iShooterShootActionIndex( -1 ) , m_iSignalArrayIndex( -1 ) , m_fHitApplyPercent( -1.0f ) , m_pOrbitProcessor( NULL ) , m_pPacketBuffer( NULL ) , m_pPacketStream( NULL ) , m_vForceDir( 0.0f, 0.0f, 0.0f ) , m_bUseForceDir( false ) { SetWeaponType( WeaponTypeEnum::Projectile ); m_hShooter = hActor; m_InvalidLocalTime = 0; m_CreateLocalTime = 0; m_bValidDamage = true; m_bFallGravity = false; m_fSpeed = 3000.f; m_fResistance = 0.f; m_nDestroyOrbitTimeGap = 3000; m_bStick = false; m_fGravity = 0.f; m_bFirstProcess = true; m_DestroyOrbitType = FallGravity; m_ValidType = ValidTypeEnum( WeaponLength | Stick ); memset( &m_HitStruct, 0, sizeof(m_HitStruct) ); m_iHitUniqueID = ++s_iHitUniqueID; m_bHitSignalProcessed = false; m_bBombHitSignalStarted = false; // 2009.7.29 한기 - 폭발시 히트 시그널이 뒤에 있는 경우가 있으므로 뒤에 있는 케이스가 있어서 히트 시그널 시작 시에 플래그 켜줌. m_VelocityType = Accell; m_ParentSkillInfo.hSkillUser = hActor; m_nMaxHitCount = 0; m_bHitActionStarted = false; m_bProcessingBombHitSignal = false; m_bFromCharger = false; m_fProjectileOrbitRotateZDegree = 0.0f; m_iShootActionIndex = -1; m_iHitActionIndex = -1; m_iDestroyActionIndex = -1; m_bHasDestroyAction = false; // 발사체로 쏜 것은 데미지 계산할 때 쏘았을 당시에 받아놨던 state 를 기반으로 처리.z m_HitParam.bFromProjectile = true; m_bTraceHitTarget = false; m_bTraceHitActorHittable = false; m_cShooterType = 0; m_dwShooterUniqueID = -1; m_nShooterSerialID = -1; m_nShooterActionIndex = -1; m_nShooterSignalIndex = -1; m_eForceHitElement = ElementEnum::ElementEnum_Amount; m_iSummonMonsterForceSkillLevel = 0; m_OrbitType = OrbitTypeCount; m_TargetType = TargetTypeCount; m_nTargetPartsBoneIndex = 0; m_nTargetPartsIndex = 0; m_nValidTime = 0; #if defined(PRE_FIX_52329) m_nIgnoreHitType = 0; #endif // PRE_FIX_52329 #if defined(PRE_ADD_55295) m_bHitActionVectorInit = false; #endif // PRE_ADD_55295 m_SkillStartTime = 0; #if defined(PRE_FIX_65287) m_fShooterFinalDamageRate = 0.0f; #endif // PRE_FIX_65287 } CDnProjectile::~CDnProjectile() { if (m_ParentSkillInfo.hSkillUser) { DnSkillHandle hSkill = m_ParentSkillInfo.hSkillUser->FindSkill(m_ParentSkillInfo.iSkillID); if (hSkill) hSkill->RemoveProjectile(this); } SAFE_DELETE( m_pOrbitProcessor ); SAFE_DELETE( m_pPacketBuffer ); SAFE_DELETE( m_pPacketStream ); FreeAction(); } bool CDnProjectile::Initialize( MatrixEx &Offset, OrbitTypeEnum OrbitType, DestroyOrbitTypeEnum DestroyType, TargetTypeEnum TargetType ) { m_OrbitType = OrbitType; m_DestroyOrbitType = DestroyType; m_TargetType = TargetType; m_Cross = Offset; m_OffsetCross = Offset; m_vStartPos = m_vPrevPos = m_Cross.m_vPosition; SetActionQueue( SHOOT_ACTION_NAME, 0, 0.f ); m_iShootActionIndex = GetElementIndex( SHOOT_ACTION_NAME ); m_nValidTime = 0; m_bFirstProcess = true; if( m_OrbitType == Homing || m_OrbitType == TerrainHoming ) { m_ValidType = (ValidTypeEnum)( m_ValidType | Time ); m_ValidType = (ValidTypeEnum)( m_ValidType & ~WeaponLength ); } if( IsExistAction( SHOOT_ACTION_NAME ) ) { // 히트 시그널이 있는지 확인한다. // 현재 호밍 가속도인 경우엔 시간이 다되면 데미지를 적용하도록 되어있기 때문에 Shoot 액션에 Hit 시그널이 없는 경우 // 데미지 처리를 하지 않도록 구분하기 위해서 여기서 미리 구분해두도록 한다. CEtActionBase::ActionElementStruct* pShootActionElement = this->GetElement( SHOOT_ACTION_NAME ); int iNumSignals = static_cast(pShootActionElement->pVecSignalList.size()); for( int iSignal = 0; iSignal < iNumSignals; ++iSignal ) { SignalTypeEnum eSignalType = (SignalTypeEnum)pShootActionElement->pVecSignalList.at( iSignal )->GetSignalIndex(); if( STE_Hit == eSignalType ) { m_bHasHitSignalInShootAction = true; break; } } } if( IsExistAction( HIT_ACTION_NAME ) ) { m_bHasHitAction = true; // 히트 시그널이 있어야 실제로 히트 액션이 유효하다고 판단한다. CEtActionBase::ActionElementStruct* pHitActionElement = this->GetElement( HIT_ACTION_NAME ); m_iHitActionIndex = GetElementIndex( HIT_ACTION_NAME ); int iNumSignals = static_cast(pHitActionElement->pVecSignalList.size()); for( int iSignal = 0; iSignal < iNumSignals; ++iSignal ) { SignalTypeEnum eSignalType = (SignalTypeEnum)pHitActionElement->pVecSignalList.at( iSignal )->GetSignalIndex(); if( STE_Hit == eSignalType || STE_Projectile == eSignalType ) // 디스토션 에로우 등은 발사체의 hit 액션에서 발사체를 다시 쏜다.. { m_bHasHitSignalInHitAction = true; break; } } } if( IsExistAction( DESTROY_ACTION_NAME ) ) { m_iDestroyActionIndex = GetElementIndex( DESTROY_ACTION_NAME ); m_bHasDestroyAction = true; } return true; } bool CDnProjectile::PostInitialize( void ) { bool bResult = false; _ASSERT( NULL == m_pOrbitProcessor ); S_PROJECTILE_PROPERTY OrbitProperty; OrbitProperty.eOrbitType = m_OrbitType; OrbitProperty.eTargetType = m_TargetType; OrbitProperty.eVelocityType = m_VelocityType; OrbitProperty.fSpeed = m_fSpeed; OrbitProperty.fResistance = m_fResistance; OrbitProperty.fProjectileOrbitRotateZ = m_fProjectileOrbitRotateZDegree; m_pOrbitProcessor = IDnOrbitProcessor::Create( m_Cross, m_OffsetCross, &OrbitProperty ); // 초기위치는 오프셋과 같지만.. 의미상 이렇게 따로 넣어준다. _ASSERT( m_pOrbitProcessor ); if( NULL != m_pOrbitProcessor ) { m_pOrbitProcessor->SetRoom( GetRoom() ); m_pOrbitProcessor->SetTargetActor( m_hTargetActor ); m_pOrbitProcessor->SetTargetPosition( m_vTargetPosition ); m_pOrbitProcessor->SetValidTimePointer( &m_nValidTime ); m_pOrbitProcessor->SetTargetPartsIndex(m_nTargetPartsIndex, m_nTargetPartsBoneIndex); bResult = true; } return bResult; } void CDnProjectile::ProcessOrbit( LOCAL_TIME LocalTime, float fDelta ) { if( m_bTraceHitTarget ) { if( m_hTraceActor ) { EtVector3 vPos = *m_hTraceActor->GetPosition(); m_Cross.SetPosition( vPos ); return; } } if( m_bStick ) return; // 생성되다 만 발사체는 PostInitialize 가 호출이 안되어 있는 경우도 있다. // 대표적인 예가 화살통 보조무기 없이 에어리얼 체인샷이 나가는 경우. if( m_pOrbitProcessor ) m_pOrbitProcessor->ProcessOrbit( m_Cross, m_vPrevPos, LocalTime, fDelta ); } void CDnProjectile::ProcessValid( LOCAL_TIME LocalTime, float fDelta ) { // 한 번 invalid 체크된 것은 다시 체크하지 않는다. // hit 되어 폭발하는 발사체 같은 경우 m_bValidDamage 플래그가 폭발시에 켜졌다가 // 다시 ProcessValid 내에서 사거리가 다 되어 ValidDamage가 false로 되는 경우가 있다. if( 0 == m_InvalidLocalTime ) { if( m_bValidDamage && ( m_ValidType & ValidTypeEnum::WeaponLength ) ) { if( GetWeaponLength() != -1 ) { bool bRangeOut = false; if( RangeFallGravity == m_DestroyOrbitType ) { float fAdditionalRange = CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::RangeFallGravityAdditionalProjectileRange ); float fRangeFallGravityLength = float(GetWeaponLength()) * fAdditionalRange; bRangeOut = (fRangeFallGravityLength < EtVec3Length( &( m_Cross.m_vPosition - m_vStartPos ) )); } else { bRangeOut = ( EtVec3Length( &( m_Cross.m_vPosition - m_vStartPos ) ) > GetWeaponLength() ); } if( bRangeOut ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; // #43085 클라에서는 Destroy 액션을 실행시키는 시점.. 서버에서는 곧바로 발사체 제거. if( m_bHasDestroyAction ) { // FallGravity 는 퍼니싱 스윙이나 파이어볼 처럼 고각으로 쏴서 떨어뜨리는 건 맞아야 하므로 RangeFallGravity 만 처리. if( RangeFallGravity == m_DestroyOrbitType ) SetDestroy(); } } } } } // 한번 invalid 된 것은 다시 invalid 처리 되지 않도록 한다. // hit 액션이 있는 경우 validdamage 가 다시 켜지므로 m_bOnCollisionCalled 로 구분한다. if( m_bValidDamage && ( m_ValidType & ValidTypeEnum::Time ) && false == m_bOnCollisionCalled ) { if( (int)( LocalTime - m_CreateLocalTime ) > m_nValidTime ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; if( m_hTargetActor && m_hShooter && OrbitTypeEnum::Homing == m_OrbitType && VelocityTypeEnum::Accell == m_VelocityType ) { if( m_HitParam.szActionName.empty() ) m_HitParam.szActionName.assign( "Hit_Front" ); // hit 액션에 hit 시그널이 있다면 valid damage go on 플래그를 true 로 호출한다. // 호밍 가속도라도 ProcessDamage 에서 hit 처리가 먼저될 때도 있고 여기서 될 때도 있다. // 발사체의 위치로 충돌 처리가 된다면 ProcessDamage 쪽에서 처리가 되고 그렇지 않다면 여기서 처리가 된다. // 호밍 가속도 발사체에 Shoot 액션에 Hit 시그널이 없다면 Hit 처리는 당연히 안되는 거고, // CheckAndApplyDamage() 함수 내부에서 추가되도록 처리되는 상태효과가 추가되면 안되므로 OnCollisionWithActor() 만 호출해준다. // 이쪽에는 처음 hit 될 때 이므로 Shoot 액션일 때 들어옴. if( m_bHasHitSignalInShootAction ) { if( m_bTraceHitTarget || m_hTargetActor->IsHittable( m_hShooter, LocalTime, &m_HitStruct ) ) { #if defined(PRE_FIX_59238) //꼭두각시와 꼭두각시를 소환한 주인 액터는 동시 히트 되지 않아야 한다. if (IsHittable(m_hTargetActor)) { CheckAndApplyDamage( m_hTargetActor, m_bHasHitSignalInHitAction ); } #else CheckAndApplyDamage( m_hTargetActor, m_bHasHitSignalInHitAction ); #endif // PRE_FIX_59238 } } else { OnCollisionWithActor(); } } else { // #38437 클라에서는 Destroy 액션을 실행시키므로 서버에서는 있는지 없는지만 체크해서 있으면 // Destroy 액션 실행이 아닌 바로 없애도록 처리. if( m_bHasDestroyAction ) SetDestroy(); } } } #if defined(PRE_FIX_52329) //IgnoreHitType 설정이 0이 아닌 경우 Valid체크는 하지 않도록 한다.. if (m_nIgnoreHitType == 0) { #endif // PRE_FIX_52329 // terrain linear 라면 현재 위치가 갈 수 없는 지역에선 프로젝타일 사라지도록 처리. // 롤링 라바 같은 경우 terraing linear 이면서 못가는 곳에 부딪혔을 때도 hit 의 hit 시그널이 // 처리 되어야 하므로 hit 액션을 갖고 있는 발사체인 경우 데미지 처리하도록 변경. if( OrbitTypeEnum::TerrainLinear == m_OrbitType || OrbitTypeEnum::TerrainHoming == m_OrbitType) { if( false == MAWalkMovement::IsMovableBlock( &INSTANCE(CDnWorld), m_Cross.GetPosition() ) ) { if( m_bValidDamage ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; } m_bStick = true; OnCollisionWithGround(); } } if( !m_bStick && ( m_ValidType & ValidTypeEnum::Stick ) ) { #if defined(PRE_FIX_55855) float fHeight = CDnWorld::GetInstance( GetRoom() ).GetMaxHeightWithProp( m_Cross.m_vPosition ); #else float fHeight = CDnWorld::GetInstance(GetRoom()).GetHeight( m_Cross.m_vPosition ); #endif // PRE_FIX_55855 if( m_Cross.m_vPosition.y < fHeight ) { // 발사체의 절대적인 위치 값 기준으로 방향을 구한다. // ZVector 는 OrbitProcessor 에서 컨트롤하고, 실제 방향과 다를 수 있다. EtVector3 vDir = m_Cross.m_vPosition - m_vPrevPos; EtVec3Normalize( &vDir, &vDir ); EtVector3 vPickPos; if( CDnWorld::GetInstance(GetRoom()).Pick( m_vPrevPos, vDir, vPickPos ) == true ) { m_Cross.m_vPosition = vPickPos; #if defined(PRE_FIX_55855) m_Cross.m_vPosition.y = CDnWorld::GetInstance( GetRoom() ).GetMaxHeightWithProp( vPickPos ); #endif // PRE_FIX_55855 } else m_Cross.m_vPosition.y = fHeight; //m_Cross.m_vPosition += m_Cross.m_vZAxis * -10.f; if( m_bValidDamage ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; } m_bStick = true; OnCollisionWithGround(); } } // 가속도 호밍인 경우엔 프랍과 충돌했는지 확인. if( m_hTargetActor && OrbitTypeEnum::Homing == m_OrbitType && VelocityTypeEnum::Accell == m_VelocityType ) { float fLength = EtVec3Length( &( m_Cross.m_vPosition - m_vPrevPos ) ); EtVector3 vCenter = m_vPrevPos + ( m_Cross.m_vZAxis * ( fLength * 0.5f ) ); float fPropContactDistance = FLT_MAX; float fPropContactTime = FLT_MAX; DnPropHandle hResultProp = _CheckPropCollision( vCenter, fLength, fPropContactTime, fPropContactDistance ); if( hResultProp ) { OnCollisionWithProp(); m_bValidDamage = false; m_InvalidLocalTime = LocalTime; m_bStick = true; } } #if defined(PRE_FIX_52329) } #endif // PRE_FIX_52329 // 이미 hit 액션을 실행하고 있다면 액션을 다시 실행시키지 않는다. (#21384) // 따로 OnCollision~ 함수가 호출되지 않았는데 hit 액션으로 바뀌어있다면 shoot 액션에서 // 직접 바꾼 경우이므로 OnCollisionWithGround() 를 호출해준다. if( false == m_bOnCollisionCalled && m_iHitActionIndex == GetCurrentActionIndex() && m_iHitActionIndex != -1 ) { if( m_bValidDamage ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; } m_bStick = true; OnChangedNextActionToHit(); } } void CDnProjectile::ProcessDestroyOrbit( LOCAL_TIME LocalTime, float fDelta ) { if( LocalTime - m_InvalidLocalTime > m_nDestroyOrbitTimeGap ) { SetDestroy(); } switch( m_DestroyOrbitType ) { case Instantly: if( false == m_bHasHitAction ) // hit 액션 갖고 있는 발사체는 hit 액션 끝나거나 valid time 다 되면 알아서 destroy 됨. SetDestroy(); break; case FallGravity: case RangeFallGravity: { if( !m_bStick ) { m_Cross.m_vPosition.y += CalcMovement( m_fGravity, fDelta, FLT_MAX, FLT_MIN, -15.f ); // summon comet 처럼 사거리 다 되어 fall gravity 로 떨어지도록 연출하는 경우가 있으므로 // fall gravity 로 떨어지는 동안에도 데미지를 먹도록 처리. m_bValidDamage = true; m_bFallGravity = true; } } break; } } DnPropHandle CDnProjectile::_CheckPropCollision( EtVector3& vCenter, float fLength, float& fPropContactDistance, float& fPropContactTime ) { DnPropHandle hResult; DNVector(DnPropHandle) hVecProp; int nCount = CDnWorld::GetInstance(GetRoom()).ScanProp( vCenter, fLength * 0.5f, hVecProp, FilterProjectileHitSignal() ); if( nCount > 0 ) { float fMinContactTime = FLT_MAX; float fNowContactTime = 0.f; SCollisionResponse Response; SSegment Segment; Segment.vOrigin = m_vPrevPos; Segment.vDirection = m_Cross.m_vZAxis * fLength; for( int i=0; iIsProjectileSkip() ) continue; if( hVecProp[i] == m_hShooterProp ) continue; if( hVecProp[i]->GetObjectHandle() && hVecProp[i]->GetObjectHandle()->FindSegmentCollision( Segment, Response ) ) { // 서버에서는 프레임이 늦을 수 있기 때문에 발사체의 현재 위치로 비교하면 안됨. // 엔진에서 던져주는 도달시간 기준으로 골라내도록한다. //fNowContactTime = EtVec3LengthSq( &( hVecProp[i]->GetMatEx()->m_vPosition - m_Cross.m_vPosition ) ); fNowContactTime = Response.fContactTime; if( fNowContactTime < fMinContactTime ) { fMinContactTime = fNowContactTime; hResult = hVecProp[i]; // EtVector3 vVec = m_Cross.m_vPosition - m_vPrevPos; // EtVec3Normalize( &vVec, &vVec ); m_vHitPos = m_vPrevPos + ( Segment.vDirection * Response.fContactTime ); fPropContactDistance = EtVec3LengthSq( &( Segment.vDirection * Response.fContactTime ) ); fPropContactTime = Response.fContactTime; } } } } return hResult; } bool CDnProjectile::ProcessDamage( LOCAL_TIME LocalTime, float fDelta, LOCAL_TIME SignalStartTime, LOCAL_TIME SignalEndTime, int nSignalIndex, BOOL bUseHitSignalArea ) { bool bHitResult = false; // 한번 hit 된 녀석에겐 다시 데미지 주지 않는다. // #40133 미션에 알려주기 위해 죽인 애들 카운트. int iKillCount = 0; if( !m_hShooter ) return false; if( (m_hShooter->GetActorType() != CDnActorState::PropActor) && m_hShooter->IsDie() ) return false; float fLength = EtVec3Length( &( m_Cross.m_vPosition - m_vPrevPos ) ); //if( /*fLength > 0.f ||*/ m_bOnCollisionCalled ) { EtVector3 vCenter = m_vPrevPos + ( m_Cross.m_vZAxis * ( fLength * 0.5f ) ); // Prop Check float fPropContactDistance = FLT_MAX; float fPropContactTime = FLT_MAX; DnPropHandle hResultProp = _CheckPropCollision( vCenter, fLength, fPropContactDistance, fPropContactTime ); // 액터 체크 float fNowFrameActorContactDistanceSQ = FLT_MAX; DNVector(DnActorHandle) hVecList; float fMinDistance = FLT_MAX; float fDist = 0.f; // 히트 시그널 들고가는 프로젝타일과 폭발 히트시그널에서 같이 사용함. if( TRUE == bUseHitSignalArea || m_bOnCollisionCalled ) { m_bProcessingBombHitSignal = true; MatrixEx CrossTemp = m_Cross; CrossTemp.MoveLocalZAxis( m_HitStruct.vOffset->z ); CrossTemp.MoveLocalXAxis( m_HitStruct.vOffset->x ); CrossTemp.MoveLocalYAxis( m_HitStruct.vOffset->y ); EtVector3 vPos = CrossTemp.m_vPosition; float fDistance = max( m_HitStruct.fDistanceMax, (m_HitStruct.fHeightMax - m_HitStruct.fHeightMin) ); float fXZDistanceSQ = m_HitStruct.fDistanceMax; float fXZDistanceMinSQ = m_HitStruct.fDistanceMin; fXZDistanceSQ *= fXZDistanceSQ; fXZDistanceMinSQ *= fXZDistanceMinSQ; CDnActor::ScanActor( GetRoom(), vPos, fDistance, hVecList ); //#53454 꼭두각시 소환액터인 경우, 꼭두각시 주인?은 HitList에서 제외 시킨다. #if defined(PRE_FIX_61382) DNVector(DnActorHandle) hVecActorToApplyStateEffect; CDnActor::ExceptionHitList2(hVecList, m_Cross, GetShooterActor(), &m_HitStruct, hVecActorToApplyStateEffect, 1, fDistance, m_vPrevPos); #else CDnActor::ExceptionHitList(hVecList, m_Cross, GetShooterActor(), &m_HitStruct); #endif // PRE_FIX_61382 EtVector3 vDir; EtVector3 vZVec = m_Cross.m_vZAxis; if( m_HitStruct.fCenterAngle != 0.f ) { EtMatrix matRotate; EtMatrixRotationY( &matRotate, EtToRadian( m_HitStruct.fCenterAngle ) ); EtVec3TransformNormal( &vZVec, &vZVec, &matRotate ); #if defined(PRE_FIX_63356) CrossTemp.m_vZAxis = vZVec; #endif // PRE_FIX_63356 } SAABox Box; float fDot = 0.0f; bool bFirstHit = true; bool bCheckEndTime = false; #ifdef PRE_FIX_PIERCE_WITH_HIT_AREA if( m_LastHitSignalEndTime > LocalTime ) { if( m_bPierce ) { bCheckEndTime = true; } else { return false; } } #else if( m_LastHitSignalEndTime > LocalTime ) return false; #endif if( m_nLastHitSignalIndex != nSignalIndex ) { bFirstHit = true; m_nLastHitSignalIndex = nSignalIndex; } bool isFirstHitActor = true; int nHitCount = 0; bool isHitLimited = false; // Actor 체크 for( DWORD i=0; i::iterator iter = find( m_VecHittedActor.begin(), m_VecHittedActor.end(), hVecList[i] ); if( iter != m_VecHittedActor.end() ) { bFindHittedActor = true; if( bCheckEndTime ) continue; } } } #endif ////////////////////////////////////////////////////////////////////////// // Hit수 제한 //최대 Hit수가 설정되어 있고, Hit수가 최대 Hit수를 넘어 가면 멈춘다. isHitLimited = (m_HitParam.nHitLimitCount != 0 && nHitCount >= m_HitParam.nHitLimitCount); if (isHitLimited) break; ////////////////////////////////////////////////////////////////////////// if (false == m_HitStruct.isSelfCheck) { // 자기 자신은 체크하지 않음. if( m_hShooter == hVecList[i] ) continue; } #if defined(PRE_FIX_59238) //꼭두각시와 꼭두각시를 소환한 주인 액터는 동시 히트 되지 않아야 한다. if (IsHittable(hVecList[i]) == false) continue; #endif // PRE_FIX_59238 bool bSendRegist = false; if( !hVecList[i]->IsHittableSkill( m_ParentSkillInfo.iSkillID , bSendRegist ) ) { if( bSendRegist ) { hVecList[i]->SendAddSEFail( CDnStateBlow::ADD_FAIL_BY_IMMUNE , STATE_BLOW::BLOW_154 ); } continue; } if( !hVecList[i]->IsHittable( m_hShooter, LocalTime, &m_HitStruct, m_HitParam.iUniqueID ) ) continue; switch( hVecList[i]->GetHitCheckType() ) { case CDnActor::BoundingBox: { m_HitParam.vPosition = vPos; vDir = *hVecList[i]->GetPosition() - vPos; vDir.y = 0.f; hVecList[i]->GetBoundingBox( Box ); if( CDnActor::SquaredDistance( vPos, Box ) > fXZDistanceSQ ) continue; if( CDnActor::SquaredDistance( vPos, Box, false ) < fXZDistanceMinSQ ) continue; EtVec3Normalize( &vDir, &vDir ); fDot = EtVec3Dot( &vZVec, &vDir ); if( EtToDegree( acos( fDot ) ) > m_HitStruct.fAngle ) continue; if( Box.Min.y < vPos.y + m_HitStruct.fHeightMin && Box.Max.y < vPos.y + m_HitStruct.fHeightMin ) continue; if( Box.Min.y > vPos.y + m_HitStruct.fHeightMax && Box.Max.y > vPos.y + m_HitStruct.fHeightMax ) continue; fNowFrameActorContactDistanceSQ = EtVec3LengthSq( &EtVector3( vPos - m_vPrevPos ) ); if( fNowFrameActorContactDistanceSQ > fPropContactDistance ) continue; m_HitParam.vViewVec = -vDir; } break; case CDnActor::Collision: { SCollisionCapsule Capsule; SCollisionResponse CollisionResult; DNVector(SCollisionResponse) vCollisionResult; // Capsule.Segment.vOrigin = vPos; float fHeight = m_HitStruct.fHeightMax - m_HitStruct.fHeightMin; Capsule.Segment.vOrigin.y = Capsule.Segment.vOrigin.y - ( m_HitStruct.fHeightMin + ( fHeight / 2.f ) ); Capsule.Segment.vDirection = EtVector3( 0.f, fHeight / 2.f, 0.f ); Capsule.fRadius = m_HitStruct.fDistanceMax; EtVector3 vDestPos; if( hVecList[i]->GetObjectHandle()->CEtCollisionEntity::FindCapsuleCollision( Capsule, CollisionResult, &vCollisionResult ) == false ) continue; if( CollisionResult.pCollisionPrimitive ) { for( UINT k=0 ; kGetBoundingBox( Box ); if( Box.Min.y < vPos.y + m_HitStruct.fHeightMin && Box.Max.y < vPos.y + m_HitStruct.fHeightMin ) { vCollisionResult.erase( vCollisionResult.begin() + k ); k--; continue; } if( Box.Min.y > vPos.y + m_HitStruct.fHeightMax && Box.Max.y > vPos.y + m_HitStruct.fHeightMax ) { vCollisionResult.erase( vCollisionResult.begin() + k ); k--; continue; } DNVector(EtVector3) vPointList; vPointList.push_back( Box.GetCenter() ); #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL if( vCollisionResult[k].pCollisionPrimitive->Type == CT_BOX || vCollisionResult[k].pCollisionPrimitive->Type == CT_CAPSULE ) { #else if( vCollisionResult[k].pCollisionPrimitive->Type == CT_BOX ) { #endif // #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL Box.GetVertices( vPointList ); } bool bCheck = false; for( DWORD m=0; mCheckCollisionHitCondition(vPos, CrossTemp, vPointList[m], m_HitStruct.fAngle) == true ) { #else if( hVecList[i]->CheckCollisionHitCondition(vPos, m_Cross, vPointList[m], m_HitStruct.fAngle) == true ) { #endif // PRE_FIX_63356 bCheck = true; break; } } if( bCheck == false ) { vCollisionResult.erase( vCollisionResult.begin() + k ); k--; continue; } m_HitParam.vBoneIndex.push_back( hVecList[i]->GetObjectHandle()->GetParentBoneIndex( vCollisionResult[k].pCollisionPrimitive ) ); } } if( vCollisionResult.empty() ) continue; GetCenterPos( *CollisionResult.pCollisionPrimitive, vDestPos ); m_HitParam.vPosition = vDestPos; } else { ASSERT( 0 ); } if( m_HitStruct.fDistanceMin > 100.f ) { vCollisionResult.clear(); Capsule.fRadius = m_HitStruct.fDistanceMin; if( hVecList[i]->GetObjectHandle()->CEtCollisionEntity::FindCapsuleCollision( Capsule, CollisionResult, &vCollisionResult ) == true ) { if( CollisionResult.pCollisionPrimitive ) { for( UINT k=0 ; kGetBoundingBox( Box ); if( Box.Min.y < vPos.y + m_HitStruct.fHeightMin && Box.Max.y < vPos.y + m_HitStruct.fHeightMin ) { vCollisionResult.erase( vCollisionResult.begin() + k ); k--; continue; } if( Box.Min.y > vPos.y + m_HitStruct.fHeightMax && Box.Max.y > vPos.y + m_HitStruct.fHeightMax ) { vCollisionResult.erase( vCollisionResult.begin() + k ); k--; continue; } DNVector(EtVector3) vPointList; vPointList.push_back( Box.GetCenter() ); #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL if( vCollisionResult[k].pCollisionPrimitive->Type == CT_BOX || vCollisionResult[k].pCollisionPrimitive->Type == CT_CAPSULE ) { #else if( vCollisionResult[k].pCollisionPrimitive->Type == CT_BOX ) { #endif // #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL Box.GetVertices( vPointList ); } bool bCheck = false; for( DWORD m=0; mCheckCollisionHitCondition(vPos, CrossTemp, vPointList[m], m_HitStruct.fAngle) == false ) { #else if( hVecList[i]->CheckCollisionHitCondition(vPos, m_Cross, vPointList[m], m_HitStruct.fAngle) == false ) { #endif // PRE_FIX_63356 #else #if defined(PRE_FIX_63356) if( hVecList[i]->CheckCollisionHitCondition(vPos, CrossTemp, vPointList[m], m_HitStruct.fAngle) == true ) { #else if( hVecList[i]->CheckCollisionHitCondition(vPos, m_Cross, vPointList[m], m_HitStruct.fAngle) == true ) { #endif // PRE_FIX_63356 #endif // #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL bCheck = true; break; } } #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL if( bCheck == true ) { #else if( bCheck == false ) { #endif // #ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL vCollisionResult.erase( vCollisionResult.begin() + k ); k--; continue; } int nBoneIndex = hVecList[i]->GetObjectHandle()->GetParentBoneIndex( vCollisionResult[k].pCollisionPrimitive ); std::vector::iterator it = std::find( m_HitParam.vBoneIndex.begin(), m_HitParam.vBoneIndex.end(), nBoneIndex ); if( it != m_HitParam.vBoneIndex.end() ) { m_HitParam.vBoneIndex.erase( it ); } } } } } if( m_HitParam.vBoneIndex.empty() ) continue; } m_HitParam.vViewVec = vPos - vDestPos; fNowFrameActorContactDistanceSQ = EtVec3LengthSq( &m_HitParam.vViewVec ); if( fNowFrameActorContactDistanceSQ > fPropContactDistance ) continue; EtVec3Normalize( &m_HitParam.vViewVec, &m_HitParam.vViewVec ); } break; } m_HitParam.bFirstHit = bFirstHit; bFirstHit = false; bHitResult = true; bool bExistActionName = !m_HitParam.szActionName.empty(); if( bExistActionName ) { m_vHitPos = vPos; hResultProp.Identity(); } #if !defined( PRE_FIX_PROJECTILE_PREFIX_APPLY_POINT ) #if defined(PRE_ADD_PREFIX_SYSTE_RENEW) // HitSignal에서 스킬 여부 상관없이 접두사 스킬 발동 // 접두어 상태효과 무시하는 상태효과가 있으면 적용 안됨 // #40186 접미사? 발동 조건 변경 (데미지 비율이 0인 경우 발동 되지 않도록함.) if (m_HitParam.bFirstHit && m_HitStruct.fDamageProb != 0.0f && !m_hShooter->IsAppliedThisStateBlow(STATE_BLOW::BLOW_183) && isFirstHitActor) { m_hShooter->ProcessPrefixOffenceSkill_New(); isFirstHitActor = false; } #else ////////////////////////////////////////////////////////////////////////// // 접두어 공격용 스킬 발동 준비 작업.. // 발사체는 bFromProjectileSkill일 경우는 스킬에 의한 발사체... // 접두어 상태효과 무시하는 상태효과가 있으면 적용 안됨 [2011/03/23 semozz] if( (!m_HitParam.bFromProjectileSkill && !m_hShooter->IsAppliedThisStateBlow(STATE_BLOW::BLOW_183)) && m_HitParam.bFirstHit && isFirstHitActor) { OutputDebug("CDnProjectile::ProcessDamage 1 -> STE_Hit start %d current %d end %d\n", SignalStartTime, LocalTime, SignalEndTime); //여기서는 자신에게 적용 하는 상태 효과만 적용시키고, //맞는 녀석에게 적용해야할 상태 효과는 Target->OnDamage에서 처리 되도록한다.?? m_hShooter->ProcessPrefixOffenceSkill(1.0f); isFirstHitActor = false; } #endif // PRE_ADD_PREFIX_SYSTE_RENEW #endif // PRE_FIX_PROJECTILE_PREFIX_APPLY_POINT #ifdef PRE_FIX_PIERCE_WITH_HIT_AREA if( m_bPierce == true && bFindHittedActor == false ) m_VecHittedActor.push_back( hVecList[i] ); #endif // CheckAndApplyDamage 에서 액션 이름이 있고 없고를 구분해서 처리하고 있으므로 액션 이름 존재 여부 조건문 바깥에서 호출해도 된다. // #23510, #23591 이슈 때문에, 데미지는 들어가지 않더라도 상태효과는 부여되도록 수정합니다. CheckAndApplyDamage( hVecList[i], true, &nHitCount ); if( bHitResult ) { // [2010/12/02 semozz] // Hit 시그널 마지막 시간을 넣어 놓으면 마지막 프레임에 걸려서 한번 더 맞을 수 있다. // 그래서 Hit 처리가 되고 나서 m_LastHitSignalEndTime에 SignalEndTime + 1을 더 이상 Hit 처리 안되도록 한다. m_LastHitSignalEndTime = SignalEndTime + 1; if( hVecList[ i ]->IsDie() ) iKillCount++; } } #if defined(PRE_FIX_61382) //히트 영역에서 소환 액터와 주인 액터가 동시에 존재 할때 주인 액터는 히트 리스트에서 제외 된다. //상태효과는 적용되어야 하는데, 이 시점에서 상태효과를 적용한다. int nApplyStateEffectActorListSize = (int)hVecActorToApplyStateEffect.size(); for (int iIndex = 0; iIndex < nApplyStateEffectActorListSize; ++iIndex) { DnActorHandle hActor = hVecActorToApplyStateEffect[iIndex]; ApplySkillStateEffect(hActor); } #endif // PRE_FIX_61382 #if defined(PRE_ADD_50903) //데미지 분산 상태효과를 위한 최종 HitCount 설정. DnSkillHandle hParentSkill = GetParentSkill(); if (hParentSkill) hParentSkill->SetHitCountForVarianceDamage(nHitCount); #endif // PRE_ADD_50903 // 히트시그널 들고가는 발사체의 경우 프랍도 체크 // Note: 프랍체크는 상태효과만 있는 Heal 같은 것은 체크하지 않도록 합니다. HitAction 이 없는 것으로 구분. if( false == m_HitParam.szActionName.empty() ) { DNVector(CEtWorldProp*) pVecProp; CDnWorld* pWorld = CDnWorld::GetInstancePtr(GetRoom()); pWorld->ScanProp( vPos, fDistance, pVecProp ); for( DWORD i=0; iIsBrokenType() ) continue; if( !((CDnWorldBrokenProp*)pVecProp[i])->IsHittable( m_hShooter, LocalTime ) ) continue; pVecProp[i]->GetBoundingBox( Box ); if( CDnActor::SquaredDistance( vPos, Box ) > fXZDistanceSQ ) continue; if( CDnActor::SquaredDistance( vPos, Box, false ) < fXZDistanceMinSQ ) continue; vDir = ((CDnWorldProp*)pVecProp[i])->GetMatEx()->m_vPosition - vPos; EtVec3Normalize( &vDir, &vDir ); fDot = EtVec3Dot( &vZVec, &vDir ); if( EtToDegree( acos( fDot ) ) > m_HitStruct.fAngle ) continue; if( Box.Min.y < vPos.y + m_HitStruct.fHeightMin && Box.Max.y < vPos.y + m_HitStruct.fHeightMin ) continue; if( Box.Min.y > vPos.y + m_HitStruct.fHeightMax && Box.Max.y > vPos.y + m_HitStruct.fHeightMax ) continue; m_HitParam.vViewVec = -vDir; ((CDnWorldProp*)pVecProp[i])->OnDamage( m_hShooter, m_HitParam ); } } } else { size_t nCount = 0; nCount = CDnActor::ScanActor( GetRoom(), vCenter, fLength, hVecList ); //#53454 꼭두각시 소환액터인 경우, 꼭두각시 주인?은 HitList에서 제외 시킨다. #if defined(PRE_FIX_61382) DNVector(DnActorHandle) hVecActorToApplyStateEffect; nCount = CDnActor::ExceptionHitList2(hVecList, m_Cross, GetShooterActor(), &m_HitStruct, hVecActorToApplyStateEffect, 2, fLength, m_vPrevPos); #else nCount = CDnActor::ExceptionHitList(hVecList, m_Cross, GetShooterActor(), &m_HitStruct); #endif // PRE_FIX_61382 if( nCount > 0 ) { m_LastHitSignalEndTime = SignalEndTime; float fThickness = max( fabs( m_HitStruct.fHeightMin ), fabs( m_HitStruct.fHeightMax ) ); SSegment Segment; Segment.vOrigin = m_vPrevPos; Segment.vDirection = m_Cross.m_vZAxis * fLength; SCollisionCapsule Capsule; Capsule.Segment = Segment; Capsule.fRadius = fThickness; bool isFirstHitActor = true; int nHitCount = 0; bool isHitLimited = false; for( size_t i=0; i= m_HitParam.nHitLimitCount); if (isHitLimited) break; // Note: 느리게 가는 프로젝타일은 여러번 맞을 수 있어야 하기 때문에 이 부분은 뺍니다.. vector::iterator iter = find( m_VecHittedActor.begin(), m_VecHittedActor.end(), hVecList[i] ); if( !m_VecHittedActor.empty() ) { if( iter != m_VecHittedActor.end() ) continue; } #if defined(PRE_FIX_59238) //꼭두각시와 꼭두각시를 소환한 주인 액터는 동시 히트 되지 않아야 한다. if (IsHittable(hVecList[i]) == false) continue; #endif // PRE_FIX_59238 if( !hVecList[i]->IsHittable( m_hShooter, LocalTime, &m_HitStruct, m_HitParam.iUniqueID ) ) continue; if( !hVecList[i]->GetObjectHandle() ) continue; // 체인 라이트닝 발사체인 경우엔 체인 발사체를 쏜 액터가 다시 hit 되지 않도록 한다. // 체인 라이트닝을 사용했을 때만 hPrevAttacker 가 셋팅된다. if( m_ParentSkillInfo.hPrevAttacker == hVecList[i] ) continue; bool bResult = false; SCollisionResponse Response; DNVector(SCollisionResponse) vResponse; CDnActor::HitCheckTypeEnum eHitCheckType = hVecList[i]->GetHitCheckType(); switch( eHitCheckType ) { case CDnActor::BoundingBox: if( fThickness == 0.f ) bResult = hVecList[i]->GetObjectHandle()->FindSegmentCollision( Segment, Response ); else bResult = hVecList[i]->GetObjectHandle()->FindCapsuleCollision( Capsule, Response ); fNowFrameActorContactDistanceSQ = EtVec3LengthSq( &EtVector3( *hVecList[i]->GetPosition() - m_vPrevPos ) ); if( fNowFrameActorContactDistanceSQ > fPropContactDistance ) continue; break; case CDnActor::Collision: { int nParentBoneIndex = -1; EtVector3 vDestPos; if( fThickness == 0.f ) { bResult = hVecList[i]->GetObjectHandle()->FindSegmentCollision( Segment, Response, &vResponse ); } else { bResult = hVecList[i]->GetObjectHandle()->FindCapsuleCollision( Capsule, Response, &vResponse ); } if( bResult ) { fNowFrameActorContactDistanceSQ = EtVec3LengthSq( &EtVector3( Segment.vDirection * Response.fContactTime ) ); if( fNowFrameActorContactDistanceSQ > fPropContactDistance ) continue; for( UINT k=0 ; kGetObjectHandle()->GetParentBoneIndex( vResponse[k].pCollisionPrimitive ) ); } } } break; } if( bResult ) { EtVector3 vHitPos = Segment.vOrigin + (Segment.vDirection * Response.fContactTime); fDist = EtVec3Length( &( *hVecList[i]->GetPosition() - m_Cross.m_vPosition ) ); float fLength2 = EtVec3Length( &( *hVecList[i]->GetPosition() - m_vStartPos ) ); SSphere Sphere; hVecList[i]->GetBoundingSphere( Sphere ); // 이 타입은 fallgravity 상태가 된 순간부터 추가 사거리 다 되면 바로 삭제. if( RangeFallGravity == m_DestroyOrbitType ) { float fAdditionalRange = CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::RangeFallGravityAdditionalProjectileRange ); float fRangeFallGravityLength = float(GetWeaponLength()) * fAdditionalRange; if( CDnActor::Collision == eHitCheckType ) { // 캡슐인 경우 캡슐의 radius 도 감안해 준다. if( 0.0f < fThickness ) { fRangeFallGravityLength += Capsule.fRadius; if( CollisionType::CT_CAPSULE == Response.pCollisionPrimitive->Type ) { fRangeFallGravityLength += static_cast(Response.pCollisionPrimitive)->fRadius; } } float fWeaponLengthSQ = fRangeFallGravityLength*fRangeFallGravityLength; EtVector3 vShootToHitPosDist = vHitPos - m_vStartPos; float fShootToHitPosDistSQ = EtVec3LengthSq( &vShootToHitPosDist ); if( fWeaponLengthSQ < fShootToHitPosDistSQ ) continue; } else if( fLength2 > fRangeFallGravityLength + ( Sphere.fRadius / 2.f ) ) { continue; } } else { float fWeaponLength = float(GetWeaponLength()); if( CDnActor::Collision == eHitCheckType ) { // 캡슐인 경우 캡슐의 radius 도 감안해 준다. if( 0.0f < fThickness ) { fWeaponLength += Capsule.fRadius; if( CollisionType::CT_CAPSULE == Response.pCollisionPrimitive->Type ) { fWeaponLength += static_cast(Response.pCollisionPrimitive)->fRadius; } } EtVector3 vShootToHitPosDist = vHitPos - m_vStartPos; float fShootToHitPosDistSQ = EtVec3LengthSq( &vShootToHitPosDist ); if( fWeaponLength*fWeaponLength < fShootToHitPosDistSQ ) continue; } else if( fLength2 > fWeaponLength + ( Sphere.fRadius / 2.f ) ) { continue; } } if( fDist < fMinDistance ) { fMinDistance = fDist; // 일직선 상으로 먼저 도달하는 것에 먼저 맞음. 프랍이 먼저 맞는다면 액터는 hit 처리하지 않는다. m_vHitPos = vHitPos; fNowFrameActorContactDistanceSQ = EtVec3LengthSq( &(Segment.vDirection * Response.fContactTime) ); if( Response.fContactTime > fPropContactTime ) continue; // 피격된 위치로 발사체의 위치를 셋팅해준다. // 이렇게 해야 폭발 액션의 hit 시그널 연산을 할 때 정확하게 체크가 된다. if( false == m_bPierce ) m_Cross.m_vPosition = m_vHitPos; } #if !defined( PRE_FIX_PROJECTILE_PREFIX_APPLY_POINT ) #if defined(PRE_ADD_PREFIX_SYSTE_RENEW) // HitSignal에서 스킬 여부 상관없이 접두사 스킬 발동 // 접두어 상태효과 무시하는 상태효과가 있으면 적용 안됨 // #40186 접미사? 발동 조건 변경 (데미지 비율이 0인 경우 발동 되지 않도록함.) if (m_HitParam.bFirstHit && m_HitStruct.fDamageProb != 0.0f && !m_hShooter->IsAppliedThisStateBlow(STATE_BLOW::BLOW_183) && isFirstHitActor) { m_hShooter->ProcessPrefixOffenceSkill_New(); isFirstHitActor = false; } #else ////////////////////////////////////////////////////////////////////////// // 접두어 공격용 스킬 발동 준비 작업.. // 평타일때만 접두어 시스템 공격스킬 발동 // 발사체는 bFromProjectileSkill일 경우는 스킬에 의한 발사체... // 접두어 상태효과 무시하는 상태효과가 있으면 적용 안됨 [2011/03/23 semozz] if( (!m_HitParam.bFromProjectileSkill && !m_hShooter->IsAppliedThisStateBlow(STATE_BLOW::BLOW_183)) && m_HitParam.bFirstHit && isFirstHitActor) { OutputDebug("CDnProjectile::ProcessDamage 2 -> STE_Hit start %d current %d end %d\n", SignalStartTime, LocalTime, SignalEndTime); //여기서는 자신에게 적용 하는 상태 효과만 적용시키고, //맞는 녀석에게 적용해야할 상태 효과는 Target->OnDamage에서 처리 되도록한다.?? m_hShooter->ProcessPrefixOffenceSkill(1.0f); isFirstHitActor = false; } ////////////////////////////////////////////////////////////////////////// #endif // PRE_ADD_PREFIX_SYSTE_RENEW #endif m_VecHittedActor.push_back( hVecList[i] ); CheckAndApplyDamage( hVecList[i], true, &nHitCount ); bHitResult = true; if( hVecList[ i ]->IsDie() ) iKillCount++; if(false == m_bPierce) return bHitResult; } } #if defined(PRE_FIX_61382) //히트 영역에서 소환 액터와 주인 액터가 동시에 존재 할때 주인 액터는 히트 리스트에서 제외 된다. //상태효과는 적용되어야 하는데, 이 시점에서 상태효과를 적용한다. int nApplyStateEffectActorListSize = (int)hVecActorToApplyStateEffect.size(); for (int iIndex = 0; iIndex < nApplyStateEffectActorListSize; ++iIndex) { DnActorHandle hActor = hVecActorToApplyStateEffect[iIndex]; ApplySkillStateEffect(hActor); } #endif // PRE_FIX_61382 #if defined(PRE_ADD_50903) //데미지 분산 상태효과를 위한 최종 HitCount 설정. DnSkillHandle hParentSkill = GetParentSkill(); if (hParentSkill) hParentSkill->SetHitCountForVarianceDamage(nHitCount); #endif // PRE_ADD_50903 } } // MakeHitParam 에서 쓰는 m_vHitPos가 액터 체크할때 업데이트 되므로 프랍 처리는 액터 처리 끝난 후 여기서. if( hResultProp ) { switch( hResultProp->GetPropType() ) { case PTE_Static: { #if defined(PRE_FIX_52329) //IgnoreHitType 설정이 0이 아닌 경우 Valid체크는 하지 않도록 한다.. if (m_nIgnoreHitType == 0) { if( m_bValidDamage ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; } m_bStick = true; } #endif } break; case PTE_Action: case PTE_Trap: if( m_bValidDamage ) { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; } m_bStick = true; break; case PTE_Broken: case PTE_BrokenDamage: case PTE_HitMoveDamageBroken: case PTE_ShooterBroken: case PTE_BuffBroken: { if( !m_bValidDamage ) break; // 프랍까지 거리가 사거리보다 길면 맞으면 안됨. #23322 float fLengthToPropSQ = EtVec3LengthSq(&EtVector3(m_vStartPos - hResultProp->GetMatEx()->m_vPosition)); // 이 타입은 fallgravity 상태가 된 순간부터 추가 사거리 다 되면 바로 삭제. float fWeaponLength = 0.0f; if( RangeFallGravity == m_DestroyOrbitType ) { float fAdditionalRange = CGlobalWeightTable::GetInstance().GetValue( CGlobalWeightTable::RangeFallGravityAdditionalProjectileRange ); fWeaponLength = float(GetWeaponLength()) * fAdditionalRange; } else { fWeaponLength = (float)GetWeaponLength(); } float fWeaponLengthSQ = fWeaponLength * fWeaponLength; if( fWeaponLengthSQ < fLengthToPropSQ ) // 제곱기준으로 fPropDistance 값 들어있음. break; if( ((CDnWorldBrokenProp*)hResultProp.GetPointer())->IsHittable( m_hShooter, LocalTime ) ) { CDnDamageBase::SHitParam HitParam; MakeHitParam( HitParam ); hResultProp->OnDamage( m_hShooter, HitParam ); } else { m_bValidDamage = false; m_InvalidLocalTime = LocalTime; m_bStick = true; } } break; } #if defined(PRE_FIX_52329) //IgnoreHitType 설정이 0이 아닌 경우 Valid체크는 하지 않도록 한다.. if (m_nIgnoreHitType == 0) { OnCollisionWithProp(); } #endif } } if( 2 <= iKillCount && m_hShooter->IsPlayerActor() ) { CDnPlayerActor* pPlayerActor = static_cast(m_hShooter.GetPointer()); CDNUserSession* pSession = pPlayerActor->GetUserSession(); pSession->GetEventSystem()->OnEvent( EventSystem::OnKillMonster, 1, EventSystem::GenocideCount, iKillCount ); } return bHitResult; } void CDnProjectile::CheckAndApplyDamage( DnActorHandle hActor, bool bValidDamageGoOn/* = true*/, int *pHitCount /*= NULL*/ ) { if( !m_hShooter || !hActor ) return; // 옵션에 따라 추적중인 액터에게는 hit 되지 않도록 한다. 디폴트는 안맞는 거임. if( false == m_bTraceHitActorHittable ) { if( m_hTraceActor == hActor ) return; } if( !m_HitParam.szActionName.empty() && !hActor->IsDie() ) { CDnDamageBase::SHitParam HitParam; MakeHitParam( HitParam ); // 차져 상태효과로 발사된 발사체라면 액터에 셋팅하고 OnDamage 호출하고 바로 풀어줌. // 차져 상태효과에서 나간 발사체는 콤보 계산에서 뺀다. if( m_bFromCharger && m_hShooter ) m_hShooter->SetAllowCombo( false ); hActor->OnDamage( m_hShooter, HitParam, &m_HitStruct ); #if defined(PRE_FIX_59238) AddHittedActor(hActor); #endif // PRE_FIX_59238 if (pHitCount) (*pHitCount)++; if( m_bFromCharger && m_hShooter ) m_hShooter->SetAllowCombo( true ); bValidDamageGoOn = SetTraceActor( hActor, bValidDamageGoOn ); OnCollisionWithActor(); // Note: OnCollision() 에서 폭발 데미지를 처리하기 위해서 ValidDamage 를 다시 true로 켜주지만 // 이 함수 호출 시에 bValidDamageGoOn 을 끄고 호출한 경우엔 폭발 데미지를 감아하지 않으므로 ValidDamage 를 꺼준다. if( false == bValidDamageGoOn ) m_bValidDamage = false; } //발사체 스킬 상태효과 적용.. ApplySkillStateEffect(hActor); // 관통형일때만,, 최대 히트수가 0인 경우엔 원래 처리되던대로 끝까지 계속 맞게 된다. if( m_bPierce && m_nMaxHitCount != 0 ) { //m_nMaxHitCount--; map::iterator iter = m_mapMaxHitCount.find(hActor.GetPointer() ); if( m_mapMaxHitCount.end() == iter ) m_mapMaxHitCount[ hActor.GetPointer() ] = 0; else { ++(iter->second); if( m_nMaxHitCount-1 == iter->second ) { m_bValidDamage = false; m_InvalidLocalTime = m_LocalTime; // 클라는 알 수가 없기 때문에 서버에서 패킷을 보내준다. BYTE pBuffer[ 32 ]; CPacketCompressStream Stream( pBuffer, 32 ); Stream.Write( &m_dwUniqueID, sizeof(DWORD) ); m_hShooter->Send( eActor::SC_INVALID_PROJECTILE, &Stream ); } } } } void CDnProjectile::Process( LOCAL_TIME LocalTime, float fDelta ) { if( m_CreateLocalTime == 0 ) m_CreateLocalTime = LocalTime; // 한기 2009.7.22 // 프로젝타일 생성된 후의 지나간 시간 // 프로젝타일 궤적 관련.. (#4851) m_fElapsedTime = float(LocalTime - m_CreateLocalTime) * 0.001f; m_bHitSignalProcessed = false; ProcessOrbit( LocalTime, fDelta ); CDnWeapon::Update( m_Cross ); CDnWeapon::Process( LocalTime, fDelta ); if( 0 != m_InvalidLocalTime ) ProcessDestroyOrbit( LocalTime, fDelta ); ProcessValid( LocalTime, fDelta ); m_vPrevPos = m_Cross.m_vPosition; m_bFirstProcess = false; // 301번 외딴섬에서 플레이어의 위치가 틀어졌을 때 칵퉤꽃 발사체의 방향 벡터가 0, 0, 0 으로 셋팅되어 사라지지 않는 경우 방어코드 넣음. if( EtVec3LengthSq( &m_Cross.m_vZAxis ) < 0.001f ) { // Projectile 타입의 발사체는 속도가 0 으로 셋팅되어있을 수 있다. 실시간으로 속도를 계산하기 때문에~ if( Projectile != m_OrbitType ) SetDestroy(); } } void CDnProjectile::MakeHitParam( CDnDamageBase::SHitParam &HitParam ) { HitParam = m_HitParam; HitParam.RemainTime = m_LastHitSignalEndTime; HitParam.hHitter = m_hShooter; HitParam.hWeapon = GetMySmartPtr(); HitParam.bFirstHit = true; HitParam.vPosition = m_vHitPos; // 영역이 있는 히트 시그널을 사용하는 폭발하는 발사체는 이미 hit 시그널 중심점을 기준으로 제대로된 뷰 벡터가 들어가 있음. if( !m_bProcessingBombHitSignal ) HitParam.vViewVec = -m_Cross.m_vZAxis; HitParam.iUniqueID = m_iHitUniqueID; if( -1.0f != m_fHitApplyPercent ) HitParam.fDamage = m_fHitApplyPercent; } void CDnProjectile::OnSignal( SignalTypeEnum Type, void *pPtr, LOCAL_TIME LocalTime, LOCAL_TIME SignalStartTime, LOCAL_TIME SignalEndTime, int nSignalIndex ) { switch( Type ) { case STE_Hit: { //OutputDebug( "STE_Hit\n" ); if (m_hShooter && m_hShooter->ProcessIgnoreHitSignal() == true) break; HitStruct *pStruct = (HitStruct *)pPtr; #ifdef PRE_FIX_MEMOPT_SIGNALH CopyShallow_HitStruct(m_HitStruct, pStruct); #else m_HitStruct = *pStruct; #endif m_HitParam.szActionName = pStruct->szTargetHitAction; m_HitParam.fDamage = pStruct->fDamageProb * 0.01f; m_HitParam.fDurability = pStruct->fDurabilityDamageProb * 0.01f; m_HitParam.vVelocity = *pStruct->vVelocity; m_HitParam.vResistance = *pStruct->vResistance; m_HitParam.fStiffProb = pStruct->fStiffProb * 0.01f; m_HitParam.nSkillSuperAmmorIndex = pStruct->nApplySuperAmmorIndex - 1; m_HitParam.nSkillSuperAmmorDamage = pStruct->nApplySuperAmmorDamage; m_HitParam.nDamageType = pStruct->nDamageType; m_HitParam.DistanceType = (pStruct->nDistanceType == 0) ? CDnDamageBase::DistanceTypeEnum::Melee : CDnDamageBase::DistanceTypeEnum::Range; m_HitParam.cAttackType = (char)pStruct->nAttackType; if( ElementEnum_Amount != m_eForceHitElement ) { m_HitParam.HasElement = m_eForceHitElement; } else { m_HitParam.HasElement = ( m_hShooter ) ? m_hShooter->CalcHitElementType( ( pStruct->bUseSkillApplyWeaponElement == TRUE ) ? true : false, m_ParentSkillInfo.eSkillElement, true ) : CDnActorState::ElementEnum_Amount; } m_HitParam.bIgnoreCanHit = ( pStruct->bIgnoreCanHit == TRUE ); m_HitParam.bIgnoreParring = ( pStruct->bIgnoreParring == TRUE ); m_HitParam.nHitLimitCount = pStruct->nHitLimitCount; if( m_bValidDamage ) { bool bHitResult = ProcessDamage( LocalTime, 0.0f, SignalStartTime, SignalEndTime, nSignalIndex, pStruct->bUseHitAreaOnProjectile ); if( bHitResult ) { // #59118 발사체도 g_Lua_BeHitSkill 적용한다. if( m_hShooter && m_hShooter->IsMonsterActor() ) { CDnMonsterActor* pMonster = static_cast(m_hShooter.GetPointer()); #ifdef PRE_FIX_PROPMON_PROJ // 프랍 몬스터의 경우 MonsterActor이면서도 AI를 가지지 않는 경우가 있음. (#59952) if (pMonster) { MAAiBase* pCurBase = pMonster->GetAIBase(); if (pCurBase) pCurBase->OnHitFinish( LocalTime, &m_HitStruct ); } #else pMonster->GetAIBase()->OnHitFinish( LocalTime, &m_HitStruct ); #endif } } } m_bHitSignalProcessed = true; // 폭발시 히트 시그널이 뒤에 있는 경우가 있으므로 뒤에 있는 케이스가 있어서 히트 시그널 시작 시에 플래그 켜줌. // 안그러면 m_bValidDamage가 false 가 되어 폭발 처리가 안됨. if( m_bOnCollisionCalled && m_bHitActionStarted ) m_bBombHitSignalStarted = true; } break; case STE_SummonMonster: { SummonMonsterStruct* pStruct = (SummonMonsterStruct *)pPtr; CDnGameTask* pGameTask = static_cast(CTaskManager::GetInstancePtr(GetRoom())->GetTask( "GameTask" )); if( pGameTask ) { if( m_hShooter ) { // 내부적으로 값을 바꿔서 사용하기 때문에 반드시 복사해서 사용한다. #ifdef PRE_FIX_MEMOPT_SIGNALH SummonMonsterStruct Struct; CopyShallow_SummonMonsterStruct(Struct, pStruct); #else SummonMonsterStruct Struct = *pStruct; #endif // 167번 소환 몬스터 스킬레벨 강제 셋팅 상태효과 처리. /////////////////// if( m_hShooter->IsPlayerActor() ) { if( 0 < m_iSummonMonsterForceSkillLevel ) Struct.nForceSkillLevel = m_iSummonMonsterForceSkillLevel; } ////////////////////////////////////////////////////////////////////////// // 발사체에서 몬스터 소환시 발사체의 위치로 대체시켜 생성시켜 준다. #18315 // #36873 네비게이션 메시나 attribute 가 무시되므로 보정된 위치로.. // #37991 이전 위치 기준으로 네비 메시가 맞는지 확인해보고 아니라면 현재 캐릭터가 서 있는 곳으로 소환시켜준다. MatrixEx CrossRevisionedPos; if( true == INSTANCE(CDnWorld).IsOnNavigationMesh( m_vPrevPos ) ) { CrossRevisionedPos = m_Cross; INSTANCE(CDnWorld).RevisionPosByNaviMesh( CrossRevisionedPos, m_vPrevPos ); } else { CrossRevisionedPos.m_vPosition = m_hShooter->GetMatEx()->m_vPosition; if( Struct.PositionCheck == TRUE ) break; } EtVector3 vTemp = *(m_hShooter->GetPosition());; m_hShooter->SetPosition( CrossRevisionedPos.m_vPosition ); pGameTask->RequestSummonMonster( m_hShooter, &Struct, false, m_ParentSkillInfo.iSkillID ); m_hShooter->SetPosition( vTemp ); } } break; } case STE_Gravity: { if( !m_hShooter ) break; GravityStruct *pStruct = (GravityStruct *)pPtr; EtVector3 vPos = m_Cross.m_vPosition + *pStruct->vOffset; DNVector(DnActorHandle) hVecList; CDnActor::ScanActor( GetRoom(), vPos, pStruct->fDistanceMax, hVecList ); for( DWORD i=0; iIsDie() ) continue; if( hVecList[i]->GetWeight() == 0.f ) continue; if( hVecList[i]->IsNpcActor() ) continue; if( hVecList[i]->ProcessIgnoreGravitySignal() ) continue; bool bHittable = false; switch( pStruct->nTargetType ) { case 0: // Enemy if( hVecList[i]->GetTeam() == m_hShooter->GetTeam() ) break; bHittable = true; break; case 1: // Friend if( hVecList[i]->GetTeam() != m_hShooter->GetTeam() ) break; bHittable = true; break; case 2: // All bHittable = true; break; } if( !bHittable ) continue; MAMovementBase *pMovement = hVecList[i]->GetMovement(); if( !pMovement ) continue; EtVector3 vDir = *hVecList[i]->GetPosition() - vPos; float fDistance = EtVec3Length( &vDir ); float fDistance2 = EtVec3Length( &EtVector3( vDir.x, 0.f, vDir.z ) ); EtVec3Normalize( &vDir, &vDir ); float fTemp = pStruct->fMinVelocity + ( ( (pStruct->fMaxVelocity - pStruct->fMinVelocity) / pStruct->fDistanceMax ) * ( pStruct->fDistanceMax - fDistance ) ); EtVector3 vVel; if( pMovement ) { pMovement->Look( EtVec3toVec2(vDir) ); pMovement->SetVelocityZ( -fTemp ); pMovement->SetResistanceZ( fTemp * 2.f ); } } } break; } CDnWeapon::OnSignal( Type, pPtr, LocalTime, SignalStartTime, SignalEndTime, nSignalIndex ); } void CDnProjectile::OnDamageSuccess( DnActorHandle hDamagedActor, CDnDamageBase::SHitParam &HitParam ) { // 여기서 피어싱이나 기타 등등의 타입에 따라서 없엘건지 따위를 셋팅해주게 하자. // 폭발하는 거 맞았을 땐 Destroy 처리 해주지 않는다. if( false == m_bPierce && /*m_bHasHitAction &&*/ !m_bOnCollisionCalled ) { ShowWeapon( false ); m_bValidDamage = false; m_InvalidLocalTime = m_LocalTime; m_DestroyOrbitType = FallGravity; m_nDestroyOrbitTimeGap = 1000; m_bStick = true; } // 발사체를 쏜 액터에게 발사체가 명중했음을 알린다. if( m_hShooter ) { m_hShooter->OnHitProjectile( m_LocalTime, hDamagedActor, HitParam ); } } void CDnProjectile::AddStateEffect( CDnSkill::StateEffectStruct &Struct ) { #if defined(PRE_FIX_59336) //242번 상태효과만 따로 리스트로 관리 하도록 한다. if (Struct.nID == STATE_BLOW::BLOW_242) { m_ComboLimitStateEffectList.push_back(Struct); } else { m_VecStateEffectList.push_back( Struct ); } #else m_VecStateEffectList.push_back( Struct ); #endif // PRE_FIX_59336 } void CDnProjectile::OnCollisionWithActor( void ) { if( false == m_bPierce && false == m_bOnCollisionCalled ) { if( m_bHasHitAction ) { #if defined(PRE_ADD_55295) //#55295 //히트 액션으로 변경될때 이 플래그가 설정 되어 있으면 방향벡터를 평면과 평행? 하도록 변경한다. if (m_bHitActionVectorInit) ChangeProjectileRotation(); #endif // PRE_ADD_55295 SetActionQueue( HIT_ACTION_NAME, 0, 0.0f ); m_bOnCollisionCalled = true; if( false == m_bValidDamage ) m_bValidDamage = true; // hit 액션 길이만큼 시간 뽑아둠 CEtActionBase::ActionElementStruct* pActionElement = GetElement( HIT_ACTION_NAME ); m_nDestroyOrbitTimeGap = int(((float)pActionElement->dwLength / CDnActionBase::m_fFPS) * 1000.0f); // 새로운 폭발 hit 시그널이 돌아갈 것이므로 발사체 맞은 첫 액터 기준 end signal 시간은 초기화 시킨다. 안그러면 폭발 데미지 안들어감. m_LastHitSignalEndTime = 0; } } } void CDnProjectile::OnCollisionWithGround( void ) { if( false == m_bOnCollisionCalled ) { if( m_bHasHitAction ) { #if defined(PRE_ADD_55295) //#55295 //히트 액션으로 변경될때 이 플래그가 설정 되어 있으면 방향벡터를 평면과 평행? 하도록 변경한다. if (m_bHitActionVectorInit) ChangeProjectileRotation(); #endif // PRE_ADD_55295 SetActionQueue( HIT_ACTION_NAME, 0, 0.0f ); m_bOnCollisionCalled = true; if( false == m_bValidDamage ) m_bValidDamage = true; // hit 액션 길이만큼 시간 뽑아둠 CEtActionBase::ActionElementStruct* pActionElement = GetElement( HIT_ACTION_NAME ); m_nDestroyOrbitTimeGap = int(((float)pActionElement->dwLength / CDnActionBase::m_fFPS) * 1000.0f); // TargetPositionType 이면 목적 위치 곧바로 셋팅 if( 2 == m_TargetType ) m_Cross.SetPosition( m_vTargetPosition ); // 새로운 폭발 hit 시그널이 돌아갈 것이므로 발사체 맞은 첫 액터 기준 end signal 시간은 초기화 시킨다. 안그러면 폭발 데미지 안들어감. m_LastHitSignalEndTime = 0; } } } void CDnProjectile::OnCollisionWithProp( void ) { if( false == m_bOnCollisionCalled ) { if( m_bHasHitAction ) { #if defined(PRE_ADD_55295) //#55295 //히트 액션으로 변경될때 이 플래그가 설정 되어 있으면 방향벡터를 평면과 평행? 하도록 변경한다. if (m_bHitActionVectorInit) ChangeProjectileRotation(); #endif // PRE_ADD_55295 // 이미 hit 액션을 실행하고 있다면 액션을 다시 실행시키지 않는다. (#21384) SetActionQueue( HIT_ACTION_NAME, 0, 0.0f ); m_bOnCollisionCalled = true; if( false == m_bValidDamage ) m_bValidDamage = true; // hit 액션 길이만큼 시간 뽑아둠 CEtActionBase::ActionElementStruct* pActionElement = GetElement( HIT_ACTION_NAME ); m_nDestroyOrbitTimeGap = int(((float)pActionElement->dwLength / CDnActionBase::m_fFPS) * 1000.0f); // 새로운 폭발 hit 시그널이 돌아갈 것이므로 발사체 맞은 첫 액터 기준 end signal 시간은 초기화 시킨다. 안그러면 폭발 데미지 안들어감. m_LastHitSignalEndTime = 0; } else { // 프랍에 부딪혔다면 hit 액션이 없거나 hit 액션은 있되 hit signal 이 없는 발사체는 곧바로 삭제되도록 처리. SetDestroy(); } } } void CDnProjectile::OnChangedNextActionToHit( void ) { if( false == m_bOnCollisionCalled ) { if( m_bHasHitAction ) { m_bOnCollisionCalled = true; if( false == m_bValidDamage ) m_bValidDamage = true; // hit 액션 길이만큼 시간 뽑아둠 CEtActionBase::ActionElementStruct* pActionElement = GetElement( HIT_ACTION_NAME ); m_nDestroyOrbitTimeGap = int(((float)pActionElement->dwLength / CDnActionBase::m_fFPS) * 1000.0f); // TargetPositionType 이면 목적 위치 곧바로 셋팅 if( 2 == m_TargetType ) m_Cross.SetPosition( m_vTargetPosition ); } } } void CDnProjectile::OnChangeAction( const char *szPrevAction ) { if( m_bHasHitAction ) { if( NULL != szPrevAction && strcmp( SHOOT_ACTION_NAME, szPrevAction ) ) { m_bHitActionStarted = true; } } // 히트된 대상이 또 히트되는것을 방지하기위해서 m_LastHitSignalEndTime이 존재하는데 , 발사체의 액션자체가 변할경우 이전 시그널 기준의 EndTime 이 설정되기때문에 // 액션이 변하고 새로운 Hit가 들어와도 이전값을 사용해서 오작동이 일어나게된다 // 그래서 액션이 바뀌면 m_LastHitSignalEndTime을 초기화 해주도록 하자 , 명확하게 하려면 발사체 액션마다 Hit된 대상의 이전정보를 기억해서 맞은 대상이 또 맞지않도록 // 설정을 해야하지만 일단은 짜여진 대로 설정하도록 합니다. m_LastHitSignalEndTime = 0; } void CDnProjectile::OnFinishAction( const char* szPrevAction, LOCAL_TIME time ) { // 폭발 액션 종료시엔 곧바로 삭제. if( m_bHasHitAction ) if( 0 == strcmp( HIT_ACTION_NAME, szPrevAction ) ) SetDestroy(); } // NOTE: CrossHair 프로젝타일인 경우 서버 쪽에서는 프로젝타일 관련 정보를 클라에서 생성된 것을 그대로 받기 때문에 (CS_PROJECTILE) // 여기서 생성되지 않습니다. // 발사체 시그널을 직접 받아서 생성. CDnProjectile *CDnProjectile::CreateProjectile( CMultiRoom *pRoom, DnActorHandle hShooter, MatrixEx &LocalCross, ProjectileStruct *pStruct, EtVector3* pForceTargetPos/*=NULL*/, DnActorHandle hForceTarget/*=CDnActor::Identity()*/, EtVector3* pForceDir/* = NULL*/ ) { MatrixEx CrossResult = LocalCross; CrossResult.m_vPosition += CrossResult.m_vXAxis * ( pStruct->vOffset->x * hShooter->GetScale()->x ); CrossResult.m_vPosition += CrossResult.m_vYAxis * ( pStruct->vOffset->y * hShooter->GetScale()->y ); CrossResult.m_vPosition += CrossResult.m_vZAxis * ( pStruct->vOffset->z * hShooter->GetScale()->z ); CDnProjectile *pProjectile = NULL; float fVelocityMultiply = 1.0f; // 시그널 구조체에 있는 방향값은 길이와 관계 없이 방향만 의미가 있고, // CrossResult.m_vZAxis 를 정규화시키고 속도는 오로지 시그널 구조체에서 정해진 fSpeed 로 결정된다. switch( pStruct->nTargetType ) { case TargetTypeEnum::CrossHair: { SAFE_DELETE( pProjectile ); return NULL; } break; case TargetTypeEnum::Direction: { pProjectile = new CDnProjectile( pRoom, hShooter ); EtVector3 vDirection = *pStruct->vDirection; bool bAILook = false; // MonsterActor if( hShooter && hShooter->IsMonsterActor() ) { CDnMonsterActor *pMonster = static_cast(hShooter.GetPointer()); if( pMonster && pMonster->bIsAILook() ) { // STE_ProjectileTargetPosition 은 TargetPosition 인 경우에만 사용하는 시그널이다. if( !pMonster->bIsProjectileTargetSignal() ) { vDirection = EtVec2toVec3( *pMonster->GetAIBase()->GetAILook() ); bAILook = true; } } } EtVector3 vTemp; if( bAILook ) vTemp = vDirection; else EtVec3TransformNormal( &vTemp, &vDirection, LocalCross ); if( FALSE == pStruct->bNormalizeDirectionVector ) { fVelocityMultiply = EtVec3Length( &vTemp ); CrossResult.m_vZAxis = vTemp / fVelocityMultiply; } else { fVelocityMultiply = 1.0f; EtVec3Normalize( &vTemp, &vTemp ); CrossResult.m_vZAxis = vTemp; } CrossResult.MakeUpCartesianByZAxis(); } break; case TargetTypeEnum::TargetPosition: case TargetTypeEnum::Target: case TargetTypeEnum::Shooter: { if( hShooter->IsPlayerActor() ) { SAFE_DELETE( pProjectile ); return NULL; } else if( hShooter->IsMonsterActor() ) { pProjectile = new CDnProjectile( pRoom, hShooter ); CDnMonsterActor *pMonster = static_cast(hShooter.GetPointer()); // 발사 대상이 Shooter 자기 자신인 경우 DnActorHandle hTarget; // ReserveProjectileTarget 시그널로 미리 정해두었던 타겟이 있다면 받아온다. // 없으면 원래 얻어오는 AI 에게서 얻어옴. hTarget = pMonster->GetReservedProjectileTarget(); if( !hTarget ) hTarget = pMonster->GetAggroTarget(); EtVector3 vTargetPos; bool bTarget = true; // 강제타겟설정 if( pForceTargetPos ) vTargetPos = *pForceTargetPos; else if( pStruct->nTargetType == TargetTypeEnum::TargetPosition && pMonster->bIsProjectileTargetSignal() ) vTargetPos = *pMonster->GetAIBase()->GetProjectileTarget(); else if( hTarget ) vTargetPos = *hTarget->GetPosition(); else bTarget = false; if( bTarget ) { // TargetPosRandomValue if( pStruct->nTargetPosRandomValue > 0 ) { _srand( pRoom, pProjectile->GetUniqueID() ); vTargetPos.x += cos( EtToRadian( _rand(pRoom)%360 ) ) * pStruct->nTargetPosRandomValue; vTargetPos.z += sin( EtToRadian( _rand(pRoom)%360 ) ) * pStruct->nTargetPosRandomValue; } float fHeight = CDnWorld::GetInstance(pRoom).GetHeight( vTargetPos ); // #28665 대포 몹이 아닌 경우에만. 계산된 대포의 목적지는 건드리지 않는다. #ifdef PRE_FIX_PARTSMONSTER_AI_TARGETTING if( hTarget && fHeight < vTargetPos.y && (pMonster->GetActorType() != CDnActorState::Cannon) && hTarget->IsPartsMonsterActor() == false ) vTargetPos -= hTarget->GetMatEx()->m_vZAxis * ( vTargetPos.y - fHeight ); #else if( hTarget && fHeight < vTargetPos.y && (pMonster->GetActorType() != CDnActorState::Cannon) ) vTargetPos -= hTarget->GetMatEx()->m_vZAxis * ( vTargetPos.y - fHeight ); #endif } else { vTargetPos = *hShooter->GetPosition() + ( hShooter->GetMatEx()->m_vZAxis * pMonster->GetThreatRange() ); } // TargetPosition if( pStruct->nTargetType == TargetTypeEnum::TargetPosition ) { pProjectile->SetTargetPosition( vTargetPos ); CrossResult.m_vZAxis = vTargetPos - CrossResult.GetPosition(); EtVec3Normalize( &CrossResult.m_vZAxis, &CrossResult.m_vZAxis ); } // Target if( pStruct->nTargetType == TargetTypeEnum::Target ) { // 강제타겟설정 if( hForceTarget ) hTarget = hForceTarget; if( !hTarget ) hTarget = hShooter; if (pStruct->nTargetStateIndex != 0) { vTargetPos = *hTarget->GetPosition(); pProjectile->SetTargetPosition(vTargetPos); } pProjectile->SetTargetActor( hTarget ); } else if( pStruct->nTargetType == TargetTypeEnum::Shooter ) { hTarget = hShooter; vTargetPos = *hTarget->GetPosition(); pProjectile->SetTargetActor( hTarget ); pProjectile->SetTargetPosition( vTargetPos ); } //목표 지점으로 날아 가는 발사체인 경우 현재 발사체 위치에서 목표 지점으로의 방향 벡터 변경 해야 한다. //일단 낙인용 발사체에서만 동작 하도록 수정한다. if (pStruct->nTargetStateIndex != 0) { CrossResult.m_vZAxis = vTargetPos - CrossResult.GetPosition(); EtVec3Normalize( &CrossResult.m_vZAxis, &CrossResult.m_vZAxis ); } else if( EtVec3LengthSq( pStruct->vDirection ) == 0.f ) { _ASSERT( !(pStruct->nOrbitType == Projectile && pStruct->VelocityType == Accell && pStruct->nTargetType == 2) && "중력 가속도가 적용되는 Projectile/TargetPosition 타입의 발사체는 반드시 방향을 정해줘야 합니다." ); CrossResult.m_vZAxis = vTargetPos - CrossResult.GetPosition(); EtVec3Normalize( &CrossResult.m_vZAxis, &CrossResult.m_vZAxis ); } else if( !( (pStruct->nOrbitType == OrbitTypeEnum::Linear) && (pStruct->nTargetType == TargetTypeEnum::TargetPosition) ) ) { // 대포에서 유저가 정한 방향으로 쏘아야할 경우 해당 방향으로 셋팅해준다. if( NULL == pForceDir ) { EtVector3 vTemp; EtVec3TransformNormal( &vTemp, pStruct->vDirection, LocalCross ); if( pStruct->bUseTargetPositionDir ) { EtVector3 vTargetPosDir = vTargetPos - CrossResult.m_vPosition; EtVec3Normalize( &vTargetPosDir, &vTargetPosDir ); MatrixEx CrossTargetPosDir; CrossTargetPosDir.m_vZAxis = vTargetPosDir; CrossTargetPosDir.MakeUpCartesianByZAxis(); EtVector3 vToolDefinedDir = *pStruct->vDirection; EtVector3 vDirZ( vToolDefinedDir.x, 0.0f, vToolDefinedDir.z ); EtVec3Normalize( &vDirZ, &vDirZ ); EtVec3Normalize( &vToolDefinedDir, &vToolDefinedDir ); float fDot = EtVec3Dot( &vToolDefinedDir, &vDirZ ); float fRotX = EtAcos( fDot ); CrossTargetPosDir.RotatePitch( EtToDegree( -fRotX ) ); vTemp = CrossTargetPosDir.m_vZAxis; } if( FALSE == pStruct->bNormalizeDirectionVector ) { fVelocityMultiply = EtVec3Length( &vTemp ); CrossResult.m_vZAxis = vTemp / fVelocityMultiply; } else { fVelocityMultiply = 1.0f; EtVec3Normalize( &vTemp, &vTemp ); CrossResult.m_vZAxis = vTemp; } } else { EtVector3 vTemp; EtVec3TransformNormal( &vTemp, pForceDir, LocalCross ); EtVec3Normalize( &CrossResult.m_vZAxis, &vTemp ); pProjectile->SetForceDir( *pForceDir ); } } CrossResult.MakeUpCartesianByZAxis(); } } break; case TargetTypeEnum::DestPosition: { pProjectile = new CDnProjectile( pRoom, hShooter ); EtVector3 vDestOffset = *(pStruct->vDestPosition); if( pStruct->nTargetPosRandomValue > 0 ) { _srand( pRoom, pProjectile->GetUniqueID() ); vDestOffset.x += cos( EtToRadian( _rand(pRoom)%360 ) ) * pStruct->nTargetPosRandomValue; vDestOffset.z += sin( EtToRadian( _rand(pRoom)%360 ) ) * pStruct->nTargetPosRandomValue; } // 방향벡터가 툴에서 설정되어있다면 dest position 이지만 그쪽 방향으로 돌려줌.. EtVector3 vDirection; if( EtVec3LengthSq( pStruct->vDirection ) != 0.f ) { vDirection = vDestOffset; EtVec3Normalize( &vDirection, &vDirection ); MatrixEx CrossDestDir; CrossDestDir.m_vZAxis = vDirection; CrossDestDir.MakeUpCartesianByZAxis(); EtVector3 vToolDefinedDir = *pStruct->vDirection; EtVector3 vDirZ( vToolDefinedDir.x, 0.0f, vToolDefinedDir.z ); EtVec3Normalize( &vDirZ, &vDirZ ); EtVec3Normalize( &vToolDefinedDir, &vToolDefinedDir ); float fDot = EtVec3Dot( &vToolDefinedDir, &vDirZ ); float fRotX = EtAcos( fDot ); CrossDestDir.RotatePitch( EtToDegree( -fRotX ) ); vDirection = CrossDestDir.m_vZAxis; } else { // 방향 벡터가 설정되어있지 않다면 dest position 쪽으로 방향을 잡아준다. vDirection = vDestOffset - (*pStruct->vOffset); EtVec3Normalize( &vDirection, &vDirection ); } EtVector3 vWorldDirection; EtVec3TransformNormal( &vWorldDirection, &vDirection, LocalCross ); CrossResult.m_vZAxis = vWorldDirection; EtVec3Normalize( &CrossResult.m_vZAxis, &CrossResult.m_vZAxis ); CrossResult.MakeUpCartesianByZAxis(); // 이미 DestPosition 을 향해 방향이 정해진 상태이므로 목적지는 로컬 Z축으로 거리값만큼 이동시키면된다. float fDestLength = EtVec3Length( &vDestOffset ); MatrixEx crossDestPosition = CrossResult; crossDestPosition.m_vPosition += CrossResult.m_vZAxis * fDestLength; crossDestPosition.m_vPosition.y = CDnWorld::GetInstance( hShooter->GetRoom() ).GetHeightWithProp( crossDestPosition.m_vPosition ); pProjectile->SetTargetPosition( crossDestPosition.m_vPosition ); } break; } if( pStruct->nWeaponTableID > 0 ) { if( pStruct->nProjectileIndex != -1 ) { DnWeaponHandle hWeapon = CDnWeapon::GetSmartPtr( (CMultiRoom*)g_pGameServerManager->GetRootRoom(), pStruct->nProjectileIndex ); if( hWeapon ) *(CDnWeapon*)pProjectile = *hWeapon.GetPointer(); } else { if (pProjectile == NULL) { _DANGER_POINT(); return NULL; } pProjectile->CDnWeapon::Initialize( pStruct->nWeaponTableID, 0 ); pProjectile->CreateObject(); } } else if( hShooter->GetWeapon(1) ) { *(CDnWeapon*)pProjectile = *hShooter->GetWeapon(1); } else { SAFE_DELETE( pProjectile ); return NULL; } pProjectile->SetWeaponType( (WeaponTypeEnum)( pProjectile->GetWeaponType() | WeaponTypeEnum::Projectile ) ); pProjectile->Initialize( CrossResult, (CDnProjectile::OrbitTypeEnum)pStruct->nOrbitType, (CDnProjectile::DestroyOrbitTypeEnum)pStruct->nDestroyOrbitType, (CDnProjectile::TargetTypeEnum)pStruct->nTargetType); pProjectile->SetSpeed( pStruct->fSpeed * fVelocityMultiply ); pProjectile->SetResistance( pStruct->fResistance ); pProjectile->SetValidTime( pStruct->nValidTime ); if( pProjectile->GetOrbitType() == OrbitTypeEnum::Homing || pProjectile->GetOrbitType() == OrbitTypeEnum::TerrainHoming || pProjectile->GetOrbitType() == OrbitTypeEnum::Projectile ) { pProjectile->SetVelocityType( (VelocityTypeEnum)pStruct->VelocityType ); } int nLength = pProjectile->GetWeaponLength(); if( pStruct->bIncludeMainWeaponLength ) { DnWeaponHandle hWeapon = hShooter->GetWeapon(0); if ( hWeapon ) nLength += hWeapon->GetWeaponLength(); } if( hShooter->IsProcessSkill() ) { nLength += hShooter->GetProcessSkill()->GetIncreaseRange(); } if( hShooter->IsEnabledToggleSkill() ) { nLength += hShooter->GetEnabledToggleSkill()->GetIncreaseRange(); } pProjectile->SetWeaponLength( nLength ); pProjectile->SetPierce( ( pStruct->bPierce == TRUE ) ? true : false ); pProjectile->SetMaxHitCount( pStruct->nMaxHitCount ); pProjectile->SetProjectileOrbitRotateZ( pStruct->fProjectileOrbitRotateZ ); pProjectile->SetTraceHitTarget( (pStruct->bTraceHitTarget == TRUE) ? true : false, (pStruct->bTraceHitActorHittable == TRUE) ? true : false ); #if defined(PRE_FIX_52329) pProjectile->SetIgnoreHitType(pStruct->nIgnoreHitType); #endif // PRE_FIX_52329 #if defined(PRE_ADD_55295) pProjectile->SetHitActionVectorInit(pStruct->bHitActionVectorInit == TRUE ? true : false); #endif // PRE_ADD_55295 pProjectile->PostInitialize(); return pProjectile; } // 클라로부터 패킷을 받아서 생성하는 발사체 객체. // 각 클라의 LocalPlayer 들로부터 패킷들이 올라옴. CDnProjectile* CDnProjectile::CreatePlayerProjectileFromClientPacket( DnActorHandle hShooter, const BYTE* pPacket ) { if( false == hShooter->IsPlayerActor() ) return NULL; CPacketCompressStream Stream( const_cast(pPacket), 128 ); CDnPlayerActor* pPlayerActor = static_cast(hShooter.GetPointer()); if( pPlayerActor->IsInvalidPlayerChecker() ) return NULL; bool bActorAttachWeapon; int nWeaponTableID, nWeaponLength = 0; DWORD dwUniqueID,dwGap; float fSpeed = 0.f; float fResistance = 0.f; int nValidTime = 0; int VelocityType = 0; EtVector3 vTargetPos; DWORD dwTargetUniqueID; CDnProjectile::OrbitTypeEnum OrbitType; CDnProjectile::DestroyOrbitTypeEnum DestroyType; CDnProjectile::TargetTypeEnum TargetType; MatrixEx Cross; EtVector3 vDirection( 0.0f, 0.0f, 0.0f ); int nActionIndex; int nMaxHitCount = 0; int nSignalIndex = -1; float fProjectileOrbitRotateZ = 0.0f; ProjectileStruct* pProjectileStruct = NULL; Stream.Read( &dwGap, sizeof(DWORD) ); Stream.Read( &bActorAttachWeapon, sizeof(bool) ); Stream.Read( &dwUniqueID, sizeof(DWORD) ); if( !bActorAttachWeapon ) { Stream.Read( &nWeaponTableID, sizeof(int) ); } Stream.Read( &Cross.m_vPosition, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &Cross.m_vXAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Stream.Read( &Cross.m_vYAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); Stream.Read( &Cross.m_vZAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); #ifdef PRE_MOD_PROJECTILE_HACK char cShooterType; DWORD dwParentShooterUniqueID = -1; DWORD dwShooterUniqueID = -1; INT64 nShooterSerialID = -1; int nShooterActionIndex; int nShooterSignalIndex; bool bSendSerialID; DnWeaponHandle hParentProjectileWeapon; Stream.Read( &cShooterType, sizeof(char) ); Stream.Read( &dwParentShooterUniqueID, sizeof(DWORD) ); switch( cShooterType ) { case 0: dwShooterUniqueID = dwParentShooterUniqueID; break; case 1: { Stream.Read( &bSendSerialID, sizeof(bool) ); if( bSendSerialID ) Stream.Read( &nShooterSerialID, sizeof(INT64) ); else Stream.Read( &dwShooterUniqueID, sizeof(DWORD) ); } break; } Stream.Read( &nShooterActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Stream.Read( &nShooterSignalIndex, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); ProjectileStruct *pShooterStruct = NULL; nWeaponLength = 0; switch( cShooterType ) { case 0: { DnActorHandle hHandle = CDnActor::FindActorFromUniqueID( pPlayerActor->GetRoom(), dwShooterUniqueID ); if( hHandle ) { if( hHandle->GetCurrentActionIndex() == nShooterActionIndex ) { bool bCheckStandAction = CDnChangeStandActionBlow::CheckUsableAction( pPlayerActor->GetActorHandle(), true, pPlayerActor->GetCurrentAction() ); if( bCheckStandAction == false ) { pPlayerActor->ReportInvalidAction(); return NULL; } CEtActionSignal *pSignal = hHandle->GetSignal( nShooterActionIndex, nShooterSignalIndex ); if( pSignal && pSignal->GetSignalIndex() == STE_Projectile ) { pShooterStruct = static_cast( pSignal->GetData() ); } else if( hHandle->GetCustomActionIndex() > 0 ) { CEtActionSignal *pCustomActionSignal = hHandle->GetSignal( hHandle->GetCustomActionIndex(), nShooterSignalIndex ); if( pCustomActionSignal && pCustomActionSignal->GetSignalIndex() == STE_Projectile ) { pShooterStruct = static_cast( pCustomActionSignal->GetData() ); } } } else { // 클라와 서버가 액션 인덱스가 다름. 핵으로 처리. return NULL; } } } break; case 1: { DnWeaponHandle hHandle; if( bSendSerialID ) { for( DWORD i=0; i<2; i++ ) { // FindItemFromSerialID 를 만들어야 하지만 일단은 필요없어서 EquipWeapon 에서만 찾습니다. DnWeaponHandle hWeapon = pPlayerActor->GetWeapon(i); if( hWeapon && hWeapon->GetSerialID() == nShooterSerialID ) { hHandle = hWeapon; break; } hWeapon = pPlayerActor->GetCashWeapon(i); if( hWeapon && hWeapon->GetSerialID() == nShooterSerialID ) { hHandle = hWeapon; break; } } } else { hHandle = CDnWeapon::FindWeaponFromUniqueIDAndShooterUniqueID( pPlayerActor->GetRoom(), dwShooterUniqueID, dwParentShooterUniqueID ); if( hHandle ) { hParentProjectileWeapon = hHandle; // nWeaponLength += hHandle->GetWeaponLength(); } } if( hHandle ) { CEtActionSignal *pSignal = hHandle->GetSignal( nShooterActionIndex, nShooterSignalIndex ); if( pSignal && pSignal->GetSignalIndex() == STE_Projectile ) { pShooterStruct = static_cast( pSignal->GetData() ); } } } break; } if( !pShooterStruct ) return NULL; pProjectileStruct = pShooterStruct; OrbitType = (CDnProjectile::OrbitTypeEnum)pShooterStruct->nOrbitType; DestroyType = (CDnProjectile::DestroyOrbitTypeEnum)pShooterStruct->nDestroyOrbitType; TargetType = (CDnProjectile::TargetTypeEnum)pShooterStruct->nTargetType; nValidTime = pShooterStruct->nValidTime; fProjectileOrbitRotateZ = pShooterStruct->fProjectileOrbitRotateZ; #ifdef PRE_ADD_PROJECTILE_RANDOM_WEAPON // 랜덤 발사체 관련 체크 if( pShooterStruct->RandomWeaponParam && strlen( pShooterStruct->RandomWeaponParam ) > 0 ) { std::vector vecWeaponID; vecWeaponID.push_back( pShooterStruct->nWeaponTableID ); std::vector tokens; TokenizeA( pShooterStruct->RandomWeaponParam, tokens, "/" ); if( tokens.size() > 1 ) { for( int i=1; i( tokens.size() ); i++ ) { std::vector tokensparam; TokenizeA( tokens[i].c_str(), tokensparam, ";" ); if( tokensparam.size() > 0 ) { vecWeaponID.push_back( atoi( tokensparam[0].c_str() ) ); } tokensparam.clear(); } } bool bUsableWeaponTableID = false; for( int i=0; i( vecWeaponID.size() ); i++ ) { if( vecWeaponID[i] == nWeaponTableID ) { bUsableWeaponTableID = true; break; } } tokens.clear(); vecWeaponID.clear(); if( !bUsableWeaponTableID ) // 사용할 수 없는 WeaponID 임! return NULL; } #endif // PRE_ADD_PROJECTILE_RANDOM_WEAPON #else Stream.Read( &OrbitType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &DestroyType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &TargetType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); Stream.Read( &nValidTime, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); Stream.Read( &nWeaponLength, sizeof(int) ); Stream.Read( &fProjectileOrbitRotateZ, sizeof(float) ); #endif int nTargetPartsIndex = -1; int nTargetBoneIndex = -1; Stream.Read(&nTargetPartsIndex, sizeof(int)); Stream.Read(&nTargetBoneIndex, sizeof(int)); switch( TargetType ) { case CDnProjectile::CrossHair: case CDnProjectile::Direction: break; case CDnProjectile::TargetPosition: case CDnProjectile::Target: case CDnProjectile::DestPosition: case CDnProjectile::Shooter: Stream.Read( &vTargetPos, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); Stream.Read( &dwTargetUniqueID, sizeof(DWORD), CPacketCompressStream::NOCOMPRESS ); break; } Stream.Read( &fSpeed, sizeof(float) ); #ifdef PRE_MOD_PROJECTILE_HACK bool bPierce = ( pShooterStruct->bPierce == TRUE ); nMaxHitCount = pShooterStruct->nMaxHitCount; bool bTraceHitTarget = ( pShooterStruct->bTraceHitTarget == TRUE ); bool bTraceHitActorHittable = (pShooterStruct->bTraceHitActorHittable == TRUE); switch( OrbitType ) { case CDnProjectile::Linear: case CDnProjectile::TerrainLinear: break; case CDnProjectile::Projectile: VelocityType = pShooterStruct->VelocityType; break; case CDnProjectile::Homing: case CDnProjectile::TerrainHoming: VelocityType = pShooterStruct->VelocityType; break; case CDnProjectile::Acceleration: fResistance = pShooterStruct->fResistance; break; } Stream.Read( &nActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); if( false == pPlayerActor->IsIgnoreSkillCoolTime() ) { if( nActionIndex != -1 && nActionIndex != pPlayerActor->GetCurrentActionIndex() ) { // 핵쓴것입니다. //break; return NULL; } } nSignalIndex = nShooterSignalIndex; #else bool bPierce = false; Stream.Read( &bPierce, sizeof(bool) ); if( bPierce ) Stream.Read( &nMaxHitCount, sizeof(int) ); bool bTraceHitTarget = false; bool bTraceHitActorHittable = false; Stream.Read( &bTraceHitTarget, sizeof(bool) ); Stream.Read( &bTraceHitActorHittable, sizeof(bool) ); switch( OrbitType ) { case CDnProjectile::Linear: case CDnProjectile::TerrainLinear: break; case CDnProjectile::Projectile: Stream.Read( &VelocityType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); break; case CDnProjectile::Homing: case CDnProjectile::TerrainHoming: Stream.Read( &VelocityType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); break; case CDnProjectile::Acceleration: Stream.Read( &fResistance, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 1.f ); break; } Stream.Read( &nActionIndex, sizeof(int) ); if( false == pPlayerActor->IsIgnoreSkillCoolTime() ) { if( nActionIndex != -1 && nActionIndex != pPlayerActor->GetCurrentActionIndex() ) { return NULL; } } // Note 한기: 체인 공격 상태효과를 위해 시그널 struct 를 찾을 수 있는 index 가 필요함. Stream.Read( &nSignalIndex, sizeof(int) ); // Note 한기: projectile 시그널 struct 를 찾는다. // 스킬일 때만 ActionIndex가 유효함. if( -1 != nSignalIndex && -1 != nActionIndex ) { CEtActionSignal *pSignal = pPlayerActor->GetSignal( nActionIndex, nSignalIndex ); if( pSignal && pSignal->GetSignalIndex() == STE_Projectile ) { pProjectileStruct = static_cast( pSignal->GetData() ); } } #endif #ifdef PRE_MOD_PROJECTILE_HACK #else // 보우 마스터의 레볼루셔너리 발리스타 같은 발사체어서 발사체 쏘는 스킬에서 최초로 나간 발사체에서 // 능력치를 이전하기 위한 정보... DWORD dwParentID = 0; Stream.Read( &dwParentID, sizeof(DWORD) ); DWORD dwParentShooterUniqueID = 0; Stream.Read( &dwParentShooterUniqueID, sizeof(DWORD) ); #endif #if defined(PRE_FIX_52329) int nIgnoreHitType = 0; Stream.Read(&nIgnoreHitType, sizeof(nIgnoreHitType)); #endif // PRE_FIX_52329 #if defined(PRE_ADD_55295) bool bHitActionVectorInit = false; Stream.Read(&bHitActionVectorInit, sizeof(bHitActionVectorInit)); #endif // PRE_ADD_55295 pPlayerActor->GetPlayerSpeedHackChecker()->OnSyncDatumGap( dwGap ); CDnProjectile* pParentProjectile = NULL; DnSkillHandle hParentSkill; bool bFromParentProjectile = false; #ifndef PRE_MOD_PROJECTILE_HACK DnWeaponHandle hParentProjectileWeapon = CDnWeapon::FindWeaponFromUniqueIDAndShooterUniqueID( pPlayerActor->GetRoom(), dwParentID, dwParentShooterUniqueID ); #endif if( hParentProjectileWeapon ) { // 발사체에서 발사체를 쏘는 경우 클라이언트에서 패킷이 이렇게 온다. if( hParentProjectileWeapon->GetWeaponType() == CDnWeapon::Projectile ) { pParentProjectile = static_cast(hParentProjectileWeapon.GetPointer()); hParentSkill = pParentProjectile->GetParentSkill(); if( hParentSkill ) // 발사체에서 발사체를 쏘는 경우의 스킬만 골라서.. { pPlayerActor->AddReservedProjectileCount(); bFromParentProjectile = true; } } } // 발사체에서 발사체를 쏘는 경우는 체크에서 제외한다. // 이유는, 발사체 쏘는 위치를 핵에서 서버로 보내더라도 실시간으로 생성되는 부모 발사체의 id 를 같이 // 보내주지 않으면 바로 위의 루틴에서 걸리기 때문에 발사체에서 발사체를 쏘는 것으로 인정되지 않기 때문이다. // 우선 일반 공격인 경우에만 막자. if( (false == bFromParentProjectile) && (-1 == nSignalIndex || -1 == nActionIndex) ) { // X, Z 평면 기준으로만 체크한다. 높이까지 체크하는 것은 추후에 필요하면 추가. EtVector2 vShooterXZPos( hShooter->GetPosition()->x, hShooter->GetPosition()->z ); EtVector2 vProjectileShootPosXZ( Cross.m_vPosition.x, Cross.m_vPosition.z ); EtVector2 vDist = vProjectileShootPosXZ - vShooterXZPos; float fDistanceSQ = EtVec2LengthSq( &vDist ); //#37327 데몰리션 발사체 경우 100을 넘게 Offset값이 설정된 경우가 있음. //일단 넉넉하게 최대 값 200으로 체크 하도록 수정(200*200 = 40000) if( 40000.0f < fDistanceSQ ) { // 발사체 시작 위치를 임의대로 조작해서 보낸 핵으로 판단. #ifndef _FINAL_BUILD OutputDebug( "발사체 발사 위치 거리 체크에 걸려 핵으로 판단.\n" ); #endif // #ifndef _FINAL_BUILD return NULL; } } bool bValidProjectile = true; //낙인을 위한 발사체인 경우 발사체 시그널 수 보다 많은 발사체가 발사된다. //낙인용 발사체가 아닌경우만 발사체 체크 하도록 수정함. if (pShooterStruct->nTargetStateIndex == 0) bValidProjectile = pPlayerActor->UseAndCheckAvailProjectileCount(); if( false == bValidProjectile ) return NULL; // 현재 타이밍에 맞는 발사체인지 체크 // 발사체에서 발사체를 쏘는 특수한 경우는 제외. if( false == bFromParentProjectile ) { bValidProjectile = true; //낙인을 위한 발사체인 경우 발사체 시그널 수 보다 많은 발사체가 발사된다. //낙인용 발사체가 아닌경우만 발사체 체크 하도록 수정함. if (pShooterStruct->nTargetStateIndex == 0) bValidProjectile = pPlayerActor->CheckProjectileSignalFrameTerm(); if( false == bValidProjectile ) { #ifndef _FINAL_BUILD OutputDebug( "CDnPlayerActor::CheckProjectileSignalFrameTerm(): 현재 타이밍에 올 수 없는 발사체 요청 시그널. 핵으로 판단.\n" ); #endif // #ifndef _FINAL_BUILD return NULL; } } CDnProjectile *pProjectile = new CDnProjectile( pPlayerActor->GetRoom(), hShooter ); pProjectile->SetUniqueID( dwUniqueID ); pProjectile->SetPierce( bPierce ); pProjectile->SetMaxHitCount( nMaxHitCount ); pProjectile->SetTargetPartsIndex(nTargetPartsIndex, nTargetBoneIndex); if( bActorAttachWeapon ) { CDnWeapon *pWeapon = pPlayerActor->GetWeapon(1); if( pWeapon ) { *(CDnWeapon*)pProjectile = *pWeapon; } } else { if( pProjectileStruct && pProjectileStruct->nProjectileIndex != -1 ) { DnWeaponHandle hLocalParentProjectileWeapon = CDnWeapon::GetSmartPtr( (CMultiRoom*)g_pGameServerManager->GetRootRoom(), pProjectileStruct->nProjectileIndex ); if( hLocalParentProjectileWeapon ) *(CDnWeapon*)pProjectile = *hLocalParentProjectileWeapon.GetPointer(); } else { // 짱깨에서 일반 공격으로 무기 ID 바꿔서 보내는 경우 체크. // 발사체에서 발사체 쏘는 특수한 경우는 제외. if( false == bFromParentProjectile ) { if( false == pPlayerActor->CheckAndEraseWeaponIDUsingProjectileSignal( nWeaponTableID ) ) { // TODO: 핵이다! 접속 끊어버렸으면 좋겠는데.. #ifndef _FINAL_BUILD OutputDebug( "CDnProjectile::CreatePlayerProjectileFromClientPacket(): 현재 액션에 있는 발사체 시그널에서 사용하지 않는 무기 테이블 인덱스. 핵입니다.\n" ); #endif // #ifndef _FINAL_BUILD return NULL; } } pProjectile->CDnWeapon::Initialize( nWeaponTableID, 0 ); pProjectile->CDnWeapon::CreateObject(); } } #ifdef PRE_MOD_PROJECTILE_HACK nWeaponLength = pProjectile->GetWeaponLength(); if( pShooterStruct->bIncludeMainWeaponLength && hShooter->GetWeapon(0) ) nWeaponLength += hShooter->GetWeapon(0)->GetWeaponLength(); if( hShooter->IsProcessSkill() ) nWeaponLength += hShooter->GetProcessSkill()->GetIncreaseRange(); if( hShooter->IsEnabledToggleSkill() ) nWeaponLength += hShooter->GetEnabledToggleSkill()->GetIncreaseRange(); #endif pProjectile->SetWeaponLength( nWeaponLength ); pProjectile->SetWeaponType( (CDnWeapon::WeaponTypeEnum)( pProjectile->GetWeaponType() | CDnWeapon::Projectile ) ); pProjectile->SetSpeed( fSpeed ); switch( OrbitType ) { case CDnProjectile::Acceleration: pProjectile->SetResistance( fResistance ); break; } switch( TargetType ) { case CDnProjectile::TargetPosition: case CDnProjectile::Target: case CDnProjectile::DestPosition: pProjectile->SetTargetPosition( vTargetPos ); pProjectile->SetTargetActor( CDnActor::FindActorFromUniqueID( pPlayerActor->GetRoom(), dwTargetUniqueID ), false ); ////////////////////////////////////////////////////////////////////////// //목표 지점으로 날아 가는 발사체인 경우 현재 발사체 위치에서 목표 지점으로의 방향 벡터 변경 해야 한다. //일단 낙인용 발사체에서만 동작 하도록 수정한다. if (pShooterStruct->nTargetStateIndex != 0) { Cross.m_vZAxis = vTargetPos - Cross.GetPosition(); EtVec3Normalize( &Cross.m_vZAxis, &Cross.m_vZAxis ); } ////////////////////////////////////////////////////////////////////////// break; } pProjectile->Initialize( Cross, OrbitType, DestroyType, TargetType ); pProjectile->SetValidTime( nValidTime ); pProjectile->SetVelocityType( (CDnProjectile::VelocityTypeEnum)VelocityType ); pProjectile->SetProjectileOrbitRotateZ( fProjectileOrbitRotateZ ); pProjectile->SetTraceHitTarget( bTraceHitTarget, bTraceHitActorHittable ); // Note: 클라이언트에서 스킬을 썼을 때만 nActionIndex 가 셋팅되어 패킷으로 날라옴. // pProjectileStruct 가 NULL 인 경우엔 클라와 서버의 nSignalIndex 가 일치하지 않아서임. // 즉, 서버/클라간의 action 파일 리소스가 다르다는 의미. if( pProjectileStruct && -1 != nSignalIndex && -1 != nActionIndex ) pPlayerActor->OnProjectile( pProjectile, pProjectileStruct, Cross, nSignalIndex ); if( bFromParentProjectile ) { pProjectile->SetShooterStateSnapshot( pParentProjectile->GetShooterStateSnapshot() ); pProjectile->SetParentSkill( hParentSkill ); #ifdef PRE_ADD_PROJECTILE_SE_INFO pProjectile->SetShooterStateBlow( pParentProjectile->GetShooterStateBlow() ); #endif //발사체에서 발사체를 쏘는 경우 부모 발사체에 있던 상태효과들을 등록한다.. pProjectile->ApplyParentProjectile(pParentProjectile); //발사체 -> 발사체 발사 할때 스킬에 의한 발사체인지 부모 발사체에서 정보를 받아서 설정 해줘야 함. pProjectile->FromSkill(pParentProjectile->IsFromSkill()); #if defined(PRE_FIX_65287) pProjectile->SetShooterFinalDamageRate(pParentProjectile->GetShooterFinalDamageRate()); #endif // PRE_FIX_65287 } else { bool bApplyStateBlowInfo = pProjectileStruct->bApplyStateBlowInfo == TRUE; #if defined( PRE_FIX_PROJECTILE_PREFIX_APPLY_POINT ) if( hShooter && hShooter->IsPlayerActor() ) { if( hShooter->IsAppliedThisStateBlow(STATE_BLOW::BLOW_183) == false ) { hShooter->ProcessPrefixOffenceSkill_New(); if( hShooter->IsApplyPrefixOffenceSkill() ) bApplyStateBlowInfo = true; } } #endif // 스킬이던 일반 평타이던 플레이어는 능력치 셋팅 필요함. (#17829) boost::shared_ptr pActorStateSnapshot( new CDnState ); *pActorStateSnapshot = *static_cast( pPlayerActor ); pProjectile->SetShooterStateSnapshot( pActorStateSnapshot ); #ifdef PRE_ADD_PROJECTILE_SE_INFO if( bApplyStateBlowInfo == true ) { boost::shared_ptr pActorStateBlow = boost::shared_ptr(new CDnStateBlow(pPlayerActor->GetMySmartPtr())); pActorStateBlow->MakeCloneStateBlowList( pPlayerActor->GetStateBlow()->GetStateBlowList() ); pProjectile->SetShooterStateBlow( pActorStateBlow ); } #endif #if defined(PRE_FIX_65287) float fFinalDamageRate = 0.0f; if (pPlayerActor && pPlayerActor->IsAppliedThisStateBlow(STATE_BLOW::BLOW_050)) { DNVector(DnBlowHandle) vlhBlows; pPlayerActor->GatherAppliedStateBlowByBlowIndex( STATE_BLOW::BLOW_050, vlhBlows ); int iNumBlow = (int)vlhBlows.size(); for( int i = 0; i < iNumBlow; ++i ) { fFinalDamageRate += vlhBlows[i]->GetFloatValue(); } } pProjectile->SetShooterFinalDamageRate(fFinalDamageRate); #endif // PRE_FIX_65287 // #30571 현재 하고 있는 스킬 액션에서 발사된 발사체에서만 호출해준다. // else 위 구문의 발사체에서 발사체를 쏘는 경우엔 추후에 발사체에서 발사체가 // 발사될 때 클라에서 패킷이 또 오므로 여기서 구분해주어서 호출해줘야 한다. hShooter->OnSkillProjectile( pProjectile ); } #if defined(PRE_FIX_52329) pProjectile->SetIgnoreHitType(nIgnoreHitType); #endif // PRE_FIX_52329 #if defined(PRE_ADD_55295) pProjectile->SetHitActionVectorInit(bHitActionVectorInit); #endif // PRE_FIX_52329 pProjectile->PostInitialize(); return pProjectile; } // 생성된 발사체 객체에서 패킷을 만든다. // 클라이언트와 완전히 동일한 루틴이어야 함. (이 파일 자체는 클라/서버 분리 되어있으므로 주의) CPacketCompressStream* CDnProjectile::GetPacketStream( void ) { if( !m_hShooter ) return NULL; // 처음 요청 들어온 것이라 객체라 없다면 패킷 스트림 만들어 줌. // 한번 만든 이후엔 발사체 객체의 내용이 바뀌어도 반영되지 않으므로 주의. // 현재 Create 된 후 생성된 발사체의 내용을 바꾸는 경우는 없다. if( NULL == m_pPacketStream ) { m_pPacketBuffer = new char[ PROJECTILE_PACKET_BUFFER_SIZE ]; m_pPacketStream = new CPacketCompressStream( m_pPacketBuffer, PROJECTILE_PACKET_BUFFER_SIZE*sizeof(char) ); bool bActorAttachWeapon = false; int VelocityType = 0; int nValidTime = 0; float fValue; if( m_hShooter->GetWeapon(1) && this->GetClassID() == m_hShooter->GetWeapon(1)->GetClassID() ) bActorAttachWeapon = true; DWORD dwGap = 0; m_pPacketStream->Write( &dwGap, sizeof(DWORD) ); // 서버에서 보내는 것이므로 처리할 필요 없음. m_pPacketStream->Write( &bActorAttachWeapon, sizeof(bool) ); int nValue = 0; DWORD dwValue = this->GetUniqueID(); m_pPacketStream->Write( &dwValue, sizeof(DWORD) ); if( bActorAttachWeapon == false ) { nValue = this->GetClassID(); m_pPacketStream->Write( &nValue, sizeof(int) ); } m_pPacketStream->Write( &this->GetMatEx()->m_vPosition, sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); m_pPacketStream->Write( &this->GetMatEx()->m_vXAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); m_pPacketStream->Write( &this->GetMatEx()->m_vYAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); m_pPacketStream->Write( &this->GetMatEx()->m_vZAxis, sizeof(EtVector3), CPacketCompressStream::VECTOR3_SHORT ); #ifdef PRE_MOD_PROJECTILE_HACK DWORD dwShooterUniqueID = m_hShooter->GetUniqueID(); m_pPacketStream->Write( &m_cShooterType, sizeof(char) ); m_pPacketStream->Write( &dwShooterUniqueID, sizeof(DWORD) ); switch( m_cShooterType ) { case 0: break; case 1: { bool bSendSerialID = ( m_nShooterSerialID == -1 ) ? false : true; m_pPacketStream->Write( &bSendSerialID, sizeof(bool) ); if( bSendSerialID ) m_pPacketStream->Write( &m_nShooterSerialID, sizeof(INT64) ); else m_pPacketStream->Write( &m_dwShooterUniqueID, sizeof(DWORD) ); } break; } m_pPacketStream->Write( &m_nShooterActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); m_pPacketStream->Write( &m_nShooterSignalIndex, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); #else nValue = (int)this->GetOrbitType(); m_pPacketStream->Write( &nValue, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); nValue = (int)this->GetDestroyOrbitType(); m_pPacketStream->Write( &nValue, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); nValue = (int)this->GetTargetType(); m_pPacketStream->Write( &nValue, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); nValidTime = this->GetValidTime(); m_pPacketStream->Write( &nValidTime, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); nValue = this->GetWeaponLength(); m_pPacketStream->Write( &nValue, sizeof(int) ); fValue = this->GetProjectileOrbitRotateZ(); m_pPacketStream->Write( &fValue, sizeof(float) ); #endif m_pPacketStream->Write(&m_nTargetPartsIndex, sizeof(int)); m_pPacketStream->Write(&m_nTargetPartsBoneIndex, sizeof(int)); switch( this->GetTargetType() ) { case CDnProjectile::CrossHair: case CDnProjectile::Direction: break; case CDnProjectile::TargetPosition: case CDnProjectile::Target: case CDnProjectile::DestPosition: case CDnProjectile::Shooter: { m_pPacketStream->Write( this->GetTargetPosition(), sizeof(EtVector3), CPacketCompressStream::VECTOR3_BIT ); DWORD dwUniqueID = ( this->GetTargetActor() ) ? this->GetTargetActor()->GetUniqueID() : -1; m_pPacketStream->Write( &dwUniqueID, sizeof(DWORD) ); } break; } fValue = this->GetSpeed(); m_pPacketStream->Write( &fValue, sizeof(float) ); #ifdef PRE_MOD_PROJECTILE_HACK int nActionIndex = -1; if( m_hShooter->GetProcessSkill() ) nActionIndex = m_hShooter->GetCurrentActionIndex(); m_pPacketStream->Write( &nActionIndex, sizeof(int), CPacketCompressStream::INTEGER_SHORT ); #else bool bPierce = this->GetPierce(); m_pPacketStream->Write( &bPierce, sizeof(bool) ); if( bPierce ) { int nMaxHitCount = this->GetMaxHitCount(); m_pPacketStream->Write( &nMaxHitCount, sizeof(int) ); } bool bTraceHitTarget = this->IsTraceHitTarget(); bool bTraceHitActorHittable = this->IsTraceHitActorHittable(); m_pPacketStream->Write( &bTraceHitTarget, sizeof(bool) ); m_pPacketStream->Write( &bTraceHitActorHittable, sizeof(bool) ); VelocityType = (int)this->GetVelocityType(); switch( this->GetOrbitType() ) { case CDnProjectile::Linear: case CDnProjectile::TerrainLinear: break; case CDnProjectile::Projectile: m_pPacketStream->Write( &VelocityType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); break; case CDnProjectile::Homing: case CDnProjectile::TerrainHoming: m_pPacketStream->Write( &VelocityType, sizeof(int), CPacketCompressStream::INTEGER_CHAR ); break; case CDnProjectile::Acceleration: fValue = this->GetResistance(); m_pPacketStream->Write( &fValue, sizeof(float), CPacketCompressStream::FLOAT_SHORT, 1.f ); break; } // 아래 이하는 몬스터에서 사용하지 않는다. int nActionIndex = -1; if( m_hShooter->GetProcessSkill() ) nActionIndex = m_hShooter->GetCurrentActionIndex(); m_pPacketStream->Write( &nActionIndex, sizeof(int) ); // Note : 체인 공격 상태효과를 위해 시그널 struct 를 찾을 수 있는 index 가 필요함. m_pPacketStream->Write( &m_iSignalArrayIndex, sizeof(int) ); // 보우 마스터의 레볼루셔너리 발리스타 같은 발사체어서 발사체 쏘는 스킬에서 최초로 나간 발사체에서 // 능력치를 이전하기 위한 정보... // 클라에서 서버로 보내는 것으로 서버에서 클라로 보낼 일은 없다. // 형식을 맞추기 위해 써준다. DWORD dwParentID = UINT_MAX;//this->GetParentProjectileID(); DWORD dwParentShooterUniqueID = m_hShooter->GetUniqueID(); m_pPacketStream->Write( &dwParentID, sizeof(DWORD) ); m_pPacketStream->Write( &dwParentShooterUniqueID, sizeof(DWORD) ); #endif #if defined(PRE_FIX_52329) m_pPacketStream->Write( &m_nIgnoreHitType, sizeof(m_nIgnoreHitType)); #endif // PRE_FIX_52329 } return m_pPacketStream; } void CDnProjectile::SetShooterType( DnActorHandle hActor, int nActionIndex, int nSignalIndex ) { m_cShooterType = 0; m_dwShooterUniqueID = hActor->GetUniqueID(); m_nShooterActionIndex = nActionIndex; m_nShooterSignalIndex = nSignalIndex; } void CDnProjectile::SetShooterType( DnWeaponHandle hWeapon, int nActionIndex, int nSignalIndex ) { m_cShooterType = 1; m_dwShooterUniqueID = -1; m_nShooterSerialID = -1; if( hWeapon->GetSerialID() == -1 ) m_dwShooterUniqueID = hWeapon->GetUniqueID(); else m_nShooterSerialID = hWeapon->GetSerialID(); m_nShooterActionIndex = nActionIndex; m_nShooterSignalIndex = nSignalIndex; } void CDnProjectile::ApplyParentProjectile(CDnProjectile* pParentProjectile) { if (pParentProjectile == NULL) return; const std::vector& stateEffectList = pParentProjectile->GetStateEffectList(); if (stateEffectList.empty()) return; int nStateEffectCount = (int)stateEffectList.size(); for (int i = 0; i < nStateEffectCount; ++i) { CDnSkill::StateEffectStruct& stateEffect = const_cast(stateEffectList.at(i)); if( stateEffect.ApplyType == CDnSkill::ApplySelf ) continue; this->AddStateEffect( stateEffect ); } } void CDnProjectile::SetTargetActor( DnActorHandle hActor, bool bUpdateTargetPartsIndex/* = true*/ ) { m_hTargetActor = hActor; if (bUpdateTargetPartsIndex) UpdateTargetPartsIndex(); } void CDnProjectile::UpdateTargetPartsIndex() { int nSelectPartsIndex = -1; int nSelectBoneIndex = -1; //파츠 몬스터 일 경우 해당 파츠를 선택 해야 한다.. if (m_hTargetActor && m_hTargetActor->IsMonsterActor()) { if( m_hTargetActor->IsPartsMonsterActor() ) { CDnPartsMonsterActor* pPartsMonsterActor = static_cast(m_hTargetActor.GetPointer()); int nCount = pPartsMonsterActor->GetPartsSize(); //선택가능한 Parts Index를 저장 해놓는다.. struct SelectPartsInfo { int PartsIndex; int BoneCount; }; std::vector indexList; for (int i = 0; i < nCount; ++i) { MonsterParts* pMonsterParts = pPartsMonsterActor->GetPartsByIndex(i); if (pMonsterParts == NULL) continue; const MonsterParts::_Info& boneInfo = pMonsterParts->GetPartsInfo(); if (boneInfo.PartsState != MonsterParts::ePartsState::eNormal) continue; //가능한 parts 인덱스와 이 Parts의 bone 갯수를 담아 놓는다. SelectPartsInfo _tempInfo; _tempInfo.PartsIndex = i; _tempInfo.BoneCount = (int)boneInfo.vParts.size(); indexList.push_back(_tempInfo); } int nListCount = (int)indexList.size(); if (nListCount > 0) { //담긴 파츠들 중에서 하나를 선택한다.. int nIndex = rand() % nListCount; SelectPartsInfo &tempPartsInfo = indexList[nIndex]; nSelectPartsIndex = tempPartsInfo.PartsIndex; //선택된 파츠중 본 하나를 선택 한다. nSelectBoneIndex = rand() % tempPartsInfo.BoneCount; } } } SetTargetPartsIndex(nSelectPartsIndex, nSelectBoneIndex); } bool CDnProjectile::SetTraceActor( DnActorHandle hActor, bool bValidDamageGoOn ) { bool bValidDamageGoOnResult = bValidDamageGoOn; // 따라가는 발사체의 경우 damage 를 hit 상태에서 계속 줄 수 있기 때문에 valid damage 를 켜준다. #22666 // shoot 액션일 때 한번만 셋팅되면 됨. if( m_nActionIndex == m_iShootActionIndex && m_bTraceHitTarget && hActor && !m_hTraceActor ) { bValidDamageGoOn = true; m_hTraceActor = hActor; // 추적 발사체가 되면 hit 액션 끝날 때 까지 시간과 무관하게 사라지지 않는다. m_ValidType = (ValidTypeEnum)( m_ValidType & ~Time ); } return bValidDamageGoOnResult; } void CDnProjectile::GetChainAttackInfo(const CDnSkill::StateEffectStruct& stateEffectStruct, float& fRange, int& nMaxCount) { switch(stateEffectStruct.nID) { case STATE_BLOW::BLOW_060: { std::string str = stateEffectStruct.szValue; std::vector tokens; std::string delimiters = ";"; //1. 구분 TokenizeA(str, tokens, delimiters); if (tokens.size() != 3) fRange = 1000.0f; else { fRange = (float)atoi(tokens[0].c_str()); nMaxCount = atoi(tokens[1].c_str()); } } break; case STATE_BLOW::BLOW_208: { std::string str = stateEffectStruct.szValue;//"최대히트수;범위(cm);비율"; std::vector tokens; std::string delimiters = ";"; //1. 구분 TokenizeA(str, tokens, delimiters); if (tokens.size() != 3) fRange = 1000.0f; else { nMaxCount = atoi(tokens[0].c_str()); fRange = (float)atoi(tokens[1].c_str()); } } break; } } DnActorHandle CDnProjectile::FindNextChainActor(int iRootAttackerTeam, DnActorHandle hActor, DnActorHandle hPrevActor, float fRange) { DNVector(DnActorHandle) vlActorsInRange; CDnActor::ScanActor( hActor->GetRoom(), *hActor->GetPosition(), fRange, vlActorsInRange ); float fShortestDistanceSQ = FLT_MAX; DnActorHandle hActorToAttack; DWORD dwNumActors = (DWORD)vlActorsInRange.size(); for( DWORD dwActor = 0; dwActor < dwNumActors; ++dwActor ) { DnActorHandle hTargetActor = vlActorsInRange.at( dwActor ); if (!hTargetActor) continue; if( false == (hTargetActor->IsShow() && hTargetActor->IsProcess()) ) continue; //죽은 녀석은 스킵... if (hTargetActor->IsDie()) continue; // 직전에 나에게 상태효과 넘겨줬던 액터한테는 다시 주지 않는다. if( hPrevActor != hTargetActor ) { // #30643 나 이외엔 다 적이다~~~ if( iRootAttackerTeam != hTargetActor->GetTeam() && hTargetActor != hActor ) { EtVector3 vDistance = (*hActor->GetPosition()) - (*hTargetActor->GetPosition()); float fLengthSQ = EtVec3LengthSq( &vDistance ); if( fLengthSQ < fShortestDistanceSQ ) { fShortestDistanceSQ = fLengthSQ; hActorToAttack = hTargetActor; } } } } return hActorToAttack; } void CDnProjectile::CreateChainAttackProjectile(DnActorHandle hRootAttacker, DnActorHandle hActor, DnActorHandle hActorToAttack, ProjectileStruct* pProjectileSignalInfo, CDnSkill::SkillInfo& parentSkillInfo) { // 프로젝타일 발사 관련 기타 설정들. DnSkillHandle hSkill = hRootAttacker->FindSkill( parentSkillInfo.iSkillID ); // 호밍으로 타겟을 설정토록 정해준다. pProjectileSignalInfo->nOrbitType = CDnProjectile::Homing; pProjectileSignalInfo->nTargetType = CDnProjectile::Target; pProjectileSignalInfo->VelocityType = CDnProjectile::Accell; pProjectileSignalInfo->fSpeed = 1000.0f;//CHAINATTACK_PROJECTILE_SPEED; pProjectileSignalInfo->nValidTime = 5000; MatrixEx Cross = *hActor->GetMatEx(); Cross.m_vPosition.y += hActor->GetHeight() / 2.0f; CDnProjectile *pProjectile = new CDnProjectile( GetRoom(), hRootAttacker ); pProjectile->SetPierce( pProjectileSignalInfo->bPierce == TRUE ? true : false ); pProjectile->SetMaxHitCount( pProjectileSignalInfo->nMaxHitCount ); if( pProjectileSignalInfo->nWeaponTableID > 0 ) { if( pProjectileSignalInfo->nProjectileIndex != -1 ) { DnWeaponHandle hWeapon = CDnWeapon::GetSmartPtr( (CMultiRoom*)g_pGameServerManager->GetRootRoom(), pProjectileSignalInfo->nProjectileIndex ); if( hWeapon ) *(CDnWeapon*)pProjectile = *hWeapon.GetPointer(); } } int nLength = pProjectile->GetWeaponLength(); if( pProjectileSignalInfo->bIncludeMainWeaponLength ) { DnWeaponHandle hWeapon = hActor->GetWeapon(0); if ( hWeapon ) nLength += hWeapon->GetWeaponLength(); } pProjectile->SetWeaponLength( nLength ); pProjectile->SetWeaponType( (CDnWeapon::WeaponTypeEnum)( pProjectile->GetWeaponType() | CDnWeapon::Projectile ) ); pProjectile->SetSpeed( pProjectileSignalInfo->fSpeed ); pProjectile->SetTargetPosition( *hActorToAttack->GetPosition() ); pProjectile->SetTargetActor( hActorToAttack ); pProjectile->Initialize( Cross, static_cast(pProjectileSignalInfo->nOrbitType), static_cast(pProjectileSignalInfo->nDestroyOrbitType), static_cast(pProjectileSignalInfo->nTargetType) ); pProjectile->SetValidTime( pProjectileSignalInfo->nValidTime ); pProjectile->SetVelocityType( static_cast(pProjectileSignalInfo->VelocityType) ); pProjectile->SetParentSkill( hSkill ); parentSkillInfo.hPrevAttacker = hActor; parentSkillInfo.iLeaveCount -= 1; // 설정되어있는 카운트 하나씩 줄인다. pProjectile->SetParentSkillInfo( parentSkillInfo ); pProjectile->FromSkill( true ); CDnState rootAttackerState; int iNumSE = hSkill->GetStateEffectCount(); for( int i = 0; i < iNumSE; ++i ) { const CDnSkill::StateEffectStruct* pStateEffect = hSkill->GetStateEffectFromIndex( i ); if( pStateEffect->ApplyType == CDnSkill::ApplySelf ) continue; CDnSkill::StateEffectStruct SE = *pStateEffect; pProjectile->AddStateEffect( SE ); } //#45331 //발사체가 생성될 시점에 저장되어 있던 액터의 상태값을 그대로 전달 해준다. pProjectile->SetShooterStateSnapshot( m_pShooterState ); #if defined(PRE_FIX_65287) pProjectile->SetShooterFinalDamageRate(m_fShooterFinalDamageRate); #endif // PRE_FIX_65287 pProjectile->PostInitialize(); } #if defined(PRE_ADD_55295) void CDnProjectile::ChangeProjectileRotation() { //방향은 그대로 두고, Y축만 다시 설정을 위해, Y축은 초기화 해서 //X, Z방향 벡터 다시 계산 m_Cross.m_vYAxis = EtVector3(0.0f, 1.0f, 0.0f); m_Cross.MakeUpCartesianByYAxis(); } #endif // PRE_ADD_55295 #if defined(PRE_FIX_59238) void CDnProjectile::AddHittedActor(DnActorHandle hHittedActor) { if (hHittedActor) m_HittedActorList.insert(std::make_pair(hHittedActor->GetUniqueID(), hHittedActor)); } bool CDnProjectile::IsHittable(DnActorHandle hActor) { //먼저 액터 유효성 확인.. if (!hActor) return false; bool isSummonMonster = false; bool isOwnerActor = false; //꼭두각시 상태효과가 있는 주인액터 인지.. if (hActor->IsAppliedThisStateBlow(STATE_BLOW::BLOW_247)) { isOwnerActor = true; } //소환 몬스터인지... else if (hActor->IsMonsterActor()) { CDnMonsterActor* pMonsterActor = static_cast(hActor.GetPointer()); if (pMonsterActor && pMonsterActor->IsSummonedMonster() && pMonsterActor->IsPuppetSummonMonster()) isSummonMonster = true; } //소환 몬스터도 아니고, 꼭두각시 상태효과를 가진 액터도 아니라면 히트 가능.. //소환 몬스터이고, 꼭두각시 상태효과를 가진 액터라면 뭔가 문제 있음..(그냥 히트 가능 하도록...) if ((isSummonMonster == false && isOwnerActor == false) || (isSummonMonster == true && isOwnerActor == true)) return true; //꼭두각시 상태효과를 가진 액터라면 이미 히트된 액터 리스트에서 자신이 소환한 소환 몬스터 액터가 //포함되어 있다면 자신은 히트 되지 않아야 한다. if (isOwnerActor == true) { //자신의 소환 몬스터 리스트 const std::list & listSummonMonster = hActor->GetSummonedMonsterList(); std::list::const_iterator iter = listSummonMonster.begin(); for( iter; iter != listSummonMonster.end(); ++iter ) { DnMonsterActorHandle hMonster = (*iter); if (!hMonster) continue; //소환 몬스터가 아니고, PuppetSummon몬스터가 아니면 스킵 if (hMonster->IsSummonedMonster() == false || hMonster->IsPuppetSummonMonster() == false) continue; //소환한 몬스터가 이미 히트 리스트에 있다면 자신은 히트 되지 않아야 한다. std::map::iterator iter = m_HittedActorList.find(hMonster->GetUniqueID()); if (iter != m_HittedActorList.end()) return false; } //자신의 소환 그룹 몬스터 리스트 const std::map >& groupSummonMonster = hActor->GetGroupingSummonedMonsterList(); std::map >::const_iterator mapIter; for (mapIter = groupSummonMonster.begin(); mapIter != groupSummonMonster.end(); ++mapIter) { const std::list & listSummonMonster = mapIter->second; std::list::const_iterator iter = listSummonMonster.begin(); for (; iter != listSummonMonster.end(); ++iter) { DnMonsterActorHandle hMonster = (*iter); if (!hMonster) continue; //소환 몬스터가 아니고, PuppetSummon몬스터가 아니면 스킵 if (hMonster->IsSummonedMonster() == false || hMonster->IsPuppetSummonMonster() == false) continue; //소환한 몬스터가 이미 히트 리스트에 있다면 자신은 히트 되지 않아야 한다. std::map::iterator iter = m_HittedActorList.find(hMonster->GetUniqueID()); if (iter != m_HittedActorList.end()) return false; } } return true; } //소환 몬스터라면, 이미 히트된 액터 리스트에서 자신의 주인 액터가 있다면 히트 되지 않아야 한다. else if (isSummonMonster == true) { DnActorHandle hOwnerActor; CDnMonsterActor* pMonsterActor = static_cast(hActor.GetPointer()); if (pMonsterActor) hOwnerActor = pMonsterActor->GetSummonerPlayerActor(); //소환 주인 액터가 꼭두각시 상태효과를 가지고 있지 않으면 히트되어도 된다.. if (!hOwnerActor || hOwnerActor->IsAppliedThisStateBlow(STATE_BLOW::BLOW_247) == false) return true; //소환 몬스터의 주인 액터가 이미 히트 리스트에 존재 하면 이 액터는 히트 되면 안되고, //주인 액터가 히트 리스트에 없으면 히트 되어야함. std::map::iterator iter = m_HittedActorList.find(hOwnerActor->GetUniqueID()); if (iter != m_HittedActorList.end()) return false; else return true; } return true; } #endif // PRE_FIX_59238 #if defined(PRE_FIX_59336) void CDnProjectile::ApplyComboLimitStateEffect( DnActorHandle hActor ) { for( DWORD i= 0 ; i < m_ComboLimitStateEffectList.size(); i++ ) { const CDnSkill::StateEffectStruct& SE = m_ComboLimitStateEffectList.at(i); if (m_HitStruct.szSkipStateBlows && CDnSkill::IsSkipStateBlow(m_HitStruct.szSkipStateBlows, (STATE_BLOW::emBLOW_INDEX)SE.nID)) continue; // 아군까지 힐 시켜주는 스킬의 경우엔 Self 힐과 Target 힐 두 개의 상태효과가 선언되어있따. // Self 상태효과는 자신에게 이미 적용되었고 여긴 Hit 시그널 판정되는 곳이기 ‹š문에 Target 만 적용된다. switch( SE.ApplyType ) { case CDnSkill::ApplySelf: continue; break; case CDnSkill::ApplyTarget: break; case CDnSkill::ApplyEnemy: if( m_hShooter->GetTeam() == hActor->GetTeam() ) continue; break; case CDnSkill::ApplyFriend: if( m_hShooter->GetTeam() != hActor->GetTeam() ) continue; break; } m_ParentSkillInfo.iProjectileShootActionIndex = m_iShooterShootActionIndex; m_ParentSkillInfo.iProjectileSignalArrayIndex = m_iSignalArrayIndex; //같은 스킬의 같은 시작 시간이 있으면 추가 안되도록.. bool isExistSameSkillTimeBlow = false; DNVector(DnBlowHandle) vlAppliedBlows; hActor->GatherAppliedStateBlowByBlowIndex( (STATE_BLOW::emBLOW_INDEX)m_ComboLimitStateEffectList[i].nID, vlAppliedBlows ); int iNumAppliedBlow = (int)vlAppliedBlows.size(); for( int iAppliedBlow = 0; iAppliedBlow < iNumAppliedBlow; ++iAppliedBlow ) { DnBlowHandle hBlow = vlAppliedBlows.at( iAppliedBlow ); if (!hBlow) continue; CDnSkill::SkillInfo* pExistSkillInfo = const_cast(hBlow->GetParentSkillInfo()); ////////////////////////////////////////////////////////////////////////// // #56880 // 스킬 시작 시간이 같은지도 확인 해야 한다.. LOCAL_TIME parentSkillStartTime = 0; LOCAL_TIME nowBlowSkillStartTime = 0; parentSkillStartTime = GetSkillStartTime(); CDnComboDamageLimitBlow* pNowBlow = static_cast(hBlow.GetPointer()); if (pNowBlow) nowBlowSkillStartTime = pNowBlow->GetSkillStartTime(); ////////////////////////////////////////////////////////////////////////// if (pExistSkillInfo) { if (m_ParentSkillInfo.hSkillUser && m_ParentSkillInfo.hSkillUser == pExistSkillInfo->hSkillUser && //스킬 사용자가 같고 m_ParentSkillInfo.iSkillID == pExistSkillInfo->iSkillID && //스킬이 같은경우 nowBlowSkillStartTime == parentSkillStartTime //스킬 시작 시간이 같은 경우 ) { isExistSameSkillTimeBlow = true; break; } } } int iID = -1; bool bCheckCanBegin = true; if (isExistSameSkillTimeBlow == false) { //발사체에서 상태효과 추가할때 발사체 스킬 시작 시간을 담아서 전달한다.. m_ParentSkillInfo.projectileSkillStartTime = GetSkillStartTime(); #ifdef PRE_FIX_REMOVE_STATE_EFFECT_PACKET // 서버에서만 사용하는 형태이기때문에 패킷쏘지않습니다. iID = hActor->CDnActor::CmdAddStateEffect( &m_ParentSkillInfo, (STATE_BLOW::emBLOW_INDEX)m_ComboLimitStateEffectList[i].nID, m_ComboLimitStateEffectList[i].nDurationTime, m_ComboLimitStateEffectList[i].szValue.c_str(), false, bCheckCanBegin ); #else iID = hActor->CmdAddStateEffect( &m_ParentSkillInfo, (STATE_BLOW::emBLOW_INDEX)m_ComboLimitStateEffectList[i].nID, m_ComboLimitStateEffectList[i].nDurationTime, m_ComboLimitStateEffectList[i].szValue.c_str(), false, bCheckCanBegin ); #endif //발사체 시간 리셋.. m_ParentSkillInfo.projectileSkillStartTime = 0; } // 추가한 상태효과 중에 chain attack 상태효과가 있다면 발사체 관련 정보들 셋팅해줘야 한다. // 핑퐁밤 상태효과도 마찬가지로 추가해줘야 함. if( iID != -1 ) { DnBlowHandle hBlow = hActor->GetStateBlowFromID( iID ); //#56880 발사체로 상태효과 추가 될때 242상태효과 인경우 발사체 스킬의 시작 시간을 설정한다..?? if (m_ComboLimitStateEffectList[i].nID == STATE_BLOW::BLOW_242) { LOCAL_TIME skillStartTime = GetSkillStartTime(); DnBlowHandle hBlow = hActor->GetStateBlowFromID( iID ); if( hBlow ) { CDnComboDamageLimitBlow* pComboLimitBlow = static_cast( hBlow.GetPointer() ); if (pComboLimitBlow) pComboLimitBlow->SetSkillStartTime(skillStartTime); } } } } } #endif // PRE_FIX_59336 void CDnProjectile::ApplySkillStateEffect(DnActorHandle hActor) { if (!hActor) return; // 대상이 얼음감옥 상태일때는 상태효과 적용 무시 if (hActor->IsAppliedThisStateBlow(STATE_BLOW::BLOW_149)) return; // 스킬 대상 설정이 아군인가 타겟인가에 따라 상태효과 적용을 구분한다. switch( m_ParentSkillInfo.eTargetType ) { case CDnSkill::Enemy: case CDnSkill::Self: if( m_hShooter->GetTeam() == hActor->GetTeam() ) return; break; case CDnSkill::Friend: case CDnSkill::Party: if( m_hShooter->GetTeam() != hActor->GetTeam() ) return; break; // 스킬 적용 대상이 아군/적군 전부 다 라면 상태효과 적용 쪽에서 적용 여부를 구분해야 한다. case CDnSkill::All: break; } // 상태이상 Add // 어떤 스킬로 인해 생성된 Projectile 이라면, CDnSkill::CanApply eResult = CDnSkill::CanApply::Apply; map mapDuplicateResult; //스킬 레벨업에 의해 발사체 스킬 정보가 Invalid될 수 있다. //발사체 액터가 있고, 스킬 ID가 설정되어 있는데 스킬이 Invalid하면 스킬 다시 설정한다. if (m_hShooter && m_ParentSkillInfo.iSkillID != 0 && !m_hParentSkill) { DnSkillHandle hSkill = m_hShooter->FindSkill(m_ParentSkillInfo.iSkillID); if (hSkill) SetParentSkill(hSkill); } if( m_hParentSkill ) { // 해당 Actor의 지속효과 구분 인덱스를 구분하여 성공한 경우에 상태이상 추가 시킴. eResult = CDnSkill::CanApplySkillStateEffect( hActor, m_hParentSkill, mapDuplicateResult, true ); } if( CDnSkill::CanApply::Fail != eResult ) { for( DWORD i= 0 ; i < m_VecStateEffectList.size(); i++ ) { const CDnSkill::StateEffectStruct& SE = m_VecStateEffectList.at(i); if (m_HitStruct.szSkipStateBlows && CDnSkill::IsSkipStateBlow(m_HitStruct.szSkipStateBlows, (STATE_BLOW::emBLOW_INDEX)SE.nID)) continue; // 아군까지 힐 시켜주는 스킬의 경우엔 Self 힐과 Target 힐 두 개의 상태효과가 선언되어있따. // Self 상태효과는 자신에게 이미 적용되었고 여긴 Hit 시그널 판정되는 곳이기 ‹š문에 Target 만 적용된다. switch( SE.ApplyType ) { case CDnSkill::ApplySelf: continue; break; case CDnSkill::ApplyTarget: break; case CDnSkill::ApplyEnemy: if( m_hShooter->GetTeam() == hActor->GetTeam() ) continue; break; case CDnSkill::ApplyFriend: if( m_hShooter->GetTeam() != hActor->GetTeam() ) continue; break; } // 같은 스킬 중첩일 경우엔 스킬 효과 중에 확률 체크하는 것들은 이미 CanApplySkillStateEffect 에서 확률체크되고 // 통과된 상태이다. 따라서 여기선 확률 체크 된건지 확인하고 된거라면 다시 확률 체크 안하도록 함수 호출 해준다. bool bAllowAddThisSE = true; bool bCheckCanBegin = true; if( CDnSkill::ApplyDuplicateSameSkill == eResult ) { map::iterator iter = mapDuplicateResult.find( m_VecStateEffectList[i].nID ); // 맵에 없는 경우 현재 액터가 상태효과에 걸려있지 않으므로 그냥 정상적으로 상태효과 추가 루틴 실행. if( mapDuplicateResult.end() != iter ) { // 같은 스킬의 확률있는 상태효과가 현재 걸려있어서 CanAdd 를 호출해보았으나 실패했음. // 이런 경우엔 상태효과 추가하지 않는다. if( false == (iter->second) ) bAllowAddThisSE = false; else // 이미 CanAdd 를 통과한 상태이므로 CmdAddStateEffect 호출 시 따로 체크하지 않도록 해준다. bCheckCanBegin = false; } } m_ParentSkillInfo.iProjectileShootActionIndex = m_iShooterShootActionIndex; m_ParentSkillInfo.iProjectileSignalArrayIndex = m_iSignalArrayIndex; if( bAllowAddThisSE ) { // #72931 스크리머 저주 쿨타임 공유 처리 -> 나중에 이런식으로 쓰는거 많아지면 일반화해야함 bool bShareCurseCoolTime = false; if( m_VecStateEffectList[i].nID == STATE_BLOW::BLOW_244 ) { DNVector(DnBlowHandle) vlhBlows; hActor->GatherAppliedStateBlowByBlowIndex( STATE_BLOW::BLOW_244, vlhBlows ); if( static_cast( vlhBlows.size() ) > 0 ) { CDnCurseBlow* pDnCurseBlow = static_cast( vlhBlows[0].GetPointer() ); if( pDnCurseBlow ) { m_VecStateEffectList[i].szValue += ";"; m_VecStateEffectList[i].szValue += FormatA( "%f", pDnCurseBlow->GetCoolTime() ); bShareCurseCoolTime = true; } } } // 여기서 등록되어 있는 제거 되어야할 상태효과들 없앤다. [2010/12/08 semozz] hActor->RemoveResetStateBlow(); //발사체에서 상태효과 추가할때 발사체 스킬 시작 시간을 담아서 전달한다.. m_ParentSkillInfo.projectileSkillStartTime = GetSkillStartTime(); int iID = hActor->CmdAddStateEffect( &m_ParentSkillInfo, (STATE_BLOW::emBLOW_INDEX)m_VecStateEffectList[i].nID, m_VecStateEffectList[i].nDurationTime, m_VecStateEffectList[i].szValue.c_str(), false, bCheckCanBegin ); if( bShareCurseCoolTime ) // #72931 쿨타임 붙였던거 다시 제거 { std::string::size_type delimiterindex = m_VecStateEffectList[i].szValue.rfind( ";" ); if( delimiterindex != std::string::npos ) { m_VecStateEffectList[i].szValue.erase( delimiterindex, m_VecStateEffectList[i].szValue.length() - delimiterindex ); } } //발사체 시간 리셋.. m_ParentSkillInfo.projectileSkillStartTime = 0; // 추가한 상태효과 중에 chain attack 상태효과가 있다면 발사체 관련 정보들 셋팅해줘야 한다. // 핑퐁밤 상태효과도 마찬가지로 추가해줘야 함. if( iID != -1 ) { DnBlowHandle hBlow = hActor->GetStateBlowFromID( iID ); if( hBlow ) { if( STATE_BLOW::BLOW_060 == m_VecStateEffectList.at( i ).nID ) { CDnChainAttackBlow* pChainAttackBlow = static_cast( hBlow.GetPointer() ); pChainAttackBlow->SetProjectileSignal( m_pProjectileSignal.get() ); pChainAttackBlow->SetRootAttackerState( m_pShooterState.get() ); } else if( STATE_BLOW::BLOW_208 == m_VecStateEffectList.at( i ).nID ) { CDnPingpongBlow* pPingPongBlow = static_cast( hBlow.GetPointer() ); pPingPongBlow->SetProjectileSignal( m_pProjectileSignal.get() ); pPingPongBlow->SetRootAttackerState( m_pShooterState.get() ); } } //#56880 발사체로 상태효과 추가 될때 242상태효과 인경우 발사체 스킬의 시작 시간을 설정한다..?? if (m_VecStateEffectList[i].nID == STATE_BLOW::BLOW_242) { LOCAL_TIME skillStartTime = GetSkillStartTime(); DnBlowHandle hBlow = hActor->GetStateBlowFromID( iID ); if( hBlow ) { CDnComboDamageLimitBlow* pComboLimitBlow = static_cast( hBlow.GetPointer() ); if (pComboLimitBlow) pComboLimitBlow->SetSkillStartTime(skillStartTime); } } } else { //ChainAttack이 아닌 경우는 아래 루틴 스킵함. STATE_BLOW::emBLOW_INDEX eBlowIndex = (STATE_BLOW::emBLOW_INDEX)m_VecStateEffectList[i].nID; if (eBlowIndex != STATE_BLOW::BLOW_060 && eBlowIndex != STATE_BLOW::BLOW_208) continue; //0. 상태효과 설정값 확인... float fRange = 1000.0f; int nMaxCount = -1; GetChainAttackInfo(m_VecStateEffectList[i], fRange, nMaxCount); //발사체 전이 횟수 설정. if (m_ParentSkillInfo.iLeaveCount == -1) m_ParentSkillInfo.iLeaveCount = nMaxCount; ////////////////////////////////////////////////////////////////////////// //2. 다음 타겟 캐릭터 선택 ////////////////////////////////////////////////////////////////////////// DnActorHandle hRootAttacker = m_ParentSkillInfo.hSkillUser; int iRootAttackerTeam = hRootAttacker->GetTeam(); DnActorHandle hActorToAttack = FindNextChainActor(iRootAttackerTeam, hActor, m_ParentSkillInfo.hPrevAttacker, fRange); //발사체 이전 횟수가 없거나, 이전될 액터를 못 찾으면 스킵.. if (m_ParentSkillInfo.iLeaveCount <= 0 || !hActorToAttack) continue; ////////////////////////////////////////////////////////////////////////// //1. 발사체 시그널 정보 설정. ////////////////////////////////////////////////////////////////////////// // 가장 처음 공격한 공격자 액터의 unique id DWORD dwRootAttackerActorUniqueID = hRootAttacker->GetUniqueID(); DWORD dwPrevAttackerActorUniqueID = m_ParentSkillInfo.hPrevAttacker ? m_ParentSkillInfo.hPrevAttacker->GetUniqueID() : UINT_MAX; // 프로젝타일 시그널을 클라이언트에서 찾는데 필요한 고유 정보들. int iActionIndex = m_ParentSkillInfo.iProjectileShootActionIndex; int iProjectileSignalArrayIndex = m_ParentSkillInfo.iProjectileSignalArrayIndex; ProjectileStruct projectileSignalInfo; bool bFound = false; CEtActionSignal *pSignal = hRootAttacker->GetSignal( iActionIndex, iProjectileSignalArrayIndex ); if( pSignal && pSignal->GetSignalIndex() == STE_Projectile ) { #ifdef PRE_FIX_MEMOPT_SIGNALH CopyShallow_ProjectileStruct(projectileSignalInfo, static_cast(pSignal->GetData())); #else projectileSignalInfo = *(static_cast(pSignal->GetData())); #endif bFound = true; } ////////////////////////////////////////////////////////////////////////// //패킷 클라이언트로 전송.. hActor->SendChainAttackProjectile(hRootAttacker, dwPrevAttackerActorUniqueID, iActionIndex, iProjectileSignalArrayIndex, hActorToAttack, m_ParentSkillInfo.iSkillID); //3. 발사체 정보 설정 하고 생성 ////////////////////////////////////////////////////////////////////////// CreateChainAttackProjectile(hRootAttacker, hActor, hActorToAttack, &projectileSignalInfo, m_ParentSkillInfo); ////////////////////////////////////////////////////////////////////////// } } } } #if defined(PRE_FIX_59336) //242번 상태효과 추가.. ApplyComboLimitStateEffect(hActor); #endif // PRE_FIX_59336 }