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

412 lines
12 KiB
C++

#include "stdafx.h"
#include "TargetManager.h"
// Commons
#include "PublicMath.h"
#include "AIParamParser.h"
#include "GameServerEx.h"
#include "MovingPlanner.h"
#include "BaseStatus.h"
#include "StatusManager.h"
#include "BattleRecord.h"
#ifdef _NA_007667_20141001_WITCHBLADE_SKILL
#include "Summoned.h"
#endif //_NA_007667_20141001_WITCHBLADE_SKILL
//==================================================================================================
TargetManager::TargetManager(NPC* pNPC)
: m_pNPC(pNPC)
, m_pCurTarget(NULL)
{
m_pBattleRecord = new BattleRecord(pNPC);
}
TargetManager::~TargetManager()
{
SAFE_DELETE(m_pBattleRecord);
}
void
TargetManager::Init()
{
m_byTrackSlot = TRACK_SLOT_NONE;
m_pCurTarget = NULL;
m_pBattleRecord->Init();
const AI_TYPE_INFO* pAITypeInfo = m_pBattleRecord->m_pAITypeInfo;
m_pSearchTimer->SetTimer(pAITypeInfo->m_wSEARCH_TARGET_TIME);
m_pAggroTimer->SetTimer(pAITypeInfo->m_wAGGRO_TIME);
}
//==================================================================================================
void
TargetManager::Update()
{
// BattleRecord 업데이트
m_pBattleRecord->Update();
// 주변 타겟리스트를 갱신한다.
if(m_pSearchTimer->IsExpired())
{
UpdateTargetList();
}
// 새로운 타겟을 찾는다.
if(m_pCurTarget && m_pAggroTimer->IsExpired())
{
// 현재는 최초 타겟을 찾을때는 SearchTarget()을 사용하고 이후에는 BattleRecord가 큰 타겟으로 바꾸는 구조이다.
// 나중에... 타겟을 찾거나 변경하는 방식을 합쳐야 할 것이다!
// HP가 약한 타겟을 우선순위로 타겟팅하는 몬스터는 SelectBestTarget()에서도 그대로 속성이 적용되어야 할 것이다.
if(m_pNPC->IsDelegate())
SelectBestTarget();
#ifdef _NA_0_20100819_AGGRO_RENEWAL
if (IsEmptyAggroDisplayUser() == false) {
NotifyAggroValue();
}
#endif
}
}
void
TargetManager::SelectBestTarget()
{
Character* pNewTarget = m_pBattleRecord->SelectBestTarget();
// 새로운 타겟을 찾은 경우에만 바꾼다.
if(!pNewTarget || pNewTarget == m_pCurTarget)
return;
// 타겟이 공격가능한 상태인지 체크
if(!pNewTarget->CanBeAttacked())
return;
// 적군인지 체크한다.
if(m_pNPC->IsFriend(pNewTarget) == USER_RELATION_FRIEND)
return;
// 타겟변경 확률을 적용한다.
const AI_TYPE_INFO* pAITypeInfo = m_pBattleRecord->m_pAITypeInfo;
if(random(0,100) <= pAITypeInfo->m_byTARGET_CHANGE_RATIO)
{
SetCurTarget(pNewTarget);
}
}
void
TargetManager::UpdateTargetList()
{
// 갱신 목적 - 리스트 갱신이 잘못된 정보가 리스트에서 삭제되지 않고 남는 형태로 불완전하다
// - SSQMonster의 갱신 로직은 달라진다. ... 달라질 것 같지는 않다. 다른 곳에서 하면 될 듯.
GameField* const pField = m_pNPC->GetField();
if(!pField)
return;
StatusManager* const pStatusManager = m_pNPC->GetStatusManager();
BaseStatus* pStatus = NULL;
// Return, Help 상태에서는 적들을 등록하지 않는다.
pStatus = pStatusManager->FindStatus(eCHAR_STATE_ETC_RETURNING);
if(pStatus)
return;
pStatus = pStatusManager->FindStatus(eCHAR_STATE_ETC_HELP);
if(pStatus)
return;
//_NA_0_20100222_UNIT_TRIGGERS_FRIEND_MONSTER
pStatus = pStatusManager->FindStatus(eCHAR_STATE_ETC_SKIP_UPDATE_TARGET_LIST);
if(pStatus)
return;
if (m_pNPC->GetSearchType() == eRARGET_SEARCH_NOT_SEARCH_AND_TARGET_CHNAGE)
return;
const WzVector& wvNPCPos = *m_pNPC->GetPosPtr();
const float fSightRange = m_pNPC->GetSightRange();
// 기존 리스트 갱신
// - 시야 2배 밖, 공격할 수 없는 적들을 BattleRecord에서 삭제한다.
BattleRecord::Records& battle_records = m_pBattleRecord->GetRecords();
if(!battle_records.empty())
{
const float fSightRange2_2x = (fSightRange*fSightRange) * (2*2); // 시야^2 의 2배
BattleRecord::Records::iterator it = battle_records.begin(), itend = battle_records.end();
while(it != itend)
{
BattleRecord::Node& rRecord = it->second;
Character* const pTarget = pField->FindCharacter(rRecord.GetObjectKey());
if(!pTarget)
{
it = battle_records.erase(it);
continue;
}
/*SUNLOG(eCRITICAL_LOG, "|Total=%u|Class=%u|is1st=%u|Distance=%u|LV=%u|HP=%u|Damage=%u|Etc=%u|",
rRecord.GetBattlePoint(BATTLE_POINT_TOTAL), rRecord.GetBattlePoint(BATTLE_POINT_CLASS),
rRecord.GetBattlePoint(BATTLE_POINT_FIRST), rRecord.GetBattlePoint(BATTLE_POINT_DISTANCE),
rRecord.GetBattlePoint(BATTLE_POINT_LEVEL), rRecord.GetBattlePoint(BATTLE_POINT_HP),
rRecord.GetBattlePoint(BATTLE_POINT_DAMAGE), rRecord.GetBattlePoint(BATTLE_POINT_ETC));*/
if(!pTarget->CanBeAttacked())
{
it = battle_records.erase(it);
continue;
}
const WzVector& wvTargetPos = *pTarget->GetPosPtr();
const BOOLEAN outRange = (Math_VectorLength2(wvNPCPos, wvTargetPos) > fSightRange2_2x);
if(outRange)
{
it = battle_records.erase(it);
continue;
}
WORD skip_check_tile_attr_for_target = 0;
if (pTarget->IsEqualObjectKind(MAPNPC_OBJECT))
{
skip_check_tile_attr_for_target = PTA_NO_WALK;
}
if (!nsAI::PlanEventObject::AcceptableTarget(pTarget, PTA_SAFETY_ZONE,
skip_check_tile_attr_for_target))
{
it = battle_records.erase(it);
continue;
}
++it;
}
}
//_NA000000_110210_ENHANCED_TARGET_SEARCH_ALGORITHM
ns_object::SearchOption::NpcAI::result_type search_result;
ns_object::SearchOption::NpcAI search_option(m_pNPC);
if (pField->FindTargetsForNpcAI(search_option, &search_result) == false) {
return;
};
;{
Character** it = search_result.targets,
** end = &search_result.targets[search_result.number_of_selected_chars];
for ( ; it != end; ++it)
{
Character* character = *it;
AddBattlePoint(character, BATTLE_POINT_DAMAGE, 0);
}
};
}
void
TargetManager::AddBattlePoint(Character* pAttacker, eBATTLE_POINT_TYPE ePointType, int iBattlePoint)
{
if(!pAttacker)
return;
if(m_pNPC->IsFriend(pAttacker) != USER_RELATION_ENEMY)
return;
#ifdef _NA_007667_20141001_WITCHBLADE_SKILL
// 고정형 소환수 대신 소환자가 어그로를 쌓는다
if (pAttacker->IsEqualObjectKind(SUMMON_OBJECT) == true &&
pAttacker->GetSummonType() == eSUMMON_IMMOVABLE)
{
GameField* field = m_pNPC->GetField();
if (field != NULL)
{
DWORD summoner_key = pAttacker->GetSummonerKey();
Character* summoner = field->FindCharacter(summoner_key);
if (summoner != NULL) {
pAttacker = summoner;
}
}
}
#endif //_NA_007667_20141001_WITCHBLADE_SKILL
BattleRecord::Node* pRecord = m_pBattleRecord->Get(pAttacker->GetObjectKey());
// 최초 타겟이면 배틀레코드에 등록!
if(pRecord == NULL)
{
pRecord = m_pBattleRecord->Add(pAttacker);
/*
#ifdef _NA_20100322_AGGRO_DISPLAY
if(SUMMON_OBJECT != m_pNPC->GetObjectType() &&
NULL != m_pNPC->GetTargetChar())
{
m_pBattleRecord->SetFirstVictim(pRecord);
}
#endif
*/
}
// 데미지 포인트의 경우 누적데미지를 기준으로 UpdateTotalPoint()를 통해 계산된다.
if(ePointType == BATTLE_POINT_DAMAGE)
{
pRecord->PlusDamage(iBattlePoint);
if(iBattlePoint)
m_pBattleRecord->SetFirstAttacker(pRecord);
}
else if(ePointType == BATTLE_POINT_ETC)
{
iBattlePoint += pRecord->GetBattlePoint(BATTLE_POINT_ETC);
pRecord->SetBattlePoint(BATTLE_POINT_ETC, iBattlePoint);
}
else
{
pRecord->PlusDamage(iBattlePoint);
SUNLOG(eFULL_LOG, "[AddBattlePoint] Invalid ePointType[%d]", ePointType);
}
}
void
TargetManager::SetCurTarget(Character* pTarget)
{
// 이미 설정되어있는 적이면 다시 설정할 필요 없다.
if(m_pCurTarget == pTarget)
{
return;
}
//#ifdef _NA_004126_20120111_CHANGE_GAME_SERVER_OBJECT_FACTORY
if (pTarget != NULL && pTarget->IsDeleted())
{
pTarget = NULL;
}
//#endif
// 이전에 선택된 타겟이 있다면 타겟슬롯을 Release한다.
if (m_pCurTarget != NULL)
{
//#ifdef _NA_004126_20120111_CHANGE_GAME_SERVER_OBJECT_FACTORY
if (m_pCurTarget->IsDeleted() == false)
{
m_pCurTarget->FreeEnemySlot(m_byTrackSlot);
m_pCurTarget->GetObservers().Remove(m_pNPC->GetObjectKey());
}
//#else
// m_pCurTarget->FreeEnemySlot(m_byTrackSlot);
// m_pCurTarget->GetObservers().Remove(m_pNPC->GetObjectKey());
//#endif
const DWORD object_key = m_pCurTarget->GetObjectKey();
m_pBattleRecord->Remove(object_key);
}
m_pCurTarget = pTarget;
m_byTrackSlot = TRACK_SLOT_NONE;
if (m_pCurTarget != NULL)
{
// 타겟을 배틀레코드에 등록!
AddBattlePoint(pTarget, BATTLE_POINT_DAMAGE, 0);
m_pCurTarget->GetObservers().Add(m_pNPC);
}
}
void
TargetManager::RemoveTarget(DWORD leaveEnemyKey)
{
if(m_pCurTarget && m_pCurTarget->GetObjectKey() == leaveEnemyKey)
{
SetCurTarget(NULL);
}
}
void
TargetManager::RemoveBattleRecord(DWORD ObjKey)
{
m_pBattleRecord->Remove(ObjKey);
}
void
TargetManager::ClearBattleRecord()
{
GameField* pField = m_pNPC->GetField();
if(!pField)
return;
const DWORD npc_object_key = m_pNPC->GetObjectKey();
BattleRecord::Records records = m_pBattleRecord->GetRecords();
BattleRecord::Records::const_iterator it = records.begin(),
itend = records.end();
for( ; it != itend; ++it)
{
const BattleRecord::Node* pRecord = &it->second;
Character* pTarget = pField->FindCharacter(pRecord->GetObjectKey());
// (BUGFIX) (091216) (WAVERIX) 타깃을 찾을 수 없다고 return처리하면 본래 로직 의도에 어긋날 듯.
// 하기 사항에 대한 좀 더 검증이 필요하다. change 'return' to 'continue' 그런데... 왜 눈치 못챘지?
if(!pTarget)
continue; // 하단에서 clear하므로 수정하지 않는다.__NA000941_080204_TASK_SURVIVAL_SOLOING_QUEST_SYSTEM__
// NPC를 타겟의 옵져버 리스트에서 제거
pTarget->GetObservers().Remove(npc_object_key);
}
SetCurTarget(NULL);
m_pBattleRecord->Clear();
}
#ifdef _NA_0_20100819_AGGRO_RENEWAL
void
TargetManager::AddAggroDisplayUser(const DWORD object_key)
{
aggro_moitor_users_.clear();
aggro_moitor_users_.push_back(object_key);
}
void
TargetManager::NotifyAggroValue()
{
MSG_CG_AGGRO_VALUES_CMD msg;
msg.aggro_player_count_ = 0;
BattleRecord::Records records = m_pBattleRecord->GetRecords();
BattleRecord::Records::const_iterator it = records.begin(),
itend = records.end();
for( ; it != itend; ++it )
{
const BattleRecord::Node* pRecord = &it->second;
Player *target_player = PlayerManager::Instance()->FindPlayerByObjKey( pRecord->GetObjectKey() );
if(target_player)
{
_tcsncpy(msg.aggor_info_[msg.aggro_player_count_].character_name_,
target_player->GetCharName(),
_countof(msg.aggor_info_[msg.aggro_player_count_].character_name_));
msg.aggor_info_[msg.aggro_player_count_].character_name_[_countof(msg.aggor_info_[msg.aggro_player_count_].character_name_) - 1] = '\0';
msg.aggor_info_[msg.aggro_player_count_++].aggro_value_ = pRecord->GetCalcTotalPoint();
}
// CHANGES: f101206.1L, added a bound checker
if (msg.aggro_player_count_ >= _countof(msg.aggor_info_)) {
break;
}
}
if(msg.aggro_player_count_ <= 0)
return;
AGGRO_MONITOR_USER_ITER iter = aggro_moitor_users_.begin(),
iterend = aggro_moitor_users_.end();
for(; iter != iterend ; iter++)
{
Player *player = PlayerManager::Instance()->FindPlayerByObjKey( (*iter) );
if( player )
{
player->SendPacket(&msg,msg.GetSize());
}
}
}
#endif