Files
Leviathan/Client/Game/game/Player/SGameLocalPet.cpp
T
2026-06-01 12:46:52 +02:00

513 lines
13 KiB
C++

#include "stdafx.h"
// sonador 10.2.1 팻 시스템 구현
#include "SGameLocalPet.h"
#include "SPetStateMachine.h"
#include "GameDefine.h"
#include "KSeqAvatarEx.h"
#include "SGameAniType.h"
#include "SSkillDB.h"
#include "SGameInput.h"
#include "SStringDB.h"
#include "SGameMessage.h"
//#include "SGameMessageUI.h"
#include "LuaVM.h"
#include <toolkit/XStringUtil.h>
#include "SChatType.h"
#include "SGameItem.h"
#include "SGameUIMgr.h"
#include "SMessengerMgr.h"
#include "SDebug_Util.h"
#include "SLog.h"
#include "SGameScript.h"
#include "SGame.h"
//#include "SAvatarProperty.h"
SGameLocalPet::SGameLocalPet( int nPetID )
: SGamePet ( nPetID )
, m_dwUpdateTime ( 0 )
, m_dwFindDetour ( 0 )
, m_dwFindDetourTick ( 200 )
, m_dwBeginScriptTime ( 0 )
, m_dwCallScriptTime ( 500 ) // 게임 로컬 펫 스크립트 호출 주기 1초에서 0.5초로 변경 (줍기 속도 개선. 2013-12-12)
, m_bPickItemMode ( false ) // sonador #2.1.2.4.3 팻 조작 UI 연동
, m_fPickItemDistance ( ITEM_TAKE_RANGE ) //
, m_vMasterLastPos( 0, 0, 0 )
{
m_bLocalPet = true;
m_pStateVM = new SLocalPetStateMachine();
m_pStateVM->SetReceiver( this );
m_arMySize = GetSize( this );
m_dwItemPickCommandTime = 0;
}
SGameLocalPet::~SGameLocalPet()
{
}
/// 2010.10.13 - prodongi
void SGameLocalPet::Destroy()
{
SGamePet::Destroy();
m_queueIgnoredItems.clear();
}
bool SGameLocalPet::Process( DWORD time, unsigned long procBitSet )
{
m_dwCurrentTime = time;
/*
/* 펫은 보낼 필요없더라.
// udpate rarely
if( m_dwUpdateTime == 0 )
m_dwUpdateTime = time;
else if( ( time - m_dwUpdateTime ) >= UPDATE_TIME )
{
m_dwUpdateTime = time;
// send udpate message to server
TS_CS_UPDATE msg;
msg.handle = GetArID();
SendMsg( &msg );
}
*/
SGamePet::Process( time, procBitSet );
if( time - m_dwBeginScriptTime > m_dwCallScriptTime )
{
UpdateStateByScript();
m_dwBeginScriptTime = time; // sonador #2.1.2.4.3 팻 조작 UI 연동
}
if( m_pStateVM )
m_pStateVM->Process( time );
if ((m_pStateVM->GetCurrentState() != SObjectState::STATE_IDLE) && m_dwItemPickCommandTime != 0)
{
if (m_dwCurrentTime - m_dwItemPickCommandTime > 7000)
{
SDEBUGLOG("펫이 아이템을 회수 못하고 있음, 강제로 상태 리셋 \n");
m_dwItemPickCommandTime = 0;
m_pStateVM->StopCurrentState();
}
}
if (!m_queueIgnoredItems.empty())
{
if (m_dwCurrentTime - m_queueIgnoredItems.front().m_dwIgnoredTime > 5 * 60 * 1000) // 10분
{
SDEBUGLOG("무시 리스트에서 1개제거\n");
m_queueIgnoredItems.pop_front(); // 1프레임에 1개씩만 제거한다. 어차피 1프레임에 1개이상 제거될 일 없으니 코드 단순하게.
}
}
return true;
}
void SGameLocalPet::UpdateStateByScript()
{
if( m_pStateVM == 0 ) return;
if( IsIng() || IsReservation() ) return;
// Fraun performance tweak
std::string Function;
switch( m_pStateVM->GetCurrentState() )
{
case SObjectState::STATE_NONE:
case SObjectState::STATE_IDLE:
{
STRUCT_CREATURE_CMD_SCRIPT Idle;
Idle.nCmdScipt = STRUCT_CREATURE_CMD_SCRIPT::CMD_IDLE;
Idle.cHandle = GetArID();
SGame* pGame = GetActGame();
if (pGame) pGame->SendCreatureCmdScript(Idle);
break;
}
case SObjectState::STATE_MOVE:
{
STRUCT_CREATURE_CMD_SCRIPT Move;
Move.nCmdScipt = STRUCT_CREATURE_CMD_SCRIPT::CMD_MOVE;
Move.cHandle = GetArID();
Move.nCreaturePosition = 1;
SGame* pGame = GetActGame();
if (pGame) pGame->SendCreatureCmdScript(Move);
break;
}
}
}
bool SGameLocalPet::HasStateHeld()
{
if( IsIng() || IsReservation() )
return true;
return false;
}
bool SGameLocalPet::IsFarFrom( SGameAvatarEx* Target, DISTANCE_TYPE Type )
{
float fDistance = GetDistance( GetCurPosWithChangeDir(), Target->GetCurPosWithChangeDir() );
float fRange = (float)Type + m_arMySize;
if( fDistance > fRange )
return true;
return false;
}
void SGameLocalPet::CmdIdle()
{
if( HasStateHeld() )
return;
if( m_pMasterPlayer )
{
if( m_bPickItemMode && !m_pMasterPlayer->IsDead() ) // #2.1.2.4.2
{
const int PET_MAX_TAKE_ITEM_SEARCH_COUNT = INT_MAX; // 검색최대 갯수
float fDistance = 0.f;
vector<SGameAvatarEx*> vecItems;
vector<float> vecDist;
m_pMsgHandler->GetItemObjects_For_PetAutoGather( *m_pMasterPlayer->GetPosition(), m_fPickItemDistance, vecItems, *GetPosition(), vecDist, PET_MAX_TAKE_ITEM_SEARCH_COUNT );
if (!vecItems.empty())
{
float maxDist = FLT_MAX;
int index = -1;
/// 2010.10.13 - prodongi
bool isInIgnoredList = false;
for (size_t i=0; i<vecItems.size(); i++)
{
float someBigDist = 0;
bool bIgnore = false;
const SGameAvatarEx* curItem = vecItems[i];
for (size_t j=0; j<m_queueIgnoredItems.size(); j++)
{
if (m_queueIgnoredItems[j].m_hItem == curItem->GetArID())
{
// if (m_dwCurrentTime - m_queueIgnoredItems[j].m_dwLastAttemptedTime > 30 * 1000) // 30초
{
someBigDist = 100000;
m_queueIgnoredItems[j].m_nTryCount++;
SDEBUGLOG("Considering ignored item: %d / trycount: %d \n", m_queueIgnoredItems[j].m_hItem, m_queueIgnoredItems[j].m_nTryCount);
if (m_queueIgnoredItems[j].m_nTryCount >= 2) // 3번까지는 시도해본다.
{
m_queueIgnoredItems[j].m_nTryCount = 0;
m_queueIgnoredItems[j].m_dwLastAttemptedTime = m_dwCurrentTime;
}
}
// else
// bIgnore = true;
/// 2010.10.13 - prodongi
bIgnore = true;
break;
}
}
// if (bIgnore) continue;
if (vecDist[i] + someBigDist < maxDist ) // 리스트에 있는 아이템을 무조건 무시가 아니라 최하순위로 고려
{
/// 2010.10.13 - prodongi
isInIgnoredList = bIgnore;
index = i;
maxDist = vecDist[i] + someBigDist;
}
}
if (index != -1)
{
SGameItem* pItemObj = static_cast< SGameItem* >( vecItems[index] );
// 2011.10.18 : servantes : SPartyMgr 관리 클래스
int nPartyID = static_cast< CPartyListMgr* >( GameUIMgrInstance.GetUIMgr( SGameUIInstance::PARTY_MGR ) )->GetPartyID();
if( pItemObj->IsPickable( m_pMasterPlayer->GetArID(), nPartyID ) )
{
///
/// 2010.10.13 왜 m_queueIgnoredItems에 들어 있는지 체크를 안 하는가???? - prodongi
///
SInputTakeItem inputTakeItem( pItemObj->GetArID(), fDistance );
OnInput( &inputTakeItem );
/// 2010.10.13 무시 리스트에 없는 것만 새로 추가한다 - prodongi
if (!isInIgnoredList)
{
IgnoredItem item;
item.m_nTryCount = 0;
item.m_dwIgnoredTime = m_dwCurrentTime;
item.m_dwLastAttemptedTime = 0;
item.m_hItem = pItemObj->GetArID();
m_queueIgnoredItems.push_back(item);
}
m_dwItemPickCommandTime = m_dwCurrentTime;
return;
}
}
}
else
{
// 주변에 아이템이 없다면 무시 리스트를 초기화 한다.
m_queueIgnoredItems.clear();
}
//SGameObject* pItem = m_pMsgHandler->GetItemObject( *m_pMasterPlayer->GetPosition(), fDistance, -1, m_fPickItemDistance );
//if( pItem )
//{
// SGameItem* pItemObj = static_cast< SGameItem* >( pItem );
// int nPartyID = static_cast< SPartyMgr* >( GameUIMgrInstance.GetUIMgr( SGameUIInstance::PARTY_MGR ) )->GetPartyID();
// if( pItemObj->IsPickable( m_pMasterPlayer->GetArID(), nPartyID ) )
// {
// SInputTakeItem inputTakeItem( pItem->GetArID(), fDistance );
// OnInput( &inputTakeItem );
// return;
// }
//}
}
if( IsFarFrom( m_pMasterPlayer, DISTANCE_3 ) || m_vMasterLastPos != *m_pMasterPlayer->GetPosition() )
{
CmdMove();
}
else
{
SetViewVectorStateIdle( m_pMasterPlayer->GetViewVector() );
}
}
}
void SGameLocalPet::CmdMove()
{
if( HasStateHeld() )
return;
if( m_pMasterPlayer )
{
if( IsFarFrom( m_pMasterPlayer, DISTANCE_15 ) )
InputCmd_MoveToFitPosition( true );
else
InputCmd_MoveToFitPosition( false );
m_vMasterLastPos = *m_pMasterPlayer->GetPosition();
}
}
void SGameLocalPet::OnInput( class SGameInput* input )
{
if( !m_pStateVM )
return;
m_pStateVM->OnInput( input );
// TODO: processing input by id
int inputID = input->GetInputID();
}
bool SGameLocalPet::GetFitPosition( K3DVector& out )
{
// TODO: locate pet behind the master player
K3DVector vUp( 0, 0, -1.f );
K3DVectorCross( out, m_pMasterPlayer->GetViewVector(), vUp );
out = m_pMasterPlayer->GetCurPosWithChangeDir() + ( out * m_arMySize );
return true;
}
AR_UNIT SGameLocalPet::GetSize( SGameObject *object )
{
AR_UNIT size = object->GetSize() / 2.f;
AR_UNIT scale = object->GetScale();
return size * scale * GameRule::DEFAULT_UNIT_SIZE;
}
void SGameLocalPet::ReqTakeItem( AR_HANDLE handle )
{
SDEBUGLOG("ReqTakeItem \n");
{
// 목표로한 아이템을 줍는다.
TS_CS_TAKE_ITEM msg;
msg.item_handle = handle;
msg.taker_handle = GetArID(); // sonador #2.1.2.4.3 팻 조작 UI 연동
SendMsg( &msg );
}
// 현재 위치에서 바로 주울 수 있는 n개의 아이템을 검색하여 줍는다.
const int PET_ADDITIONAL_MAX_TAKE_ITEM_COUNT = INT_MAX; // 팻이 한번에 아이템을 추가로 주울수 있는 갯수
if( PET_ADDITIONAL_MAX_TAKE_ITEM_COUNT > 0 )
{
vector<SGameAvatarEx*> vecItems;
vector<float> vecDist;
m_pMsgHandler->GetItemObjects_For_PetAutoGather( *GetPosition(), GameRule::MAX_TAKE_ITEM_RANGE, vecItems, *GetPosition(), vecDist, PET_ADDITIONAL_MAX_TAKE_ITEM_COUNT );
if (!vecItems.empty())
{
auto pos = vecItems.cbegin();
auto end = vecItems.cend();
for( ; pos != end; ++pos )
{
SGameItem* pItemObj = static_cast< SGameItem* >( *pos );
// 무시된 아이템 제외
auto ignored_pos = m_queueIgnoredItems.cbegin();
auto ignored_end = m_queueIgnoredItems.cend();
bool ignored_flag = false;
for( ; ignored_pos != ignored_end; ++ignored_pos )
{
const IgnoredItem& ignored_item = (*ignored_pos);
if (ignored_item.m_hItem == pItemObj->GetArID())
{
ignored_flag = true;
break;
}
}
if( ignored_flag == false )
{
// 2011.10.18 : servantes : SPartyMgr 관리 클래스
int nPartyID = static_cast< CPartyListMgr* >( GameUIMgrInstance.GetUIMgr( SGameUIInstance::PARTY_MGR ) )->GetPartyID();
if( pItemObj->GetArID() != handle && pItemObj->IsPickable( m_pMasterPlayer->GetArID(), nPartyID ) )
{
IgnoredItem item;
item.m_nTryCount = 0;
item.m_dwIgnoredTime = m_dwCurrentTime;
item.m_dwLastAttemptedTime = 0;
item.m_hItem = pItemObj->GetArID();
m_queueIgnoredItems.push_back(item);
TS_CS_TAKE_ITEM msg;
msg.item_handle = pItemObj->GetArID();
msg.taker_handle = GetArID();
SendMsg( &msg );
}
}
}
}
}
}
void SGameLocalPet::ReqMove( AR_HANDLE handle, const ArPosition& target, bool speedSync, bool chase )
{
SDEBUGLOG("ReqMove \n");
if( IsUsingSkill() || IsIng() )
return;
m_vTargetPos.x = target.x;
m_vTargetPos.y = target.y;
m_vTargetPos.z = target.z;
int nDetourSize = GetAllDetourPointSize();
if( nDetourSize > 0 )
SGameObject::ReqMove( handle, target, GetAllDetourPoint(), nDetourSize, speedSync );
else
SGameObject::ReqMove( handle, target, &target, 1, speedSync );
DeleteReqPoint();
}
void SGameLocalPet::ReqCast( AR_HANDLE target, int skillID, int skillLv, K3DVector const& targetPos )
{
if( IsIng() )
return;
_SKILL_FX* skillFx = GetSkillStageDB().GetSkillStageData( skillID );
if( !skillFx )
{
assert( 0 && "스킬 ID 확인 : 스킬 유형이 없다!!!" );
return;
}
TS_CS_SKILL msg;
msg.caster = GetArID();
msg.target = 0;
msg.skill_id = skillID;
msg.skill_level = skillLv;
msg.x = targetPos.x;
msg.y = targetPos.y;
msg.z = targetPos.z;
SendMsg( &msg );
}
void SGameLocalPet::ReqCancelAction()
{
TS_CS_CANCEL_ACTION msg;
msg.handle = GetArID();
SendMsg( &msg );
}
void SGameLocalPet::InputCmd_MoveToFitPosition( bool syncWithMasterPlayer, bool immediately )
{
K3DVector out, nextPos;
if( GetFitPosition( out ) == false ) return;
if( !immediately )
{
if( m_dwTime - m_dwFindDetour > m_dwFindDetourTick )
{
SInputMove input( out, syncWithMasterPlayer );
OnInput( &input );
m_dwFindDetour = m_dwTime;
}
}
else
{
SInputMove input( out, syncWithMasterPlayer );
OnInput( &input );
}
}
//void SGameLocalPet::SetBestPositionEx( bool immediately )
//{
// K3DVector out, nextPos;
// if( GetFitPosition( out ) == false ) return;
//
// if( !immediately )
// {
// if( m_dwTime - m_dwFindDetour > m_dwFindDetourTick )
// {
// SInputMove input( out, false );
// OnInput( &input );
//
// m_dwFindDetour = m_dwTime;
// }
// }
// else
// {
// SInputMove input( out, false );
// OnInput( &input );
// }
//}
//팻 자동 줍기 가능 거리 설정 // sonador #2.1.2.4.3 팻 조작 UI 연동
void SGameLocalPet::SetAutoPickItemDistanceByMeter( float meter )
{
m_fPickItemDistance = meter * GameRule::DEFAULT_UNIT_SIZE;
}
void SGameLocalPet::DeleteItemFromIgnoreQueue(AR_HANDLE id)
{
for (std::deque<IgnoredItem>::iterator i=m_queueIgnoredItems.begin(); i!=m_queueIgnoredItems.end(); ++i)
{
if ((*i).m_hItem == id)
{
SDEBUGLOG("Removed from ignore list: %d", id);
m_queueIgnoredItems.erase(i);
SDEBUGLOG("Item ignore list: ");
for (size_t i=0; i<m_queueIgnoredItems.size(); i++)
SDEBUGLOG("( %d ) ", m_queueIgnoredItems[i].m_hItem);
SDEBUGLOG("\n");
break;
}
}
}