Files
Client/Server/RylServerProject/RylGameLibrary/Creature/Monster/MonsterBehavior.cpp
LGram16 dd97ddec92 Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code:
- Client: Game client source (moved to Client/Client/)
- Server: Game server source
- GameTools: Development tools
- CryptoSource: Encryption utilities
- database: Database scripts
- Script: Game scripts
- rylCoder_16.02.2008_src: Legacy coder tools
- GMFont, Game: Additional resources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:17:20 +09:00

1333 lines
37 KiB
C++

#include "stdafx.h"
#include <Utility/Registry/RegFunctions.h>
#include <Utility/Math/Math.h>
#include <Skill/Spell/Spell.h>
#include <Creature/CreatureManager.h>
#include <Creature/Monster/VirtualMonsterMgr.h>
#include <Map/FieldMap/CellManager.h>
#include <Map/FieldMap/VirtualArea/VirtualArea.h>
#include <Map/FieldMap/VirtualArea/VirtualAreaMgr.h>
#include <network/packet/packetstruct/serverinfo.h>
#include "Monster.h"
#include "PatternMonster.h"
//---------------------------------------------------------------------------------------
// 기본 몬스터
///////////////////////////////////////////////////////////////////////////////////
// Function : CMonster::CheckPartyTarget
//
// Description : 몬스터 파티의 타겟을 체크해서 가장 가까운 타켓을 공격한다.
//
// Inputs : None.
//
// Outputs : None.
//
// Returns : Attack 행동으로 변경되면 true 리턴, 행동에 변화가 없으면 false 리턴
///////////////////////////////////////////////////////////////////////////////////
bool CMonster::CheckPartyTarget(void)
{
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty* >(GetParty());
if (NULL == lpParty)
{
return false;
}
CMonsterParty::PartyTargetSet& targetSet = lpParty->GetPartyTargetSet();
if (true == targetSet.empty())
{
return false;
}
typedef std::map<float, CAggresiveCreature*> DistanceTargetMap;
CAggresiveCreature* lpTarget = NULL;
DistanceTargetMap attackTargetMap;
CMonsterParty::PartyTargetSet::iterator itr = targetSet.begin();
CMonsterParty::PartyTargetSet::iterator end = targetSet.end();
for (; itr != end; )
{
unsigned long dwTargetCID = *itr;
// 없는 크리쳐인지 체크
if (0 != GetMapIndex())
{
VirtualArea::CVirtualArea* lpVirtualArea = VirtualArea::CVirtualAreaMgr::GetInstance().GetVirtualArea(GetMapIndex());
if (NULL != lpVirtualArea)
{
CVirtualMonsterMgr* lpVirtualMonsterMgr = lpVirtualArea->GetMonsterManager();
if (NULL != lpVirtualMonsterMgr)
{
lpTarget = lpVirtualMonsterMgr->GetAggresiveCreature(dwTargetCID);
}
}
}
else
{
lpTarget = CCreatureManager::GetInstance().GetAggresiveCreature(dwTargetCID);
}
if (NULL == lpTarget || true == lpTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
itr = targetSet.erase(itr);
}
else
{
const Position& targetPos = lpTarget->GetCurrentPos();
// 거리 계산
float fSquareTargetDistance = (m_CurrentPos.m_fPointX - targetPos.m_fPointX) * (m_CurrentPos.m_fPointX - targetPos.m_fPointX) +
(m_CurrentPos.m_fPointZ - targetPos.m_fPointZ) * (m_CurrentPos.m_fPointZ - targetPos.m_fPointZ);
float fSquareHelpDistance = (lpParty->GetHelpRange() * lpParty->GetHelpRange());
if (fSquareTargetDistance <= fSquareHelpDistance)
{
attackTargetMap.insert(make_pair(fSquareTargetDistance, lpTarget));
}
++itr;
}
}
// 거리가 가장 가까운 타겟을 가져온다.
if (false == attackTargetMap.empty())
{
const float fAttackRange = m_MonsterInfo.m_wAttackRange / 100.0f;
DistanceTargetMap::iterator itr = attackTargetMap.begin();
const float fDistance = sqrtf(itr->first);
lpTarget = itr->second;
m_Threat.AddToThreatList(lpTarget, 1);
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_SEEN_PLAYER);
if (fAttackRange < fDistance || GetPattern() == MonsterInfo::PATTERN_MAGE || GetPattern() == MonsterInfo::PATTERN_ACOLYTE)
{
m_bLongRangeAttacked = true;
}
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////
// Function : CMonster::SearchPlayer
//
// Description : 지역내 플레이어 찾기
//
// Inputs : None.
//
// Outputs : None.
//
// Returns : None.
///////////////////////////////////////////////////////////////////////////////////
void CMonster::SearchPlayer(void)
{
// TODO : 해상도 조절을 통해 float 계산을 없애보자.
if (NULL == m_CellPos.m_lpCell)
{
ERRLOG1(g_Log, "CID:0X%08 몬스터가 셀 범위 밖에 있습니다.", m_dwCID);
return;
}
CCell* pCell = NULL;
CCharacter* pTempTarget = NULL;
CCharacter* pCurrentTarget = NULL;
const float fSquareSearchRange = (float)(m_wSearchRange * m_wSearchRange);
for (int nCellCount = 0; nCellCount < CCell::CONNECT_NUM; ++nCellCount)
{
pCell = m_CellPos.m_lpCell->GetConnectCell(nCellCount);
if (NULL == pCell || false == pCell->IsCharacter())
{
continue;
}
pTempTarget = pCell->GetFirstCharacter();
while (NULL != pTempTarget)
{
if (pTempTarget->GetStatus().m_nNowHP > 0 && false == pTempTarget->IsRideArms())
{
if (false == pTempTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Stealth) &&
false == pTempTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Invincible) &&
false == pTempTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
const float fDX = pTempTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
const float fDZ = pTempTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
const float fDistance = (fDX * fDX) + (fDZ * fDZ);
if (fDistance < fSquareSearchRange)
{
// edith 2008.02.13 선공몹 처리확율
// 레벨차에 따른 선공 확률
// 레벨 갭을 리턴한다.
// if ((CalculateLevelGap(pTempTarget) + 10) * 2 > static_cast<int>(Math::Random::ComplexRandom(100)))
int iGap = CalculateLevelGap(pTempTarget);
if(pTempTarget->GetAbilityValue(Skill::Type::AB_NOFIRSTATK))
{
// 선공 금지 어빌리티
if ( iGap > pTempTarget->GetAbilityValue(Skill::Type::AB_NOFIRSTATK))
{
pCurrentTarget = pTempTarget;
break;
}
}
else
{
// 100퍼센트 선공
if ( iGap >= 0)
{
pCurrentTarget = pTempTarget;
break;
}
else if( iGap >= -5 && static_cast<int>(Math::Random::ComplexRandom(2)) == 1)
{
// 50프로 선공
pCurrentTarget = pTempTarget;
break;
}
}
}
}
}
pTempTarget = pCell->GetNextCharacter();
}
}
if (NULL != pCurrentTarget)
{
m_Threat.AddToThreatList(pCurrentTarget, 1);
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_SEEN_PLAYER);
}
}
void CMonster::NormalBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
if (m_nLeaveMovingNum != 0)
{
m_nLeaveMovingNum--;
if (F_CELL_CANNOTCHANGE == MoveTo(CalculateCoor(), false))
{
ms_NormalBehaviorSendCount += SendMove(RUN_ANI_LIMIT_MIN);
m_nLeaveMovingNum = 0;
}
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
return;
}
// 파티가 있고, 파티가 싸우고 있다면...
// 싸우는 상대중 일정 범위 내에서.. 자신에게 가까운 녀석에게 달려간다.
// 자신의 시야 안에 들어오면 Threat 리스트에 해당 몬스터를 추가하고,
// Attack 상태로 만든다.
if (GetParty())
{
if (CheckPartyTarget()) return;
}
// 선공 몹 처리
if (NULL == m_lpTarget && true == m_MonsterInfo.m_bFirstAttack)
{
SearchPlayer();
}
// TODO : 이동 패턴에 따른 처리 (루트형이 아직 미결)
if (CCellManager::GetInstance().IsMoving() && m_nNormalMovingDelay <= 0 &&
CCellManager::GetInstance().GetMovingNum() > Math::Random::ComplexRandom(100))
{
const float fOriginalDX = m_OriginalPosition.m_fPointX - GetCurrentPos().m_fPointX;
const float fOriginalDZ = m_OriginalPosition.m_fPointZ - GetCurrentPos().m_fPointZ;
const float fOriginalDistance = (fOriginalDX * fOriginalDX) + (fOriginalDZ * fOriginalDZ);
if (fOriginalDistance > (MoveRange * MoveRange) && m_nMovingPattern == AREA)
{
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_RUN;
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo);
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_OriginalPosition.m_fPointX, m_OriginalPosition.m_fPointZ);
}
else
{
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_WALK;
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo);
m_MotionInfo.m_fDirection = (Math::Random::ComplexRandom(62832)) / 10000.0f;
}
if (m_nMovingPattern == FIXED)
{
m_MotionInfo.m_fDirection = 0;
m_MotionInfo.m_fVelocity = 0;
}
m_nNormalMovingDelay = SendMove(RUN_ANI_LIMIT_MAX);
ms_NormalBehaviorSendCount += m_nNormalMovingDelay;
m_nLeaveMovingNum--;
MoveTo(CalculateCoor(), false);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
else
{
m_nNormalMovingDelay -= RATE_MOVING_AND_PLAYER;
m_lCurrentFrame = FPS;
}
}
void CMonster::AttackBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
m_lpTarget = m_Threat.GetTarget();
if (NULL == m_lpTarget || MonsterInfo::PATTERN_BG == m_MonsterInfo.m_cSkillPattern)
{
CancelTarget();
return;
}
else if (m_lpTarget && true == m_lpTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
CancelTarget();
return;
}
// 병기에 탑승한 캐릭터는 때리지 않는다.
if (Creature::CT_PC == Creature::GetCreatureType(m_lpTarget->GetCID()))
{
CCharacter* lpTargetChar = reinterpret_cast<CCharacter*>(m_lpTarget);
if (lpTargetChar->IsRideArms())
{
CancelTarget();
return;
}
}
const float fDX = m_lpTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
const float fDZ = m_lpTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
const float fDistance = sqrtf((fDX * fDX) + (fDZ * fDZ));
// 검색할수 있는 영역보다 넓거나 HP가 0이면 캔슬한다.
if (((fDistance > m_wSearchRange) && false == m_bLongRangeAttacked) || (0 == m_lpTarget->GetStatus().m_nNowHP))
{
CancelTarget();
return;
}
// 파티의 타겟으로 집어 넣는다.
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
if (NULL != lpParty)
{
// 레벨차이가 너무 나면 파티의 타겟으로 집어넣지 않는다.
if (lpParty->GetHighestLevel() + CMonsterParty::ERROR_OF_LEVEL_GAP > m_lpTarget->GetStatus().m_nLevel)
{
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
}
else if (GetPattern() == MonsterInfo::PATTERN_BOSS ||
GetPattern() == MonsterInfo::PATTERN_NAMED ||
GetPattern() == MonsterInfo::PATTERN_CHIEF)
{
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
}
}
const float fAttackRange = m_MonsterInfo.m_wAttackRange / 100.0f;
if (fDistance > fAttackRange && 0 >= m_lCurrentFrame)
{
// 공격 범위 밖이다.
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_WALK; // 일단 걷기로 세팅
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo); // 걷기 모션 정보를 얻어온다.
// 몬스터가 이동 공격을 할만한 거리인가 체크
if (fDistance < fAttackRange + m_MotionInfo.m_fVelocity)
{
// edith 2009.08.06 공격시 걸어서 공격을 없앤다.
// 달려간다
RunAction(fDistance, m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
// 걸어가면서 공격
// WalkAttackAction(fDistance - fAttackRange + 0.1f);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
else
{
// 달려간다
RunAction(fDistance, m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
if (false == m_bAttacking)
{
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
}
else
{
// 공격 범위 안이다.
if (false == m_bAttacking)
{
AttackAction();
}
}
}
void CMonster::ReturnBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
if (m_nLeaveMovingNum <= 0)
{
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_RUN;
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo);
const float fOriginalDX = m_OriginalPosition.m_fPointX - GetCurrentPos().m_fPointX;
const float fOriginalDZ = m_OriginalPosition.m_fPointZ - GetCurrentPos().m_fPointZ;
const float fOriginalDistance = (fOriginalDX * fOriginalDX) + (fOriginalDZ * fOriginalDZ);
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_OriginalPosition.m_fPointX, m_OriginalPosition.m_fPointZ);
unsigned short nAniNum = (unsigned short)(sqrtf(fOriginalDistance) / m_MotionInfo.m_fVelocity);
nAniNum = (nAniNum > RUN_ANI_LIMIT_MAX) ? RUN_ANI_LIMIT_MAX : nAniNum;
if (nAniNum == 0)
{
m_wSearchRange = m_wDefaultSearchRange;
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ARRIVAL_SITE);
m_Threat.ClearThreatList();
CancelTarget();
return;
}
ms_ReturnBehaviorSendCount += SendMove(nAniNum);
}
m_nLeaveMovingNum--;
if (F_CELL_CANNOTCHANGE == MoveTo(CalculateCoor(), false))
{
ms_ReturnBehaviorSendCount += SendMove(RUN_ANI_LIMIT_MIN);
m_nLeaveMovingNum = 0;
}
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
void CMonster::EscapeBehavior(void)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
if (NULL == m_lpTarget) {
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_LEAVE_PLAYER);
return;
}
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_RUN;
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo);
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
m_MotionInfo.m_fDirection += Math::Const::PI;
if (m_MotionInfo.m_fDirection >= (Math::Const::PI * 2.0f)) {
m_MotionInfo.m_fDirection -= (Math::Const::PI * 2.0f);
}
ms_EscapeBehaviorSendCount += SendMove(RUN_ANI_LIMIT_MIN);
MoveTo(CalculateCoor(), false);
m_nLeaveMovingNum = 0;
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
void CMonster::DeadBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
if (dwTick < m_dwLastTime + m_MonsterInfo.m_dwRespawnTime)
{
m_lCurrentFrame = FPS;
return;
}
// m_Threat.ClearThreatList();
// CancelTarget();
m_nLeaveMovingNum = 0;
Respawn(dwTick);
}
//---------------------------------------------------------------------------------------
// 소환 몬스터
void CSummonMonster::NormalBehavior(unsigned long dwTick)
{
if (NULL == m_lpMaster)
{
CMonster::NormalBehavior(dwTick);
return;
}
--m_nLeaveMovingNum;
if (0 >= m_nLeaveMovingNum)
{
const float fDX = m_lpMaster->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
const float fDZ = m_lpMaster->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
const float fDistance = sqrtf((fDX * fDX) + (fDZ * fDZ));
if (fDistance <= SUPPORT_DISTANCE) { return; }
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_RUN;
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo);
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_lpMaster->GetCurrentPos().m_fPointX, m_lpMaster->GetCurrentPos().m_fPointZ);
unsigned short nAniNum = static_cast<unsigned short>(fDistance / m_MotionInfo.m_fVelocity);
nAniNum = (nAniNum > RUN_ANI_LIMIT_MAX) ? RUN_ANI_LIMIT_MAX : nAniNum;
if (0 == nAniNum) { return; }
ms_NormalBehaviorSendCount += SendMove(nAniNum);
}
if (F_CELL_CANNOTCHANGE == MoveTo(CalculateCoor(), false))
{
m_nLeaveMovingNum = 0;
ms_ReturnBehaviorSendCount += SendMove(RUN_ANI_LIMIT_MIN);
}
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
void CSummonMonster::AttackBehavior(unsigned long dwTick)
{
if (NULL != m_lpMaster)
{
m_OriginalPosition = m_lpMaster->GetCurrentPos();
}
CMonster::AttackBehavior(dwTick);
}
void CSummonMonster::ReturnBehavior(unsigned long dwTick)
{
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ARRIVAL_SITE);
m_Threat.ClearThreatList();
CancelTarget();
}
void CSummonMonster::DeadBehavior(unsigned long dwTick)
{
// 소환 몬스터는 사망하면 삭제될 때까지 대기한다. 삭제는 메인 루프에서 일괄 처리한다.
}
//---------------------------------------------------------------------------------------
// 명예의 석상
void CStatue::NormalBehavior(unsigned long dwTick)
{
unsigned char cServerZone = CServerSetup::GetInstance().GetServerZone();
if (SERVER_ID::ZONE3 == cServerZone || SERVER_ID::STATUE_ZONE == cServerZone)
{
CCreatureManager::GetInstance().ProcessAllCharacter(CStatueInfo(this, false, 0));
}
// 1레벨
if (MonsterInfo::STATUE_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::STATUE_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::POWER_STATUE_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::POWER_STATUE_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::INT_STATUE_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::INT_STATUE_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::EXP_STATUE_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::EXP_STATUE_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::WEALTH_STATUE_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::WEALTH_STATUE_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
//2레벨
MonsterInfo::STATUE_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::STATUE_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::POWER_STATUE_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::POWER_STATUE_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::INT_STATUE_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::INT_STATUE_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::EXP_STATUE_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::EXP_STATUE_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::WEALTH_STATUE_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::WEALTH_STATUE_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID)
{
if (dwTick > m_dwLastTime + STATUE_LOADING_TIME)
{
Dead(NULL);
return;
}
}
else if (MonsterInfo::BG_STATUE_HUMAN_LOADING1 == m_MonsterInfo.m_dwKID ||
MonsterInfo::BG_STATUE_AKHAN_LOADING1 == m_MonsterInfo.m_dwKID ||
// 2레벨
MonsterInfo::BG_STATUE_HUMAN_LOADING2 == m_MonsterInfo.m_dwKID ||
MonsterInfo::BG_STATUE_AKHAN_LOADING2 == m_MonsterInfo.m_dwKID)
{
if (dwTick > m_dwLastTime + BG_STATUE_LOADING_TIME)
{
Dead(NULL);
return;
}
}
m_lCurrentFrame = UPDATE_INFO_CYCLE;
}
void CStatue::AttackBehavior(unsigned long dwTick)
{
m_lpTarget = m_Threat.GetTarget();
if (NULL == m_lpTarget || m_wDefaultSearchRange < m_CurrentPos.GetDistance(m_lpTarget->GetCurrentPos()))
{
CancelTarget();
}
else if (m_lpTarget && true == m_lpTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
CancelTarget();
}
NormalBehavior(dwTick);
}
void CStatue::ReturnBehavior(unsigned long dwTick)
{
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ARRIVAL_SITE);
NormalBehavior(dwTick);
}
void CStatue::DeadBehavior(unsigned long dwTick)
{
// 구조물은 죽은 후 자동으로 리스폰되지 않는다. 링크된 다른 구조물에 의해 리스폰.
}
// -------------------------------------------------------------------------------------
// 수집 몬스터
void CGatherMonster::NormalBehavior(unsigned long dwTick)
{
return;
}
void CGatherMonster::AttackBehavior(unsigned long dwTick)
{
m_lpTarget = m_Threat.GetTarget();
if (NULL != m_lpTarget)
{
// 파티의 타겟으로 집어 넣는다.
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
if (NULL != lpParty)
{
// 레벨차이가 너무 나면 파티의 타겟으로 집어넣지 않는다.
if (lpParty->GetHighestLevel() + CMonsterParty::ERROR_OF_LEVEL_GAP > m_lpTarget->GetStatus().m_nLevel)
{
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
return;
}
}
}
CancelTarget(true);
}
void CGatherMonster::ReturnBehavior(unsigned long dwTick)
{
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ARRIVAL_SITE);
}
// -------------------------------------------------------------------------------------
// 오브젝트 몬스터 (꽃, 나무, 버섯류)
void CObjectMonster::NormalBehavior(unsigned long dwTick)
{
return;
}
void CObjectMonster::AttackBehavior(unsigned long dwTick)
{
m_lpTarget = m_Threat.GetTarget();
if (NULL != m_lpTarget)
{
// 파티의 타겟으로 집어 넣는다.
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
if (NULL != lpParty)
{
// 레벨차이가 너무 나면 파티의 타겟으로 집어넣지 않는다.
if (lpParty->GetHighestLevel() + CMonsterParty::ERROR_OF_LEVEL_GAP > m_lpTarget->GetStatus().m_nLevel)
{
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
return;
}
}
}
CancelTarget(true);
}
void CObjectMonster::ReturnBehavior(unsigned long dwTick)
{
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ARRIVAL_SITE);
}
//---------------------------------------------------------------------------------------
// 패턴 몬스터
void CSkillMonster::NormalBehavior(unsigned long dwTick)
{
CMonster::NormalBehavior(dwTick);
// Chant 스킬 사용을 끈다.
if (GetPattern() == MonsterInfo::PATTERN_BOSS ||
GetPattern() == MonsterInfo::PATTERN_NAMED ||
GetPattern() == MonsterInfo::PATTERN_CHIEF)
{
m_SpellMgr.GetCastingInfo().DisableChant();
}
}
void CSkillMonster::AttackBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
// 마법 캐스팅 중일때는.. 아무런 다른 행동을 해서는 안된다.
if (true == m_bCasting)
{
CastingAttackAction();
return;
}
m_lpTarget = m_Threat.GetTarget();
if (NULL == m_lpTarget || MonsterInfo::PATTERN_BG == m_MonsterInfo.m_cSkillPattern)
{
CancelTarget();
return;
}
else if (m_lpTarget && true == m_lpTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
CancelTarget();
return;
}
// 병기에 탑승한 캐릭터는 때리지 않는다.
if (Creature::CT_PC == Creature::GetCreatureType(m_lpTarget->GetCID()))
{
CCharacter* lpTargetChar = reinterpret_cast<CCharacter*>(m_lpTarget);
if (lpTargetChar->IsRideArms())
{
CancelTarget();
return;
}
}
const float fDX = m_lpTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
const float fDZ = m_lpTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
const float fDistance = sqrtf((fDX * fDX) + (fDZ * fDZ));
if (((fDistance > m_wSearchRange) && false == m_bLongRangeAttacked) || (0 == m_lpTarget->GetStatus().m_nNowHP))
{
CancelTarget();
return;
}
// 파티의 타겟으로 집어 넣는다.
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
if (NULL != lpParty)
{
// 레벨차이가 너무 나면 파티의 타겟으로 집어넣지 않는다.
if (lpParty->GetHighestLevel() + CMonsterParty::ERROR_OF_LEVEL_GAP > m_lpTarget->GetStatus().m_nLevel)
{
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
}
else if (GetPattern() == MonsterInfo::PATTERN_BOSS ||
GetPattern() == MonsterInfo::PATTERN_NAMED ||
GetPattern() == MonsterInfo::PATTERN_CHIEF)
{
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
}
}
const float fAttackRange = m_MonsterInfo.m_wAttackRange / 100.0f;
if (fDistance > fAttackRange && 0 >= m_lCurrentFrame)
{
// 공격 범위 밖이다.
if (true == SkillAttack()) // 스킬 공격을 사용하는가 체크
{
SkillAttackAction();
}
else
{
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_WALK; // 일단 걷기로 세팅
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo); // 걷기 모션 정보를 얻어온다.
// 몬스터가 이동 공격을 할만한 거리인가 체크
if (fDistance < fAttackRange + m_MotionInfo.m_fVelocity)
{
// edith 2009.08.06 공격시 걸어서 공격을 없앤다.
// 달려간다
RunAction(fDistance, m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
// 걸어가면서 공격한다.
// WalkAttackAction(fDistance - fAttackRange + 0.1f);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
else
{
// 달려간다
RunAction(fDistance, m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
}
if (false == m_bAttacking)
{
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
}
else
{
// 공격 범위 안이다.
if (false == m_bAttacking)
{
if (true == SkillAttack()) // 스킬 공격을 사용하는가 체크
{
SkillAttackAction();
}
else
{
AttackAction();
}
}
}
}
void CMageMonster::AttackBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
// 일정 거리를 유지하면서 마법 공격을 사용
if (GetParty() == NULL) CSkillMonster::AttackBehavior(dwTick);
else
{
// 마법 캐스팅 중일때는.. 아무런 다른 행동을 해서는 안된다.
if (true == m_bCasting)
{
CastingAttackAction();
return;
}
m_lpTarget = m_Threat.GetTarget();
if (NULL == m_lpTarget)
{
CancelTarget();
return;
}
else if (m_lpTarget && true == m_lpTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
CancelTarget();
return;
}
// 병기에 탑승한 캐릭터는 때리지 않는다.
if (Creature::CT_PC == Creature::GetCreatureType(m_lpTarget->GetCID()))
{
CCharacter* lpTargetChar = reinterpret_cast<CCharacter*>(m_lpTarget);
if (lpTargetChar->IsRideArms())
{
CancelTarget();
return;
}
}
const float fDX = m_lpTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
const float fDZ = m_lpTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
const float fDistance = sqrtf((fDX * fDX) + (fDZ * fDZ));
if (((fDistance > m_wSearchRange) && false == m_bLongRangeAttacked) || (0 == m_lpTarget->GetStatus().m_nNowHP))
{
CancelTarget();
return;
}
// 파티의 타겟으로 집어 넣는다.
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
if (NULL != lpParty)
{
// 레벨차이가 너무 나면 파티의 타겟으로 집어넣지 않는다.
if (lpParty->GetHighestLevel() + CMonsterParty::ERROR_OF_LEVEL_GAP > m_lpTarget->GetStatus().m_nLevel)
{
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
}
}
// 거리 체크
float fToMemberX, fToMemberZ, fToTargetX, fToTargetZ;
float fDstX, fDstZ;
fToTargetX = m_lpTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
fToTargetZ = m_lpTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
CAggresiveCreature* lpHiLVMember = FindHighestLVMember();
if (lpHiLVMember)
{
fToMemberX = lpHiLVMember->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
fToMemberZ = lpHiLVMember->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
}
else
{
fToMemberX = GetCurrentPos().m_fPointX;
fToMemberZ = GetCurrentPos().m_fPointZ;
}
if (0 >= m_lCurrentFrame)
{
if (fDistance < NEAR_DIST_TO_TARGET)
{
// 너무 가까우면 도망간다.
fDstX = GetCurrentPos().m_fPointX - (fToMemberX + fToTargetX) / 2;
fDstZ = GetCurrentPos().m_fPointZ - (fToMemberZ + fToTargetZ) / 2;
// 달려간다
RunAction(fDistance, fDstX, fDstZ);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
else if (fDistance > FAR_DIST_TO_TARGET)
{
fDstX = GetCurrentPos().m_fPointX + (fToMemberX + fToTargetX) / 2;
fDstZ = GetCurrentPos().m_fPointZ + (fToMemberZ + fToTargetZ) / 2;
// 달려간다
RunAction(fDistance, fDstX, fDstZ);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
else
{
// 스킬 공격을 사용하는가 체크
if (false == m_bAttacking && true == SkillAttack())
{
SkillAttackAction();
}
}
if (false == m_bAttacking)
{
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
}
}
}
void CAcolyteMonster::AttackBehavior(unsigned long dwTick)
{
PERFORMANCE_CHECK(FunctionTimingCheck)
// 일정 거리를 유지하면서 마법 공격을 사용
if (GetParty() == NULL) CSkillMonster::AttackBehavior(dwTick);
else
{
// 마법 캐스팅 중일때는.. 아무런 다른 행동을 해서는 안된다.
if (true == m_bCasting)
{
CastingAttackAction();
return;
}
m_lpTarget = m_Threat.GetTarget();
if (NULL == m_lpTarget)
{
CancelTarget();
return;
}
else if (m_lpTarget && true == m_lpTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
CancelTarget();
return;
}
// 병기에 탑승한 캐릭터는 때리지 않는다.
if (Creature::CT_PC == Creature::GetCreatureType(m_lpTarget->GetCID()))
{
CCharacter* lpTargetChar = reinterpret_cast<CCharacter*>(m_lpTarget);
if (lpTargetChar->IsRideArms())
{
CancelTarget();
return;
}
}
const float fDX = m_lpTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
const float fDZ = m_lpTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
const float fDistance = sqrtf((fDX * fDX) + (fDZ * fDZ));
if (((fDistance > m_wSearchRange) && false == m_bLongRangeAttacked) || (0 == m_lpTarget->GetStatus().m_nNowHP))
{
CancelTarget();
return;
}
// 파티의 타겟으로 집어 넣는다.
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
if (NULL != lpParty)
{
// 레벨차이가 너무 나면 파티의 타겟으로 집어넣지 않는다.
if (lpParty->GetHighestLevel() + CMonsterParty::ERROR_OF_LEVEL_GAP > m_lpTarget->GetStatus().m_nLevel)
{
lpParty->GetPartyTargetSet().insert(m_lpTarget->GetCID());
// set 에서 없어지는 것은 CancelTarget 에서 처리한다.
}
}
// 거리 체크
float fToMemberX, fToMemberZ, fToTargetX, fToTargetZ;
float fDstX, fDstZ;
fToTargetX = m_lpTarget->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
fToTargetZ = m_lpTarget->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
CAggresiveCreature* lpHiLVMember = FindHighestLVMember();
if (lpHiLVMember)
{
fToMemberX = lpHiLVMember->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
fToMemberZ = lpHiLVMember->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
}
else
{
fToMemberX = GetCurrentPos().m_fPointX;
fToMemberZ = GetCurrentPos().m_fPointZ;
}
if (0 >= m_lCurrentFrame)
{
if (fDistance < NEAR_DIST_TO_TARGET)
{
// 너무 가까우면 도망간다.
fDstX = GetCurrentPos().m_fPointX - (fToMemberX + fToTargetX) / 2;
fDstZ = GetCurrentPos().m_fPointZ - (fToMemberZ + fToTargetZ) / 2;
// 달려간다
RunAction(fDistance, fDstX, fDstZ);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
else if (fDistance > FAR_DIST_TO_TARGET)
{
fDstX = GetCurrentPos().m_fPointX + (fToMemberX + fToTargetX) / 2;
fDstZ = GetCurrentPos().m_fPointZ + (fToMemberZ + fToTargetZ) / 2;
// 달려간다
RunAction(fDistance, fDstX, fDstZ);
// 내 활동구역을 벗어나따. 돌아가자~!
if (true == m_MonsterInfo.m_bReturnPosition && true == IsReturn())
{
m_wSearchRange = OutsideSearchRange;
CancelTarget();
}
}
else
{
// 스킬 공격을 사용하는가 체크
if (false == m_bAttacking && true == SkillAttack())
{
SkillAttackAction();
}
}
if (false == m_bAttacking)
{
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
}
}
}
void CMonster::AttackAction()
{
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
if (false == MultiAttack())
{
ERRLOG1(g_Log, "CID:0x%08x 몬스터가 공격을 실패하였습니다.", m_dwCID);
}
m_bAttacking = true;
m_bLongRangeAttacked = false;
GetMotion(MonsterInfo::Z3D_CA_ATTACK, m_MotionInfo);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
m_MotionInfo.m_fVelocity = 0;
// edith 2009.08.06 스킬 공격시 이동하지 않게 하기
ms_AttackBehaviorSendCount += SendMove(1);
m_nLeaveMovingNum = 0;
}
void CMonster::WalkAttackAction(float fVelocity)
{
// 공격 범위 밖이다.
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_WALK; // 일단 걷기로 세팅
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo); // 걷기 모션 정보를 얻어온다.
// 걸어간다.
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
// 적당히 걸어가서...
// m_MotionInfo.m_fVelocity = fDistance - fAttackRange + 0.1f;
m_MotionInfo.m_fVelocity = fVelocity;
// 공격~!!
if (false == MultiAttack())
{
ERRLOG1(g_Log, "CID:0x%08x 몬스터가 공격을 실패하였습니다.", m_dwCID);
}
m_bAttacking = true;
m_bLongRangeAttacked = false;
ms_AttackBehaviorSendCount += SendMove(RUN_ANI_LIMIT_MAX);
MoveTo(CalculateCoor(), false);
m_nLeaveMovingNum = 0;
GetMotion(MonsterInfo::Z3D_CA_ATTACK, m_MotionInfo);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
void CMonster::RunAction(float fDistance, float fDstX, float fDstZ)
{
// 달려간다
m_MotionInfo.m_wAction = MonsterInfo::Z3D_CA_RUN;
GetMotion(m_MotionInfo.m_wAction, m_MotionInfo);
float fNewDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ, fDstX, fDstZ);
if (m_nLeaveMovingNum <= 0 || (m_MotionInfo.m_fDirection - fNewDirection) > 1.0f || (fNewDirection - m_MotionInfo.m_fDirection) > 1.0f)
{
m_MotionInfo.m_fDirection = fNewDirection;
short nAniNum = static_cast<short>(fDistance / m_MotionInfo.m_fVelocity);
if(nAniNum <= 0)
nAniNum = RUN_ANI_LIMIT_MIN;
// edith 2008.01.23 몬스터가 공격을 시작했을대 이동할수있는 루프
nAniNum = (nAniNum > RUN_ANI_LIMIT_MAX) ? RUN_ANI_LIMIT_MAX : nAniNum;//RUN_ANI_LIMIT_MIN;
ms_AttackBehaviorSendCount += SendMove(nAniNum);
}
--m_nLeaveMovingNum;
if (F_CELL_CANNOTCHANGE == MoveTo(CalculateCoor(), false))
{
ms_AttackBehaviorSendCount += SendMove(RUN_ANI_LIMIT_MIN);
m_nLeaveMovingNum = 0;
}
}
bool CMonster::IsReturn()
{
const float fOriginalDX = m_OriginalPosition.m_fPointX - GetCurrentPos().m_fPointX;
const float fOriginalDZ = m_OriginalPosition.m_fPointZ - GetCurrentPos().m_fPointZ;
const float fOriginalDistance = (fOriginalDX * fOriginalDX) + (fOriginalDZ * fOriginalDZ);
const int nTempReturnRange = (true == m_bLongRangeAttacked) ? (LongRange + Math::Random::ComplexRandom(6) - 2) : (ReturnRange + Math::Random::ComplexRandom(6) - 2);
if (fOriginalDistance > (nTempReturnRange * nTempReturnRange)) return true;
return false;
}
void CSkillMonster::CastingAttackAction()
{
// 캐스팅 애니메이션 중이라면...
if (m_lCurrentFrame > 0)
{
CAggresiveCreature* pTarget = CCreatureManager::GetInstance().GetAggresiveCreature(m_dwTargetCID);
if (NULL == pTarget ||
pTarget->GetMapIndex() != GetMapIndex() ||
true == pTarget->GetEnchantInfo().GetFlag(Skill::SpellID::Hide))
{
m_dwTargetCID = 0;
m_lpTarget = NULL;
m_bCasting = false;
CancelTarget();
return;
}
// 병기에 탑승한 캐릭터는 때리지 않는다.
if (Creature::CT_PC == Creature::GetCreatureType(pTarget->GetCID()))
{
CCharacter* lpTargetChar = reinterpret_cast<CCharacter*>(pTarget);
if (lpTargetChar->IsRideArms())
{
m_dwTargetCID = 0;
m_lpTarget = NULL;
m_bCasting = false;
CancelTarget();
return;
}
}
return;
}
// 치프, 메이지, 어콜라이트는 캐스팅 동작을 오래 취한다.
if (GetPattern() == MonsterInfo::PATTERN_CHIEF ||
GetPattern() == MonsterInfo::PATTERN_MAGE ||
GetPattern() == MonsterInfo::PATTERN_ACOLYTE)
{
if (m_nCastingCount > 0)
{
if (m_lpTarget)
{
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
GetMotion(MonsterInfo::Z3D_CA_CASTING, m_MotionInfo);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
m_bCasting = true;
m_MotionInfo.m_fVelocity = 0.0f;
// edith 2009.08.06 스킬 공격시 이동하지 않게 하기
ms_AttackBehaviorSendCount += SendMove(1);
m_nLeaveMovingNum = 0;
--m_nCastingCount;
}
else
{
m_dwTargetCID = 0;
m_bCasting = false;
m_nCastingCount = 0;
CancelTarget();
}
return;
}
}
// 캐스팅이 끝난후 스킬 사용
UseCastedSkill();
m_bCasting = false;
m_bAttacking = true;
m_bLongRangeAttacked = false;
m_nCastingCount = 0;
GetMotion(MonsterInfo::Z3D_CA_ATTACK, m_MotionInfo);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
}
void CSkillMonster::SkillAttackAction()
{
m_bAttacking = true;
m_MotionInfo.m_fDirection = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
// 캐스팅 타입의 스킬이라면 캐스팅 액션으로 변경
if (true == m_bCasting)
{
GetMotion(MonsterInfo::Z3D_CA_CASTING, m_MotionInfo);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
m_MotionInfo.m_fVelocity = 0.0f;
// edith 2009.08.06 스킬 공격시 이동하지 않게 하기
ms_AttackBehaviorSendCount += SendMove(1);
m_nLeaveMovingNum = 0;
m_nCastingCount = m_attackType.m_cSkillLockCount;
}
else
{
GetMotion(MonsterInfo::Z3D_CA_ATTACK, m_MotionInfo);
m_lCurrentFrame = m_MotionInfo.m_dwFrame;
m_MotionInfo.m_fVelocity = 0.0f;
// edith 2009.08.06 스킬 공격시 이동하지 않게 하기
ms_AttackBehaviorSendCount += SendMove(1);
m_nLeaveMovingNum = 0;
m_nCastingCount = 0;
}
}