#include #include #include #include #include #include #include "LogClient/LogClient.h" #include "StructMonster.h" #include "Constant.h" #include "GameContent.h" #include "SendMessage.h" #include "StructItem.h" #include "StructPlayer.h" #include "StructSummon.h" #include "StructRoamer.h" #include "PartyManager.h" #include "GuildManager.h" #include "Extern.h" #include "GameRule.h" #include "GameProc.h" #include "GameMessage.h" #include "StructEventItemManager.h" #include "DungeonManager.h" #include "InstanceDungeonManager.h" #include "StructSkill.h" #include "GameAllocator.h" #include "LuaVM.h" #include "ThreadPlayerHelper.h" #include "toolkit/XEnv.h" #ifdef _MONSTER_DEBUG #define _monster_debug_output _oprint #else #define _monster_debug_output #endif XCriticalSection m_csHate( "m_csHate" ); struct takePriority { StructItem::ITEM_PICKUP_ORDER ItemPickupOrder; }; void StructMonster::BindProperty() { StructMonster temp( 0, 0 ); temp.Bind( "hp", &temp.m_nHP ); temp.Bind( "mp", &temp.m_nMP ); temp.Bind( "max_hp", &temp.m_nMaxHP ); temp.Bind( "max_mp", &temp.m_nMaxMP ); temp.Bind( "level", &temp.m_nLevel ); temp.Bind( "lv", &temp.m_nLevel ); 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( "x", &temp.mv.x ); temp.Bind( "y", &temp.mv.y ); temp.Bind( "layer", &temp.layer ); temp.Bind( "speed", const_cast< __int64 * >( &temp.m_Attribute.fMoveSpeed.get() ) ); temp.Bind( "critical", const_cast< __int64 * >( &temp.m_Attribute.fCritical.get() ) ); temp.Bind( "critical_power", const_cast< __int64 * >( &temp.m_Attribute.fCriticalPower.get() ) ); temp.Bind( "attack_point", const_cast< __int64 * >( &temp.m_Attribute.fAttackPointRight.get() ) ); temp.Bind( "attack_right", const_cast< __int64 * >( &temp.m_Attribute.fAttackPointRight.get() ) ); temp.Bind( "attack_left", const_cast< __int64 * >( &temp.m_Attribute.fAttackPointLeft.get() ) ); temp.Bind( "defence", const_cast< __int64 * >( &temp.m_Attribute.fDefence.get() ) ); temp.Bind( "block_defence", const_cast< __int64 * >( &temp.m_Attribute.fBlockDefence.get() ) ); temp.Bind( "magic_pt", const_cast< __int64 * >( &temp.m_Attribute.fMagicPoint.get() ) ); temp.Bind( "magic_def", const_cast< __int64 * >( &temp.m_Attribute.fMagicDefence.get() ) ); temp.Bind( "accuracy", const_cast< __int64 * >( &temp.m_Attribute.fAccuracyRight.get() ) ); temp.Bind( "accuracy_left", const_cast< __int64 * >( &temp.m_Attribute.fAccuracyLeft.get() ) ); temp.Bind( "avoid", const_cast< __int64 * >( &temp.m_Attribute.fAvoid.get() ) ); temp.Bind( "magic_avoid", const_cast< __int64 * >( &temp.m_Attribute.fMagicAvoid.get() ) ); temp.Bind( "block_chance", const_cast< __int64 * >( &temp.m_Attribute.fBlockChance.get() ) ); temp.Bind( "speed", const_cast< __int64 * >( &temp.m_Attribute.fMoveSpeed.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( "perfect_block", const_cast< __int64 * >( &temp.m_Attribute.fPerfectBlock.get() ) ); temp.Bind( "physical_ignore", const_cast< __int64 * >( &temp.m_Attribute.fPhysicalDefIgnore.get() ) ); temp.Bind( "physical_ignore_ratio", const_cast< __int64 * >( &temp.m_Attribute.fPhysicalDefIgnoreRatio.get() ) ); temp.Bind( "magical_ignore", const_cast< __int64 * >( &temp.m_Attribute.fMagicalDefIgnore.get() ) ); temp.Bind( "magical_ignore_ratio", const_cast< __int64 * >( &temp.m_Attribute.fMagicalDefIgnoreRatio.get() ) ); temp.Bind( "physical_penetration", const_cast< __int64 * >( &temp.m_Attribute.fPhysicalPenetration.get() ) ); temp.Bind( "physical_penetration_ratio", const_cast< __int64 * >( &temp.m_Attribute.fPhysicalPenetrationRatio.get() ) ); temp.Bind( "magical_penetration", const_cast< __int64 * >( &temp.m_Attribute.fMagicalPenetration.get() ) ); temp.Bind( "magical_penetration_ratio", const_cast< __int64 * >( &temp.m_Attribute.fMagicalPenetrationRatio.get() ) ); } StructMonster* StructMonster::AllocMonster( unsigned idx, unsigned char difficulty ) { StructMonster* p; struct _myIntializer : GameAllocateFunctor { _myIntializer( int _idx, unsigned char _difficulty ) : m_nIdx( _idx ), m_nDifficulty( _difficulty ) {} virtual void operator()( void * pObj, AR_HANDLE handle ) { new (pObj) StructMonster( handle, m_nIdx, m_nDifficulty ); } unsigned m_nIdx; unsigned char m_nDifficulty; }; AR_HANDLE handle = allocMonsterStruct( &p, _myIntializer( idx, difficulty ) ); // new (p) StructMonster( handle, idx ); if( !p->GetMonsterId() ) { freeMonsterStruct( p ); FILELOG( "Invalid Monster Id : %d", idx ); return NULL; } for( std::vector< MonsterBase::MONSTER_SKILL_INFO >::const_iterator it = p->m_pContentInfo->skill_info_list->begin() ; it != p->m_pContentInfo->skill_info_list->end() ; ++it ) { if( (*it).skill_id ) { p->SetSkill( StructSkill::SKILL_UID_MONSTER_SKILL, (*it).skill_id, (*it).skill_lv, 0 ); } } p->CalculateStat(); p->m_nHP = p->m_nMaxHP; p->m_nMP = p->m_nMaxMP; return p; } void StructMonster::FreeMonster( StructMonster* p ) { // _oprint( "DEL MONSTER : %08X\n", p ); // { 임시 /* extern std::vector< StructNPC* > g_vNPC; std::vector< StructNPC* >::iterator it; for( it = g_vNPC.begin(); it != g_vNPC.end(); it++ ) { if( *it != p ) continue; g_vNPC.erase( it ); break; } */ // } prepareFreeMonsterStruct( p ); p->StructMonster::~StructMonster(); freeMonsterStruct( p ); } StructMonster::StructMonster( AR_HANDLE handle, unsigned idx, unsigned char difficulty ) { m_hHandle = handle; m_nRegenTime = GetArTime(); m_bRespawnByScript = false; m_nArObjectType = ArObject::MOVABLE_OBJECT; //SetTag( static_cast< GameObject* >( this ) ); m_nStatus = STATUS_NORMAL; m_bComeBackHome = false; if ( idx != 0 ) { // idx == 0은 임시 객체임을 의미함 SetMonsterInfo( idx ); m_vTriggerFlag.reserve( m_pContentInfo->trigger_list->size() ); for( size_t nCount = 0 ; nCount < m_pContentInfo->trigger_list->size() ; ++nCount ) m_vTriggerFlag.push_back( 0 ); } m_nLastTrackTime = 0; m_nTotalDamage = 0; m_nStatus = 0; m_nGenerateCode = BY_NONE; m_pDeleteHandler = NULL; m_nLifeTime = 0; m_nDungeonId = 0; m_bIsDungeonRaidMonster = false; m_bIsDungeonOwnerGuardian = false; m_bIsDungeonSiegerGuardian = false; m_nInstanceRespawnID = 0; m_hFirstAttacker = 0; m_nFirstAttackTime = 0; m_nHateCheckTime = 0; m_vHateModifierByState.clear(); m_bNeedToFindEnemy = false; m_hTamer = 0; m_nTamedTime = INFINITE_TIME; m_RespawnX = m_RespawnY = 0; m_nMaxHate = 0; m_bTamedSuccess = false; m_nTamingSkillLevel = 0; m_pTamingSkill = NULL; m_nLastEnemyDistance = 0; m_nLastHateUpdateTime = 0; // 임시 : 랜덤하게 다른방향 보게 하자.. -_-; mv.face = (float)XRandom() / 100; m_bIsWandering = true; #ifndef _USE_NEW_ROAMING_ONLY m_pWayPointInfo = NULL; m_nWayPointIdx = 0; #endif m_pRoamer = NULL; m_bContinueAttack = false; m_nDifficulty = difficulty; } StructMonster::~StructMonster() { _monster_debug_output( "DELETE : %I64d\n", GetHandle() ); } bool StructMonster::ProcDelete() { FreeMonster( this ); return true; } bool StructMonster::SetMonsterInfo( unsigned idx ) { m_nLevel = GameContent::GetMonsterInfo( idx )->level; m_pContentInfo = GameContent::GetMonsterInfo( idx ); m_Stat = GameContent::GetStatInfo( m_pContentInfo->stat_id ); return true; } int StructMonster::GetMonsterId() { return m_pContentInfo->uid; } const char* StructMonster::GetName() const { return GameContent::GetString( m_pContentInfo->name_id ); } int StructMonster::GetNameID() const //AziaMafia get_monster_info { return m_pContentInfo->name_id; } unsigned char StructMonster::GetRace() const { return CREATURE_ETC; } float StructMonster::GetScale() const { return m_pContentInfo->scale; } float StructMonster::GetSize() const { return m_pContentInfo->size; } StructMonster::MONSTER_STATUS StructMonster::GetStatus() const { return static_cast< MONSTER_STATUS >( m_nStatus ); } void StructMonster::SetStatus( MONSTER_STATUS status ) { if( m_nStatus == STATUS_DEAD ) { return; } if( status != m_nStatus && status != STATUS_DEAD && status != STATUS_NORMAL && m_nStatus == STATUS_NORMAL ) { ResetTriggerCondition(); } if( m_nStatus != status ) { m_nStatus = status; BroadcastStatusMessage( this ); } else { m_nStatus = status; } } bool StructMonster::IsFirstAttacker() const { return ( m_pContentInfo->attack_type & MonsterBase::AT_FIRST_ATTACK ); } bool StructMonster::IsGroupRevenger() const { return ( m_pContentInfo->attack_type & MonsterBase::AT_RESPONSE_RACE ); } bool StructMonster::IsCastRevenger() const { return ( m_pContentInfo->attack_type & MonsterBase::AT_RESPONSE_CASTING ); } bool StructMonster::IsBattleRevenger() const { return ( m_pContentInfo->attack_type & MonsterBase::AT_RESPONSE_BATTLE ); } int StructMonster::GetMonsterGroup() const { return m_pContentInfo->monster_group; } bool StructMonster::IsGroupFirstAttacker() const { return ( m_pContentInfo->attack_type & MonsterBase::AT_GROUP_FIRST_ATTACK ); } bool StructMonster::IsTameable() const { return !!m_pContentInfo->taming_code; } const c_fixed10 & StructMonster::GetTamePercetage() const { ItemBase::ItemCode TameCode = GetTameItemCode(); return m_pContentInfo->taming_percentage; } float StructMonster::GetTameExpAdjust() const { return m_pContentInfo->taming_exp_adjust; } int StructMonster::GetTameCode() const { return m_pContentInfo->taming_code; } bool StructMonster::IsMonsterCreatureTame() const { if( m_pContentInfo->creature_taming_code_group ) return true; return false; } int StructMonster::GetMonsterCreatureTameGroupCode() const { if( IsMonsterCreatureTame() ) { return m_pContentInfo->creature_taming_code_group; } return 0; } int StructMonster::GetMonsterCreatureTameCode() const { if( IsMonsterCreatureTame() ) { return GameContent::GetMonsterCreatureTameCode( GetMonsterCreatureTameGroupCode() ); } return 0; } int StructMonster::GetMonsterCreatureTameItemCode() const { if( IsMonsterCreatureTame() ) { // 무조건 하나의 그룹에 있는 몬스터들은 무조건 동일한 크리쳐 카드를 써야 한다. return static_cast< ItemBase::ItemCode >( GameContent::GetSummonInfo( GetMonsterCreatureTameCode() )->card_id ); } return 0; } ItemBase::ItemCode StructMonster::GetTameItemCode() const { if( !m_pContentInfo->taming_code && !m_pContentInfo->creature_taming_code_group ) return 0; // taming_code와 creature_taming_code_group 가 전부 값이 존재하면 안됨. 하나는 0이어야 함 if( m_pContentInfo->taming_code && m_pContentInfo->creature_taming_code_group ) return 0; // 기존 크리쳐 카드 if( m_pContentInfo->taming_code ) { return static_cast< ItemBase::ItemCode >( GameContent::GetSummonInfo( m_pContentInfo->taming_code )->card_id ); } // 모든 몬스터 크리쳐 카드 ( 소울 테이밍 카드 ) if( m_pContentInfo->creature_taming_code_group ) { return GetMonsterCreatureTameItemCode(); } return 0; } int StructMonster::AddHate( AR_HANDLE handle, int pt, bool bBroadcast, bool bProcRoamingMonster ) { if( IsDead() || IsEnvironmentMonster() ) return 0; AR_TIME t = GetArTime(); StructCreature::iterator it = StructCreature::get( handle ); if( !(*it) ) return 0; if( !IsEnemy( (*it) ) ) return 0; int nHate = addHate( handle, pt )->nHate; // 동일 로밍 그룹 몬스터 헤이트 분배 if( bProcRoamingMonster && GetRoamer() ) { GetRoamer()->PendHateShare( GetHandle(), handle, m_fHateRatio * pt ); } if( pt < 0 ) { findNextEnemy(); } // 동족 인식 방송을 해야 하는 놈들이라도 그룹 로밍 몹이면 동족 인식 처리 안 함 else if( bBroadcast && IsGroupRevenger() && !GetRoamer() ) { // 자. 동족 몹들아~ 반격하자아~ std::vector< AR_HANDLE > vResult; ArcadiaServer::Instance().EnumMovableObject( GetPos(), GetLayer(), GetFirstAttackRange() * 0.8f, &vResult, false, true ); for( std::vector< AR_HANDLE >::iterator it = vResult.begin(); it != vResult.end(); ++it ) { StructCreature* pCreature = static_cast< StructCreature* >( StructCreature::raw_get( *it ) ); if( !pCreature ) continue; if( pCreature->IsMonster() && pCreature != this ) { if( static_cast< StructMonster * >( pCreature )->GetMonsterGroup() == GetMonsterGroup() ) { static_cast< StructMonster * >( pCreature )->AddHate( handle, m_fHateRatio * pt, false ); } } } } return nHate; } int StructMonster::RemoveHate( AR_HANDLE handle, int pt ) { _HATE_TAG * pHateTag = getHateTag( handle, GetArTime() ); if( pHateTag ) { if( pt < pHateTag->nHate ) { pHateTag->nHate -= pt; if( handle == m_hEnemy && !IsDead() ) { findNextEnemy(); } return pHateTag->nHate; } else { removeFromHateList( handle ); if( handle == m_hEnemy ) { // 문제 생기나? m_hEnemy = 0; findNextEnemy(); } return 0; } } return -1; } int StructMonster::GetHate( AR_HANDLE handle ) { _HATE_TAG * pHateTag = getHateTag( handle ); if( pHateTag ) return pHateTag->nHate; return 0; } _HATE_TAG * StructMonster::getHateTag( AR_HANDLE handle, AR_TIME t ) { THREAD_SYNCRONIZE( &m_csHate ); _HATE_TAG * pHateTag = NULL; std::vector< _HATE_TAG >::reverse_iterator it; for( it = m_vHateList.rbegin(); it != m_vHateList.rend(); it++ ) { if( (*it).uid == handle ) { pHateTag = &(*it); if( t ) pHateTag->nTime = t; break; } } return pHateTag; } _DAMAGE_TAG * StructMonster::getDamageTag( AR_HANDLE handle, AR_TIME t ) { _DAMAGE_TAG * pDamageTag = NULL; std::vector< _DAMAGE_TAG >::iterator it; for( it = m_vDamageList.begin(); it != m_vDamageList.end(); ) { if( (*it).uid == handle ) { pDamageTag = &(*it); if( t ) pDamageTag->nTime = t; break; } // 5분이 지나도록 공방이 없었다면 damage list 에서 제거 if( (*it).nTime + 30000 < t ) { m_nTotalDamage -= (*it).nDamage; it = m_vDamageList.erase( it ); continue; } it++; } return pDamageTag; } _HATE_TAG * StructMonster::addHate( AR_HANDLE handle, int nHate ) { _HATE_TAG * pHateTag = NULL; AR_TIME t = GetArTime(); pHateTag = getHateTag( handle, t ); if( !pHateTag ) { THREAD_SYNCRONIZE( &m_csHate ); m_vHateList.push_back( _HATE_TAG( t, handle, 0 ) ); pHateTag = &m_vHateList.back(); StructCreature::iterator it = StructCreature::get( handle ); (*it)->AddToEnemyList( GetHandle() ); } pHateTag->nHate += nHate; if( pHateTag->nHate < 0 ) pHateTag->nHate = 0; pHateTag->nLastMaxHate = pHateTag->nHate; if( !pHateTag->bIsActive ) { //pHateTag->bIsActive = true; // //int nFrenzyLev = 0; //AR_TIME nFrenzyTime = 0; // //if( pHateTag->nBadAttackCount == 1 ) //{ // nFrenzyLev = 1; // nFrenzyTime = 300; //} //else //{ // nFrenzyLev = 2; // nFrenzyTime = 500; //} // //AddState( StructState::FRENZY, GetHandle(), nFrenzyLev, t, t + nFrenzyTime ); pHateTag->nHate = 0; } if( !IsAttacking() ) { m_nMaxHate = pHateTag->nHate; StructCreature::iterator it = StructCreature::get( handle ); StructCreature * pTarget = (*it); if( pTarget ) { //pTarget->OnUpdate(); StartAttack( handle, false ); } } else if( m_hEnemy != handle && pHateTag->nHate > m_nMaxHate ) { m_nMaxHate = pHateTag->nHate; m_hEnemy = handle; } else if( m_hEnemy == handle ) { m_nMaxHate = pHateTag->nHate; } return pHateTag; } _DAMAGE_TAG * StructMonster::addDamage( AR_HANDLE handle, int nDamage ) { _DAMAGE_TAG * pDamageTag = NULL; AR_TIME t = GetArTime(); pDamageTag = getDamageTag( handle, t ); if( !pDamageTag ) { m_vDamageList.push_back( _DAMAGE_TAG( t, handle, 0 ) ); pDamageTag = &m_vDamageList.back(); } m_nTotalDamage += nDamage; pDamageTag->nDamage += nDamage; return pDamageTag; } void StructMonster::clearHateList() { THREAD_SYNCRONIZE( &m_csHate ); std::vector< _HATE_TAG >::iterator it; for( it = m_vHateList.begin(); it != m_vHateList.end(); it++ ) { StructCreature::iterator itTarget = StructCreature::get( (*it).uid ); if( *itTarget ) { ( *itTarget )->RemoveFromEnemyList( GetHandle() ); } } m_vHateList.clear(); } bool StructMonster::removeFromHateList( AR_HANDLE handle ) { THREAD_SYNCRONIZE( &m_csHate ); bool bSuccess = false; std::vector< _HATE_TAG >::iterator it; for( it = m_vHateList.begin(); it != m_vHateList.end(); it++ ) { if( (*it).uid == handle ) { m_vHateList.erase( it ); bSuccess = true; break; } } if( bSuccess ) { StructCreature::iterator itTarget = StructCreature::get( handle ); if( *itTarget ) { ( *itTarget )->RemoveFromEnemyList( GetHandle() ); } } return bSuccess; } bool StructMonster::removeFromDamageList( AR_HANDLE handle ) { bool bSuccess = false; std::vector< _DAMAGE_TAG >::iterator it; for( it = m_vDamageList.begin(); it != m_vDamageList.end(); it++ ) { if( (*it).uid == handle ) { m_nTotalDamage -= (*it).nDamage; m_vDamageList.erase( it ); bSuccess = true; break; } } return bSuccess; } int StructMonster::onDamage( StructCreature * pFrom, Elemental::Type elementalType, DamageType damageType, int nDamage, bool bCritical ) { nDamage = std::min( GetHP(), nDamage ); if( !nDamage ) return StructCreature::onDamage( pFrom, elementalType, damageType, nDamage, bCritical ); AR_TIME t = GetArTime(); int nHate = nDamage; if( !m_hFirstAttacker ) { if( pFrom->IsSummon() ) { if( static_cast< StructSummon * >( pFrom )->GetMaster() ) { m_hFirstAttacker = static_cast< StructSummon * >( pFrom )->GetMaster()->GetHandle(); } // 주인 없는 소환수가 친 경우 선공자 처리 안 함(어차피 소환수 곧 없어지고 몹은 제자리로...) } else { m_hFirstAttacker = pFrom->GetHandle(); } m_nFirstAttackTime = t; } bool bIsTamer = false; // Damage 증가 if( pFrom->IsSummon() ) { // 소환수가 때린거라면 실제로는 주인이 데미지를 입힌걸로 처리. StructSummon * pSummon = static_cast< StructSummon* >( pFrom ); if( pSummon->GetMaster() ) { addDamage( pSummon->GetMaster()->GetHandle(), nDamage ); // 테이머의 소환수가 때린거라면 테이밍 유지시간 증가 if( GetTamer() == pSummon->GetMaster()->GetHandle() ) bIsTamer = true; } } else { addDamage( pFrom->GetHandle(), nDamage ); // 테이머가 때린거라면 테이밍 유지시간 증가 if( GetTamer() == pFrom->GetHandle() ) bIsTamer = true; } // 테이밍 유지시간 증가 if( bIsTamer ) m_nTamedTime = t + GameRule::TAMING_INTERVAL; return StructCreature::onDamage( pFrom, elementalType, damageType, nDamage, bCritical ); } bool StructMonster::StartAttack( AR_HANDLE target, bool bNeedFastReaction ) { if( StructCreature::StartAttack( target, bNeedFastReaction ) ) { SetStatus( StructMonster::STATUS_ATTACK ); return true; } return false; } AR_HANDLE StructMonster::GetTamer() const { return m_hTamer; } void StructMonster::SetTamer( AR_HANDLE handle, StructSkill* pTamingSkill ) { m_hTamer = handle; m_nTamedTime = GetArTime() + GameRule::TAMING_INTERVAL; m_pTamingSkill = pTamingSkill; // FireSkill 이후 RequestSkillLevel은 초기화 되는데 크리처 테이밍은 전투후에 다시 이 정보가 필요하므로 스킬레벨만 따로 데이터 보관. if( pTamingSkill ) m_nTamingSkillLevel = pTamingSkill->GetRequestedSkillLevel(); else m_nTamingSkillLevel = 0; } int StructMonster::GetLevel() const { return m_pContentInfo->level; } const CreatureStat & StructMonster::GetBaseStat() const { return GameContent::GetStatInfo( m_pContentInfo->stat_id ); } static void addChaos( StructCreature *pCorpse, StructPlayer *pPlayer, float chaos ) { // 더이상 저장할 수 없으면 즐 if( pPlayer->GetMaxChaos() <= pPlayer->GetChaos() ) return; // 멀리 떨어져 있으면 KIN AR_TIME t = GetArTime(); AR_UNIT distance = pCorpse->GetCurrentPosition( t ).GetDistance( pPlayer->GetCurrentPosition( t ) ); if( distance > RANGE_LIMIT ) return; TS_SC_GET_CHAOS msg; int nChaos = GameRule::GetIntValueByRandom< int >( chaos ); msg.hPlayer = pPlayer->GetHandle(); msg.hCorpse = pCorpse->GetHandle(); msg.nChaos = nChaos; msg.nBonusType = TS_SC_GET_CHAOS::CHAOS_BONUS_NONE; msg.nBonusPercent = 0; msg.nBonus = 0; // 중독 방지 시스템 관련 제한 체크 AR_TIME nContinuousPlayTime = pPlayer->GetContinuousPlayTime(); if( pPlayer->IsGameTimeLimited() && nContinuousPlayTime >= GameRule::nMaxHealthyGameTime ) { chaos = GameRule::GetGameTimeLimitPenalty( nContinuousPlayTime ) * chaos; } else { switch( pPlayer->GetPCBangMode() ) { case GameRule::PCBANG_ALLY_BONUS: msg.nBonus = GameRule::fAllyPCBangChaosBonusRate * nChaos; msg.nBonusType = TS_SC_GET_CHAOS::CHAOS_BONUS_PCBANG; msg.nBonusPercent = GameRule::fAllyPCBangChaosBonusRate * 100; break; case GameRule::PCBANG_PREMIUM_BONUS: msg.nBonus = GameRule::fPremiumPCBangChaosBonusRate * nChaos; msg.nBonusType = TS_SC_GET_CHAOS::CHAOS_BONUS_PREMIUM_PCBANG; msg.nBonusPercent = GameRule::fPremiumPCBangChaosBonusRate * 100; break; default: break; } // 보너스 획득 라크가 0이면 보너스 메시지도 안 띄움 if( !msg.nBonus ) { msg.nBonusType = TS_SC_GET_CHAOS::CHAOS_BONUS_NONE; } msg.nChaos += msg.nBonus; } // 라크 획득이 음수일 경우 중단 if( chaos <= 0.0f ) return; ArcadiaServer::Instance().Broadcast( pCorpse->GetRX(), pCorpse->GetRY(),pCorpse->GetLayer(), &msg ); pPlayer->AddChaos( msg.nChaos ); LOG::Log11N4S( LM_LAC_TAKE, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, 0, msg.nChaos, pPlayer->GetChaos(), 0, 0, 0, 0, 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } static void addChaos( StructCreature *pCorpse, int nPartyID, float chaos ) { struct myPartyFunctor1 : PartyManager::PartyFunctor { myPartyFunctor1() { t = GetArTime(); } virtual bool operator()( AR_HANDLE handle ) { StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( !pPlayer || !pPlayer->IsInWorld() ) return true; // 멀리 떨어져 있으면 KIN AR_UNIT distance = pos.GetDistance( pPlayer->GetPos() ); if( distance > RANGE_LIMIT ) return true; vPlayers.push_back( pPlayer ); return true; } ArPosition pos; AR_TIME t; std::vector< StructPlayer * > vPlayers; } _tmp; _tmp.pos = pCorpse->GetCurrentPosition( GetArTime() ); PartyManager::GetInstance().DoEachMember( nPartyID, _tmp ); std::vector< StructPlayer * >::iterator it; float fSharedChaos = chaos / _tmp.vPlayers.size(); for( it = _tmp.vPlayers.begin(); it != _tmp.vPlayers.end(); ++it ) { addChaos( pCorpse, (*it), fSharedChaos ); } } static void addEXP( StructCreature *pCorpse, StructPlayer *pPlayer, float exp, float jp ) { if( pPlayer->IsDead() ) return; float fJP = jp; // { ImmoralPoint 보정 if( pPlayer->GetImmoralPoint() > 0 && pCorpse->GetLevel() >= pPlayer->GetLevel() ) { float fIPDec = -1; if( pPlayer->GetImmoralPoint() >= GameRule::CRIME_LIMIT ) { fIPDec = -0.5f; } if( pPlayer->GetPartyID() ) { fIPDec *= ( 2 / (float)( PartyManager::GetInstance().GetMemberCount( pPlayer->GetPartyID() ) + 1 ) ); } bool bIsNeedToSendStatus = false; if( pPlayer->GetImmoralPoint() >= GameRule::CRIME_LIMIT && ( pPlayer->GetImmoralPoint() + fIPDec ) < GameRule::CRIME_LIMIT ) { bIsNeedToSendStatus = true; } else if( pPlayer->GetImmoralPoint() >= GameRule::MORAL_LIMIT && ( pPlayer->GetImmoralPoint() + fIPDec ) < GameRule::MORAL_LIMIT ) { bIsNeedToSendStatus = true; } pPlayer->SetImmoralPoint( pPlayer->GetImmoralPoint() + fIPDec ); // 모럴 포인트 개념 추가로 음수 이모럴 포인트도 유효 //if( pPlayer->GetImmoralPoint() <= 0 ) //{ // bIsNeedToSendStatus = true; // pPlayer->SetImmoralPoint( 0 ); //} if( bIsNeedToSendStatus ) { BroadcastStatusMessage( pPlayer ); } } // } /* 개인 몫으로 분배된 경험치에, 잡힌 몬스터가 분배받을자의 레벨보다 낮으면 레벨차 패널티를 부가한다. 몬스터LV < 플레이어LV 이면, 플레이어가 받을 EXP = 분배된 EXP * { 1- ((플레이어LV-몬스터Lv) * 0.05) } 플레이어가 받을 JP = 분배된 JP * { 1- ((플레이어LV-몬스터Lv) * 0.05) }*/ #ifdef _EXPDEBUG _cprint( "경험치 계산! : %s Lv%d %s Lv%d\n", pCorpse->GetName(), pCorpse->GetLevel(), pPlayer->GetName(), pPlayer->GetLevel() ); _cprint( "경험치 1 : %f,%f (%s)\n", exp, fJP, pPlayer->GetName() ); #endif int nLevelDiff = pCorpse->GetLevel() - pPlayer->GetLevel(); if( nLevelDiff > 21 ) { exp = exp * std::max( ( 1.8f - ( nLevelDiff - 21 ) * 0.075 ), 0.0 ); fJP = fJP * std::max( ( 1.8f - ( nLevelDiff - 21 ) * 0.075 ), 0.0 ); } else { //AziaMafia Fix Xp Malus //original exp = exp * std::max( ( 1.095f - ( -nLevelDiff ) * 0.031 ), 0.0 ); fJP = fJP * std::max( ( 1.095f - ( -nLevelDiff ) * 0.031 ), 0.0 ); //Debut Modif AziaMafia /* if (-nLevelDiff <= 5 || nLevelDiff > 0 ) { exp = exp * std::max( ( 1.095f - ( -nLevelDiff ) * 0.031 ), 0.0 ); fJP = fJP * std::max( ( 1.095f - ( -nLevelDiff ) * 0.031 ), 0.0 ); } else if (-nLevelDiff > 5 && -nLevelDiff < 10 ) { exp = exp * std::max((0.365f - (-nLevelDiff) * 0.011), 0.0); fJP = fJP * std::max((0.365f - (-nLevelDiff) * 0.011), 0.0); } else { exp = exp * 0.0f; fJP = fJP * 0.0f; } */ //Fin Modif AziaMafia } #ifdef _EXPDEBUG _cprint( "경험치 2 : %f,%f (%s)\n", exp, fJP, pPlayer->GetName() ); #endif /* 보너스 jp 사라짐 float fBonusJP = 0.0f; if( pPlayer->GetJobLevel() >= fJP*2 ) fBonusJP = fJP * 2; else fBonusJP = pPlayer->GetJobLevel(); #ifdef _EXPDEBUG _cprint( "경험치 3 : %f,%f (%s)\n", exp, fJP, pPlayer->GetName() ); #endif if( pPlayer->GetLevel() > pCorpse->GetLevel() ) { int nGap = pPlayer->GetLevel() - pCorpse->GetLevel(); if( nGap >= 10 ) fBonusJP = 0; else if( 10 > nGap && nGap >= 3 ) { fBonusJP = fBonusJP * ( 1 - ( nGap - 2 ) * 0.1f ); } } fJP += fBonusJP; */ #ifdef _EXPDEBUG _cprint( "경험치 4 : %f,%f (%s)\n", exp, fJP, pPlayer->GetName() ); #endif // 멀리 떨어져 있으면 KIN AR_TIME t = GetArTime(); AR_UNIT distance = pCorpse->GetCurrentPosition( t ).GetDistance( pPlayer->GetCurrentPosition( t ) ); if( distance > RANGE_LIMIT ) return; std::string new_item; std::string strResult; //XStringUtil::Format(new_item, "return LootBot( '%s' , 3)", pPlayer->GetName()); //LUA()->RunString(new_item.c_str(), &strResult); //if (strResult == "true") return; if (exp < 1) return; // AziaMafia Fix Exp exp = 1; if (fJP < 0) fJP = 0; //AziaMafia AddExp Script ThreadPlayerHelper TPHelper(pPlayer); //char szScript[1024] = { 0, }; //s_sprintf(szScript, _countof(szScript), "by_add_exp(%i , \"%s\")", pCorpse->GetLevel(), pPlayer->GetName()); //LUA()->RunString(szScript); // 테이밍 한 플레이어면 경험치 조정 if( pCorpse->IsMonster() && static_cast< StructMonster* >( pCorpse )->GetTamer() == pPlayer->GetHandle() && static_cast< StructMonster* >( pCorpse )->IsTamingSuccessed() ) { exp *= static_cast< StructMonster* >( pCorpse )->GetTameExpAdjust(); fJP *= static_cast< StructMonster* >( pCorpse )->GetTameExpAdjust(); } float fEXPMod = GameRule::fEXPRate; float fCPMod = GameRule::fEXPRate; exp = fEXPMod * exp; fJP = fCPMod * fJP; __int64 nEXP = GameRule::GetIntValueByRandom< __int64 >( exp ); __int64 nJP = GameRule::GetIntValueByRandom< __int64 >( fJP ); pPlayer->AddExp( nEXP, nJP ); #ifdef _EXPDEBUG _cprint( "경험치 5 : %d,%d (%s)\n", nEXP, nJP, pPlayer->GetName() ); #endif // 힐러, 버퍼, 디버퍼 등, 공헌도를 따질 수 없는 직업을 위해 경험치 배분을 기준으로 Kill 체크를 한다. // 소환수 경험치 배분도 동일한 룰에 의해 시야 거리 밖이면 핸들러를 호출하지 않도록 한다. ArPosition pos = pPlayer->GetCurrentPosition( t ); StructSummon *pMainSummon = pPlayer->GetMainSummon(); StructSummon *pSubSummon = pPlayer->GetSubSummon(); pPlayer->OnKill( pCorpse ); if( pMainSummon && pos.GetDistance( pMainSummon->GetCurrentPosition( t ) ) <= GameRule::VISIBLE_RANGE ) pMainSummon->OnKill( pCorpse ); if( pSubSummon && pos.GetDistance( pSubSummon->GetCurrentPosition( t ) ) <= GameRule::VISIBLE_RANGE ) pSubSummon->OnKill( pCorpse ); } static void addEXP( StructCreature *pCorpse, int nPartyID, int exp, int jp ) { struct myPartyFunctor1 : PartyManager::PartyFunctor { myPartyFunctor1() { t = GetArTime(); nMaxLevel = 0; nTotalLevel = 0; nCount = 0; nTotalCount = 0; nMinLevel = GameRule::MAX_LEVEL; } virtual bool operator()( AR_HANDLE handle ) { ++nTotalCount; StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( !pPlayer || !pPlayer->IsInWorld() ) return true; // 멀리 떨어져 있으면 KIN if( layer != pPlayer->GetLayer() ) return true; AR_UNIT distance = pos.GetDistance( pPlayer->GetPos() ); if( distance > RANGE_LIMIT ) return true; pOneManPlayer = pPlayer; // One Man Party 처리를 위해 if( nMaxLevel < pPlayer->GetLevel() ) nMaxLevel = pPlayer->GetLevel(); if( nMinLevel > pPlayer->GetLevel() ) nMinLevel = pPlayer->GetLevel(); nTotalLevel += pPlayer->GetLevel(); nCount++; return true; } ArPosition pos; unsigned char layer; AR_TIME t; int nTotalCount; int nCount; int nMaxLevel; int nMinLevel; int nTotalLevel; StructPlayer * pOneManPlayer; } _tmp; _tmp.pos = pCorpse->GetCurrentPosition( GetArTime() ); _tmp.layer = pCorpse->GetLayer(); PartyManager::GetInstance().DoEachMember( nPartyID, _tmp ); // 파티원이 없으면 if( _tmp.nCount < 1 ) { return; } // 적용되는 파티원이 혼자이면.. if( _tmp.nCount < 2 ) { addEXP( pCorpse, _tmp.pOneManPlayer, exp, jp ); return; } struct myPartyFunctor : PartyManager::PartyFunctor { virtual bool operator()( AR_HANDLE handle ) { StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( !pPlayer ) return true; // 파티원 개인에게 돌아갈 경험치 = ( ( 개인Lv / 전 파티원 Lv 총합 ) * 파티에 분배된 경험치) * 레벨차보정팩터 // 파티별로 할당된 경험치가 파티원들에게 분배된다. 이때는 데미지량, 공헌도에 관계없이 파티원의 LV에 따라 분배한다. 힐러, 버퍼, 디버퍼등, 공헌도를 따질 수 없는 직업이 다수 존재하기 때문이다. float fT = ( (float)pPlayer->GetLevel() / nTotalLevel ); float fEXP = nSharedEXP * fT; float fJP = nSharedJP * fT; // 파티내 최고레벨 플레이어보다 15레벨 이상 낮은 레벨의 플레이어는 10레벨 단위로 10%씩의 경험치 패널티를 받는다. (100레벨차부터는 100% 패널티로 경험치를 전혀 얻지 못한다.) // 레벨차보정팩터 = 1 - [ { (파티내 최고레벨 아바타의 Lv - 자신의 Lv)*0.1} * 0.1] 단, 레벨차 보정팩터가 0이하일경우 0으로 고정, 1이상일경우 1로 고정한다. float fLevelPenalty = 1 - int( ( nMaxLevel - pPlayer->GetLevel() - GameRule::nPartyExpPenaltyLevel ) * 0.1f ) * 0.1f; fLevelPenalty = std::max( 0.0f, std::min( 1.0f, fLevelPenalty ) ); fEXP *= fLevelPenalty; fJP *= fLevelPenalty; // fPartyEXPRate[ n ] = 파티원이 n+2명일때 적용 되는 계수 (예: 파티원 2명 -> fPartEXPRate[0] 참조) if( nMembers > 1 ) { fEXP *= GameRule::fPartyEXPRate[ nMembers - 2]; fJP *= GameRule::fPartyEXPRate[ nMembers - 2]; } if( fEXP < 1 ) fEXP = 1; addEXP( pCorpse, pPlayer, fEXP, fJP ); return true; } StructCreature *pCorpse; int nMaxLevel; int nTotalLevel; __int64 nSharedEXP; int nSharedJP; int nMembers; } _fo; int nLevelDiff = _tmp.nMaxLevel - _tmp.nMinLevel; if( _tmp.nTotalCount + 40 > nLevelDiff && nLevelDiff >= _tmp.nTotalCount + GameRule::nPartyExpPenaltyLevel ) { float fLevelPenalty = 1 - pow( ( nLevelDiff - _tmp.nCount - GameRule::nPartyExpPenaltyLevel ), 1.1f ) * 0.02f; exp *= fLevelPenalty; jp *= fLevelPenalty; } else if( nLevelDiff >= _tmp.nTotalCount + 40 ) { exp = 0; jp = 0; } _fo.nMaxLevel = _tmp.nMaxLevel; _fo.nTotalLevel = _tmp.nTotalLevel; // AziaMafia Group XP // _fo.nSharedEXP = exp * ( 1 + _tmp.nCount * _tmp.nCount * 2 * 0.01 ); _fo.nSharedEXP = exp ; // original _fo.nSharedJP = jp * ( 1 + _tmp.nCount * _tmp.nCount * 2 * 0.01 ); _fo.pCorpse = pCorpse; _fo.nMembers = _tmp.nCount; PartyManager::GetInstance().DoEachMember( nPartyID, _fo ); } void StructMonster::calcPartyContribute( StructCreature * pKiller, std::vector< VIRTUAL_PARTY > &vPartyContribute ) { if( m_vDamageList.empty() ) return; struct PARTY_DAMAGE { PARTY_DAMAGE() { nDamage = 0; nLevel = 0; } PARTY_DAMAGE( int _damage, int _level ) : nDamage( _damage ), nLevel( _level ) { } int nDamage; int nLevel; }; // 공헌도 맵 AR_TIME t = GetArTime(); AR_HANDLE hKiller = 0; std::vector< VIRTUAL_PARTY >::iterator itVector; std::map< int, PARTY_DAMAGE > mapPartyDamageContribute; std::map< int, PARTY_DAMAGE >::iterator itMap; int nTamerPartyID = 0; int nFirstAttackPartyID = 0; int nLastAttackPartyID = 0; float fMaxDamageAdd = 0.1f; // 최다 뎀지 보너스 vPartyContribute.reserve( m_vDamageList.size() ); // 1분 이상 지났으면 선공 보너스 사라짐 if( m_nFirstAttackTime + GameRule::MAX_FIRST_ATTACK_BONUS_TIME < t ) { fMaxDamageAdd = 0.4f; // 선공 보너스를 최다 뎀지 입힌 파티에게로.. m_hFirstAttacker = NULL; } // 막타 넣은사람 찾기 if( pKiller->IsSummon() ) { StructPlayer * pPlayer = static_cast< StructSummon* >( pKiller )->GetMaster(); if( pPlayer ) { hKiller = pPlayer->GetHandle(); nLastAttackPartyID = pPlayer->GetPartyID(); } } else if( pKiller->IsPlayer() ) { hKiller = pKiller->GetHandle(); nLastAttackPartyID = static_cast< StructPlayer* >( pKiller )->GetPartyID(); } // mapPartyDamageContribute 구성 std::vector< _DAMAGE_TAG >::iterator it; for( it = m_vDamageList.begin(); it != m_vDamageList.end(); it++ ) { StructCreature::iterator pit( StructCreature::get( (*it).uid ) ); StructCreature *pCreature = *pit; if( !pCreature ) continue; StructPlayer *pPlayer = ( pCreature->IsPlayer() ? static_cast< StructPlayer * >( pCreature ) : NULL ); StructSummon *pSummon = ( pCreature->IsSummon() ? static_cast< StructSummon * >( pCreature ) : NULL ); if( !pPlayer && !pSummon ) continue; if( !pPlayer ) pPlayer = pSummon->GetMaster(); // 파티 데미지 계산 if( pPlayer->IsInParty() ) { // 테이머 파티 저장 if( GetTamer() == pPlayer->GetHandle() ) nTamerPartyID = pPlayer->GetPartyID(); itMap = mapPartyDamageContribute.find( pPlayer->GetPartyID() ); if( itMap == mapPartyDamageContribute.end() ) { mapPartyDamageContribute[ pPlayer->GetPartyID() ] = PARTY_DAMAGE( (*it).nDamage, pPlayer->GetLevel() ); } else { itMap->second.nDamage += (*it).nDamage; if( itMap->second.nLevel < pPlayer->GetLevel() ) itMap->second.nLevel = pPlayer->GetLevel(); } // 선공 파티 저장 if( pPlayer->GetHandle() == m_hFirstAttacker ) { nFirstAttackPartyID = pPlayer->GetPartyID(); } } else { for( itVector = vPartyContribute.begin(); itVector != vPartyContribute.end(); ++itVector ) { if( (*itVector).hPlayer == pPlayer->GetHandle() ) { (*itVector).nDamage += (*it).nDamage; break; } } if( itVector == vPartyContribute.end() ) vPartyContribute.push_back( VIRTUAL_PARTY( pPlayer->GetHandle(), (*it).nDamage, pPlayer->GetLevel() ) ); } } for( itMap = mapPartyDamageContribute.begin(); itMap != mapPartyDamageContribute.end(); ++itMap ) { vPartyContribute.push_back( VIRTUAL_PARTY( itMap->first, itMap->second.nDamage, itMap->second.nLevel ) ); } // TODO: 게임 종료한 사용자의 경우 여기로 떨어질 수 있으므로, 여기서 바로 RETURN할 수 있도록. // assert( vPartyContribute.size() != 0 ); // 뎀지순으로 정렬 std::sort( vPartyContribute.begin(), vPartyContribute.end(), VIRTUAL_PARTY::greaterByDamage ); for( itVector = vPartyContribute.begin(); itVector != vPartyContribute.end(); ++itVector ) { float fContribute = 0.0f; // { 테이머 파티 셋팅 if( nTamerPartyID != 0 && nTamerPartyID == (*itVector).nPartyID ) { (*itVector).bTamer = true; } // ) // 공헌도 = ( 데미지양 / 그 몬스터가 입은 총 데미지양) * 0.5 if( m_nTotalDamage ) fContribute = ( (float) (*itVector).nDamage / m_nTotalDamage ) * 0.5f; // 최다 데미지 보너스 if( itVector == vPartyContribute.begin() ) fContribute += fMaxDamageAdd; // { 선공 보너스 - 공헌도 0.3 추가 if( nFirstAttackPartyID != 0 ) { if( (*itVector).nPartyID == nFirstAttackPartyID ) fContribute += 0.3f; } else if( m_hFirstAttacker && (*itVector).hPlayer == m_hFirstAttacker ) fContribute += 0.3f; // } // { 막타 보너스 - 공헌도 0.1 추가 if( nLastAttackPartyID != 0 ) { if( (*itVector).nPartyID == nLastAttackPartyID ) fContribute += 0.1f; // 파티 } else { if( (*itVector).hPlayer == hKiller ) fContribute += 0.1f; // 솔로 } // } (*itVector).fContribute = fContribute; } } void StructMonster::procEXP( StructCreature *pKiller, std::vector< VIRTUAL_PARTY > & vPartyContribute ) { if( m_vDamageList.empty() ) return; // 최종EXP = 기본EXP * [ 1 + { (친사람수 -1) * 0.2} ] (단, 6명까지만.) // deleted int nEXP = m_pContentInfo->exp; int nJP = m_pContentInfo->jp; std::vector< VIRTUAL_PARTY >::iterator itVector; for( itVector = vPartyContribute.begin(); itVector != vPartyContribute.end(); ++itVector ) { // { 공헌도에 따른 경험치/JP 계산 int nSharedEXP = nEXP * (*itVector).fContribute; float fSharedJP = nJP * (*itVector).fContribute; if( nSharedEXP < 1 ) nSharedEXP = 1; // EXP 는 최소 1 // } // { 테이밍 보너스 if( (*itVector).hPlayer ) { if( (*itVector).hPlayer == GetTamer() ) { nSharedEXP *= GetTameExpAdjust(); fSharedJP *= GetTameExpAdjust(); } } else { if( (*itVector).bTamer ) { nSharedEXP *= GetTameExpAdjust(); fSharedJP *= GetTameExpAdjust(); } } // } if( (*itVector).hPlayer ) { StructPlayer::iterator pit( StructPlayer::get( (*itVector).hPlayer ) ); if( *pit ) { addEXP( this, *pit, nSharedEXP, fSharedJP ); // 솔로일경우 } } else { addEXP( this, (*itVector).nPartyID, nSharedEXP, fSharedJP ); // 파티일경우 } } } void StructMonster::procDropChaos( StructCreature *pKiller, std::vector< VIRTUAL_PARTY > & vPartyContribute, float fDropRatePenalty, float fPCBangDropRateBonus ) { if( m_pContentInfo->chaos_drop_percentage * GameRule::fChaosDropRate * fDropRatePenalty * fPCBangDropRateBonus > XRandom() % 100 ) { c_fixed10 fGameTimeLimitPenalty; fGameTimeLimitPenalty.set( 0 ); THREAD_SYNCRONIZE( &m_csHate ); // 중독 방지 시스템에 의해 몬스터 공격자가 모두 해로운 게임 시간이면 드랍 안 함 // 페널티 수치를 공격자 내 최고 수치로 적용(드랍율은 변동 없이 드랍량만 변경) for( std::vector< struct _HATE_TAG >::iterator it = m_vHateList.begin() ; it != m_vHateList.end() ; ++it ) { if( (*it).uid ) { StructCreature::iterator pit = StructCreature::get( (*it).uid ); if( *pit ) { StructPlayer *pPlayer = NULL; if( (*pit)->IsPlayer() ) pPlayer = static_cast< StructPlayer * >( *pit ); else if( (*pit)->IsSummon() ) pPlayer = static_cast< StructSummon * >( *pit )->GetMaster(); if( !pPlayer || !pPlayer->IsLoginComplete() || pPlayer->GetLogoutTime() || !pPlayer->IsInWorld() ) continue; if( pPlayer->IsGameTimeLimited() ) { c_fixed10 fCurrentPlayerPenalty = GameRule::GetGameTimeLimitPenalty( pPlayer->GetContinuousPlayTime() ); if( fGameTimeLimitPenalty < fCurrentPlayerPenalty ) { fGameTimeLimitPenalty = fCurrentPlayerPenalty; } } else { fGameTimeLimitPenalty.set( 10000 ); break; } } } } // 원샷 킬 하면 킬러의 Hate가 Add되기 전에 StructMonster::onDead 가 불려서 이쪽으로 와버림 // ( m_vHateList.empty() == true )가 되므로 그냥 겸사겸사 킬러에 대해서 한 번 더 검사 if( fGameTimeLimitPenalty == 0 ) { if( pKiller ) { StructPlayer *pPlayer = NULL; if( pKiller->IsPlayer() ) pPlayer = static_cast< StructPlayer * >( pKiller ); else if( pKiller->IsSummon() ) pPlayer = static_cast< StructSummon * >( pKiller )->GetMaster(); if( pPlayer && pPlayer->IsLoginComplete() && !pPlayer->GetLogoutTime() && pPlayer->IsInWorld() ) { if( pPlayer->IsGameTimeLimited() ) { c_fixed10 fCurrentPlayerPenalty = GameRule::GetGameTimeLimitPenalty( pPlayer->GetContinuousPlayTime() ); if( fGameTimeLimitPenalty < fCurrentPlayerPenalty ) { fGameTimeLimitPenalty = fCurrentPlayerPenalty; } } else { fGameTimeLimitPenalty.set( 10000 ); } } } } if( fGameTimeLimitPenalty == 0 ) { return; } // 여기까지는 int 절사 float chaos = (int)( fGameTimeLimitPenalty * XRandom( m_pContentInfo->chaos_min, m_pContentInfo->chaos_max ) ); if( GetDungeonId() ) { int nTaxRate = 0; int nGuildID = 0; DungeonManager::Instance().GetTaxRate( GetDungeonId(), &nTaxRate, &nGuildID ); int dungeon_tax = chaos * nTaxRate / 100.0f; if( nGuildID ) GuildManager::GetInstance().GiveTax( nGuildID, StructGold( 0 ), dungeon_tax ); chaos -= dungeon_tax; } std::vector< VIRTUAL_PARTY >::iterator itVector; for( itVector = vPartyContribute.begin(); itVector != vPartyContribute.end(); ++itVector ) { float fSharedChaos = chaos * (*itVector).fContribute; if( (*itVector).hPlayer ) { StructPlayer::iterator pit( StructPlayer::get( (*itVector).hPlayer ) ); if( *pit ) addChaos( this, *pit, fSharedChaos ); // 솔로일경우 } else { addChaos( this, (*itVector).nPartyID, fSharedChaos ); } } } } void StructMonster::procDropGold( const ArPosition & pos, StructCreature * pKiller, struct takePriority* pPriority, std::vector< VIRTUAL_PARTY > & vPartyContribute, float fDropRatePenalty, float fPCBangDropRateBonus ) { float fGoldDropRate = GameRule::fGoldDropRate * fDropRatePenalty * fPCBangDropRateBonus; if( m_pContentInfo->gold_drop_percentage * fGoldDropRate > XRandom() % 100 ) { c_fixed10 fGameTimeLimitPenalty; fGameTimeLimitPenalty.set( 0 ); StructPlayer* pPlayer = NULL; // AziaMafia THREAD_SYNCRONIZE( &m_csHate ); // 중독 방지 시스템에 의해 몬스터 공격자가 모두 해로운 게임 시간이면 드랍 안 함 // 페널티 수치를 공격자 내 최고 수치로 적용(드랍율은 변동 없이 드랍량만 변경) for( std::vector< struct _HATE_TAG >::iterator it = m_vHateList.begin() ; it != m_vHateList.end() ; ++it ) { if( (*it).uid ) { StructCreature::iterator pit = StructCreature::get( (*it).uid ); if( *pit ) { pPlayer = NULL; if( (*pit)->IsPlayer() ) pPlayer = static_cast< StructPlayer * >( *pit ); else if( (*pit)->IsSummon() ) pPlayer = static_cast< StructSummon * >( *pit )->GetMaster(); if( !pPlayer || !pPlayer->IsLoginComplete() || pPlayer->GetLogoutTime() || !pPlayer->IsInWorld() ) continue; if( pPlayer->IsGameTimeLimited() ) { c_fixed10 fCurrentPlayerPenalty = GameRule::GetGameTimeLimitPenalty( pPlayer->GetContinuousPlayTime() ); if( fGameTimeLimitPenalty < fCurrentPlayerPenalty ) { fGameTimeLimitPenalty = fCurrentPlayerPenalty; } } else { fGameTimeLimitPenalty.set( 10000 ); break; } } } } // 원샷 킬 하면 킬러의 Hate가 Add되기 전에 StructMonster::onDead 가 불려서 이쪽으로 와버림 // ( m_vHateList.empty() == true )가 되므로 그냥 겸사겸사 킬러에 대해서 한 번 더 검사 if( fGameTimeLimitPenalty == 0 ) { if( pKiller ) { pPlayer = NULL; //StructPlayer *pPlayer = NULL; if( pKiller->IsPlayer() ) pPlayer = static_cast< StructPlayer * >( pKiller ); else if( pKiller->IsSummon() ) pPlayer = static_cast< StructSummon * >( pKiller )->GetMaster(); if( pPlayer && pPlayer->IsLoginComplete() && !pPlayer->GetLogoutTime() && pPlayer->IsInWorld() ) { if( pPlayer->IsGameTimeLimited() ) { c_fixed10 fCurrentPlayerPenalty = GameRule::GetGameTimeLimitPenalty( pPlayer->GetContinuousPlayTime() ); if( fGameTimeLimitPenalty < fCurrentPlayerPenalty ) { fGameTimeLimitPenalty = fCurrentPlayerPenalty; } } else { fGameTimeLimitPenalty.set( 10000 ); } } } } if( fGameTimeLimitPenalty == 0 ) { return; } StructGold gold( fGameTimeLimitPenalty * XRandom( m_pContentInfo->gold_min, m_pContentInfo->gold_max ) ); if( gold < 0 ) gold.SetRawData( 1 ); if( gold > GameRule::MAX_GOLD_DROP ) gold = GameRule::MAX_GOLD_DROP; if( GetDungeonId() ) { int nTaxRate = 0; int nGuildID = 0; DungeonManager::Instance().GetTaxRate( GetDungeonId(), &nTaxRate, &nGuildID ); StructGold dungeon_tax( gold.GetRawData() * nTaxRate / 100.0f ); if( nGuildID ) GuildManager::GetInstance().GiveTax( nGuildID, dungeon_tax, 0 ); gold -= dungeon_tax; } if( gold >= 0 ) { StructItem* pItem = StructItem::AllocGold( gold, ItemInstance::BY_MONSTER ); ArPosition valid_pos; if( GameContent::GetValidRandomPos( pos, valid_pos, ITEM_DROP_LENGTH ) == false ) { pKiller ? valid_pos = pKiller->GetPos() : valid_pos = pos; /* if( pKiller ) { #ifdef _DEBUG _cprint( "gold drop valid pos failed. set killer pos." ); FILELOG( "gold drop valid pos failed. set killer pos." ); #endif valid_pos = pKiller->GetPos(); if( GameContent::IsBlocked( valid_pos.x, valid_pos.y ) == true ) { #ifdef _DEBUG _cprint( "killer pos is blocked." ); FILELOG( "killer pos is blocked." ); #endif } } else { #ifdef _DEBUG _cprint( "gold drop valid pos failed. killer == NULL" ); FILELOG( "gold drop valid pos failed. killer == NULL\n" ); #endif valid_pos = pos; } */ } pItem->SetCurrentXY( valid_pos.x, valid_pos.y ); pItem->SetCurrentLayer( GetLayer() ); pItem->SetPickupOrder( pPriority->ItemPickupOrder ); if (GameRule::bDirectInventoryLoot) { //Credits to AziaMafia: Direct inventory looting MonsterDropItemToInventory(pPlayer, pItem); } else { MonsterDropItemToWorld(this, pItem); } } } } void StructMonster::dropItem(const ArPosition& pos, StructCreature* pKiller, struct takePriority* pPriority, std::vector< VIRTUAL_PARTY >& vPartyContribute, ItemBase::ItemCode code, const __int64& count, int level, bool bIsEventItem, int nFlagIndex) { if (!count) { FILELOG("dropItem: count was 0. (x: %f, y: %f, code: %d, Killer: %s)", pos.GetX(), pos.GetY(), code, pKiller->GetName()); return; } // Due to the anti-poisoning system, monster attackers do not drop if all harmful game time bool bHarmfulUsersOnly = true; StructPlayer* pPlayer = NULL; // AziaMafia for (std::vector< struct _HATE_TAG >::iterator it = m_vHateList.begin(); it != m_vHateList.end(); ++it) { if ((*it).uid) { StructCreature::iterator pit = StructCreature::get((*it).uid); if (*pit) { //StructPlayer* pPlayer = NULL; if ((*pit)->IsPlayer()) pPlayer = static_cast(*pit); else if ((*pit)->IsSummon()) pPlayer = static_cast(*pit)->GetMaster(); if (!pPlayer || !pPlayer->IsLoginComplete() || pPlayer->GetLogoutTime() || !pPlayer->IsInWorld()) continue; if (!pPlayer->IsGameTimeLimited() || pPlayer->GetContinuousPlayTime() < GameRule::nMaxTiredGameTime) { bHarmfulUsersOnly = false; break; } } } } // If you do a one-shot kill, StructMonster::onDead is called before the killer's Hate is added and you come here. // ( m_vHateList.empty() == true ), so just check one more time for the killer if (bHarmfulUsersOnly) { if (pKiller) { pPlayer = NULL; if (pKiller->IsPlayer()) pPlayer = static_cast(pKiller); else if (pKiller->IsSummon()) pPlayer = static_cast(pKiller)->GetMaster(); if (pPlayer && pPlayer->IsLoginComplete() && !pPlayer->GetLogoutTime() && pPlayer->IsInWorld() && (!pPlayer->IsGameTimeLimited() || pPlayer->GetContinuousPlayTime() < GameRule::nMaxTiredGameTime)) { bHarmfulUsersOnly = false; } } } if (bHarmfulUsersOnly) { return; } StructItem* pItem = StructItem::AllocItem(0, code, count, ItemInstance::BY_MONSTER, level); ArPosition valid_pos; if (GameContent::GetValidRandomPos(pos, valid_pos, ITEM_DROP_LENGTH) == false) { if (pKiller) { _cprint("item drop valid pos failed. set killer pos."); FILELOG("item drop valid pos failed. set killer pos."); valid_pos = pKiller->GetPos(); if (GameContent::IsBlocked(valid_pos.x, valid_pos.y) == true) { _cprint("killer pos is blocked (x: %i; y: %i).", valid_pos.x, valid_pos.y); FILELOG("killer pos is blocked (x: %i; y: %i).", valid_pos.x, valid_pos.y); } } else { _cprint("item drop valid pos failed. killer == NULL"); FILELOG("item drop valid pos failed. killer == NULL\n"); valid_pos = pos; } } pItem->SetCurrentXY(valid_pos.x, valid_pos.y); pItem->SetCurrentLayer(GetLayer()); pItem->SetPickupOrder(pPriority->ItemPickupOrder); // If nFlag is a valid flag, set that flag if (nFlagIndex >= 0 && nFlagIndex < pItem->GetInstanceFlag().TOTAL_BITS) pItem->SetInstanceFlagOn(nFlagIndex); if (bIsEventItem) { pItem->SetEventDropFlag(true); pItem->SetInstanceFlagOn(ItemInstance::ITEM_FLAG_EVENT); } if (GameRule::bDirectInventoryLoot) { //Credits to AziaMafia: Direct inventory looting MonsterDropItemToInventory(pPlayer, pItem); } else { MonsterDropItemToWorld(this, pItem); } } static bool checkDrop( int code, int percentage, StructCreature * pKiller, float fDropRatePenalty, float fPCBangDropRateBonus ) { StructPlayer *pPlayer = NULL; if( pKiller->IsPlayer() ) pPlayer = static_cast< StructPlayer * >( pKiller ); else if( pKiller->IsSummon() ) pPlayer = static_cast< StructSummon * >( pKiller )->GetMaster(); float fCreatureCardMod = 1.0f; // Apply item drop chance float fMod = ( 1.0f + pPlayer->GetItemChance() * 0.01f ); if( code > 0 ) { // Apply increased drop rate for summon cards if( StructItem::GetItemBase( code ).nGroup == ItemBase::GROUP_SUMMONCARD ) { fCreatureCardMod = pKiller->GetCreatureCardChance(); } // For quest items(usually not dropped by monsters, but occasionally used — e.g., collectable junk items), drop rate penalties are not applied if( StructItem::GetItemBase( code ).Flag.IsOn( ItemBase::FLAG_QUEST ) ) { fDropRatePenalty = 1.0f; } const bool alwaysDropBossCard = ENV().GetInt("game.always_drop_boss_card", 0) != 0; if (alwaysDropBossCard && StructItem::GetItemBase(code).nGroup == ItemBase::GROUP_EQUIPMENT_ON_BELT) { fDropRatePenalty = 1.0f; } } // When killing monsters in Deathmatch, no level penalty is applied if( pPlayer && pPlayer->IsInDeathmatch() ) fDropRatePenalty = 1.0f; // After probability calculation, KIN float fItemDropRate = GameRule::fItemDropRate; if (pPlayer->IsPKOn() ) fItemDropRate += GameRule::fPlayerKillerBonusRate; if( code >= 540200 && code <= 540204 ) { fItemDropRate *= GameRule::fCardDropRate; } if( XRandom( 1, 100000000 ) > ( percentage * fMod * fItemDropRate * fDropRatePenalty * fPCBangDropRateBonus ) * fCreatureCardMod ) return false; return true; } void StructMonster::dropItemGroup( const ArPosition & pos, StructCreature * pKiller, struct takePriority* pPriority, std::vector< VIRTUAL_PARTY > & vPartyContribute, int nDropGroupID, const __int64 & count, int level, int nFlagIndex ) { std::map< ItemBase::ItemCode, __int64 > mapDropItem; for( int i = 0 ; i < static_cast< int >( count ) ; ++i ) { ItemBase::ItemCode nItemID = nDropGroupID; __int64 nItemCount = 1; do { GameContent::SelectItemIDFromDropGroup( nItemID, nItemID, nItemCount ); } while( nItemID < 0 ); // 꽝이 아니고 아이템이 선택된 경우에만 드랍 대상으로 추가 if( nItemID > 0 ) { std::map< ItemBase::ItemCode, __int64 >::iterator it = mapDropItem.find( nItemID ); if( it == mapDropItem.end() ) { mapDropItem.insert( std::make_pair( nItemID, nItemCount ) ); } else { (*it).second += nItemCount; } } } for( std::map< ItemBase::ItemCode, __int64 >::const_iterator it = mapDropItem.begin() ; it != mapDropItem.end() ; ++it ) { dropItem( pos, pKiller, pPriority, vPartyContribute, (*it).first, (*it).second, level, false, nFlagIndex ); } } void StructMonster::procDropItem( const ArPosition & pos, StructCreature * pKiller, takePriority* pPriority, std::vector< VIRTUAL_PARTY > & vPartyContribute, float fDropRatePenalty, float fPCBangDropRateBonus ) { if( m_pContentInfo && m_pContentInfo->item_drop_list ) { if( StructEventItemManager::GetInstance().IsStartingDungeonDropRateEvent() ) { int nDungeonID = DungeonManager::Instance().GetDungeonID( GetX(), GetY() ); if( !nDungeonID ) { nDungeonID = InstanceDungeonManager::Instance().GetInstanceDungeonID( ArPosition( GetX(), GetY() ) ); } if( nDungeonID ) { fPCBangDropRateBonus *= StructEventItemManager::GetInstance().GetEventDungeonDropRate( nDungeonID ); } } for( std::vector< MonsterBase::MONSTER_ITEM_DROP_INFO >::const_iterator it = m_pContentInfo->item_drop_list->begin() ; it != m_pContentInfo->item_drop_list->end() ; ++it ) { const MonsterBase::MONSTER_ITEM_DROP_INFO & item_drop = (*it); if( !item_drop.item_id ) continue; if( !checkDrop( item_drop.item_id, item_drop.percentage, pKiller, fDropRatePenalty, fPCBangDropRateBonus ) ) continue; // 갯수 __int64 item_count = XRandom( item_drop.min_count, item_drop.max_count ); if( item_count < item_drop.min_count || item_count > item_drop.max_count ) { _cprint( "StructMonster::procDropItem(...) : invalid item count! [%d][%d]\n", item_drop.item_id, item_count ); FILELOG( "StructMonster::procDropItem(...) : invalid item count! [%d][%d]", item_drop.item_id, item_count ); continue; } // 레벨 int item_level = XRandom( item_drop.min_level, item_drop.max_level ); if (item_count > 0) { if (item_drop.item_id < 0) { dropItemGroup(pos, pKiller, pPriority, vPartyContribute, item_drop.item_id, item_count, item_level, ItemInstance::ITEM_FLAG_NONE); } else { dropItem(pos, pKiller, pPriority, vPartyContribute, item_drop.item_id, item_count, item_level, false, ItemInstance::ITEM_FLAG_NONE); } } } // 이벤트 드랍 ItemBase::ItemCode nEventItemCode = StructEventItemManager::GetInstance().GetActiveDrop( GetArTime(), fDropRatePenalty ); if( nEventItemCode && GameContent::IsValidItemCode( nEventItemCode ) ) { dropItem( pos, pKiller, pPriority, vPartyContribute, nEventItemCode, 1, 1, true, ItemInstance::ITEM_FLAG_NONE ); StructPlayer *pPlayer = NULL; if( pKiller ) { if( pKiller->IsPlayer() ) pPlayer = static_cast< StructPlayer * >( pKiller ); else if( pKiller->IsSummon() ) pPlayer = static_cast< StructSummon * >( pKiller )->GetMaster(); } if( pPlayer ) { LOG::Log11N4S( LM_ITEM_EVENT_DROP, pPlayer->GetAccountID(), pPlayer->GetSID(), 0, nEventItemCode, 1, this->GetMonsterId(), this->GetX(), this->GetY(), 0, 0, 0, pPlayer->GetAccountName(), LOG::STR_NTS, pPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } else { LOG::Log11N4S( LM_ITEM_EVENT_DROP, 0, 0, 0, nEventItemCode, 1, this->GetMonsterId(), this->GetX(), this->GetY(), 0, 0, 0, "", 0, "", 0, "", 0, "", 0 ); } } } // 퀘스트 및 호칭 처리 procQuestAndTitle( pos, pKiller, pPriority, vPartyContribute, fDropRatePenalty ); } void StructMonster::onDead( StructCreature * pKiller, bool decreaseEXPOnDead ) { std::vector< VIRTUAL_PARTY > vPartyContribute; StructCreature::onDead( pKiller, decreaseEXPOnDead ); SetStatus( STATUS_DEAD ); _monster_debug_output( "DEAD : %I64d\n", GetHandle() ); // Remove from scanner quadTreeItem.RemoveMe(); m_bTamedSuccess = false; // Taming Processing if( GetTamer() ) { StructPlayer::iterator it = StructPlayer::get( GetTamer() ); if( *it ) m_bTamedSuccess = ProcTame( this ); } calcPartyContribute( pKiller, vPartyContribute ); // Scenery distribution (changed to apply experience points only when the experience points obtained in the monster DB are above the above) if( GetMonsterBase()->exp > 0 ) { procEXP( pKiller, vPartyContribute ); } StructPlayer* pKillPlayer = NULL; if( pKiller != nullptr && pKiller != NULL ) { if( pKiller->IsPlayer() ) { pKillPlayer = static_cast< StructPlayer* >( pKiller ); } else if( pKiller->IsSummon() ) { StructSummon* pKillSummon = static_cast< StructSummon* >( pKiller ); if( pKillSummon != NULL ) { pKillPlayer = pKillSummon->GetMaster(); } } } // If there is a script to run on death, run the script (need to re-determine where to run it and what parameters to pass to Lua) if( GetMonsterBase()->script_on_dead[ 0 ] ) { ThreadPlayerHelper TPHepler( pKillPlayer ); char szScriptBuffer[ 256 ]; s_strcpy( szScriptBuffer, _countof( szScriptBuffer ), GetMonsterBase()->script_on_dead ); char szElementBuffer[ 32 ]; s_sprintf( szElementBuffer, _countof( szElementBuffer ), "%d", (int)GetX() ); XStringUtil::Replace( szScriptBuffer, _countof( szScriptBuffer ), "#@pos_x@#", szElementBuffer ); s_sprintf( szElementBuffer, _countof( szElementBuffer ), "%d", (int)GetY() ); XStringUtil::Replace( szScriptBuffer, _countof( szScriptBuffer ), "#@pos_y@#", szElementBuffer ); s_sprintf( szElementBuffer, _countof( szElementBuffer ), "%d", (int)GetLayer() ); XStringUtil::Replace( szScriptBuffer, _countof( szScriptBuffer ), "#@pos_layer@#", szElementBuffer ); s_sprintf( szElementBuffer, _countof( szElementBuffer ), "%u", GetHandle() ); XStringUtil::Replace( szScriptBuffer, _countof( szScriptBuffer ), "#@monster_handle@#", szElementBuffer ); LUA()->RunString( szScriptBuffer ); } // Log when a boss-level monster dies if( GetMonsterType() >= MonsterBase::MONSTER_TYPE_HIGHEST_1_STAR ) { int nDungeonID = 0; if(pKillPlayer != NULL && pKillPlayer->IsInDungeon() ) { nDungeonID = DungeonManager::Instance().GetDungeonID( pKillPlayer->GetX(), pKillPlayer->GetY() ); } else if( pKillPlayer->IsInInstanceDungeon() ) { nDungeonID = InstanceDungeonManager::Instance().GetInstanceDungeonID( ArPosition( pKillPlayer->GetX(), pKillPlayer->GetY() ) ); } LOG::Log11N4S( LM_MONSTER_KILL, pKillPlayer->GetAccountID(), pKillPlayer->GetPlayerUID(), pKillPlayer->GetPartyID(), GetMonsterId(), nDungeonID, GetX(), GetY(), pKillPlayer->GetX(), pKillPlayer->GetY(), GetLayer(), 0, pKillPlayer->GetAccountName(), LOG::STR_NTS, pKillPlayer->GetName(), LOG::STR_NTS, "", 0, "", 0 ); } // Drop if not tamed if( !m_bTamedSuccess ) { std::sort( vPartyContribute.begin(), vPartyContribute.end(), VIRTUAL_PARTY::greaterByContribute ); ArPosition pos = GetCurrentPosition( GetArTime() ); takePriority Priority; std::vector< VIRTUAL_PARTY >::iterator itContribute; // { 획득 우선순위 생성 int nCount = 0; for( itContribute = vPartyContribute.begin(); itContribute != vPartyContribute.end(); ++itContribute ) { // 레이드 보스 몬스터의 경우는 파티가 바로 해산되므로 소유권을 없애야 함 if( IsDungeonRaidMonster() && GetMonsterType() >= MonsterBase::MONSTER_TYPE_HIGHEST_1_STAR ) { Priority.ItemPickupOrder.hPlayer[nCount] = 0; Priority.ItemPickupOrder.nPartyID[nCount] = 0; } else if( (*itContribute).hPlayer ) { Priority.ItemPickupOrder.hPlayer[nCount] = (*itContribute).hPlayer; Priority.ItemPickupOrder.nPartyID[nCount] = 0; } else { Priority.ItemPickupOrder.hPlayer[nCount] = 0; Priority.ItemPickupOrder.nPartyID[nCount] = (*itContribute).nPartyID; } if( ++nCount >= 3 ) { break; } } // } // 최고 우선순위 보유자 정보 적용 itContribute = vPartyContribute.begin(); int nMaxPriorityLevel = ( vPartyContribute.empty() ) ? 0 : (*itContribute).nLevel; float fMaxContribute = ( vPartyContribute.empty() ) ? 0.0f : (*itContribute).fContribute; float fDropRatePenalty = 1.0f; int nLevelDiff = nMaxPriorityLevel - GetLevel(); if( nLevelDiff >= 10 ) { fDropRatePenalty = std::max( 1 - ( nLevelDiff - 10 ) * 0.05f, 0.0f ); } if (GetLevel() >= 150 ) { fDropRatePenalty = 1.0f; } // Check if PC Bang premium pass exist bool bPremiumPCBang = false; // If there is no aggro information (one-shot kill), apply based on the killer if( vPartyContribute.empty() ) { StructPlayer *pKillPlayer = NULL; if( pKiller->IsPlayer() ) pKillPlayer = static_cast< StructPlayer * >( pKiller ); else if( pKiller->IsSummon() ) pKillPlayer = static_cast< StructSummon * >( pKiller )->GetMaster(); bPremiumPCBang = pKillPlayer && pKillPlayer->GetPCBangMode() == GameRule::PCBANG_PREMIUM_BONUS; } // If the highest priority was assigned to the player else if( (*itContribute).hPlayer ) { StructPlayer::iterator pit = StructPlayer::get( (*itContribute).hPlayer ); bPremiumPCBang = (*pit) && static_cast< StructPlayer * >( (*pit) )->GetPCBangMode() == GameRule::PCBANG_PREMIUM_BONUS; } // 최고 우선순위가 파티에게 있었을 경우 else if( (*itContribute).nPartyID ) { // 파티 멤버 중 더블 플러스 PC방 멤버 찾기 struct partyPCBangBonusChecker : public PartyManager::PartyFunctor { partyPCBangBonusChecker( const ArPosition & posKiller ) : m_posKiller( posKiller ) , m_bPremiumPCBang( false ) {} virtual bool operator()( AR_HANDLE handle ) { if( m_bPremiumPCBang ) return true; StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( !pPlayer || !pPlayer->IsInWorld() ) return true; // 멀리 떨어져 있으면 KIN if( m_posKiller.GetDistance( pPlayer->GetPos() ) > RANGE_LIMIT ) return true; // 파티 멤버 중 더블 플러스 PC방 유저 있는지 찾기 if( pPlayer->GetPCBangMode() == GameRule::PCBANG_PREMIUM_BONUS ) m_bPremiumPCBang = true; return true; } const ArPosition & m_posKiller; bool m_bPremiumPCBang; } _fo( GetPos() ); PartyManager::GetInstance().DoEachMember( (*itContribute).nPartyID, _fo ); bPremiumPCBang = _fo.m_bPremiumPCBang; } c_fixed10 difficultyBonus = GameRule::GetDifficultyBonus( m_nDifficulty ); float fGoldDropRateBonus = 1.0f * difficultyBonus; float fItemDropRateBonus = 1.0f * difficultyBonus; float fChaosDropRateBonus = 1.0f * difficultyBonus; if( bPremiumPCBang ) { fGoldDropRateBonus = GameRule::fPremiumPCBangGoldBonusDropRate; fItemDropRateBonus = GameRule::fPremiumPCBangItemBonusDropRate; fChaosDropRateBonus = GameRule::fPremiumPCBangChaosBonusDropRate; } // Collect 1 LAK quest if( pKiller->IsPlayer() && static_cast< StructPlayer * >( pKiller )->GetChaos() < 1 && static_cast< StructPlayer * >( pKiller )->GetQuestProgress( StructQuest::QUEST_CODE_CHAOS_COLLECT ) == QuestInstance::IN_PROGRESS ) { addChaos( this, static_cast< StructPlayer * >( pKiller ), 1.0f ); } if( pKiller != nullptr && !IsDungeonRaidMonster() ) { // Drop Gold procDropGold( pos, pKiller, &Priority, vPartyContribute, fDropRatePenalty, fGoldDropRateBonus ); // Drop Chaos procDropChaos(pKiller, vPartyContribute, fDropRatePenalty, fChaosDropRateBonus); // Drop Item procDropItem( pos, pKiller, &Priority, vPartyContribute, fDropRatePenalty, fItemDropRateBonus ); } } // Hate list std::vector< _HATE_TAG >::iterator it; StructCreature::onDead( pKiller,decreaseEXPOnDead ); for( it = m_vHateList.begin(); it != m_vHateList.end(); ++it ) { StructCreature::iterator itEnemy = StructCreature::get( (*it).uid ); if( *itEnemy ) { (*itEnemy)->RemoveFromEnemyList( GetHandle() ); } } } void StructMonster::procQuestAndTitle( const ArPosition & pos, StructCreature *pKiller, struct takePriority* pPriority, std::vector< VIRTUAL_PARTY > & vPartyContribute, float fDropRatePenalty ) { if( vPartyContribute.empty() ) { // assert( 0 ); return; } VIRTUAL_PARTY & vp_tag = vPartyContribute.front(); std::vector< struct StructPlayer * > vPlayer; // 대상 선택 if( vp_tag.hPlayer ) { StructPlayer::iterator pit = StructPlayer::get( vp_tag.hPlayer ); StructPlayer *pPlayer = *pit; if( pPlayer ) vPlayer.push_back( pPlayer ); } else { struct myPartyFunctor : PartyManager::PartyFunctor { myPartyFunctor( ArPosition p, std::vector< struct StructPlayer * > * pList ) : pos( p ), pList( pList ) { } virtual bool operator()( AR_HANDLE handle ) { StructPlayer::iterator pit = StructPlayer::get( handle ); StructPlayer *pPlayer = *pit; if( !pPlayer || !pPlayer->IsInWorld() ) return true; // 멀리 떨어져 있으면 KIN AR_UNIT distance = pos.GetDistance( pPlayer->GetPos() ); if( distance > RANGE_LIMIT ) return true; pList->push_back( pPlayer ); return true; } ArPosition pos; std::vector< struct StructPlayer * > *pList; } _tmp( pos, &vPlayer ); PartyManager::GetInstance().DoEachMember( vp_tag.nPartyID, _tmp ); } for( std::vector< struct StructPlayer * >::iterator it = vPlayer.begin(); it != vPlayer.end(); ++it ) { // 퀘스트 처리 // 킬수 처리 (*it)->UpdateQuestStatusByMonsterKill( GetMonsterId() ); // 사냥 수집 퀘스트의 경우 드랍그룹 적용 std::vector< StructQuest* > vQuestList; (*it)->GetQuestByMonster( GetMonsterId(), vQuestList, QuestBase::TYPE_FLAG_HUNT_ITEM | QuestBase::TYPE_FLAG_HUNT_ITEM_FROM_ANY_MONSTSERS ); for( std::vector< StructQuest* >::iterator it2 = vQuestList.begin(); it2 != vQuestList.end(); ++it2 ) { StructQuest *pQuest = (*it2); if( pQuest && pQuest->GetQuestBase().nDropGroupId && !pQuest->IsFinishable() ) { if( pQuest->GetQuestType() == QuestBase::QUEST_HUNT_ITEM_DROP_PENALTY ) { unsigned int n64Random = XRandom( 1, 100000000 ); unsigned int n64Rate = 100000000 * fDropRatePenalty; if( n64Rate > n64Random ) { dropItemGroup( pos, pKiller, pPriority, vPartyContribute, pQuest->GetQuestBase().nDropGroupId, 1, 1, ItemInstance::ITEM_FLAG_NONE ); } } else { dropItemGroup( pos, pKiller, pPriority, vPartyContribute, pQuest->GetQuestBase().nDropGroupId, 1, 1, ItemInstance::ITEM_FLAG_NONE ); } } } // 호칭 처리 (*it)->UpdateTitleConditionByMonsterKill( GetMonsterId(), GetTameCode() ); } } AR_UNIT StructMonster::GetFirstAttackRange() const { return m_pContentInfo->visible_range; } bool StructMonster::IsEnemy( StructCreature* pTarget, bool bIncludeHiding ) { if( !StructCreature::IsEnemy( pTarget, bIncludeHiding ) ) return false; if( pTarget->IsNPC() ) return false; return true; } bool StructMonster::IsAlly( StructCreature* pTarget ) { if( pTarget->IsMonster() ) { return true; } else if( IsOriginalDungeonOwnerGuardian() || IsOriginalDungeonSiegerGuardian() ) { StructPlayer * pPlayer = NULL; if( pTarget->IsPlayer() ) { pPlayer = static_cast< StructPlayer * >( pTarget ); } else if( pTarget->IsSummon() ) { pPlayer = static_cast< StructSummon * >( pTarget )->GetMaster(); } if( pPlayer && pPlayer->GetPartyID() ) { int dungeon_id = DungeonManager::Instance().GetDungeonID( GetX(), GetY() ); if( dungeon_id ) { int guild_id = PartyManager::GetInstance().GetAttackTeamGuildID( pPlayer->GetPartyID() ); if( guild_id == DungeonManager::Instance().GetOriginalOwnGuildID( dungeon_id ) ) { if( IsOriginalDungeonOwnerGuardian() ) return true; } else { if( IsOriginalDungeonSiegerGuardian() ) return true; } } } } return StructCreature::IsAlly( pTarget ); } int StructMonster::GetCreatureGroup() const { if( m_nChangingGroup != CREATURE_NONE ) return m_nChangingGroup; return m_pContentInfo->group; } bool StructMonster::IsAttackable() { if( m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_ENVIRONMENT || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_DUNGEON_CONNECTOR || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_AUTO_TRAP || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_TRAP || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_RUNAWAY || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_IMMORTAL_DUMMY || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_DEFENCE_SKILL ) return false; return StructCreature::IsAttackable(); } bool StructMonster::IsEnvironmentMonster() const { return m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_ENVIRONMENT; } bool StructMonster::IsDungeonConnector() const { return m_pContentInfo->race == MonsterBase::MONSTER_RACE_DUNGEON_CONNECTOR; } bool StructMonster::IsAgent() const { return m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_AGENT; } bool StructMonster::IsAutoTrap() const { return m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_AUTO_TRAP; } bool StructMonster::IsRunaway() const { return m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_RUNAWAY; } bool StructMonster::IsMovable() { if( m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_DUNGEON_CONNECTOR || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_AUTO_TRAP || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_TRAP || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_NOT_MOVABLE || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_IMMORTAL_DUMMY || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_DEFENCE_SKILL ) return false; return StructCreature::IsMovable(); } bool StructMonster::IsKnockbackable() { if( m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_DUNGEON_CONNECTOR || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_AUTO_TRAP || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_TRAP || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_NOT_MOVABLE || m_pContentInfo->fight_type == MonsterBase::FIGHT_TYPE_IMMORTAL_DUMMY || IsBossMonster() ) return false; return StructCreature::IsKnockbackable(); } bool StructMonster::IsSkillCastable() { if( !IsActable() || GetState( StructState::MUTE ) ) return false; if( IsSitDown() ) return false; if( IsUsingSkill() ) return false; return m_StatusFlag.IsOn( STATUS_SKILL_CASTABLE ); } bool StructMonster::IsMagicCastable() { if( !IsActable() || GetState( StructState::MUTE ) ) return false; if( IsSitDown() ) return false; if( IsUsingSkill() ) return false; return m_StatusFlag.IsOn( STATUS_MAGIC_CASTABLE ); } bool StructMonster::IsBossMonster() const { if( m_pContentInfo->monster_type >= MonsterBase::MONSTER_TYPE_HIGH_3_STAR ) return true; return false; } bool StructMonster::IsDungeonCore() const { return ( m_pContentInfo->race == MonsterBase::MONSTER_RACE_DUNGEON_CORE ); } AR_UNIT StructMonster::GetChaseRange() const { return m_pContentInfo->chase_range * GameRule::DEFAULT_UNIT_SIZE; }