#include "stdafx.h" #include "SStateMachine.h" #include "SGameAvatarEx.h" //#include "Smove_filter.h" #include "TerrainMapEngine.h" #include "SGameWorld.h" #include "SkillBaseFile.h" #include "SSkillDB.h" #include "SSKillStageType.h" #include "SDebug_Util.h" #include "SLog.h" //namespace PF //{ // SGameObject *s_pGameObj; // move_filter_history history; // // struct intf_move_filter // { // static bool is_empty_at( float x, float y ) // { // return !s_pGameObj->IsBlockedAttribute( x, y ); // } // }; // typedef move_filter SPATHFINDER; // // // SPATHFINDER finder; // void ResetHistory() // { // history.reset(); // } // bool GetNextPos( SGameObject *pObj, const K3DVector &vCur, const K3DVector &vTarget, K3DVector *vNext ) // { // s_pGameObj = pObj; // return finder.get_direct_dest( history, vCur.x, vCur.y, vTarget.x, vTarget.y, vNext->x, vNext->y ); // } //}; const float c_fRangeAcceptable = 1.f; //100% 정도로 거리를 줄인다 SObjectStateMachine::SObjectStateMachine() : m_pCurrentState( NULL ) , m_idPendingState( SObjectState::STATE_NONE ) , m_idDefaultState( SObjectState::STATE_NONE ) , m_idNextState( SObjectState::STATE_NONE ) , m_idPrevState( SObjectState::STATE_NONE ) , m_hTargetHandle( 0 ) , m_pReceiver( NULL ) , m_dwTime( 0 ) , m_dwCurrentStateStartTime( 0 ) , m_idCurState( SObjectState::STATE_IDLE ) , m_idCreatureState( SObjectState::STATE_IDLE ) , m_nNeedStopPos( NEED_STOP_POS ) , m_dwFindDetour( 0 ) , m_hAttackTarget( 0 ) , m_bAutoAttack( false ) , m_dwAttackDelayTime( 0 ) , m_bInputFreezing(false) { SetMode( MODE_NORMAL ); //전투대기 상태 , 공격을 받거나, 내가 공격 시작하면 설정 된다. } void SObjectStateMachine::ClearInfo() { m_pCurrentState = NULL; m_idPendingState = SObjectState::STATE_NONE; m_idDefaultState = SObjectState::STATE_IDLE; m_idNextState = SObjectState::STATE_NONE; m_idPrevState = SObjectState::STATE_NONE; m_hTargetHandle = 0; m_dwCurrentStateStartTime = 0; m_idCurState = SObjectState::STATE_IDLE; m_idCreatureState = SObjectState::STATE_IDLE; m_nNeedStopPos = NEED_STOP_POS; m_dwFindDetour = 0; m_hAttackTarget = 0; // 2010.06.15 - prodongi //m_bAutoAttack = false; setAutoAttack(false); m_dwAttackDelayTime = 0; m_bInputFreezing = false; SetMode( MODE_NORMAL ); //전투대기 상태 , 공격을 받거나, 내가 공격 시작하면 설정 된다. while( !m_queInputState.empty() ) m_queInputState.pop(); m_infoCurrent.Reset(); m_infoPending.Reset(); m_infoDefault.Reset(); m_infoNext.Reset(); } SObjectStateMachine::~SObjectStateMachine() { for( OBJSTATE_MAP::iterator it = m_StateMap.begin(); it != m_StateMap.end(); it++ ) delete it->second; while( !m_queInputState.empty() ) m_queInputState.pop(); } SObjectState::REQCOMMAND_PERMIT SObjectStateMachine::CheckNextPermission( SObjectState::ID state ) { if( NULL != m_pCurrentState ) { //Mode 허용 처리 SObjectState * pReqState = GetObjState( state ); if( pReqState ) { if( pReqState->CheckModePermission( GetMode() ) == false ) return SObjectState::PERMIT_FORBIDDEN; } // 현재 상태의 퍼미션 체크 return m_pCurrentState->GetRequestPermission( state ); } return SObjectState::PERMIT_IMMEDIATELY; } bool SObjectStateMachine::RequestNewState( SObjectState::ID NewState, const SStateInfo &infoState ) { // _oprint( "[상태머신 : RequestNewState : %d %d]\n", m_idCurState, NewState ); bool bNextMove = false; K3DVertex vtNextMove, vtMyself; vtMyself = *m_pReceiver->GetPosition(); if( NewState == SObjectState::STATE_MOVE ) m_nNeedStopPos = NEED_STOP_POS; /* else if( m_nNeedStopPos == INSPECTED_STOP_POS ) m_nNeedStopPos = NOT_NEED_STOP_POS; */ if( ( NewState != SObjectState::STATE_IDLE /*&&m_pReceiver->GetCurrState() != NewState*/ ) || m_pReceiver->GetCurrTarget() != infoState.hTarget ) { m_pReceiver->EraseDetour(); if( NewState != SObjectState::STATE_IDLE ) m_pReceiver->SetCurrState( NewState ); m_pReceiver->SetCurrTarget( infoState.hTarget ); // 2008. 6. 18 floyd #2.3.1.23 // http://bug.nflavor.com/view.php?id=2868 // 아바타가 대상을 타겟하지 않으면, 크리쳐는 스킬 자동공격을 하지 않는 문제 관련 수정 // 한 동작(state?)가 끝나고 StopCurrentState()가 호출될때 m_infoDefault내용을 복사하며 크리쳐에 설정된 타겟이 NULL로 세팅되는 문제때문에 // m_infoDefault.hTarget 에 마지막으로 설정된 타겟을 기억하고 있도록 수정 m_infoDefault.hTarget = infoState.hTarget; } //추적중에 공격형 명령이 들어왔다면 if( m_idCurState == SObjectState::STATE_CHASE && IsCombatState( NewState ) ) { //현재 타겟과 새로운 타겟을 비교한다 if( m_infoCurrent.hTarget != infoState.hTarget || m_idNextState == SObjectState::STATE_NONE ) { //현재 타겟과 새로운 타겟이 다르다면 명령 실행 m_nNeedStopPos = NEED_STOP_POS; } } // 현재 상태의 퍼미션 체크 switch( CheckNextPermission( NewState ) ) { case SObjectState::PERMIT_IMMEDIATELY: // 즉시 허용일 경우 바로 현재 상태로 설정 // _oprint( "[상태머신 : RequestNewState - SObjectState::PERMIT_IMMEDIATELY]\n" ); if ( infoState.fRange > 0.f ) { /* //정지할 위치가 필요없다면 if( m_nNeedStopPos == NOT_NEED_STOP_POS ) { return true; } */ SStateInfo newinfo = infoState; /// 2010.10.21 - prodongi //if ( newinfo.hTarget != 0 ) //타겟이 있다. if ( newinfo.hTarget != 0 || newinfo.isRegionTarget ) //타겟이 있다. { /// 2010.10.21 - prodongi SGameAvatarEx* pTarget = NULL; if (!newinfo.isRegionTarget) { pTarget = (SGameAvatarEx*)m_pReceiver->GetGameObject( newinfo.hTarget ); if (!pTarget) return false; } //SGameAvatarEx* pTarget = (SGameAvatarEx*)m_pReceiver->GetGameObject( newinfo.hTarget ); //if ( pTarget == NULL ) return false; //없어 지면 끝 //else { // 2010.05.24 - prodongi if( m_pReceiver->GetObjType() == TS_ENTER::GAME_SUMMON && !infoState.isValidToCorpse ) //if( m_pReceiver->GetObjType() == TS_ENTER::GAME_SUMMON ) { //if( IsCombatState( NewState ) ) if( IsCombatState( NewState ) && !newinfo.isRegionTarget ) { switch( pTarget->GetObjType() ) { case TS_ENTER::GAME_MOB: { if(pTarget->GetHP() <= 0 ) return false; } break; case TS_ENTER::GAME_PLAYER: { if( ( pTarget->GetStatus() & TS_ENTER::PlayerInfo::FLAG_PK_ON ) || ( pTarget->GetStatus() & TS_ENTER::PlayerInfo::FLAG_DEMONIAC ) || ( pTarget->GetStatus() & TS_ENTER::PlayerInfo::FLAG_BLOODY ) ) { if(pTarget->GetHP() <= 0 ) return false; } } break; case TS_ENTER::GAME_SUMMON: { SGameAvatarEx* pMaster = (SGameAvatarEx*)m_pReceiver->GetGameObject( pTarget->GetMaster() ); if( pMaster ) { if( ( pMaster->GetStatus() & TS_ENTER::PlayerInfo::FLAG_PK_ON ) || ( pMaster->GetStatus() & TS_ENTER::PlayerInfo::FLAG_DEMONIAC ) || ( pMaster->GetStatus() & TS_ENTER::PlayerInfo::FLAG_BLOODY ) ) { if(pTarget->GetHP() <= 0 ) return false; } } } break; } } } } if (newinfo.isRegionTarget) { K3DVector regionTargetPos = newinfo.vRegionTargetPos; vtNextMove = GetChasePos( NULL, ®ionTargetPos, 1, newinfo, NewState ); } else { /*if( m_pReceiver->GetObjType() == TS_ENTER::GAME_SUMMON ) vtNextMove = GetCreatureChasePos( m_pReceiver, pTarget, pTarget->GetPosition(), newinfo, NewState ); else*/ vtNextMove = GetChasePos( pTarget, pTarget->GetPosition(), GetSize(pTarget), newinfo, NewState ); } if( vtNextMove.x < 0 || vtNextMove.y < 0 ) return false; //임시 int nReusultLen = 0; //공격 가능 한가 검사 한다. bool isInRange = false; if (newinfo.isRegionTarget) { isInRange = IsRange( *m_pReceiver->GetPosition(), GetSize( m_pReceiver ), newinfo.vRegionTargetPos, 1, infoState.fRange, true, nReusultLen ); } else { AR_UNIT fTargetSize = GetSize( pTarget ); AR_UNIT fMySize = GetSize( m_pReceiver ); bool bCollisionToLine = true; if( NewState == SObjectState::STATE_TAKEITEM ) { fTargetSize = 0; fMySize = 0; // 펫은 충돌 검사를 하지 않는다. if( m_pReceiver->GetObjType() == TS_ENTER::GAME_PET ) { bCollisionToLine = false; } } isInRange = IsRange( *m_pReceiver->GetPosition(), fMySize, *pTarget->GetPosition(), fTargetSize, infoState.fRange, bCollisionToLine, nReusultLen ); } if (isInRange) { //내가 이동 중이 아니면 if ( m_pReceiver->IsMoving() == false ) { //일반 공격 if( m_idCurState == SObjectState::STATE_ATTACK ) { //공격 상태 중에 이미 공격 중인 것은 다시 때리지 않는다. if( m_infoCurrent.hTarget == newinfo.hTarget ) return true; if( m_infoCurrent.hTarget != newinfo.hTarget ) { m_pReceiver->ReqCancelAction(); } } //TODO : SObjectState::STATE_AIMING 일때, 다른 타겟의 공격 요청시, 활을 발사 완료 후 Attack이 되어야 하는데, // 현재 Pending 시스템은 상태 전이가 한다계 뿐이 안 되므로, 아직 지원이 안된다. if( m_idCurState == SObjectState::STATE_SHOOTING ) { if( m_infoCurrent.hTarget != newinfo.hTarget ) m_pReceiver->ReqCancelAction(); } if( m_idCurState == SObjectState::STATE_ATTACK && NewState == SObjectState::STATE_CAST ) { m_pReceiver->ReqCancelAction(); } //바로 공격~ SendServerReq( NewState, newinfo ); return true; } else { if( newinfo.hTarget != m_pReceiver->GetArID()) { //범위 안 이면서, 이동 중이면, 거리는 -1m 처리 스킵 if( nReusultLen <= 0 && nReusultLen > -GameRule::DEFAULT_UNIT_SIZE ) //(-)이면 스킵 { return true; } } } m_pReceiver->FindDetour( vtNextMove, true, true); //추적 위치 검사 newinfo.vTargetPos = vtNextMove; newinfo.bIsMovePos = false; //그냥 이동이 아니다. newinfo.SpeedSync = false; //크리처일 경우 자기 속도로 움직인다 //새로운 정보의 타겟이 존재한다면 현재 정보 갱신 /// 2010.10.21 - prodongi if (newinfo.isRegionTarget) { m_infoCurrent.isRegionTarget = true; m_infoCurrent.vRegionTargetPos = newinfo.vRegionTargetPos; m_infoCurrent.vTargetPos = newinfo.vTargetPos; m_infoCurrent.fRange = newinfo.fRange; m_infoCurrent.nSkillID = newinfo.nSkillID; } else { if( newinfo.hTarget != 0 ) { m_infoCurrent.hTarget = newinfo.hTarget; m_infoCurrent.fRange = newinfo.fRange; m_infoCurrent.nSkillID = newinfo.nSkillID; } else m_infoCurrent.vTargetPos = newinfo.vTargetPos; } //다음 상태 설정 SetNextState( NewState, newinfo ); //추적이다. 서버에 알림 if (!newinfo.isRegionTarget) // 논 타겟팅일 경우 추적하지 않습니다. //SendServerReq( SObjectState::STATE_CHASE, newinfo ); SendServerReq( SObjectState::STATE_ATTACK, newinfo ); return true; } newinfo.vTargetPos = vtNextMove; newinfo.bIsMovePos = false; m_pReceiver->FindDetour( vtNextMove, true, true); } else { newinfo.vTargetPos = GetChasePos(); m_pReceiver->FindDetour( newinfo.vTargetPos, true, true ); size_t numDetourPoints = m_pReceiver->GetDetourSize(); if(numDetourPoints >= 2) { int x, y; if(m_pReceiver->GetDetourPoint(1, x, y)) { vtNextMove.x = (float) x; vtNextMove.y = (float) y; vtNextMove.z = 0.0f; bNextMove = true; } } } newinfo.vTargetPos = vtNextMove; newinfo.SpeedSync = false; //크리처일 경우 자기 속도로 움직인다 //새로운 정보의 타겟이 존재한다면 현재 정보 갱신 if( newinfo.hTarget != 0 ) { m_infoCurrent.hTarget = newinfo.hTarget; m_infoCurrent.fRange = newinfo.fRange; m_infoCurrent.nSkillID = newinfo.nSkillID; } else m_infoCurrent.vTargetPos = newinfo.vTargetPos; if ( m_pReceiver->CollisionToLine( *m_pReceiver->GetPosition(), vtNextMove ) ) // 앞에 장애물이 있으면.. { newinfo.vTargetPos = *m_pReceiver->GetPosition(); SetNextState( NewState, newinfo ); SendServerReq( SObjectState::STATE_MOVE, newinfo ); } else { SetNextState( NewState, newinfo ); SendServerReq( SObjectState::STATE_CHASE, newinfo ); } return true; } //이동중인 상태가 아니면서, 내가 이동 중이면 if ( CheckMovingState( NewState ) == false && m_pReceiver->IsMoving() == true ) { if( NewState != SObjectState::STATE_IDLE && NewState != SObjectState::STATE_NONE ) { //정지할 위치가 필요없다면 if( m_nNeedStopPos == NOT_NEED_STOP_POS ) return true; //추적하라??? SStateInfo newinfo = infoState; newinfo.vTargetPos = GetChasePos(); m_pReceiver->FindDetour( newinfo.vTargetPos, true, true ); size_t numDetourPoints = m_pReceiver->GetDetourSize(); if(numDetourPoints >= 2) { int x, y; if(m_pReceiver->GetDetourPoint(1, x, y)) { vtNextMove.x = (float) x; vtNextMove.y = (float) y; vtNextMove.z = 0.0f; bNextMove = true; } } if(!bNextMove) newinfo.vTargetPos = vtMyself; //새로운 정보의 타겟이 존재한다면 현재 정보 갱신 if( newinfo.hTarget != 0 ) { m_infoCurrent.hTarget = newinfo.hTarget; m_infoCurrent.fRange = newinfo.fRange; m_infoCurrent.nSkillID = newinfo.nSkillID; } else m_infoCurrent.vTargetPos = newinfo.vTargetPos; SetNextState( NewState, newinfo ); SendServerReq( SObjectState::STATE_CHASE, newinfo ); } } else { if( NewState == SObjectState::STATE_MOVE ) { if(SObjectState::STATE_SIT == m_idCurState) { m_pReceiver->ReqNormal(); } int nDetourSize = m_pReceiver->GetAllDetourPointSize(); if( nDetourSize <= 0 ) { m_pReceiver->FindDetour( infoState.vTargetPos, true, false ); } } SendServerReq( NewState, infoState ); } break; case SObjectState::PERMIT_FORBIDDEN: // 불가 // _oprint( "[상태머신 : RequestNewState - SObjectState::PERMIT_FORBIDDEN]\n" ); { /* if( m_idCurState == SObjectState::STATE_ATTACK && NewState == SObjectState::STATE_CAST ) { int nBoneCnt = 0; DWORD dwMinTime = 0, dwMaxTime = 0; DWORD dwCurAniTime = 0; m_pReceiver->GetCurrentAnimationInfo( nBoneCnt, dwMinTime, dwMaxTime, dwCurAniTime ); dwMaxTime = dwMaxTime / 160; dwCurAniTime = (dwCurAniTime / 160) * 4.8f; float fPlayProgress = (dwCurAniTime / (float)dwMaxTime); //80프로 애니메이션 진행되면 모션 캔슬 가능 if( fPlayProgress > 0.8f ) { int x = 0; x = 0; } }*/ } return false; case SObjectState::PERMIT_PENDING: // 대기시킨다. // _oprint( "[상태머신 : RequestNewState - SObjectState::PERMIT_PENDING]\n" ); if( !infoState.chase ) { if( NewState == SObjectState::STATE_ATTACK ) { //공격 중인 상태에서 같은 타겟에 공격 명령이 들어 오면 Input 안됨 if( SObjectState::STATE_ATTACK == m_idCurState || SObjectState::STATE_AIMING == m_idCurState ) { if( m_infoCurrent.hTarget == infoState.hTarget ) return false; } } } /// 2010.12.06 파이어 상태일 때는 캐스팅과 아이템 사용을 못 하게 한다,(연속 스킬일 경우에 스킬이 취소되는 것을 방지하기 위해서) - prodongi if ((SObjectState::STATE_CAST == NewState || SObjectState::STATE_USEITEM == NewState) && SObjectState::STATE_FIRE == m_idCurState) { return false; } //모션 캔슬 가능 상태 bool bMotionCancel = true; if( m_pCurrentState->GetMotionCancelInfo() ) bMotionCancel = m_pReceiver->MotionCancel(); SetPendingState( NewState, infoState ); //개선 해야함 switch( NewState ) { case SObjectState::STATE_MOVE: case SObjectState::STATE_TAKEITEM: case SObjectState::STATE_SIT: case SObjectState::STATE_USEITEM: case SObjectState::STATE_CAST: { //공격 및 슈팅 중에 이동이나 아이템 줍기 및 아이템 사용을 요청할때 바로 반응 할수 있도록 먼저 캐스팅 캔슬을 요청한다 if( SObjectState::STATE_SHOOTING == m_idCurState ) { m_pReceiver->ReqCancelAction(); } else if( SObjectState::STATE_ATTACK == m_idCurState ) { if( bMotionCancel ) { m_pReceiver->ReqCancelAction(); } } } break; case SObjectState::STATE_CAST_CANCEL: { //공격 및 조준중일때 와 캐스팅 일때 캐스팅 캔슬이 가능하다 if( SObjectState::STATE_ATTACK == m_idCurState || SObjectState::STATE_AIMING == m_idCurState || SObjectState::STATE_SHOOTING == m_idCurState || SObjectState::STATE_CAST == m_idCurState ) { m_pReceiver->ReqCancelAction(); } } break; case SObjectState::STATE_ATTACK: case SObjectState::STATE_AIMING: { //공격중이고 다시 공격 명령이 들어왔다면 현재 타겟과 새로운 타겟이 다른지 검사한다 if( SObjectState::STATE_ATTACK == m_idCurState || SObjectState::STATE_AIMING == m_idCurState ) { if( m_infoCurrent.hTarget != infoState.hTarget ) m_pReceiver->ReqCancelAction(); } } break; } } return true; } //현재 상태 정지 void SObjectStateMachine::StopCurrentState() { //SDEBUGLOG( "[상태머신 : StopCurrentState]\n" ); if( NULL != m_pCurrentState ) { if ( SObjectState::STATE_NONE == m_idNextState ) { // 다음 상태가 없는 경우 // 대기 상태가 있으면 현재 상태로 돌린다. if( m_idPendingState != SObjectState::STATE_NONE ) { //SDEBUGLOG( "[상태머신 : StopCurrentState 01]\n" ); bool bRequestNewState = true; if( m_pCurrentState->GetInfo().nSkillID == SKILL_NAME::SKILL_WARP ) { bRequestNewState = false; } m_pReceiver->ReqCancelAction(); m_pCurrentState = NULL; m_idCurState = SObjectState::STATE_NONE; m_idCreatureState = SObjectState::STATE_IDLE; if( bRequestNewState ) RequestNewState( m_idPendingState, m_infoPending ); ClearPending(); } // 대기 상태가 없으면 디폴트 상태로 돌린다. else { //SDEBUGLOG( "[상태머신 : StopCurrentState 02]\n" ); m_pCurrentState = NULL; m_idCurState = SObjectState::STATE_NONE; m_idCreatureState = SObjectState::STATE_IDLE; RequestNewState( m_idDefaultState, m_infoDefault ); m_pCurrentState = NULL; } } // 다음 상태가 있으면 설정 else { //SDEBUGLOG( "[상태머신 : StopCurrentState 03]\n" ); // m_pCurrentState = NULL; // m_idCurState = SObjectState::STATE_NONE; //현재 상태도 None // m_idCreatureState = SObjectState::STATE_IDLE; // m_infoNext.vTargetPos = *m_pReceiver->GetPosition(); // SendServerReq( m_idNextState, m_infoNext ); bool bRequestNewState = true; if( m_pCurrentState->GetInfo().nSkillID == SKILL_NAME::SKILL_WARP ) { bRequestNewState = false; } m_pCurrentState = NULL; SObjectState::ID NextState = m_idNextState; m_idNextState = SObjectState::STATE_NONE; //다음 상태도 None if( RequestNewState( NextState, m_infoNext ) == false && bRequestNewState ) { ClearAllState(); ClearPendingNextState(); } } } /// 2010.12.21 상태가 초기화 되지 않아서 펫이 가만히 있는 것 같음 - prodongi else { m_pCurrentState = NULL; m_idCurState = SObjectState::STATE_NONE; m_idCreatureState = SObjectState::STATE_IDLE; RequestNewState( m_idDefaultState, m_infoDefault ); m_pCurrentState = NULL; } } void SObjectStateMachine::SetNextState( SObjectState::ID State, const SStateInfo &info ) { SDEBUGLOG( "[상태머신 : SetNextState]\n" ); m_idNextState = State; m_infoNext = info; } float SObjectStateMachine::GetTargetDist( SGameObject *pTarget ) { K3DVector spos, tpos; spos = *m_pReceiver->GetPosition(); tpos = *pTarget->GetPosition(); spos.z = 0.f; tpos.z = 0.f; m_pReceiver->CorrectivePosition( spos, spos ); m_pReceiver->CorrectivePosition( tpos, tpos ); return GetDistance( spos, tpos ); } //추적 거리 구하기 K3DVector SObjectStateMachine::GetChasePos( SGameAvatarEx* pTarget, K3DVector* pTargetPos, float fTargetSize, const SStateInfo &infoState, SObjectState::ID NewState ) { //길찾기 땜에 int로 변환한다 안그럼 오차생김 K3DVector pos = K3DVector( (int)pTargetPos->x, (int)pTargetPos->y, (int)pTargetPos->z ); //정지할 위치를 구했다면 더이상 정지할 위치를 필요로 하지 않는다 if( m_nNeedStopPos != NOT_NEED_STOP_POS ) m_nNeedStopPos = INSPECTED_STOP_POS; return pos; /* K3DVector pos, newpos; float fDistance; bool bIsNeedRange = false; if ( infoState.fRange > 0 ) { K3DVector vTarget = K3DVector( pTargetPos->x, pTargetPos->y, 0.0f ); K3DVector vSelf = *m_pReceiver->GetPosition(); vSelf.z = 0.f; K3DVector attackview; K3DVector vDist = vTarget - vSelf; float tar_range = fTargetSize; float my_range = infoState.fRange + GetSize( m_pReceiver ); K3DVectorNormalize( attackview, vDist ); //원거리 공격 이라면 if( NotProximityAttack( NewState ) ) { //이동 중이다 if( m_pReceiver->IsMoving() ) { //회전 중이다 if( m_pReceiver->IsRotation() ) { float fTargetRoll = m_pReceiver->GetTargetRoll(); K3DMatrix zRollMat; K3DMatrixIdentity( zRollMat ); K3DMatrixRotationZ( zRollMat, fTargetRoll + 3.1415926f/2 ); attackview = K3DVector( 0.0f, 1.0f, 0.0f ); K3DVectorTransform( attackview, attackview, zRollMat ); attackview = -attackview; } else attackview = m_pReceiver->GetViewVector(); } //활을 들고 있다면 활 전용 스킬 if( IsBowAttack() ) { bIsNeedRange = true; } else { if( infoState.nSkillID != 0 ) { SkillBaseEx* s_data = GetSkillDB().GetSkillData( infoState.nSkillID ); if( s_data ) { //근접이 아니라면 if( s_data->GetCastRange() != -1 ) { bIsNeedRange = true; } } } } } //근접 공격이라면 else if( ProximityAttack( NewState ) ) { //활 공격이냐 if( IsBowAttack() ) { if( m_pReceiver->IsMoving() ) { if( m_pReceiver->IsRotation() ) { float fTargetRoll = m_pReceiver->GetTargetRoll(); K3DMatrix zRollMat; K3DMatrixIdentity( zRollMat ); K3DMatrixRotationZ( zRollMat, fTargetRoll + 3.1415926f/2 ); attackview = K3DVector( 0.0f, 1.0f, 0.0f ); K3DVectorTransform( attackview, attackview, zRollMat ); attackview = -attackview; } else attackview = m_pReceiver->GetViewVector(); } bIsNeedRange = true; } else { if( m_pReceiver->GetDBAttackRange() > 1.f ) bIsNeedRange = true; } } fDistance = GetDistance( vTarget, vSelf ); //전체 거리 /// 2010.10.14 줍기 거리와 구분 해줘야 된다 - prodongi bool isInRange = false; if (SObjectState::STATE_TAKEITEM != NewState) { fDistance = fDistance - (my_range + tar_range ); } //fDistance = fDistance - (my_range + tar_range ); if ( fDistance < 0.f ) // 레인지 범위 안쪽 { if( m_pReceiver->IsMoving() ) { //이동 중일때는 가는 방향에서 약간 앞으로 이동.. pos = vSelf + ( attackview * (GameRule::DEFAULT_UNIT_SIZE)); // 1m 더 간다. } else { //정지 중... 지금 자리 준다. pos = vSelf; // 일단은 그대로 쓴다... // 나중에 너무 가까울때를 처리 해야 할 수도 있다. } } else { K3DVector vRadius; if( bIsNeedRange ) //원거리 공격 { /// 2010.10.21 - prodongi //if( pTarget->IsMoving() ) if(pTarget && pTarget->IsMoving() ) { // ArMoveVector _mv = pTarget->GetArObject().mv; // DWORD dwEndTime = pTarget->GetArObject().mv.GetTargetPosTime(); // DWORD dwProcTime = pTarget->GetArObject().mv.GetProcTime(); // DWORD dwCurTime = GetArTime(); // dwCurTime += (dwEndTime - dwProcTime)/2; // _mv.Step( dwCurTime ); // K3DVector vTargetFinalPos( _mv.x, _mv.y, 0.0f ); // vRadius = vSelf - vTargetFinalPos; vRadius = vSelf - vTarget; vRadius.Normalize(); float needdist = tar_range + my_range; vRadius = vRadius * (needdist-(float)GameRule::DEFAULT_UNIT_SIZE); pos = vTarget + vRadius; } else { vRadius = vSelf - vTarget; vRadius.Normalize(); float needdist = tar_range + my_range; vRadius = vRadius * (needdist-(float)GameRule::DEFAULT_UNIT_SIZE); pos = vTarget + vRadius; } } /// 2010.10.21 타겟이 없는 것은 타겟이 제자리에 있다고 생각해도 될 것 같다 - prodongi //else if( !pTarget->IsMoving() ) //근접이고 타겟이 제자리에 있다면 else if( !pTarget || !pTarget->IsMoving() ) //근접이고 타겟이 제자리에 있다면 { vRadius = vSelf - vTarget; vRadius.Normalize(); /// 2010.10.14 - prodongi if (SObjectState::STATE_TAKEITEM == NewState) { vRadius = vRadius * (GameRule::MAX_TAKE_ITEM_RANGE*c_fRangeAcceptable); //80% 앞으로 땡겨주자 } else { float needdist = tar_range + GetSize( m_pReceiver ); vRadius = vRadius * (needdist*c_fRangeAcceptable); //80% 앞으로 땡겨주자 } //float needdist = tar_range + GetSize( m_pReceiver ); //vRadius = vRadius * (needdist*c_fRangeAcceptable); //80% 앞으로 땡겨주자 pos = vTarget + vRadius; } /// 2010.10.21 - prodongi //else //근접이고 타겟이 이동중이면 else //근접이고 타겟이 이동중이면 { //예측 이동 ArMoveVector _mv = pTarget->GetArObject().mv; DWORD dwEndTime = pTarget->GetArObject().mv.GetTargetPosTime(); DWORD dwProcTime = pTarget->GetArObject().mv.GetProcTime(); DWORD dwCurTime = GetArTime(); if( (int)(dwEndTime - dwProcTime) <= 60 ) dwCurTime = dwEndTime - 30; else dwCurTime = dwCurTime + 50; _mv.Step( dwCurTime ); pos.x = _mv.x; pos.y = _mv.y; } } } else pos = GetChasePos(); //보정 해야 한다. m_pReceiver->CorrectivePosition( pos, newpos ); //정지할 위치를 구했다면 더이상 정지할 위치를 필요로 하지 않는다 if( m_nNeedStopPos != NOT_NEED_STOP_POS ) m_nNeedStopPos = INSPECTED_STOP_POS; newpos.x = (int)newpos.x; newpos.y = (int)newpos.y; newpos.z = (int)newpos.z; return newpos; */ } K3DVector SObjectStateMachine::GetChasePos() { K3DVector vSelf = *m_pReceiver->GetPosition(); K3DVector attackview = m_pReceiver->GetViewVector(); //이동 중이라면 내가 클릭한 이동 위치로 방향을 구하자 if( m_pReceiver->IsMoving() ) { if( m_pReceiver->IsRotation() ) { float fTargetRoll = m_pReceiver->GetTargetRoll(); K3DMatrix zRollMat; K3DMatrixIdentity( zRollMat ); K3DMatrixRotationZ( zRollMat, fTargetRoll + 3.1415926f/2 ); attackview = K3DVector( 0.0f, 1.0f, 0.0f ); K3DVectorTransform( attackview, attackview, zRollMat ); attackview = -attackview; } else { attackview = *m_pReceiver->GetTerrainPickPos() - vSelf; attackview.Normalize(); } // float needdist = 4.f; float needdist = (float)GameRule::DEFAULT_UNIT_SIZE; vSelf = vSelf + ( attackview * needdist ); } //길찾기 땜에 int로 변환한다 안그럼 오차생김 K3DVector pos = K3DVector( (int)vSelf.x, (int)vSelf.y, (int)vSelf.z ); //정지할 위치를 구했다면 더이상 정지할 위치를 필요로 하지 않는다 if( m_nNeedStopPos != NOT_NEED_STOP_POS ) m_nNeedStopPos = INSPECTED_STOP_POS; return pos; // return vSelf; } K3DVector SObjectStateMachine::GetCreatureChasePos( SGameAvatarEx* pReceiver, SGameAvatarEx* pTarget, K3DVector* pTargetPos, const SStateInfo &infoState, SObjectState::ID NewState ) { //위에서 pReceiver, pTarget 검사하기 때문에 NULL 체크 하지 않는다 float fDistance; bool bIsNeedRange = false; K3DVector pos; K3DVector vTarget = *pTargetPos; K3DVector vSelf = *pReceiver->GetPosition(); vTarget.z = 0.f; vSelf.z = 0.f; K3DVector attackview; K3DVector vDist = vTarget - vSelf; float tar_range = GetSize( pTarget ); float my_range = infoState.fRange + GetSize( m_pReceiver ); K3DVectorNormalize( attackview, vDist ); //원거리 공격 이라면 if( NotProximityAttack( NewState ) ) { //이동 중이다 if( m_pReceiver->IsMoving() ) { //회전 중이다 if( m_pReceiver->IsRotation() ) { float fTargetRoll = m_pReceiver->GetTargetRoll(); K3DMatrix zRollMat; K3DMatrixIdentity( zRollMat ); K3DMatrixRotationZ( zRollMat, fTargetRoll + 3.1415926f/2 ); attackview = K3DVector( 0.0f, 1.0f, 0.0f ); K3DVectorTransform( attackview, attackview, zRollMat ); attackview = -attackview; } else attackview = m_pReceiver->GetViewVector(); } //활을 들고 있다면 활 전용 스킬 if( IsBowAttack() ) { bIsNeedRange = true; } else { if( infoState.nSkillID != 0 ) { SkillBaseEx* s_data = GetSkillDB().GetSkillData( infoState.nSkillID ); if( s_data ) { //근접이 아니라면 if( s_data->GetCastRange() != -1 ) { bIsNeedRange = true; } } } } } //근접 공격이라면 else if( ProximityAttack( NewState ) ) { //활 공격이냐 if( IsBowAttack() ) { if( m_pReceiver->IsMoving() ) { if( m_pReceiver->IsRotation() ) { float fTargetRoll = m_pReceiver->GetTargetRoll(); K3DMatrix zRollMat; K3DMatrixIdentity( zRollMat ); K3DMatrixRotationZ( zRollMat, fTargetRoll + 3.1415926f/2 ); attackview = K3DVector( 0.0f, 1.0f, 0.0f ); K3DVectorTransform( attackview, attackview, zRollMat ); attackview = -attackview; } else attackview = m_pReceiver->GetViewVector(); } bIsNeedRange = true; } else { if( m_pReceiver->GetDBAttackRange() > 1.f ) bIsNeedRange = true; } } fDistance = GetDistance( vTarget, vSelf ); //전체 거리 fDistance = fDistance - (my_range + tar_range ); if ( fDistance < 0.f ) // 레인지 범위 안쪽 { if( m_pReceiver->IsMoving() ) { //이동 중일때는 가는 방향에서 약간 앞으로 이동.. pos = vSelf + ( attackview * (GameRule::DEFAULT_UNIT_SIZE)); // 1m 더 간다. } else { //정지 중... 지금 자리 준다. pos = vSelf; // 일단은 그대로 쓴다... // 나중에 너무 가까울때를 처리 해야 할 수도 있다. } } else // 범위 밖 레인지 만큼 다가섬 { K3DVector vRadius; if( bIsNeedRange ) //원거리 공격 { if( pTarget->IsMoving() ) { vRadius = vSelf - vTarget; vRadius.Normalize(); float needdist = tar_range + my_range; vRadius = vRadius * (needdist-(float)GameRule::DEFAULT_UNIT_SIZE); pos = vTarget + vRadius; } else { vRadius = vSelf - vTarget; vRadius.Normalize(); float needdist = tar_range + my_range; vRadius = vRadius * (needdist-(float)GameRule::DEFAULT_UNIT_SIZE); pos = vTarget + vRadius; } } else if( !pTarget->IsMoving() ) //근접이고 타겟이 제자리에 있다면 { vRadius = vSelf - vTarget; vRadius.Normalize(); float needdist = tar_range + GetSize( m_pReceiver ); vRadius = vRadius * (needdist*c_fRangeAcceptable); //80% 앞으로 땡겨주자 pos = vTarget + vRadius; } else //근접이고 타겟이 이동중이면 { //예측 이동 ArMoveVector _mv = pTarget->GetArObject().mv; DWORD dwEndTime = pTarget->GetArObject().mv.GetTargetPosTime(); DWORD dwProcTime = pTarget->GetArObject().mv.GetProcTime(); DWORD dwCurTime = GetArTime(); if( (int)(dwEndTime - dwProcTime) <= 60 ) dwCurTime = dwEndTime - 30; else dwCurTime = dwCurTime + 50; _mv.Step( dwCurTime ); pos.x = _mv.x; pos.y = _mv.y; } } return pos; } // 2010.06.15 - prodongi void SObjectStateMachine::setAutoAttack(bool autoAttack) { m_bAutoAttack = autoAttack; } AR_UNIT SObjectStateMachine::GetSize( SGameAvatarEx *pObject ) { AR_UNIT size = pObject->GetSize()/2.f; // 캐릭터 사이즈는 지름이라 2 나눈다. AR_UNIT scale = pObject->GetScale(); // 캐릭터 사이즈 비율 if( pObject->GetObjType() == TS_ENTER::GAME_PLAYER ) { // 플레이어의 경우 서버에서 금지된 융합, 변신등으로 인한 사이즈가 반영되지 않는다. size = 1.f / 2.f; scale = 1.f; } AR_UNIT allsize = size*scale * GameRule::DEFAULT_UNIT_SIZE; // 실제 좌표로 변환 return allsize; } //현재 타겟과의 유효거리와 사거리 검사 bool SObjectStateMachine::IsRange( K3DVector & my_pos, float fmy_size, const K3DVector & pos, float ftarget_size, float fRange, bool bCollisionToLine, int & nResultLength ) { K3DVector vSelf = my_pos; // *m_pReceiver->GetPosition(); K3DVector vTarget = pos; vSelf.z = 0.f; vTarget.z = 0.f; float target_size = ftarget_size; float my_size = fmy_size; // GetSize( m_pReceiver ); float fDistance = GetDistance( vTarget, vSelf ); if( fDistance > 0.0f ) { //거리가 양수 이면, 사거리와 크기를 뺀다. fDistance = fDistance - (fRange + my_size + target_size ); nResultLength = (int)fDistance; if( 0 <= fDistance && fDistance < 1 ) // 정확도 오차가 있을 수 있어 정확히 맞는 범위(fDistance == 0)는 사거리에 들었다고 판단하지 않는다. { nResultLength = 1; } if( nResultLength <= 0 ) { // 사거리 안에 들었다면 앞에 무언가로 막혀있는지 확인한다. if( bCollisionToLine == false || m_pReceiver->CollisionToLine( vSelf, vTarget ) == false ) { return true; } } return false; } else { nResultLength = (int)fDistance; return true; } nResultLength = (int)fDistance; return false; } void SObjectStateMachine::SetAttackLock( bool bAttackLock ) { m_bLockAttack = bAttackLock; #ifndef NDEBUG if( m_bLockAttack ) _oprint( "SetAttackLock - Lock\n" ); else _oprint( "SetAttackLock - UnLock\n" ); #endif } void SObjectStateMachine::Process( DWORD dwTime ) { m_dwTime = dwTime; if( !m_queInputState.empty() ) { PENDING_INPUT_STATE* prevstate = &m_queInputState.front(); if( prevstate->nInputID != RQ_MOVE ) { PendingInput( prevstate ); m_queInputState.pop(); return; } else if( m_queInputState.size() >= 3 ) //큐에 쌓인 입력 정보가 3개를 넘었다면 버그 다 날려버리자 { while( !m_queInputState.empty() ) m_queInputState.pop(); } } if( !m_pReceiver->IsMoving() ) { m_nNeedStopPos = NEED_STOP_POS; } //전투 대기 상태 if( GetMode() == MODE_ATTACK ) { if( m_dwAttackTime == 0 ) m_dwAttackTime = m_dwTime; bool bCurState = (GetCurrentState() == SObjectState::STATE_SKILL_END ); bool bPrevState = (GetPrevState() == SObjectState::STATE_ATTACK || GetPrevState() == SObjectState::STATE_SHOOTING); bool bPendingState = (GetPendingState() == SObjectState::STATE_NONE); //현재 상태는 IDLE이고 Pending된 명령이 없어야 하며 //이전 상태가 공격이였거나 || 자동 공격을 해야 할경우 if( bCurState && bPendingState && ( bPrevState || m_bAutoAttack ) ) { // sonador 1.8.13 아바타 IDLE 모션 동작 구현 if( IsMoving() == false && ( m_pReceiver->GetCurrAnimationID() == ANI_DEFAULT01 || m_pReceiver->GetCurrAnimationID() == ANI_DEFAULT02 || m_pReceiver->GetCurrAnimationID() == ANI_IDLE ) ) { m_pReceiver->CheckPrevState(); SetDefaultPrevState(); // 2010.06.15 - prodongi //m_bAutoAttack = false; setAutoAttack(false); m_dwAttackTime = m_dwTime; } return; } if( (m_dwTime - m_dwAttackTime) > ATTACK_WAIT_TIME ) { //Attack Casting Fire중일때 시간 재설정 if( m_pReceiver->IsAttacking() || m_pReceiver->IsUsingSkill() ) { m_dwAttackTime = m_dwTime; } else { SetMode( MODE_NORMAL ); m_pReceiver->ReqNormal(); //기본 상태로 //안 움직이고 있는 상태 일때만, 통지~ if( IsMoving() == false && m_pReceiver->IsMoving() == false ) { //앉아 있으면, 안함. if( m_pReceiver->GetStatus() & TS_ENTER::PlayerInfo::FLAG_SITDOWN ) return; StopCurrentState(); return; } } } } if( GetMode() == MODE_MOUNT ) { if( m_idCurState == SObjectState::STATE_MOUNT ) { //플레이어 인지 체크, 이동중인지 체크 if( /*m_pReceiver->GetObjType() == TS_ENTER::PLAYER &&*/ !m_pReceiver->IsMoving() ) { //이동이 끝났다면 IDLE상태로 StopCurrentState(); } } } // 탑승모드가 아니고 내리는 상태라면 else if( m_idCurState == SObjectState::STATE_UNMOUNT ) { StopCurrentState(); } if( NULL != m_pCurrentState ) { //스킬 완료는 들어 오자 마자 Stop 시킨다. if ( m_idCurState == SObjectState::STATE_FIRE || m_idCurState == SObjectState::STATE_SKILL_END ) { if( m_pReceiver->IsUseSkill_complete() && !m_pReceiver->IsUsingSkill() ) { StopCurrentState(); } return; } //공격 검사 if( m_idCurState == SObjectState::STATE_ATTACK || m_idCurState == SObjectState::STATE_SHOOTING ) { if( m_dwAttackDelayTime == 0 ) m_dwAttackDelayTime = m_dwTime; if( !m_pReceiver->IsAttacking() && !m_pReceiver->IsUsingSkill() ) //Fixed Follow Problem // ZONE { StopCurrentState(); return; } //예약된게 있다면 else if( m_idPendingState != SObjectState::STATE_NONE ) { //휴지기가 없다면 바로 캔슬을 날리자 if( m_infoCurrent.dwDuration <= 0 ) m_pReceiver->ReqCancelAction(); else { if( ( m_dwTime - m_dwAttackDelayTime ) >= (m_infoCurrent.dwDuration / 2) ) m_pReceiver->ReqCancelAction(); } } return; } if( m_idCurState == SObjectState::STATE_ATTACK_END ) { if( !m_pReceiver->IsAttacking() && !m_pReceiver->IsUsingSkill() ) { StopCurrentState(); } return; } /* if( m_idCurState == SObjectState::STATE_CAST_CANCEL ) { ClearAllState(); return; }*/ if( m_idCurState == SObjectState::STATE_IDLE || m_idCurState == SObjectState::STATE_CAST_CANCEL ) { if( m_pReceiver->IsUseSkill_complete() && !m_pReceiver->IsUsingSkill() ) // floyd #2.3.1.15 { StopCurrentState(); } return; } // TODO : ATTACK_END가 오면 state 중지 // 지속 시간 /* DWORD dwDurationTime = m_pCurrentState->GetInfo().dwDuration; // 지속시간이 정해져 있고, 시간이 초과 되었다면 현재 state를 중지 if( 0 < dwDurationTime && (m_dwTime - m_dwCurrentStateStartTime) >= dwDurationTime ) { StopCurrentState(); return; }*/ // 타겟의 유효성 검사 SGameAvatarEx* pTarget = NULL; if ( m_infoCurrent.hTarget != 0 ) { pTarget = (SGameAvatarEx*)m_pReceiver->GetGameObject( m_infoCurrent.hTarget ); if ( pTarget == NULL ) // 타겟이 없어지면 { StopCurrentState(); return; } } //추적이나 이동 상태이고, 이동이 완료된 경우 if( IsMoving() == true && m_pReceiver->IsMoving() == false ) { // 목적지 도착인 경우 StopCurrentState(); return; } if( ( m_idCurState == SObjectState::STATE_CHASE && m_pReceiver->IsChasing() ) || ( m_idCurState == SObjectState::STATE_MOVE && m_pReceiver->IsChasing() ) ) // 이동 상태이고 길찾기는 안끝났고 추적 모드면 { if( pTarget == NULL && !m_infoCurrent.isRegionTarget ) return; static DWORD dwFindDetourTick = 100; if( (dwTime - m_dwFindDetour) > dwFindDetourTick ) { m_dwFindDetour = dwTime; K3DVertex vtMyPos = *m_pReceiver->GetPosition(); vtMyPos.z = 0.f; // 아이템을 줍는다면 그냥 목적지까지 간다. if( m_idNextState == SObjectState::STATE_TAKEITEM ) { return; } int nReusultLen = 0; if ( m_infoCurrent.isRegionTarget && IsRange(vtMyPos, GetSize( m_pReceiver ), m_infoCurrent.vRegionTargetPos, 1, m_infoCurrent.fRange, true, nReusultLen ) ) { m_pReceiver->EraseDetour(); m_pReceiver->SetArObjectStop(); m_infoCurrent.vTargetPos = vtMyPos; SendServerReq( m_idCurState, m_infoCurrent ); }else if( pTarget && IsRange( vtMyPos, GetSize( m_pReceiver ), *pTarget->GetPosition(), GetSize( pTarget ), m_infoCurrent.fRange*c_fRangeAcceptable, true, nReusultLen ) ) { m_pReceiver->EraseDetour(); m_pReceiver->SetArObjectStop(); m_infoCurrent.vTargetPos = vtMyPos; SendServerReq( m_idCurState, m_infoCurrent ); } else { K3DVertex vtTargetPos; if ( m_infoCurrent.isRegionTarget ) vtTargetPos = m_infoCurrent.vRegionTargetPos; else vtTargetPos = *pTarget->GetPosition(); size_t numDetourPoints = m_pReceiver->GetDetourSize(); int x = 0; int y = 0; //길찾기로 찾은 경로의 마지막 위치를 가져온다 if( m_pReceiver->GetDetourPoint(numDetourPoints - 1, x, y) ) { K3DVector vFinalPos = K3DVector( x, y, 0.f ); K3DVector vTargetTemp = vtTargetPos; vTargetTemp.z = 0; int nFinalLen = static_cast< int >( GetDistance( vFinalPos, vtMyPos ) ); int nTargetLen = static_cast< int >( GetDistance( vTargetTemp, vtMyPos ) ); // 목적지 보다 타겟이 더 가까이 있는가? if( nTargetLen < nFinalLen ) { K3DVertex vtNextMove; /*if( m_pReceiver->GetObjType() == TS_ENTER::GAME_SUMMON ) vtNextMove = GetCreatureChasePos( m_pReceiver, pTarget, pTarget->GetPosition(), m_infoCurrent, m_idNextState ); else*/ if ( m_infoCurrent.isRegionTarget ) vtNextMove = GetChasePos( NULL, &m_infoCurrent.vRegionTargetPos, 1, m_infoCurrent, m_idNextState ); else vtNextMove = GetChasePos( pTarget, pTarget->GetPosition(), GetSize(pTarget), m_infoCurrent, m_idNextState ); m_pReceiver->FindDetour(vtNextMove, true, true); m_pReceiver->MoveDetour(vtNextMove); m_infoCurrent.vTargetPos = vtNextMove; SendServerReq( m_idCurState, m_infoCurrent ); } // 목적지에서 타겟의 공격이 가능한가? else if( pTarget && !IsRange( vFinalPos, GetSize( m_pReceiver ), *pTarget->GetPosition(), GetSize( pTarget ), m_infoCurrent.fRange*c_fRangeAcceptable, true, nReusultLen ) ) { // 마지막 위치가 공격 가능하지 않다면 다시 이동 K3DVertex vtNextMove; /*if( m_pReceiver->GetObjType() == TS_ENTER::GAME_SUMMON ) vtNextMove = GetCreatureChasePos( m_pReceiver, pTarget, pTarget->GetPosition(), m_infoCurrent, m_idNextState ); else*/ if ( m_infoCurrent.isRegionTarget ) vtNextMove = GetChasePos( NULL, &m_infoCurrent.vRegionTargetPos, 1, m_infoCurrent, m_idNextState ); else vtNextMove = GetChasePos( pTarget, pTarget->GetPosition(), GetSize(pTarget), m_infoCurrent, m_idNextState ); m_pReceiver->FindDetour(vtNextMove, true, true); m_pReceiver->MoveDetour(vtNextMove); m_infoCurrent.vTargetPos = vtNextMove; SendServerReq( m_idCurState, m_infoCurrent ); } } } } return; } if ( m_idCurState == SObjectState::STATE_ATTACK ) { if ( ((SGameAvatarEx*)pTarget)->IsLive() == false && m_pReceiver->IsAniLock() == false ) { // 죽었고, 공격 모션이 완료 되었다. StopCurrentState(); } // 레인지 체크 - 타겟과 레인지가 존재할때 else if ( m_infoCurrent.bIsMovePos == false && m_infoCurrent.fRange > 0.0f ) { K3DVertex vtMyPos, vtTargetPos; K3DVertex vtNextMove; vtMyPos = *m_pReceiver->GetPosition(); vtTargetPos = *pTarget->GetPosition(); int nReusultLen = 0; if( !IsRange( vtMyPos, GetSize( m_pReceiver ), vtTargetPos, GetSize( pTarget ), m_infoCurrent.fRange*c_fRangeAcceptable, true, nReusultLen ) ) { //타겟의 위치가 바뀌었을때 위치 갱신 /*if( m_pReceiver->GetObjType() == TS_ENTER::GAME_SUMMON ) vtNextMove = GetCreatureChasePos( m_pReceiver, pTarget, pTarget->GetPosition(), m_infoCurrent, m_idNextState ); else*/ vtNextMove = GetChasePos( pTarget, pTarget->GetPosition(), GetSize(pTarget), m_infoCurrent, m_idNextState ); m_pReceiver->FindDetour(vtNextMove, true, true); m_pReceiver->MoveDetour(vtNextMove); m_infoCurrent.vTargetPos = vtNextMove; SObjectState::ID id = m_idCurState; SStateInfo info = m_infoCurrent; // 서버에 공격취소 요청 m_pReceiver->ReqCancelAction(); // RequestNewState( SObjectState::STATE_CHASE, info ); SendServerReq( SObjectState::STATE_CHASE, info ); SetNextState( id, info ); } } } } } SObjectState* SObjectStateMachine::GetObjState( const SObjectState::ID State ) const { OBJSTATE_MAP::const_iterator it = m_StateMap.find( State ); if( it != m_StateMap.end() ) return it->second; assert( false ); return NULL; } void SObjectStateMachine::StartNewState( SObjectState::ID state, const SStateInfo &info ) { m_pCurrentState = GetObjState( state ); if( m_pCurrentState ) { m_pCurrentState->SetInfo( info ); m_idCurState = state; m_idCreatureState = state; m_infoCurrent = info; m_dwCurrentStateStartTime = m_dwTime; m_pCurrentState->m_infoState.dwStartTime = m_infoCurrent.dwStartTime = m_dwTime; m_dwAttackDelayTime = 0; if ( m_pReceiver ) { m_pReceiver->OnChangeState( m_idCurState, m_infoCurrent ); } } else { ClearInfo(); } } void SObjectStateMachine::SetPendingState( SObjectState::ID state, const SStateInfo &infoState ) { // _oprint( "[상태머신 : SetPendingState]\n" ); m_idPendingState = state; m_infoPending = infoState; } void SObjectStateMachine::ClearPending() { // _oprint( "[상태머신 : ClearPending]\n" ); m_idPendingState = SObjectState::STATE_NONE; } void SObjectStateMachine::AddState( SObjectState* pObjState ) { // _oprint( "[상태머신 : AddState]\n" ); if( m_StateMap.find( pObjState->GetID() ) == m_StateMap.end() ) m_StateMap.insert( std::make_pair( pObjState->GetID(), pObjState ) ); } void SObjectStateMachine::RemoveState( const SObjectState::ID State ) { // _oprint( "[상태머신 : RemoveState]\n" ); OBJSTATE_MAP::iterator it = m_StateMap.find( State ); if( it != m_StateMap.end() ) { delete it->second; m_StateMap.erase( it ); } } void SObjectStateMachine::SetDefaultState( const SObjectState::ID State, const SStateInfo &info ) { // _oprint( "[상태머신 : SetDefaultState]\n" ); m_idDefaultState = State; m_infoDefault = info; } //전투형 상태인가 bool SObjectStateMachine::IsCombatState( SObjectState::ID newstate ) { return ( newstate == SObjectState::STATE_AIMING || newstate == SObjectState::STATE_SHOOTING || newstate == SObjectState::STATE_ATTACK || newstate == SObjectState::STATE_CAST || newstate == SObjectState::STATE_FIRE || newstate == SObjectState::STATE_USEITEM ); } //일반 공격인지 검사 bool SObjectStateMachine::ProximityAttack( SObjectState::ID stateId ) { if( stateId == SObjectState::STATE_ATTACK || stateId == SObjectState::STATE_TAKEITEM ) return true; return false; } //원거리 공격인지 검사 bool SObjectStateMachine::NotProximityAttack( SObjectState::ID stateId ) { if( stateId == SObjectState::STATE_CAST || stateId == SObjectState::STATE_AIMING || stateId == SObjectState::STATE_FIRE || stateId == SObjectState::STATE_SHOOTING || stateId == SObjectState::STATE_USEITEM ) return true; return false; } //활 공격 인지 검사 // AziaMafia Double Crossbow //bool SObjectStateMachine::IsBowAttack() //{ // ItemBase::ItemClass nLeftClass = m_pReceiver->GetLeftWeaponInfo(); // ItemBase::ItemClass nRightClass = m_pReceiver->GetRightWeaponInfo(); // if ((nLeftClass == ItemBase::CLASS_LIGHT_BOW || nLeftClass == ItemBase::CLASS_HEAVY_BOW) || // (nRightClass == ItemBase::CLASS_LIGHT_BOW || nRightClass == ItemBase::CLASS_HEAVY_BOW)) // { // return true; // } // // return false; //} // From ZONE source; dual crossbows bool SObjectStateMachine::IsBowAttack() { ItemBase::ItemClass nLeftClass = m_pReceiver->GetLeftWeaponInfo(); ItemBase::ItemClass nRightClass = m_pReceiver->GetRightWeaponInfo(); if( ( nLeftClass == ItemBase::CLASS_LIGHT_BOW || nLeftClass == ItemBase::CLASS_HEAVY_BOW || nLeftClass == ItemBase::CLASS_CROSSBOW ) || ( nRightClass == ItemBase::CLASS_LIGHT_BOW || nRightClass == ItemBase::CLASS_HEAVY_BOW || nRightClass == ItemBase::CLASS_CROSSBOW ) ) { return true; } return false; } void SObjectStateMachine::ClearAllState() { m_pCurrentState = NULL; m_idCurState = SObjectState::STATE_NONE; m_idCreatureState = SObjectState::STATE_IDLE; } void SObjectStateMachine::Dead() { m_pCurrentState = NULL; m_idCurState = SObjectState::STATE_NONE; m_idCreatureState = SObjectState::STATE_IDLE; ClearPending(); ClearNextState(); SetDefaultPrevState(); } void SObjectStateMachine::ClearPendingNextState() { m_idPendingState = SObjectState::STATE_NONE; m_idNextState = SObjectState::STATE_NONE; while( !m_queInputState.empty() ) m_queInputState.pop(); } void SObjectStateMachine::InputFreezing() { m_bInputFreezing = true; } void SObjectStateMachine::InputThawing() { m_bInputFreezing = false; }