#include #include #include #include "LogClient/LogClient.h" #include "ErrorCode/ErrorCode.h" #include "StructSummon.h" #include "GameContent.h" #include "SendMessage.h" #include "StructItem.h" #include "StructPlayer.h" #include "StructMonster.h" #include "DB_Commands.h" #include "StructSkill.h" #include "GameProc.h" #include "GameMessage.h" #include "CompeteManager.h" #include "GameAllocator.h" #include "LuaVM.h" void StructSummon::BindProperty() { StructSummon temp( "for bind property" ); temp.Bind( "handle", (int *)&temp.m_hHandle ); temp.Bind( "hp", &temp.m_nHP ); temp.Bind( "mp", &temp.m_nMP ); temp.Bind( "sp", &temp.m_nSP ); temp.Bind( "max_hp", &temp.m_nMaxHP ); temp.Bind( "max_mp", &temp.m_nMaxMP ); temp.Bind( "max_sp", &temp.m_nMaxSP ); temp.BindCString( "name", temp.m_szName, _countof( temp.m_szName ) ); temp.Bind( "exp", &temp.m_nEXP ); temp.Bind( "jp", &temp.m_nJobPoint ); temp.Bind( "job_level", &temp.m_nJobLevel ); temp.Bind( "jlv", &temp.m_nJobLevel ); temp.Bind( "ev_1_ID", &temp.m_nPrevJobId[0] ); temp.Bind( "ev_2_ID", &temp.m_nPrevJobId[1] ); temp.Bind( "ev_1_level", &temp.m_nPrevJobLevel[0] ); temp.Bind( "ev_2_level", &temp.m_nPrevJobLevel[1] ); temp.Bind( "job", &temp.m_nJob ); temp.Bind( "level", &temp.m_nLevel ); temp.Bind( "lv", &temp.m_nLevel ); temp.Bind( "max_level", &temp.m_nMaxReachedLevel ); temp.Bind( "str", const_cast< __int64 * >( &temp.m_Stat.strength.get() ) ); temp.Bind( "agi", const_cast< __int64 * >( &temp.m_Stat.agility.get() ) ); temp.Bind( "dex", const_cast< __int64 * >( &temp.m_Stat.dexterity.get() ) ); temp.Bind( "int", const_cast< __int64 * >( &temp.m_Stat.intelligence.get() ) ); temp.Bind( "luck", const_cast< __int64 * >( &temp.m_Stat.luck.get() ) ); temp.Bind( "vital", const_cast< __int64 * >( &temp.m_Stat.vital.get() ) ); temp.Bind( "mental", const_cast< __int64 * >( &temp.m_Stat.mentality.get() ) ); temp.Bind( "race", &temp.m_nRace ); temp.Bind( "x", &temp.mv.x ); temp.Bind( "y", &temp.mv.y ); temp.Bind( "weight", &temp.m_fWeight ); temp.Bind( "max_weight", const_cast< __int64 * >( &temp.m_Attribute.fMaxWeight.get() ) ); temp.Bind( "speed", const_cast< __int64 * >( &temp.m_Attribute.fMoveSpeed.get() ) ); temp.Bind( "attack_point", const_cast< __int64 * >( &temp.m_Attribute.fAttackPointRight.get() ) ); temp.Bind( "attack_point_right", const_cast< __int64 * >( &temp.m_Attribute.fAttackPointRight.get() ) ); temp.Bind( "attack_point_left", const_cast< __int64 * >( &temp.m_Attribute.fAttackPointLeft.get() ) ); temp.Bind( "defence", const_cast< __int64 * >( &temp.m_Attribute.fDefence.get() ) ); temp.Bind( "magic_def", const_cast< __int64 * >( &temp.m_Attribute.fMagicDefence.get() ) ); temp.Bind( "magic_pt", const_cast< __int64 * >( &temp.m_Attribute.fMagicPoint.get() ) ); temp.Bind( "accuracy", const_cast< __int64 * >( &temp.m_Attribute.fAccuracyRight.get() ) ); temp.Bind( "accuracy", const_cast< __int64 * >( &temp.m_Attribute.fAccuracyRight.get() ) ); temp.Bind( "accuracy", const_cast< __int64 * >( &temp.m_Attribute.fAccuracyLeft.get() ) ); temp.Bind( "attack_range", const_cast< __int64 * >( &temp.m_Attribute.fAttackRange.get() ) ); temp.Bind( "attack_speed", const_cast< __int64 * >( &temp.m_Attribute.fAttackSpeed.get() ) ); temp.Bind( "avoid", const_cast< __int64 * >( &temp.m_Attribute.fAvoid.get() ) ); temp.Bind( "block_chance", const_cast< __int64 * >( &temp.m_Attribute.fBlockChance.get() ) ); temp.Bind( "summon_state", &temp.m_bIsSummoned ); temp.Bind( "evolution_depth", &temp.m_nTransformLevel ); // Epic 3 이전까지 진화 시스템에서는 진화시 레벨이 초기화 되므로 // 따로 계산이 필요했으나 Epic 4부터 현재 레벨을 총 레벨로 보면 됨 temp.Bind( "total_level", &temp.m_nLevel ); } StructSummon* StructSummon::AllocSummon( struct StructPlayer* pMaster, unsigned idx ) { StructSummon* p; struct _myIntializer : GameAllocateFunctor { _myIntializer( int _idx ) : m_nIdx( _idx ) {} virtual void operator()( void * pObj, AR_HANDLE handle ) { new (pObj) StructSummon( handle, m_nIdx ); } unsigned m_nIdx; }; AR_HANDLE handle = allocSummonStruct( &p, _myIntializer( idx ) ); // new (p) StructSummon( handle, idx ); p->m_pMaster = pMaster; p->CalculateStat(); p->m_nHP = p->m_nMaxHP; p->m_nMP = p->m_nMaxMP; p->m_nMaxReachedLevel = 1; p->m_nJobPoint = 1; p->m_fCM = 0; // 소환수는 id + 1000 이 직업 ID 임. if( p->GetRidingInfo() == SummonBase::RIDING_LENT ) { // 라이딩 스킬 넣어줘야 함. p->SetSkill( StructSkill::SKILL_UID_SUMMON_SKILL, StructSkill::SKILL_CREATURE_RIDING, 1, 0 ); } return p; } char StructSummon::GetRidingInfo() const { return m_pContentInfo->is_riding_only; } void StructSummon::FreeSummon( StructSummon* p ) { // _oprint( "DEL SUMMON : %08X\n", p ); prepareFreeSummonStruct( p ); p->StructSummon::~StructSummon(); freeSummonStruct( p ); } void StructSummon::onSPChange() { if( GetMaster() ) SendSPMsg( GetMaster(), this ); } void StructSummon::AddExp( __int64 exp, __int64 jp, bool bApplyStamina, bool bForce ) { __int64 exp_limit = 0; // exp나 jp가 크면 float은 오류가 너무 많이 나므로 double로 계산 해줌 //exp *= static_cast< double >( m_fEXPMod + 1.0f ); //pet exp cap jp *= static_cast< double >( m_fEXPMod + 1.0f ); if (!bForce && m_pMaster) exp = exp ; //Azia Mafia Fix Leveling Pets //std::min( exp, static_cast< __int64 >( GameRule::GetSummonEXPLimit( GetLevel() ) ) ); // 획득 가능 한계 경험치를 체크하여 더 이상 경험치 획득 불가능 상태면 획득 취소 switch( GetTransformLevel() ) { case SummonBase::EVOLVE_GROWTH: exp_limit = GameContent::GetNeedSummonExp( GameRule::GROWTH_SUMMON_MAX_LEVEL ) - 1; if( exp_limit <= m_nEXP ) return; else if( GameContent::GetNeedSummonExp( GameRule::GROWTH_SUMMON_MAX_LEVEL - 1 ) < m_nEXP ) { exp = std::min( __int64(exp), exp_limit - m_nEXP ); } break; case SummonBase::EVOLVE_EVOLVE: exp_limit = GameContent::GetNeedSummonExp( GameRule::EVOLVE_SUMMON_MAX_LEVEL ) - 1; if( exp_limit <= m_nEXP ) return; else if( GameContent::GetNeedSummonExp( GameRule::EVOLVE_SUMMON_MAX_LEVEL - 1 ) < m_nEXP ) { exp = std::min( __int64( exp ), exp_limit - m_nEXP ); } break; default: exp_limit = GameContent::GetNeedSummonExp( GameRule::NORMAL_SUMMON_MAX_LEVEL ) - 1; if( exp_limit <= m_nEXP ) return; else if( GameContent::GetNeedSummonExp( GameRule::NORMAL_SUMMON_MAX_LEVEL - 1 ) < m_nEXP ) { exp = std::min( __int64( exp ), exp_limit - m_nEXP ); } break; } StructCreature::AddExp( exp, jp ); } void StructSummon::onExpChange() { int nLevel = 1; int nJP = 0; int nMaxLevel = 0; int nEvolvableLevel = 0; switch( GetTransformLevel() ) { case SummonBase::EVOLVE_NORMAL: nMaxLevel = GameRule::NORMAL_SUMMON_MAX_LEVEL; nEvolvableLevel = GameRule::NORMAL_SUMMON_EVOLVE_LEVEL; break; case SummonBase::EVOLVE_GROWTH: nMaxLevel = GameRule::GROWTH_SUMMON_MAX_LEVEL; nEvolvableLevel = GameRule::GROWTH_SUMMON_EVOLVE_LEVEL; break; case SummonBase::EVOLVE_EVOLVE: nMaxLevel = GameRule::EVOLVE_SUMMON_MAX_LEVEL; nEvolvableLevel = nMaxLevel; break; } // 레벨업 처리 전에 m_nMaxReachedLevel(기존 도달 최대 레벨-렙다 후 렙업시 jp획득 방지용)을 확인 if( m_nMaxReachedLevel < m_nLevel ) m_nMaxReachedLevel = m_nLevel; while( true ) { if( nLevel >= nMaxLevel ) break; // 레벨업에 필요한 경험치 테이블 검사로 레벨업 가능한 모든 구간을 처리 __int64 nNeedExp = GameContent::GetNeedSummonExp( nLevel ); if( nNeedExp == 0 ) break; if( nNeedExp > m_nEXP ) break; ++nLevel; if( m_nMaxReachedLevel < nLevel ) { ++nJP; // 오버브리드의 경우에는 JP를 1개 더 줌 if( nLevel > nEvolvableLevel ) ++nJP; } } if( GetMaster() ) SendExpMsg( GetMaster(), this ); if( nLevel && nLevel != m_nLevel ) { __int64 nCardUID = 0; if( GetParentCard() ) { nCardUID = GetParentCard()->GetItemUID(); } LOG::Log11N4S( LM_SUMMON_LEVEL_UP, GetMaster()->GetAccountID(), GetMaster()->GetSID(), GetSID(), m_nLevel, nLevel, GetPrevJobLevel( 0 ), GetPrevJobLevel( 1 ), GetJobId(), m_nJobPoint, ( nLevel > m_nLevel ) ? nJP : 0, nCardUID, GetMaster()->GetAccountName(), LOG::STR_NTS, GetMaster()->GetName(), LOG::STR_NTS, GetName(), LOG::STR_NTS, GetSummonName(), LOG::STR_NTS ); if( nLevel > m_nLevel ) { int prev_hp = GetHP(); int prev_mp = GetMP(); m_nJobLevel = m_nLevel = nLevel; m_nJobPoint += nJP; if( m_nMaxReachedLevel < m_nLevel ) m_nMaxReachedLevel = m_nLevel; CalculateStat(); if( !IsDead() ) { m_nHP = m_nMaxHP; m_nMP = m_nMaxMP; } if( IsInWorld() ) { BroadcastHPMPMsg( this, m_nHP - prev_hp, m_nMP - prev_mp ); } else if( GetMaster() ) { SendHPMPMsg( GetMaster(), this, m_nHP - prev_hp, m_nMP - prev_mp ); } if( GetMaster() ) SendPropertyMessage( GetMaster(), GetHandle(), "jp", m_nJobPoint ); } else { m_nJobLevel = m_nLevel = nLevel; CalculateStat(); } DBQuery( new DB_UpdateSummon( this ) ); if( GetParentCard() && GetMaster() ) SendItemMessage( GetMaster(), GetParentCard() ); if( IsInWorld() ) BroadcastLevelMsg( this ); if( GetMaster() ) SendLevelMsg( GetMaster(), this ); } } void StructSummon::onEnergyChange() { TS_SC_ENERGY msg; msg.handle = GetHandle(); msg.energy = GetEnergyCount(); if( IsInWorld() ) { ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg ); } else if( GetMaster() ) { PendMessage( GetMaster(), &msg ); } } const CreatureStat & StructSummon::GetBaseStat() const { return GameContent::GetStatInfo( m_pContentInfo->stat_id ); } const char* StructSummon::GetSummonName() { return GameContent::GetString( m_pContentInfo->name_id ); } int StructSummon::GetSummonNameId() { return m_pContentInfo->name_id; } const char* StructSummon::GetName() const { return m_szName; } int StructSummon::GetSummonCode() { return m_pContentInfo->uid; } int StructSummon::GetSummonType() { if( m_nChangingGroup != CREATURE_NONE ) return m_nChangingGroup; return m_pContentInfo->type; } int StructSummon::GetSummonElementalType() { return m_pContentInfo->magic_type; } bool StructSummon::SetSummonInfo( unsigned idx ) { m_pContentInfo = GameContent::GetSummonInfo( idx ); m_nTransformLevel = m_pContentInfo->evolve_type; m_nJob = idx; return true; } int StructSummon::GetJobDepth() const { return GetTransformLevel()-1; } void StructSummon::SetName( const char *szName ) { s_strcpy( m_szName, _countof( m_szName ), szName ); } StructSummon::StructSummon( AR_HANDLE handle, unsigned idx ) { m_hHandle = handle; m_nAccountId = 0; m_nLastProcessTime = 0; m_nArObjectType = ArObject::MOVABLE_OBJECT; //SetTag( static_cast< GameObject* >( this ) ); m_nTransformLevel = 0; SetSummonInfo( idx ); m_nStatus = 0; m_nSP = m_nMaxSP = 1000; m_fMaxSPAmplifier = 0.0f; m_fSPRegenAddAmplifier = 0.0f; m_bIsSummoned = false; m_bIsRidable = true; m_pMaster = 0; m_pItem = NULL; m_nSID = 0; m_nJobLevel = m_nLevel = m_nMaxReachedLevel = 1; m_cSlotIndex = 0; m_nMaxItemWear = 2; memset( m_szName, 0, sizeof(m_szName) ); //ArcadiaServer::Instance().SetObjectPriority( this, ArObject::UPDATE_PRIORITY_NORMAL ); // 임시 : 랜덤하게 다른방향 보게 하자.. -_-; mv.face = (float)XRandom() / 100; m_bIsBattleMode = false; m_nLastCantAttackTime = 0; m_nSPRegenAdd = 0; #ifdef _MEM_USAGE_DEBUG XSEH::IncreaseAllocCount( "StructSummon" ); #endif } StructSummon::~StructSummon() { if( IsInWorld() ) { ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); RemoveSummonFromWorld( this ); } #ifdef _MEM_USAGE_DEBUG XSEH::DecreaseAllocCount( "StructSummon" ); #endif } bool StructSummon::ProcDelete() { FreeSummon( this ); return true; } bool StructSummon::IsUsingBow() const { for( int i = 0 ; i < m_nMaxItemWear ; ++i ) { if( m_anWear[i] && m_anWear[i]->IsBow() ) return true; } return false; } bool StructSummon::IsUsingCrossBow() const { for( int i = 0 ; i < m_nMaxItemWear ; ++i ) { if( m_anWear[i] && m_anWear[i]->IsCrossBow() ) return true; } return false; } bool StructSummon::TranslateWearPosition( ItemBase::ItemWearType & pos, struct StructItem* pItem, std::vector< int > * vpOverlappItemList) { struct StructItem* pWearedItem; if( !StructCreature::TranslateWearPosition( pos, pItem, vpOverlappItemList ) ) return false; //AziaMafia PetSlot Off PetCardMode //if( !pItem->GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_CARD ) ) return false; if( pos > ItemBase::MAX_ITEM_WEAR || pos < 0 ) { return false; } // 날아온 포지션은 쌩깐다. if( pos == (ItemBase::ItemWearType) ItemBase::MAX_ITEM_WEAR ) { // 아티팩트 아이템은 4번째 슬롯부터 장착할 수 있다. if( pItem->IsArtifact() ) { // 같은 계열의 소환수일 때만 장착 가능 (단, 아티팩트의 소환수코드가 없으면 모든 크리쳐용 아티팩트) int nSummonCode = pItem->GetSummonCode(); if( nSummonCode ) { int nJobId = GetPrevJobId( 0 ); if( !nJobId ) nJobId = GetJobId(); if( nJobId != nSummonCode ) return false; } } //AziaMafia PetSlot for( int i = 0; i < StructSummon::SUMMON_MAX_ITEM_WEAR; ++i ) { switch ( i ) { case 0: { if (!pItem->IsSkin1()) continue; } break; case 1: case 2: case 6: case 7: case 8: case 14: { if (!pItem->IsWeapon() && !pItem->IsArmor() && !pItem->IsHelmet() && !pItem->IsGloves() && !pItem->IsBoots() && !pItem->IsShield() // && !pItem->IsBelt() ) continue; } break; case 3: case 9: case 11: case 13: { if (!pItem->IsEarRing() && !pItem->IsRing() && !pItem->IsAmulet() && !pItem->IsBelt() ) continue; } break; case 4: case 5: case 10: case 12: { if (!pItem->IsArtifact()) continue; } break; } pWearedItem = m_anWear[i]; //AziaMafia PetSlot //if (!pItem->IsArtifact() && pWearedItem && pWearedItem->GetItemGroup() == pItem->GetItemGroup()) if ( ( pItem->IsEarRing() || pItem->IsRing() || pItem->IsAmulet() || pItem->IsBelt() ) && pWearedItem && pWearedItem->GetItemClass() == pItem->GetItemClass() ) return false; if (!pItem->IsArtifact() && !pItem->IsEarRing() && !pItem->IsRing() && !pItem->IsAmulet() && !pItem->IsBelt() && pWearedItem && pWearedItem->GetItemGroup() == pItem->GetItemGroup()) return false; if( pWearedItem == pItem ) return false; if( !pWearedItem && pos == ItemBase::MAX_ITEM_WEAR ) { pos = (ItemBase::ItemWearType) i; break; } } } else if( pos >= m_nMaxItemWear ) { return false; } else if( vpOverlappItemList ) { // 장착 위치에 무언가 입고 있다면 if( m_anWear[ pos ] ) { vpOverlappItemList->push_back( pos ); } } if (pos >= m_nMaxItemWear) { return false; } return true; } unsigned char StructSummon::GetRace() const { return CREATURE_ETC; } __int64 StructSummon::GetDeadEXPPenalty() { return GameContent::GetNeedSummonExp( GetLevel() ) * ( 0.15 / ( GetLevel() - 1 ) + 0.0005 ); } bool StructSummon::IsEXPLimitReached() { __int64 exp_limit = 0; exp_limit = GameContent::GetNeedSummonExp( GameRule::nMaxCreatureLevel ) - 1; return ( exp_limit <= m_nEXP ); } void StructSummon::onDead( StructCreature *pFrom, bool decreaseEXPOnDead ) { StructPlayer * pMaster = GetMaster(); if( pMaster->IsRiding() && pMaster->GetRideObject() == this ) { pMaster->UnMount( StructPlayer::UNMOUNT_FALL, pFrom ); } StructCreature::onDead( pFrom, decreaseEXPOnDead ); StructPlayer * pKiller = NULL; if( pFrom->IsPlayer() ) { pKiller = static_cast< StructPlayer * >( pFrom ); } else if( pFrom->IsSummon() ) { pKiller = static_cast< StructSummon * >( pFrom )->GetMaster(); } char * szKillerType = "UNKN"; int nKillerID = 0; if( pFrom->IsPlayer() ) { szKillerType = "PLYR"; nKillerID = static_cast< StructPlayer* >( pFrom )->GetPlayerUID(); } if( pFrom->IsMonster() ) { szKillerType = "MNST"; nKillerID = static_cast< StructMonster* >( pFrom )->GetMonsterId(); } if( pFrom->IsSummon() ) { szKillerType = "SUMN"; nKillerID = static_cast< StructSummon* >( pFrom )->GetSummonSID(); } __int64 nLostExp = 0; // 대련 중에 범위 스킬 등으로 플레이어가 죽은 직후 소환수가 같이 죽으면 // 주인의 CompeteID는 0이지만 대련 중의 사망이었으므로 페널티 안 받게 주인이 대련 사망 상태인지도 체크해야 함 if( ( !pMaster->IsInBattleArena() && !pMaster->IsInDeathmatch() && !pMaster->IsInBattleField() && !pMaster->IsInStartedCompete( true ) && !( pMaster->IsDead() && pMaster->IsCompeteDead() ) ) || !pKiller ) { if( pKiller && pKiller != pMaster ) { pMaster->ProcImmoralPoint( pKiller, 0, false ); } if( GetLevel() > 5 && pFrom->IsMonster() && !pMaster->IsInSiegeOrRaidDungeon() && !pMaster->IsInBattleField() && decreaseEXPOnDead ) { //Azia MAfia FIX Loose XP nLostExp = GetDeadEXPPenalty(); SetEXP( GetEXP() - nLostExp ); } // 장착 중인 아이템의 에테리얼 내구도 감소 처리(던전 시즈/레이드는 예외) if( !pMaster->IsInSiegeOrRaidDungeon() ) { // 몬스터에게 죽은 경우나 주인에 의한 자살일 때 장착 중인 아이템의 에테리얼 내구도 감소 처리 if( (!pKiller || pKiller == pMaster) && !GameRule::bItemDurabilitySwitch) // Credits to AziaMafia: Durability consumption switch { bool bNeedToCalculateStat = false; for( int i = 0 ; i < m_nMaxItemWear ; ++i ) { StructItem * pItem = m_anWear[ i ]; if( !pItem || !pItem->GetMaxEtherealDurability() || !pItem->GetCurrentEtherealDurability() ) continue; // 최대 내구도의 10% 감소(곱하기 상수화하면 소수자리 오차 발생하므로 나누기 정수 연산으로 적용함) int nDecrement = pItem->GetMaxEtherealDurability() / 10; pItem->AddCurrentEtherealDurability( nDecrement * -1 ); SendItemMessage( pMaster, pItem ); bNeedToCalculateStat |= ( pItem->GetCurrentEtherealDurability() == 0 ); } if( bNeedToCalculateStat ) CalculateStat(); } // 몬스터에게 죽은 경우나 주인 이외의 플레이어에 의해 죽었을 때 강화 크리처의 내구도 감소 GetParentCard()->SetInstanceFlagOff( ItemInstance::ITEM_FLAG_SUMMON_DURABILITY ); if( (!pKiller || pKiller != pMaster ) && !GameRule::bItemDurabilitySwitch) // Credits to AziaMafia: Durability consumption switch { if( GetParentCard()->GetItemEnhance() != 0 ) { GetParentCard()->SetInstanceFlagOn( ItemInstance::ITEM_FLAG_SUMMON_DURABILITY ); GetParentCard()->SetCurrentEtherealDurability( GetParentCard()->GetCurrentEtherealDurability() - 1 ); SendItemMessage( this->GetMaster(), GetParentCard() ); } } } } //Azia MAfia FIX Loose XP SetLastDecreasedEXP(nLostExp); //SetLastDecreasedEXP( 0 ); ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_NORMAL ); LOG::Log11N4S( LM_SUMMON_DEATH, pMaster->GetAccountID(), pMaster->GetSID(), GetSID(), nKillerID, nLostExp, 0, 0, 0, GetX(), GetY(), GetEXP(), pMaster->GetAccountName(), LOG::STR_NTS, pMaster->GetName(), LOG::STR_NTS, GetName(), LOG::STR_NTS, szKillerType, LOG::STR_NTS ); } void StructSummon::onJPChange() { SendPropertyMessage( this->GetMaster(), GetHandle(), "jp", GetJobPoint() ); } void StructSummon::onRegisterSkill( int skill_uid, int skill_id, int skill_level, bool is_new_skill ) { if( is_new_skill ) { // 새로 배우는거면 insert DBQuery( new DB_InsertSkill( this, skill_uid, 0, 0/*JP*/, GetSummonSID(), skill_id, skill_level, 0 ) ); } else { // 아니면 update DBQuery( new DB_UpdateSkill( this, skill_uid, skill_level, GetRemainCoolTime( skill_id ) ) ); } ::SendSkillMessage( GetMaster(), this, skill_id ); } __int64 StructSummon::getJPAfterSkillReset() { int nJP = m_nMaxReachedLevel; // 강화 크리처의 추가 잡포인트 if( GetParentCard() ) nJP += GameContent::GetCreatureEnhanceInfo( GetParentCard()->GetItemEnhance() )->jp_addition; // 오버 브리드 레벨 계산 switch( GetTransformLevel() ) { case SummonBase::EVOLVE_EVOLVE: { // 2차 진화시 오버브리드 JP 처리 int nEvolveLevel = GetPrevJobLevel( 1 ); nJP += ( nEvolveLevel > GameRule::GROWTH_SUMMON_EVOLVE_LEVEL ) ? nEvolveLevel - GameRule::GROWTH_SUMMON_EVOLVE_LEVEL : 0; // 1차 진화시 오버브리드 JP 처리 nEvolveLevel = GetPrevJobLevel( 0 ); nJP += ( nEvolveLevel > GameRule::NORMAL_SUMMON_EVOLVE_LEVEL ) ? nEvolveLevel - GameRule::NORMAL_SUMMON_EVOLVE_LEVEL : 0; } break; case SummonBase::EVOLVE_GROWTH: { // 1차 진화시 오버브리드 JP 처리 int nEvolveLevel = GetPrevJobLevel( 0 ); nJP += ( nEvolveLevel > GameRule::NORMAL_SUMMON_EVOLVE_LEVEL ) ? nEvolveLevel - GameRule::NORMAL_SUMMON_EVOLVE_LEVEL : 0; // 현재 오버브리드 진행 중일 경우 JP 처리 nJP += ( m_nMaxReachedLevel > GameRule::GROWTH_SUMMON_EVOLVE_LEVEL ) ? m_nMaxReachedLevel - GameRule::GROWTH_SUMMON_EVOLVE_LEVEL : 0; } break; case SummonBase::EVOLVE_NORMAL: { // 현재 오버브리드 진행 중일 경우 JP 처리 nJP += ( m_nMaxReachedLevel > GameRule::NORMAL_SUMMON_EVOLVE_LEVEL ) ? m_nMaxReachedLevel - GameRule::NORMAL_SUMMON_EVOLVE_LEVEL : 0; } break; default: break; } return nJP; } int StructSummon::IsLearnableSkill( int nSkillID, int nSkillLevel, int *nSkillTreeID ) { int nResult = RESULT_ACCESS_DENIED; // 우선 전 진화 단계에서 배울 수 있는 스킬 트리로 한번 검색해주고. for( int i = 0 ; i < 2 && ( nResult == RESULT_ACCESS_DENIED || nResult == RESULT_LIMIT_MAX || nResult == RESULT_NOT_ENOUGH_JOB_LEVEL ) ; ++i ) { int nSummonID = GetPrevJobId( i ); if( !nSummonID ) continue; const SummonBase *pBase = GameContent::GetSummonInfo( nSummonID ); if( !pBase ) continue; int nLevel = GetPrevJobLevel( i ); for( int j = 0 ; j < 5 ; ++j ) { if( !pBase->skill_tree_id[ j ] ) continue; nResult = GameContent::isLearnableSkill( this, nSkillID, nSkillLevel, pBase->skill_tree_id[ j ], nLevel ); if( nResult == RESULT_SUCCESS ) { *nSkillTreeID = pBase->skill_tree_id[ j ]; break; } } } // 전 진화단계에서 못 찾았다면 현재 소환수 코드로 한번 더 검색 (단 ERROR 조건이 밑 3개가 아니라면 검사할 필요가 없음.) if( nResult == RESULT_ACCESS_DENIED || nResult == RESULT_LIMIT_MAX || nResult == RESULT_NOT_ENOUGH_JOB_LEVEL ) { const SummonBase *pBase = GameContent::GetSummonInfo( GetJobId() ); if( !pBase ) return nResult; int nLevel = GetJobLevel(); for( int i = 0 ; i < 5 ; ++i ) { if( !pBase->skill_tree_id[ i ] ) continue; nResult = GameContent::isLearnableSkill( this, nSkillID, nSkillLevel, pBase->skill_tree_id[ i ], nLevel ); if( nResult == RESULT_SUCCESS ) { *nSkillTreeID = pBase->skill_tree_id[ i ]; break; } } } return nResult; } void StructSummon::onResetSkill( const int jobDepth ) { GetMaster()->Save(); ::SendSkillMessage( GetMaster(), this, -1, true ); } void StructSummon::onRemoveSkill( StructSkill* removedSkill ) { DBQuery( new DB_DeleteSkill( this, removedSkill->GetSkillUID() ) ); } void StructSummon::procMoveSpeedChangement() { if( !IsMoving() ) return; ArPosition pos = GetCurrentPosition( GetArTime() ); // 이동 불가가 되었다면 현재 위치에 정지 if( !IsMovable() ) { ArcadiaServer::Instance().SetMove( this, pos, pos, 0, true, GetArTime() ); } // 이동 중이었고 이동 가능 상태라면 현재 이동 속도와 스텟상의 이동 속도가 다를 경우 다시 이동 시킴 else { unsigned char nSpeed = GetRealMoveSpeed(); // 라이딩 소환수여서 주인이 타고 있는 상태라면 라이딩 이속 적용 StructPlayer * pMaster = GetMaster(); if( pMaster && pMaster->IsRiding() && pMaster->GetRideObject() == this ) { nSpeed = pMaster->GetRealRidingSpeed(); } // 라이딩 상태가 아니라면 주인과 이속 싱크 상태(onMoveRequest에서 pMsg->speed_sync 처리 참조)에 대한 처리를 해야 하지만 // 현재 이동 중인 속도가 싱크에 의한 이속인지 아닌지 알 수가 없으므로 여기서는 그냥 스텟상 고유 이속으로 이동되도록 함 // (이 위치는 이동 중에 버프가 걸리거나 풀리는 경우만이므로 그런 이속 증폭의 혜택을 못 받는 걸로 무마!) if( GetSpeed() != nSpeed ) { 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, nSpeed, true, GetArTime() ); } } } void StructSummon::onAfterRemoveState( StructState & state, const bool bByDead ) { // 부활에 의한 지속효과 복구 기능을 구현하려면 bByDead가 true일 경우에 만료 처리를 하지 않고 DB에 저장하도록 해야한다. // StructPlayer의 onAfterRemoveState 참조 state.Expire( this ); SetNeedToUpdateState(); StructCreature::onAfterRemoveState( state ); } void StructSummon::onUpdateState( StructState & state, bool bIsExpire ) { if( GetMaster() && GetMaster()->IsInWorld() ) { SendStateMessage( GetMaster(), GetHandle(), &state, bIsExpire ); } StructCreature::onUpdateState( state, bIsExpire ); if( state.GetBaseEffectID() && this->GetPriority() < ArSchedulerObject::UPDATE_PRIORITY_HIGH ) { ArcadiaServer::Instance().SetObjectPriority( this, ArSchedulerObject::UPDATE_PRIORITY_HIGH ); } } void StructSummon::onChangeProperty( const std::string & strKey, const char *data ) { const char *szKey = strKey.c_str(); if( !_stricmp( szKey, "lv" ) || !_stricmp( szKey, "level" ) ) { // m_nLevel은 이미 증가된 상태이므로 잡 포인트 획득 및 기타 처리가 // 정상적으로 onExpChange에서 이루어지지 않게 됨 // 명령어에 의해서만 프로퍼티가 변경되므로 그냥 1에서 업한 것처럼 처리 m_nLevel = 0; SetEXP( ( atoi(data) > 1 ) ? GameContent::GetNeedSummonExp( atoi(data) - 1 ) : 0 ); return; } else if( !_stricmp( szKey, "exp" ) ) { onExpChange(); return; } else if( !_stricmp( szKey, "hp" ) ) { BroadcastHPMPMsg( this, atoi( data ), 0 ); return; } else if( !_stricmp( szKey, "mp" ) ) { BroadcastHPMPMsg( this, 0, atoi( data ) ); return; } SendPropertyMessage( GetMaster(), GetHandle(), szKey, GetAsString( szKey ).c_str() ); } void StructSummon::DBQuery( GameDBManager::DBProc *pWork ) { THREAD_SYNCRONIZE( m_bQueryLock ); if( m_lQueryList.empty() ) { DB().Push( pWork ); } m_lQueryList.push_back( pWork ); } void StructSummon::onEndQuery() { THREAD_SYNCRONIZE( m_bQueryLock ); m_lQueryList.pop_front(); if( !m_lQueryList.empty() ) { DB().Push( m_lQueryList.front() ); } } bool StructSummon::IsDeleteable() { THREAD_SYNCRONIZE( m_bQueryLock ); // 스케쥴러에 등록되어 있으면 삭제 불가 && 다른 쓰레드에서 참조중이면 삭제 불가 if( !GameObject::IsDeleteable() ) return false; // DB 쿼리 미결된게 있다면 삭제 불가 if( !m_lQueryList.empty() ) return false; return true; } bool StructSummon::IsEnemy( StructCreature* pTarget, bool bIncludeHiding ) { return GetMaster()->IsEnemy( pTarget, bIncludeHiding ); } bool StructSummon::IsVisible( StructCreature *pTarget ) { StructPlayer * pMaster = GetMaster(); return ( pMaster ) ? pMaster->IsVisible( pTarget ) : StructCreature::IsVisible( pTarget ); } bool StructSummon::IsSkillCastable() { if( IsRidable() && GetMaster() && GetMaster()->GetRideObject() == this ) return false; return StructCreature::IsSkillCastable(); } bool StructSummon::IsMagicCastable() { if( IsRidable() && GetMaster() && GetMaster()->GetRideObject() == this ) return false; return StructCreature::IsMagicCastable(); } ItemBase::ItemWearType StructSummon::GetAbsoluteWearPos( ItemBase::ItemWearType & pos ) { if( pos < ItemBase::MAX_ITEM_WEAR && pos >= 0 ) { for( int i = 0; i < ItemBase::MAX_ITEM_WEAR; ++i ) { if( !m_anWear[i] ) continue; ItemBase::ItemWearType type = m_anWear[i]->GetWearType(); if( type == pos ) { return static_cast< ItemBase::ItemWearType >( i ); } if( type == ItemBase::WEAR_TWOFINGER_RING && pos == ItemBase::WEAR_RING ) { return static_cast< ItemBase::ItemWearType >( i ); } if( type == ItemBase::WEAR_TWOHAND && pos == ItemBase::WEAR_WEAPON ) { return static_cast< ItemBase::ItemWearType >( i ); } if( type == ItemBase::WEAR_SUMMON_ONLY && i == pos ) { return static_cast< ItemBase::ItemWearType >( i ); } } } return static_cast< ItemBase::ItemWearType >( -1 ); } struct StructItem* StructSummon::GetWearedItem( ItemBase::ItemWearType idx ) const { if( idx < ItemBase::MAX_ITEM_WEAR && idx >= 0 ) { for( int i = 0; i < ItemBase::MAX_ITEM_WEAR; ++i ) { if( !m_anWear[i] ) continue; ItemBase::ItemWearType type = m_anWear[i]->GetWearType(); if( type == idx ) { return m_anWear[i]; } if( type == ItemBase::WEAR_TWOFINGER_RING && idx == ItemBase::WEAR_RING ) { return m_anWear[i]; } if( type == ItemBase::WEAR_TWOHAND && idx == ItemBase::WEAR_WEAPON ) { return m_anWear[i]; } if( type == ItemBase::WEAR_SUMMON_ONLY && i == idx ) { return m_anWear[i]; } } } return NULL; } struct StructItem* StructSummon::GetSummonWearedItem(ItemBase::ItemWearType idx) const { if (idx < ItemBase::MAX_ITEM_WEAR && idx >= 0) return m_anWear[idx]; return NULL; } void StructSummon::ProcEtherealDurabilityConsumption( const bool bIsAttacker, const DamageType eDamageType, const int nDamage ) { // 실제 소모량 = (기본 소모량) * (소모율) * (상황에 따른 소모율) // 기본 소모량 const int nBaseConsumption = GameRule::GetEtherealDurabilityBaseConsumption( bIsAttacker, eDamageType ); // 기본 소모량이 0이면 소모율과 상황에 다른 소모율이 몇이어도 소모되지 않음 if( !nBaseConsumption ) return; // 소모율(계산값) c_fixed10 fConsumeRate( GameRule::GetEtherealDurabilityConsumeRate( GetLevel(), GetJobId(), bIsAttacker, nDamage ) ); // 상황에 따른 보정치(기본값 100%) c_fixed10 fEnvironmentalConsumeRate( 1 ); StructPlayer * pMaster = GetMaster(); // 상태에 따른 소모율 계산(소환수 자신은 관계 없고 주인의 상태를 따름) if( pMaster && pMaster->IsInWorld() ) { // 대련 상대끼리나 데스매치 존, 배틀아레나 안에서는 5%(대련 상대가 아닌 대상에게 피격당할 경우 대련 상태가 풀리므로 별도 체크 안 함) // 본래대로라면 대련이 시작된 이후, 즉 IsInStartedCompete() 함수로 체크해야 하나 매 공격마다 체크하기엔 부하가 있으므로 일단 예외로 둠 if( pMaster->GetCompeteID() || pMaster->IsInDeathmatch() || pMaster->IsInBattleArena() ) fEnvironmentalConsumeRate = GameRule::ETHEREAL_DURABILITY_ENVIRONMENTAL_CONSUME_RATE_ON_PVP; // PK On 상태면 200% else if( pMaster->IsPKOn() || pMaster->IsPKOffing() ) fEnvironmentalConsumeRate = GameRule::ETHEREAL_DURABILITY_ENVIRONMENTAL_CONSUME_RATE_ON_PK; } // 장착된 아이템에 에테리얼 내구도 감소 적용 bool bNeedToCalculateStat = false; for( int i = 0 ; i < m_nMaxItemWear ; ++i ) { static __declspec( thread ) StructItem * pItemForDebugging; StructItem * pItem = m_anWear[ i ]; pItemForDebugging = pItem; // 아이템이 없거나 공격 시인데 무기가 아니거나 피격 시인데 무기면 패스 if( !pItem || bIsAttacker != pItem->IsWeapon() ) continue; if( !pItem->ProcEtherealDurabilityConsumption( nBaseConsumption, fConsumeRate, fEnvironmentalConsumeRate ) ) continue; if( pMaster && pMaster->IsInWorld() ) SendItemMessage( pMaster, pItem ); // 새로 파괴된 아이템이 있으면 스텟 재 계산 if( !pItem->GetCurrentEtherealDurability() ) { StructPlayer *pPlayer = GetMaster(); if( pPlayer ) { LOG::Log11N4S( LM_ITEM_ETHEREAL_EXHAUST, pPlayer->GetAccountID(), pPlayer->GetSID(), // N1, N2 pItem->GetItemEnhance() * 100 + pItem->GetItemLevel(), pItem->GetItemCode(), // N3, N4 GetSID(), 0, 0, pItem->GetMaxEtherealDurability(), // N5, N6, N7, N8 GetX(), GetY(), pItem->GetItemUID(), // N9, N10, N11 pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, GetName(), LOG::STR_NTS, bIsAttacker?"ATTACKING":"BEING_ATTACKED", LOG::STR_NTS ); } bNeedToCalculateStat = true; } } // 스텟 재계산이 필요할 경우 재계산 if( bNeedToCalculateStat ) CalculateStat(); } unsigned short StructSummon::putonItem( ItemBase::ItemWearType pos , struct StructItem * pItem ) { StructPlayer *pMaster = GetMaster(); //AziaMafia PetSlot //if (!pMaster || !pMaster->IsInWorld() || !pMaster->IsLoginComplete() || !pItem->GetInstanceFlag().IsOn(ItemInstance::ITEM_FLAG_CARD)) if( !pMaster || !pMaster->IsInWorld() || !pMaster->IsLoginComplete() ) { return RESULT_ACCESS_DENIED; } unsigned short nRet = StructCreature::putonItem( pos, pItem ); if( nRet != RESULT_SUCCESS ) return nRet; // floyd 2008. 7..3 // http://bug.nflavor.com/view.php?id=3380 관련 문제를 수정하다가 StructCreate::putonItem에서 옮겨옴 pItem->SetOwnSummonInfo( GetHandle(), GetSID() ); // 아이템 착용에 의한 스크립트 실행 if( !pItem->GetItemBase().strScript.empty() ) { std::string strScript( pItem->GetItemBase().strScript ); const char * pszProcScript = "on_equip_item"; // 스크립트 내용에 on_equip_item이 있을 경우 파라미터 추가 if( strstr( strScript.c_str(), pszProcScript ) ) { // on_equip_item( 아이템 코드, 착용자 타입, 착용자 핸들, [소환수인 경우에만]Summon 코드값, [소환수인 경우에만]주인 핸들, 착용 위치 ) std::string strOnEquipItem; XStringUtil::Format( strOnEquipItem, "on_equip_item( %d, %d, %u, %d, %u, %d )", pItem->GetItemCode(), StructItem::TARGET_TYPE_SUMMON, GetHandle(), GetSummonCode(), ( GetMaster() ) ? GetMaster()->GetHandle() : 0, pos ); XStringUtil::Replace( strScript, pszProcScript, strlen( pszProcScript ), strOnEquipItem.c_str(), strOnEquipItem.length() ); } // 일부 스크립트 함수에 의한 기능에서 대화 중인 NPC 핸들 값에 의해 제어되는 부분이 있으므로 // 아이템에 의한 스크립트 실행 동안은 대화 중인 NPC 핸들을 0으로 설정함 AR_HANDLE nContactNPCHandle = pMaster->GetContactNPCHandle(); pMaster->SetContactNPCHandle( 0 ); LUA()->RunString( strScript.c_str() ); // 대화 중인 NPC 핸들 값을 원래대로 되돌림 pMaster->SetContactNPCHandle( nContactNPCHandle ); } // 착용된 아이템은 더 이상 무게에 포함되지 않으므로 뺀다 if( m_anWear[ pos ] == pItem ) pMaster->GetInventory()->AddWeightModifier( -pItem->GetWeight() ); pMaster->UpdateWeightWithInventory(); pMaster->UpdateQuestStatusByItemUpgrade(); return RESULT_SUCCESS; } unsigned short StructSummon::putoffItem( ItemBase::ItemWearType pos ) { StructPlayer *pMaster = GetMaster(); if( !pMaster || !pMaster->IsInWorld() || !pMaster->IsLoginComplete() ) { return RESULT_ACCESS_DENIED; } if( !m_anWear[ pos ] ) return RESULT_NOT_EXIST; StructItem *pItem = m_anWear[ pos ]; // floyd 2008. 7..3 // http://bug.nflavor.com/view.php?id=3380 // 크리쳐가 착용했던 장비의 소환수 정보를 지워주지 않아서 드물지만 복잡한 프로세스를 거치면 캐릭터가 장비하고 있는데도, 크리쳐가 입고 있는 것으로 인식하게 됨 pItem->SetOwnSummonInfo( 0, 0 ); unsigned short nRet = StructCreature::putoffItem( pos ); // 착용되지 않은 아이템은 무게에 다시 포함되어야 함 if( m_anWear[ pos ] == NULL ) pMaster->GetInventory()->AddWeightModifier( pItem->GetWeight() ); pMaster->UpdateWeightWithInventory(); pMaster->UpdateQuestStatusByItemUpgrade(); return nRet; } bool StructSummon::DoEvolution( int nTargetCode ) { //ARCADIA_LOCK( ArcadiaServer::Instance().LockObjectWithVisibleRange( this ) ); int prev_hp = GetHP(); int prev_mp = GetMP(); if( GetMaster()->IsRiding() && GetMaster()->GetRideObject() == this ) return false; int nCurrentCode = m_pContentInfo->uid; int nCurrentLevel = GetLevel(); if( nTargetCode == 0 ) { if( m_pContentInfo->evolve_target != 0 ) nTargetCode = m_pContentInfo->evolve_target; else return false; } SetSummonInfo( nTargetCode ); CalculateStat(); GetMaster()->Save(); TS_SC_SUMMON_EVOLUTION msg; msg.summon_handle = GetHandle(); // 소환수 핸들 msg.card_handle = GetParentCard()->GetHandle(); s_strcpy( msg.name, _countof( msg.name ), GetName() ); // 소환수 이름 msg.code = GetSummonCode(); if( IsInWorld() ) { ArcadiaServer::Instance().Broadcast( GetRX(), GetRY(), GetLayer(), &msg ); } else if( GetMaster() ) { PendMessage( GetMaster(), &msg ); } if( !IsVisibleRegion( GetRX(), GetRY(), GetMaster()->GetRX(), GetMaster()->GetRY() ) ) { PendMessage( GetMaster(), &msg ); } SendStatInfo( GetMaster(), this ); // 소환수 스탯정보 얻어서 // 보내주자 SendHPMPMsg( GetMaster(), this, GetHP() - prev_hp, GetMP() - prev_mp ); // HP/MP SendLevelMsg( GetMaster(), this ); // 레벨 SendExpMsg( GetMaster(), this ); // 경치 SendSPMsg( GetMaster(), this ); __int64 nCardUID = 0; StructItem * pItem = GetParentCard(); if( pItem ) { nCardUID = pItem->GetItemUID(); int depth = GetTransformLevel(); int i; for( i = 0; i < depth - 1; ++i ) { pItem->SetSocketCode( i + 1, GetPrevJobLevel( i ) ); } pItem->SetSocketCode( i + 1, GetLevel() ); pItem->SetSummonCode( GetSummonCode() ); pItem->DBQuery( new DB_UpdateItem( pItem ) ); if( GetMaster() ) { SendItemMessage( GetMaster(), GetParentCard() ); } } LOG::Log11N4S( LM_SUMMON_EVOLUTION, GetMaster()->GetAccountID(), GetMaster()->GetSID(), GetSID(), nCurrentCode, nTargetCode, nCurrentLevel, GetPrevJobId( 0 ), GetPrevJobLevel( 0 ), GetPrevJobId( 1 ), GetPrevJobLevel( 1 ), nCardUID, GetMaster()->GetAccountName(), LOG::STR_NTS, GetMaster()->GetName(), LOG::STR_NTS, GetName(), LOG::STR_NTS, "", 0 ); return true; } bool StructSummon::Reborn() { bool bSuccess = true; if( GetMaster()->IsRiding() && GetMaster()->GetRideObject() == this ) return false; // 스킬 리셋부터 하고 bSuccess = ResetSkill( SRM_SUMMON_ENHANCE ); if( !bSuccess ) return bSuccess; int nTargetCode = m_nPrevJobId[0]; if( !nTargetCode ) nTargetCode = m_pContentInfo->uid; m_nEXP = 0; m_nTransformLevel = 1; m_nJobLevel = m_nLevel = m_nMaxReachedLevel = 1; m_nPrevJobId[0] = m_nPrevJobId[1] = m_nPrevJobId[2] = 0; m_nPrevJobLevel[0] = m_nPrevJobLevel[1] = m_nPrevJobLevel[2] = 0; m_nLastDecreasedEXP = 0; if( GetParentCard() ) { GetParentCard()->SetInstanceFlagOff( ItemInstance::ITEM_FLAG_SUMMON_DURABILITY ); for( int i = 1; i < ItemBase::MAX_SOCKET_NUMBER; ++i ) { GetParentCard()->SetSocketCode( i, 0 ); } } bSuccess = DoEvolution( nTargetCode ); if( !bSuccess ) return bSuccess; // 크리쳐 강화의 경우 JP 보너스 추가가 있고 레벨이 1로 초기화되기 때문에 JP를 새로 계산해 반납해준다. // 나중에 새로운 JP 계산 시스템이나 한두개의 스킬만 JP를 반납하는 등의 시스템이 생기면 // 아래 getJPAfterSkillReset 코드를 통해 고려해줘야한다. SetJP( getJPAfterSkillReset() ); SetTalentPoint( getTPAfterSkillReset() ); return bSuccess; } bool StructSummon::IsAlly( StructCreature *pTarget ) { return GetMaster()->IsAlly( pTarget ); } int StructSummon::onDamage( StructCreature * pFrom, Elemental::Type elementalType, DamageType damageType, int nDamage, bool bCritical ) { StructPlayer * pMaster = GetMaster(); if( ( pMaster->IsRiding() && pMaster->GetRideObject() == this ) && ( damageType == DT_NORMAL_PHYSICAL_DAMAGE || damageType == DT_NORMAL_MAGICAL_DAMAGE || damageType == DT_NORMAL_PHYSICAL_LEFT_HAND_DAMAGE || damageType == DT_NORMAL_PHYSICAL_SKILL_DAMAGE ) ) { if( ( bCritical && XRandom() % 100 <= pMaster->GetUnMountProbabilityOnCriticalDamage() ) || XRandom() % 100 <= pMaster->GetUnMountProbabilityOnDamage() ) { pMaster->UnMount( StructPlayer::UNMOUNT_FALL, pFrom ); } } if( m_fSoulConnectionRatio && pMaster ) { int nPassDamage = std::min( int( nDamage * m_fSoulConnectionRatio), pMaster->GetHP() ); if( nPassDamage ) { pMaster->damage( pFrom, nPassDamage ); nDamage -= nPassDamage; // Master를 중심으로 VisibleRegion에 Lock이 걸려있다는 보장은 없으나 일단 대충;; // 이 부분이 문제가 될 경우 주인에게 락을 따로 걸고 데미지를 주기 위한 방법을 // 강구해야 함 BroadcastHPMPMsg( pMaster, 0 - nPassDamage, 0 ); } } AR_TIME t = GetArTime(); ArPosition pos = GetCurrentPosition( t ); if( pMaster && pMaster->IsInStartedCompete( true ) ) { StructPlayer * pAttackPlayer = NULL; if( pFrom->IsPlayer() ) pAttackPlayer = static_cast< StructPlayer * >( pFrom ); else if( pFrom->IsSummon() ) pAttackPlayer = static_cast< StructSummon * >( pFrom )->GetMaster(); if( !pAttackPlayer || pMaster->GetCompeteID() != pAttackPlayer->GetCompeteID() ) { CompeteManager::Instance().RetireCompeteWithPlayer( pMaster, COMPETE_END_BY_INTERRUPT ); } } return StructCreature::onDamage( pFrom, elementalType, damageType, nDamage, bCritical ); } int StructSummon::GetCreatureGroup() const { if( m_nChangingGroup != CREATURE_NONE ) return m_nChangingGroup; return m_pContentInfo->type; } void StructSummon::onCantAttack( AR_HANDLE target, AR_TIME t ) { if( m_nLastCantAttackTime + 100 < t ) { m_nLastCantAttackTime = t; SendCantAttackMsg( GetMaster(), GetHandle(), target, RESULT_TOO_FAR ); } } float StructSummon::GetScale() const { return m_pContentInfo->scale; } float StructSummon::GetSize() const { return m_pContentInfo->size; } int StructSummon::GetTransformLevel() const { return m_pContentInfo->evolve_type; } int StructSummon::GetEvolveTarget() const { return m_pContentInfo->evolve_target; } unsigned char StructSummon::GetRidingMoveSpeed() { // 튼튼한 발굽 등의 라이딩 속도 증가 아이템이 크리처 라이딩에도 적용 될 수 있게 변경 (2013-12-20) return ( m_pContentInfo->riding_speed + GetMaster()->GetRideSpeedModifier() ) / 7.0 * m_fRideSpeedMod; } char StructSummon::GetRate() const { return m_pContentInfo->rate; }