Files
Sun1602/Server/GameServer/NPC.cpp
T
git 9cb257e7ae Add GameClients drops and keep current 1602 source work.
CN1602 is the local client. KR is the official tree with unpacked Data, System, and Sound. Packed .wpk files stay off git.
2026-08-30 23:24:31 +03: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] unknown attack attitude "
"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] should not be called here!" );
SUNLOG( eCRITICAL_LOG, "[NPC::GetSummonedRenderInfo] ObjectType[%d] should not be called here!", GetObjectType() );
}
VOID NPC::GetTotemNPCRenderInfo( TOTEMNPC_RENDER_INFO * OUT pRenderInfo )
{
ASSERT( !"[NPC::GetTotemNPCRenderInfo] should not be called here!" );
SUNLOG( eCRITICAL_LOG, "[NPC::GetTotemNPCRenderInfo] ObjectType[%d] should not be called here!", 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();
}