Files
Sun1602/Server/GameServer/NPC.cpp
T
2022-10-26 12:25:11 +08:00

1852 lines
61 KiB
C++

// NPC.cpp: implementation of the NPC class.
// @history
// ~09/12/29
// 09/12/29|waverix|change NPC moving controllers by '_NA001415_091123_CHANGE_MOB_AI_IN_SAFETY_AREA_'
// |TARGETS={ ExecuteThrust, (Thrust)MoveAndBroadcast }|
//
//==================================================================================================
#include "stdafx.h"
#include "./NPC.h"
// Utility
#include <PerfCheck.h>
//==================================================================================================
// ProgramCommon
#include <PublicMath.h>
#include <SkillInfoParser.h>
#include <NPCInfoParser.h>
#include <AIParamParser.h>
#include <Battle.h>
#include <PacketControl/WarMessages_Sync.h>
//==================================================================================================
#include "GameServerEx.h"
#include "GameZone.h"
#include "GameField.h"
#include "GameRoom.h"
#include "MovingPlanner.h"
#include "CharacterFormula.h"
#include "MoveStateControl.h"
#include "NPCStateManager.h"
#include "AIMessageQueue.h"
#include "TargetManager.h"
#include "StatusManager.h"
#include "State.h"
#include "EtcStatus.h"
#include "RecoverStatus.h"
#include "ChaoState.h"
#include "Skill.h"
#include "UnitListOfGroup.h"
#include "UnitRegenInfo.h"
#include "SummonManager.h"
#include "SummonedNPCs.h"
#include "Summoned.h"
//==================================================================================================
#include "GameParty.h"
#include "GamePartyManager.h"
#include "Player.h"
#include "TargetFinder.h"
//==================================================================================================
// Construction/Destruction
NPC::NPC()
: m_pUnitRegenInfo(NULL)
, m_pUnitListOfGroup(NULL)
, m_pUnitGroupInfo( NULL )
{
SetObjectType(NPC_OBJECT);
// CHANGES: f110307.2L
;{
ns_object::SearchReferences ref_option = { 0, };
ref_option.hp = reinterpret_cast<int*>(&m_dwHP);
_RegisterSearchReference(ref_option);
};
m_pAIMessageQueue = new AIMessageQueue(eAIMESSAGE_DEFAULT_SIZE);
m_pTempMessageQueue = new AIMessageQueue(eAIMESSAGE_TEMP_SIZE);
m_pNPCStatusManager = new NPCStateManager(this);
m_Attr.SetOwner( this );
m_pTargetManager = new TargetManager(this);
}
NPC::~NPC()
{
SAFE_DELETE( m_pAIMessageQueue );
SAFE_DELETE( m_pTempMessageQueue );
SAFE_DELETE( m_pNPCStatusManager );
SAFE_DELETE( m_pTargetManager );
}
VOID NPC::Release()
{
m_LinkedAction = NULL;
// OnDead에서 하면 죽은 후에 부활이 안된다. 같은 적을 공격하던 애를 부활시키기 때문에..
SetTargetChar( NULL );
Character::Release();
m_Attr.Clear();
m_pNPCStatusManager->Release();
m_SkillSelector.Release();
// AIMessage 초기화
m_pAIMessageQueue->Clear();
m_pTempMessageQueue->Clear();
m_pSkillSelectTimer->Stop();
// CHANGES, f100827.4L
FreeAllSkillCoolTime( TRUE );
// CHANGES: f110311.4L add a slot type for a trigger area linker to support a multi-area link
trigger_links_.Disconnect(NULL);
}
BOOL NPC::Init()
{
m_SelectedSkillCode = SKILLCODE_INVALID;
m_wSelectedSkillDelay = 0;
m_KillerType = MAX_OBJECT;
m_dwKillerObjectKey = 0;
m_bDestroy = FALSE;
m_iAngle = -1;
m_bGMIdleOn = FALSE;
m_pUnitRegenInfo = NULL;
m_pUnitListOfGroup = NULL;
m_pUnitGroupInfo = NULL;
// CHANGES: f110311.4L add a slot type for a trigger area linker to support a multi-area link
trigger_links_.ns_trigger::TriggerNpcLinkInGameSlots::~TriggerNpcLinkInGameSlots();
new (&trigger_links_) ns_trigger::TriggerNpcLinkInGameSlots(this);
// NOTE: need replacement, _NA_0_20100222_UNIT_TRIGGERS_FRIEND_MONSTER = {
search_type_ = eRARGET_NOT_SEARCH;
search_taget_unitID_ = 0;
search_taget_monstercode_ = 0;
search_taget_mapobjectID_ = 0;
//}
trigger_animation_code_ = 0;
if (!Character::Init())
{
return FALSE;
}
return TRUE;
}
VOID NPC::SetBaseInfo( WORD NPCCode, BYTE byMoveType, DWORD dwMoveAreaID, DWORD dwStateID, LPARAM param1 )
{
m_pBaseInfo = NPCInfoParser::Instance()->GetNPCInfo( NPCCode );
ASSERT( m_pBaseInfo );
// HP, MP 회복률
int iRecoverHP = 0;
int iRecoverMP = 0;
int iRecoverSD = 0;
if ( IsHPMPRegenNPC() )
{
iRecoverHP = CalcNPCHPRecover( m_pBaseInfo->m_dwMaxHP );
iRecoverMP = CalcNPCMPRecover( m_pBaseInfo->m_dwMaxMP );
}
if ( IsSDRegenNPC() )
{
iRecoverSD = CalcNPCSDRecover( m_pBaseInfo->m_dwMaxSD );
}
#ifdef _NA_007667_20141001_WITCHBLADE_SKILL
// 여기서 시간을 세팅해주면 스킬사용에 선딜이 발생한다.
// 어차피 스킬사용 후마다 SetTime해주고, '확률'이 존재하므로 초기화땐 0으로해도 무방
m_pSkillSelectTimer->SetTimer( 0 );
#else
m_pSkillSelectTimer->SetTimer( m_pBaseInfo->m_wSkillUpdateTime );
#endif //_NA_007667_20141001_WITCHBLADE_SKILL
m_Attr.SetCharInfo( m_pBaseInfo, iRecoverHP, iRecoverMP ,iRecoverSD );
m_Attr.Update();
// FSM 초기화
SetInitialState( dwStateID, byMoveType, dwMoveAreaID, param1 );
// 스크립트에서 얻어온 스피드는 0.001을 곱해준다.
m_pMoveStateControl->Init( this, CMS_WALK );
SetHP( m_pBaseInfo->m_dwMaxHP );
SetMP( m_pBaseInfo->m_dwMaxMP );
SetSD( m_pBaseInfo->m_dwMaxSD );
m_pTargetManager->Init();
//_NA_0_20100222_UNIT_TRIGGERS_FRIEND_MONSTER
search_type_ = FindSearchTypeByAttitude(m_pBaseInfo->m_byAttitude);
// 사용 가능한 스킬들을 등록한다.
AddDefaultSkill();
}
// implemented by _NA_0_20100222_UNIT_TRIGGERS_FRIEND_MONSTER
// 공격 성향과 검색 타입의 변환
eTARGET_SEARCH_TYPE NPC::FindSearchTypeByAttitude(const BYTE attitude)
{
switch (attitude)
{
case ATTACK_ATTITUDE_AFTER:
return eRARGET_NOT_SEARCH;
case ATTACK_ATTITUDE_NEARES_FIRST:
return eRARGET_SEARCH_NEAREST;
case ATTACK_ATTITUDE_ONE_TARGET:
return eRARGET_SEARCH_NONTARGET_LOW_HP;
case ATTACK_ATTITUDE_LOW_HP_FIRST:
return eRARGET_SEARCH_LOW_HP;
case ATTACK_ATTITUDE_LOW_LEVEL_FIRST:
return eRARGET_SEARCH_LOW_LEVEL;
case ATTACK_ATTITUDE_HIGH_MP_FIRST:
return eRARGET_SEARCH_HIGH_MP;
//_NA001385_20090924_DOMINATION_MAPNPC_AI_BUG
case ATTACK_ATTITUDE_NO_ATTACK:
return eRARGET_SEARCH_NOT_SEARCH_AND_TARGET_CHNAGE;
default:
SUNLOG(eCRITICAL_LOG, "[NPC::FindSearchTypeByAttitude]존재하지 않는 공격 성향 "
"MonsterCode[%d] attitude[%d]",
m_pBaseInfo->m_MonsterCode, attitude);
return eRARGET_NOT_SEARCH;
}
}
void NPC::SetSearchType(eTARGET_SEARCH_TYPE search_type, DWORD object_key)
{
search_type_ = search_type;
search_taget_unitID_ = 0;
search_taget_mapobjectID_ = 0;
search_taget_monstercode_ = 0;
switch (search_type)
{
case eRARGET_SEARCH_MAPOBJECTID:
search_taget_mapobjectID_ = object_key;
break;
case eRARGET_SEARCH_UNITID:
search_taget_unitID_ = static_cast<REGENID>(object_key);
break;
case eRARGET_SEARCH_MONSTERCODE:
search_taget_monstercode_ = static_cast<MONSTERCODE>(object_key);
break;
default:
break;
}
}
//==================================================================================================
BOOL NPC::IsHPMPRegenNPC()
{
if ( !m_pBaseInfo ) return FALSE;
switch ( m_pBaseInfo->m_byGrade )
{
case eNPC_ELITE: case eNPC_LEADER: case eNPC_MIDDLEBOSS:
case eNPC_BOSS: case eNPC_SUMMON_NPC:
return TRUE;
default:
return FALSE;
}
}
BOOL NPC::IsSDRegenNPC()
{
if (!m_pBaseInfo)
return FALSE;
// 서버쪽에서는 NPC 등급 보다는 객체 타입으로 평가 [2/23/2010 lst1024]
if (IsEqualObjectKind(SSQMONSTER_OBJECT) == 0)
return TRUE;
return FALSE;
}
//==================================================================================================
VOID NPC::GetNPCRenderInfo( MONSTER_RENDER_INFO * OUT pRenderInfo )
{
pRenderInfo->m_dwObjectKey = GetObjectKey();
pRenderInfo->m_dwMonsterCode = GetBaseInfo()->m_MonsterCode;
GetPos( &pRenderInfo->m_wvPos );
pRenderInfo->m_dwMaxHP = GetMaxHP();
pRenderInfo->m_dwHP = GetHP();
pRenderInfo->m_wMoveSpeedRatio = (WORD)m_Attr.GetMoveSpeedRatio();
pRenderInfo->m_wAttSpeedRatio = m_Attr.GetAttSpeedRatio();
pRenderInfo->m_wAngle = 0;
if ( m_iAngle >= 0 ) pRenderInfo->m_wAngle = (WORD)m_iAngle;
// STATE_INFO를 채운다.
GetStatusManager()->SerialzieStatusInfo( pRenderInfo->m_StateTotalInfo );
}
VOID NPC::GetSummonedRenderInfo( SUMMONED_RENDER_INFO * OUT pRenderInfo )
{
ASSERT( !"[NPC::GetSummonedRenderInfo] 일루 호출되면 안된다!" );
SUNLOG( eCRITICAL_LOG, "[NPC::GetSummonedRenderInfo] ObjectType[%d] 일루 호출되면 안된다!", GetObjectType() );
}
VOID NPC::GetTotemNPCRenderInfo( TOTEMNPC_RENDER_INFO * OUT pRenderInfo )
{
ASSERT( !"[NPC::GetTotemNPCRenderInfo] 일루 호출되면 안된다!" );
SUNLOG( eCRITICAL_LOG, "[NPC::GetTotemNPCRenderInfo] ObjectType[%d] 일루 호출되면 안된다!", GetObjectType() );
}
//==================================================================================================
// profiling
struct sProfileBlock
{
LARGE_INTEGER start_counter, end_counter;
INT64 elapsedClock;
INT64 elapsedMilliSecond;
};
#ifdef _SUSUNLOVE_TEST_CODE_
#define PROFILING_NPC_UPDATE_START() \
QueryPerformanceCounter(&profile_storage.start_counter)
#define PROFILING_NPC_UPDATE_CHECK() \
QueryPerformanceCounter(&profile_storage.end_counter)
#define IF_PROFILING_NPC_UPDATE_OVERTHAN( millisecond ) \
profile_storage.elapsedClock = end_counter.QuadPart - start_counter.QuadPart; \
profile_storage.elapsedMilliSecond = profile_storage.elapsedClock * 1000 / g_pGameServer->m_freq.QuadPart; \
if ( profile_storage.elapsedMilliSecond >= (millisecond) )
#else
#define PROFILING_NPC_UPDATE_START()
#define PROFILING_NPC_UPDATE_CHECK()
#define IF_PROFILING_NPC_UPDATE_OVERTHAN( millisecond )
#endif
//==================================================================================================
BOOL
NPC::Update( DWORD dwDeltaTick )
{
// need Time Profiling
static sProfileBlock profile_storage;
PROFILING_NPC_UPDATE_START();
#if WAVERIX_CODE_DELETE_RESERVED
static MONSTERCODE npcCode_s; // (WAVERIX) debug code
npcCode_s = this->GetBaseInfo()->m_MonsterCode; // (WAVERIX) debug code
#endif
if (Character::Update(dwDeltaTick) == FALSE)
return FALSE;
GameField* const pField = GetField();
if ( !pField )
return TRUE;
GameZone* const pZone = pField->GetGameZone();
const sPOLICY_ZONE& ZonePolicy = pZone->Policyment;
const SECTOR_IDX sector_index = GetSectorIndex();
BOOLEAN need_proc_state = FALSE;
//////////////////////////////////////////////////////////////////////////
// <AI CONTROL>
FLOWCONTROL_START()
{
AIMsgProgress(); // 큐에 쌓인 AI 메세지 처리
Sector* const pSector = pField->GetSector( sector_index );
if ( !pSector )
break;
need_proc_state = (ZonePolicy.FIELD & ZonePolicy.ENABLE_NPC_UPDATE_REF_SKIP) ||
pSector->GetReferenceCount();
if (need_proc_state == false)
{
if (Policyment.FIELD & Policyment.PCC_CONTINUING_NPC_UPDATE) {
need_proc_state = true;
}
}
#ifndef _SUSUNLOVE_TEST_CODE_
if ( need_proc_state )
#endif
{
const DWORD start_tick = GetTickCount();
#ifdef _SUSUNLOVE_TEST_CODE_
PROFILING_NPC_UPDATE_START();
BOOLEAN status_result = FALSE;
PROFILE_TIME_TEST(
status_result = PlanEvent->Update( dwDeltaTick, need_proc_state );
)
PROFILING_NPC_UPDATE_CHECK();
IF_PROFILING_NPC_UPDATE_OVERTHAN( 30 )
{
SUNLOG( eCRITICAL_LOG, "[PathExp_Progress] ObjKey[%d] Tick[%I64d]", GetObjectKey(), profile_storage.elapsedMilliSecond );
}
#else
const BOOLEAN status_result = PlanEvent->Update( dwDeltaTick, need_proc_state );
#endif
if ( !status_result )
return FALSE;
}
}
FLOWCONTROL_END;
//////////////////////////////////////////////////////////////////////////
// <MOVING CONTROL>
if ( !need_proc_state )
PlanEvent->Update( dwDeltaTick, FALSE );
// 이동 중 발생한 트리거 액션에 의해 필드 퇴장이 일어 났는지 검사한다.
if (GetField() == NULL)
{
return FALSE;
}
// sector_index <- 이동 전 섹터 인덱스
const WzVector* const pwvCurPos = GetPosPtr();
const SECTOR_IDX new_sector_index = pField->GetSectorIndex( pwvCurPos ); // <- 이동 후 섹터 인덱스
pField->CheckAndApplyChangeSector( this, new_sector_index );
return TRUE;
}
//==================================================================================================
Character* NPC::SelectSkillTarget( SLOTCODE SkillCode, BOOL bHeal )
{
SkillScriptInfo *pSkillInfo = SkillInfoParser::Instance()->GetSkillInfo( SkillCode );
if ( !pSkillInfo ) return NULL;
GameField *pField = GetField();
if ( !pField ) return NULL;
// 나중에 수정좀 봐야함...
switch ( pSkillInfo->m_byTarget )
{
case SKILL_TARGET_ENEMY:
case SKILL_TARGET_REACHABLE_ENEMY:
// 대상이 적일 경우 현재 타겟을 그대로 유지한다.
return GetTargetChar();
case SKILL_TARGET_FRIEND:
case SKILL_TARGET_REACHABLE_FRIEND:
{
TargetFinderArg find_arg;
find_arg.owner_ = this;
find_arg.search_type_ = eRARGET_SEARCH_LOW_HPRATIO;
find_arg.target_type_ = SKILL_TARGET_FRIEND;
find_arg.unitID_ = 0;
find_arg.mapobjectID_ = 0;
find_arg.npc_code_ = 0;
return pField->SearchTarget(find_arg);
}
case SKILL_TARGET_ME:
case SKILL_TARGET_REACHABLE_ME:
return this;
case SKILL_TARGET_FRIEND_CORPSE:
{
TargetFinderArg find_arg;
find_arg.owner_ = this;
find_arg.search_type_ = eRARGET_SEARCH_CORPSE;
find_arg.target_type_ = SKILL_TARGET_FRIEND;
find_arg.unitID_ = 0;
find_arg.mapobjectID_ = 0;
find_arg.npc_code_ = 0;
return pField->SearchTarget(find_arg);
}
case SKILL_TARGET_SUMMON:
// 소환을 할 경우 따로 타겟이 필요가 없으며 현재 타겟을 그대로 유지하면 된다.
return GetTargetChar();
case SKILL_TARGET_AREA:
return GetTargetChar();
default :
SUNLOG( eFULL_LOG, "[NPC::SelectSkillTarget] Invalid TargetType[%d] NPCCode[%d] ", pSkillInfo->m_byTarget, GetBaseInfo()->m_MonsterCode );
}
return GetTargetChar();
}
VOID NPC::ChangeState( DWORD dwStateID, LPARAM param1, LPARAM param2, LPARAM param3 )
{
m_pNPCStatusManager->ChangeState( dwStateID, param1, param2, param3 );
}
// note2 : 모호함을 해결하기 위해 함수를 제거함 - arycoat. 2013.09.13
//float NPC::GetDistToTarget() const
//{
// // (NOTE) 생각하면 생각할 수록 무대뽀 처리를 수행하는 걸로 판단되는 함수다.
// // 도대체 무슨 근거를 두고 처리를 하는가? 물론 본인의 경우, 이렇게 한 경우가 없지 않아 있는데,
// // 그 사례에 따르면, 이것은 그때 그때 즉흥적으로 할 경우에나 나올 법한 방식이다.
// // 시나리오: 이러한 계산이 필요하다. 없어? 추가... 없어? 추가...
//
// // 좀 더 따져 봐야겠지만, 최대한 이런 것들을 사용하는 사례를 변경시키도록 해본다.
// // 이것 외에 거리 계산 관련 in Character, in NPC, etc...
// Character *pTarget = m_pTargetManager->GetCurTarget();
//
// return GetDist( pTarget );
//}
float NPC::GetDistToNewTarget( Character* pNewTargetChar ) const
{
return GetDist( pNewTargetChar );
}
//==================================================================================================
// for AI
VOID NPC::SetInitialState( DWORD dwStateID, BYTE byMoveType, DWORD dwMoveAreaID, LPARAM param1 )
{
if ( byMoveType )
{
if ( !dwMoveAreaID )
{
dwMoveAreaID = m_pBaseInfo->m_dwMoveAreaID;
}
}
else
{
byMoveType = m_pBaseInfo->m_byMoveAttitude;
dwMoveAreaID = m_pBaseInfo->m_dwMoveAreaID;
}
#ifdef _SUSUNLOVE_TEST_CODE_
byMoveType = eMOVE_ATTITUDE_WANDER;
#endif
// 변신시 기존 상태를 제거한다.
m_pNPCStatusManager->ResetStateObject();
// 완전 정지형
if ( byMoveType == eMOVE_ATTITUDE_ETERNAL_STOP )
{
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_STOP_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_STOP_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_STOP_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_STOP_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
}
// 완전 정지형(공격안함, dead시 사라지지 않음.)
else if ( byMoveType == eMOVE_ATTITUDE_ETERNAL_STOP_NO_ATTACK )
{
//상태 전이가 없는 상태
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_NO_TRANS );
}
// 스폰 정지형
else if ( byMoveType == eMOVE_ATTITUDE_SPAWN_STOP )
{
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_SPAWN_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_RETURN );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_TRACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_HELP );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_THRUST, STATE_ID_THRUST );
m_pNPCStatusManager->AddStateObject( STATE_ID_FLYING, STATE_ID_FLYING );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_JUMP, STATE_ID_JUMP );
m_pNPCStatusManager->AddStateObject( STATE_ID_FALL_APART, STATE_ID_FALL_APART );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETURN, STATE_ID_RETURN );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETREAT, STATE_ID_RETREAT );
m_pNPCStatusManager->AddStateObject( STATE_ID_RUNAWAY, STATE_ID_RUNAWAY );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
#ifdef _NA_007907_20141222_MODIFY_MONSTER_MOVE_TRIGGER
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK_AREA, STATE_ID_TRACK_AREA );
#endif // _NA_007907_20141222_MODIFY_MONSTER_MOVE_TRIGGER
}
// 패트롤
else if ( byMoveType == eMOVE_ATTITUDE_PATROL )
{
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_PATROL );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_PATROL );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_TRACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_HELP );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_THRUST, STATE_ID_THRUST );
m_pNPCStatusManager->AddStateObject( STATE_ID_FLYING, STATE_ID_FLYING );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_JUMP, STATE_ID_JUMP );
m_pNPCStatusManager->AddStateObject( STATE_ID_FALL_APART, STATE_ID_FALL_APART );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETURN, STATE_ID_RETURN );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETREAT, STATE_ID_RETREAT );
m_pNPCStatusManager->AddStateObject( STATE_ID_RUNAWAY, STATE_ID_RUNAWAY );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
//_NA_0_20100817_HELLON_LAST_TRIGGER
m_pNPCStatusManager->AddStateObject( STATE_ID_STOP_IDLE, STATE_ID_STOP_IDLE );
}
// added by _NA_0_20100222_UNIT_TRIGGERS_PATHLIST = {
else if (byMoveType == eMOVE_ATTITUDE_PATHLIST)
{
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_PATHLIST );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_PATHLIST );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_TRACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_HELP );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_THRUST, STATE_ID_THRUST );
m_pNPCStatusManager->AddStateObject( STATE_ID_FLYING, STATE_ID_FLYING );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_JUMP, STATE_ID_JUMP );
m_pNPCStatusManager->AddStateObject( STATE_ID_FALL_APART, STATE_ID_FALL_APART );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETURN, STATE_ID_RETURN );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETREAT, STATE_ID_RETREAT );
m_pNPCStatusManager->AddStateObject( STATE_ID_RUNAWAY, STATE_ID_RUNAWAY );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
//_NA_0_20100817_HELLON_LAST_TRIGGER
m_pNPCStatusManager->AddStateObject( STATE_ID_STOP_IDLE, STATE_ID_STOP_IDLE );
}
else if (byMoveType == eMOVE_ATTITUDE_FOLLOW)
{
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_FOLLOW );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_FOLLOW );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_TRACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_HELP );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_THRUST, STATE_ID_THRUST );
m_pNPCStatusManager->AddStateObject( STATE_ID_FLYING, STATE_ID_FLYING );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_JUMP, STATE_ID_JUMP );
m_pNPCStatusManager->AddStateObject( STATE_ID_FALL_APART, STATE_ID_FALL_APART );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETURN, STATE_ID_FOLLOW );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETREAT, STATE_ID_RETREAT );
m_pNPCStatusManager->AddStateObject( STATE_ID_RUNAWAY, STATE_ID_RUNAWAY );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
//_NA_0_20100817_HELLON_LAST_TRIGGER
m_pNPCStatusManager->AddStateObject( STATE_ID_STOP_IDLE, STATE_ID_STOP_IDLE );
}
//} end added ai list '_NA_0_20100222_UNIT_TRIGGERS_PATHLIST'
#ifdef _NA_007907_20141222_MODIFY_MONSTER_MOVE_TRIGGER
else if (byMoveType == eMOVE_ATTITUDE_ATTACK_GROUND)
{
// 어택 그라운드 형
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_SPAWN_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_TRACK_AREA );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_TRACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_HELP );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_THRUST, STATE_ID_THRUST );
m_pNPCStatusManager->AddStateObject( STATE_ID_FLYING, STATE_ID_FLYING );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_JUMP, STATE_ID_JUMP );
m_pNPCStatusManager->AddStateObject( STATE_ID_FALL_APART, STATE_ID_FALL_APART );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETURN, STATE_ID_RETURN );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETREAT, STATE_ID_RETREAT );
m_pNPCStatusManager->AddStateObject( STATE_ID_RUNAWAY, STATE_ID_TRACK_AREA );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK_AREA, STATE_ID_TRACK_AREA );
}
#endif // _NA_007907_20141222_MODIFY_MONSTER_MOVE_TRIGGER
else
{
// 자유 이동형 외 기타
m_pNPCStatusManager->AddStateObject( STATE_ID_IDLE, STATE_ID_IDLE );
m_pNPCStatusManager->AddStateObject( STATE_ID_WANDER, STATE_ID_WANDER );
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK, STATE_ID_TRACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_ATTACK, STATE_ID_ATTACK );
m_pNPCStatusManager->AddStateObject( STATE_ID_HELP, STATE_ID_HELP );
m_pNPCStatusManager->AddStateObject( STATE_ID_DEAD, STATE_ID_DEAD );
m_pNPCStatusManager->AddStateObject( STATE_ID_THRUST, STATE_ID_THRUST );
m_pNPCStatusManager->AddStateObject( STATE_ID_FLYING, STATE_ID_FLYING );
m_pNPCStatusManager->AddStateObject( STATE_ID_KNOCKDOWN, STATE_ID_KNOCKDOWN );
m_pNPCStatusManager->AddStateObject( STATE_ID_JUMP, STATE_ID_JUMP );
m_pNPCStatusManager->AddStateObject( STATE_ID_FALL_APART, STATE_ID_FALL_APART );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETURN, STATE_ID_RETURN );
m_pNPCStatusManager->AddStateObject( STATE_ID_RETREAT, STATE_ID_RETREAT );
m_pNPCStatusManager->AddStateObject( STATE_ID_RUNAWAY, STATE_ID_RUNAWAY );
m_pNPCStatusManager->AddStateObject( STATE_ID_CHAOS, STATE_ID_CHAOS );
m_pNPCStatusManager->AddStateObject( STATE_ID_BLIND, STATE_ID_BLIND );
m_pNPCStatusManager->AddStateObject( STATE_ID_DESTROY_OBJECT, STATE_ID_DESTROY_OBJECT );
m_pNPCStatusManager->AddStateObject( STATE_ID_SPECIAL_DEAD_ACTION, STATE_ID_SPECIAL_DEAD_ACTION );
//_NA_0_20100817_HELLON_LAST_TRIGGER
m_pNPCStatusManager->AddStateObject( STATE_ID_STOP_IDLE, STATE_ID_STOP_IDLE );
// 특정영역 우선검색
if ( byMoveType == eMOVE_ATTITUDE_SEARCH_AREA )
{
m_pNPCStatusManager->AddStateObject( STATE_ID_TRACK_AREA, STATE_ID_TRACK_AREA );
// 특정영역으로 달려가야 하므로 초기상태를 달려가는 상태로 설정한다.
dwStateID = STATE_ID_TRACK_AREA;
}
}
// 초기상태 설정
m_pNPCStatusManager->Init( dwStateID, byMoveType, dwMoveAreaID, param1 );
}
VOID NPC::SendAIMessage( AI_MSG *pMsg, WORD wSize, DWORD dwDelay )
{
m_pAIMessageQueue->SendAIMessage( pMsg, wSize, dwDelay );
}
VOID NPC::SendAIMessageToGroupExceptMe( AI_MSG *pMsg, WORD wSize, DWORD dwDelay )
{
if ( m_pUnitListOfGroup )
m_pUnitListOfGroup->SendAIMessage( pMsg, wSize, dwDelay, GetObjectKey() );
}
VOID NPC::SendAIMessageAroundExceptMe( AI_MSG *pMsg, WORD wSize, DWORD dwDelay )
{
GameField *pField = GetField();
if ( !pField ) return;
pField->SendAIMessageAroundExceptMe( this, pMsg, wSize, dwDelay );
}
// 주변에서 적을 찾아서 그 포인터를 리턴한다.
Character* NPC::SearchTarget()
{
GameField *pField = GetField();
if ( !pField ) return NULL;
TargetFinderArg find_arg;
find_arg.owner_ = this;
find_arg.search_type_ = search_type_;
find_arg.target_type_ = SKILL_TARGET_ENEMY;
find_arg.unitID_ = search_taget_unitID_;
find_arg.mapobjectID_ = search_taget_mapobjectID_;
find_arg.npc_code_ = search_taget_monstercode_;
return pField->SearchTarget(find_arg);
}
Character* NPC::SearchTargetFriend()
{
GameField* field = GetField();
if (field == NULL)
return NULL;
TargetFinderArg find_arg;
find_arg.owner_ = this;
find_arg.search_type_ = search_type_;
find_arg.target_type_ = SKILL_TARGET_FRIEND;
find_arg.unitID_ = search_taget_unitID_;
find_arg.mapobjectID_ = search_taget_mapobjectID_;
find_arg.npc_code_ = search_taget_monstercode_;
return field->SearchTarget(find_arg);
}
VOID NPC::AIMsgProgress()
{
// LARGE_INTEGER firstcounter,nextcounter;
// __int64 elapsedclock, elapsedmillsecond;
AI_MSG *pRecvMsg;
WORD wSize;
DWORD dwCurTick = GetTickCount();
pRecvMsg = (AI_MSG*)m_pAIMessageQueue->RecvAIMessage( wSize );
while ( pRecvMsg )
{
//QueryPerformanceCounter(&firstcounter);
if ( dwCurTick > pRecvMsg->dwDeliveryTick )
{
m_pNPCStatusManager->GetCurState()->OnAIMsg( pRecvMsg );
}
else
{
// 메세지 딜레이만큼 지나지 않았으면 임시 큐에 넣고 처리하지 않는다.
// 딜레이값 다시 넣어줘야함...좃치안타 -_-;
m_pTempMessageQueue->SendAIMessage( pRecvMsg, wSize, pRecvMsg->dwDeliveryTick - dwCurTick );
}
m_pAIMessageQueue->Completion( wSize );
//QueryPerformanceCounter(&nextcounter);
//elapsedclock = nextcounter.QuadPart - firstcounter.QuadPart;
//elapsedmillsecond = elapsedclock * 1000 / g_pGameServer->m_freq.QuadPart;
//if ( elapsedmillsecond >= 10 )
//{
// SUNLOG( eCRITICAL_LOG, "[NPC::AIMsgProgress] 업데이트가 너무 오래걸림! ObjKey[%d] CurState[%d] MsgID[%d] Tick[%d]",
// GetObjectKey(), m_pNPCStatusManager->GetCurState()->GetStateID(), pRecvMsg->dwMsgID, elapsedmillsecond );
//}
pRecvMsg = (AI_MSG*)m_pAIMessageQueue->RecvAIMessage( wSize );
}
// 임시 큐에 있던 메세지 다시 집어 넣기
pRecvMsg = (AI_MSG*)m_pTempMessageQueue->RecvAIMessage( wSize );
while ( pRecvMsg )
{
// 딜레이값 다시 넣어줘야함...좃치안타 -_-;
m_pAIMessageQueue->SendAIMessage( pRecvMsg, wSize, pRecvMsg->dwDeliveryTick - dwCurTick );
m_pTempMessageQueue->Completion( wSize );
pRecvMsg = (AI_MSG*)m_pTempMessageQueue->RecvAIMessage( wSize );
}
}
VOID NPC::SetTargetChar( Character *pTarget )
{
m_pTargetManager->SetCurTarget( pTarget );
}
Character* NPC::GetTargetChar()
{
Character* target = m_pTargetManager->GetCurTarget();
if (obj_util::IsValid(target, __FUNCTION__, __LINE__) == false)
{
SetTargetChar(NULL);
return NULL;
}
return target;
}
VOID NPC::GroupCmdAttackToGroup( Character* pTargetChar )
{
// 그룹의 리더라면
if ( IsLeader() )
//if ( GetGroupState().IsLeader() )
{
AI_MSG_GROUP_C0MMAND AIGroupCmdMsg;
AIGroupCmdMsg.byType = GROUP_CMD_TYPE_ATTACK;
AIGroupCmdMsg.dwTargetKey = pTargetChar->GetObjectKey();
SendAIMessageToGroupExceptMe( &AIGroupCmdMsg, sizeof(AI_MSG_GROUP_C0MMAND) );
}
}
VOID NPC::GroupCmdStopAttackToGroup()
{
// 그룹의 리더라면
if ( IsLeader() )
//if ( GetGroupState().IsLeader() )
{
AI_MSG_GROUP_C0MMAND AIGroupCmdMsg;
AIGroupCmdMsg.byType = GROUP_CMD_TYPE_STOP_ATTACK;
SendAIMessageToGroupExceptMe( &AIGroupCmdMsg, sizeof(AI_MSG_GROUP_C0MMAND) );
}
}
//==================================================================================================
// moving supporter
BOOL NPC::IsSamePositionWithTarget()
{
WzVector vTargetPos, vMyPos;
if (GetTargetChar() == NULL)
return FALSE;
GetTargetChar()->GetPos( &vTargetPos );
GetPos( &vMyPos );
if ( vMyPos == vTargetPos )
{
return TRUE;
}
else
{
return FALSE;
}
}
VOID NPC::GetTrackPos( WzVector &wvDestPos, float fAttackRangeRatio )
{
// 이전에 추적하던 타겟슬롯을 Release한다.
GetTargetChar()->FreeEnemySlot( m_pTargetManager->GetTrackSlot() );
// 새로운 타겟슬롯을 찾아서 정보를 갱신한다.
WzVector vMyPos; GetPos( &vMyPos );
BYTE byTrackSlot = GetTargetChar()->AllocEnemySlot( &vMyPos, wvDestPos, fAttackRangeRatio );
m_pTargetManager->SetTrackSlot( byTrackSlot );
}
BOOL NPC::OnResurrection( float fRecoverExp, float fRecoverHP, float fRecoverMP, Player *pHealer )
{
if ( Character::OnResurrection( fRecoverExp, fRecoverHP, fRecoverMP, pHealer ) )
{
ChangeState( STATE_ID_IDLE );
return TRUE;
}
return FALSE;
}
VOID NPC::OnDead()
{
SASSERT( GetHP() == 0, "GetHP() != 0" );
// (WAVERIX)(080923)(NOTE) 처리 변경할 시간이 없다. 기존 로직 형태로 묻어간다.
//!~ too many state change try! - arycoat 2014.08.14
State* pState = GetStateManager()->GetCurState();
if (!pState || pState->GetStateID() == STATE_ID_DEAD) return;
//~!
ChangeState(STATE_ID_DEAD, m_pBaseInfo->m_dwDeadWaitingTime);
SetTargetChar( NULL );
m_pAIMessageQueue->Clear();
Character::OnDead();
}
BOOL
NPC::ExecuteThrust(BOOL bForcedMove,
WzVector* pwvMoveDistance, WzVector& wvPosAfterThrust, float fMoveDistance,
BOOL is_down_after_thrust) // 밀리기 후에 다운 상태로 넘어갈 것인가?
{
// (CHANGES) (091229) (WAVERIX) code-arrangement for NPC moving control
GameField* pField = GetField();
if (pField == NULL)
return false;
// 완전 정지형인 경우 밀리기가 안된다.
if (m_pBaseInfo->m_byMoveAttitude == eMOVE_ATTITUDE_ETERNAL_STOP ||
m_pBaseInfo->m_byMoveAttitude == eMOVE_ATTITUDE_ETERNAL_STOP_NO_ATTACK)
{
return false;
}
// TODO: 밀리는 방향이 없는 경우 문제가 생긴다. 랜덤으로??
if (pwvMoveDistance->x == 0.0f && pwvMoveDistance->y == 0.0f) {
pwvMoveDistance->x = 1.0f;
WzVector wvTemp = *pwvMoveDistance;
int angle = State::GetStateRandomNo() % 360;
Math_ZRotateVector(&wvTemp, *pwvMoveDistance, angle);
*pwvMoveDistance = *pwvMoveDistance * fMoveDistance;
}
// NPC 밀리기 처리
if (pField->FindPathThrust(this, pwvMoveDistance, bForcedMove) == false)
return false;
// (NOTE) FindPathThrust에 따른 무의미한 검증이 되어야 할 것이다.
if (IsMoving() == false)
return false;
// 밀리기 후의 이동 좌표 얻기
wvPosAfterThrust = GetPathExplorer()->GetTargetPos();
// NPC에게 밀리기 메세지를 보냄
AI_MSG_THRUST thrust_msg;
thrust_msg.bDownAfterThrust = !!is_down_after_thrust;
SendAIMessage(&thrust_msg, sizeof(thrust_msg));
return true;
}
BOOL NPC::IsTooCloseToTarget()
{
if (GetTargetChar() == NULL)
return FALSE;
// 타겟이 본인일 경우(셀프힐)
if ( GetTargetChar() == this ) return FALSE;
// 너무 가까운 경우.. 오히려 멀어지자
// (!) Player의 공격거리랑 차이가 많이 나는 경우는 플레이어가 다가가면 무조건 멀어지는 경향을 보일 수 있다.
// --> 적당히 기본 공격거리를 통일하든지 허용 범위를 조절하든지 해야할듯
AI_COMMON_INFO& stAICommonInfo = AIParamParser::Instance()->GetCommonInfo();
WzVector vCurPos, vTargetPos;
GetPos( &vCurPos ); GetTargetChar()->GetPos( &vTargetPos );
float fDistToTarget2 = VectorLength2( &(vCurPos-vTargetPos) );
// 타겟과의 거리가 MAX_ATTACK_RANGE_LIMIT이상이면 그냥 공격->장거리인 애들이 너무 멀어지는걸 방지
if ( fDistToTarget2 < stAICommonInfo.m_fFALLAPART_MIN_ATTACK_RANGE_LIMIT*stAICommonInfo.m_fFALLAPART_MIN_ATTACK_RANGE_LIMIT )
{
return TRUE;
}
return FALSE;
}
BOOL NPC::NeedToRetreatFromTarget()
{
Character* target = GetTargetChar();
if (target == NULL)
return FALSE;
if ( GetSelectedSkill() != SKILLCODE_NORMAL_ATTACK ) return FALSE;
// 공격거리가 5m는 넘어야 후퇴 공격을 한다.
AI_COMMON_INFO& stAICommonInfo = AIParamParser::Instance()->GetCommonInfo();
if ( GetAttackRange()>=stAICommonInfo.m_fRETREAT_MIN_ATTACKRANGE_LIMIT )
{
float fDistToTarget = GetDist(target);
float fMinAttackDist = GetAttackRange() * stAICommonInfo.m_fRETREAT_MIN_DISTANCE_LIMIT_RATIO;
if (fDistToTarget < fMinAttackDist)
{
return TRUE;
}
}
return FALSE;
}
// 타겟보다 적당히 높은 위치에 있고 수평 거리도 충분히 가까워야 점프할 수 있으므로 체크한다.
BOOL NPC::IsOnGoodPositionToJump()
{
if ( !GetTargetChar() )
return FALSE;
WzVector wvCurPos, wvTargetPos;
GetPos( &wvCurPos ); GetTargetChar()->GetPos( &wvTargetPos );
// 높이가 너무 높거나 너무 낮으면 실패
float fHeight = wvCurPos.z - wvTargetPos.z;
AI_COMMON_INFO& stAICommonInfo = AIParamParser::Instance()->GetCommonInfo();
if ( fHeight < stAICommonInfo.m_fJUMP_MIN_HEIGHT || fHeight > stAICommonInfo.m_fJUMP_MAX_HEIGHT )
{
return FALSE;
}
// 수평 거리가 너무 멀거나 너무 가까우면 실패
float fDistance = sqrt( ( wvTargetPos.x - wvCurPos.x ) * ( wvTargetPos.x - wvCurPos.x ) +
( wvTargetPos.y - wvCurPos.y ) * ( wvTargetPos.y - wvCurPos.y ) );
if ( fDistance < stAICommonInfo.m_fJUMP_MIN_DISTANCE || fDistance > stAICommonInfo.m_fJUMP_MAX_DISTANCE )
{
return FALSE;
}
m_vJumpPos = wvTargetPos;
return TRUE;
}
// 길찾기를 하고 패킷을 날림
BOOL
NPC::MoveAndBroadcast(WzVector* pwvDestPos, eCHAR_MOVE_STATE moveState)
{
// (CHANGES) (091229) (WAVERIX) code-arrangement for NPC moving control
GameField* pField = GetField();
if (pField == NULL)
return false;
#ifdef _SUSUNLOVE_TEST_CODE_
LARGE_INTEGER firstcounter,nextcounter;
QueryPerformanceCounter(&firstcounter);
#endif
// 길찾기... 실패시 리턴
if (pField->FindPath(this, pwvDestPos) == false)
return false;
#ifdef _SUSUNLOVE_TEST_CODE_
QueryPerformanceCounter(&nextcounter);
__int64 elapsedclock = nextcounter.QuadPart - firstcounter.QuadPart;
__int64 elapsedmillsecond = elapsedclock * 1000000 / g_pGameServer->m_freq.QuadPart;
if (elapsedmillsecond >= 20) {
//SUNLOG(eCRITICAL_LOG, "[PathExp_FindPath] ObjKey[%d] Tick[%I64d]", GetObjectKey(), elapsedmillsecond);
}
#endif
SetMoveState(moveState); // changes NPC move state = { RUN/WALK/etc... }
{ //broadcast
const WzVector* const pMobPos = GetPosPtr();
MSGSUB_SYNC_MOVE_BRD msg;
msg.m_dwObjectKey = GetObjectKey();
msg.m_byState = (BYTE)moveState; // RUN or WALK
msg.m_ForcedMove = false;
msg.m_wvCurPos = *pMobPos;
msg.m_wvDestPos = *pwvDestPos;
SendPacketAroundForSync(&msg);
};
return true;
}
BOOL NPC::CanJumpDist( Character *pTarget )
{
if ( !pTarget ) return FALSE;
AI_COMMON_INFO& stAICommonInfo = AIParamParser::Instance()->GetCommonInfo();
WzVector vCurPos, vTargetPos;
GetPos( &vCurPos ); pTarget->GetPos( &vTargetPos );
// 타겟과의 거리가 일정 범위에 들어야 점프를 한다.
float fDistToTarget = VectorLength( &(vCurPos-vTargetPos) );
if ( fDistToTarget < stAICommonInfo.m_fJUMP_ATTACK_MIN_DISTANCE || fDistToTarget > stAICommonInfo.m_fJUMP_ATTACK_MAX_DISTANCE )
return FALSE;
return TRUE;
}
BOOL NPC::SetJumpPos( Character *pTarget )
{
if ( !pTarget ) return FALSE;
GameField *pField = GetField();
if ( !pField ) return FALSE;
AI_COMMON_INFO& stAICommonInfo = AIParamParser::Instance()->GetCommonInfo();
WzVector vCurPos, vTargetPos, vNormal, vDistanceWantToMove, vDestPosAfterThrust;
// 현재 위치
GetPos( &vCurPos ); pTarget->GetPos( &vTargetPos );
// 점프 목적지 계산
VectorNormalize( &vNormal, &( vCurPos-vTargetPos ) ); vNormal.z = 0;
float fAttackRange = max( stAICommonInfo.m_fFALLAPART_MIN_ATTACK_RANGE_LIMIT, GetAttackRange()*stAICommonInfo.m_fJUMP_ATTACK_RANGE_RATIO );
m_vJumpPos = vTargetPos + vNormal * fAttackRange;
vDistanceWantToMove = m_vJumpPos - vCurPos;
float fWantToMoveLength = VectorLength2( &vDistanceWantToMove );
// 길찾기 가능한지 체크
if ( !pField->FindPathThrust( this, &vDistanceWantToMove ) )
{
return FALSE;
}
// FindPath후 설정된 거리가 의도한 거리에 못미치면 장애물에 닿은 것으로 간주한다.
vDestPosAfterThrust = GetPathExplorer()->GetTargetPos();
float fMoveLength = VectorLength2( &(vDestPosAfterThrust - vCurPos) );
// Thrust 거리가 의도한 거리만큼 안되는 경우
if ( fMoveLength < fWantToMoveLength * 0.95f )
{
StopMoving();
return FALSE;
}
SetMoveState( CMS_MONSTER_ATTACKJUMP );
return TRUE;
}
BOOL
NPC::ThrustMoveAndBroadcast(WzVector* pwvDestPos, eCHAR_MOVE_STATE moveState,
BOOL& bTouchedSomething,
WzVector& wvRotatedVector)
{
GameField* pField = GetField();
if (pField == NULL)
return false;
const WzVector& wvCurPos = *GetPosPtr();
WzVector& wvDistanceWantToMove = *pwvDestPos - wvCurPos;
float fWantToMoveLength = VectorLength2(&wvDistanceWantToMove);
// 목적지까지 길찾기 경로를 셋팅한다.
#ifdef _SUSUNLOVE_TEST_CODE_
LARGE_INTEGER firstcounter,nextcounter;
QueryPerformanceCounter(&firstcounter);
BOOL bResult = false;
// PROFILE_TIME_TEST(bResult = pField->FindPathThrust(this, &wvDistanceWantToMove);)
PROFILE_TIME_TEST(bResult = pField->FindPath(this, pwvDestPos);)
if (bResult == false)
return false;
QueryPerformanceCounter(&nextcounter);
__int64 elapsedclock = nextcounter.QuadPart - firstcounter.QuadPart;
__int64 elapsedmillsecond = elapsedclock * 1000000 / g_pGameServer->m_freq.QuadPart;
if (elapsedmillsecond >= 20) {
SUNLOG(eCRITICAL_LOG, "[PathExp_Thrust] ObjKey[%d] Tick[%I64d]",
GetObjectKey(), elapsedmillsecond);
}
else {
SUNLOG(eCRITICAL_LOG, "[PathExp_Thrust] No Delay!! ObjKey[%d] Tick[%I64d]",
GetObjectKey(), elapsedmillsecond);
}
#else
if (pField->FindPathThrust(this, &wvDistanceWantToMove) == false) {
return false;
}
#endif
// FindPath후 설정된 거리가 의도한 거리에 못미치면 장애물에 닿은 것으로 간주한다.
const WzVector& wvDestPosAfterThrust = GetPathExplorer()->GetTargetPos();
float fMoveLength = VectorLength2(&(wvDestPosAfterThrust - wvCurPos));
// Thrust 거리가 의도한 거리만큼 안되는 경우
if (fMoveLength < fWantToMoveLength * 0.95f)
{
// 장애물에 닿았다는 표시를 하고 지금 가려는 방향에서 49도 회전시킨 벡터를 구해둔다.
bTouchedSomething = true;
const float calc_cos = g_SinTable.FCOS(AIParamParser::AICommonInfo.m_wSEARCH_ROTATE_ANGLE);
const float calc_sin = g_SinTable.FSIN(AIParamParser::AICommonInfo.m_wSEARCH_ROTATE_ANGLE);
wvRotatedVector.x = wvDistanceWantToMove.x * calc_cos + wvDistanceWantToMove.y * calc_sin;
wvRotatedVector.y = -wvDistanceWantToMove.x * calc_sin + wvDistanceWantToMove.y * calc_cos;
wvRotatedVector.z = 0.0f;
// (NOTE) (WAVERIX) 좀 더 따져볼 필요 있다. 이동하지 않을 경우 생기는 문제는?
// 만약 이동은 하고 성공처리시에는?
// 이동 거리가 너무 짧으면 이동하지 말고(패킷 송신 없이) 그냥 리턴
if (fMoveLength < AIParamParser::AICommonInfo.m_fMIN_MOVABLE_DISTANCE *
AIParamParser::AICommonInfo.m_fMIN_MOVABLE_DISTANCE)
{
StopMoving();
return false;
}
}
SetMoveState(moveState);
{
MSGSUB_SYNC_MOVE_THRUST_BRD msg;
msg.m_dwObjectKey = GetObjectKey();
msg.m_byState = moveState;
msg.m_wTileIndex = GetPathExplorer()->GetTile();
// (WAVERIX) (081209) (BUG-FIX) 무조건 달리기 수정
//msg.m_byState = CMS_RUN; //캐릭터의 경우는 무조건 뛰기만 존재!!(몬스터와 같이 쓰는 패킷이므로 값은 넣어줌)
msg.m_wvCurPos = wvCurPos;
msg.m_wvDestPos = wvDestPosAfterThrust;
SendPacketAroundForSync(&msg);
WAVERIX_WRAPPER((WAVERIX_LC_CALL2("waverix_log_npcmove", this, &msg)));
};
return true;
}
float NPC::GetAttackRange() const
{
float fAttackRange = GetNormalRangeBonus();
if ( GetSelectedSkill() == SKILLCODE_INVALID )
{
// 이 함수는 '적이 공격사거리내에 들어왔는가?'를 체크할 때에도 사용하고 이 때엔, 대개 해당 분기를 타게된다.
// 추적상태에서 불렸을 시에 이부분으로
// 이 분기문에 들어오는 것은 일어날 수 있는 일이므로 ASSERT 제거
//ASSERT( GetSelectedSkill() != SKILLCODE_INVALID );
fAttackRange += 2.5f;
}
else if ( GetSelectedSkill() == SKILLCODE_NORMAL_ATTACK )
{
// 일반 공격시 NPC의 기본 공격 사거리를 리턴
fAttackRange += m_pBaseInfo->m_fAttRange;
}
else
{
// 스킬 공격시 해당 스킬의 공격 사거리를 리턴
SkillScriptInfo *pBaseSkillInfo = SkillInfoParser::Instance()->GetSkillInfo( GetSelectedSkill() );
if ( NULL == pBaseSkillInfo )
{
SUNLOG( eCRITICAL_LOG,"[NPC::GetAttackRange] SkillInfo is NULL [%d]\n", GetSelectedSkill() );
return 1.0f;
}
fAttackRange = pBaseSkillInfo->m_wSkillRange/10.f + GetSkillRangeBonus();
}
return fAttackRange + GetBodyRadius();
}
BOOL NPC::CheckSkillRange( Character *pTarget, const WzVector &vTargetPos, float fSkillRange )
{
fSkillRange += GetBodyRadius();
return Character::CheckSkillRange( pTarget, vTargetPos, fSkillRange );
}
//==================================================================================================
eUSER_RELATION_TYPE NPC::IsFriend( Character *pTarget )
{
if (obj_util::IsValid(pTarget, __FUNCTION__, __LINE__) == false) {
return USER_RELATION_NEUTRALIST;
}
// 본인인지 체크한다.
if ( this == pTarget ) return USER_RELATION_FRIEND;
// 본인과 타겟의 소환된 여부를 확인한다.
Character* pMySummoner = NULL;
Character* pTargetSummoner = NULL;
#ifdef _NA_000000_20140224_SUMMONER_REFACTORING
if (this->IsEqualObjectKind(SUMMON_OBJECT)) {
pMySummoner = GetField()->FindCharacter(this->GetSummonerKey());
}
if (pTarget->IsEqualObjectKind(SUMMON_OBJECT)) {
pTargetSummoner = pTarget->GetField()->FindCharacter(pTarget->GetSummonerKey());
}
#else
SummonedNPCs* pMySummon = SummonManager::Instance()->FindSummonNPCs( m_dwSummonerKey );
SummonedNPCs* pTargetSummon = SummonManager::Instance()->FindSummonNPCs( pTarget->GetSummonerKey() );
if ( pMySummon )
{
pMySummoner = pMySummon->GetSummoner();
if ( pMySummoner == this )
pMySummoner = NULL;
}
if ( pTargetSummon )
{
pTargetSummoner = pTargetSummon->GetSummoner();
if ( pTargetSummoner == pTarget )
pTargetSummoner = NULL;
}
#endif //_NA_000000_20140224_SUMMONER_REFACTORING
if ( pMySummoner && pTargetSummoner )
{
// 본인소환자의 타입과 타겟소환자의 타입이 같으면 아군이 된다.
return pMySummoner->IsFriend( pTargetSummoner );
}
else if ( pMySummoner )
{
// 본인소환자의 타입과 타겟의 타입이 같으면 아군이 된다.
return pMySummoner->IsFriend( pTarget );
}
else if ( pTargetSummoner )
{
// 본인 타입과 타겟소환자의 타입이 같으면 아군이 된다.
return IsFriend( pTargetSummoner );
}
if ( Policyment.FIELD & Policyment.PCC_IN_SSQ_FIELD )
{
// SSQMonster는 몬스터들 끼리도 적으로 인식한다.
// 트랩 계열 NPC들을 공격하는 것을 방지하기 위해서 이들만 친구로 인식하도록 한다.
if (pTarget->IsEqualObjectKind(MAPNPC_OBJECT))
return USER_RELATION_FRIEND;
return USER_RELATION_ENEMY;
}
else
{
// 같은 몬스터끼리는 아군으로 판정한다.
if (pTarget->IsEqualObjectKind(NPC_OBJECT))
{
NPC* const npc = static_cast<NPC*>(pTarget);
const BYTE grade = npc->GetGrade();
// CHANGES: f110525.9L, add iff relations about eNPC_MOVE_SYNC_MERCHANT
if (grade != eNPC_FRIEND_MAPOBJECT_NPC &&
grade != eNPC_FRIEND_MONSTER &&
grade != eNPC_MOVE_SYNC_MERCHANT)
{
return USER_RELATION_FRIEND;
}
else if ((grade == eNPC_MOVE_SYNC_MERCHANT) &&
(npc->Policyment.FIELD & npc->Policyment.PCC_PROTECTED_NPC) == 0)
{ // CHANGES: f110704.5L, add iff relations to support the custom map unit.
// see the NpcExtraInfo.NpcType and ExtraNPCManager::CheckProtectedNpc
// the concept of 'protected' is based on player view.
return USER_RELATION_FRIEND;
}
else
{
return USER_RELATION_ENEMY;
}
}
else
{
return USER_RELATION_ENEMY;
}
}
return USER_RELATION_NEUTRALIST;
}
VOID NPC::OnAttack( Character *pTarget, SLOTCODE skillcode, DAMAGETYPE damage )
{
Character::OnAttack( pTarget, skillcode, damage );
// 최초로 공격을 가한 넘에게 초기 포인트를 주기위해 AddAttackPoint를 호출한다.
m_pTargetManager->AddBattlePoint( pTarget, BATTLE_POINT_DAMAGE, 0 );
}
void
NPC::_Damaged(DamageArgs* damage_arg)
{
Character* pAttacker = damage_arg->actor_;
// (WAVERIX)(081013)(NOTE) 너무 길고 중복이 많다. 디파인 제거 작업이 끝나면 정리하기로 한다.
if ( Policyment.FIELD & Policyment.PCC_IN_SSQ_FIELD )
{
int damage = damage_arg->damage_;
if (damage) {
damage = (damage >> 1);
if (damage == 0) {
damage = 1;
}
damage_arg->damage_ = damage;
}
}
if (damage_arg->damage_ == 0) {
return;
}
#ifndef _NA_0_20100819_AGGRO_RENEWAL
if ( pAttacker )
{
DAMAGETYPE wResultDamage = damage_arg->damage_;
DWORD hp = GetHP();
if ( hp <= damage_arg->damage_ )
wResultDamage = (DAMAGETYPE)hp;
// 계산된 데미지 추가? 실제 적용 데미지를 추가해야 하는것이 아닌가? [2/17/2010 lst1024]
m_pTargetManager->AddBattlePoint( pAttacker, BATTLE_POINT_DAMAGE, wResultDamage );
}
#endif
Character::_Damaged(damage_arg);
if ( pAttacker && IsDead() )
{
Player* pAttackPlayer = pAttacker->GetPlayerOwner();
if ( pAttackPlayer )
{
ChaoState& rChaoState = pAttackPlayer->GetChaoState();
rChaoState.KillMonster( this ); // 몬스터가 카오 플레이어로부터 공격을 받아서 죽었을 경우
if (damage_arg->option_.is_mirror_ == false)
{
Attributes& rAtt = pAttackPlayer->GetAttr();
const int AbsorbHP = rAtt.GetAbsorbHP();
const int AbsorbMP = rAtt.GetAbsorbMP();
if ( AbsorbHP )
pAttackPlayer->OnRecover( AbsorbHP, 0 ); // HP 흡수
if ( AbsorbMP )
pAttackPlayer->OnRecover( 0, AbsorbMP ); // MP 흡수
}
}
}
}
VOID NPC::AddBattlePoint( Character* pAttacker, eBATTLE_POINT_TYPE ePointType, int iBattlePoint )
{
m_pTargetManager->AddBattlePoint( pAttacker, ePointType, iBattlePoint );
}
BOOL NPC::IsSpecialAction( eNPC_SPECIAL_ACTION_TYPE eType )
{
// 조건 검사
for ( int i = 0; i < 2; ++i )
{
SPECIAL_CONDITION *pSpecialCondition = &GetBaseInfo()->m_SpecialCondition[i];
if ( pSpecialCondition->wActionType == eType )
return TRUE;
}
return FALSE;
}
SPECIAL_CONDITION* NPC::GetDrawLotsSpecialAction()
{
// TODO: need compare with the custom logic 'SpecialAction()'
for ( int i = 0; i < BASE_NPCINFO::MAX_SPECIAL_NUM; ++i )
{
SPECIAL_CONDITION& special_condition = GetBaseInfo()->m_SpecialCondition[i];
if (CheckCondition(special_condition.byCondition, special_condition.wConditionParam))
{
if (DrawLots(special_condition.byActionRate))
{
return &special_condition;
}
}
}
return NULL;
}
VOID NPC::SpecialAction(const SPECIAL_CONDITION& special_condition)
{
switch (special_condition.wActionType)
{
case eNPC_SPECIAL_ACTION_HELPREQUEST:
{
// 공격대상이 없으면 지원요청 무효
if ( !GetTargetChar() ) return;
if ( !GetStateManager()->IsRequestHelp() )
{
AI_MSG_HELP_REQUEST AIHelpMsg;
AIHelpMsg.dwAttackerKey = GetTargetChar()->GetObjectKey();
AIHelpMsg.dwTargetKey = GetObjectKey();
SendAIMessageAroundExceptMe( &AIHelpMsg, sizeof(AIHelpMsg) );
GetStateManager()->SetRequestHelp( TRUE );
}
}
break;
case eNPC_SPECIAL_ACTION_TRANSFORMATION:
case eNPC_SPECIAL_ACTION_SELP_DESTRUCTION:
{
UseSkill( special_condition.wActionParam );
}
break;
case eNPC_SPECIAL_ACTION_SKILL:
{
// 공격대상이 없으면 스킬구사 무효
if ( !GetTargetChar() ) return;
SelectSkill( GetTargetChar(), special_condition.wActionParam );
ChangeState( STATE_ID_TRACK );
}
break;
}
}
//_NA_0_20100514_NPC_DEAD_STATE_TIME(삭제)
VOID NPC::SpecialAction()
{
SPECIAL_CONDITION *pSpecialCondition = NULL;
// 조건 검사
for ( int i = 0; i < 2; ++i )
{
pSpecialCondition = &GetBaseInfo()->m_SpecialCondition[i];
if ( CheckCondition( pSpecialCondition->byCondition, pSpecialCondition->wConditionParam ) )
{
// 확률 체크
if ( DrawLots( pSpecialCondition->byActionRate ) )
break;
}
pSpecialCondition = NULL;
}
if ( !pSpecialCondition ) return;
switch ( pSpecialCondition->wActionType )
{
case eNPC_SPECIAL_ACTION_HELPREQUEST:
{
// 공격대상이 없으면 지원요청 무효
if ( !GetTargetChar() ) return;
if ( !GetStateManager()->IsRequestHelp() )
{
AI_MSG_HELP_REQUEST AIHelpMsg;
AIHelpMsg.dwAttackerKey = GetTargetChar()->GetObjectKey();
AIHelpMsg.dwTargetKey = GetObjectKey();
SendAIMessageAroundExceptMe( &AIHelpMsg, sizeof(AIHelpMsg) );
GetStateManager()->SetRequestHelp( TRUE );
}
}
break;
case eNPC_SPECIAL_ACTION_TRANSFORMATION:
case eNPC_SPECIAL_ACTION_SELP_DESTRUCTION:
{
UseSkill( pSpecialCondition->wActionParam );
}
break;
case eNPC_SPECIAL_ACTION_SKILL:
{
// 공격대상이 없으면 스킬구사 무효
if ( !GetTargetChar() ) return;
SelectSkill( GetTargetChar(), pSpecialCondition->wActionParam );
ChangeState( STATE_ID_TRACK );
}
break;
}
}
VOID NPC::StatusResist()
{
// (NOTE) if a monster has a resistance about specific states,
// the monster enable released status by a release ratio of a probability.
const BASE_NPCINFO* const npc_info = GetBaseInfo();
// (CHANGES) (f100628.3L) (WAVERIX) change to a more cheap routine
for (int i = 0; i < _countof(npc_info->m_ResistanceCondition); ++i)
{
const RESISTANCE_CONDITION& resist_condition = npc_info->m_ResistanceCondition[i];
const eCHAR_STATE_TYPE state_id = \
static_cast<eCHAR_STATE_TYPE>(resist_condition.m_wStateCode);
if (state_id == eCHAR_STATE_INVALID) {
continue;
};
if (m_pStatusManager->FindStatus(state_id) == false) {
continue;
};
if (DrawLots(resist_condition.m_byRatio) == false) {
continue;
};
if (m_pStatusManager->Remove(state_id))
{
MSG_CG_BATTLE_MONSTER_STATUS_RESIST_BRD msg_brd;
msg_brd.m_dwMonsterObjKey = GetObjectKey();
BOOST_STATIC_ASSERT(sizeof(msg_brd.m_ScriptIndex) == sizeof(BYTE));
msg_brd.m_ScriptIndex = static_cast<BYTE>(i);
SendPacketAround(&msg_brd, sizeof(msg_brd));
};
}
}
//_NA_0_20091118_GAMEROOM_BATTLE_RESULT
VOID NPC::OnRecover( INT recoverHP, INT recoverMP, INT recoverSD, eRECOVER_TYPE Type, Character* pAttacker )
{
DWORD oldHP = GetHP();
// 실제 회복을 시킨다.
//_NA_0_20091118_GAMEROOM_BATTLE_RESULT
Character::OnRecover( recoverHP, recoverMP, recoverSD, Type, pAttacker );
// HP 변화
if ( oldHP != GetHP() )
SendHPChange();
}
void NPC::SendHPChange() //_NA001385_20090924_DOMINATION 함수로 변경
{
MSG_CG_STATUS_CHANGE_HP_BRD HPMsg;
HPMsg.m_dwObjectKey = GetObjectKey();
HPMsg.m_dwTargetHP = GetHP();
//여기는 몬스터나 npc 들의 정보다. 몬스터들의 ReserveHP는 항상 꽉 차있다.
HPMsg.m_dwReserveHP = GetMaxHP() - GetHP();
SendPacketAround(&HPMsg, sizeof(HPMsg), FALSE);
}
void NPC::SendAreaDamage(const DAMAGETYPE damage)
{
MSG_CG_TRIGGER_AREADAMAGE_BRD msg;
msg.m_dwObjectKey = GetObjectKey();
msg.m_wDamage = damage;
msg.m_dwHP = GetHP();
#ifdef _NA_008804_20160502_COMBAT_LOG
time_t t;
util::TimeSync::time(&t);
msg.m_SvrTime = t;
#endif //(_NA_008804_20160502_COMBAT_LOG)
SendPacketAround(&msg, sizeof(msg), FALSE);
}
float NPC::GetHPRatio() const
{
const INT maxHP = m_Attr.GetMaxHP();
if ( maxHP <= 0 )
return 0;
return (float)m_dwHP / maxHP;
}
float NPC::GetMPRatio() const
{
const INT maxMP = m_Attr.GetMaxMP();
if ( maxMP <= 0 )
return 0;
return (float)m_dwMP / maxMP;
}
float NPC::GetSDRatio() const
{
const INT maxSD = m_Attr.GetMaxSD();
if ( maxSD <= 0 )
return 0;
return (float)m_dwSD / maxSD;
}
VOID NPC::SetHP( DWORD dwHP )
{
const DWORD maxHP = (DWORD)m_Attr.GetMaxHP();
m_dwHP = BOUND_CHECK( 0, dwHP, maxHP );
}
VOID NPC::SetAttr( eATTR_TYPE eAttrType, eATTR_KIND eAttrKind, int iValue )
{
if ( eAttrType <= eATTR_TYPE_INVALID || eAttrType >= eATTR_MAX )
{
SUNLOG( eCRITICAL_LOG, "[NPC::SetAttr] eAttrType[%d] is Invalid", eAttrType );
return;
}
m_Attr[eAttrType][eAttrKind] = iValue;
m_Attr.UpdateEx();
}
bool NPC::IsLeaderAlive()
{
if ( !m_pUnitListOfGroup || !m_pUnitGroupInfo ) return false;
return m_pUnitListOfGroup->IsAlive(m_pUnitGroupInfo->GetLeaderRegenID());
}
bool NPC::IsLeader()
{
if ( !m_pUnitRegenInfo || !m_pUnitGroupInfo ) return false;
return m_pUnitRegenInfo->GetID() == m_pUnitGroupInfo->GetLeaderRegenID();
}
// (WAVERIX) (090617) (BUG-FIX) 그룹 멤버들은 자발적으로 후퇴 못하는 상황 감소시키기 작업
NPC*
NPC::GetLeaderInField()
{
if (m_pUnitRegenInfo == 0 || m_pUnitGroupInfo == 0)
return 0;
REGENID leaderID = m_pUnitGroupInfo->GetLeaderRegenID();
BOOLEAN isLeader = m_pUnitRegenInfo->GetID() == leaderID;
if (isLeader)
return this;
const UNIT_MEMBER_HASH& rList = m_pUnitListOfGroup->GetUnitMemberHash();
UNIT_MEMBER_HASH::const_iterator it = rList.find(leaderID);
if (it != rList.end())
return it->second;
return 0;
}
// NOTE: f110503.3L, logic improvements
void NPC::SetHPAttackPowerAsParty(DWORD number_of_party_members)
{
if (number_of_party_members == 0) {
return;
};
switch (eOBJECT_TYPE npc_object_type = GetObjectType())
{
case MONSTER_OBJECT:
case MAPNPC_OBJECT:
break;
default:
return;
};
//
const Attributes& attributes = m_Attr;
//----------------------------------------------------------------------------------------------
// NOTE: increase minimum and maximum attack power
;{
bool is_melee_type = (this->GetWeaponBaseAttType() != eATTACK_TYPE_RANGE);
int min_attack_power = is_melee_type ? attributes.GetBaseMeleeMinAttPower()
: attributes.GetBaseRangeMinAttPower();
int max_attack_power = is_melee_type ? attributes.GetBaseMeleeMaxAttPower()
: attributes.GetBaseRangeMaxAttPower();
;
const int additional_option_attack_power = ns_formula::CalcAttackPowerIncreaseAsParty(
number_of_party_members,
((min_attack_power + max_attack_power) >> 1));
if (AttrValue* attr_value = &m_Attr[eATTR_OPTION_PHYSICAL_ATTACK_POWER]) {
(*attr_value)[eATTR_KIND_BASE] = additional_option_attack_power;
};
};
//----------------------------------------------------------------------------------------------
// NOTE: increase skill attack power, in cases of EP1, increase magical option attack power
#ifdef _NA001605_EP2_ATTRIBUTES_RENEWAL_
if (const BASE_NPCINFO* npc_info = m_pBaseInfo)
{
int skill_attack_power = npc_info->skill_power;// GetAttr().GetBaseSkillAttackPower();
; skill_attack_power = ns_formula::CalcAttackPowerIncreaseAsParty(
number_of_party_members, skill_attack_power);
if (AttrValue* attr_value = &m_Attr[eATTR_ADD_SKILL_ATTACK_POWER]) {
(*attr_value)[eATTR_KIND_BASE] = skill_attack_power;
};
};
#else
;{
// 마법 옵션 공격력 상승
const int min_attack_power = attributes.GetBaseMagicMinAttPower();
const int max_attack_power = attributes.GetBaseMagicMaxAttPower();
const int additional_magic_option_attack_power = ns_formula::CalcAttackPowerIncreaseAsParty(
number_of_party_members, ((min_attack_power + max_attack_power) >> 1));
if (AttrValue* attr_value = &m_Attr[eATTR_OPTION_MAGICAL_ATTACK_POWER]) {
(*attr_value)[eATTR_KIND_BASE] = additional_magic_option_attack_power;
};
};
#endif
//----------------------------------------------------------------------------------------------
// NOTE: increase maximum HP quantity
DWORD last_update_cur_hp = GetHP();
if (const BASE_NPCINFO* npc_info = m_pBaseInfo)
{
float increasing_ratio = \
ns_formula::CalcHPIncreaseRatioAsParty(number_of_party_members);
DWORD npc_info_max_hp = npc_info->m_dwMaxHP;
const DWORD max_hp = util::LL_Round(npc_info_max_hp * increasing_ratio);
; DWORD cur_hp = last_update_cur_hp;
if (cur_hp)
{
DWORD cur_max_hp = GetMaxHP();
if (cur_max_hp)
{
float hp_relative_ratio = cur_hp / static_cast<float>(cur_max_hp);
DWORD calculated_hp = util::LL_Round(hp_relative_ratio * max_hp);
cur_hp = max(calculated_hp, 1);
};
};
//
if (AttrValue* attr_value = &m_Attr[eATTR_MAX_HP]) {
(*attr_value)[eATTR_KIND_BASE] = max_hp;
};
//
last_update_cur_hp = cur_hp;
};
//----------------------------------------------------------------------------------------------
// NOTE: the latest changes apply to npc attributes = { last_update_cur_hp, ... }
m_Attr.Update();
this->SetHP(last_update_cur_hp);
//----------------------------------------------------------------------------------------------
};
ENUM_STATD_ID NPC::GetCurStateID()
{
// 2014.02.04 / 민경승 / 정의되어있지 않던 NPC::GetCurStateID()를 정의.
// return형을 DWORD형에서 ENUM_STATD_ID로 수정. 해당 함수는 ENUM으로 정의된 stateID값과의 비교에
// 주로 사용되기 때문.
return m_pNPCStatusManager->GetCurState()->GetStateID();
}