DragonNest/Server/DNGameServer/DnProjectile.cpp
Cussrro 47f7895977 Revert "修复编码问题"
This reverts commit 9e69c01767.
2024-12-21 10:04:04 +08:00

3805 lines
No EOL
125 KiB
C++

#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<int>(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<int>(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; i<nCount; i++ )
{
if( !hVecProp[i] )
continue;
if( hVecProp[i]->IsProjectileSkip() )
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<hVecList.size(); i++ )
{
#ifdef PRE_FIX_PIERCE_WITH_HIT_AREA
bool bFindHittedActor = false;
if( m_bPierce == true )
{
if( !m_VecHittedActor.empty() )
{
std::vector<DnActorHandle>::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 ; k<vCollisionResult.size() ; ++k )
{
if( vCollisionResult[k].pCollisionPrimitive ) {
vCollisionResult[k].pCollisionPrimitive->GetBoundingBox( 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; m<vPointList.size(); m++ ) {
#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
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 ; k<vCollisionResult.size() ; ++k )
{
if( vCollisionResult[k].pCollisionPrimitive ) {
vCollisionResult[k].pCollisionPrimitive->GetBoundingBox( 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; m<vPointList.size(); m++ ) {
#ifdef PRE_FIX_COLMESH_RECTANGLE_HITSIGNAL
// 빼야하는 영역에서 한점이라도 안 걸리면 hit 영역과 걸쳐 있는 놈이므로 빼지 않는다...
#if defined(PRE_FIX_63356)
if( hVecList[i]->CheckCollisionHitCondition(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<int>::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; i<pVecProp.size(); i++ )
{
if( false == ((CDnWorldProp*)pVecProp[i])->IsBrokenType() )
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<nCount; i++ )
{
// Hit수 제한
//최대 Hit수가 설정되어 있고, Hit수가 최대 Hit수를 넘어 가면 멈춘다.
isHitLimited = (m_HitParam.nHitLimitCount != 0 && nHitCount >= m_HitParam.nHitLimitCount);
if (isHitLimited)
break;
// Note: 느리게 가는 프로젝타일은 여러번 맞을 수 있어야 하기 때문에 이 부분은 뺍니다..
vector<DnActorHandle>::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 ; k<vResponse.size() ; ++k )
{
if( vResponse[k].pCollisionPrimitive )
m_HitParam.vBoneIndex.push_back( hVecList[i]->GetObjectHandle()->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<SCollisionCapsule*>(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<SCollisionCapsule*>(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<CDnPlayerActor*>(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<CDnActor*, int>::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<CDnMonsterActor*>(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<CDnGameTask*>(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; i<hVecList.size(); i++ ) {
if( !hVecList[i] ) continue;
if( hVecList[i] == m_hShooter ) continue;
if( hVecList[i]->IsDie() ) 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<CDnMonsterActor *>(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<CDnMonsterActor *>(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<BYTE*>(pPacket), 128 );
CDnPlayerActor* pPlayerActor = static_cast<CDnPlayerActor*>(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<ProjectileStruct*>( pSignal->GetData() );
}
else if( hHandle->GetCustomActionIndex() > 0 )
{
CEtActionSignal *pCustomActionSignal = hHandle->GetSignal( hHandle->GetCustomActionIndex(), nShooterSignalIndex );
if( pCustomActionSignal && pCustomActionSignal->GetSignalIndex() == STE_Projectile )
{
pShooterStruct = static_cast<ProjectileStruct*>( 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<ProjectileStruct*>( 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<int> vecWeaponID;
vecWeaponID.push_back( pShooterStruct->nWeaponTableID );
std::vector<std::string> tokens;
TokenizeA( pShooterStruct->RandomWeaponParam, tokens, "/" );
if( tokens.size() > 1 )
{
for( int i=1; i<static_cast<int>( tokens.size() ); i++ )
{
std::vector<std::string> 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<static_cast<int>( 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<ProjectileStruct*>( 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<CDnProjectile*>(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<CDnState> pActorStateSnapshot( new CDnState );
*pActorStateSnapshot = *static_cast<CDnState*>( pPlayerActor );
pProjectile->SetShooterStateSnapshot( pActorStateSnapshot );
#ifdef PRE_ADD_PROJECTILE_SE_INFO
if( bApplyStateBlowInfo == true )
{
boost::shared_ptr<CDnStateBlow> pActorStateBlow = boost::shared_ptr<CDnStateBlow>(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<CDnSkill::StateEffectStruct>& stateEffectList = pParentProjectile->GetStateEffectList();
if (stateEffectList.empty())
return;
int nStateEffectCount = (int)stateEffectList.size();
for (int i = 0; i < nStateEffectCount; ++i)
{
CDnSkill::StateEffectStruct& stateEffect = const_cast<CDnSkill::StateEffectStruct&>(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<CDnPartsMonsterActor*>(m_hTargetActor.GetPointer());
int nCount = pPartsMonsterActor->GetPartsSize();
//선택가능한 Parts Index를 저장 해놓는다..
struct SelectPartsInfo
{
int PartsIndex;
int BoneCount;
};
std::vector<SelectPartsInfo> 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<std::string> 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<std::string> 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<CDnProjectile::OrbitTypeEnum>(pProjectileSignalInfo->nOrbitType),
static_cast<CDnProjectile::DestroyOrbitTypeEnum>(pProjectileSignalInfo->nDestroyOrbitType),
static_cast<CDnProjectile::TargetTypeEnum>(pProjectileSignalInfo->nTargetType) );
pProjectile->SetValidTime( pProjectileSignalInfo->nValidTime );
pProjectile->SetVelocityType( static_cast<CDnProjectile::VelocityTypeEnum>(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<CDnMonsterActor*>(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<DnMonsterActorHandle> & listSummonMonster = hActor->GetSummonedMonsterList();
std::list<DnMonsterActorHandle>::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<DWORD, DnActorHandle>::iterator iter = m_HittedActorList.find(hMonster->GetUniqueID());
if (iter != m_HittedActorList.end())
return false;
}
//자신의 소환 그룹 몬스터 리스트
const std::map<int, std::list<DnMonsterActorHandle> >& groupSummonMonster = hActor->GetGroupingSummonedMonsterList();
std::map<int, std::list<DnMonsterActorHandle> >::const_iterator mapIter;
for (mapIter = groupSummonMonster.begin(); mapIter != groupSummonMonster.end(); ++mapIter)
{
const std::list<DnMonsterActorHandle> & listSummonMonster = mapIter->second;
std::list<DnMonsterActorHandle>::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<DWORD, DnActorHandle>::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<CDnMonsterActor*>(hActor.GetPointer());
if (pMonsterActor)
hOwnerActor = pMonsterActor->GetSummonerPlayerActor();
//소환 주인 액터가 꼭두각시 상태효과를 가지고 있지 않으면 히트되어도 된다..
if (!hOwnerActor || hOwnerActor->IsAppliedThisStateBlow(STATE_BLOW::BLOW_247) == false)
return true;
//소환 몬스터의 주인 액터가 이미 히트 리스트에 존재 하면 이 액터는 히트 되면 안되고,
//주인 액터가 히트 리스트에 없으면 히트 되어야함.
std::map<DWORD, DnActorHandle>::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<CDnSkill::SkillInfo*>(hBlow->GetParentSkillInfo());
//////////////////////////////////////////////////////////////////////////
// #56880
// 스킬 시작 시간이 같은지도 확인 해야 한다..
LOCAL_TIME parentSkillStartTime = 0;
LOCAL_TIME nowBlowSkillStartTime = 0;
parentSkillStartTime = GetSkillStartTime();
CDnComboDamageLimitBlow* pNowBlow = static_cast<CDnComboDamageLimitBlow*>(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<CDnComboDamageLimitBlow*>( 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<int, bool> 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<int, bool>::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<int>( vlhBlows.size() ) > 0 )
{
CDnCurseBlow* pDnCurseBlow = static_cast<CDnCurseBlow*>( 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<CDnChainAttackBlow*>( 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<CDnPingpongBlow*>( 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<CDnComboDamageLimitBlow*>( 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<ProjectileStruct*>(pSignal->GetData()));
#else
projectileSignalInfo = *(static_cast<ProjectileStruct*>(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
}