#include #include #include #include "StructNPC.h" #include "GameContent.h" #include "SendMessage.h" #include "GameMessage.h" #include "GameProc.h" #include "StructSummon.h" #include "StructPlayer.h" #include "StructMonster.h" #include "StructRoamer.h" #include "GameAllocator.h" #include "StructSkill.h" #include "ThreadPlayerHelper.h" extern __declspec( thread ) XSEH::THREAD_INFO s_ThreadInfo; ArPosition StructNPC::getNonDuplicateAttackPos( StructCreature * pEnemy ) { AR_TIME t = GetArTime(); ArPosition targetPos = pEnemy->GetCurrentPosition( t ); AR_UNIT dist = GetUnitSize() / 2 + pEnemy->GetUnitSize() / 2 + GetRealAttackRange(); float angle = 3.1415f * 2 * 0.1f; float fMod = (float) XRandom( -1, 3 ); angle *= fMod == 0 ? 1.0f : fMod; ArPosition myPosition = GetCurrentPosition( t ); AR_UNIT distance = myPosition.GetDistance( targetPos ); targetPos.x += cos( angle ) * (float) dist; targetPos.y += sin( angle ) * (float) dist; return targetPos; } AR_HANDLE StructNPC::GetEnemyUID() { return m_hEnemy; } void StructNPC::AI_processAttack( AR_TIME t ) { AR_HANDLE enemy_handle = GetEnemyUID(); StructCreature* pEnemy = static_cast< StructCreature* >( GameObject::raw_get( enemy_handle ) ); // 주적이 없거나 적이 죽었거나 적이 유효하지 않으면 다음 적을 찾자. { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); if( !pEnemy || pEnemy->GetHandle() != enemy_handle || pEnemy->IsDead() || !pEnemy->IsInWorld() ) { comeBackHome( false ); // 한타임 쉬고, 다음 턴에 어택들어가주자. return; } } { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( this, pEnemy ) ); if( isFirstAttack() ) { pEnemy->OnUpdate(); m_StatusFlag.Off( STATUS_FIRST_ATTACK ); } } //AI_processAttack( pEnemy, t ); } void StructNPC::comeBackHome( bool bInvincible ) { if( !IsMovable() ) return; // 정상상태로~ SetStatus( StructNPC::STATUS_NORMAL ); m_hEnemy = NULL; m_nLastEnemyDistance = 0; m_bComeBackHome = true; // 귀환 std::vector< ArPosition > newPos; newPos.push_back( ArPosition( m_RespawnX, m_RespawnY, 0 ) ); if( bInvincible ) { SetInvincible( true ); SetPendingMove( newPos, GetRealMoveSpeed() * 2 ); } else { SetPendingMove( newPos, GetRealMoveSpeed() ); } } void StructNPC::processDead( AR_TIME t ) { bool bDelete = false; { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); // 업데이트 주기를 5초로 수정. ArcadiaServer::Instance().SetObjectPriority( this, ArObject::UPDATE_PRIORITY_NORMAL ); // 시체유지시간만큼 지났으면 없애버리자 if( GetDeadTime() + GameRule::GetCorpseHoldTime() < t ) { ArcadiaServer::Instance().SetObjectPriority( this, ArObject::UPDATE_PRIORITY_IDLE ); // 월드에서 제거한다. if( IsInWorld() ) { RemoveNPCFromWorld( this ); } bDelete = true; } } // no other lock~ if( bDelete && m_pDeadHandler ) { m_pDeadHandler->onNPCDead( this ); } } void StructNPC::onProcess( int nThreadIdx ) { char buf[255]; s_sprintf( buf, _countof( buf ), "thread.scheduler.%d.proc", nThreadIdx ); ENV().Set( buf, "StructNPC" ); AR_TIME t = GetArTime(); s_sprintf( s_ThreadInfo.job_info, _countof( s_ThreadInfo.job_info ), "StructNPC(0x%08X)", (UINT_PTR)this ); s_ThreadInfo.last_execute_time = t; ThreadPlayerHelper TPHelper( NULL ); { switch( GetStatus() ) { case StructNPC::STATUS_DEAD: { // 죽은 녀석이면 시체 사라지는 처리만 하면 된다. processDead( t ); return; } break; case StructNPC::STATUS_NORMAL: { if( GetNPCBase()->is_periodic && GetNPCBase()->end_of_period <= time( NULL ) ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); onDead( NULL, false ); SetDeadTime( 0 ); RemoveNPCFromWorld( this ); return; } if( GetRoamer() ) { if( GetPriority() != ArSchedulerObject::UPDATE_PRIORITY_HIGH ) { ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_HIGH ); } } else { if( !HasPendingMove() && GetPriority() > ArObject::UPDATE_PRIORITY_NORMAL ) { ArcadiaServer::Instance().SetObjectPriority( this, ArObject::UPDATE_PRIORITY_NORMAL ); } } if( m_nLastUpdateTime + 3000 < t || HasPendingMove() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); // 시간대 제한 진행 퀘스트의 수락 가능 조건이 변경되었을 수 있으므로 30초에 한 번쯤 주변에 업데이트 날려 줌 SetNeedToBroadcastStatusFlag(); OnUpdate(); if( IsDead() ) return; } // 움직이고 있지 않다면 이리저리 돌아다니게 처리 if( IsActable() && !m_bComeBackHome ) processFirstAttack( t ); // if( IsActable() && IsMovable() && !m_bComeBackHome ) processMove( t ); } break; case StructNPC::STATUS_TRACKING: case StructNPC::STATUS_FIND_ATTACK_POS: case StructNPC::STATUS_ATTACK: { // 전투중이면 전투 AI 처리 { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); if( m_nLastUpdateTime + 50 < t ) OnUpdate(); } if( IsActable() ) { if( IsDead() ) return; // 업데이트 안에서 뒤지는 경우가 생기는구나.-_- if( GetRoamer() && !GetRoamer()->IsPaused() ) { GetRoamer()->PauseRoaming(); GetRoamer()->PendHateShare( GetHandle(), GetEnemyUID(), 1, StructRoamer::HATE_TYPE_SHARE_FIRST | StructRoamer::HATE_TYPE_FULL_SHARE ); break; } AI_processAttack( t ); } } break; } } if( IsDead() ) return; if( HasPendingMove() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); processPendingMove(); } // 움직이고 있으면 움직임 연산 if( ArObject::IsMoving() && IsMovable() ) { processWalk( t ); } // StructCreature를 상속받은 클래스는 모두 호출을 해야하지만 현재는 굳이 호출할 필요가 없다. // StructCreature::onProcess( nThreadIdx ); } void StructNPC::processWalk( AR_TIME t ) { ArMoveVector tmp_mv; // 시간만큼 이동해 본다. { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); tmp_mv = GetMv(); } tmp_mv.Step( t ); // 만약 이동했는데 Region 변경이 일어났거나 혹은 이동이 멈추었다면 // 그것을 ArcadiaServer 에게 통지해준다. if( tmp_mv.GetRX() != GetRX() || tmp_mv.GetRY() != GetRY() || !tmp_mv.IsMoving() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithSpecificRegion( this, tmp_mv.GetRX(), tmp_mv.GetRY() ) ); if( m_bComeBackHome && !IsBattleMode() && !tmp_mv.IsMoving() ) { m_bComeBackHome = false; if( IsAttackableNPC() ) SetInvincible( false ); } ArcadiaServer::Instance().onRegionChange( this, t - lastStepTime, !tmp_mv.IsMoving() ); } } bool StructNPC::IsFirstAttacker() const { return m_pBaseInfo->offensive_type; } void StructNPC::processFirstAttack( AR_TIME t ) { if( !IsAttackableNPC() ) return; ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); // 죽었으면 스탑~ if( GetStatus() == StructNPC::STATUS_DEAD ) return; if( GetStatus() == StructNPC::STATUS_NORMAL ) { std::vector< AR_HANDLE > vResult; ArcadiaServer::Instance().EnumMovableObject( GetPos(), GetLayer(), GameRule::AGGRESIVE_MONSTER_REACTION_RANGE, &vResult, true, true ); AR_UNIT distance = GameRule::AGGRESIVE_MONSTER_REACTION_RANGE + 1; StructCreature * pTarget = NULL; for( std::vector< AR_HANDLE >::iterator it = vResult.begin(); it != vResult.end(); ++it ) { StructCreature* pCreature = static_cast< StructCreature* >( StructCreature::raw_get( *it ) ); if( !pCreature ) continue; if( IsEnemy( pCreature ) ) { if( IsFirstAttacker() ) { AR_UNIT _distance = GetPos().GetDistance( pCreature->GetCurrentPosition( t ) ); if( pCreature->IsSummon() && _distance > GameRule::AGGRESIVE_MONSTER_REACTION_RANGE / 2 ) { // 소환수는 절반. continue; } if( _distance < distance ) { distance = _distance; pTarget = pCreature; } } else { if( pCreature->IsAttacking() ) { pTarget = pCreature; break; } else if( pCreature->IsUsingSkill() ) { AR_HANDLE target = pCreature->GetCastSkill()->GetTargetHandle(); if( target ) { StructCreature* pSkillTarget = static_cast< StructCreature* >( StructCreature::raw_get( target ) ); if( pSkillTarget && ( pSkillTarget->IsPlayer() || pSkillTarget->IsSummon() ) && pSkillTarget->IsEnemy( pCreature ) ) { pTarget = pCreature; break; } } } } } } if( pTarget && pTarget->IsSummon() ) { for( std::vector< AR_HANDLE >::iterator it = vResult.begin(); it != vResult.end(); ++it ) { if( static_cast< StructSummon * >( pTarget )->GetMaster()->GetHandle() == (*it) ) { pTarget = static_cast< StructSummon * >( pTarget )->GetMaster(); break; } } } if( pTarget ) { SetAttacker( pTarget ); } } } bool StructNPC::StartAttack( AR_HANDLE target, bool bNeedFastReaction ) { if( StructCreature::StartAttack( target, bNeedFastReaction ) ) { SetStatus( StructNPC::STATUS_ATTACK ); return true; } return false; } void StructNPC::AI_processAttack( StructCreature * pEnemy, AR_TIME t ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjects( this, pEnemy ) ); // 적이 가시 범위 내인가..? if( !IsVisibleRegion( GetRX(), GetRY(), pEnemy->GetRX(), pEnemy->GetRY() ) || pEnemy->GetLayer() != GetLayer() ) { comeBackHome( false ); return; } // 공격 사정거리 밖이면 ?아가자 ArPosition enemyPosition = pEnemy->GetCurrentPosition( t ); ArPosition myPosition = GetCurrentPosition( t ); AR_UNIT enemy_distance = myPosition.GetDistance( enemyPosition ); if( ( pEnemy->IsMoving() && enemy_distance > GameRule::MONSTER_TRACKING_RANGE_BY_TIME ) || ( m_nLastEnemyDistance && enemy_distance > GameRule::MONSTER_TRACKING_RANGE && enemy_distance > m_nLastEnemyDistance ) ) { // 치고 빠지다니. 나쁜 녀석~ comeBackHome( false ); return; } m_nLastEnemyDistance = enemy_distance; AR_UNIT gap = GetUnitSize()/2 + pEnemy->GetUnitSize()/2; if( ( pEnemy->IsMoving() && ( enemy_distance - gap > GetRealAttackRange() * 1.5f ) ) || ( !pEnemy->IsMoving() && ( enemy_distance - gap > GetRealAttackRange() * 1.2f ) ) ) { if( !IsActable() || !IsMovable() || GetMovableTime() > t ) return; ArPosition targetPosition = enemyPosition; if( pEnemy->IsMoving() ) { AR_TIME nModTime = 15; // 게임 룰로 뺍시다아~ targetPosition = pEnemy->GetCurrentPosition( t + nModTime ); } else { findAttackablePosition( myPosition, targetPosition, enemy_distance, gap + GetRealAttackRange() ); } // { 길찾기 테스트 /* std::vector< ArPosition > vMoveInfo; if( GameRule::bMonsterPathFinding && !GameContent::IsBlocked( targetPosition.x, targetPosition.y ) ) { if( GameContent::FindPath( myPosition.x, myPosition.y, targetPosition.x, targetPosition.y, vMoveInfo ) == false ) { // ? } } */ // 추적상태로 변경 SetStatus( StructNPC::STATUS_TRACKING ); ArPosition homePosition( m_RespawnX, m_RespawnY ); AR_UNIT track_distance = myPosition.GetDistance( homePosition ); if( track_distance > GetChaseRange() ) { comeBackHome( true ); return; } if( m_nLastTrackTime + 50 < t ) { m_nLastTrackTime = t; float fMod = XRandom( 0, 9 ); fMod /= 100.0f; fMod += 1; if( GameContent::IsBlocked( targetPosition.x, targetPosition.y ) ) { return; } ArcadiaServer::Instance().SetMove( this, GetCurrentPosition( t ), targetPosition, (int) ( (float) GetRealMoveSpeed() * fMod ), true, GetArTime() ); } } else { if( GetStatus() == StructMonster::STATUS_FIND_ATTACK_POS && IsMoving( t ) ) return; if( !pEnemy->IsMoving( t ) && ( GetStatus() == StructMonster::STATUS_TRACKING || ( pEnemy->GetEnemyCount() < 6 && XRandom( 0, 99 ) < GameRule::MONSTER_FIND_ATTACK_POS_RATIO ) ) ) { std::vector< AR_HANDLE > vList; std::vector< AR_HANDLE >::iterator it; ArcadiaServer::Instance().EnumMovableObject( myPosition, GetLayer(), GetUnitSize() / 2, &vList ); for( it = vList.begin(); it != vList.end(); ++it ) { if( ( ::IsMonster( (*it) ) && (*it) > GetHandle() ) || ( pEnemy->IsPlayer() && static_cast< StructPlayer * >( pEnemy )->GetHandle() == (*it) ) ) { ArPosition attack_pos = getNonDuplicateAttackPos( pEnemy ); SetStatus( StructNPC::STATUS_FIND_ATTACK_POS ); if( GameContent::IsBlocked( attack_pos.x, attack_pos.y ) ) continue; ArcadiaServer::Instance().SetMove( this, myPosition, attack_pos, GetRealMoveSpeed() , true, GetArTime() ); return; } } } // 공격 상태로 변경 SetStatus( StructNPC::STATUS_ATTACK ); // 행동시간 체크 if( GetNextAttackableTime() <= t ) { if( !IsAttackable() || !pEnemy || !IsEnemy( pEnemy ) || pEnemy->IsDead() ) return; // 두들겨 패자~~ 랄라~ _ATTACK_INFO Damages[4]; bool bDoubleAttack = false; Attack( pEnemy, t, GetAttackInterval(), Damages, bDoubleAttack ); float fAttackMotionRatio = 0.5f; /* switch( m_pContentInfo->attack_speed_type ) { case 0: // 공속 느림 fAttackMotionRatio = 0.5f; break; case 1: fAttackMotionRatio = 0.6f; break; case 2: fAttackMotionRatio = 0.7f; break; } */ SetMovableTime( t + GetAttackInterval() * fAttackMotionRatio ); broadcastAttackMessage( pEnemy, Damages, GetAttackInterval()*10, ( GetNextAttackableTime() - t ) * 10, bDoubleAttack, false ); } } } void StructNPC::findAttackablePosition( const ArPosition & myPosition, ArPosition & enemyPosition, AR_UNIT distance, AR_UNIT gap ) { ArPosition duplicateEnemyPosition = enemyPosition; AR_UNIT walk_length = distance - gap; if( walk_length < 0 ) { enemyPosition = myPosition; return; } assert( walk_length >= 0 ); assert( int( distance ) ); float v = walk_length / (float)distance; // 가야할 비율 v += 0.1f; // 사거리보다 10% 정도 더 가자 enemyPosition.x -= myPosition.x; enemyPosition.y -= myPosition.y; enemyPosition.z -= myPosition.z; enemyPosition.x *= v; enemyPosition.y *= v; enemyPosition.z *= v; enemyPosition.x += myPosition.x; enemyPosition.y += myPosition.y; enemyPosition.z += myPosition.z; if( GameContent::IsBlocked( enemyPosition.x, enemyPosition.y ) ) { enemyPosition = duplicateEnemyPosition; } } void StructNPC::SetAttacker( StructCreature * pAttacker ) { if( !IsAttackableNPC() ) return; if( !m_hEnemy ) { StartAttack( pAttacker->GetHandle(), false ); } }