DragonNest/Server/DNGameServer/DnPlayerActor.cpp
2024-12-20 16:56:44 +08:00

6774 lines
No EOL
423 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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