Files
2026-06-01 12:46:52 +02:00

7541 lines
223 KiB
C++

#include <network/IConnection.h>
#include <mmo/ArRegion.h>
#include <mmo/ArcadiaServer.h>
#include <toolkit/XConsole.h>
#include <toolkit/XQuadScanner.h>
#include <toolkit/XRandom.h>
#include <toolkit/XSTLUtil.h>
#include <toolkit/XEnv.h>
#include "ErrorCode/ErrorCode.h"
#include "LogClient/LogClient.h"
#include "StructCreature.h"
#include "StructPlayer.h" // 흠 ;;
#include "StructMonster.h" // 냥... ;;
#include "StructNPC.h"
#include "StructSkill.h"
#include "StructSkillProp.h"
#include "StructSummon.h"
#include "StructPet.h"
#include "StructItem.h"
#include "StructProc.h"
#include "CreatureBase.h"
#include "Extern.h"
#include "GameAllocator.h"
#include "SendMessage.h"
#include "GameProc.h"
#include "GameContent.h"
#include "GameMessage.h"
#include "ChannelManager.h"
#include "GuildManager.h"
#include "DungeonManager.h"
#include "PartyManager.h"
#include "DB_Commands.h"
#include "HuntaholicManager.h"
#include "Damage.h"
XQuadScanner< StructCreature::QuadTreeItem, AR_UNIT > * g_pQuadScanner = NULL;
volatile LONG s_nMaxSkillUID;
volatile LONG s_nMaxStateUID;
static std::vector< int > s_vDisabledStateList;
static XCriticalSection s_csDisabledStateListLock( "s_csDisabledStateListLock" );
volatile int g_bIgnoreRandomDamage = 0;
void StructCreature::InitCreatureSystem()
{
if( !g_pQuadScanner )
g_pQuadScanner = new XQuadScanner< StructCreature::QuadTreeItem, AR_UNIT >( g_nMapWidth, g_nMapHeight );
}
void StructCreature::DeInitCreatureSystem()
{
if( g_pQuadScanner )
{
delete g_pQuadScanner;
g_pQuadScanner = NULL;
}
}
bool StructCreature::QuadTreeItem::IsCanAdd( AR_UNIT x, AR_UNIT y )
{
if( g_pQuadScanner )
return !g_pQuadScanner->IsExist( QuadTreeItem::getX( x ), QuadTreeItem::getY( y ) );
return true;
}
void StructCreature::QuadTreeItem::AddMe()
{
x = getX( pParent->GetX() );
y = getX( pParent->GetX() );
if( g_pQuadScanner )
g_pQuadScanner->Add( *this );
}
bool StructCreature::QuadTreeItem::RemoveMe()
{
if( g_pQuadScanner )
{
if( g_pQuadScanner->Remove( *this ) ) return true;
// assert( 0 );
return false;
}
return true;
}
static int applyPercentageVar( const int & base_var, const int & percentage )
{
return base_var * ( ( 100 - percentage ) / 100 ); // 비율적용
}
template <typename T >
T MIN( const T & lh, const T & rh )
{
return lh < rh ? lh : rh;
}
template <typename T >
T MAX( const T & lh, const T & rh )
{
return lh > rh ? lh : rh;
}
StructCreature::StructCreature()
{
m_nTotalJobPoint = 0;
m_nMaxHP = m_nHP = 1;
m_nMinHP = 0;
m_fMinHP = 0.0f;
m_nMaxMP = m_nMP = 1;
m_nHPDecPart = m_nMPDecPart = 0;
m_nRegenHP = 0;
m_nRegenHP = 0;
m_nMaxEnergy = 0;
m_nEnergy = 0;
m_nEnergyStartPos = 0;
m_nEnergyUpkeepTime = 0;
m_nEnergyUnconsumptionRate = 0;
memset( m_arEnergy, 0, sizeof( m_arEnergy ) );
m_StatusFlag.On( STATUS_FIRST_ENTER );
m_bNeedToBroadcastStatusFlag = false;
quadTreeItem.Set( *this );
m_nUnitExpertLevel = 0;
m_fWeight = 0.0f;
m_nNextAttackableTime = 0;
m_nNextCastableTime = 0;
m_nDeadTime = GetArTime();
m_nMovableTime = 0;
m_nNextAttackMode = StructCreature::AM_AIMING;
m_fBowInterval = 0.0f;
m_nCriticalCount = 0;
m_nCastKeep = 100;
m_fBattleLevel = m_nLevel = m_nMaxReachedLevel = 1;
m_nEXP = 0;
m_nLastDecreasedEXP = 0;
m_fEXPMod = 0.0f;
m_nJob = 100;
m_nJobLevel= 0;
m_nJobPoint= 0;
m_fItemMod = 1.0f;
m_fHealRatio = 1.0f;
m_fMPHealRatio = 1.0f;
m_fHealRatioByItem = 1.0f;
m_fMPHealRatioByItem = 1.0f;
m_fHealRatioByRest = 2.0f;
m_fMPHealRatioByRest = 2.0f;
m_nAdditionalHeal = 0;
m_nAdditionalMPHeal = 0;
m_nAdditionalHealByItem = 0;
m_nAdditionalMPHealByItem = 0;
m_nAdditionalHealByRest = 0;
m_nAdditionalMPHealByRest = 0;
m_fHateRatio = 1.0f;
m_nCurrentStateUID = 0;
m_nPendedClearStateFlag = 0;
m_fResistHarmfulState = 0.0f;
m_mapResistHarmfulState.clear();
m_nLastUpdateTime = GetArTime();
m_nLastStateProcTime = GetArTime();
m_hEnemy = 0;
m_StatusFlag.On( STATUS_FIRST_ATTACK );
m_hSkillTarget = NULL;
m_hTrap = NULL;
m_pCastSkill = NULL;
memset( m_anWear, 0, sizeof( m_anWear ) );
memset( m_nPrevJobId, 0, sizeof( m_nPrevJobId ) );
memset( m_nPrevJobLevel, 0, sizeof( m_nPrevJobLevel ) );
m_nPendingMoveSpeed = 0;
m_fDetectHideRange = 0.0f;
m_StatusFlag.On( STATUS_MOVABLE );
m_StatusFlag.On( STATUS_ATTACKABLE );
m_StatusFlag.On( STATUS_SKILL_CASTABLE );
m_StatusFlag.On( STATUS_MAGIC_CASTABLE );
m_StatusFlag.On( STATUS_ITEM_USABLE );
m_StatusFlag.On( STATUS_MORTAL );
m_StatusFlag.On( STATUS_NEED_TO_CALCULATE_STAT );
m_nDoubleWeaponMasteryLevel = 0;
memset( &m_ParameterForSummon.Stat, 0, sizeof( m_ParameterForSummon.Stat ) );
memset( &m_ParameterForSummon.Attribute, 0, sizeof( m_ParameterForSummon.Attribute ) );
memset( &m_ParameterForSummon.Resist, 0, sizeof( m_ParameterForSummon.Resist ) );
m_ParameterForSummon.nMaxHP = 0;
m_ParameterForSummon.nMaxMP = 0;
m_ParameterForSummon.nMaxSP = 0;
m_ParameterForSummon.nSPRegenAdd = 0;
m_ParameterForSummon.fAttackPointRightWithoutWeapon = 0.0f;
m_ParameterForSummon.fMoveSpeed = 0.0f;
m_ParameterForSummon.fAttackRange = 0.0f;
m_ParameterForSummon.fMaxHPAmplifier = 0.0f;
m_ParameterForSummon.fMaxMPAmplifier = 0.0f;
m_ParameterForSummon.fMaxSPAmplifier = 0.0f;
m_ParameterForSummon.fSPRegenAddAmplifier = 0.0f;
memset( &m_ParameterForSummon.AttributeAmplifier, 0, sizeof( m_ParameterForSummon.AttributeAmplifier ) );
}
StructCreature::~StructCreature()
{
// 스킬 정보 삭제
std::vector< StructSkill * >::iterator it;
for( it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
// 메모리 풀 사용하도록 변경
//delete *it;
(*it)->FreeSkill();
}
}
int StructCreature::GetJobDepth() const
{
const JobInfo* pJobInfo = GameContent::GetJobInfo( GetJobId() );
return ( pJobInfo ? pJobInfo->job_depth :0 );
}
void StructCreature::SetMaxSkillUID( int nUID )
{
s_nMaxSkillUID = nUID;
}
void StructCreature::SetMaxStateUID( int nUID )
{
s_nMaxStateUID = nUID;
}
void StructCreature::SetDisabledStateList( std::vector< int > & vDisabledStateList )
{
THREAD_SYNCHRONIZE( s_csDisabledStateListLock );
s_vDisabledStateList.swap( vDisabledStateList );
}
void StructCreature::AddExp( __int64 exp, __int64 jp, bool bApplyStamina )
{
m_nEXP += ( exp );
m_nJobPoint += jp;
if( jp > 0 )
{
m_nTotalJobPoint += ( jp );
}
if( IsLoginComplete() )
onExpChange();
}
void StructCreature::SetEXP( __int64 exp )
{
m_nEXP = exp;
if( IsLoginComplete() )
onExpChange();
}
void StructCreature::SetJP( __int64 jp )
{
m_nJobPoint = jp;
if( m_nJobPoint < 0 )
m_nJobPoint = 0;
if( IsLoginComplete() )
onJPChange();
}
void StructCreature::regenHPMP( AR_TIME t )
{
if( t - m_nLastUpdateTime < 300 )
return;
// 기존의 100초간 리젠량 표기에서 60초간 리젠량으로 파라미터 룰 변경 (2007-03-24 리뉴얼 적용)
float f_100 = ( t - m_nLastUpdateTime ) / 6000.0f;
if( IsAlive() )
{
float pt = 0;
int prev_hp = GetHP();
int prev_mp = GetMP();
if( !IsMagicalImmune() )
{
if( m_Attribute.fHPAdd )
{
pt = m_Attribute.fHPAdd * f_100;
if( pt < 1 ) pt += 1;
if( pt ) Heal( pt );
}
if( m_Attribute.fHPAddByItem )
{
pt = m_Attribute.fHPAddByItem * f_100;
if( pt < 1 ) pt += 1;
if( pt ) HealByItem( pt );
}
if( m_Attribute.fMPAdd )
{
pt = m_Attribute.fMPAdd * f_100;
if( pt < 1 ) pt += 1;
if( pt ) MPHeal( pt );
}
if( m_Attribute.fMPAddByItem )
{
pt = m_Attribute.fMPAddByItem * f_100;
if( pt < 1 ) pt += 1;
if( pt ) MPHealByItem( pt );
}
}
// HP 증가
if( !isHPRegenStopped() )
{
int HPRegenPercentage = GetHPRegenPercentage();
if ( IsMonster() )
{
StructMonster* pMonster = static_cast< StructMonster * >( this );
if ( pMonster->GetMonsterType() >= MonsterBase::MONSTER_TYPE_HIGHEST_1_STAR )
HPRegenPercentage = GameRule::fMonsterRegenBoss;
else
HPRegenPercentage = GameRule::fMonsterRegen;
}
pt = 0;
pt += HPRegenPercentage * GetMaxHP() * 0.01 * f_100;
pt += GetHPRegenPoint() * f_100;
if( IsSitDown() )
{
// 휴식으로 인한 체력 회복 증폭 및 추가 증폭 패시브 적용
pt *= m_fHealRatioByRest ;
pt += m_nAdditionalHealByRest ;
}
pt *= m_fHPRegenMod;
pt = std::max<float>( pt, 1 );
pt = std::min<float>( pt, GetMaxHP() - GetHP() );
if( pt ) AddHP( pt );
// 버려진 소수 자리 누적(소수 둘째 자리 까지)
m_nHPDecPart += ( pt - (int)pt ) * 100;
if( m_nHPDecPart / 100 )
{
AddHP( m_nHPDecPart / 100 );
m_nHPDecPart %= 100;
}
}
// MP 증가
if( !isMPRegenStopped() )
{
pt = 0;
pt += GetMPRegenPercentage() * GetMaxMP() * 0.01 * f_100;
pt += GetMPRegenPoint() * f_100;
if( IsSitDown() )
{
// 휴식으로 인한 마나 회복 증폭 및 추가 증폭 패시브 적용
pt *= m_fMPHealRatioByRest ;
}
pt *= m_fMPRegenMod;
pt = std::max<float>( pt, 1 );
pt = std::min<float>( pt, GetMaxMP() - GetMP() );
if( pt ) AddMP( pt );
// 버려진 소수 자리 누적(소수 둘째 자리 까지)
m_nMPDecPart += ( pt - (int)pt ) * 100;
if( m_nMPDecPart / 100 )
{
AddMP( m_nMPDecPart / 100 );
m_nMPDecPart %= 100;
}
}
do
{
// 바뀐거 없음 패스
if( prev_hp == GetHP() && prev_mp == GetMP() )
break;
m_nRegenHP = GetHP() - prev_hp;
m_nRegenMP = GetMP() - prev_mp;
// 만땅 차거나, 3% 이상 변했으면 broadcast
// AziaMafia Fix HP-MP REGEN
//Or //if( GetMaxHP() == GetHP() || GetMaxMP() == GetMP() || m_nRegenHP * 100 / GetMaxHP() > 0 || m_nRegenMP * 100 / GetMaxMP() > 0 )
//Or //if (GetMaxHP() == GetHP() || GetMaxMP() == GetMP() || m_nRegenHP > 0 || m_nRegenMP > 0)
if (GetMaxHP() == GetHP() || GetMaxMP() == GetMP() || m_nRegenHP * 100 / GetMaxHP() > 0 || m_nRegenMP * 100 / GetMaxMP() > 0)
{
TS_SC_REGEN_HPMP msg;
msg.handle = GetHandle();
msg.hp_regen = m_nRegenHP;
msg.mp_regen = m_nRegenMP;
msg.hp = GetHP();
msg.mp = GetMP();
m_nRegenHP = 0;
m_nRegenMP = 0;
if( IsInWorld() )
{
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
if( IsSummon() )
{
StructPlayer * pPlayer = static_cast< StructSummon * >( this )->GetMaster();
if( pPlayer && pPlayer->IsInWorld() && pPlayer->IsLoginComplete() && !pPlayer->GetLogoutTime() )
{
PendMessage( pPlayer, &msg );
}
}
}
}
while( false );
}
m_nLastUpdateTime = t;
}
void StructCreature::RegenFullHPMP()
{
int nHP = GetHP();
int nMP = GetMP();
if( nHP == GetMaxHP() && nMP == GetMaxMP() )
return;
SetHP( GetMaxHP() );
SetMP( GetMaxMP() );
TS_SC_REGEN_HPMP msg;
msg.handle = GetHandle();
msg.hp_regen = GetMaxHP() - nHP;
msg.mp_regen = GetMaxMP() - nMP;
msg.hp = GetHP();
msg.mp = GetMP();
m_nRegenHP = 0;
m_nRegenMP = 0;
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
struct StateDamage
{
StateDamage( AR_HANDLE _caster, Elemental::Type _elemental_type, int _base_effect_id, StructState::StateCode _code, unsigned short _level, int _damage_hp, int _damage_mp, bool _final )
: caster( _caster )
, elemental_type( _elemental_type )
, base_effect_id( _base_effect_id )
, code( _code )
, level( _level )
, damage_hp( _damage_hp )
, damage_mp( _damage_mp )
, final( _final )
{}
AR_HANDLE caster;
Elemental::Type elemental_type;
int base_effect_id;
StructState::StateCode code;
unsigned short level;
int damage_hp;
int damage_mp;
bool final;
};
void StructCreature::procState( AR_TIME t )
{
STATE_ITERATOR it;
// EF_ADD_REGION_STATE 유형 지속효과 처리용
struct ADD_STATE_INFO
{
StructState::StateCode code;
int nLevel;
AR_TIME nEndTime;
int nHate;
StructCreature * pTarget;
} add_state_info;
// 인스턴스를 보관하는 벡터이므로 Deep copy가 필요한 구조체를 넣으면 안됨
std::vector< ADD_STATE_INFO > vAddStateInfo;
std::vector< StructState > vGoodStateRemover; // 이름 참 구리다...(두 개의 바이올린을 위한 협주곡은 야밤에 들어도 좋구나...)
std::vector< StructState::StateCode > vGoodStates;
bool bHasSequantialStateRemover = false; // 지속효과를 제거하는 지속효과가 두개 이상 있을 수 있고, 순서대로 지우는 녀석이 있을 경우 먼저 처리해줘야 하므로...
for( it = m_vStateList.begin(); it != m_vStateList.end(); ++it )
{
assert( (*it).GetLastProcessedTime() > 0 );
if( (*it).GetEffectType() == StructState::EF_REMOVE_GOOD_STATE )
{
AR_TIME nThisFireTime = (*it).GetLastProcessedTime() + (*it).GetFireInterval();
if( nThisFireTime >= t || nThisFireTime > (*it).GetEndTime() )
continue;
if( (*it).GetValue( 5 ) == 0 ) // sequantial remover
{
bHasSequantialStateRemover = true;
}
vGoodStateRemover.push_back( (*it) );
}
else if( !(*it).IsHarmful() && ( (*it).GetTimeType() & StateInfo::ERASE_ON_DEAD ) ) // must not contain remover
{
vGoodStates.push_back( (*it).GetCode() );
}
if( (*it).GetEffectType() == StructState::EF_ADD_REGION_STATE )
{
AR_TIME t = GetArTime();
if( (*it).GetLastProcessedTime() + (*it).GetValue(1) * 100 > t )
{
continue;
}
(*it).SetLastProcessedTime( t );
StructState::StateCode code = static_cast< StructState::StateCode >( (int)(*it).GetValue(0) );
int nLevel = (*it).GetLevel();
int nHitRate = (*it).GetValue(8) + nLevel * (*it).GetValue(9);
AR_TIME end_time = t + ( (*it).GetValue(2) + nLevel * (*it).GetValue(3) ) * 100.0;
AR_HANDLE hCaster = (*it).GetCaster();
if( !hCaster )
continue;
StructCreature *pCaster = static_cast< StructCreature * >( GameObject::raw_get( hCaster ) );
if( !pCaster )
continue;
if( (*it).GetValue(12) == StructState::USE_TARGET_REGION_TYPE )
{
float fEffectLength = (*it).GetValue(4) * GameRule::DEFAULT_UNIT_SIZE;
int nTargetType = (*it).GetValue(7);
std::vector< StructCreature * > vTargetList;
EnumSkillTargetsAndCalcDamage( GetCurrentPosition( t ), GetLayer(), GetCurrentPosition( t ), true, fEffectLength, -1, 0, 0, true, pCaster, (*it).GetValue(5), (*it).GetValue(6), vTargetList, false );
for( std::vector< StructCreature * >::iterator itTarget = vTargetList.begin() ; itTarget != vTargetList.end() ; ++itTarget )
{
StructCreature *pTarget = (*itTarget);
if( !pTarget || pTarget->IsDead() ) continue;
// 효과 대상 1 : 동료 & 중립
if( nTargetType == 1 && IsEnemy( pTarget, true ) )
continue;
// 효과 대상 2 : 동료
else if( nTargetType == 2 && !IsAlly( pTarget ) )
continue;
// 효과 대상 3 : 적
else if( nTargetType == 3 && !IsEnemy( pTarget, true ) )
continue;
// 성공률 체크
if( nHitRate < XRandom( 0, 99 ) )
continue;
add_state_info.code = code;
add_state_info.nLevel = nLevel;
add_state_info.nEndTime = end_time;
add_state_info.nHate = (*it).GetValue(10) + nLevel * (*it).GetValue(11);
add_state_info.pTarget = pTarget;
vAddStateInfo.push_back( add_state_info );
}
}
else if( (*it).GetValue(12) == StructState::USE_TARGET_OWNER )
{
// 성공률 체크
if( nHitRate < XRandom( 0, 99 ) )
continue;
add_state_info.code = code;
add_state_info.nLevel = nLevel;
add_state_info.nEndTime = end_time;
add_state_info.nHate = (*it).GetValue(10) + nLevel * (*it).GetValue(11);
add_state_info.pTarget = this;
vAddStateInfo.push_back( add_state_info );
}
}
}
struct lessGoodStateRemover
{
bool operator()(StructState & lh, StructState & rh )
{
if( lh.GetValue( 5 ) == 0 && rh.GetValue( 5 ) != 0 )
return true;
return false;
}
};
if( vGoodStateRemover.size() )
{
if( bHasSequantialStateRemover )
{
std::sort( vGoodStateRemover.begin(), vGoodStateRemover.end(), lessGoodStateRemover() );
std::reverse( vGoodStates.begin(), vGoodStates.end() ); // 뒤에서부터 뽑아야 하니 한번 뒤집어 놓는다.
}
for( std::vector< StructState >::iterator iter = vGoodStateRemover.begin(); iter != vGoodStateRemover.end(); ++iter )
{
if( !vGoodStates.size() || ( (*iter).GetValue( 0 ) + (*iter).GetValue( 1 ) * (*iter).GetLevel() <= XRandom( 0, 99 ) ) )
break;
if( (*iter).GetValue( 5 ) != 0 )
{
std::random_shuffle( vGoodStates.begin(), vGoodStates.end() );
}
int nRemoveCount = (*iter).GetValue( 3 ) + (*iter).GetValue( 4 ) * (*iter).GetLevel();
for( int i = 0; i < nRemoveCount; ++i )
{
if( !vGoodStates.size() )
break;
RemoveState( vGoodStates.back() );
vGoodStates.pop_back();
}
}
}
// EF_ADD_REGION_STATE 유형 결과 리스트 처리
if( !vAddStateInfo.empty() )
{
for( std::vector< ADD_STATE_INFO >::iterator it = vAddStateInfo.begin() ; it != vAddStateInfo.end() ; ++it )
{
if( (*it).pTarget->IsDead() || !(*it).pTarget->IsInWorld() )
continue;
(*it).pTarget->AddState( (*it).code, GetHandle(), (*it).nLevel, t, (*it).nEndTime );
if( (*it).pTarget->IsMonster() )
static_cast< StructMonster * >( (*it).pTarget )->AddHate( GetHandle(), (*it).nHate );
}
}
}
void StructCreature::procStateDamage( AR_TIME t )
{
// 직접 DealDamage 를 하면 DealDamage 내부에서 RemoveState() 를 하기도 하기 때문에 낭패.
// StateDamage는 여기서는 힐의 경우에는 힐량을 데미지로 보관하기도 함.
std::vector< StateDamage > vDamageList;
std::vector< StateDamage >::iterator vit;
bool bCreatureNeedNormalPriority = false;
bool bNeedNormalPriority = false;
// 뇌기 폭주 처리 필요 여부 조사
bool bNeedToProcLightningForceCongestion = false;
struct ByDeadCreature
{
ByDeadCreature( const StructCreature* target ) : target( target ) {}
const bool operator() ( const StructState& state )
{
if( target->IsPlayer() || target->IsSummon() )
{
StructCreature::iterator itCaster = StructCreature::get( state.GetCaster() );
if( !(*itCaster) &&
state.IsHarmful() &&
state.GetCode() != StructState::GAIA_MEMBER_SHIP &&
state.GetCode() != StructState::NEMESIS &&
state.GetCode() != StructState::NEMESIS_FOR_AUTO &&
state.GetCode() != StructState::CARELESSNESS &&
state.GetCode() != StructState::FALL_FROM_SUMMON )
{
return true;
}
}
return false;
}
const StructCreature* target;
} byDeadCreature( this );
RemoveStateIf( byDeadCreature );
STATE_ITERATOR it;
for( it = m_vStateList.begin(); it != m_vStateList.end(); ++it )
{
// 이번에 발동될 시각 미리 계산
AR_TIME nThisFireTime = (*it).GetLastProcessedTime() + (*it).GetFireInterval();
if( nThisFireTime >= t || nThisFireTime > (*it).GetEndTime() )
continue;
// 뇌기 폭주면 처리 필요 플래그 체크
if( (*it).GetCode() == StructState::LIGHTNING_FORCE_CONGESTION )
bNeedToProcLightningForceCongestion = true;
// 기본 효과가 0이면 데미지를 입히는 효과는 없음
int nBaseEffectID = (*it).GetBaseEffectID();
if( !nBaseEffectID )
continue;
bNeedNormalPriority = true;
int nDamageHP = 0;
int nDamageMP = 0;
Elemental::Type elem = static_cast< Elemental::Type >( (*it).GetElementalType() );
switch( nBaseEffectID )
{
case StructState::EF_PHYSICAL_STATE_DAMAGE:
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_STATE_DAMAGE:
case StructState::EF_MAGICAL_STATE_DAMAGE:
case StructState::EF_MAGICAL_IGNORE_RESIST_STATE_DAMAGE:
case StructState::EF_HEAL_HP_BY_MAGIC:
nDamageHP = (*it).GetBaseDamage();
nDamageHP = nDamageHP * ( (*it).GetAmplifyBase() + (*it).GetAmplifyPerSkillLevel() * (*it).GetLevel() ) + (*it).GetAddDamageBase() + (*it).GetAddDamagePerSkillLevel() * (*it).GetLevel();
break;
case StructState::EF_HEAL_MP_BY_MAGIC:
nDamageMP = (*it).GetBaseDamage();
nDamageMP = nDamageMP * ( (*it).GetAmplifyBase() + (*it).GetAmplifyPerSkillLevel() * (*it).GetLevel() ) + (*it).GetAddDamageBase() + (*it).GetAddDamagePerSkillLevel() * (*it).GetLevel();
break;
case StructState::EF_HEAL_HP_BY_ITEM:
nDamageHP = (*it).GetAddDamageBase() + (*it).GetAddDamagePerSkillLevel() * (*it).GetLevel();
break;
case StructState::EF_HEAL_MP_BY_ITEM:
nDamageMP = (*it).GetAddDamageBase() + (*it).GetAddDamagePerSkillLevel() * (*it).GetLevel();
break;
case StructState::EF_HEAL_HPMP_BY_ITEM:
nDamageHP = (*it).GetAddDamageBase() + (*it).GetAddDamagePerSkillLevel() * (*it).GetLevel();
nDamageMP = nDamageHP;
break;
case StructState::EF_HEAL_HPMP_PER_BY_ITEM:
nDamageHP = ( (*it).GetValue(0) + (*it).GetValue(1) * (*it).GetLevel() ) * GetMaxHP();
nDamageMP = ( (*it).GetValue(3) + (*it).GetValue(4) * (*it).GetLevel() ) * GetMaxMP();
break;
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_PER_STATE_DAMAGE:
nDamageHP = ( (*it).GetValue(0) + (*it).GetValue(1) * (*it).GetLevel() ) * GetMaxHP();
nDamageMP = ( (*it).GetValue(2) + (*it).GetValue(3) * (*it).GetLevel() ) * GetMaxMP();
break;
default:
assert( 0 );
continue;
}
if( !nDamageHP && !nDamageMP )
continue;
(*it).SetLastProcessedTime( nThisFireTime );
bool bFinal = nThisFireTime + (*it).GetFireInterval() > (*it).GetEndTime();
vDamageList.push_back( StateDamage( (*it).GetCaster(), elem, nBaseEffectID, (*it).GetCode(), (*it).GetLevel(), nDamageHP, nDamageMP, bFinal ) );
}
// 데미지/힐을 주는 상태이상이 있을 경우
if( !vDamageList.empty() )
{
// priority가 normal 미만인 경우 normal로 설정(단, 로그아웃을 위해 IDLE이 된 경우에는 제외)
if( GetFinalPriority() < UPDATE_PRIORITY_HIGH && GetFinalPriority() != UPDATE_PRIORITY_IDLE )
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_HIGH );
}
for( vit = vDamageList.begin(); vit != vDamageList.end(); ++vit )
{
StructCreature::iterator cit = StructCreature::get( (*vit).caster );
switch( (*vit).base_effect_id )
{
// HP 데미지 종류는 일단 들어가셈
case StructState::EF_PHYSICAL_STATE_DAMAGE:
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_STATE_DAMAGE:
case StructState::EF_MAGICAL_STATE_DAMAGE:
case StructState::EF_MAGICAL_IGNORE_RESIST_STATE_DAMAGE:
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_PER_STATE_DAMAGE:
{
// 데미지류는 캐스터 없으면 제거
if( !(*cit) )
{
RemoveState( (*vit).code );
continue;
}
int nFlag = 0;
StructCreature::_DAMAGE damage;
switch( (*vit).base_effect_id )
{
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_STATE_DAMAGE:
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_PER_STATE_DAMAGE:
nFlag |= IGNORE_DEFENCE;
case StructState::EF_PHYSICAL_STATE_DAMAGE:
nFlag |= IGNORE_AVOID | IGNORE_BLOCK;
damage = DealPhysicalStateDamage( (*cit), (*vit).damage_hp, (*vit).elemental_type, 0, 0, nFlag, &m_StateStatePenalty );
break;
case StructState::EF_MAGICAL_IGNORE_RESIST_STATE_DAMAGE:
nFlag |= IGNORE_DEFENCE;
case StructState::EF_MAGICAL_STATE_DAMAGE:
nFlag |= IGNORE_BLOCK;
damage = DealStateMagicalDamage( (*cit), (*vit).damage_hp, (*vit).elemental_type, 0, 0, nFlag, &m_StateStatePenalty );
break;
default:
assert(0);
}
std::vector< StructState >::iterator it;
int total_damage = 0;
for( it = m_vStateList.begin(); it != m_vStateList.end(); ++it )
{
if( (*it).GetCode() == (*vit).code )
{
(*it).AddTotalDamage( damage.nDamage );
total_damage = (*it).GetTotalDamage();
break;
}
}
TS_SC_STATE_RESULT msg;
msg.result_type = TS_SC_STATE_RESULT::STATE_DAMAGE_HP;
msg.target_value = GetHP();
msg.caster_handle = (*vit).caster;
msg.target_handle = GetHandle();
msg.code = (*vit).code;
msg.level = (*vit).level;
msg.value = damage.nDamage;
msg.final = (*vit).final;
msg.total_amount = total_damage;
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
break;
case StructState::EF_HEAL_HP_BY_MAGIC:
case StructState::EF_HEAL_MP_BY_MAGIC:
case StructState::EF_HEAL_HP_BY_ITEM:
case StructState::EF_HEAL_MP_BY_ITEM:
case StructState::EF_HEAL_HPMP_BY_ITEM:
case StructState::EF_HEAL_HPMP_PER_BY_ITEM:
{
int nHealHP = 0;
int nHealMP = 0;
if( IsDead() ) // 죽은 상태에서 힐이 들어갈 여지가 있음
{
break;
}
if( (*vit).base_effect_id == StructState::EF_HEAL_HP_BY_MAGIC )
{
nHealHP = Heal( (*vit).damage_hp );
}
else if( (*vit).base_effect_id == StructState::EF_HEAL_MP_BY_MAGIC )
{
nHealMP = MPHeal( (*vit).damage_mp );
}
else if( (*vit).base_effect_id == StructState::EF_HEAL_HP_BY_ITEM )
{
nHealHP = HealByItem( (*vit).damage_hp );
}
else if( (*vit).base_effect_id == StructState::EF_HEAL_MP_BY_ITEM )
{
nHealMP = MPHealByItem( (*vit).damage_mp );
}
else if( (*vit).base_effect_id == StructState::EF_HEAL_HPMP_BY_ITEM ||
(*vit).base_effect_id == StructState::EF_HEAL_HPMP_PER_BY_ITEM )
{
nHealHP = HealByItem( (*vit).damage_hp );
nHealMP = MPHealByItem( (*vit).damage_mp );
}
else
assert( 0 ); // -┏;? 뉘쇼?
TS_SC_STATE_RESULT msg;
int total_heal = 0;
std::vector< StructState >::iterator it;
for( it = m_vStateList.begin(); it != m_vStateList.end(); ++it )
{
if( (*it).GetCode() == (*vit).code )
{
if( nHealHP )
{
(*it).AddTotalDamage( nHealHP );
}
else if( nHealMP )
{
(*it).AddTotalDamage( nHealMP );
}
total_heal = (*it).GetTotalDamage();
break;
}
}
msg.caster_handle = (*vit).caster;
msg.target_handle = GetHandle();
msg.code = (*vit).code;
msg.level = (*vit).level;
msg.final = (*vit).final;
if( nHealHP )
{
msg.total_amount = total_heal;
msg.value = nHealHP;
msg.target_value = GetHP();
msg.result_type = TS_SC_STATE_RESULT::STATE_HEAL_HP;
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
if( nHealMP )
{
// HP, MP 모두 차는 건 MP 메시지에는 total을 0으로,
// MP만 차는 건 MP 메시지라도 total을 계산해서 보냄
if( msg.total_amount )
msg.total_amount = 0;
else
msg.total_amount = total_heal;
msg.value = nHealMP;
msg.target_value = GetMP();
msg.result_type = TS_SC_STATE_RESULT::STATE_HEAL_MP;
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
}
break;
}
}
// 뇌기 폭주 처리
if( bNeedToProcLightningForceCongestion )
{
do
{
StructState *pState = GetState( StructState::LIGHTNING_FORCE_CONGESTION );
if( !pState )
break;
StructCreature::iterator itCaster = StructCreature::get( pState->GetCaster() );
int damage_hp = pState->GetValue(6) + pState->GetLevel() * pState->GetValue(7);
int damage_mp = pState->GetValue(8) + pState->GetLevel() * pState->GetValue(9);
if( !(*itCaster) || GetHP() < damage_hp || GetMP() < damage_mp )
{
RemoveState( pState->GetCode() );
break;
}
AR_TIME nThisFireTime = pState->GetLastProcessedTime() + pState->GetFireInterval();
TS_SC_STATE_RESULT msg;
msg.caster_handle = pState->GetCaster();
msg.target_handle = GetHandle();
msg.final = nThisFireTime + pState->GetFireInterval() > pState->GetEndTime();
msg.code = StructState::LIGHTNING_FORCE_CONGESTION;
msg.level = pState->GetLevel();
pState->SetLastProcessedTime( nThisFireTime );
if( damage_hp )
{
StructCreature::_DAMAGE damage = DealPhysicalStateDamage( (*itCaster), damage_hp, static_cast< Elemental::Type >( pState->GetElementalType() ), 0, 0, 0, &m_StateStatePenalty );
if( damage.nDamage )
{
pState->AddTotalDamage( damage.nDamage );
msg.value = damage.nDamage;
msg.target_value = GetHP();
msg.result_type = TS_SC_STATE_RESULT::STATE_DAMAGE_HP;
msg.total_amount = pState->GetTotalDamage();
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
}
if( damage_mp )
{
AddMP( -damage_mp );
msg.value = damage_mp;
msg.target_value = GetMP();
msg.result_type = TS_SC_STATE_RESULT::STATE_DAMAGE_MP;
msg.total_amount = damage_mp;
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg );
}
} while( false );
}
}
void StructCreature::OnUpdate()
{
AR_TIME t = GetArTime();
assert( !IsInWorld() || ArcadiaServer::Instance().IsLocked( this ) );
if( isNeedToCalculateStat() )
{
CalculateStat();
m_StatusFlag.Off( STATUS_NEED_TO_CALCULATE_STAT );
}
// { energy process
int nEnergyCnt = GetEnergyCount();
if( nEnergyCnt )
{
int pos, cnt;
cnt = 0;
for( pos = m_nEnergyStartPos; m_arEnergy[pos] < t; pos = ++pos % GameRule::ENERGY_MAX )
{
if( !m_arEnergy[pos] || cnt == nEnergyCnt )
break;
++cnt;
}
if( cnt )
RemoveEnergy( cnt );
}
// } energy process
regenHPMP( t );
// 장비로 인해 지속적으로 거는 지속 효과 적용
// 동작 방식으로 보아 유사 오오라로 취급. 단, 오오라와 같이 주변에 대상에게도 지속효과를 건다면 AddState를 직접 호출해서는 안 됨
if( m_vPendStateListByItem.size() > 0 )
{
// m_vPendStateListByItem는 AddState 안에서 CalculateStat을 호출하면 초기화되었다가 다시 세팅되는데,
// 대개는 해당 벡터의 내용이 그대로 다시 채워지기 때문에 문제가 없지만 vector 순회 중에 포함된 데이터가 변경되는 건
// 위험한 상태이기 때문에 m_vPendStateListByItem를 복사한 vPendStateListByItem를 만들어서 순회함
// * AddState가 반드시 호출된다는 보장도 없고, 호출되더라도 CalculateStat이 호출되지 않는 경우도 있기 때문에
// m_vPendStateListByItem와 vPendStateListByItem를 swap 시키거나 m_vPendStateListByItem.clear()를 쓰면 안 됨(다음번 OnUpdate에서 바보됨)
std::vector< std::pair< AR_TIME, StructState > > vPendStateListByItem( m_vPendStateListByItem );
for( std::vector< std::pair< AR_TIME, StructState > >::iterator it = vPendStateListByItem.begin(); it != vPendStateListByItem.end(); ++it )
{
StructState & State = (*it).second;
if( (*it).first + SkillBase::TOGGLE_REFRESH_TIME <= t )
{
AddState( State.GetCode(), State.GetCaster(), State.GetLevel(), t, t + State.GetEndTime() - State.GetStartTime(), State.IsAura(), State.GetStateValue(), State.GetStateStringValue(), State.IsByEvent() );
(*it).first = t;
}
}
}
// 오오라 적용
if( m_vAura.size() > 0 )
{
std::vector< std::pair<StructSkill *, int> >::iterator it;
for( it = m_vAura.begin(); it != m_vAura.end(); )
{
if( (*it).first->GetAuraRefreshTime() + SkillBase::TOGGLE_REFRESH_TIME <= t )
{
if( !onProcAura( (*it).first, (*it).second ) )
{
TS_SC_AURA msg;
msg.caster = GetHandle();
msg.skill_id = (*it).first->GetSkillId();
msg.status = false;
if( IsPlayer() )
{
PendMessage( this, &msg );
}
else if( IsSummon() )
{
StructSummon * pSummon = static_cast< StructSummon * >( this );
if( pSummon->GetMaster() && pSummon->IsInWorld() )
PendMessage( pSummon->GetMaster(), &msg );
}
it = m_vAura.erase( it );
continue;
}
(*it).first->SetAuraRefreshTime( t );
}
++it;
}
}
// 이하는 월드에 있을 때만 처리되어야 하는 내용들(방송을 동반하므로 -_ -)
if( !IsInWorld() )
return;
if( m_nPendedClearStateFlag )
{
int nClearStateFlag = m_nPendedClearStateFlag;
m_nPendedClearStateFlag = 0;
removeStateWithFlag( nClearStateFlag );
}
if( m_vStateList.size() && ClearExpiredState( t ) )
{
CalculateStat();
}
if( IsFeared() && ( !IsMoving( GetArTime() ) || !isFearMoving() ) )
{
StructState * pState = GetState( StructState::FEAR );
if( pState )
{
int nMoveSpeedAdd = pState->GetValue( 1 );
ArPosition newPos;
float theta = ( XRandom() % 628 ) / 100.0f;
newPos.x = GetX() + sin( theta ) * 120;
newPos.y = GetY() + cos( theta ) * 120;
if( !isFearMoving() )
{
m_StatusFlag.On( STATUS_MOVING_BY_FEAR );
AR_HANDLE hCaster = pState->GetCaster();
StructCreature::iterator itCaster = StructCreature::get( hCaster );
StructCreature * pCaster = (*itCaster);
if( pCaster )
{
ArPosition caster_pos = pCaster->GetPos();
ArPosition my_pos = GetCurrentPosition( GetArTime() );
AR_UNIT distance = my_pos.GetDistance( caster_pos );
if( distance > 0 )
{
newPos.x = my_pos.x + ( caster_pos.x - my_pos.x ) * 120 / distance;
newPos.y = my_pos.y + ( caster_pos.y - my_pos.y ) * 120 / distance;
}
}
}
if( newPos.x > g_nMapWidth ) newPos.x = g_nMapWidth;
if( newPos.y > g_nMapWidth ) newPos.y = g_nMapWidth;
if( newPos.x < 0 ) newPos.x = 0;
if( newPos.y < 0 ) newPos.y = 0;
if( GameRule::bMonsterCollisionToLine == false || GameContent::CollisionToLine( GetX(), GetY(), newPos.x, newPos.y ) == false )
{
if( IsInWorld() && GetRealMoveSpeed() )
{
ArcadiaServer::Instance().SetMove( this, GetPos(), newPos, GetRealMoveSpeed() + nMoveSpeedAdd / 7, true, GetArTime() );
}
}
}
}
if( IsNeedToBroadcastStatusFlag() )
{
SetNeedToBroadcastStatusFlag( false );
BroadcastStatusMessage( this );
}
}
bool StructCreature::onProcAura( struct StructSkill *pSkill, int nRequestedSkillLevel )
{
pSkill->SetRequestedSkillLevel( nRequestedSkillLevel );
bool ret = pSkill->ProcAura();
pSkill->SetRequestedSkillLevel( 0 );
return ret;
}
StructState * StructCreature::GetState( StructState::StateCode code )
{
StructState * pRtn = NULL;
STATE_ITERATOR it;
for( it = m_vStateList.begin() ; it != m_vStateList.end() ; ++it )
{
if( (*it).GetCode() == code ) return &(*it);
}
return NULL;
}
const StructState * StructCreature::GetState( StructState::StateCode code ) const
{
StructState * pRtn = NULL;
STATE_CONST_ITERATOR it;
for( it = m_vStateList.begin() ; it != m_vStateList.end() ; ++it )
{
if( (*it).GetCode() == code ) return &(*it);
}
return NULL;
}
int StructCreature::AllocStateUID( int & nDBEnable )
{
int nUID = 0;
if( s_vDisabledStateList.size() )
{
THREAD_SYNCHRONIZE( s_csDisabledStateListLock );
if( s_vDisabledStateList.size() )
{
// 사용한 UID가 재사용되도록 유도하기 위해 위해 반환된 값을 먼저 사용한다.
// UID가 재사용될 수 있다는 것은 현재 DB의 Enable 값이 false라는 의미이다.
nUID = s_vDisabledStateList.back();
nDBEnable = StructState::UNUSED_IN_DB;
s_vDisabledStateList.pop_back();
return nUID;
}
}
nUID = InterlockedIncrement( &s_nMaxStateUID );
nDBEnable = StructState::NOT_IN_DB;
return nUID;
}
void StructCreature::DeallocStateUID( const int nUID )
{
#ifdef _DEBUG
if( nUID <= 0 )
{
assert( 0 );
return;
}
#endif
THREAD_SYNCHRONIZE( s_csDisabledStateListLock );
s_vDisabledStateList.push_back( nUID );
}
unsigned short StructCreature::AddState( StructState::StateCode code, AR_HANDLE caster, int level, AR_TIME start_time, AR_TIME end_time, bool bIsAura, int nStateValue, const char * szStateValue, const bool bByEvent, const int nAllocatedUID, int nDBEnable )
{
SetNeedToUpdateState( true );
// 사망시 사라지지 않는 효과는 죽은채로도 걸 수 있도록 변경
// 사망 상태에서 접속시 더블 플러스, 무한 스테 세이버 효과를 받지 못 하는 문제가 있었음. 2007-05-18
//if( IsDead() )
// return RESULT_NOT_ACTABLE;
const StateInfo * pStateInfo = GameContent::GetStateInfo( code );
if( !pStateInfo )
{
GameObject *pObj = GameObject::raw_get( caster );
if( pObj && pObj->IsPlayer() && static_cast< StructPlayer * >(pObj)->pConnection )
{
static_cast< StructPlayer * >(pObj)->pConnection->Close();
}
return RESULT_NOT_EXIST;
}
// 지속 효과 적용 대상 플래그 체크
// ( 우선 현재 지속효과는 플레이어, 몬스터, 소환수에게만 가능하다. )
if( !( pStateInfo->uf_avatar && IsPlayer() ) &&
!( pStateInfo->uf_monster && IsMonster() ) &&
!( pStateInfo->uf_summon && IsSummon() ) )
return RESULT_ACCESS_DENIED;
STATE_ITERATOR it;
// 플레이어일 경우 체크되는 조건 검사(이벤트에 의한 강제 지속효과의 경우 체크 안 함)
if( IsPlayer() && !bByEvent )
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
// 데스매치에서 제한된 지속효과는 걸 수 없음
if( pPlayer->IsInDeathmatch() && pStateInfo->state_time_type & StateInfo::NOT_ACTABLE_ON_DEATHMATCH )
{
return RESULT_NOT_ACTABLE_IN_DEATHMATCH;
}
if( pPlayer->IsInStartedCompete( true ) && pStateInfo->state_time_type & StateInfo::NOT_ACTABLE_IN_COMPETE )
{
return RESULT_NOT_IN_COMPETE;
}
}
//AziaMafia KeepBuff & fix
//if( IsDead() && ( pStateInfo->state_time_type & StateInfo::ERASE_ON_DEAD || pStateInfo->state_time_type & StateInfo::ERASE_ON_RESURRECT ) && !bByEvent )
if (IsDead() && (pStateInfo->state_time_type & StateInfo::ERASE_ON_DEAD) && !bByEvent)
{
return RESULT_ACCESS_DENIED;
}
// 일어설 때 사라지는 효과는 일어선 대상에게 걸 수 없음(이벤트에 의한 강제 지속효과의 경우 체크 안 함)
if( !IsSitDown() && ( pStateInfo->state_time_type & StateInfo::ERASE_ON_STAND_UP ) && !bByEvent )
{
return RESULT_NOT_ACTABLE_ON_STAND_UP;
}
if( IsMonster() )
{
StructMonster * pMonster = static_cast< StructMonster * >( this );
// state_time_type is also used to determine whether it is an un-usable persistent effect on bosses. Might need renaming
if( ( pStateInfo->state_time_type & StateInfo::NOT_ACTABLE_TO_BOSS ) && pMonster->IsBossMonster() && !GameRule::nBossEffect )
{
return RESULT_LIMIT_TARGET;
}
// 던전 코어, 오토 트랩은 공포에 걸리지 않아야 함(돌아댕기면 이상하니까...)
if( code == StructState::FEAR && ( pMonster->IsDungeonConnector() || pMonster->IsAutoTrap() ) )
{
return RESULT_LIMIT_TARGET;
}
if( pMonster->IsRunaway() )
{
return RESULT_LIMIT_TARGET;
}
}
// 지속효과가 행동 불능을 야기시키는 경우 스킬 캔슬, 탑승 크리처에서 떨어짐
if( code == StructState::SLEEP || code == StructState::NIGHTMARE || code == StructState::SEAL ||
code == StructState::SHINE_WALL || code == StructState::FEAR || code == StructState::STUN ||
// AziaMafia Transformation bug pStateInfo->effect_type == StructState::EF_TRANSFORMATION ||
( pStateInfo->effect_type == StructState::EF_MEZZ && ( pStateInfo->fValue[0] || pStateInfo->fValue[1] || pStateInfo->fValue[2] || pStateInfo->fValue[3] ) ) )
{
if( IsUsingSkill() )
CancelSkill();
// 자신이 크리처 탑승 상태의 플레이어 혹은 플레이어를 태운 소환수였으면 낙상 처리
StructPlayer * pThisPlayer = NULL;
if( IsPlayer() )
pThisPlayer = static_cast< StructPlayer * >( this );
else if( IsSummon() )
{
pThisPlayer = static_cast< StructSummon * >( this )->GetMaster();
// 탑승 상태이긴 했는데 얻어맞은 소환수가 아닌 다른 놈을 탑승 중이었으면 낙상 처리 안 함
if( pThisPlayer && pThisPlayer->GetRideObject() != this )
pThisPlayer = NULL;
}
if( pThisPlayer )
{
// 행동 불능 지속효과를 건 주체가 낙상을 유발한 것으로 처리(데미지가 들어가야 하기 때문)
StructCreature::iterator itCaster = StructCreature::get( caster );
StructCreature * pCaster = (*itCaster);
// 행동 불능 지속효과를 건 주체가 없으면 낙상 처리 안 함(몬스터나 자신이어도 괜찮음. 아예 없지만 않으면 됨)
if( pCaster )
{
if( pThisPlayer )
{
if( pThisPlayer->IsRiding() || pThisPlayer->HasRidingState() )
{
pThisPlayer->UnMount( StructPlayer::UNMOUNT_FALL, pCaster );
// 낙상 발생 후 사망 가능. 사망시 사라지는 효과는 시체한테는 걸 수 없음(이벤트에 의한 강제 지속효과의 경우 체크 안 함)
if( IsDead() && ( pStateInfo->state_time_type & StateInfo::ERASE_ON_DEAD || pStateInfo->state_time_type & StateInfo::ERASE_ON_RESURRECT ) && !bByEvent )
{
return RESULT_ACCESS_DENIED;
}
}
else if( pThisPlayer->IsSitDown() )
{
pThisPlayer->StandUp();
BroadcastStatusMessage( pThisPlayer );
AR_TIME t = GetArTime();
pThisPlayer->AddState( StructState::CARELESSNESS, 0, 1, t, t + 300 );
}
}
}
}
}
// 지속효과가 크리처 탑승 효과일 경우 은신 해제됨
if( pStateInfo->effect_type == StructState::EF_RIDING && IsHiding() )
{
RemoveState( StructState::HIDE, GameRule::MAX_STATE_LEVEL );
RemoveState( StructState::TRACE_OF_FUGITIVE, GameRule::MAX_STATE_LEVEL );
}
if( code == StructState::FEAR )
{
m_StatusFlag.Off( STATUS_MOVING_BY_FEAR );
}
StructCreature::iterator itCaster;
itCaster = StructCreature::get( caster );
int base_damage = 0;
if( (*itCaster) )
{
switch( pStateInfo->base_effect_id )
{
case StructState::EF_PHYSICAL_STATE_DAMAGE:
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_STATE_DAMAGE:
case StructState::EF_PHYSICAL_IGNORE_DEFENCE_PER_STATE_DAMAGE:
base_damage = (*itCaster)->GetAttackPointRight( static_cast< Elemental::Type >(pStateInfo->elemental_type), true, true );
break;
case StructState::EF_MAGICAL_STATE_DAMAGE:
case StructState::EF_MAGICAL_IGNORE_RESIST_STATE_DAMAGE:
case StructState::EF_HEAL_HP_BY_MAGIC:
case StructState::EF_HEAL_MP_BY_MAGIC:
base_damage = (*itCaster)->GetMagicPoint( static_cast< Elemental::Type >(pStateInfo->elemental_type), false, true );
break;
}
}
bool bNotErasable = pStateInfo->state_time_type & StateInfo::NOT_ERASABLE;
std::vector< StructState::StateCode > vDeleteStateCode;
bool bAlreadyExist = false;
for( it = m_vStateList.begin() ; it != m_vStateList.end() ; ++it )
{
bool bIsDuplicatedGroup = false;
if( code == (*it).GetCode() )
{
bAlreadyExist = true;
bIsDuplicatedGroup = true;
// 중첩 가능 횟수(reiteration_count)가 2회 이상일 경우, 중첩이 가능한 지속효과로 판단한다.
// 중첩 횟수는 사실상 레벨과 동일한 개념이며, 기존의 지속효과에 새로운 지속효과의 레벨을 더해 중첩을 표현한다.
// 따라서 한 번에 더해지는 중첩 횟수는 개념상 1회이며, 이에 따라 중첩 가능한 지속효과들은 1레벨 단위로만 걸릴 수 있다.
if( pStateInfo->reiteration_count >= 2 )
level = std::min( static_cast< int >( pStateInfo->reiteration_count ), (*it).GetLevel() + level );
}
else
{
for( int i = 0 ; i < 3 ; ++i )
{
if( (*it).IsDuplicatedGroup( pStateInfo->duplicate_group[i] ) )
{
bIsDuplicatedGroup = true;
break;
}
}
}
if( bIsDuplicatedGroup )
{
// 이벤트에 의한 강제 지속효과의 경우 무조건 기존 버프 삭제(동일한 지속효과가 있었다고 해도 업데이트 아니고 강제로 삭제)
// 이벤트에 의한 강제 지속효과가 아니고 기존에 중복되어 있던 지속효과가 이벤트에 의한 강제 지속효과일 경우는 아무것도 못 함
if( bByEvent )
{
bAlreadyExist = false;
vDeleteStateCode.push_back( (*it).GetCode() );
continue;
}
else if( (*it).IsByEvent() )
{
return RESULT_ALREADY_EXIST;
}
bool bNotErasableCur = (*it).GetTimeType() & StateInfo::NOT_ERASABLE;
// 기존, 새 지속효과 모두 소멸 안 됨 플래그 없거나 있음
if( bNotErasable == bNotErasableCur )
{
// 그룹 같고 둘다 플래그 걸려 있으면 같은 지속효과여야 함.
// 만일 같지 않은 지속효과가 추가되어야 할 경우가 생기면
// 플래그 있을 때와 없을 때 분리해야 함
if( ( (*it).GetLevel() > level ) || ( (*it).GetLevel() == level && (*it).GetEndTime() > end_time ) )
{
return RESULT_ALREADY_EXIST;
}
// 같은 코드이면 기존의 지속효과를 업데이트 시키도록 해야 함(토글형의 갱신 문제)
if( code == (*it).GetCode() )
{
continue;
}
vDeleteStateCode.push_back( (*it).GetCode() );
}
// 새 지속효과에만 소멸 안 됨 플래그 있음
else if( bNotErasable )
{
vDeleteStateCode.push_back( (*it).GetCode() );
continue;
}
// 기존 지속효과에만 소멸 안 됨 플래그 있음
else
{
return RESULT_ALREADY_EXIST;
}
}
}
for( std::vector< StructState::StateCode >::iterator dit = vDeleteStateCode.begin() ; dit != vDeleteStateCode.end() ; ++dit )
{
RemoveState( *dit );
}
if( bAlreadyExist )
{
for( it = m_vStateList.begin() ; it != m_vStateList.end() ; ++it )
{
if( code == (*it).GetCode() )
{
// 변화가 있다면 스탯 재계산 및 방송
if( (*it).AddState( caster, level, start_time, end_time, base_damage, bIsAura ) )
{
CalculateStat();
onUpdateState( (*it), false );
onAfterAddState( (*it) );
}
else if( !bIsAura )
{
onUpdateState( (*it), false );
}
break;
}
}
return RESULT_SUCCESS;
}
int nUID = -1;
// EventState 지속효과나 로그아웃 시 삭제되는 지속효과와 같이 저장이 필요없는 지속효과는 UID를 할당하지 않는다.
// 여기서 UID가 부여되지 않는다면 UID의 존재 여부로 지속효과의 저장 필요 여부를 판단할 수 있어 효율적이다.
if( ( !IsPlayer() && !IsSummon() ) ||
bByEvent ||
pStateInfo->state_time_type & StateInfo::ERASE_ON_LOGOUT )
{
nUID = -1;
}
else
{
// UID 할당이 필요한 경우에도 미리 할당된 UID를 사용한다면 새로 발급할 필요가 없다.
// 실제 저장되지 않고 만료되는 지속효과가 많기에 대부분의 UID 발급이 무의미하지만, Save에 의해 이미 부여된 지속효과의 UID가 변경되는 경우는 발생해서는 안 된다.
// 이는 UID가 클라이언트로 방송될 때 handle로 활용되기 때문인데 handle이라고 해서 반드시 고유값일 필요는 없고 변경만 발생하지 않으면 문제가 없다.
// 예를 들어 위와 같이 DB에 저장이 필요없는 경우 UID를 -1로 일괄 부여하더라도 UID가 변경되지 않기 때문에 클라이언트에서 문제 없이 처리할 수 있다.
if( nAllocatedUID )
nUID = nAllocatedUID;
else
nUID = StructCreature::AllocStateUID( nDBEnable );
}
m_vStateList.push_back( StructState( code, nUID, caster, level, start_time, end_time, base_damage, bIsAura, nStateValue, szStateValue, bByEvent, nDBEnable ) );
CalculateStat();
onUpdateState( m_vStateList.back(), false );
if( IsMonster() && !IsMovable() )
{
if( m_Attribute.fAttackRange < GameRule::MAX_ATTACK_RANGE * GameRule::ATTACK_RANGE_UNIT )
{
m_Attribute.fAttackRange = GameRule::MAX_ATTACK_RANGE * GameRule::ATTACK_RANGE_UNIT;
}
}
onAfterAddState( m_vStateList.back() );
return RESULT_SUCCESS;
}
void StructCreature::PendAddState( StructState::StateCode code, AR_HANDLE caster, int level, AR_TIME start_time, AR_TIME end_time, bool bIsAura, int nStateValue, const char * szStateValue, const bool bByEvent )
{
m_vPendStateList.push_back( StructState( code, -1, caster, level, start_time, end_time, -1, bIsAura, nStateValue, szStateValue, bByEvent, StructState::NOT_IN_DB ) );
}
void StructCreature::PendAddStateByItem( StructState::StateCode code, AR_HANDLE caster, int level, AR_TIME start_time, AR_TIME end_time, bool bIsAura, int nStateValue, const char * szStateValue, const bool bByEvent )
{
m_vPendStateListByItem.push_back( std::make_pair( 0, StructState( code, -1, caster, level, start_time, end_time, -1, bIsAura, nStateValue, szStateValue, bByEvent, StructState::NOT_IN_DB ) ) );
}
void StructCreature::onUpdateState( StructState & state, bool bIsExpire )
{
BroadcastStateMessage( this, &state, bIsExpire );
}
void StructCreature::RemoveState( StructState::StateCode code, int state_level, const bool bByEvent )
{
bool bFlag = false;
STATE_ITERATOR it;
for( it = m_vStateList.begin(); it != m_vStateList.end(); )
{
if( (*it).GetCode() == code && (*it).GetLevel() <= state_level && ( !(*it).IsByEvent() || bByEvent ) )
{
bFlag = true;
break;
}
++it;
}
if( !bFlag ) return;
if( (*it).GetLevel() <= state_level )
{
StructState state = (*it);
onUpdateState( (*it), true );
m_vStateList.erase( it );
CalculateStat();
onAfterRemoveState( state );
}
}
template< typename Predicate >
void StructCreature::RemoveStateIf( Predicate& predicate, std::vector< StructState >* result, const bool bByDead )
{
std::vector< StructState > removedStates;
std::vector< StructState >::iterator it;
std::vector< StructState >::iterator trail;
// 필요한 경우 삭제된 state들을 반환하기 위해 우선 스왑
if( result != NULL )
{
removedStates.swap( *result );
}
// predicate이 true인 값은 removedStates로 이동시키고 false인 값은 m_vStateList에 남겨둠
for ( it = m_vStateList.begin(), trail = it; it != m_vStateList.end(); ++it )
{
if ( predicate( *it ) )
{
removedStates.push_back( *it );
}
else
{
if( trail != it )
{
*trail = *it;
}
++trail;
}
}
m_vStateList.resize( trail - m_vStateList.begin() );
// 삭제 대상인 녀석들을 대상으로 사후 처리
for ( it = removedStates.begin(); it != removedStates.end(); ++it )
{
onUpdateState( (*it), true );
onAfterRemoveState( *it, bByDead );
}
// 삭제된 지속효과가 하나라도 있다면 CalculateStat을 호출한다.
if( removedStates.size() )
{
CalculateStat();
}
// 삭제된 state들을 추가한 vector를 반환해주자
if( result != NULL )
{
removedStates.swap( *result );
}
}
void StructCreature::RemoveStatesOnDamaged()
{
RemoveStateIf( StateFlagChecker( StateInfo::ERASE_ON_DAMAGED ) );
}
void StructCreature::DecreaseState( StructState::StateCode code, const unsigned char level, const bool bByEvent )
{
struct CheckStateCode
{
CheckStateCode( StructState::StateCode code, bool byEvent ) : code( code ), byEvent( byEvent ) {}
const bool operator () ( const StructState & state ) const
{
if( state.GetCode() != code || state.IsByEvent() ^ byEvent )
return false;
return true;
}
StructState::StateCode code;
bool byEvent;
} checkStateCode( code, bByEvent );
DecreaseStateIf( checkStateCode, level );
}
template< typename Predicate >
void StructCreature::DecreaseStateIf( Predicate& predicate, const unsigned char level, std::vector< StructState >* result )
{
bool bDecreased = false;
std::vector< StructState > removedStates;
std::vector< StructState >::iterator it;
std::vector< StructState >::iterator trail;
// 필요한 경우 삭제된 state들을 반환하기 위해 우선 스왑
if( result != NULL )
{
removedStates.swap( *result );
}
// predicate이 true인 값은 지속 레벨을 감소시키고 만약 레벨이 0이 된다면 removedStates로 이동시킨다.
for ( it = m_vStateList.begin(), trail = it; it != m_vStateList.end(); ++it )
{
if ( predicate( *it ) )
{
bDecreased = true;
(*it).SetLevel( (*it).GetLevel() > level ? (*it).GetLevel() - level : 0 );
// 감소된 지속효과 레벨이 0이면 삭제
if( (*it).GetLevel() == 0 )
{
removedStates.push_back( *it );
continue;
}
// 삭제되지 않은 지속효과라면 변경된 레벨을 방송
onUpdateState( *it );
}
if( trail != it )
{
*trail = *it;
}
++trail;
}
m_vStateList.resize( trail - m_vStateList.begin() );
// 삭제 대상인 녀석들을 대상으로 사후 처리
for ( it = removedStates.begin(); it != removedStates.end(); ++it )
{
onUpdateState( (*it), true );
onAfterRemoveState( *it );
}
// 감소되었거나 삭제된 지속효과가 하나라도 있다면 CalculateStat을 호출한다.
if( bDecreased || removedStates.size() )
{
CalculateStat();
}
// 삭제된 state들을 추가한 vector를 반환해주자
if( result != NULL )
{
removedStates.swap( *result );
}
}
bool StructCreature::ClearExpiredState( AR_TIME t )
{
struct CheckAndProcessExpiredState {
CheckAndProcessExpiredState( StructCreature& creature, AR_TIME time )
: creature( creature ), isExpiredStateExist( false ), time( time ) {}
const bool operator () ( StructState& state )
{
bool erase = false;
// 이벤트에 의해 부여된 지속효과가 아닐 경우에만 만료 처리 체크 함
if( !state.IsByEvent() )
{
if( state.ClearExpiredState( time ) )
{
isExpiredStateExist = true;
erase = true;
}
}
return erase;
}
StructCreature& creature;
bool isExpiredStateExist;
AR_TIME time;
} checkAndProcessExpiredState( *this, t );
std::vector< StructState > removedStates;
RemoveStateIf( checkAndProcessExpiredState, &removedStates );
bool returnValue = checkAndProcessExpiredState.isExpiredStateExist;
for ( std::vector< StructState >::iterator it = removedStates.begin(); it != removedStates.end(); ++it )
{
// { 추가 작업
if( (*it).GetCode() == StructState::ADD_ENERGY )
{
AddEnergy();
}
else if( (*it).GetEffectType() == StructState::EF_PROVOKE && IsMonster() )
{
StructMonster *pMonster = static_cast< StructMonster * >( this );
pMonster->SetNeedToFindEnemy();
}
// 지속효과 소멸 시 로그 남겨야 하는 경우 로그 남김
if( it->IsLogRequiredOnExpiration() )
{
StructPlayer * pPlayer = NULL;
StructSummon * pSummon = NULL;
if( IsPlayer() )
{
pPlayer = static_cast< StructPlayer * >( this );
}
else if( IsSummon() )
{
pSummon = static_cast< StructSummon * >( this );
pPlayer = pSummon->GetMaster();
}
if( pPlayer && pPlayer->IsLogin() && !pPlayer->IsDeleteRequested() )
{
if( pSummon )
{
LOG::Log11N4S( LM_STATE_EXPIRATION, pPlayer->GetAccountID(), pPlayer->GetSID(), it->GetCode(), it->GetLevel(),
pSummon->GetSID(), pSummon->GetSummonCode(), pSummon->GetLevel(), 0, pSummon->GetX(), pSummon->GetY(), pSummon->GetParentCard()->GetItemUID(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, pSummon->GetName(), LOG::STR_NTS, "", 0 );
}
else
{
LOG::Log11N4S( LM_STATE_EXPIRATION, pPlayer->GetAccountID(), pPlayer->GetSID(), it->GetCode(), it->GetLevel(),
0, 0, 0, 0, 0, pPlayer->GetX(), pPlayer->GetY(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 );
}
}
}
}
return returnValue;
}
void StructCreature::RestoreRemovedStateByDead()
{
// 사망시 없어진 지속효과를 시작/종료 시각 조정하여 다시 부여
AR_TIME t = GetArTime();
for( STATE_ITERATOR it = m_vStateListRemovedByDeath.begin() ; it != m_vStateListRemovedByDeath.end() ; )
{
const StructState & state = (*it);
// 이미 걸려있는 지속효과면 패스
if( GetState( state.GetCode() ) || ( it->GetTimeType() & StateInfo::ERASE_ON_RESURRECT ) )
{
++it;
continue;
}
if( state.GetEndTime() == (AR_TIME)-1 )
{
AddState( state.GetCode(), state.GetCaster(), state.GetLevel(), state.GetStartTime(), -1, state.IsAura(), state.GetStateValue(), state.GetStateStringValue(), state.IsByEvent(), state.GetUID(), state.GetDBEnable() );
}
else
{
AR_TIME time_difference = GetArTime() - GetDeadTime();
AddState( state.GetCode(), state.GetCaster(), state.GetLevel(), state.GetStartTime() + time_difference, state.GetEndTime() + time_difference, state.IsAura(), state.GetStateValue(), state.GetStateStringValue(), state.IsByEvent(), state.GetUID(), state.GetDBEnable() );
}
it = m_vStateListRemovedByDeath.erase( it );
}
//AziaMafia KeepBuff & fix
ClearRemovedStateByDead();
}
void StructCreature::ClearRemovedStateByDead()
{
for( std::vector< StructState >::iterator it = m_vStateListRemovedByDeath.begin(); it != m_vStateListRemovedByDeath.end(); ++it )
{
(*it).Expire( this );
}
m_vStateListRemovedByDeath.clear();
}
void StructCreature::RemoveAllAura()
{
if( m_vAura.size() )
{
std::vector< std::pair<StructSkill *, int> >::iterator it;
for( it = m_vAura.begin(); it != m_vAura.end(); ++it )
{
TS_SC_AURA msg;
msg.caster = GetHandle();
msg.skill_id = (*it).first->GetSkillId();
msg.status = false;
if( IsPlayer() )
{
PendMessage( this, &msg );
}
else if( IsSummon() )
{
PendMessage( static_cast< StructSummon * >( this )->GetMaster(), &msg );
}
}
m_vAura.clear();
}
}
void StructCreature::RemoveAllStateByDeadOrLogout()
{
// 소환수 역소환에 의해 호출되는 함수로 실제로는 죽는 것과 관련이 없는 함수이다. 따라서 bByDead를 false로 넘긴다.
// 또한 소환수의 경우는 부활 시 지속효과 복구 기능이 구현되어 있지 않으며 사용하지도 않는다.
RemoveStateIf( StateFlagChecker( StateInfo::ERASE_ON_DEAD | StateInfo::ERASE_ON_LOGOUT ) );
}
void StructCreature::RemoveAllStateByQuittingHuntaholic()
{
RemoveStateIf( StateFlagChecker( StateInfo::ERASE_ON_QUIT_HUNTAHOLIC ) );
}
void StructCreature::RemoveStateByEnteringDeathmatch()
{
struct DeathMatchFlagChecker
{
const bool operator() ( const StructState& state )
{
return ( !( state.GetTimeType() & StateInfo::NOT_ERASABLE_ON_ENTER_DEATHMATCH ) ) && !state.IsAura() && !state.IsByEvent();
}
} deathMatchFlagChecker;
RemoveStateIf( deathMatchFlagChecker );
}
void StructCreature::RemoveAllStateByQuittingDeathmatch()
{
RemoveStateIf( StateFlagChecker( StateInfo::ERASE_ON_QUIT_DEATHMATCH ) );
}
struct BattleArenaStateChecker
{
static const int REMOVE_FLAG = ( StateInfo::ERASE_ON_DEAD | StateInfo::ERASE_ON_QUIT_BATTLE_ARENA );
const bool operator()( const StructState & state )
{
return ( state.GetTimeType() & REMOVE_FLAG ) != 0;
}
};
void StructCreature::RemoveAllStateByQuittingBattleArena()
{
BattleArenaStateChecker basc;
RemoveStateIf( basc );
}
bool StructCreature::AddAimer( AR_HANDLE aimer )
{
if( std::find( m_vAimerList.begin(), m_vAimerList.end(), aimer ) != m_vAimerList.end() )
return false;
m_vAimerList.push_back( aimer );
return true;
}
bool StructCreature::RemoveAimer( AR_HANDLE aimer )
{
std::vector< AR_HANDLE >::iterator it;
it = std::find( m_vAimerList.begin(), m_vAimerList.end(), aimer );
if( it == m_vAimerList.end() )
return false;
vector_fast_erase( &m_vAimerList, it );
return true;
}
void StructCreature::ReleaseAimerList()
{
std::vector< AR_HANDLE >::iterator it;
for( it = m_vAimerList.begin(); it != m_vAimerList.end(); it++ )
{
StructPlayer::iterator itPlayer = StructPlayer::get( *it );
StructPlayer *pObj = *itPlayer;
if( pObj )
{
pObj->SetTarget( 0 );
}
}
m_vAimerList.clear();
}
bool StructCreature::IsUsingSkill()
{
return !!m_pCastSkill;
}
bool StructCreature::CancelSkill()
{
if( !IsUsingSkill() )
return false;
if( !m_pCastSkill->Cancel() )
return false;
m_pCastSkill = NULL;
// _oprint("[%s] 스킬 캔슬\n", GetName() );
return true;
}
bool StructCreature::OnCompleteSkill()
{
if( !IsUsingSkill() ) return false;
m_pCastSkill = NULL;
// _oprint("[%s] 스킬 시전 완료\n", GetName() );
return true;
}
const unsigned short StructCreature::CastSkill( int nSkillId, int nSkillLevel, AR_HANDLE target_handle, const ArPosition & pos, unsigned char layer, AR_HANDLE hCastItem )
{
ArPosition target_pos = pos;
// AziaMafia Fix bug if skill is being used
if( IsUsingSkill() ) return RESULT_NOT_ACTABLE;
if( IsPlayer() && static_cast< StructPlayer * >( this )->IsUsingStorage() ) return RESULT_NOT_ACTABLE;
StructSkill* pSkill = getSkill( nSkillId );
if( !pSkill ) return RESULT_NO_SKILL;
GameObject::iterator it = StructCreature::get( target_handle );
StructCreature *pSkillTarget = NULL;
if( *it )
{
if( (*it)->IsCreature() ) pSkillTarget = static_cast< StructCreature * >( *it );
}
// Check if skill is usable on the target
if( pSkill->GetSkillBase()->GetSkillTargetType() == SkillBase::TARGET_MASTER )
{
if( !IsSummon() )
{
assert( 0 );
return RESULT_NOT_ACTABLE;
}
StructSummon* pSummon = static_cast< StructSummon* >( this );
if( pSummon->GetMaster() != pSkillTarget )
{
return RESULT_NOT_ACTABLE;
}
}
else if( pSkill->GetSkillBase()->GetSkillTargetType() == SkillBase::TARGET_SELF_WITH_MASTER )
{
if( !IsSummon() )
{
assert( 0 );
return RESULT_NOT_ACTABLE;
}
StructSummon* pSummon = static_cast< StructSummon* >( this );
if( pSkillTarget != this && pSkillTarget != pSummon->GetMaster() )
{
return RESULT_NOT_ACTABLE;
}
}
// 자신에게 사용할 수 없는 스킬일 경우 체크
if( pSkill->GetSkillBase()->GetSkillTargetType() == SkillBase::TARGET_TARGET_EXCEPT_CASTER &&
pSkillTarget == this )
{
return RESULT_NOT_ACTABLE;
}
if( pSkillTarget )
{
if( !pSkillTarget->IsInWorld() )
return RESULT_NOT_ACTABLE;
// ??-_-??
// if( ( pSkill->GetSkillBase()->IsHarmful() && pSkill->IsNeedTarget() && !IsEnemy( pSkillTarget ) ) || ( !pSkill->GetSkillBase()->IsHarmful() && IsEnemy( pSkillTarget ) ) )
// {
// return false;
// }
AR_TIME t = GetArTime();
ArPosition enemyPosition = pSkillTarget->GetCurrentPosition( t );
ArPosition myPosition = GetCurrentPosition( t );
AR_UNIT enemy_distance = myPosition.GetDistance( enemyPosition ) - GetUnitSize()/2 - pSkillTarget->GetUnitSize()/2;;
float range_mod = 1.2f;
if( pSkillTarget->IsMoving() )
{
range_mod = 1.5f;
}
if( pSkill->GetCastRange() == -1 )
{
if( enemy_distance > GetRealAttackRange() * range_mod )
{
return RESULT_TOO_FAR;
}
}
else if( enemy_distance > pSkill->GetCastRange() * GameRule::DEFAULT_UNIT_SIZE * range_mod )
{
return RESULT_TOO_FAR;
}
if( pSkill->GetSkillBase()->IsValidToCorpse() )
{
if( !pSkillTarget->IsDead() )
// _cprint( "스킬 시전 실패 : 살아있는 대상에게 사용 불가\n" );
return RESULT_NOT_ACTABLE;
// 죽었더라도 내구도가 다한 소환수는 살릴 수 없음
else if( pSkillTarget->IsSummon() )
{
StructItem *pCardItem = static_cast< StructSummon * >( pSkillTarget )->GetParentCard();
if( pCardItem && pCardItem->GetMaxEtherealDurability() && !pCardItem->GetCurrentEtherealDurability() )
return RESULT_NOT_ACTABLE;
}
}
if( pSkillTarget == this || ( pSkillTarget->IsSummon() && static_cast< StructSummon * >( pSkillTarget )->GetMaster() == this ) )
{
if( !pSkill->IsUsable( SkillBase::USE_SELF ) )
return RESULT_NOT_ACTABLE;
}
else if( IsAlly( pSkillTarget ) )
{
if( !pSkill->IsUsable( SkillBase::USE_ALLY ) )
return RESULT_NOT_ACTABLE;
}
else if( IsEnemy( pSkillTarget ) )
{
if( !pSkill->IsUsable( SkillBase::USE_ENEMY ) )
return RESULT_NOT_ACTABLE;
}
else
{
if( !pSkill->IsUsable( SkillBase::USE_NEUTRAL ) )
return RESULT_NOT_ACTABLE;
}
if( pSkillTarget->IsPlayer() && !pSkill->IsUseableOnAvatar() )
{
// _cprint( "스킬 시전 실패 : 아바타에게 사용 불가\n" );
return RESULT_NOT_ACTABLE;
}
if( pSkillTarget->IsMonster() && !pSkill->IsUseableOnMonster() )
{
// _cprint( "스킬 시전 실패 : 몬스터에게 사용 불가\n" );
return RESULT_NOT_ACTABLE;
}
if( pSkillTarget->IsSummon() && !pSkill->IsUseableOnSummon() )
{
// _cprint( "스킬 시전 실패 : 소환수에게 사용 불가\n" );
return RESULT_NOT_ACTABLE;
}
target_pos = pSkillTarget->GetCurrentPosition( GetArTime() );
}
else
{
if( nSkillId == StructSkill::SKILL_RETURN_FEATHER && IsPlayer() )
{
if( static_cast< StructPlayer * >( this )->IsInSiegeOrRaidDungeon() )
{
// _cprintf" 스킬 시전 실패 : 귀환의 깃털은 레이드/시즈 던전 안에서는 사용 불가\n")
return RESULT_NOT_ACTABLE;
}
// 숨겨진 던전 안에서는 귀환의 깃털 스킬을 사용할 수 없도록 되어 있었으나, 사용 가능한 크루 아이템의 추가로 여기서의 체크 제거
// * 체크를 제거해도 실제로 layer를 별도의 manager에 의해 관리하거나 하는 영역이 아니기 때문에 시스템상 별다른 문제는 없을 걸로 예상됨
if( static_cast< StructPlayer * >( this )->IsInInstanceDungeon() )
{
// _cprintf" 스킬 시전 실패 : 귀환의 깃털은 인스턴스 던전 안에서는 사용 불가\n")
return RESULT_NOT_ACTABLE_IN_INSTANCE_DUNGEON;
}
}
}
if( !m_vInterruptedSkill.empty() )
{
for( std::vector< int >::iterator it = m_vInterruptedSkill.begin() ; it != m_vInterruptedSkill.end() ; ++it )
{
if( (*it) == nSkillId )
{
return RESULT_NOT_ACTABLE;
}
}
}
if( !m_vAllowedSkill.empty() )
{
bool bActable = true;
for( std::vector< std::set< int > >::iterator it = m_vAllowedSkill.begin(); bActable && it != m_vAllowedSkill.end(); it++ )
{
if( (*it).find( pSkill->GetSkillId() ) == (*it).end() )
{
return RESULT_NOT_ACTABLE;
}
}
}
SetDirection( target_pos );
m_pCastSkill = pSkill;
// _oprint("[%s] 스킬 시전 m_pSkill 설정\n", GetName() );
int nErrorCode = m_pCastSkill->Cast( nSkillLevel, target_handle, target_pos, layer, hCastItem );
if( nErrorCode != RESULT_SUCCESS )
{
// AziaMafia log GS
//_cprint("Cast nErrorCode != RESULT_SUCCESS : %s / nErrorCode = %d / skillid = %d / hCastItem = %d \n", GetName(), nErrorCode , nSkillId , hCastItem);
//FILELOG("Cast nErrorCode != RESULT_SUCCESS : %s / nErrorCode = %d / skillid = %d / hCastItem = %d ", GetName(), nErrorCode, nSkillId, hCastItem);
// _oprint("[%s] 스킬 시전 실패\n", GetName() );
m_pCastSkill = NULL;
return nErrorCode;
}
// _oprint("[%s] 스킬 시전 성공\n", GetName() );
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
// C급 보스 이상의 몬스터가 스킬 시전에 성공한 경우 로그 남김
if( IsMonster() && static_cast< StructMonster * >( this )->GetMonsterType() >= MonsterBase::MONSTER_TYPE_HIGHEST_1_STAR )
{
StructMonster * pMonster = static_cast< StructMonster * >( this );
if( pSkillTarget )
{
StructPlayer * pPlayer = NULL;
if( pSkillTarget->IsPlayer() )
{
pPlayer = static_cast< StructPlayer * >( pSkillTarget );
}
else if( pSkillTarget->IsSummon() )
{
pPlayer = static_cast< StructSummon * >( pSkillTarget )->GetMaster();
}
if( pPlayer )
{
LOG::Log11N4S( LM_MONSTER_SKILL_CAST, pPlayer->GetAccountID(), pPlayer->GetSID(), pSkillTarget->GetX(), pSkillTarget->GetY(), pSkillTarget->GetLayer(),
pMonster->GetMonsterId(), pMonster->GetX(), pMonster->GetY(), pMonster->GetLayer(),
nSkillId, nSkillLevel,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS,
pSkillTarget->GetName(), LOG::STR_NTS, pMonster->GetName(), LOG::STR_NTS );
}
else
{
LOG::Log11N4S( LM_MONSTER_SKILL_CAST, 0, 0, pSkillTarget->GetX(), pSkillTarget->GetY(), pSkillTarget->GetLayer(),
pMonster->GetMonsterId(), pMonster->GetX(), pMonster->GetY(), pMonster->GetLayer(),
nSkillId, nSkillLevel,
"", 0, "", 0,
pSkillTarget->GetName(), LOG::STR_NTS, pMonster->GetName(), LOG::STR_NTS );
}
}
else
{
LOG::Log11N4S( LM_MONSTER_SKILL_CAST, 0, 0, 0, 0, 0,
pMonster->GetMonsterId(), pMonster->GetX(), pMonster->GetY(), pMonster->GetLayer(),
nSkillId, nSkillLevel,
"", 0, "", 0,
"", 0, pMonster->GetName(), LOG::STR_NTS );
}
}
return RESULT_SUCCESS;
}
// 두 벡터의 각도차이
static short getDegreeDiff( const ArMoveVector & mv1, const ArMoveVector & mv2 )
{
short d1 = mv1.GetDegree();
short d2 = mv2.GetDegree();
short diff = ( d1 > d2 ? d1 - d2 : d2 - d1 );
if( diff < 0 ) diff = 0 - diff;
return diff;
}
bool StructCreature::IsBackOf( const StructCreature & target )
{
short diff = getDegreeDiff( target.GetMv(), GetMv() );
return ( diff < 180 + 90 / 2 && 180 - 90 / 2 );
}
bool StructCreature::IsSideOf( const StructCreature & target )
{
short diff = getDegreeDiff( target.GetMv(), GetMv() );
return ( diff < 90 + 90 / 2 && 90 - 90 / 2 ) ||
( diff < 270 + 90 / 2 && 270 - 90 / 2 );
}
StructSkill * StructCreature::GetSkill( int nSkillId ) const
{
std::vector< StructSkill * >::const_iterator it;
for( it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
if( (*it)->GetSkillId() == nSkillId ) return *it;
}
return NULL;
}
const StructSkill * StructCreature::GetSkillByEffectType( const int nEffectTypeID ) const
{
std::vector< StructSkill * >::const_iterator it;
for( it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
if( (*it)->GetSkillBase()->GetEffectType() == nEffectTypeID ) return *it;
}
return NULL;
}
StructSkill * StructCreature::getSkill( int nSkillId ) const
{
std::vector< StructSkill * >::const_iterator it;
for( it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
if( (*it)->GetSkillId() == nSkillId ) return *it;
}
return NULL;
}
StructSkill * StructCreature::SetSkill( int skill_uid, int skill_id, int skill_level, AR_TIME remain_cool_time )
{
int nPrevLevel;
if( !GameContent::GetSkillBase( skill_id ) ) return NULL;
StructSkill *pSkill = getSkill( skill_id );
if( !pSkill )
{
// 메모리 풀 사용하도록 변경
//pSkill = new StructSkill( this, skill_uid, skill_id );
pSkill = StructSkill::AllocSkill( this, skill_uid, skill_id );
m_vAllSkillList.push_back( pSkill );
if( pSkill->IsPassiveSkill() ) m_vPassiveSkillList.push_back( pSkill );
else m_vActiveSkillList.push_back( pSkill );
}
nPrevLevel = pSkill->GetBaseSkillLevel();
pSkill->SetBaseSkillLevel( skill_level );
// remain_cool_time이 가끔 음수인 경우가 있는데 여기서 DBCoolTime을 0으로 보정하지 않고 그대로 음수로 저장을 하면,
// 로그아웃 시점의 remain_cool_time이 0이라고 하더라도 저장이 되며 0 또는 양수의 정상 범위 값으로 변한다.
if( remain_cool_time ) pSkill->SetRemainCoolTime( remain_cool_time );
pSkill->SetDBCoolTime( remain_cool_time );
CheckAndSetEnhanceSkill( pSkill );
return pSkill;
}
void StructCreature::CheckAndSetEnhanceSkill( StructSkill* skill )
{
// 강화 스킬은 강화의 대상이 되지 않도록 의도하였다. 강화 자체가 재귀적으로 이루어지는 것이 아니라 최대 한 단계만 참조하기 ‹š문.
// 다시 말해 강화 스킬1을 강화하는 스킬2가 있다고 해도 스킬1에 의해 강화되는 스킬은 스킬2에 아무런 영향을 받지 않는다.
if ( skill->GetSkillBase()->GetSkillEffectType() == SkillBase::EF_ENHANCE_SKILL )
{
for( std::vector< StructSkill* >::iterator it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
if ( skill->GetSkillBase()->GetEnhancedSkillId() == (*it)->GetSkillId() )
{
assert( (*it)->GetSkillBase()->GetSkillEffectType() != SkillBase::EF_ENHANCE_SKILL );
(*it)->SetEnhanceSkill( skill );
}
}
}
else
{
for( std::vector< StructSkill* >::iterator it = m_vPassiveSkillList.begin(); it != m_vPassiveSkillList.end(); ++it )
{
if( (*it)->GetSkillBase()->GetSkillEffectType() == SkillBase::EF_ENHANCE_SKILL &&
(*it)->GetSkillBase()->GetEnhancedSkillId() == skill->GetSkillId() )
{
skill->SetEnhanceSkill( *it );
}
}
}
}
void StructCreature::RegisterSkill( int skill_id, int skill_level, AR_TIME remain_cool_time, int skill_tree_id )
{
int nJobId = GetJobId();
// JP, TP 소비
__int64 nNeedJP = GameContent::GetNeedJpForSkillLevelUp( skill_id, skill_level, skill_tree_id );
int nNeedTP = GameContent::GetNeedTpForSkillLevelUp( skill_id, skill_level );
if( nNeedJP > GetJobPoint() || nNeedTP > GetTalentPoint() )
{
return;
}
if( nNeedJP > 0 ) m_nJobPoint -= nNeedJP;
if( nNeedTP > 0 )
{
// TP 사용은 로그로 찍어준다.
StructPlayer * pClient = NULL;
if( IsPlayer() )
{
pClient = static_cast< StructPlayer * >( this );
}
else if( IsSummon() )
{
pClient = static_cast< StructPlayer * >( static_cast< StructSummon * >( this )->GetMaster() );
}
if( pClient )
{
LOG::Log11N4S( LM_CHARACTER_USE_TP, pClient->GetAccountID(),
pClient->GetSID(), nJobId,
GetJobLevel(), skill_id,
skill_level, nNeedJP,
nNeedTP, GetTalentPoint() - nNeedTP,
0, 0,
pClient->GetAccountName(), LOG::STR_NTS,
pClient->GetName(), LOG::STR_NTS,
"", 0,
"ByLearnSkill", LOG::STR_NTS );
}
SetTalentPoint( GetTalentPoint() - nNeedTP );
}
if( m_nJobPoint < 0 ) m_nJobPoint = 0;
// 여기서 렙업할 일은 없고, 렙업할 일이 없다는 것은 calculatestat호출 될 일이 없다는 것이므로, 여기선 lock을 걸지 않는다.
// JP 방송은 onExpChange에서 하고 TP 방송은 SetTalentPoint에서 이미 했다.
onExpChange();
//
int nSkillUID = 0;
int nPrevLevel = GetBaseSkillLevel( skill_id );
bool bIsNewSkill = false;
if( !GetSkill( skill_id ) )
{
bIsNewSkill = true;
nSkillUID = InterlockedIncrement( &s_nMaxSkillUID );
}
StructSkill *pSkill = SetSkill( nSkillUID, skill_id, skill_level, remain_cool_time );
if( pSkill )
{
if( IsPlayer() )
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
LOG::Log11N4S( LM_CHARACTER_LEARN_SKILL, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, skill_id, skill_level, nNeedJP, GetJobPoint(), nSkillUID, nNeedTP, GetTalentPoint(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 );
}
else if( IsSummon() )
{
StructSummon * pSummon = static_cast< StructSummon * >( this );
__int64 nCardUID = 0;
if( pSummon->GetParentCard() )
{
nCardUID = pSummon->GetParentCard()->GetItemUID();
}
LOG::Log11N4S( LM_SUMMON_LEARN_SKILL, pSummon->GetMaster()->GetAccountID(), pSummon->GetMaster()->GetSID(), pSummon->GetSID(), skill_id, skill_level, nNeedJP, m_nJobPoint, nSkillUID, nNeedTP, GetTalentPoint(), nCardUID, pSummon->GetMaster()->GetAccountName(), LOG::STR_NTS, pSummon->GetMaster()->GetName(), LOG::STR_NTS, pSummon->GetName(), LOG::STR_NTS, "", 0 );
}
if( pSkill->IsPassiveSkill() )
{
CalculateStat();
}
onRegisterSkill( pSkill->GetSkillUID(), skill_id, skill_level, bIsNewSkill );
}
}
void StructCreature::EnumState( StructCreature::STATE_FUNCTOR & _fo )
{
std::vector< StructState >::iterator it;
for( it = m_vStateList.begin(); it != m_vStateList.end(); ++it )
{
_fo.onState( &(*it) );
}
}
void StructCreature::EnumSkill( StructCreature::SKILL_FUNCTOR & _fo ) const
{
AR_TIME t = GetArTime();
std::vector< StructSkill * >::const_iterator it;
for( it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
if( (*it)->GetSkillUID() > 0 )
_fo.onSkill( (*it)->GetSkillUID(), (*it)->GetSkillId(), (*it)->GetBaseSkillLevel(), (*it)->GetCurrentSkillLevel(), (*it)->GetSkillCoolTime(), (*it)->GetRemainCoolTime( t ) );
}
}
StructSkill* StructCreature::GetCurrentPassiveSkill( int skill_id ) const
{
std::vector< StructSkill * >::const_iterator it;
for( it = m_vPassiveSkillList.begin(); it != m_vPassiveSkillList.end(); ++it )
{
if( (*it)->GetSkillId() == skill_id ) return (*it);
}
return NULL;
}
int StructCreature::GetCurrentPassiveSkillLevel( int skill_id ) const
{
StructSkill* pSkill = GetCurrentPassiveSkill( skill_id );
if( pSkill ) return pSkill->GetCurrentSkillLevel();
return 0;
}
// skillLevelBase에서 skillLevelTarget까지 필요한 JP, TP 반환
const LearningCost GetSumOfSkillLearningCost( const StructCreature* creature, const int skillId, const int skillLevelBase, const int skillLevelTarget )
{
int jobId[GameRule::MAX_JOB_DEPTH];
int jobDepth = creature->GetJobDepth();
for( int i = 0; i < jobDepth; ++i )
{
jobId[i] = creature->GetPrevJobId(i);
}
jobId[ jobDepth ] = creature->GetJobId();
LearningCost cost;
for( int skillLevel = skillLevelTarget; skillLevel > skillLevelBase; skillLevel-- )
{
int i = 0;
__int64 JPPerLv = -1;
while( JPPerLv == -1 && i <= jobDepth )
{
if( creature->IsSummon() )
{
SummonBase *pBase = GameContent::GetSummonInfo( jobId[i++] );
if( !pBase )
continue;
for( int j = 0 ; j < GameRule::MAX_SUMMON_SKILL_TREE && JPPerLv == -1 ; ++j )
{
if( !pBase->skill_tree_id[j] )
continue;
JPPerLv = GameContent::GetNeedJpForSkillLevelUp( skillId, skillLevel, pBase->skill_tree_id[j] );
}
}
else if( creature->IsPlayer() )
{
const JobInfo *pInfo = GameContent::GetJobInfo( jobId[i++] );
if( !pInfo || !pInfo->skill_tree_id )
continue;
JPPerLv = GameContent::GetNeedJpForSkillLevelUp( skillId, skillLevel, pInfo->skill_tree_id );
}
else
break;
}
if( JPPerLv != -1 )
{
cost.jp += JPPerLv;
}
int TPPerLv = GameContent::GetNeedTpForSkillLevelUp( skillId, skillLevel );
if( TPPerLv != -1 )
{
cost.tp += TPPerLv;
}
}
return cost;
}
const int GetAllowedMaxSkillLevel( const StructCreature* creature, const StructSkill* skill, const int remainingMaxJobDepth )
{
int allowedSkillLevel = 0;
int maxJobDepth = std::min( creature->GetJobDepth(), remainingMaxJobDepth );
for( int checkedJobDepth = 0; checkedJobDepth <= maxJobDepth; checkedJobDepth++ )
{
int jobId = ( checkedJobDepth == creature->GetJobDepth() ) ? creature->GetJobId() : creature->GetPrevJobId( checkedJobDepth );
if( creature->IsPlayer() )
{
const JobInfo *pInfo = GameContent::GetJobInfo( jobId );
if( pInfo )
{
allowedSkillLevel = std::max( allowedSkillLevel, GameContent::GetAllowedMaxSkillLevel( pInfo->skill_tree, skill->GetSkillId() ) );
}
}
else if( creature->IsSummon() )
{
const SummonBase *pBase = GameContent::GetSummonInfo( jobId );
if( pBase )
{
for( int i = 0 ; i < GameRule::MAX_SUMMON_SKILL_TREE ; ++i )
{
if( !pBase->skill_tree_id[ i ] )
continue;
allowedSkillLevel = std::max( allowedSkillLevel, GameContent::GetAllowedMaxSkillLevel( pBase->skill_tree[ i ], skill->GetSkillId() ) );
}
}
}
}
return allowedSkillLevel;
}
template< typename SkillPredicate >
void StructCreature::turnOffAuraOnSkillReset( const SkillPredicate& predicate )
{
typedef std::pair<StructSkill *, int> Aura;
// TODO : lambda 쓰면 한 줄로 처리 가능하니 차후 바꾸자
struct RemainingAuraCriteria
{
RemainingAuraCriteria( const SkillPredicate& pred ) : predicate( pred ) {}
const SkillPredicate& predicate;
const bool operator() ( const Aura& aura ) const { return !predicate( aura.first ); }
};
std::vector< Aura >::iterator removedItems =
std::stable_partition( m_vAura.begin(), m_vAura.end(), RemainingAuraCriteria( predicate ) );
for( std::vector< Aura >::iterator it = removedItems; it != m_vAura.end(); ++it )
{
SendAuraMsessage( this, it->first->GetSkillId() );
}
m_vAura.erase( removedItems, m_vAura.end() );
}
template< typename SkillPredicate >
LearningCost StructCreature::removeSkillFromList( const SkillPredicate& predicate )
{
// TODO : lambda 쓰면 한 줄로 처리 가능하니 차후 바꾸자
struct RemainingSkillCriteria
{
RemainingSkillCriteria( const SkillPredicate& pred ) : predicate( pred ) {}
const SkillPredicate& predicate;
const bool operator() ( const StructSkill* skill ) const {
return ( skill->GetSkillUID() < 0 ) || !predicate( skill );
}
};
std::vector< StructSkill* >::iterator removedSkills =
std::stable_partition( m_vAllSkillList.begin(), m_vAllSkillList.end(), RemainingSkillCriteria( predicate ) );
LearningCost returnedSkillCost;
for( std::vector< StructSkill* >::iterator it = removedSkills; it != m_vAllSkillList.end(); ++it )
{
StructSkill* removedSkill = *it;
if( removedSkill->IsPassiveSkill() )
{
m_vPassiveSkillList.erase( std::find( m_vPassiveSkillList.begin(), m_vPassiveSkillList.end(), removedSkill ) );
}
else
{
m_vActiveSkillList.erase( std::find( m_vActiveSkillList.begin(), m_vActiveSkillList.end(), removedSkill ) );
}
returnedSkillCost += GetSumOfSkillLearningCost( this, removedSkill->GetSkillId(), 0, removedSkill->GetBaseSkillLevel() );
onRemoveSkill( removedSkill );
removedSkill->FreeSkill();
}
m_vAllSkillList.erase( removedSkills, m_vAllSkillList.end() );
return returnedSkillCost;
}
// 아래 함수에서는 클라쪽으로 스킬 메시지를 보내는 처리를 따로 하지는 않는다.
// 이는 메시지 처리를 가급적 한꺼번에 처리하도록 하기 위함이다
template< typename SkillPredicate >
LearningCost StructCreature::RemoveSkill( const SkillPredicate& predicate )
{
LearningCost returnSkillCost;
if( IsUsingSkill() )
{
CancelSkill();
}
turnOffAuraOnSkillReset( predicate );
returnSkillCost = removeSkillFromList( predicate );
return returnSkillCost;
}
// TODO: RegisterSkill과 중복되는 코드가 잔뜩 있으나 일단은 구현부터 해놓고 나중에 리팩토링
const bool StructCreature::UpdateSkillLevel( StructSkill* skill, int targetLevel )
{
if( targetLevel < 0 )
{
return false;
}
// JP, TP 소비
LearningCost cost;
int prevLevel = skill->GetBaseSkillLevel();
if( prevLevel > targetLevel )
{
cost = GetSumOfSkillLearningCost( this, skill->GetSkillId(), targetLevel, skill->GetBaseSkillLevel() );
cost.jp = -cost.jp;
cost.tp = -cost.tp;
}
else
{
cost = GetSumOfSkillLearningCost( this, skill->GetSkillId(), skill->GetBaseSkillLevel(), targetLevel );
if( cost.jp > GetJobPoint() || cost.tp > GetTalentPoint() )
{
return false;
}
}
if( cost.jp != 0 ) SetJP( std::max<__int64>( GetJobPoint() - cost.jp, 0 ) ) ;
if( cost.tp != 0 )
{
if( IsPlayer() )
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
if( cost.tp > 0 )
{
LOG::Log11N4S( LM_CHARACTER_GAIN_TP, pPlayer->GetAccountID(),
pPlayer->GetSID(), skill->GetSkillId(),
pPlayer->GetPrevJobId( pPlayer->GetJobDepth() - 1 ), pPlayer->GetJobId(),
pPlayer->GetPrevJobLevel( pPlayer->GetJobDepth() - 1 ), pPlayer->GetJobLevel(),
pPlayer->GetTalentPoint(), pPlayer->GetTalentPoint() - cost.tp,
skill->GetBaseSkillLevel(), targetLevel,
pPlayer->GetAccountName(), LOG::STR_NTS,
pPlayer->GetName(), LOG::STR_NTS,
"", 0,
"ByUpdateSkill", LOG::STR_NTS );
}
else
{
LOG::Log11N4S( LM_CHARACTER_USE_TP, pPlayer->GetAccountID(),
pPlayer->GetSID(), pPlayer->GetJobId(),
GetJobLevel(), skill->GetSkillId(),
skill->GetBaseSkillLevel(), cost.jp,
pPlayer->GetTalentPoint(), pPlayer->GetTalentPoint() - cost.tp,
targetLevel, 0,
pPlayer->GetAccountName(), LOG::STR_NTS,
pPlayer->GetName(), LOG::STR_NTS,
"", 0,
"ByUpdateSkill", LOG::STR_NTS );
}
}
SetTalentPoint( GetTalentPoint() - cost.tp );
}
skill->SetBaseSkillLevel( targetLevel );
onRegisterSkill( skill->GetSkillUID(), skill->GetSkillId(), targetLevel, false );
return true;
}
// 현재 스킬 트리에서 허용된 레벨 외의 범위에 있는 스킬들의 레벨을 재조정, 스킬 리셋에서 사용한다
void StructCreature::AdjustOverflowedSkillLevel( const int remainingMaxJobDepth )
{
for( std::vector< StructSkill* >::iterator it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it )
{
StructSkill* skill = *it;
if( skill->GetSkillUID() < 0 )
{ // 시스템 스킬인 경우는 패스
continue;
}
int allowedSkillLevel = GetAllowedMaxSkillLevel( this, skill, remainingMaxJobDepth );
if( allowedSkillLevel < skill->GetBaseSkillLevel() )
{
UpdateSkillLevel( skill, allowedSkillLevel );
}
}
}
bool StructCreature::ResetSkill( const _SKILL_RESET_METHOD eMethod, const int jobDepth, int doResetRandomSkill )
{
if( jobDepth < 0 || jobDepth > GetJobDepth() )
{
return false;
}
__int64 nPrevJP = GetJobPoint();
int nPrevTP = GetTalentPoint();
if( doResetRandomSkill == 2 )
{
bool hasRandomSkill = false;
bool hasResetRandomSkill = false;
for (std::vector< StructSkill* >::iterator it = m_vAllSkillList.begin(); it != m_vAllSkillList.end(); ++it)
{
if( (*it)->IsRandomSkill() )
{
if ( (*it)->GetCurrentSkillLevel() == 0 )
hasResetRandomSkill = true;
else
hasRandomSkill = true;
}
}
if (!hasRandomSkill || !hasResetRandomSkill)
{
return false;
}
}
// TODO: 차후 람다로 교체
struct NotOwnedByRemainingSkillTree
{
NotOwnedByRemainingSkillTree( const StructCreature* creature, const int remainingMaxJobDepth, int resetRandomSkill )
: creature( creature ), remainingMaxJobDepth( remainingMaxJobDepth ), resetRandomSkill( resetRandomSkill ) {}
const bool operator() ( const StructSkill* skill ) const
{
// 만약 크리쳐 랜덤 스킬일 경우 삭제하지 않는다. (밑에서 skill_level만 0으로 만들어준다.)
// 단, 강제 초기화 일 경우 스킬 다 날려줌. (resetRandomSkill == true)
if( !resetRandomSkill && creature->IsSummon() && skill->IsRandomSkill() )
{
return false;
}
if( resetRandomSkill == 2 && creature->IsSummon() && ( !skill->IsRandomSkill() || skill->GetBaseSkillLevel() > 0 ) )
{
return false;
}
// 남아 있어야하는 스킬 트리에서 허용 레벨이 0 -> 삭제 대상
return GetAllowedMaxSkillLevel( creature, skill, remainingMaxJobDepth ) == 0;
}
const StructCreature* creature;
int remainingMaxJobDepth;
int resetRandomSkill;
};
// 스킬 초기화 후 포인트 반납
LearningCost returnedSkillCost = RemoveSkill( NotOwnedByRemainingSkillTree( this, jobDepth-1, doResetRandomSkill ) );
// AziaMafia CP Change
SetJP( GetJobPoint() + returnedSkillCost.jp );
//if (IsSummon()) SetJP(GetJobPoint() + returnedSkillCost.jp);
if( returnedSkillCost.tp )
{
if( IsPlayer() )
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
LOG::Log11N4S( LM_CHARACTER_GAIN_TP, pPlayer->GetAccountID(),
pPlayer->GetSID(), 0,
pPlayer->GetPrevJobId( pPlayer->GetJobDepth() - 1 ), pPlayer->GetJobId(),
pPlayer->GetPrevJobLevel( pPlayer->GetJobDepth() - 1 ), pPlayer->GetJobLevel(),
pPlayer->GetTalentPoint(), pPlayer->GetTalentPoint() + returnedSkillCost.tp,
0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS,
pPlayer->GetName(), LOG::STR_NTS,
"", 0,
"ByResetSkill", LOG::STR_NTS );
}
SetTalentPoint( GetTalentPoint() + returnedSkillCost.tp );
}
// 크리쳐의 경우 남아 있는 랜덤 스킬들을 0으로 업데이트 시켜줌. 여기서 0으로 업데이트 된 애들 스킬 포인트 반납
if( IsSummon() )
{
// 남아있는 랜덤 스킬들의 경우 0으로 업데이트 시켜준다.
for( std::vector< StructSkill* >::iterator it = m_vAllSkillList.begin() ; it != m_vAllSkillList.end() ; ++it )
{
if( doResetRandomSkill != 2 )
UpdateSkillLevel((*it), 0);
}
}
if( jobDepth > 0 )
{
// 현재 스킬 트리로는 찍을 수 없는 일부 스킬이 남아 있을 수 있다.
// 이 경우는 스킬 리스트에서 남아 있는 녀석들을 대상으로 스킬 레벨을 업데이트하자
AdjustOverflowedSkillLevel( jobDepth-1 );
}
CalculateStat();
onResetSkill( jobDepth );
if( IsPlayer() )
{
const StructPlayer * pPlayer = static_cast< const StructPlayer * >( this );
LOG::Log11N4S( LM_SKILL_RESET, pPlayer->GetAccountID(), pPlayer->GetPlayerUID(), 0, eMethod, nPrevJP, nPrevTP, GetJobPoint(), GetTalentPoint(), 0, 0, 0,
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 );
}
else if( IsSummon() )
{
const StructSummon * pSummon = static_cast< const StructSummon * >( this );
const StructPlayer * pPlayer = pSummon->GetMaster();
// pPlayer == NULL 일 가능성이 작지만 있으므로 pPlayer 참조 부분에 모두 NULL 체크해서 대체 값이 존재하도록 처리해야 함
LOG::Log11N4S( LM_SKILL_RESET,
( pPlayer ) ? pPlayer->GetAccountID() : 0,
( pPlayer ) ? pPlayer->GetPlayerUID() : 0,
pSummon->GetSummonSID(), eMethod, nPrevJP, nPrevTP, GetJobPoint(), GetTalentPoint(), 0, 0, 0,
( pPlayer ) ? pPlayer->GetAccountName() : "", LOG::STR_NTS,
( pPlayer ) ? pPlayer->GetName() : "", LOG::STR_NTS,
pSummon->GetName(), LOG::STR_NTS, "", 0 );
}
return true;
}
__int64 StructCreature::GetAllSkillJP() const
{
int nJobId[GameRule::MAX_JOB_DEPTH];
int nJobDepth = GetJobDepth();
int i;
for( i = 0; i < nJobDepth; ++i )
{
nJobId[i] = GetPrevJobId(i);
}
nJobId[i] = GetJobId();
__int64 nJP = 0;
// 지금 가지고 있는 스킬들 배우는데 필요한 잡포 계산
for( std::vector< StructSkill * >::const_iterator it = m_vAllSkillList.begin() ; it != m_vAllSkillList.end() ; it++ )
{
StructSkill *pSkill = (*it);
if( (*it)->GetSkillUID() == StructSkill::SKILL_UID_ITEM_SKILL ||
(*it)->GetSkillUID() == StructSkill::SKILL_UID_PROP_SKILL ||
(*it)->GetSkillUID() == StructSkill::SKILL_UID_SUMMON_SKILL ||
(*it)->GetSkillUID() == StructSkill::SKILL_UID_MONSTER_SKILL ||
(*it)->GetSkillUID() == StructSkill::SKILL_UID_PET_SKILL ||
(*it)->GetSkillUID() == StructSkill::SKILL_UID_BOOSTER_SKILL )
continue;
for( int nSkillLevel = pSkill->GetBaseSkillLevel() ; nSkillLevel > 0 ; nSkillLevel-- )
{
int jp = -1;
i = 0;
while( jp == -1 && i <= nJobDepth )
{
if( IsSummon() )
{
SummonBase *pBase = GameContent::GetSummonInfo( nJobId[i++] );
if( !pBase )
continue;
for( int j = 0 ; j < GameRule::MAX_SUMMON_SKILL_TREE && jp == -1 ; ++j )
{
if( !pBase->skill_tree_id[j] )
continue;
jp = GameContent::GetNeedJpForSkillLevelUp( pSkill->GetSkillId(), nSkillLevel, pBase->skill_tree_id[j] );
}
}
else if( IsPlayer() )
{
const JobInfo *pInfo = GameContent::GetJobInfo( nJobId[i++] );
if( !pInfo || !pInfo->skill_tree_id )
continue;
jp = GameContent::GetNeedJpForSkillLevelUp( pSkill->GetSkillId(), nSkillLevel, pInfo->skill_tree_id );
}
else
break;
}
if( jp != -1 )
{
nJP += jp;
}
}
}
return nJP;
}
__int64 StructCreature::GetAllJobLevelJP() const
{
__int64 nJP = 0;
int nJobLevel = GetJobLevel();
int nJobDepth = GetJobDepth();
for( int i = 1 ; i < nJobLevel ; ++i )
{
nJP += GameContent::GetNeedJpForJobLevelUp( i, nJobDepth );
}
return nJP;
}
int StructCreature::IsLearnableSkill( int nSkillID, int nSkillLevel, int *nSkillTreeID )
{
return RESULT_ACCESS_DENIED;
}
int StructCreature::GetBaseSkillLevel( int skill_id ) const
{
StructSkill *pSkill = getSkill( skill_id );
if( !pSkill ) return 0;
return pSkill->GetBaseSkillLevel();
}
int StructCreature::GetAddedSkillLevel( const StructSkill *const pSkill ) const
{
// if( pSkill->IsLimitedAddedSkill() ) return 0; // AziaMafia Skill ++
std::vector< std::pair< int, int > >::const_iterator it;
int skill_id = pSkill->GetSkillId();
int skill_type = 0;
int addedSkillLevel = 0;
// 스킬의 타입을 정의
if( pSkill->GetSkillBase()->IsHarmful() ) skill_type += StructState::FLAG_HARMFUL;
else skill_type += StructState::FLAG_HELPFUL;
if( pSkill->GetSkillBase()->IsPassive() ) skill_type += StructState::FLAG_PASSIVE;
else skill_type += StructState::FLAG_ACTIVE;
if( pSkill->GetSkillBase()->IsPhysicalSkill() ) skill_type += StructState::FLAG_PHYSICAL;
else skill_type += StructState::FLAG_MAGICAL;
for( it = m_vAddedSkillBySkillId.begin(); it != m_vAddedSkillBySkillId.end(); it++ )
{
if( (*it).first == skill_id )
{
addedSkillLevel += (*it).second;
break;
}
}
for( it = m_vAddedSkillBySkillType.begin(); it != m_vAddedSkillBySkillType.end(); it++ )
{
if( (*it).first & skill_type )
{
if( !pSkill->IsLimitedAddedSkill() ) // AziaMafia Skill ++
addedSkillLevel += (*it).second;
}
}
if( addedSkillLevel < 0 && addedSkillLevel < this->GetBaseSkillLevel( skill_id ) )
addedSkillLevel = -this->GetBaseSkillLevel( skill_id );
return addedSkillLevel;
}
int StructCreature::GetCurrentSkillLevel( int skill_id ) const
{
StructSkill *pSkill = getSkill( skill_id );
if( !pSkill ) return 0;
return pSkill->GetCurrentSkillLevel();
}
int StructCreature::GetCurrentSkillEnchant(int skill_id) const
{
StructSkill* pSkill = getSkill(skill_id);
if (!pSkill) return 0;
return pSkill->GetEnhance();
}
void StructCreature::AddRemainCoolTime( const int skill_id, const int nInc, const float fAmp )
{
StructPlayer * pClient = NULL;
if( IsPlayer() )
{
pClient = static_cast< StructPlayer * >( this );
}
else if( IsSummon() )
{
pClient = static_cast< StructSummon * >( this )->GetMaster();
}
if( skill_id < 0 )
{
struct _mySkillFunctor : SKILL_POINTER_FUNCTOR
{
_mySkillFunctor( const bool _bExceptRulerOfTime, const int _nInc, const float _fAmp ) : tCurrentTime( GetArTime() ), bExceptRulerOfTime( _bExceptRulerOfTime ), nInc( _nInc ), fAmp( _fAmp ) {}
virtual void onSkill( StructSkill *pSkill )
{
// 은총 스킬은 전직업 공통 스킬로 시스템 스킬의 의도로 들어갔기 때문에 쿨타임 제어에 영향을 받지 않아야 한다.
if( pSkill->GetSkillId() == StructSkill::SKILL_GRACE ) return;
// 시간의 지배자의 쿨타임 값을 변경하지 않아야하는 경우는 변경하지 않도록 한다.
if( bExceptRulerOfTime && pSkill->GetSkillId() == StructSkill::SKILL_RULER_OF_TIME ) return;
if( pSkill->CheckCoolTime( tCurrentTime ) )
{
return;
}
// 현재는 증가와 증폭이 동시에 적용되지 않는다.
// ArTime은 1/100초
pSkill->SetRemainCoolTime( pSkill->GetRemainCoolTime( tCurrentTime ) * fAmp + nInc * 100 );
vSkill.push_back( pSkill->GetSkillId() );
}
AR_TIME tCurrentTime;
std::vector< int > vSkill;
bool bExceptRulerOfTime;
int nInc;
float fAmp;
} foCooldown( ( m_pCastSkill && m_pCastSkill->GetSkillId() == StructSkill::SKILL_RULER_OF_TIME ), nInc, fAmp );
EnumActiveSkill( foCooldown );
SendSkillMessage( pClient, this, foCooldown.vSkill );
}
else
{
// 은총 스킬은 전직업 공통 스킬로 시스템 스킬의 의도로 들어갔기 때문에 쿨타임 제어에 영향을 받지 않아야 한다.
if( skill_id == StructSkill::SKILL_GRACE ) return;
AR_TIME tCurrentTime = GetArTime();
StructSkill *pSkill = GetSkill( skill_id );
if( !pSkill || pSkill->GetSkillUID() <= 0 || pSkill->CheckCoolTime( tCurrentTime ) ) return;
// 현재는 증가와 증폭이 동시에 적용되지 않는다.
// ArTime은 1/100초
pSkill->SetRemainCoolTime( pSkill->GetRemainCoolTime( tCurrentTime ) * fAmp + nInc * 100 );
SendSkillMessage( pClient, this, skill_id );
}
}
AR_TIME StructCreature::GetRemainCoolTime( int skill_id ) const
{
AR_TIME t = GetArTime();
if( ENV().GetInt( "game.no_skill_cooltime", 0 ) )
return 0;
StructSkill *pSkill = getSkill( skill_id );
if( !pSkill ) return 0;
return pSkill->GetRemainCoolTime( t );
}
AR_TIME StructCreature::GetTotalCoolTime( int skill_id ) const
{
AR_TIME t = GetArTime();
if( ENV().GetInt( "game.no_skill_cooltime", 0 ) )
return 0;
StructSkill *pSkill = getSkill( skill_id );
if( !pSkill ) return 0;
return pSkill->GetSkillCoolTime();
}
const __int64 StructCreature::GetBulletCount() const
{
return 1; // Full Bullet
StructItem* pBullet = GetWearedItem(ItemBase::WEAR_BULLET);
return (pBullet && pBullet->IsBullet()) ? pBullet->GetCount() : 0;
}
void StructCreature::BindSkillCard( struct StructItem *pItem, const bool bSkipDBUpdate )
{
StructSkill *pSkill = getSkill( pItem->GetSkillId() );
if( !pSkill ) return;
pSkill->SetEnhance( pItem->GetItemEnhance() );
pItem->SetBindTarget( this, bSkipDBUpdate );
SendSkillCardInfo( pItem );
// 다만 스킬 카드가 2개 이상일 경우 나머지 카드들은 빼서 뒤로 넣어준다
if( pItem->GetCount() > 1 )
{
StructPlayer * pClient;
if( IsPlayer() )
{
pClient = static_cast< StructPlayer * >( this );
}
else if( IsSummon() )
{
pClient = static_cast< StructPlayer * >( static_cast< StructSummon* >( this )->GetMaster() );
}
StructItem * pItemOriginal = pItem;
int nCount = pItemOriginal->GetCount();
pItemOriginal->SetCount( 1 );
SendItemMessage( pClient, pItemOriginal );
pItem = StructItem::AllocItem( 0, pItem->GetItemCode(), 1, ItemInstance::BY_DIVIDE );
pItem->CopyFrom( pItemOriginal );
pItem->SetBindTarget( NULL );
pItem->SetCount( nCount - 1 );
pItem->SetIdx( 0 );
pClient->PushItem( pItem, nCount - 1 );
// 아이템 추적을 위해 분리되면 로그로 기록해준다.
LOG::Log11N4S( LM_ITEM_DIVIDE, pClient->GetAccountID(),
pClient->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(),
pItem->GetItemCode(), pItem->GetCount(),
pItemOriginal->GetCount(), pClient->GetGold().GetRawData(),
0, 0,
pItemOriginal->GetItemUID(), pItem->GetItemUID(),
pClient->GetAccountName(), LOG::STR_NTS,
pClient->GetName(), LOG::STR_NTS,
"", 0,
"CARD_BIND", LOG::STR_NTS );
}
}
void StructCreature::UnBindSkillCard( struct StructItem *pItem )
{
// 해제는 할 수 있어야 한다.
StructSkill *pSkill = getSkill( pItem->GetSkillId() );
if( pSkill )
pSkill->SetEnhance( 0 );
pItem->SetBindTarget( NULL );
SendSkillCardInfo( pItem );
// 스킬 카드 해제 시 같은 카드들이 인벤에 있나 보고 있다면 가져와서 합쳐준다.
StructPlayer * pClient = NULL;
if( IsPlayer() )
{
pClient = static_cast< StructPlayer * >( this );
}
if( IsSummon() )
{
pClient = static_cast< StructSummon * >( this )->GetMaster();
}
StructItem *pJoinableItem = pClient->GetInventory()->FindJoinablePair( pItem );
if( pJoinableItem )
{
int nCount = pJoinableItem->GetCount();
pClient->PopItem( pJoinableItem, nCount );
pItem->SetCount( nCount + 1 );
SendItemMessage( pClient, pItem );
// 아이템 추적을 위해 합쳐지면 로그로 기록해준다.
LOG::Log11N4S( LM_ITEM_JOIN, pClient->GetAccountID(),
pClient->GetSID(), pJoinableItem->GetItemEnhance() * 100 + pJoinableItem->GetItemLevel(),
pJoinableItem->GetItemCode(), pJoinableItem->GetCount(),
pItem->GetCount(), pClient->GetGold().GetRawData(),
0, 0,
pItem->GetItemUID(), pJoinableItem->GetItemUID(),
pClient->GetAccountName(), LOG::STR_NTS,
pClient->GetName(), LOG::STR_NTS,
"", 0,
"CARD_UNBIND", LOG::STR_NTS );
}
}
AR_HANDLE StructCreature::GetTrapHandle()
{
if( !m_hTrap )
return NULL;
GameObject *pObj = GameObject::raw_get( m_hTrap );
if( !pObj || !pObj->IsSkillProp() || !pObj->IsInWorld() || pObj->IsDeleteRequested() )
{
m_hTrap = NULL;
return NULL;
}
return m_hTrap;
}
bool StructCreature::IsWearShield() const
{
StructItem * pShield = GetWearedItem( ItemBase::WEAR_SHIELD );
return pShield ? pShield->GetItemClass() == ItemBase::CLASS_SHIELD : false;
}
bool StructCreature::IsWeared( AR_HANDLE ItemHandle )
{
StructItem *pItem = StructItem::FindItem( ItemHandle );
if( !pItem ) return false;
for( int i = 0; i < ItemBase::MAX_SPARE_ITEM_WEAR; ++i )
{
if( m_anWear[i] == pItem ) return true;
}
return false;
}
bool StructCreature::IsWeared( ItemBase::ItemCode code )
{
for( int i = 0; i < ItemBase::MAX_SPARE_ITEM_WEAR; ++i )
{
if( m_anWear[i] )
{
if( m_anWear[i]->GetItemCode() == code )
return true;
}
}
return false;
}
bool StructCreature::TranslateWearPosition( ItemBase::ItemWearType & pos, struct StructItem *pItem, std::vector< int > * vpOverlappItemList )
{
// 장비품 아님
if( pItem->GetWearType() == ItemBase::WEAR_CANTWEAR && !pItem->IsChaosStone() ) return false;
// 파기되었는지?
if( !pItem->IsWearable() ) return false;
// 유니트 익스퍼트 스킬이 있으면 랭크 무시하고 장착 가능하다고 함.
int nMyLevel = m_nUnitExpertLevel > GetLevel() ? m_nUnitExpertLevel : GetLevel();
if( pItem->GetLevelLimit() > nMyLevel ) return false;
// 장비 아이템에 레벨 제한 설정이 별도로 걸려 있을 경우 해당 레벨 제한을 확인
// 최소 레벨 체크
if( pItem->GetItemBase().nMinLevel && GetLevel() < pItem->GetItemBase().nMinLevel )
return false;
// 최대 레벨 체크
if( pItem->GetItemBase().nMaxLevel && GetLevel() > pItem->GetItemBase().nMaxLevel )
return false;
return true;
}
unsigned short StructCreature::putonItem( ItemBase::ItemWearType pos, struct StructItem * pItem )
{
assert( pos < ItemBase::MAX_SPARE_ITEM_WEAR );
assert( !m_anWear[ pos ] );
m_anWear[ pos ] = pItem;
// floyd 2008. 7..3
// http://bug.nflavor.com/view.php?id=3380 관련 문제를 처리하다 수정
// StructSummon::putonItem에서 처리하면 굳이 IsSummon() 확인 할 필요가 없다.
// if( IsSummon() )
// {
// pItem->SetOwnSummonInfo( GetHandle(), GetSID() );
// }
pItem->SetWearInfo( pos );
pItem->SetBindedCreatureHandle( GetHandle() );
pItem->TurnOnUpdateFlag();
if( ( pItem->IsBow() || pItem->IsCrossBow() ) && pos < ItemBase::MAX_ITEM_WEAR )
{
m_nNextAttackMode = StructCreature::AM_AIMING;
}
if( IsPlayer() )
{
SendItemWearInfoMessage( static_cast< StructPlayer* >( this ), this, m_anWear[ pos ] );
}
else if( IsSummon() )
{
SendItemWearInfoMessage( static_cast< StructPlayer* >( static_cast< StructSummon * >(this)->GetMaster() ), this, m_anWear[ pos ] );
}
return RESULT_SUCCESS;
}
unsigned short StructCreature::putoffItem( ItemBase::ItemWearType pos )
{
assert( pos < ItemBase::MAX_SPARE_ITEM_WEAR );
m_anWear[ pos ]->SetWearInfo( static_cast< ItemBase::ItemWearType >( -1 ) );
m_anWear[ pos ]->SetBindedCreatureHandle( NULL );
m_anWear[ pos ]->TurnOnUpdateFlag();
if( IsPlayer() )
{
SendItemWearInfoMessage( static_cast< StructPlayer* >( this ), this, m_anWear[ pos ] );
}
else if( IsSummon() )
{
SendItemWearInfoMessage( static_cast< StructPlayer* >( static_cast< StructSummon * >(this)->GetMaster() ), this, m_anWear[ pos ] );
}
m_anWear[ pos ] = NULL;
return RESULT_SUCCESS;
}
void StructCreature::PutonSet( struct StructItem* pItemList[] )
{
int idx = 0;
// 일단 다 벗자
for( idx = 0; idx < ItemBase::MAX_ITEM_WEAR; ++idx )
{
if( m_anWear[idx] ) putoffItem( static_cast< ItemBase::ItemWearType >( idx ) );
}
// 다 입자
for( idx = 0; idx < ItemBase::MAX_ITEM_WEAR; ++idx )
{
if( ! ( pItemList[idx] ) ) continue;
Puton( static_cast< ItemBase::ItemWearType >( idx ), pItemList[idx] );
}
// 스탯 재계산
CalculateStat();
}
unsigned short StructCreature::Puton( ItemBase::ItemWearType pos, struct StructItem * pItem )
{
if( pItem->IsChaosStone() )
{
pos = ItemBase::WEAR_CHAOS_STONE;
}
if( !pItem->IsInInventory() )
{
// _cprint( "StructCreature::Puton() :: 장비 불가 : (%I64d)\n", pItem->GetItemUID() );
return RESULT_ACCESS_DENIED;
}
if( pItem->GetWearInfo() != ItemBase::WEAR_NONE )
{
return RESULT_NOT_ACTABLE;
}
std::vector< int > vOverlappItemList;
if( !TranslateWearPosition( pos, pItem, &vOverlappItemList ) )
{
return RESULT_NOT_ACTABLE;
}
// 장착 위치에 무언가 입고 있다면 벗는다
for( std::vector< int >::iterator it = vOverlappItemList.begin(); it != vOverlappItemList.end(); it++ )
{
putoffItem( static_cast< ItemBase::ItemWearType >( *it ) );
if( m_anWear[ *it ] ) return RESULT_NOT_ACTABLE; // 벗기 실패. (목걸이 같은 경우)
}
// 장착
return putonItem( pos, pItem );
}
unsigned short StructCreature::Putoff( ItemBase::ItemWearType pos )
{
// 두손무기는 오른손이므로..
if( pos == ItemBase::WEAR_TWOHAND ) pos = ItemBase::WEAR_WEAPON;
// 예외처리 #3. 투슬롯 반지를 장착해야 하는데 이미 오른쪽에 반지를 입고 있으면 벗는다
if( pos == ItemBase::WEAR_TWOFINGER_RING )
{
pos = ItemBase::WEAR_RING;
}
// 검증
if( ( pos >= ItemBase::MAX_SPARE_ITEM_WEAR && pos != ItemBase::WEAR_TWOHAND ) || pos < 0 )
{
// _cprint( "StructCreature::Putoff() :: 잘못된 탈착위치 : %d\n", pos );
return RESULT_NOT_ACTABLE;
}
// 입고있는 상태가 아니라면 실패
ItemBase::ItemWearType absolute_pos = GetAbsoluteWearPos( pos );
if( absolute_pos == static_cast< ItemBase::ItemWearType >( -1 ) )
{
return RESULT_NOT_ACTABLE;
}
// 장착된 가방을 해제하려 할 때 최대 무게치보다 소지량이 커지면 불가
if( pos == ItemBase::WEAR_BAG_SLOT )
{
if( GetMaxWeight() < GetWeight() )
{
return RESULT_TOO_HEAVY;
}
const ItemBaseServer & current_bag_base = m_anWear[ absolute_pos ]->GetItemBase();
c_fixed10 current_bag_capacity;
for( int i = 0 ; i < ItemBase::MAX_OPTION_NUMBER ; ++i )
{
if( current_bag_base.nOptType[ i ] != ITEM_EFFECT_PASSIVE::CARRY_WEIGHT )
continue;
current_bag_capacity += current_bag_base.fOptVar1[ i ];
}
// 가방에는 소울스톤 세공이 불가능하므로 소울스톤 중에 소지량 증가 성능이 있는 경우를 체크할 필요는 없음
if( current_bag_base.pvEffectList && !current_bag_base.pvEffectList->empty() )
{
for( std::vector< EffectInfo * >::const_iterator it = current_bag_base.pvEffectList->begin() ; it != current_bag_base.pvEffectList->end() ; ++it )
{
EffectInfo * pEffect = (*it);
if( pEffect->nMinLevel && GetLevel() < pEffect->nMinLevel ) continue;
if( pEffect->nMaxLevel && GetLevel() > pEffect->nMaxLevel ) continue;
if( pEffect->eType != EffectInfo::EFFECT_TYPE_OPTIONAL )
continue;
if( pEffect->fValue[ 0 ] == ITEM_EFFECT_PASSIVE::CARRY_WEIGHT ) current_bag_capacity += pEffect->fValue[ 1 ];
if( pEffect->fValue[ 3 ] == ITEM_EFFECT_PASSIVE::CARRY_WEIGHT ) current_bag_capacity += pEffect->fValue[ 4 ];
if( pEffect->fValue[ 6 ] == ITEM_EFFECT_PASSIVE::CARRY_WEIGHT ) current_bag_capacity += pEffect->fValue[ 7 ];
if( pEffect->fValue[ 9 ] == ITEM_EFFECT_PASSIVE::CARRY_WEIGHT ) current_bag_capacity += pEffect->fValue[ 10 ];
}
}
current_bag_capacity *= m_fItemMod;
if( GetMaxWeight() - current_bag_capacity < GetWeight() )
{
return RESULT_TOO_HEAVY;
}
}
// 벗자
return putoffItem( absolute_pos );
}
void StructCreature::ProcByAttack( StructCreature *pTarget, const int nDamage, const bool bIsAttacking, const DWORD nAttackType, const int nElementalType )
{
std::vector< _ATTACK_TAG > *pvProc;
std::vector< StructProc * > vActableProc;
if( bIsAttacking ) pvProc = &m_vProcByAttack;
else pvProc = &m_vProcByBeingAttacked;
std::vector< _ATTACK_TAG >::const_iterator it;
for( it = pvProc->begin(); it != pvProc->end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProcByAttack( GetHPPercentage(), pTarget ? pTarget->GetHPPercentage() : -1, nAttackType, nElementalType ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pTarget, nDamage, bIsAttacking );
delete (*procIt);
}
}
void StructCreature::ProcBySkillId( StructCreature *pTarget, const int nDamage, const bool bIsAttacking, const int nSkillId )
{
std::map< int, std::vector< _PROC_TAG > >::iterator foundIt = m_mapProcBySkillId.find( nSkillId );
if( foundIt == m_mapProcBySkillId.end() ) return;
std::vector< _PROC_TAG > *pvProc = &(*foundIt).second;
std::vector< StructProc * > vActableProc;
std::vector< _PROC_TAG >::const_iterator it;
for( it = pvProc->begin(); it != pvProc->end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProc( GetHPPercentage(), pTarget ? pTarget->GetHPPercentage() : -1 ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pTarget, nDamage, bIsAttacking );
delete (*procIt);
}
}
void StructCreature::ProcByKill( StructCreature *pTarget, const bool bIsKilling )
{
std::vector< _KILL_TAG > *pvProc;
std::vector< StructProc * > vActableProc;
if( bIsKilling ) pvProc = &m_vProcByKill;
else pvProc = &m_vProcByDead;
std::vector< _KILL_TAG >::const_iterator it;
for( it = pvProc->begin(); it != pvProc->end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProcByDead( GetHPPercentage(), pTarget ? pTarget->GetHPPercentage() : -1, GetLevel() - pTarget->GetLevel(), GetMPPercentage() ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pTarget, 0, bIsKilling );
delete (*procIt);
}
}
void StructCreature::ProcByCritical( StructCreature *pTarget, const int nDamage, const bool bIsAttacking, const DWORD nAttackType, const int nElementalType )
{
std::vector< _ATTACK_TAG > *pvProc;
std::vector< StructProc * > vActableProc;
if( bIsAttacking ) pvProc = &m_vProcByCriticalAttack;
else pvProc = &m_vProcByBeingCriticalAttacked;
std::vector< _ATTACK_TAG >::const_iterator it;
for( it = pvProc->begin(); it != pvProc->end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProcByAttack( GetHPPercentage(), pTarget ? pTarget->GetHPPercentage() : -1, nAttackType, nElementalType ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pTarget, nDamage, bIsAttacking );
delete (*procIt);
}
}
void StructCreature::ProcByAvoid( StructCreature *pFrom, const DWORD nAttackType, const int nElementalType )
{
std::vector< StructProc * > vActableProc;
std::vector< _ATTACK_TAG >::const_iterator it;
for( it = m_vProcByAvoid.begin(); it != m_vProcByAvoid.end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProcByAttack( GetHPPercentage(), pFrom ? pFrom->GetHPPercentage() : -1, nAttackType, nElementalType ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pFrom, 0, false );
delete (*procIt);
}
}
void StructCreature::ProcByBlock( StructCreature *pTarget, const int nDamage, const DWORD nAttackType, const int nElementalType )
{
std::vector< StructProc * > vActableProc;
std::vector< _ATTACK_TAG >::const_iterator it;
for( it = m_vProcByBlock.begin(); it != m_vProcByBlock.end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProcByAttack( GetHPPercentage(), pTarget ? pTarget->GetHPPercentage() : -1, nAttackType, nElementalType ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pTarget, nDamage, false );
delete (*procIt);
}
}
void StructCreature::ProcByPerfectBlock( StructCreature *pTarget, const DWORD nAttackType, const int nElementalType )
{
std::vector< StructProc * > vActableProc;
std::vector< _ATTACK_TAG >::const_iterator it;
for( it = m_vProcByPerfectBlock.begin(); it != m_vProcByPerfectBlock.end(); ++it )
{
// 타겟이 존재하지 않는 경우에도 정상 호출이 되어야 하는 경우가 있으므로 우선 검사한다.
if( (*it).CheckProcByAttack( GetHPPercentage(), pTarget ? pTarget->GetHPPercentage() : -1, nAttackType, nElementalType ) )
{
vActableProc.push_back( (*it).proc->Clone() );
}
}
std::vector< StructProc * >::iterator procIt;
for( procIt = vActableProc.begin(); procIt != vActableProc.end(); ++procIt )
{
(*procIt)->Proc( this, pTarget, 0, false );
delete (*procIt);
}
}
void StructCreature::Attack( StructCreature *pTarget, AR_TIME t, AR_TIME attack_interval, struct StructCreature::_ATTACK_INFO * arDamage, bool & bIsDoubleAttack )
{
if( !t ) t = GetArTime();
// 공격 시간 설정
SetNextAttackableTime( t + attack_interval );
// 초기화
int nHate = 0;
int nAttackCount = 1;
bIsDoubleAttack = false;
if( IsUsingDoubleWeapon() )
nAttackCount *= 2; // 아픔 두배
if( XRandom() % 100 < m_Attribute.fDoubleAttackRatio )
{
bIsDoubleAttack = true;
nAttackCount *= 2; // 아픔 두배
}
for( int i = 0; i < nAttackCount; ++i )
{
bool bLeftHandAttack = IsUsingDoubleWeapon() && i % 2; // 양손 공격이고 두번째 공격이면 왼손 공격입니다아~
int prev_target_hp = pTarget->GetHP();
int prev_target_mp = pTarget->GetMP();
int prev_hp = GetHP();
int prev_mp = GetMP();
if( bLeftHandAttack )
{
arDamage[i].SetDamageInfo( pTarget->DealPhysicalNormalLeftHandDamage( this, GetAttackPointLeft(), Elemental::TYPE_NONE, 0, 0 ) );
}
else
{
arDamage[i].SetDamageInfo( pTarget->DealPhysicalNormalDamage( this, GetAttackPointRight(), Elemental::TYPE_NONE, 0, 0 ) );
}
bool bSuccess = !arDamage[i].bMiss;
if( bSuccess )
{
// 소울스톤 내구도 감소
StructItem *pItem = m_anWear[ ItemBase::WEAR_RIGHTHAND ];
if( bLeftHandAttack )
{
pItem = m_anWear[ ItemBase::WEAR_LEFTHAND ];
}
// AziaMafia No Endurence
/*
if( pItem && pItem->GetCurrentEndurance() > 0 && pItem->GetUsingSocketCount() > 0 )
{
int nPrevEndurance = pItem->GetCurrentEndurance();
pItem->SetCurrentEndurance( pItem->GetCurrentEndurance() - GameRule::ENDURANCE_REDUCE_ON_ATTACK );
if( GameRule::GetDecreasedEndurancePoint( nPrevEndurance, pItem->GetCurrentEndurance() ) > 0 )
{
if( IsPlayer() )
{
SendItemMessage( static_cast< StructPlayer * >( this ), pItem );
}
else if( IsSummon() )
{
StructPlayer *pMaster = static_cast< StructSummon * >( this )->GetMaster();
if( pMaster && pMaster->IsLogin() && pMaster->IsInWorld() )
{
SendItemMessage( pMaster, pItem );
}
}
}
if( pItem->GetCurrentEndurance() == 0 )
SetNeedCalculateStat();
}
*/
}
// 여기서 한번 더 보정
arDamage[i].nDamage = prev_target_hp - pTarget->GetHP();
arDamage[i].mp_damage = prev_target_mp - pTarget->GetMP();
arDamage[i].attacker_damage = prev_hp - GetHP();
arDamage[i].attacker_mp_damage = prev_mp - GetMP();
arDamage[i].target_hp = pTarget->GetHP();
arDamage[i].target_mp = pTarget->GetMP();
arDamage[i].attacker_hp = GetHP();
arDamage[i].attacker_mp = GetMP();
nHate += arDamage[i].nDamage;
onAttack( pTarget, arDamage[i].nDamage );
// 명중
if( bSuccess )
{
// 추가타인 경우는 각종 발동이 일어나지 않게 하려는 의도
// 이도에 추가타가 붙은 경우, 오른손, 왼손, 오른손 추가타, 왼손 추가타 순서이다.
if( !bIsDoubleAttack || ( bIsDoubleAttack && i < nAttackCount / 2 ) )
OnAttack( pTarget, arDamage[i].nDamage, StructState::NormalAttack, Elemental::TYPE_NONE );
}
// 크리티컬/블럭/퍼펙트블럭/회피
if( arDamage[i].bCritical ) OnCritical( pTarget, arDamage[i].nDamage, StructState::NormalAttack, Elemental::TYPE_NONE );
if( arDamage[i].bBlock ) pTarget->OnBlock( this, arDamage[i].nDamage, StructState::NormalAttack, Elemental::TYPE_NONE );
if( arDamage[i].bPerfectBlock ) pTarget->OnPerfectBlock( this, StructState::NormalAttack, Elemental::TYPE_NONE );
if( arDamage[i].bMiss ) pTarget->OnAvoid( this, StructState::NormalAttack, Elemental::TYPE_NONE );
}
if( pTarget->IsMonster() )
{
bool bRange = ( IsUsingBow() || IsUsingCrossBow() ) && IsPlayer();
std::pair< float, int > HateMod = GetHateMod( 3, true );
nHate += HateMod.second;
nHate *= HateMod.first;
if( bRange )
{
static_cast< StructMonster * >( pTarget )->AddHate( GetHandle(), m_fHateRatio * nHate * m_RangeStateAdvantage.fHate * pTarget->m_RangeStatePenalty.fHate );
}
else
{
static_cast< StructMonster * >( pTarget )->AddHate( GetHandle(), m_fHateRatio * nHate * m_NormalStateAdvantage.fHate * pTarget->m_NormalStatePenalty.fHate );
}
}
else if( pTarget->IsNPC() )
{
static_cast< StructNPC * >( pTarget )->SetAttacker( pTarget );
}
return;
}
void StructCreature::broadcastAttackMessage( StructCreature *pTarget, struct StructCreature::_ATTACK_INFO * arDamage, int tm, int delay, bool bIsDoubleAttack, bool bIsAiming, bool bEndAttack, bool bCancelAttack )
{
char buf[ sizeof( TS_ATTACK_EVENT ) + sizeof( TS_ATTACK_EVENT::ATTACK_INFO ) * 4 ]; // 최대 4개라서 이리 했음. 나중에 또 추가되면 또 늘려야 함.
TS_ATTACK_EVENT * pMsg = new ( buf ) TS_ATTACK_EVENT();
int nAttackCount = 1;
if( bEndAttack || bCancelAttack )
nAttackCount = 0;
if( !IsFormChanged() )
{
if( IsUsingDoubleWeapon() )
nAttackCount *= 2;
if( bIsDoubleAttack )
nAttackCount *= 2;
}
pMsg->size += sizeof( TS_ATTACK_EVENT::ATTACK_INFO ) * nAttackCount;
pMsg->attack_flag = 0;
if( !IsFormChanged() )
{
if( bIsDoubleAttack ) pMsg->attack_flag |= TS_ATTACK_EVENT::ATTACK_FLAG_DOUBLE_ATTACK;
if( IsUsingDoubleWeapon() ) pMsg->attack_flag |= TS_ATTACK_EVENT::ATTACK_FLAG_DOUBLE_WEAPON;
if( IsUsingBow() && IsPlayer() ) pMsg->attack_flag |= TS_ATTACK_EVENT::ATTACK_FLAG_BOW;
if( IsUsingCrossBow() && IsPlayer() ) pMsg->attack_flag |= TS_ATTACK_EVENT::ATTACK_FLAG_CROSS_BOW;
}
pMsg->attack_action = TS_ATTACK_EVENT::ATTACK_ATTACK;
if( bIsAiming ) pMsg->attack_action = TS_ATTACK_EVENT::ATTACK_AIMING;
else if( bEndAttack ) pMsg->attack_action = TS_ATTACK_EVENT::ATTACK_END;
else if( bCancelAttack ) pMsg->attack_action = TS_ATTACK_EVENT::ATTACK_CANCEL;
pMsg->attack_speed = tm;
pMsg->attack_delay = delay;
pMsg->count = nAttackCount;
pMsg->attacker_handle = GetHandle();
if( pTarget )
{
pMsg->target_handle = pTarget->GetHandle(); // 타겟
}
else
{
pMsg->target_handle = 0; // 타겟
}
TS_ATTACK_EVENT::ATTACK_INFO * pAttackInfo = reinterpret_cast< TS_ATTACK_EVENT::ATTACK_INFO * >( pMsg + 1 );
for( int i = 0; i < nAttackCount; ++i )
{
pAttackInfo[i].damage = arDamage[i].nDamage;
pAttackInfo[i].mp_damage = arDamage[i].mp_damage;
pAttackInfo[i].attacker_damage = arDamage[i].attacker_damage;
pAttackInfo[i].attacker_mp_damage = arDamage[i].attacker_mp_damage;
pAttackInfo[i].target_hp = arDamage[i].target_hp;
pAttackInfo[i].target_mp = arDamage[i].target_mp;
pAttackInfo[i].attacker_hp = arDamage[i].attacker_hp;
pAttackInfo[i].attacker_mp = arDamage[i].attacker_mp;
pAttackInfo[i].flag = 0;
// 플래그 설정
if( arDamage[i].bPerfectBlock ) pAttackInfo[i].flag |= TS_ATTACK_EVENT::FLAG_PERFECT_BLOCK;
if( arDamage[i].bBlock ) pAttackInfo[i].flag |= TS_ATTACK_EVENT::FLAG_BLOCK;
if( arDamage[i].bMiss ) pAttackInfo[i].flag |= TS_ATTACK_EVENT::FLAG_MISS;
if( arDamage[i].bCritical ) pAttackInfo[i].flag |= TS_ATTACK_EVENT::FLAG_CRITICAL;
// 루프 펼치기 신공. 이게 더 빠르거든. -_-
pAttackInfo[i].elemental_damage[0] = arDamage[i].elemental_damage[0];
pAttackInfo[i].elemental_damage[1] = arDamage[i].elemental_damage[1];
pAttackInfo[i].elemental_damage[2] = arDamage[i].elemental_damage[2];
pAttackInfo[i].elemental_damage[3] = arDamage[i].elemental_damage[3];
pAttackInfo[i].elemental_damage[4] = arDamage[i].elemental_damage[4];
pAttackInfo[i].elemental_damage[5] = arDamage[i].elemental_damage[5];
pAttackInfo[i].elemental_damage[6] = arDamage[i].elemental_damage[6];
}
// 방송
ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), pMsg );
}
int StructCreature::GetCriticalDamage( int damage, float critical_amp, int critical_bonus )
{
// 크리티컬
if( XRandom( 0, 99 ) <= ( critical_amp * GetCritical() + critical_bonus ) )
{
return ( damage * ( GetCriticalPower() / 100.0f ) );
}
return 0;
}
bool StructCreature::StartAttack( AR_HANDLE target, bool bNeedFastReaction )
{
if( IsDead() )
{
return false;
}
// AziaMafia Fix Skill
/*
if( IsAttacking() )
{
return false;
}
*/
m_hEnemy = target;
m_StatusFlag.Off( STATUS_ATTACK_STARTED );
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
/*
if( ( IsUsingBow() || IsUsingCrossBow() ) && IsPlayer() )
{
m_nNextAttackMode = StructCreature::AM_AIMING;
}
*/
if( bNeedFastReaction ) onAttackAndSkillProcess();
return true;
}
void StructCreature::CancelAttack()
{
if( ( IsUsingBow() || IsUsingCrossBow() ) && IsPlayer() )
{
if( m_nNextAttackMode == StructCreature::AM_ATTACK )
{
m_nNextAttackMode = StructCreature::AM_AIMING;
// 조준 시간 초기화.
SetNextAttackableTime( GetArTime() );
}
}
if( isAttackStarted() )
{
StructCreature::iterator it = StructCreature::get( m_hEnemy );
StructCreature * pCreature = (*it);
_ATTACK_INFO info[4];
broadcastAttackMessage( pCreature, info, 0, 0, false, false, false, true );
}
m_hEnemy = 0;
m_StatusFlag.On( STATUS_FIRST_ATTACK );
}
void StructCreature::EndAttack()
{
if( ( IsUsingBow() || IsUsingCrossBow() ) && IsPlayer() )
{
if( m_nNextAttackMode == StructCreature::AM_ATTACK )
{
m_nNextAttackMode = StructCreature::AM_AIMING;
// 조준 시간 초기화.
SetNextAttackableTime( GetArTime() );
}
}
if( isAttackStarted() )
{
StructCreature::iterator it = StructCreature::get( m_hEnemy );
StructCreature * pCreature = (*it);
_ATTACK_INFO info[4];
if( ( IsPlayer() || IsSummon() ) && pCreature )
broadcastAttackMessage( pCreature, info, 0, 0, false, false, true );
}
m_hEnemy = 0;
m_StatusFlag.On( STATUS_FIRST_ATTACK );
}
void StructCreature::ProcessAttack()
{
if( IsAttacking() )
{
processAttack();
}
}
void StructCreature::onAttackAndSkillProcess()
{
if( IsUsingSkill() )
{
processSkill();
}
else if( IsAttacking() )
{
processAttack();
}
}
void StructCreature::processSkill()
{
// m_pCastSkill 멤버가 지역락의 보호를 받아야 하지만 StructSkill::ProcSkill은 지역락을 걸지 않고 호출해야 하므로...
StructSkill * pCastSkill = NULL;
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) );
pCastSkill = m_pCastSkill;
}
if( pCastSkill )
{
pCastSkill->ProcSkill();
return;
}
}
void StructCreature::OnAttack( StructCreature *pTarget, const int nDamage, const DWORD nAttackType, const int nElementalType, const int nSkillId )
{
bool bIsAttacking = false;
// AziaMafia TryFix
ProcByAttack( pTarget, nDamage, true, nAttackType, nElementalType );
if( pTarget )
{
bIsAttacking = true;
pTarget->ProcByAttack( this, nDamage, false, nAttackType, nElementalType );
}
// 스킬 아이디를 기준으로 핸들러 호출
// 현재는 특정 스킬 피격 시에 대한 유형은 존재하지 않음
if( nSkillId )
{
ProcBySkillId( pTarget, nDamage, true, nSkillId );
}
if( bIsAttacking )
{
StructState::StateCode nCode = static_cast<StructState::StateCode>( 9903 );
if( m_nLuna && !pTarget->GetState( nCode ) && IsEnemy(pTarget, false))
{
pTarget->AddState( nCode, GetHandle(), 1, GetArTime(), GetArTime() + 18000 );
}
}
}
void StructCreature::OnKill( StructCreature *pTarget )
{
ProcByKill( pTarget, true );
if( pTarget )
pTarget->ProcByKill( this, false );
}
void StructCreature::OnCritical( StructCreature *pTarget, const int nDamage, const DWORD nAttackType, const int nElementalType )
{
ProcByCritical( pTarget, nDamage, true, nAttackType, nElementalType );
if( pTarget )
pTarget->ProcByCritical( this, nDamage, false, nAttackType, nElementalType );
// 기존 유형 유지
ProcessAddHPMPOnCritical();
}
void StructCreature::OnAvoid( StructCreature *pFrom, const DWORD nAttackType, const int nElementalType )
{
ProcByAvoid( pFrom, nAttackType, nElementalType );
}
void StructCreature::OnBlock( StructCreature *pFrom, const int nDamage, const DWORD nAttackType, const int nElementalType )
{
ProcByBlock( pFrom, nDamage, nAttackType, nElementalType );
// 기존 유형 유지
AR_TIME t = GetArTime();
for( std::vector< StateReflectInfo >::iterator it = m_vStateReflectInfo.begin() ; it != m_vStateReflectInfo.end() ; ++it )
{
if( !pFrom->IsNPC() )
pFrom->AddState( (*it).nCode, GetHandle(), (*it).nLevel, t, t + (*it).nDuration );
}
}
void StructCreature::OnPerfectBlock( StructCreature *pFrom, const DWORD nAttackType, const int nElementalType )
{
ProcByPerfectBlock( pFrom, nAttackType, nElementalType );
}
void StructCreature::processAttack()
{
AR_TIME t = GetArTime();
// 행동 못하면 KIN
if( !IsAttackable() )
{
return;
}
bool bIsUsingBow = IsUsingBow();
bool bIsUsingCrossBow = IsUsingCrossBow();
// 공격 가능 시간 안되었다면 KIN
if( GetNextAttackableTime() > t )
{
return;
}
if( ( bIsUsingBow || bIsUsingCrossBow ) && IsPlayer() )
{
// 화살 없으면 KIN
if( GetBulletCount() < 1 )
{
// AziaMafia No Need Bullet
//EndAttack();
//return;
}
}
// 적 포인터를 얻어옴
StructCreature::iterator it = StructCreature::get( m_hEnemy );
StructCreature *pEnemy = *it;
// 근처 범위 Lock
ArcadiaAutoLock _lock;
if( pEnemy )
{
_lock.set( ArcadiaServer::Instance().LockObjects( this, pEnemy ), __FILE__, __LINE__ );
}
else
{
if( IsPlayer() )
{
SendCantAttackMsg( static_cast< StructPlayer * >( this ), GetHandle(), m_hEnemy, RESULT_NOT_EXIST );
}
else if( IsSummon() )
{
SendCantAttackMsg( static_cast< StructSummon * >( this )->GetMaster(), GetHandle(), m_hEnemy, RESULT_NOT_EXIST );
}
EndAttack();
return;
}
if( IsDead() )
{
CancelAttack();
return;
}
// 이동중이라면 KIN
if( IsMoving( t ) )
{
return;
}
// 공격중이 아니라면 KIN
if( !IsAttacking() )
{
return;
}
// 적이 유효하지 않다면 KIN
if( !pEnemy || !IsEnemy( pEnemy ) || pEnemy->IsDead() || !pEnemy->IsInWorld() || !IsVisibleRegion( GetRX(), GetRY(), pEnemy->GetRX(), pEnemy->GetRY() ) )
{
if( IsPlayer() )
SendCantAttackMsg( static_cast< StructPlayer * >( this ), GetHandle(), m_hEnemy, RESULT_NOT_EXIST );
else if( IsSummon() )
SendCantAttackMsg( static_cast< StructSummon * >( this )->GetMaster(), GetHandle(), m_hEnemy, RESULT_NOT_EXIST );
EndAttack();
return;
}
if( isFirstAttack() )
{
pEnemy->OnUpdate();
m_StatusFlag.Off( STATUS_FIRST_ATTACK );
}
if( pEnemy->IsDead() )
{
if( IsPlayer() )
SendCantAttackMsg( static_cast< StructPlayer * >( this ), GetHandle(), m_hEnemy, RESULT_NOT_EXIST );
else if( IsSummon() )
SendCantAttackMsg( static_cast< StructSummon * >( this )->GetMaster(), GetHandle(), m_hEnemy, RESULT_NOT_EXIST );
EndAttack();
return;
}
// 내위치, 상대위치, 거리 얻자
ArPosition enemyPosition = pEnemy->GetCurrentPosition( t );
ArPosition myPosition = GetCurrentPosition( t );
AR_UNIT distance = myPosition.GetDistance( enemyPosition );
distance -= ( pEnemy->GetUnitSize()/2 + GetUnitSize()/2 );
AR_UNIT attack_range = GetRealAttackRange();
// 타겟 방향을 향하도록..
SetDirection( enemyPosition );
{
if( pEnemy->IsMoving() )
attack_range *= 1.5f;
else
attack_range *= 1.2f;
}
// 변수
_ATTACK_INFO Damages[4];
bool bIsDoubleAttack = false;
int nAttackTime = GetAttackInterval(); // 순수 공격에만 소요되는 시간
int nAttackInterval = GetAttackInterval(); // 다음번 공격까지의 interval
// 이번에 수행할 공격 모드 저장
int nCurrentAttackMode = m_nNextAttackMode;
// 사거리에 못미친다면..
if( distance > attack_range )
{
onCantAttack( pEnemy->GetHandle(), t );
return;
}
// AziaMafia TryFix if( ( bIsUsingBow || bIsUsingCrossBow ) && IsPlayer() )
/*
if( ( bIsUsingBow ) && IsPlayer() )
{
// 장전 처리를 해야 한다면
if( nCurrentAttackMode == StructCreature::AM_AIMING )
{
nAttackTime = GetBowAttackInterval() * GameRule::BOW_AIMING_TIME_RATIO;
// 장전시간 설정
nAttackInterval = nAttackTime;
SetNextAttackableTime( t + nAttackInterval );
// 다음번에는 공격하세~
m_nNextAttackMode = StructCreature::AM_ATTACK;
}
else
{
nAttackTime = GetBowAttackInterval() * ( 1.0f - GameRule::BOW_AIMING_TIME_RATIO );
nAttackInterval = nAttackTime + GetBowInterval();
// 다음번에는 장전하세~
m_nNextAttackMode = StructCreature::AM_AIMING;
}
} else nCurrentAttackMode = StructCreature::AM_ATTACK;
NEVER AIM AziaMafia */
nCurrentAttackMode = StructCreature::AM_ATTACK;
// NEVER AIM AziaMafia
// 공격 모드라면 두들겨 패자~~
if( nCurrentAttackMode == StructCreature::AM_ATTACK )
{
//AziaMafia Double Arba
//if ((bIsUsingBow || bIsUsingCrossBow) && IsPlayer())
/*
if( ( bIsUsingBow ) && IsPlayer() )
{
static_cast< StructPlayer * >( this )->EraseBullet( 1 );
}
*/
SetMovableTime( t + nAttackTime );
Attack( pEnemy, t, nAttackInterval, Damages, bIsDoubleAttack );
}
m_StatusFlag.On( STATUS_ATTACK_STARTED );
if( IsFormChanged() )
{
// sum all damage info
int nAttackCount = 1;
if( IsUsingDoubleWeapon() ) nAttackCount = 2;
if( bIsDoubleAttack ) nAttackCount *= 2;
for( int i = 1; i < nAttackCount; ++i )
{
// damage는 더하고
Damages[0].nDamage += Damages[i].nDamage;
Damages[0].mp_damage += Damages[i].mp_damage;
Damages[0].attacker_damage += Damages[i].attacker_damage;
Damages[0].attacker_mp_damage += Damages[i].attacker_mp_damage;
// 체력등의 결과는 최종 버전으로
Damages[0].target_hp = Damages[i].target_hp;
Damages[0].target_mp = Damages[i].target_mp;
Damages[0].attacker_hp = Damages[i].attacker_hp;
Damages[0].attacker_mp = Damages[i].attacker_mp;
// 플래그는 적당히 합치고...
Damages[0].bPerfectBlock |= Damages[i].bPerfectBlock;
Damages[0].bBlock |= Damages[i].bBlock;
Damages[0].bMiss &= Damages[i].bMiss; // 미스는 모두 미스일때만
Damages[0].bCritical |= Damages[i].bCritical;
// 루프 펼치기 신공. 이게 더 빠르거든. -_-
Damages[0].elemental_damage[0] += Damages[i].elemental_damage[0];
Damages[0].elemental_damage[1] += Damages[i].elemental_damage[1];
Damages[0].elemental_damage[2] += Damages[i].elemental_damage[2];
Damages[0].elemental_damage[3] += Damages[i].elemental_damage[3];
Damages[0].elemental_damage[4] += Damages[i].elemental_damage[4];
Damages[0].elemental_damage[5] += Damages[i].elemental_damage[5];
Damages[0].elemental_damage[6] += Damages[i].elemental_damage[6];
}
}
// 메세지 방송
if( !IsFormChanged() || nCurrentAttackMode != StructCreature::AM_AIMING )
broadcastAttackMessage( pEnemy, Damages, nAttackTime * 10, ( GetNextAttackableTime() - t ) * 10, bIsDoubleAttack, nCurrentAttackMode == StructCreature::AM_AIMING );
}
unsigned short StructCreature::onItemUseEffect( struct StructCreature *pCaster, struct StructItem* pItem, int type, c_fixed10 var1, c_fixed10 var2, const char *szParameter )
{
unsigned short nRet = RESULT_SUCCESS;
switch( type )
{
case ITEM_EFFECT_INSTANT::INC_HP :
{
int prev_hp = GetHP();
HealByItem( var1 );
BroadcastHPMPMsg( this, GetHP() - prev_hp, 0 );
break;
}
case ITEM_EFFECT_INSTANT::INC_MP :
{
int prev_mp = GetMP();
MPHealByItem( var1 );
BroadcastHPMPMsg( this, 0, GetMP() - prev_mp );
break;
}
case ITEM_EFFECT_INSTANT::INC_HP_PERCENT:
{
int prev_hp = GetHP();
HealByItem( var1 * GetMaxHP() );
BroadcastHPMPMsg( this, GetHP() - prev_hp, 0 );
break;
}
case ITEM_EFFECT_INSTANT::INC_MP_PERCENT:
{
int prev_mp = GetMP();
MPHealByItem( var1 * GetMaxMP() );
BroadcastHPMPMsg( this, 0, GetMP() - prev_mp );
break;
}
case ITEM_EFFECT_INSTANT::INC_STAMINA :
case ITEM_EFFECT_INSTANT::INC_STAMINA_EX:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
static_cast< StructPlayer * >( this )->AddStamina( var1 );
break;
}
case ITEM_EFFECT_INSTANT::RESURECTION :
{
nRet = RESULT_ACCESS_DENIED;
// 자신에게는 못씀
if( pItem->GetOwnerHandle() == GetHandle() )
break;
// 안죽었으면 못씀
if( IsAlive() ) break;
// 죽었더라도 내구도가 다한 소환수는 살릴 수 없음
if( IsSummon() )
{
StructItem *pCardItem = static_cast< StructSummon * >( this )->GetParentCard();
if( pCardItem && pCardItem->GetMaxEtherealDurability() && !pCardItem->GetCurrentEtherealDurability() )
break;
}
if( !pCaster->IsAlly( this ) )
break;
int prev_hp = GetHP();
AddHP( GetMaxHP() * 0.2f );
BroadcastHPMPMsg( this, GetHP() - prev_hp, 0 );
//AziaMafia KeepBuff & fix
ClearRemovedStateByDead();
if( IsPlayer() )
{
StructPlayer *pPlayer = static_cast< StructPlayer * >(this);
StructPlayer *pCastPlayer = ( pCaster->IsPlayer() ) ? static_cast< StructPlayer * >( pCaster ) : NULL;
if( pPlayer->IsCompeteDead() )
pPlayer->SetCompeteDead( false );
LOG::Log11N4S( LM_CHARACTER_RESURRECTION, pPlayer->GetAccountID(), pPlayer->GetSID(),
CRT_ITEM, ( pCastPlayer ) ? pCastPlayer->GetAccountID() : 0, ( pCastPlayer ) ? pCastPlayer->GetPlayerUID() : 0, 0, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), 0, pPlayer->GetEXP(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, ( pCastPlayer ) ? pCastPlayer->GetAccountName() : "", LOG::STR_NTS, ( pCastPlayer ) ? pCastPlayer->GetName() : "" , LOG::STR_NTS );
}
else if( IsSummon() )
{
StructSummon *pSummon = static_cast< StructSummon * >(this);
StructPlayer *pPlayer = static_cast< StructPlayer * >(pSummon->GetMaster());
LOG::Log11N4S( LM_SUMMON_RESURRECTION, pPlayer->GetAccountID(), pPlayer->GetSID(), pSummon->GetSID(), 0, 0, 0, 0, 0, 0, 0, pSummon->GetEXP(), pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS );
}
nRet = RESULT_SUCCESS;
break;
}
case ITEM_EFFECT_INSTANT::WARP :
{
break;
}
case ITEM_EFFECT_INSTANT::SKILL : // 스킬 호출
{
AR_HANDLE hTarget = GetHandle();
// 귀환/복귀의 깃털 스킬일 경우 타겟을 아이템 핸들로 대체(소켓에 이동할 좌표가 들어있으므로)
if( var1 == StructSkill::SKILL_RETURN_FEATHER || var1 == StructSkill::SKILL_RETURN_BACK_FEATHER )
hTarget = pItem->GetHandle();
nRet = pCaster->CastSkill( var1, var2, hTarget, ArPosition(), GetLayer(), pItem->GetHandle() );
break;
}
case ITEM_EFFECT_INSTANT::ADD_STATE : // 지속효과 호출
case ITEM_EFFECT_INSTANT::ADD_STATE_EX : // 확장 지속효과 호출
{
StructState::StateCode eCode = static_cast< StructState::StateCode >( ( type == ITEM_EFFECT_INSTANT::ADD_STATE ) ? pItem->GetStateCode() : (int)var1 );
if( IsPlayer() )
{
StructPlayer *pPlayer = static_cast< StructPlayer * >( this );
const StateInfo * pStateInfo = GameContent::GetStateInfo( eCode );
if( !pStateInfo )
{
nRet = RESULT_NOT_ACTABLE;
break;
}
if( pStateInfo->effect_type == StructState::EF_RIDING )
{
if( pPlayer->IsRiding() || pPlayer->HasRidingState() || !pPlayer->IsMountable( true ) )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
}
if( eCode == StructState::STAMINA_SAVE )
{
// 스테미너 효과가 더블 플러스 PC방에서 적용되지 않는 경우는 스테미너 세이버(비매품 포함)는 프리미엄 PC방에서는 사용 불가
if( pPlayer->GetPCBangMode() == GameRule::PCBANG_PREMIUM_BONUS && !GameRule::bApplyStaminaBonusInPremiumPCBang )
{
nRet = RESULT_ALREADY_STAMINA_SAVED;
break;
}
// 게임 중독 방지 시스템 관련 스테미너 세이버 사용 방지 체크
if( pPlayer->IsGameTimeLimited() )
{
if( pPlayer->GetContinuousPlayTime() >= GameRule::nMaxTiredGameTime )
{
nRet = RESULT_GAMETIME_HARMFUL_STAMINA_SAVER;
break;
}
else if( pPlayer->GetContinuousPlayTime() >= GameRule::nMaxHealthyGameTime )
{
nRet = RESULT_GAMETIME_TIRED_STAMINA_SAVER;
break;
}
}
// 스태미너 세이버 효과가 있는 상태라면 사용 불가
if( pPlayer->GetState( StructState::STAMINA_SAVE ) )
{
nRet = RESULT_ALREADY_STAMINA_SAVED;
break;
}
}
if( eCode == StructState::SUPER_SAVE_0 ||
eCode == StructState::SUPER_SAVE_1 ||
eCode == StructState::SUPER_SAVE_2 ||
eCode == StructState::SUPER_SAVE_3 )
{
// 게임 중독 방지 시스템 관련 성장의 물약(구 슈퍼 세이버) 사용 방지 체크
if( pPlayer->IsGameTimeLimited() )
{
if( pPlayer->GetContinuousPlayTime() >= GameRule::nMaxTiredGameTime )
{
nRet = RESULT_GAMETIME_HARMFUL_SUPER_SAVER;
break;
}
else if( pPlayer->GetContinuousPlayTime() >= GameRule::nMaxHealthyGameTime )
{
nRet = RESULT_GAMETIME_TIRED_SUPER_SAVER;
break;
}
}
// 성장의 물약(구 슈퍼 세이버)는 중첩 불가
if( pPlayer->GetState( eCode ) )
{
nRet = RESULT_ALREADY_SUPER_SAVER;
break;
}
}
}
AR_TIME t = GetArTime();
int nLevel = ( type == ITEM_EFFECT_INSTANT::ADD_STATE ) ? pItem->GetStateLevel() : var2;
nRet = AddState( static_cast< StructState::StateCode >( eCode ), pItem->GetOwnerHandle(), nLevel, t, t + pItem->GetStateTime()*100 );
break;
}
case ITEM_EFFECT_INSTANT::REMOVE_STATE : // 지속효과 제거
{
RemoveState( (StructState::StateCode)pItem->GetStateCode(), pItem->GetStateLevel() );
break;
}
case ITEM_EFFECT_INSTANT::TOGGLE_STATE :
{
StructItem *pWearingRideItem = GetWearedItem( ItemBase::WEAR_RIDE_ITEM );
// 내리기는 탑승시 사용한 아이템(장착되어 있음)으로만 가능
if( GetState( static_cast< StructState::StateCode >( pItem->GetStateCode() ) ) && pWearingRideItem == pItem )
{
RemoveState( (StructState::StateCode)pItem->GetStateCode(), pItem->GetStateLevel() );
break;
}
else
{
if( IsPlayer() )
{
StructPlayer *pPlayer = static_cast< StructPlayer * >( this );
if( pPlayer->IsRiding() || pPlayer->HasRidingState() || !pPlayer->IsMountable( true ) )
{
const StateInfo * pStateInfo = GameContent::GetStateInfo( pItem->GetStateCode() );
if( pStateInfo->effect_type == StructState::EF_RIDING )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
}
if( pWearingRideItem )
{
if( pWearingRideItem != pItem && ( pPlayer->Putoff( ItemBase::WEAR_RIDE_ITEM ) != RESULT_SUCCESS || pPlayer->Puton( ItemBase::WEAR_RIDE_ITEM, pItem ) != RESULT_SUCCESS ) )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
}
else if( pPlayer->Puton( ItemBase::WEAR_RIDE_ITEM, pItem ) != RESULT_SUCCESS )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
}
AR_TIME t = GetArTime();
nRet = AddState( (StructState::StateCode)pItem->GetStateCode(), pItem->GetOwnerHandle(), pItem->GetStateLevel(), t, -1, true );
break;
}
}
case ITEM_EFFECT_INSTANT::GENERATE_ITEM : // 아이템 생성
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if( var1 == 0 )
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
if( pPlayer->ChangeGold( pPlayer->GetGold() + var2.GetAsInt64() ) != RESULT_SUCCESS )
{
nRet = RESULT_TOO_MUCH_MONEY;
break;
}
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, 0, 0, 0, var2, pPlayer->GetGold().GetRawData(), pPlayer->GetX(), pPlayer->GetY(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "IGEN", LOG::STR_NTS );
}
else
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
// 일반 아이템 생성이면 그 아이템을 var2개 만큼 생성, 드랍 그룹 아이템 생성이면 var2 횟수만큼 드랍 그룹 반복 선택
int nGenCount = ( var1 >= 0 ) ? 1 : var2;
//bool bIsBlank = true;
for( int i = 0 ; i < nGenCount ; ++i )
{
ItemBase::ItemCode nItemID = var1;
__int64 nItemCount = var2;
while( nItemID < 0 )
{
GameContent::SelectItemIDFromDropGroup( nItemID, nItemID, nItemCount );
}
if( nItemID )
{
//bIsBlank = true;
StructItem *pItem = StructItem::AllocItem( 0, nItemID, nItemCount, ItemInstance::BY_ITEM );
// 아이템 획득 메시지를 보여주기 위해 그냥 보내주기
StructItem *pNewItem = static_cast< StructPlayer *>( this )->PushItem( pItem, pItem->GetCount() );
if( pNewItem )
{
SendResult( this, TM_CS_TAKE_ITEM, RESULT_SUCCESS, pNewItem->GetHandle() );
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), pItem->GetItemCode(), pItem->GetCount(), pNewItem ? pNewItem->GetCount() : 0, 0, 0, pPlayer->GetX(), pPlayer->GetY(), pItem->GetItemUID(), pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "IGEN", LOG::STR_NTS );
}
if( pNewItem != pItem )
{
StructItem::PendFreeItem( pItem );
}
}
else
{
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, 0, 0, 0, 0, pPlayer->GetGold().GetRawData(), pPlayer->GetX(), pPlayer->GetY(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "IGEN", LOG::STR_NTS );
}
}
// 2008.12.02 꽝 처리 일시적으로 제거(2008년 크리스마스 이벤트 관련)
// 아이템 사용함으로 인해 아이템이 하나도 안 나온 경우에만 꽝 메시지 출력
// 아이템 하나에서 드랍그룹 2개 이상 사용 시에 아이템을 1개는 얻었고 나머지 드랍 그룹에서는 꽝이 나와도 꽝은 아니므로...
//if( bIsBlank )
//{
// PrintfChatMessage( false, CHAT_ITEM, "@SYSTEM", pPlayer, "@6696" );
//}
}
break;
}
case ITEM_EFFECT_INSTANT::INC_GOLD:
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
StructGold nIncGold( XRandom( var1, var2 ) );
if( pPlayer->ChangeGold( pPlayer->GetGold() + nIncGold ) != RESULT_SUCCESS )
{
nRet = RESULT_TOO_MUCH_MONEY;
break;
}
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, 0, 0, 0, nIncGold.GetRawData(), pPlayer->GetGold().GetRawData(), pPlayer->GetX(), pPlayer->GetY(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "IGEN", LOG::STR_NTS );
break;
}
case ITEM_EFFECT_INSTANT::INC_HUNTAHOLIC_POINT:
{
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
int nIncHuntaholicPoint( XRandom( var1, var2 ) );
pPlayer->SetHuntaholicPoint( pPlayer->GetHuntaholicPoint() + nIncHuntaholicPoint );
LOG::Log11N4S( LM_ITEM_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, ItemBase::ITEM_CODE_HUNTAHOLIC_POINT_TICKET, nIncHuntaholicPoint, pPlayer->GetHuntaholicPoint(), 0, pPlayer->GetGold().GetRawData(), pPlayer->GetX(), pPlayer->GetY(), 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "IGEN", LOG::STR_NTS );
break;
}
case ITEM_EFFECT_INSTANT::RESET_SKILL: // 리트레이닝 포션
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if( GetJobDepth() < var1 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
nRet = ResetSkill( SRM_ITEM ) ? RESULT_SUCCESS : RESULT_ACCESS_DENIED;
break;
}
case ITEM_EFFECT_INSTANT::RESET_JOB:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if( GetJobDepth() < var1 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
nRet = static_cast< StructPlayer * >( this )->ResetJob( 0 ) ? RESULT_SUCCESS : RESULT_ACCESS_DENIED;
break;
}
case ITEM_EFFECT_INSTANT::RESET_SUMMON_SKILL:
{
if( !IsSummon() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
// 랜덤 스킬 초기화 유형은 랜덤스킬을 가진 소울 크리처에만 사용 가능하다.
// IsSoulCreature 함수의 인자를 위해 기본형의 소환수 코드를 가져온다.
StructSummon * pSummon = static_cast< StructSummon * >( this );
int nBasicSummonCode = pSummon->GetSummonCode();
if( pSummon->GetTransformLevel() == SummonBase::EVOLVE_GROWTH || pSummon->GetTransformLevel() == SummonBase::EVOLVE_EVOLVE )
{
nBasicSummonCode = pSummon->GetPrevJobId( 0 );
}
if( !IsInWorld() || IsDead() ||
static_cast< StructSummon * >( this )->GetMaster() != pCaster )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
nRet = ResetSkill( SRM_ITEM, 0, int(var1) ) ? RESULT_SUCCESS : RESULT_ACCESS_DENIED;
break;
}
case ITEM_EFFECT_INSTANT::RECALL:
{
if( !IsPlayer() || !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer * pThisPlayer = static_cast< StructPlayer * >( this );
StructPlayer * pCastPlayer = static_cast< StructPlayer * >( pCaster );
// 시크루트는 들어가도 아무것도 못하므로 그냥 놔두기
if( pCastPlayer->IsInSiegeOrRaidDungeon() )
{
nRet = RESULT_NOT_ACTABLE_HERE;
break;
}
if( pCastPlayer->IsInInstanceDungeon() )
{
nRet = RESULT_NOT_ACTABLE_IN_INSTANCE_DUNGEON;
break;
}
char szBuf[255];
s_sprintf( szBuf, _countof( szBuf ), "recall_feather( %f, %f, %d )", pCastPlayer->GetX(), pCastPlayer->GetY(), pCastPlayer->GetLayer() );
if( var1 == 1.0f ) // 한 사람만 호출
{
// 시체거나, 파티원이 아니거나 창고 사용 중, 혹은 자신이면 사용 실패
if( pThisPlayer->IsDead() || !pThisPlayer->GetPartyID() || pThisPlayer->GetPartyID() != pCastPlayer->GetPartyID()
|| pThisPlayer->IsUsingStorage() || pCastPlayer == pThisPlayer )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
// 일반 던전일 경우 레벨 제한 체크
if( pCastPlayer->IsInDungeon() )
{
int nDungeonID = DungeonManager::Instance().GetDungeonID( pCastPlayer->GetX(), pCastPlayer->GetY() );
if( nDungeonID && DungeonManager::IsRestrictedToEnter( DungeonManager::Instance().GetDungeonLevel( nDungeonID ), pThisPlayer->GetLevel() ) )
{
// 이 곳으로 소환하기엔 레벨이 딸립니다.
PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pCastPlayer, "@563\v#@player_name@#\v%s", pThisPlayer->GetName() );
PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pThisPlayer, "@563\v#@player_name@#\v%s", pThisPlayer->GetName() );
nRet = RESULT_ACCESS_DENIED;
break;
}
}
pThisPlayer->SetFixedDialogTrigger( szBuf );
pThisPlayer->SetNonNPCDialog( true );
SendWindowMessage( pThisPlayer, "recall_feather_confirm_window", pCastPlayer->GetName(), szBuf );
LOG::Log11N4S( LM_ITEM_RECALL_FEATHER_REQUEST, pCastPlayer->GetAccountID(), pCastPlayer->GetSID(), pCastPlayer->GetPartyID(),
pCastPlayer->GetX(), pCastPlayer->GetY(), pCastPlayer->GetLayer(),
pThisPlayer->GetAccountID(), pThisPlayer->GetSID(), pThisPlayer->GetX(), pThisPlayer->GetY(), pThisPlayer->GetLayer(),
pCastPlayer->GetAccountName(), LOG::STR_NTS, pCastPlayer->GetName(), LOG::STR_NTS, pThisPlayer->GetAccountName(), LOG::STR_NTS, pThisPlayer->GetName(), LOG::STR_NTS );
}
else // 파티 전체 호출, 실제 var1 은 8이어야 함. ItemDB 참조.
{
if( !pCastPlayer->GetPartyID() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
struct RecallFeatherCheckFunctor : PartyManager::PartyFunctor
{
RecallFeatherCheckFunctor( StructPlayer * pCaster, StructItem *pItem ) : m_pCaster( pCaster ), m_pItem( pItem ), m_nResult( RESULT_SUCCESS ) {};
virtual bool operator()( AR_HANDLE handle )
{
if( m_nResult != RESULT_SUCCESS )
return false;
StructPlayer::iterator pit = StructPlayer::get( handle );
StructPlayer *pPlayer = *pit;
if( pPlayer == m_pCaster )
return true;
if( pPlayer->IsInEventmap() )
{
m_nResult = RESULT_TARGET_IN_EVENTMAP;
return false;
}
if( HuntaholicManager::Instance().GetHuntaholicID( pPlayer->GetPos() ) )
{
m_nResult = RESULT_TARGET_IN_HUNTAHOLIC;
return false;
}
if( !pPlayer || !pPlayer->GetPartyID() || pPlayer->GetPartyID() != m_pCaster->GetPartyID() || pPlayer->IsUsingStorage() )
{
m_nResult = RESULT_NOT_ACTABLE;
return false;
}
if( m_pCaster->IsInDungeon() )
{
int nDungeonID = DungeonManager::Instance().GetDungeonID( m_pCaster->GetX(), m_pCaster->GetY() );
if( nDungeonID && DungeonManager::IsRestrictedToEnter( DungeonManager::Instance().GetDungeonLevel( nDungeonID ), pPlayer->GetLevel() ) )
{
// 이 곳으로 소환하기엔 레벨이 딸립니다.
PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", m_pCaster, "@563\v#@player_name@#\v%s", pPlayer->GetName() );
PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pPlayer, "@563\v#@player_name@#\v%s", pPlayer->GetName() );
m_nResult = RESULT_ACCESS_DENIED;
return false;
}
}
return true;
}
StructPlayer * m_pCaster;
StructItem *m_pItem;
int m_nResult;
} foChecker( pCastPlayer, pItem );
size_t nCount = PartyManager::GetInstance().DoEachMember( pCastPlayer->GetPartyID(), foChecker );
// 아이템을 사용한 플레이어를 제외하고 한 명 이상의 플레이어가 로그인되어 있어야 한다.(최소 두 명)
if( nCount < 2 )
{
nRet = RESULT_NOT_EXIST;
break;
}
if( foChecker.m_nResult != RESULT_SUCCESS )
{
nRet = foChecker.m_nResult;
break;
}
struct RecallFeatherFunctor : PartyManager::PartyFunctor
{
RecallFeatherFunctor( StructPlayer * pCaster, const char * _szTrigger ) : m_pCaster( pCaster ), szTrigger( _szTrigger ) {};
virtual bool operator()( AR_HANDLE handle )
{
StructPlayer::iterator pit = StructPlayer::get( handle );
StructPlayer *pPlayer = *pit;
if( !pPlayer || pPlayer->IsDead() || pPlayer == m_pCaster
|| !pPlayer->GetPartyID() || pPlayer->GetPartyID() != m_pCaster->GetPartyID() || pPlayer->IsUsingStorage() )
return true;
pPlayer->SetFixedDialogTrigger( szTrigger );
pPlayer->SetNonNPCDialog( true );
SendWindowMessage( pPlayer, "recall_feather_confirm_window", m_pCaster->GetName(), szTrigger );
LOG::Log11N4S( LM_ITEM_RECALL_FEATHER_REQUEST, m_pCaster->GetAccountID(), m_pCaster->GetSID(), m_pCaster->GetPartyID(),
m_pCaster->GetX(), m_pCaster->GetY(), m_pCaster->GetLayer(),
pPlayer->GetAccountID(), pPlayer->GetSID(), pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(),
m_pCaster->GetAccountName(), LOG::STR_NTS, m_pCaster->GetName(), LOG::STR_NTS, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS );
return true;
}
StructPlayer * m_pCaster;
const char * szTrigger;
} fo( pCastPlayer, szBuf );
PartyManager::GetInstance().DoEachMember( pCastPlayer->GetPartyID(), fo );
}
break;
}
case ITEM_EFFECT_INSTANT::ADD_IMMORAL_POINT:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer *pPlayer = static_cast< StructPlayer * >(this);
// 모럴 상태에서는 아이템을 사용할 수 없으며, 이모럴일 때에는 이모럴 수치가 깎일 수치보다 적다고 해도 그냥 적용
if( pPlayer->GetImmoralPoint() <= 0 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
pPlayer->SetImmoralPoint( std::max( 0.0, static_cast< double >( pPlayer->GetImmoralPoint() ) + var1 ) );
BroadcastStatusMessage( pPlayer );
}
break;
case ITEM_EFFECT_INSTANT::SET_IMMORAL_POINT:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer *pPlayer = static_cast< StructPlayer * >(this);
// 세팅하려는 수치보다 이모럴 포인트가 같거나 낮은 상태에서는 아이템을 사용할 수 없음
if( pPlayer->GetImmoralPoint() <= var1 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
pPlayer->SetImmoralPoint( std::max( 0.0, double(var1) ) );
BroadcastStatusMessage( pPlayer );
}
break;
case ITEM_EFFECT_INSTANT::RENAME_SUMMON:
{
if( !IsSummon() || static_cast< StructSummon * >(this)->GetRidingInfo() == SummonBase::RIDING_LENT )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructSummon *pSummon = static_cast< StructSummon * >( this );
StructPlayer *pPlayer = pSummon->GetMaster();
// pPlayer->GetSubSummon 테스트는 2마리의 크리처가 소환되어 있는지 확인하기 위함.
// 2마리 소환 상태에서는 사용 불가능
if( !pSummon->IsInWorld() || !pPlayer || pPlayer != pCaster || !pPlayer->IsInWorld() || ( pPlayer->GetSubSummon() && pPlayer->GetSubSummon()->IsInWorld() ) )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
// 이름 미지정(szParameter가 NULL이 되는 경우는 없지만...) 또는 길이 부족
int code_page = ENV().GetInt( "CodePage", CP_ACP );
if( !szParameter || strlen( szParameter ) < 4 )
{
nRet = RESULT_INVALID_TEXT;
break;
}
// 같은 이름 사용 불가
else if( !strncmp( szParameter, pSummon->GetName(), std::max( strlen( szParameter ), strlen( pSummon->GetName() ) ) ) )
{
nRet = RESULT_INVALID_TEXT;
break;
}
else if( !GameRule::IsValidName( code_page, szParameter, (int)strlen( szParameter ) + 1, 4, 18 ) || // IsValidName에서 소환수 이름은 최대 길이 19 Byte이므로 버퍼 제한 18로 고정
GameContent::IsBannedWord( code_page, szParameter ) )
{
nRet = RESULT_INVALID_TEXT;
break;
}
LOG::Log11N4S( LM_SUMMON_CHANGE_NAME, pPlayer->GetAccountID(), pPlayer->GetPlayerUID(), pSummon->GetSID(), 0, 0, 0, 0, 0, 0, 0, pSummon->GetParentCard()->GetItemUID(), pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, pSummon->GetName(), LOG::STR_NTS, szParameter, LOG::STR_NTS );
pSummon->SetName( szParameter );
// 각종 UI에 이름 변경을 알려주기 위함
SendPropertyMessage( pPlayer, pSummon->GetHandle(), "name", pSummon->GetName() );
// 주변 사람들에게 바뀐 이름 보이게 하기 위함
TS_SC_CHANGE_NAME msg;
msg.handle = pSummon->GetHandle();
s_strcpy( msg.name, _countof( msg.name ), szParameter );
ArcadiaServer::Instance().Broadcast( pSummon->GetRX(), pSummon->GetRY(), pSummon->GetLayer(), &msg );
// 소환수가 월드에 있을 때만 처리되므로 SetNeedCalculateStat으로만 처리하면 됨.
// 없을 경우에는 CalculateStat 바로 호출해도 됨.
pSummon->SetNeedCalculateStat();
}
break;
case ITEM_EFFECT_INSTANT::RENAME_CHARACTER:
{
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer * pCastPlayer = static_cast< StructPlayer * >( pCaster );
if( !pCastPlayer->ChangeName( szParameter, var1, true, pItem->GetHandle() ) )
{
// 아이템을 사용하지 못했다는 내용에 대한 시스템 메시지를 출력하지 않도록 정의되지 않은 오류 코드를 반환
nRet = RESULT_UNKNOWN;
break;
}
}
break;
case ITEM_EFFECT_INSTANT::RENAME_PET:
{
if( !IsPet() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPet *pPet = static_cast< StructPet * >( this );
StructPlayer *pPlayer = pPet->GetMaster();
if( !pPet->IsInWorld() || !pPlayer || pPlayer != pCaster || !pPlayer->IsInWorld() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if( !pPet->GetParentCage() || pPet->GetParentCage()->IsExpireItem() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
// 이름 미지정(szParameter가 NULL이 되는 경우는 없지만...) 또는 길이 부족
int code_page = ENV().GetInt( "CodePage", CP_ACP );
if( !szParameter || strlen( szParameter ) < 4 )
{
nRet = RESULT_INVALID_TEXT;
break;
}
// 같은 이름 사용 불가
else if( !strncmp( szParameter, pPet->GetName(), std::max( strlen( szParameter ), strlen( pPet->GetName() ) ) ) )
{
nRet = RESULT_INVALID_TEXT;
break;
}
else if( !GameRule::IsValidName( code_page, szParameter, (int)strlen( szParameter ) + 1, 4, 18 ) || // IsValidName에서 펫 이름은 최대 길이 19 Byte이므로 버퍼 제한 18로 고정
GameContent::IsBannedWord( code_page, szParameter ) )
{
nRet = RESULT_INVALID_TEXT;
break;
}
LOG::Log11N4S( LM_PET_CHANGE_NAME, pPlayer->GetAccountID(), pPlayer->GetPlayerUID(), pPet->GetSID(), 0, 0, 0, 0, 0, 0, 0, pPet->GetParentCage()->GetItemUID(), pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, pPet->GetName(), LOG::STR_NTS, szParameter, LOG::STR_NTS );
nRet = pPet->ChangeName( szParameter, true );
}
break;
case ITEM_EFFECT_INSTANT::WARP_TO_SPECIAL_POSITION:
{
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer *pCastPlayer = static_cast< StructPlayer * >( pCaster );
unsigned char layer = 0;
int current_channel = ChannelManager::GetChannelId( pCastPlayer->GetX(), pCastPlayer->GetY() );
int target_channel = ChannelManager::GetChannelId( var1, var2 );
if( current_channel && current_channel == target_channel )
{
layer = pCastPlayer->GetLayer();
}
else if( target_channel )
{
layer = ChannelManager::GetProperLayer( var1, var2 );
}
pCastPlayer->PendWarp( var1, var2, layer );
ArcadiaServer::Instance().SetObjectPriority( pCastPlayer, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
}
break;
case ITEM_EFFECT_INSTANT::WARP_TO_PLAYER:
{
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer *pCastPlayer = static_cast< StructPlayer * >( pCaster );
if( !szParameter )
{
nRet = RESULT_INVALID_ARGUMENT;
break;
}
StructPlayer::iterator pit = StructPlayer::get( StructPlayer::FindPlayer( szParameter ) );
StructPlayer *pTarget = *pit;
if( !pTarget || !pTarget->IsInWorld() || !pTarget->IsLogin() || !pTarget->IsLoginComplete() || pTarget == this )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if( pTarget->IsInSecroute() )
{
nRet = RESULT_TARGET_IN_SECROUTE;
break;
}
if( pTarget->IsInSiegeOrRaidDungeon() )
{
nRet = RESULT_TARGET_IN_SIEGE_OR_RAID;
break;
}
// 값 2의 값이 0이면 숨겨진 던전 내의 대상을 향해 워프 불가, 1이면(0 외의 경우) 가능
assert( var2 == 0 || var2 == 1 );
if( var2 == 0 && pTarget->IsInSecretDungeon() )
{
nRet = RESULT_TARGET_IN_SECRET_DUNGEON;
break;
}
if( pTarget->IsInInstanceDungeon() )
{
nRet = RESULT_TARGET_IN_INSTANCE_DUNGEON;
break;
}
if( pTarget->IsInDungeon() )
{
int nDungeonID = DungeonManager::Instance().GetDungeonID( pTarget->GetX(), pTarget->GetY() );
if( nDungeonID && DungeonManager::IsRestrictedToEnter( DungeonManager::Instance().GetDungeonLevel( nDungeonID ), pCaster->GetLevel() ) )
{
// 이 곳으로 워프하기엔 레벨이 딸립니다.
PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", pTarget, "@563\v#@player_name@#\v%s", pCaster->GetName() );
PrintfChatMessage( false, CHAT_NOTICE, "@NOTICE", static_cast< StructPlayer* >( pCaster ), "@563\v#@player_name@#\v%s", pCaster->GetName() );
nRet = RESULT_ACCESS_DENIED;
break;
}
}
if( pTarget->IsInDeathmatch() )
{
nRet = RESULT_TARGET_IN_DEATHMATCH;
break;
}
if( pTarget->IsInEventmap() )
{
nRet = RESULT_TARGET_IN_EVENTMAP;
break;
}
if( pTarget->IsInBattleArena() )
{
nRet = RESULT_TARGET_IN_BATTLE_ARENA;
break;
}
if( HuntaholicManager::Instance().GetHuntaholicID( pTarget->GetPos() ) )
{
nRet = RESULT_TARGET_IN_HUNTAHOLIC;
break;
}
// 파티원, 길드원, 길드 연합원만 가능
int nPartyID = pTarget->GetPartyID();
if( !nPartyID || nPartyID != pCastPlayer->GetPartyID() )
{
int nGuildId = pTarget->GetGuildID();
int nCasterGuildID = pCastPlayer->GetGuildID();
if( !nGuildId || nGuildId != nCasterGuildID )
{
int nAllianceID = GuildManager::GetInstance().GetAllianceID( nGuildId );
if( !nAllianceID || nAllianceID != GuildManager::GetInstance().GetAllianceID( nCasterGuildID ) )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
}
}
pCastPlayer->PendWarp( pTarget->GetX(), pTarget->GetY(), pTarget->GetLayer() );
ArcadiaServer::Instance().SetObjectPriority( pCastPlayer, ArSchedulerObject::UPDATE_PRIORITY_HIGHEST );
}
break;
case ITEM_EFFECT_INSTANT::SUMMON_PET:
{
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPet *pPet = pItem->GetPetStruct();
if( !pItem->IsPetCage() || !pItem->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_CONTAIN_PET ) )
{
nRet = RESULT_NOT_ACTABLE;
break;
}
StructPlayer *pCastPlayer = static_cast< StructPlayer * >( pCaster );
// 테이밍 되어있는데 펫 구조체가 없을 경우 새로 할당
if( !pPet )
{
// 자동 테이밍 처리 가능 아이템 체크(GetSummonCode를 펫 우리의 경우 자동 테이밍 종류 펫으로 사용)
if( !pItem->GetSummonCode() )
{
nRet = RESULT_NOT_ACTABLE;
break;
}
pPet = AllocNewPet( pCastPlayer, pItem, pItem->GetSummonCode() );
// 잘못된 펫 코드일 가능성 높음
if( !pPet )
{
assert( 0 );
nRet = RESULT_NOT_ACTABLE;
break;
}
// 클라한테 추가 펫 정보 송신
pCastPlayer->AddPet( pPet, false );
// DB에 신규 펫 정보 추가, 펫 우리 아이템 업데이트
pPet->DBQuery( new DB_InsertPet( pPet ) );
pItem->DBQuery( new DB_UpdateItem( pItem ) );
}
if( !pCastPlayer->IsInWorld() || pCastPlayer->IsDead() || pCastPlayer->IsUsingStorage() ||
pCastPlayer->IsBoothOpen() || pCastPlayer->IsBoothWatching() || pCastPlayer->IsTrading() ||
pCastPlayer->IsUsingSkill() || pCastPlayer->IsRiding() || pCastPlayer->HasRidingState() )
{
nRet = RESULT_NOT_ACTABLE;
break;
}
if( pPet->IsNameChanged() )
{
// 이미 소환된 펫이 있으면 역소환
if( pCastPlayer->GetSummonedPet() )
{
// 사용된 아이템의 펫과 같다면 역소환만 함
bool bUnsummonOnly = false;
if( pCastPlayer->GetSummonedPet() == pItem->GetPetStruct() )
bUnsummonOnly = true;
// 역소환
pCastPlayer->UnSummonPet();
if( bUnsummonOnly )
break;
}
// 이름 변경 불가면 바로 소환
pCastPlayer->SummonPet( pItem->GetPetStruct() );
}
else
{
// 이름을 변경해야 하면 이름 변경 메시지 송신
TS_SC_SHOW_SET_PET_NAME msg;
msg.handle = pPet->GetHandle();
PendMessage( pCastPlayer, &msg );
}
}
break;
case ITEM_EFFECT_INSTANT::ADD_CASH: // 인포인트 아이템, 캐시 증가
{
if( pItem->GetItemCode() != ItemBase::ITEM_CODE_INGAME_POINT &&
pItem->GetItemCode() != ItemBase::ITEM_CODE_INGAME_POINT2 &&
pItem->GetItemCode() != ItemBase::ITEM_CODE_INGAME_POINT3 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer *pPlayer = static_cast< StructPlayer * >( this );
pPlayer->DBQuery( new DB_InsertCash( pPlayer, pPlayer->GetAccountName(), pPlayer->GetAccountID(), pItem->GetItemCode(), pItem->GetItemUID() ) );
}
break;
case ITEM_EFFECT_INSTANT::HAIR_DYEING:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
// If the item's additional effect includes a hair style change effect, do not perform any checks (the necessary checks are already handled by the hair style change effect itself).
// If the item's additional effect does not include a hair style change effect, then attempting to change to the same color should fail
int nIdx = 0;
const ItemBaseServer & itemBase = pItem->GetItemBase();
for( nIdx = 0 ; nIdx < ItemBase::MAX_OPTION_NUMBER ; ++nIdx )
{
if( itemBase.nOptType[ nIdx ] != ITEM_EFFECT_INSTANT::SET_HAIR_STYLE )
continue;
break;
}
// If the item does not include a hair style change effect
if( nIdx >= ItemBase::MAX_OPTION_NUMBER )
{
// Fail if trying to change to the same color as the current one
if( pPlayer->GetHairColorIndex() == var1 && pPlayer->GetHairColorRGB() == var2 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
}
pPlayer->SetHairColorIndex( var1 );
pPlayer->SetHairColorRGB( var2 );
// Broadcast the hair style change packet and save it to the DB
BroadcastHairInfo( pPlayer );
pPlayer->DBQuery( new DB_UpdateCharacterHair( pPlayer ) );
}
break;
case ITEM_EFFECT_INSTANT::SET_HAIR_STYLE:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
// 아이템의 추가 성능에 염색 성능이 포함되어 있을 경우에는 같은 스타일/색상으로 변경하려는 거면 실패
// 아이템의 추가 성능에 염색 성능이 포함되어 있지 않은 경우에는 같은 스타일로 변경하려는 거면 실패
int nIdx = ItemBase::MAX_OPTION_NUMBER - 1;
const ItemBaseServer & itemBase = pItem->GetItemBase();
for( ; nIdx >= 0 ; --nIdx )
{
if( itemBase.nOptType[ nIdx ] != ITEM_EFFECT_INSTANT::HAIR_DYEING )
continue;
if( itemBase.fOptVar1[ nIdx ] == pPlayer->GetHairColorIndex() && itemBase.fOptVar2[ nIdx ] == pPlayer->GetHairColorRGB() &&
var1 == pPlayer->GetBaseModelId( 0 ) )
{
nRet = RESULT_ACCESS_DENIED;
}
break;
}
// 윗 단계의 체크 중에 오류 코드가 세팅된 경우 처리 중지(오류 코드가 세팅되지 않았다면 nRet가 초기값인 RESULT_SUCCESS인 상태가 됨)
if( nRet != RESULT_SUCCESS )
break;
// 염색 성능이 없는 헤어 스타일 변경 아이템일 경우 현재 스타일로 변경하려는 건지 체크
if( nIdx < 0 && var1 == pPlayer->GetBaseModelId( 0 ) )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
pPlayer->SetBaseModelId( 0, var1 );
// 염색 성능이 없는 헤어 스타일 변경 아이템일 경우에만 방송 및 DB 저장(염색 성능이 있는 아이템이면 염색 성능 처리 후에 저장 및 방송됨)
if( nIdx < 0 )
{
BroadcastHairInfo( pPlayer );
pPlayer->DBQuery( new DB_UpdateCharacterHair( pPlayer ) );
}
}
break;
case ITEM_EFFECT_INSTANT::SET_SKIN_COLOR:
{
if( !IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
StructPlayer * pPlayer = static_cast< StructPlayer * >( this );
// 같은 색상으로 변경하려는 경우 실패
if( pPlayer->GetSkinColor() == var1 )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
// 색깔 바꾸고 방송하고 디비 저장.
pPlayer->SetSkinColor( var1 );
BroadcastSkinInfo( pPlayer );
pPlayer->DBQuery( new DB_UpdateCharacterSkin( pPlayer ) );
}
break;
case ITEM_EFFECT_INSTANT::CAST_WORLD_STATE:
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if ( !static_cast< StructPlayer* >( pCaster )->PendWorldState( (StructState::StateCode)pItem->GetStateCode(), pItem->GetStateLevel(), pItem->GetStateTime() * 100, var1 * 100, pItem->GetItemCode() ) )
{
nRet = RESULT_COOL_TIME;
}
break;
case ITEM_EFFECT_INSTANT::CAST_PARTY_STATE:
{
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if ( !static_cast< StructPlayer* >( pCaster )->PendPartyState( (StructState::StateCode)pItem->GetStateCode(), pItem->GetStateLevel(), pItem->GetStateTime() * 100, var1 * 100, pItem->GetItemCode() ) )
{
nRet = RESULT_COOL_TIME;
}
break;
}
case ITEM_EFFECT_INSTANT::CAST_GUILD_STATE:
{
if( !pCaster->IsPlayer() )
{
nRet = RESULT_ACCESS_DENIED;
break;
}
if ( !static_cast< StructPlayer* >( pCaster )->PendGuildState( (StructState::StateCode)pItem->GetStateCode(), pItem->GetStateLevel(), pItem->GetStateTime() * 100, var1 * 100, pItem->GetItemCode() ) )
{
nRet = RESULT_COOL_TIME;
}
break;
}
default: break;
}
return nRet;
}
bool StructCreature::TurnOnAura( struct StructSkill * pSkill )
{
int nToggleGroup = pSkill->GetSkillBase()->GetToggleGroup();
if( nToggleGroup > SkillBase::MAX_TOGGLE_GROUP || nToggleGroup < 0 )
{
assert( 0 );
return false;
}
TS_SC_AURA msg;
bool bExistSameGroup = false;
std::vector< std::pair<StructSkill *, int> >::iterator it;
for( it = m_vAura.begin(); it != m_vAura.end(); ++it )
{
if( (*it).first->GetSkillBase()->GetToggleGroup() != 0 && (*it).first->GetSkillBase()->GetToggleGroup() == nToggleGroup )
{
msg.caster = GetHandle();
msg.skill_id = (*it).first->GetSkillId();
msg.status = false;
if( IsPlayer() )
{
PendMessage( this, &msg );
}
else if( IsSummon() )
{
PendMessage( static_cast< StructSummon * >( this )->GetMaster(), &msg );
}
(*it) = std::pair<StructSkill *,int>( pSkill, pSkill->GetRequestedSkillLevel() );
bExistSameGroup = true;
break;
}
}
if( !bExistSameGroup )
m_vAura.push_back( std::pair<StructSkill *,int>( pSkill, pSkill->GetRequestedSkillLevel() ) );
msg.caster = GetHandle();
msg.skill_id = pSkill->GetSkillId();
msg.status = true;
if( IsPlayer() )
{
PendMessage( this, &msg );
}
else if( IsSummon() )
{
PendMessage( static_cast< StructSummon * >( this )->GetMaster(), &msg );
}
return true;
}
bool StructCreature::IsActiveAura( struct StructSkill * pSkill )
{
std::vector< std::pair<StructSkill *, int> >::iterator it;
for( it = m_vAura.begin(); it != m_vAura.end(); ++it )
{
if( (*it).first == pSkill )
{
return true;
}
}
return false;
}
bool StructCreature::TurnOffAura( struct StructSkill * pSkill )
{
std::vector< std::pair<StructSkill *, int> >::iterator it;
for( it = m_vAura.begin(); it != m_vAura.end(); ++it )
{
if( (*it).first == pSkill )
{
TS_SC_AURA msg;
msg.caster = GetHandle();
msg.skill_id = (*it).first->GetSkillId();
msg.status = false;
if( IsPlayer() )
{
PendMessage( this, &msg );
}
else if( IsSummon() )
{
PendMessage( static_cast< StructSummon * >( this )->GetMaster(), &msg );
}
vector_fast_erase( &m_vAura, it );
return true;
}
}
return false;
}
void StructCreature::ToggleAura( struct StructSkill * pSkill )
{
if( !TurnOffAura( pSkill ) )
TurnOnAura( pSkill );
}
void StructCreature::onDead( StructCreature *pFrom, bool decreaseEXPOnDead )
{
// 캐스팅 취소
if( IsUsingSkill() )
{
CancelSkill();
}
// 죽었으니 이동 정지
if( IsMoving() )
{
ArPosition pos = GetCurrentPosition( m_nDeadTime );
ArcadiaServer::Instance().SetMove( this, pos, pos, 0, true, GetArTime() );
if( IsPlayer() && static_cast< StructPlayer * >( this )->GetRideObject() )
{
ArcadiaServer::Instance().SetMove( static_cast< StructPlayer * >( this )->GetRideObject(), pos, pos, 0, true, GetArTime() );
}
}
if( IsAttacking() )
{
EndAttack();
}
// 오라 제거~
RemoveAllHate();
removeStateByDead();
//AziaMafia KeepBuff & fix
//RemoveAllAura();
// 경험치 감소 여부에 관계없이 마지막에 감소되었던 경험치를 초기화하여 의도하지 않은 동작을 막는다.
// 만약 경험치 감소가 일어난다면 이후에 새로운 값이 설정될 것이며 경험치 감소가 일어나지 않는다하더라도 최종적으로는 감소된 경험치가 0으로 설정된다.
SetLastDecreasedEXP( 0 );
}
void StructCreature::RemoveAllHate()
{
// { Hate 초기화
std::vector< AR_HANDLE >::iterator it;
std::vector< AR_HANDLE > vEnemyList = m_vEnemyList;
for( it = vEnemyList.begin(); it != vEnemyList.end(); ++it )
{
StructCreature::iterator itMonster = StructCreature::get( *it );
if( (*itMonster) && (*itMonster)->IsMonster() )
{
StructMonster * pMonster = static_cast< StructMonster * >( *itMonster );
pMonster->RemoveHate( GetHandle(), pMonster->GetHate( GetHandle() ) );
}
}
{
THREAD_SYNCHRONIZE( m_csEnemy );
m_vEnemyList.clear();
}
// }
}
bool StructCreature::IsEnemy( StructCreature* pTarget, bool bIncludeHiding )
{
if( !pTarget )
return false;
if( !pTarget->IsInWorld() )
return false;
// 나 자신이 무적이라고 해서 적을 적이라 부르지 못하는 건 아님 2012-05-17 Synastry
//if( IsInvincible() || !isMortal() )
// return false;
if( pTarget->IsInvincible() || !pTarget->isMortal() )
return false;
if( !bIncludeHiding && !IsVisible( pTarget ) )
return false;
if( IsAlly( pTarget ) )
return false;
if( pTarget->IsPet() )
return false;
return true;
}
bool StructCreature::IsAlly( StructCreature* pTarget )
{
return false;
}
const c_fixed10 StructCreature::GetAttackPointRightWithoutWeapon( Elemental::Type type, bool bPhysical, bool bBad ) const
{
if( bPhysical )
{
if( bBad )
return GetAttackPointRightWithoutWeapon() * m_BadPhysicalElementalSkillStateMod[type].fPhysicalDamage;
return GetAttackPointRightWithoutWeapon() * m_GoodPhysicalElementalSkillStateMod[type].fPhysicalDamage;
}
if( bBad )
return GetAttackPointRightWithoutWeapon() * m_BadMagicalElementalSkillStateMod[type].fPhysicalDamage;
return GetAttackPointRightWithoutWeapon() * m_GoodMagicalElementalSkillStateMod[type].fPhysicalDamage;
}
int StructCreature::GetAttackPointRight( Elemental::Type type, bool bPhysical, bool bBad )
{
if( bPhysical )
{
if( bBad )
return GetAttackPointRight() * m_BadPhysicalElementalSkillStateMod[type].fPhysicalDamage;
return GetAttackPointRight() * m_GoodPhysicalElementalSkillStateMod[type].fPhysicalDamage;
}
if( bBad )
return GetAttackPointRight() * m_BadMagicalElementalSkillStateMod[type].fPhysicalDamage;
return GetAttackPointRight() * m_GoodMagicalElementalSkillStateMod[type].fPhysicalDamage;
}
int StructCreature::GetMagicPoint( Elemental::Type type, bool bPhysical, bool bBad )
{
if( bPhysical )
{
if( bBad )
return m_Attribute.fMagicPoint * m_BadPhysicalElementalSkillStateMod[type].fMagicalDamage;
return m_Attribute.fMagicPoint * m_GoodPhysicalElementalSkillStateMod[type].fMagicalDamage;
}
if( bBad )
return m_Attribute.fMagicPoint * m_BadMagicalElementalSkillStateMod[type].fMagicalDamage;
return m_Attribute.fMagicPoint * m_GoodMagicalElementalSkillStateMod[type].fMagicalDamage;
}
float StructCreature::GetMagicalHateMod( Elemental::Type type, bool bPhysical, bool bBad )
{
if( bPhysical )
{
if( bBad )
return m_BadPhysicalElementalSkillStateMod[type].fHate;
return m_GoodPhysicalElementalSkillStateMod[type].fHate;
}
if( bBad )
return m_BadMagicalElementalSkillStateMod[type].fHate;
return m_GoodMagicalElementalSkillStateMod[type].fHate;
}
std::pair< float, int> StructCreature::GetHateMod( int nHateModType, bool bIsHarmful )
{
float fAmpValue = 1.0f;
int nIncValue = 0;
for( std::vector< HateModifier >::iterator it = m_vHateMod.begin(); it != m_vHateMod.end(); ++it )
{
bool bApply = false;
if( ( bIsHarmful && (*it).bIsApplyToHarmful ) || ( !bIsHarmful && (*it).bIsApplyToHelpful ) )
{
if( ( nHateModType == 1 && (*it).bIsApplyToPhysicalSkill )
|| ( nHateModType == 2 && (*it).bIsApplyToMagicalSkill )
|| ( nHateModType == 3 && (*it).bIsApplyToPhysicalAttack ) )
{
bApply = true;
}
}
if( bApply )
{
fAmpValue += (*it).fAmpValue;
nIncValue += (*it).nIncValue;
}
}
return std::pair< float, int >( fAmpValue, nIncValue );
}
unsigned char StructCreature::GetRealMoveSpeed() const
{
int RealMoveSpeed = GetMoveSpeed()/7;
if( RealMoveSpeed > UCHAR_MAX )
RealMoveSpeed = UCHAR_MAX;
return RealMoveSpeed;
}
unsigned char StructCreature::GetRealRidingSpeed() const
{
int RealRidingSpeed = GetRidingSpeed()/7;
if( RealRidingSpeed > UCHAR_MAX )
RealRidingSpeed = UCHAR_MAX;
return RealRidingSpeed;
}
float StructCreature::GetManaCostRatio( Elemental::Type type, bool bPhysical, bool bBad )
{
if( bPhysical )
{
if( bBad )
return m_BadPhysicalElementalSkillStateMod[type].fManaCostRatio;
return m_GoodPhysicalElementalSkillStateMod[type].fManaCostRatio;
}
if( bBad )
return m_BadMagicalElementalSkillStateMod[type].fManaCostRatio;
return m_GoodMagicalElementalSkillStateMod[type].fManaCostRatio;
}
float StructCreature::GetCastingMod( Elemental::Type type, bool bPhysical, bool bBad, AR_TIME nOriginalCoolTime )
{
if( bPhysical )
{
if( bBad )
{
if( m_BadPhysicalElementalSkillStateMod[type].nCastingSpeedApplyTime >= nOriginalCoolTime )
{
return m_BadPhysicalElementalSkillStateMod[type].fCastingSpeed;
}
else
{
return 1.0f;
}
}
if( m_GoodPhysicalElementalSkillStateMod[type].nCastingSpeedApplyTime >= nOriginalCoolTime )
{
return m_GoodPhysicalElementalSkillStateMod[type].fCastingSpeed;
}
else
{
return 1.0f;
}
}
if( bBad )
{
if( m_BadMagicalElementalSkillStateMod[type].nCastingSpeedApplyTime >= nOriginalCoolTime )
{
return m_BadMagicalElementalSkillStateMod[type].fCastingSpeed;
}
else
{
return 1.0f;
}
}
if( m_GoodMagicalElementalSkillStateMod[type].nCastingSpeedApplyTime >= nOriginalCoolTime )
{
return m_GoodMagicalElementalSkillStateMod[type].fCastingSpeed;
}
return 1.0f;
}
float StructCreature::GetCoolTimeMod( Elemental::Type type, bool bPhysical, bool bBad )
{
if( bPhysical )
{
if( bBad )
return m_BadPhysicalElementalSkillStateMod[type].fCooltime;
return m_GoodPhysicalElementalSkillStateMod[type].fCooltime;
}
if( bBad )
return m_BadMagicalElementalSkillStateMod[type].fCooltime;
return m_GoodMagicalElementalSkillStateMod[type].fCooltime;
}
bool StructCreature::SetPendingMove( const std::vector< ArPosition > & newPos, unsigned char speed )
{
if( HasPendingMove() )
{
return false;
}
m_PendingMovePos = newPos;
m_nPendingMoveSpeed = speed;
m_StatusFlag.On( STATUS_MOVE_PENDED );
return true;
}
void StructCreature::AddHateToEnemyList( AR_HANDLE handle, int pt )
{
std::vector< AR_HANDLE >::iterator it;
std::vector< AR_HANDLE > vInvalidEnemy;
std::vector< AR_HANDLE > vEnemy;
{
THREAD_SYNCHRONIZE( m_csEnemy );
for( it = m_vEnemyList.begin(); it != m_vEnemyList.end(); ++it )
{
StructCreature::iterator itEnemy = StructCreature::get( (*it) );
if( !(*itEnemy) || !(*itEnemy)->IsMonster() )
{
vInvalidEnemy.push_back( (*it) );
continue;
}
if( (*itEnemy)->GetPos().GetDistance( GetPos() ) > GameRule::VISIBLE_RANGE )
continue;
else if( static_cast< StructMonster * >(*itEnemy)->GetStatus() == StructMonster::STATUS_NORMAL )
continue;
vEnemy.push_back( *it );
}
}
for( it = vEnemy.begin(); it != vEnemy.end(); ++it )
{
StructCreature::iterator itEnemy = StructCreature::get( (*it) );
if( (*itEnemy) && (*itEnemy)->IsMonster() )
static_cast< StructMonster *>( (*itEnemy) )->AddHate( handle, m_fHateRatio * pt );
}
for( it = vInvalidEnemy.begin(); it != vInvalidEnemy.end(); ++it )
{
RemoveFromEnemyList( (*it) );
}
}
bool StructCreature::AddToEnemyList( AR_HANDLE handle )
{
std::vector< AR_HANDLE >::iterator it;
THREAD_SYNCHRONIZE( m_csEnemy );
for( it = m_vEnemyList.begin(); it != m_vEnemyList.end(); ++it )
{
if( (*it) == handle )
return false;
}
m_vEnemyList.push_back( handle );
return true;
}
bool StructCreature::RemoveFromEnemyList( AR_HANDLE handle )
{
std::vector< AR_HANDLE >::iterator it;
THREAD_SYNCHRONIZE( m_csEnemy );
for( it = m_vEnemyList.begin(); it != m_vEnemyList.end(); ++it )
{
if( (*it) == handle )
{
vector_fast_erase( &m_vEnemyList, it );
return true;
}
}
return false;
}
void StructCreature::AddEnergy( int nEnergy )
{
if( m_nEnergy >= m_nMaxEnergy )
return;
AR_TIME nKeepTime = GetArTime() + m_nEnergyUpkeepTime;
for( int i = 0; i < nEnergy; ++i )
{
int pos = m_nEnergy + m_nEnergyStartPos + i;
if( pos >= GameRule::ENERGY_MAX )
{
pos -= GameRule::ENERGY_MAX;
}
m_arEnergy[pos] = nKeepTime;
++m_nEnergy;
if( m_nEnergy >= m_nMaxEnergy )
break;
}
onEnergyChange();
}
void StructCreature::RemoveEnergy( int nEnergy )
{
// 일정 확률로 기공을 소모하지 않는다.
if( XRandom( 0, 99 ) < GetEnergyUnconsumptionRate() )
return;
int nRemoveCnt = std::min( nEnergy, m_nEnergy );
if( nRemoveCnt < 1 )
return;
m_nEnergy -= nRemoveCnt;
m_nEnergyStartPos += nRemoveCnt;
if( m_nEnergyStartPos >= GameRule::ENERGY_MAX )
{
m_nEnergyStartPos -= GameRule::ENERGY_MAX;
}
onEnergyChange();
}
ItemBase::ItemClass StructCreature::GetArmorClass()
{
StructItem * pArmor = GetWearedItem( ItemBase::WEAR_ARMOR );
if( !pArmor )
return ItemBase::CLASS_ETC;
return pArmor->GetItemClass();
}
ItemBase::ItemClass StructCreature::GetWeaponClass()
{
StructItem * pWeapon = GetWearedItem( ItemBase::WEAR_WEAPON );
if( !pWeapon )
return ItemBase::CLASS_ETC;
if( IsUsingDoubleWeapon() )
{
StructItem * pLeftWeapon = GetWearedItem( ItemBase::WEAR_LEFTHAND );
if( pWeapon->GetItemClass() == ItemBase::CLASS_ONEHAND_SWORD && pLeftWeapon->GetItemClass() == ItemBase::CLASS_ONEHAND_SWORD )
{
return ItemBase::CLASS_DOUBLE_SWORD;
}
else if( pWeapon->GetItemClass() == ItemBase::CLASS_DAGGER && pLeftWeapon->GetItemClass() == ItemBase::CLASS_DAGGER )
{
return ItemBase::CLASS_DOUBLE_DAGGER;
}
else if( pWeapon->GetItemClass() == ItemBase::CLASS_ONEHAND_AXE && pLeftWeapon->GetItemClass() == ItemBase::CLASS_ONEHAND_AXE )
{
return ItemBase::CLASS_DOUBLE_AXE;
}
<<<<<<< HEAD
else if (pWeapon->GetItemClass() == ItemBase::CLASS_CROSSBOW && pLeftWeapon->GetItemClass() == ItemBase::CLASS_CROSSBOW) // Double Crossbow
{
return ItemBase::CLASS_DOUBLE_CROSSBOW;
=======
else if (pWeapon->GetItemClass() == ItemBase::CLASS_CROSSBOW && pLeftWeapon->GetItemClass() == ItemBase::CLASS_CROSSBOW) // ZONE DOUBLE CROSSBOW
{
return ItemBase::CLASS_DOUBLE_CROSSBOW; // From ZONE source; dual crossbows
>>>>>>> a34328224e914a577b99c79ec6e8ffbcea554a05
}
}
return pWeapon->GetItemClass();
}
unsigned short StructCreature::putoffItem( StructCreature * pCreature, ItemBase::ItemWearType pos )
{
return pCreature->putoffItem( pos );
}
int StructCreature::onDamage( StructCreature * pFrom, Elemental::Type elementalType, DamageType damageType, int nDamage, bool bCritical )
{
// 일반 공격의 하보크 처리는 Attack 함수에서 처리(블럭, 퍼펙트 블럭, 미스 판정 여부를 알아야 함)
// 불사신 효과는 데미지는 받지만 죽지 않는 상태이므로 패스데미지/데미지 반사 등의 처리가 모두 된 이후에 실제 데미지가 들어가지 않도록 한다.
int nMinHP = m_fMinHP * GetMaxHP();
if( nMinHP < m_nMinHP )
nMinHP = m_nMinHP;
if( m_nHP <= nMinHP )
nDamage = 0;
else if( m_nHP - nDamage < nMinHP )
nDamage = m_nHP - nMinHP;
return nDamage;
}
void StructCreature::EnumActiveSkill( SKILL_POINTER_FUNCTOR & _fo )
{
std::vector< StructSkill * >::const_iterator it;
for( it = m_vActiveSkillList.begin(); it != m_vActiveSkillList.end(); ++it )
{
_fo.onSkill( (*it) );
}
}
void StructCreature::EnumPassiveSkill( SKILL_POINTER_FUNCTOR & _fo )
{
std::vector< StructSkill * >::const_iterator it;
for( it = m_vPassiveSkillList.begin(); it != m_vPassiveSkillList.end(); ++it )
{
_fo.onSkill( (*it) );
}
}
void StructCreature::EnumAddedSkill( ADDED_SKILL_FUNCTOR & _fo ) const
{
std::vector< std::pair< int, int > >::const_iterator it;
for( it = m_vAddedSkillBySkillId.begin(); it != m_vAddedSkillBySkillId.end(); it++ )
{
_fo.onSkill( (*it).first, false, (*it).second );
}
for( it = m_vAddedSkillBySkillType.begin(); it != m_vAddedSkillBySkillType.end(); it++ )
{
_fo.onSkill( (*it).first, true, (*it).second );
}
}
void StructCreature::removeStateByDead()
{
ClearRemovedStateByDead();
std::vector< StructState > removedStates;
RemoveStateIf( StateFlagChecker( StateInfo::ERASE_ON_DEAD ), &removedStates, true );
// m_vStateListRemovedByDeath 변수는 StructCreature에 선언되어있지만 StructPlayer 밖에 사용할 수가 없다.
if( IsPlayer() )
{
for ( std::vector< StructState >::iterator it = removedStates.begin(); it != removedStates.end(); ++it )
{
// 크루 아이템을 통한 부활시 없어진 지속효과 복구를 위해 임시 보관
// 해당 리스트에서 StructState를 다시 꺼내서 걸 경우 시작 시간, 끝 시간을 조정해야 함
if( !it->IsHarmful() )
{
(*it).SaveByDead( this );
m_vStateListRemovedByDeath.push_back( *it );
}
else
{
(*it).Expire( this );
}
}
}
}
void StructCreature::removeStateWithFlag( const int & nFlag )
{
RemoveStateIf( StateFlagChecker( nFlag ) );
}
void StructCreature::processPendingMove()
{
// { pending move
do
{
if( HasPendingMove() && GetMovableTime() < GetArTime() )
{
m_StatusFlag.Off( STATUS_MOVE_PENDED );
if( IsActable() && IsMovable() )
{
AR_TIME tm = GetArTime();
ArPosition pos = GetCurrentPosition( tm );
ArcadiaServer::Instance().SetMultipleMove( this, pos, m_PendingMovePos, m_nPendingMoveSpeed, true, tm );
if( IsPlayer() && static_cast< StructPlayer * >( this )->IsRiding() )
{
ArcadiaServer::Instance().SetMultipleMove( static_cast< StructPlayer * >( this )->GetRideObject(), pos, m_PendingMovePos, m_nPendingMoveSpeed, true, tm );
}
if( IsSummon() )
{
ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_NORMAL );
}
}
}
} while( false );
// } pending move
}
void StructCreature::procMoveSpeedChangement()
{
if( !IsMoving() )
return;
ArPosition pos = GetCurrentPosition( GetArTime() );
// 이동 불가가 되었다면 현재 위치에 정지
if( !IsMovable() )
{
ArcadiaServer::Instance().SetMove( this, pos, pos, 0, true, GetArTime() );
}
// 이동 중이었고 이동 가능 상태라면 현재 이동 속도와 스텟상의 이동 속도가 다를 경우 다시 이동 시킴
else if( GetSpeed() != GetRealMoveSpeed() )
{
const std::vector< ArMoveVector::MOVE_INFO > & vMoveVector( GetTargetPosList() );
std::vector< ArPosition > vMovePos;
for( std::vector< ArMoveVector::MOVE_INFO >::const_iterator it = vMoveVector.begin() ; it != vMoveVector.end() ; ++it )
vMovePos.push_back( (*it).end );
ArcadiaServer::Instance().SetMultipleMove( this, pos, vMovePos, GetRealMoveSpeed(), true, GetArTime() );
}
}
void StructCreature::PrepareRemoveExhaustiveSkillStateMod( bool bPhysical, bool bHarmful, int nElementalType, AR_TIME nOriginalCastingDelay )
{
if( bPhysical )
{
if( bHarmful )
{
m_BadPhysicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCodeForDelete = m_BadPhysicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCode;
}
else
{
m_GoodPhysicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCodeForDelete = m_GoodPhysicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCode;
}
}
else
{
if( bHarmful )
{
m_BadMagicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCodeForDelete = m_BadMagicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCode;
}
else
{
m_GoodMagicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCodeForDelete = m_GoodMagicalElementalSkillStateMod[ nElementalType ].vExhaustiveStateCode;
}
}
}
void StructCreature::RemoveExhaustiveSkillStateMod( bool bPhysical, bool bHarmful, int nElementalType, AR_TIME nOriginalCastingDelay )
{
if( bPhysical )
{
if( bHarmful )
{
removeExhaustiveSkillStateMod( &m_BadPhysicalElementalSkillStateMod[ nElementalType ], nOriginalCastingDelay );
}
else
{
removeExhaustiveSkillStateMod( &m_GoodPhysicalElementalSkillStateMod[ nElementalType ], nOriginalCastingDelay );
}
}
else
{
if( bHarmful )
{
removeExhaustiveSkillStateMod( &m_BadMagicalElementalSkillStateMod[ nElementalType ], nOriginalCastingDelay );
}
else
{
removeExhaustiveSkillStateMod( &m_GoodMagicalElementalSkillStateMod[ nElementalType ], nOriginalCastingDelay );
}
}
CalculateStat();
}
void StructCreature::removeExhaustiveSkillStateMod( ElementalSkillStateMod *pSkillStateMod, AR_TIME nOriginalCastingDelay )
{
std::vector< StructState::StateCode > vEraseList = pSkillStateMod->vExhaustiveStateCodeForDelete;
std::vector< StructState::StateCode >::iterator it;
for( it = vEraseList.begin() ; it != vEraseList.end() ; ++it )
{
StructState *pState = GetState( *it );
if( !pState )
{
continue;
}
if( pState->GetEffectType() == StructState::EF_ADD_PARAMETER_ON_SKILL && pState->GetValue( 11 ) && pState->GetValue( 11 ) * 100 < nOriginalCastingDelay )
{
continue;
}
RemoveState( *it, pState->GetLevel() );
}
}
void StructCreature::RemoveGoodState( int state_level )
{
std::vector< StructState::StateCode > vEraseStateList;
STATE_ITERATOR it;
for( it = m_vStateList.begin(); it != m_vStateList.end(); ++it )
{
if( (*it).IsHarmful() ) continue;
if( !( (*it).GetTimeType() & StateInfo::ERASE_ON_DEAD ) ) continue;
vEraseStateList.push_back( (*it).GetCode() );
}
for( std::vector< StructState::StateCode >::iterator itState = vEraseStateList.begin(); itState != vEraseStateList.end(); ++itState )
{
RemoveState( (*itState), state_level );
}
}
int StructCreature::damage( StructCreature * pFrom, int nDamage, bool decreaseEXPOnDead )
{
if( IsDead() ) return 0;
// 은신 해제
if( IsHiding() )
{
RemoveState( StructState::HIDE, GameRule::MAX_STATE_LEVEL );
RemoveState( StructState::TRACE_OF_FUGITIVE, GameRule::MAX_STATE_LEVEL );
}
m_nHP = ( m_nHP > nDamage ? m_nHP - nDamage : 0 );
if( IsDead() )
{
// 죽었다면 죽은 시간 세팅
SetDeadTime( GetArTime() );
onDead( pFrom, decreaseEXPOnDead );
}
return nDamage;
}
void StructCreature::ProcessAddHPMPOnCritical()
{
if( m_vAddHPMPOnCritical.empty() )
return;
for( std::vector< AddHPMPOnCriticalInfo >::iterator it = m_vAddHPMPOnCritical.begin() ; it != m_vAddHPMPOnCritical.end() ; ++it )
{
if( XRandom( 0, 99 ) < (*it).nActivationRate )
{
AddHP( (*it).nAddHP );
AddMP( (*it).nAddMP );
}
}
}
bool StructCreature::IsVisible( StructCreature *pTarget )
{
AR_TIME t = GetArTime();
ArPosition pos = GetCurrentPosition( t );
if( !pTarget->IsInvisible() && ( !pTarget->IsHiding() || pTarget->GetCurrentPosition( t ).GetDistance( pos ) < m_fDetectHideRange ) )
return true;
return false;
}
void StructCreature::onProcess( int nThreadIdx )
{
AR_TIME t = GetArTime();
{
ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) );
for( std::vector< StructState >::iterator it = m_vPendStateList.begin(); it != m_vPendStateList.end(); ++it )
{
AddState( (*it).GetCode(), (*it).GetCaster(), (*it).GetLevel(), t, t + (*it).GetEndTime() - (*it).GetStartTime(), (*it).IsAura(), (*it).GetStateValue(), (*it).GetStateStringValue(), (*it).IsByEvent() );
}
m_vPendStateList.clear();
// 지속효과 적용 (여기서 사망하실수도 있는지라 가장 늦게 적용해야함 -.-)
if( m_vStateList.size() && m_nLastStateProcTime + 100 < t )
{
procStateDamage( t );
procState( t );
m_nLastStateProcTime = t;
}
}
}
void StructCreature::onHPChange( int nPrevHP )
{
if( nPrevHP == 0 && GetHP() > 0 )
{
removeStateWithFlag( StateInfo::ERASE_ON_RESURRECT );
}
}
// 범용적인 부활 처리, 추후에 기존의 부활들도 이 함수를 통하도록 수정
bool StructCreature::Resurrect( const _CHARACTER_RESURRECTION_TYPE eResurrectType, const int nIncHP, const int nIncMP, const __int64 nRecoveryEXP, const bool bIsRestoreRemovedStateByDead )
{
if( !IsDead() || ( !IsSummon() && !IsPlayer() ) )
{
return false;
}
// 최소 1 회복
AddHP( std::max( nIncHP, 1 ) );
AddMP( nIncMP );
SetEXP( GetEXP() + nRecoveryEXP );
//AziaMafia KeepBuff & fix
if (bIsRestoreRemovedStateByDead) RestoreRemovedStateByDead();
else ClearRemovedStateByDead();
//RestoreRemovedStateByDead();
if( IsPlayer() )
{
StructPlayer *pPlayer = static_cast< StructPlayer * >( this );
pPlayer->Save( true );
if( pPlayer->IsCompeteDead() )
pPlayer->SetCompeteDead( false );
LOG::Log11N4S( LM_CHARACTER_RESURRECTION, pPlayer->GetAccountID(), pPlayer->GetSID(), eResurrectType, 0, 0, 0, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), nRecoveryEXP, pPlayer->GetEXP(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "" , LOG::STR_NTS );
}
else if( IsSummon() )
{
StructSummon * pSummon = static_cast< StructSummon * >( this );
StructItem *pCard = pSummon->GetParentCard();
if( pCard && pCard->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_SUMMON_DURABILITY ) )
{
pCard->SetCurrentEtherealDurability( pCard->GetCurrentEtherealDurability() + 1 );
if( pSummon->GetMaster() )
SendItemMessage( pSummon->GetMaster(), pCard );
pCard->SetInstanceFlagOff( ItemInstance::ITEM_FLAG_SUMMON_DURABILITY );
}
pSummon->DBQuery( new DB_UpdateSummon( pSummon ) );
StructPlayer *pPlayer = static_cast< StructPlayer * >(pSummon->GetMaster());
LOG::Log11N4S( LM_SUMMON_RESURRECTION, pPlayer->GetAccountID(), pPlayer->GetSID(), pSummon->GetSID(), 0, 0, 0, 0, 0, (nRecoveryEXP > 0), nRecoveryEXP, pSummon->GetEXP(), pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS );
}
BroadcastHPMPMsg( this, nIncHP, nIncMP );
return true;
}
bool StructCreature::ResurrectByState()
{
if (!IsDead() || (!IsSummon() && !IsPlayer()))
{
return false;
}
StructState* pResurrectState = NULL;
for (std::vector< StructState >::iterator it = m_vStateList.begin(); it != m_vStateList.end(); ++it)
{
if ((*it).GetEffectType() == StructState::EF_RESURRECTION)
{
if (!pResurrectState || pResurrectState->GetLevel() < (*it).GetLevel())
{
pResurrectState = &(*it);
}
}
}
if (!pResurrectState)
{
return false;
}
// resurrect~
int nIncHP = (pResurrectState->GetValue(0) + pResurrectState->GetValue(1) * pResurrectState->GetLevel()) * GetMaxHP();
int nIncMP = (pResurrectState->GetValue(2) + pResurrectState->GetValue(3) * pResurrectState->GetLevel()) * GetMaxMP();
__int64 nRecoveryEXP = GetLastDecreasedEXP() * (pResurrectState->GetValue(4) + pResurrectState->GetValue(5) * pResurrectState->GetLevel());
SetEXP(GetEXP() + nRecoveryEXP);
AddHP(nIncHP);
AddMP(nIncMP);
if (IsPlayer())
{
StructPlayer* pPlayer = static_cast<StructPlayer*>(this);
pPlayer->Save(true);
if (pPlayer->IsCompeteDead())
pPlayer->SetCompeteDead(false);
LOG::Log11N4S(LM_CHARACTER_RESURRECTION, pPlayer->GetAccountID(), pPlayer->GetSID(), CRT_STATE, 0, 0, 0, pPlayer->GetX(), pPlayer->GetY(), pPlayer->GetLayer(), nRecoveryEXP, pPlayer->GetEXP(),
pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS);
}
else if (IsSummon())
{
StructSummon* pSummon = static_cast<StructSummon*>(this);
StructItem* pCard = pSummon->GetParentCard();
if (pCard && pCard->GetInstanceFlag().IsOn(ItemInstance::ITEM_FLAG_SUMMON_DURABILITY))
{
pCard->SetCurrentEtherealDurability(pCard->GetCurrentEtherealDurability() + 1);
if (pSummon->GetMaster())
SendItemMessage(pSummon->GetMaster(), pCard);
pCard->SetInstanceFlagOff(ItemInstance::ITEM_FLAG_SUMMON_DURABILITY);
}
pSummon->DBQuery(new DB_UpdateSummon(pSummon));
StructPlayer* pPlayer = static_cast<StructPlayer*>(pSummon->GetMaster());
LOG::Log11N4S(LM_SUMMON_RESURRECTION, pPlayer->GetAccountID(), pPlayer->GetSID(), pSummon->GetSID(), 0, 0, 0, 0, 0, (nRecoveryEXP > 0), nRecoveryEXP, pSummon->GetEXP(), pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", LOG::STR_NTS, "", LOG::STR_NTS);
}
BroadcastHPMPMsg(this, nIncHP, nIncMP);
//AziaMafia KeepBuff & fix
ClearRemovedStateByDead();
return true;
}
/*
* StructCreature가 데미지를 받아 이를 반영하는 과정은 크게 4단계로 나뉜다.
*
*
* 1. 데미지 계산에 필요한 각종 정보들을 수집하는 단계
*
* 인자로 주어진 DamageMessage를 해석, 데미지를 주고 받는 StructCreature로 부터 데미지 계산에 필요한 수치들을 수집한다.
* 이 과정에 수행 순서에 의존하는 로직이 들어가면 부적절하다. 만약 필요하다면 DamageCalculator를 수정, 3번 단계에서
* 해당 로직을 수행하도록 해야 한다.
*
*
* 2. 데미지 계산에 영향을 줄 수 있는 외적 요인들을 반영하는 단계
*
* 크리티컬, 회피 시뮬레이션과 같이 순수 계산 과정에서는 예상하기 힘든, 데미지 계산에 영향을 줄 수 있는 외적 요인들을
* 반영하는 단계이다.
*
*
* 3. 실제 적용되는 데미지를 계산하는 단계
*
* 위에서 수집된 정보들과 시뮬레이션 결과를 기반으로 실제로 적용되는 데미지과 효과 적용 단계에 필요한 결과를 계산한다.
*
*
* 4. 데미지로 인한 효과를 적용하는 단계
*
* 계산의 결과 값을 실제로 StructCreature에 반영한다.
*
*
* 계산 과정을 굳이 이렇게 분리한 까닭은 아래와 같다.
*
* - 대부분의 코드가 계산 순서에 심하게 의존하고 있으며, 이로 인해 계산에 영향을 줄 수 있는 다양한 요인들이 서로 엉켜 있다.
* - 이러한 환경 하에서는 계산 과정 전반에 영향을 줄 수 있는 요인을 추가하기가 까다롭다.
* - 또한 외부 요인에 의해 계산 결과가 바뀔 수 있으므로 자동화된 테스트의 도입이 불가능에 가깝다.
*
* 이러한 이유로 순수한 계산 로직과 외적 요인들을 분리, 실제 계산에는 최소한의 복잡도만이 노출되도록 구조를 변경하였다.
*
*
* TODO:
*
* 현재는 외부에 노출되는 인터페이스를 바꾸지 않는 한도 내에서만 수정하였기에 구조적인 측면에서 일부 부적절한 면이 있다.
* 최종적으로는 데미지를 입히기를 원하는 호출부에서 DamageMessage를 직접 생성하여 이용하는 것이 적절할 것이다. (이게 바로
* 데미지 구조체에 Message라는 단어를 사용한 이유이다.)
*
* 이러면 호출부는 DamageMessage 내부에 직접 각종 연산의 (크리, 회피, 데미지 리듀스, 관통 등의 다양한 요소들) 적용 여부나
* 해당 데미지에만 적용되는 보너스 등의 고유한 정보들을 세팅하여 StructCreature에 질의를 날리고, 이러면 StructCreature는
* 이 값과 대상, 공격자의 파라메터들을 토대로 DamageCalculator를 생성하여 계산된 결과를 자신과 상대에게 반영하도록 하는
* 그림이 나올 것이다.
*
*/
StructCreature::_DAMAGE StructCreature::DealDamage( const DamageMessage& message )
{
DamageCalculator calculator( message );
CalculationResult result;
calculator.Calculate( result );
StructCreature* target = message.target;
result.finalDamage = target->onDamage( message.from, message.elementalType, message.oldDamageType, result.damage, result.simulation.critical );
target->damage( message.from, result.finalDamage );
ProcessPostDamageEffect( message, result );
return CreateDamageStruct( result );
}
// 데미지 계산에 필요한 각종 수치를 모아서 calculator에 반영하는 함수
// 계산 순서에 의존하거나 결과를 예상할 수 없는 로직은 가급적 여기에 넣지 말 것.
void StructCreature::ProvideAttackerInfo( const DamageMessage& message, DamageCalculator& calculator )
{
StructCreature* target = message.target;
calculator.expertiseAdvantage.amp = std::max(0.00f, (1.00f + m_Expert[target->GetCreatureGroup()].fDamage)) ;
if( message.damageType == DamageMessage::STATE_DAMAGE && message.elementalType >= 0 && message.elementalType < Elemental::TYPE_COUNT )
{
calculator.beforeDefence.amp += m_ElementalStateDamageAmplifier[message.elementalType];
calculator.beforeDefence.inc += m_ElementalStateDamageAdder[message.elementalType];
}
if( message.attackType == DamageMessage::ADDITIONAL_DAMAGE && IsUsingDoubleWeapon() )
{
if( message.damageType == DamageMessage::PHYSICAL_LEFT_HAND_ATTACK )
{
calculator.rule.amp *= ( 0.44f + m_nDoubleWeaponMasteryLevel * 0.02f );
}
else if ( message.damageType == DamageMessage::PHYSICAL_ATTACK )
{
calculator.rule.amp *= ( 0.90f + m_nDoubleWeaponMasteryLevel * 0.01f );
}
}
// 방어력/공격력/관통력 관련 정보
calculator.attackerLevel = GetLevel();
calculator.critical = GetCritical();
calculator.critPower = GetCriticalPower();
if( message.attackType == DamageMessage::MAGICAL_ATTACK )
{
calculator.attackPoint = GetMagicPoint();
calculator.penetration = m_Attribute.fMagicalPenetration;
calculator.penetrationRatio = m_Attribute.fMagicalPenetrationRatio * 0.01f;
calculator.defenceIgnore = m_Attribute.fMagicalDefIgnore;
calculator.defenceIgnoreRatio = m_Attribute.fPhysicalDefIgnoreRatio * 0.01f;
calculator.accuracy = GetMagicAccuracy();
}
else
{
calculator.attackPoint = GetAttackPointRight();
calculator.penetration = m_Attribute.fPhysicalPenetration;
calculator.penetrationRatio = m_Attribute.fPhysicalPenetrationRatio * 0.01f;
calculator.defenceIgnore = m_Attribute.fPhysicalDefIgnore;
calculator.defenceIgnoreRatio = m_Attribute.fPhysicalDefIgnoreRatio * 0.01f;
if( message.attackType == DamageMessage::PHYSICAL_LEFT_HAND_ATTACK )
{
calculator.accuracy = GetAccuracyLeft();
}
else
{
calculator.accuracy = GetAccuracyRight();
}
}
if ( IsSkillProp() )
{
calculator.flag &= ~DamageMessage::BLOCK;
calculator.damageType = DamageMessage::NAKED_DAMAGE;
}
else
{
calculator.damageType = message.damageType;
}
for ( std::vector< StructSkill* >::iterator it = message.from->m_vSkillRelatedToDamaging.begin();
it != message.from->m_vSkillRelatedToDamaging.end(); ++it )
{
ApplySkillToCalculation( message, calculator, *it );
}
}
void StructCreature::ProvideTargetInfo( const DamageMessage& message, DamageCalculator& calculator )
{
StructCreature* from = message.from;
calculator.expertisePenalty.amp = std::max(0.00f, (1.00f - m_Expert[from->GetCreatureGroup()].fAvoid) );
// TODO: 아래 수식을 실행하면 calculator.resist.amp는 무조건 1.0f로 나옴.
// 당장은 동작을 그대로 옮기나, 버그일 가능성이 크니 차후 수정
calculator.resist.amp -= GetElementalResist( message.elementalType ) / 300;
calculator.targetLevel = GetLevel();
calculator.manaShieldLimit = GetMP();
if( message.attackType == DamageMessage::MAGICAL_ATTACK )
{
calculator.defence = GetMagicDefence();
calculator.avoid = GetMagicAvoid();
calculator.isImmune = IsMagicalImmune();
calculator.manaShieldRatio = m_fMagicalDamageManaShieldAbsorbRatio;
}
else
{
calculator.defence = GetDefence();
calculator.blockDefence = calculator.defence + GetBlockDefence();
calculator.avoid = GetAvoid();
calculator.blockRate = GetBlockChance();
calculator.perfectBlockRate = m_Attribute.fPerfectBlock;
calculator.isImmune = IsPhysicalImmune();
calculator.manaShieldRatio = m_fPhysicalDamageManaShieldAbsorbRatio;
}
if ( !IsWearShield() && !IsSummon() )
{
calculator.flag &= ~DamageMessage::BLOCK;
}
if( ( IsPlayer() || IsSummon() ) && this != from )
{
if( from->IsPlayer() )
{
calculator.rule.amp *= GameRule::fPVPDamageRateForPlayer;
}
else if( from->IsSummon() )
{
calculator.rule.amp *= GameRule::fPVPDamageRateForSummon;
}
else if( from->IsMonster() )
{
StructMonster * pMonster = static_cast< StructMonster * >( from );
if( pMonster->GetMonsterType() >= MonsterBase::MONSTER_TYPE_HIGHEST_1_STAR )
{
if( this->IsPlayer() ) calculator.rule.amp *= GameRule::fEVPDamageRate;
if( this->IsSummon() ) calculator.rule.amp *= GameRule::fEVSDamageRate;
}
else
{
if( this->IsPlayer() ) calculator.rule.amp *= GameRule::fEVPBossDamageRate;
if( this->IsSummon() ) calculator.rule.amp *= GameRule::fEVSBossDamageRate;
}
}
}
if ( message.flag & DamageMessage::DAMAGE_REDUCE )
{
StructCreature* target = message.target;
calculator.statePenalty.inc -= CalculateDamageReduceSum( message, m_vDamageReduceValueInfo );
calculator.statePenalty.amp -= CalculateDamageReduceSum( message, m_vDamageReducePercentInfo );
}
// 플레이어 전용
// 앉은 상태에서 피격 시 모든 공격이 크리티컬로 터진다.
if( IsPlayer() )
{
StructPlayer *pPlayer = static_cast< StructPlayer * >( this );
if( pPlayer->GetBattleArenaID() )
calculator.isIgnoreLevelPenalty = true;
if( pPlayer->IsSitDown() )
calculator.critRateInc = 100;
}
}
const float StructCreature::CalculateDamageReduceSum( const DamageMessage& message, const std::vector< DamageReduceInfo >& reduceInfo )
{
std::vector< DamageReduceInfo >::const_iterator dit;
const int targetGroup = message.from->GetCreatureGroup();
float sum = 0.0f;
for( dit = reduceInfo.begin() ; dit != reduceInfo.end() ; ++dit )
{
if( dit->IsAppliableCreatureGroup( targetGroup ) && dit->ratio > XRandom() % 100 )
{
switch ( message.attackType )
{
case DamageMessage::MAGICAL_ATTACK:
sum += dit->magical_skill_reduce;
break;
case DamageMessage::PHYSICAL_ATTACK:
case DamageMessage::PHYSICAL_LEFT_HAND_ATTACK:
sum += dit->physical_reduce;
break;
case DamageMessage::PHYSICAL_SKILL_ATTACK:
sum += dit->physical_skill_reduce;
break;
default:
assert( false );
}
}
}
return sum;
}
void StructCreature::ApplySkillToCalculation( const DamageMessage& message, DamageCalculator& calculator, StructSkill* skill )
{
StructCreature* target = message.target;
StructCreature* from = message.from;
int effectType = skill->GetSkillBase()->GetEffectType();
switch ( effectType )
{
case SkillBase::EF_INC_DAMAGE_BY_TARGET_STATE:
case SkillBase::EF_AMP_DAMAGE_BY_TARGET_STATE:
{
bool isAndOp = ( skill->GetVar( 12 ) == 1 );
bool apply = isAndOp;
for ( int i = 0; i < 12; i++ )
{
StructState::StateCode id = (StructState::StateCode)(int)skill->GetVar( i );
if( id == 0 )
{
break;
}
if( target->GetState( id ) != NULL )
{
if( isAndOp == false )
{
apply = true;
break;
}
}
else if( isAndOp == true )
{
apply = false;
break;
}
}
if( apply == true )
{
c_fixed10 damage;
if( message.damageType == DamageMessage::DIRECT_DAMAGE )
{
switch( message.attackType )
{
case DamageMessage::PHYSICAL_ATTACK:
case DamageMessage::PHYSICAL_LEFT_HAND_ATTACK:
damage = skill->GetVar( 14 ) + skill->GetVar( 15 ) * skill->GetCurrentSkillLevel();
break;
case DamageMessage::PHYSICAL_SKILL_ATTACK:
damage = skill->GetVar( 16 ) + skill->GetVar( 17 ) * skill->GetCurrentSkillLevel();
break;
case DamageMessage::MAGICAL_ATTACK:
damage = skill->GetVar( 18 ) + skill->GetVar( 19 ) * skill->GetCurrentSkillLevel();
break;
}
}
if( effectType == SkillBase::EF_INC_DAMAGE_BY_TARGET_STATE )
{
calculator.afterDefence.inc += damage;
}
else
{
calculator.afterDefence.amp += damage;
}
}
break;
}
case SkillBase::EF_INC_DAMAGE_INC_CRIT_RATE_BY_TARGET_HP_RATIO:
case SkillBase::EF_AMP_DAMAGE_INC_CRIT_RATE_BY_TARGET_HP_RATIO:
{
c_fixed10 condition = skill->GetVar( 0 ) + skill->GetVar( 1 ) * skill->GetCurrentSkillLevel();
c_fixed10 HPPercentage = ( static_cast< c_fixed10 > ( target->GetHP() ) * 100 ) / target->GetMaxHP();
if( HPPercentage <= condition )
{
bool targetIsInProperGroup = false;
for ( int i = 11; i < 16; i++ )
{
int requiredGroup = skill->GetVar( i );
if( requiredGroup == CREATURE_ALL || requiredGroup == target->GetCreatureGroup() )
{
targetIsInProperGroup = true;
break;
}
else if( requiredGroup == CREATURE_NONE )
{
break;
}
}
if( targetIsInProperGroup == true && XRandom( 0, 99 ) < skill->GetVar( 10 ) )
{
c_fixed10 damage;
if( message.damageType == DamageMessage::DIRECT_DAMAGE )
{
switch( message.attackType )
{
case DamageMessage::PHYSICAL_ATTACK:
case DamageMessage::PHYSICAL_LEFT_HAND_ATTACK:
damage = skill->GetVar( 2 ) + skill->GetVar( 3 ) * skill->GetCurrentSkillLevel();
break;
case DamageMessage::PHYSICAL_SKILL_ATTACK:
damage = skill->GetVar( 4 ) + skill->GetVar( 5 ) * skill->GetCurrentSkillLevel();
break;
case DamageMessage::MAGICAL_ATTACK:
damage = skill->GetVar( 6 ) + skill->GetVar( 7 ) * skill->GetCurrentSkillLevel();
break;
}
}
if( effectType == SkillBase::EF_INC_DAMAGE_INC_CRIT_RATE_BY_TARGET_HP_RATIO )
{
calculator.afterDefence.inc += damage;
}
else
{
calculator.afterDefence.amp += damage;
}
calculator.critRateInc += skill->GetVar( 8 ) + skill->GetVar( 9 ) * skill->GetCurrentSkillLevel();
}
}
break;
}
default:
// 엉뚱한/미구현 스킬이 m_vSkillRelatedToDamaging에 등록된 상황이다!
assert( false );
break;
}
}
void StructCreature::ProcessPostDamageEffect( const DamageMessage& message, const CalculationResult& result )
{
ApplyManaShieldEffect( message, result );
//InterruptSpellCast( message, result );
/* AziaMafia Delete Pierre ame durabilité
if( !result.simulation.missed )
{
RemoveStateOnDamage( message, result );
//ConsumeSoulStoneDurabilityOnDamage( message, result ); // AziaMafia No Endurence
if( XRandom( 1, 100 ) <= GameRule::nEtherealDurabilityConsumptionRate * 100 )
ConsumeEtherealDurabilityOnDamage( message, result );
}
*/
RemoveStateOnDamage(message, result);
ReflectDamage( message, result );
}
void StructCreature::ApplyManaShieldEffect( const DamageMessage& message, const CalculationResult& result )
{
StructCreature* target = message.target;
if ( result.manaShieldAbsorption > 0 )
{
target->AddMP( -result.manaShieldAbsorption );
BroadcastHPMPMsg( message.target, 0, result.manaShieldAbsorption );
}
}
void StructCreature::InterruptSpellCast( const DamageMessage& message, const CalculationResult& result )
{
StructCreature* target = message.target;
// 캐스팅 캔슬 처리
if( target->IsUsingSkill() )
{
target->m_pCastSkill->onDamage( result.damage );
}
}
void StructCreature::RemoveStateOnDamage( const DamageMessage& message, const CalculationResult& result )
{
StructCreature* target = message.target;
target->RemoveStatesOnDamaged();
target->RemoveState( StructState::SLEEP, GameRule::MAX_STATE_LEVEL );
target->RemoveState( StructState::NIGHTMARE, GameRule::MAX_STATE_LEVEL );
target->RemoveState( StructState::ANOMALY_SLEEP, GameRule::MAX_STATE_LEVEL );
target->RemoveState( StructState::MONSTER_SLEEP, GameRule::MAX_STATE_LEVEL );
//AziaMafia FOZEN STATE
target->RemoveState( StructState::FROZEN, GameRule::MAX_STATE_LEVEL);
target->RemoveState( StructState::TOTAL_FROZEN, GameRule::MAX_STATE_LEVEL);
// 맞으면 샤인월 해제된다
if( message.attackType != DamageMessage::MAGICAL_ATTACK )
target->RemoveState( StructState::SHINE_WALL, GameRule::MAX_STATE_LEVEL );
// 맞으면 일정 확률로 빙결 해제
const StructState::StateCode nFrozen[] = { StructState::FROZEN, StructState::TOTAL_FROZEN };
for( int i = 0; i < 2; ++i )
{
StructState * pFrozen = target->GetState( nFrozen[i] );
if( pFrozen )
{
int nRatio = ( pFrozen->GetValue(4) + pFrozen->GetLevel() * pFrozen->GetValue(5) ) * 100.0f;
if( XRandom() % 100 < nRatio )
target->RemoveState( nFrozen[i], GameRule::MAX_STATE_LEVEL );
}
}
}
void StructCreature::ConsumeSoulStoneDurabilityOnDamage( const DamageMessage& message, const CalculationResult& result )
{
StructCreature* target = message.target;
// 소울스톤 내구도 감소
for( int pos = 0 ; pos < 5 ; ++pos )
{
static const ItemBase::ItemWearType WeaponIndices[ 5 ] = { ItemBase::WEAR_ARMOR, ItemBase::WEAR_HELM, ItemBase::WEAR_GLOVE, ItemBase::WEAR_BOOTS, ItemBase::WEAR_MANTLE };
ItemBase::ItemWearType idx = WeaponIndices[ pos ];
StructItem *pItem = target->m_anWear[ idx ];
if( pItem && pItem->GetCurrentEndurance() > 0 && pItem->GetUsingSocketCount() > 0 )
{
int nPrevEndurance = pItem->GetCurrentEndurance();
pItem->SetCurrentEndurance( pItem->GetCurrentEndurance() - GameRule::ENDURANCE_REDUCE_ON_DAMAGE );
if( target->IsPlayer() && GameRule::GetDecreasedEndurancePoint( nPrevEndurance, pItem->GetCurrentEndurance() ) > 0 )
SendItemMessage( static_cast< StructPlayer * >( target ), pItem );
if( pItem->GetCurrentEndurance() == 0 )
target->SetNeedCalculateStat();
}
}
}
void StructCreature::ConsumeEtherealDurabilityOnDamage( const DamageMessage& message, const CalculationResult& result )
{
StructCreature* from = message.from;
StructCreature* target = message.target;
// 에테리얼 내구도 감소(평타, 평타-왼손, 물리스킬, 마법스킬, 물리지속, 마법지속 데미지일 때만 적용)
if( message.damageType != DamageMessage::ADDITIONAL_DAMAGE )
{
// 공격자가 자신이 아니고 반사막에 의한 공격이 아닐 경우 에테리얼 내구도 소모 적용
if( from != target && !from->IsProcessingReflectDamage() )
{
// 공격자의 에테리얼 내구도 소모는 공격자가 준 데미지 기준으로 적용
from->ProcEtherealDurabilityConsumption( true, message.oldDamageType, message.damage );
}
// 피격자의 에테리얼 내구도 소모는 피격자가 받은 데미지 기준으로 적용
target->ProcEtherealDurabilityConsumption( false, message.oldDamageType, result.damage );
}
}
void StructCreature::ReflectDamage( const DamageMessage& message, const CalculationResult& result )
{
StructCreature* from = message.from;
StructCreature* target = message.target;
const std::vector< StructCreature::DamageReflectInfo > reflectInfo = target->m_vDamageReflectInfo;
// 두 케릭이 서로 반사막 켜고 피케하면 연속 반사 되는 걸 방지하기 위해 IsProcessingReflectDamage() 사용
// 이 부분은 상대방을 죽일 수도 있으므로 상대방에게 적용시키는 모든 동작의 맨 마지막이 되어야 함
// Ex. 지속효과 부여 -> 반사 데미지 처리시 사망 : 부여된 지속효과 자동 제거 (O)
// 반사 데미지 처리시 사망 -> 지속효과 부여 : 사망 후 지속효과가 부여됨 (X)
if( !target->IsProcessingReflectDamage() && !from->IsProcessingReflectDamage() && !reflectInfo.empty() )
{
target->m_StatusFlag.On( STATUS_PROCESSING_REFELCT );
for( std::vector< DamageReflectInfo >::const_iterator it = reflectInfo.begin(); it != reflectInfo.end(); ++it )
{
if( XRandom( 0, 100 ) < it->fire_ratio &&
from->GetCurrentPosition( GetArTime() ).GetDistance( target->GetCurrentPosition( GetArTime() ) ) <=
it->range * GameRule::DEFAULT_UNIT_SIZE )
{
int nPrevHP = from->GetHP();
int nPrevMP = from->GetMP();
int nDamageFlag = IGNORE_AVOID | IGNORE_BLOCK | IGNORE_CRITICAL;
if( it->bIgnoreDefence )
nDamageFlag |= IGNORE_DEFENCE;
// 직접적으로 받은 데미지 유형일 경우만 반사막 일반 데미지 작동(일반 물리 공격, 왼손 물리 공격, 마법 공격)
if( message.damageType == DamageMessage::DIRECT_DAMAGE )
{
if ( it->nReflectDamage )
{
from->DealAdditionalMagicalDamage( target, it->nReflectDamage, it->type, 0, 0, nDamageFlag );
}
int nReflectDamage = 0;
switch( message.attackType )
{
case DamageMessage::PHYSICAL_ATTACK:
case DamageMessage::PHYSICAL_LEFT_HAND_ATTACK:
// 물리 데미지 비율 반사는 물리 데미지일 경우만 작동(반사도 물리 데미지임)
nReflectDamage = it->fPhysicalReflectRatio * result.finalDamage;
if ( nReflectDamage )
{
from->DealPhysicalDamage( target, nReflectDamage, it->type, 0, 0, nDamageFlag );
}
break;
case DamageMessage::PHYSICAL_SKILL_ATTACK:
// 물리 스킬 데미지 비율 반사는 물리 스킬 데미지일 경우만 작동(반사도 물리 스킬 데미지)
nReflectDamage = it->fPhysicalSkillReflectRatio * result.finalDamage;
if ( nReflectDamage )
{
from->DealPhysicalSkillDamage( target, nReflectDamage, it->type, 0, 0, nDamageFlag );
}
break;
case DamageMessage::MAGICAL_ATTACK:
// 마법 스킬 데미지 비율 반사는 마법 스킬 데미지일 경우만 작동(반사도 마법 스킬 데미지)
nReflectDamage = it->fMagicalReflectRatio * result.finalDamage;
if ( nReflectDamage )
{
from->DealMagicalSkillDamage( target, nReflectDamage, it->type, 0, 0, nDamageFlag );
}
break;
}
}
// 반사막에 의한 데미지 표시를 위해 방송 후 한 번 더 보냄
BroadcastHPMPMsg( from, from->GetHP() - nPrevHP, from->GetMP() - nPrevMP );
if( target->IsPlayer() )
{
SendHPMPMsg( static_cast< StructPlayer * >( target ), from, from->GetHP() - nPrevHP, from->GetMP() - nPrevMP, true );
}
else if( target->IsSummon() && static_cast< StructSummon * >( target )->GetMaster() )
{
SendHPMPMsg( static_cast< StructSummon * >( target )->GetMaster(), from, from->GetHP() - nPrevHP, from->GetMP() - nPrevMP, true );
}
if( from->IsPlayer() )
{
SendHPMPMsg( static_cast< StructPlayer * >( from ), from, from->GetHP() - nPrevHP, from->GetMP() - nPrevMP, true );
}
else if( from->IsSummon() && static_cast< StructSummon * >( from )->GetMaster() )
{
SendHPMPMsg( static_cast< StructSummon * >( from )->GetMaster(), from, from->GetHP() - nPrevHP, from->GetMP() - nPrevMP, true );
}
}
}
target->m_StatusFlag.Off( STATUS_PROCESSING_REFELCT );
}
}
StructCreature::_DAMAGE StructCreature::CreateDamageStruct( const CalculationResult& result )
{
_DAMAGE damage;
damage.nDamage = result.finalDamage;
damage.nResistedDamage = result.resistedDamage;
damage.bBlock = result.simulation.blocked;
damage.bCritical = result.simulation.critical;
damage.bMiss = result.simulation.missed;
damage.bPerfectBlock = result.simulation.perfectBlocked;
return damage;
}
StructCreature::_DAMAGE_INFO StructCreature::DealMagicalSkillDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag )
{
_DAMAGE_INFO damage_info;
DamageMessage message( pFrom, this, DT_NORMAL_MAGICAL_DAMAGE, nFlag, elemental_type, nDamage );
message.penalty = &m_MagicalSkillStatePenalty;
message.flag |= DamageMessage::DAMAGE_REDUCE;
message.criticalBonus = critical_bonus + pFrom->m_BadMagicalElementalSkillStateMod[elemental_type].nCritical;
//AziaMafia WTF damage ????
//message.accuracyBonus = accuracy_bonus + pFrom->m_BadMagicalElementalSkillStateMod[elemental_type].nPhysicalAccuracy;
message.accuracyBonus = accuracy_bonus + pFrom->m_BadMagicalElementalSkillStateMod[elemental_type].nMagicalAccuracy;
damage_info.SetDamage( DealDamage( message ) );
ProcessAdditionalDamage( message, DT_ADDITIONAL_MAGICAL_DAMAGE, pFrom->m_vMagicalSkillAdditionalDamage, damage_info );
damage_info.target_hp = GetHP();
return damage_info;
}
StructCreature::_DAMAGE_INFO StructCreature::DealPhysicalNormalDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag )
{
_DAMAGE_INFO damage_info;
DamageMessage message( pFrom, this, DT_NORMAL_PHYSICAL_DAMAGE, nFlag, elemental_type, nDamage );
bool bRange = ( pFrom->IsUsingBow() || pFrom->IsUsingCrossBow() ) && pFrom->IsPlayer();
message.advantage = bRange ? &pFrom->m_RangeStateAdvantage : &pFrom->m_NormalStateAdvantage;
message.penalty = bRange ? &m_RangeStatePenalty : &m_NormalStatePenalty;
message.flag |= DamageMessage::DAMAGE_REDUCE;
message.criticalBonus = critical_bonus;
message.accuracyBonus = accuracy_bonus;
damage_info.SetDamage( DealDamage( message ) );
ProcessAdditionalDamage( message, DT_ADDITIONAL_DAMAGE,
bRange ? pFrom->m_vRangeAdditionalDamage : pFrom->m_vNormalAdditionalDamage, damage_info );
damage_info.target_hp = GetHP();
return damage_info;
}
StructCreature::_DAMAGE_INFO StructCreature::DealPhysicalNormalLeftHandDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag )
{
_DAMAGE_INFO damage_info;
DamageMessage message( pFrom, this, DT_NORMAL_PHYSICAL_LEFT_HAND_DAMAGE, nFlag, elemental_type, nDamage );
message.advantage = &pFrom->m_NormalStateAdvantage;
message.penalty = &m_NormalStatePenalty;
message.flag |= DamageMessage::DAMAGE_REDUCE;
message.criticalBonus = critical_bonus;
message.accuracyBonus = accuracy_bonus;
damage_info.SetDamage( DealDamage( message ) );
ProcessAdditionalDamage( message, DT_ADDITIONAL_LEFT_HAND_DAMAGE, pFrom->m_vNormalAdditionalDamage, damage_info );
damage_info.target_hp = GetHP();
return damage_info;
}
StructCreature::_DAMAGE_INFO StructCreature::DealPhysicalSkillDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag )
{
_DAMAGE_INFO damage_info;
DamageMessage message( pFrom, this, DT_NORMAL_PHYSICAL_SKILL_DAMAGE, nFlag, elemental_type, nDamage );
message.penalty = &m_PhysicalSkillStatePenalty;
message.flag |= DamageMessage::DAMAGE_REDUCE;
message.criticalBonus = critical_bonus + pFrom->m_BadPhysicalElementalSkillStateMod[elemental_type].nCritical;
message.accuracyBonus = accuracy_bonus + pFrom->m_BadPhysicalElementalSkillStateMod[elemental_type].nPhysicalAccuracy;
damage_info.SetDamage( DealDamage( message ) );
ProcessAdditionalDamage( message, DT_ADDITIONAL_DAMAGE, pFrom->m_vPhysicalSkillAdditionalDamage, damage_info );
damage_info.target_hp = GetHP();
return damage_info;
}
void StructCreature::ProcessAdditionalDamage( const DamageMessage& sourceMessage, const DamageType additionalDamageType, const std::vector< AdditionalDamageInfo >& additionalDamage, _DAMAGE_INFO& damageInfo )
{
if( !damageInfo.bMiss && !damageInfo.bPerfectBlock )
{
StructCreature* target = sourceMessage.target;
// 추가 데미지 처리
std::vector< AdditionalDamageInfo >::const_iterator it;
for( it = additionalDamage.begin(); it != additionalDamage.end(); ++it )
{
if( ( it->require_type == 99 || it->require_type == sourceMessage.elementalType ) && it->ratio > XRandom() % 100 )
{
int damage = 0;
if( it->nDamage )
{
damage = it->nDamage;
}
else
{
damage = it->fDamage * damageInfo.nDamage;
}
DamageMessage message( sourceMessage.from, sourceMessage.target, additionalDamageType, 0, it->type, damage );
damage = target->DealDamage( message ).nDamage;
if ( it->type >= 0 && it->type < Elemental::TYPE_COUNT )
{
damageInfo.elemental_damage[ it->type ] += damage;
}
damageInfo.nDamage += damage;
}
}
}
}
StructCreature::_DAMAGE StructCreature::DealDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, DamageType damageType, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
_DAMAGE_INFO damage_info;
DamageMessage message( pFrom, this, damageType, nFlag, elemental_type, nDamage );
message.advantage = damage_advantage;
message.penalty = damage_penalty;
message.criticalBonus = critical_bonus;
message.accuracyBonus = accuracy_bonus;
return DealDamage( message );
}
// 이하 함수들은 인터페이스 차원의 호환을 위해 남겨두었으나, 이런 류의 복붙 래퍼 함수가 굳이 필요한지는 의문이다.
// TODO : 추후 리팩토링을 통해 인터페이스를 적절하게 정리하도록 하자.
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealMagicalDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_NORMAL_MAGICAL_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealPhysicalDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_NORMAL_PHYSICAL_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealStateMagicalDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_STATE_MAGICAL_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealPhysicalStateDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_STATE_PHYSICAL_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealPhysicalLeftHandDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_NORMAL_PHYSICAL_LEFT_HAND_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealAdditionalMagicalDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_ADDITIONAL_MAGICAL_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealAdditionalDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_ADDITIONAL_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}
// 데미지를 리턴함
StructCreature::_DAMAGE StructCreature::DealAdditionalLeftHandDamage( StructCreature *pFrom, int nDamage, Elemental::Type elemental_type, int accuracy_bonus, int critical_bonus, int nFlag, const StateMod * damage_penalty, const StateMod * damage_advantage )
{
return DealDamage( pFrom, nDamage, elemental_type, DT_ADDITIONAL_LEFT_HAND_DAMAGE, accuracy_bonus, critical_bonus, nFlag, damage_penalty, damage_advantage );
}