892 lines
23 KiB
C++
892 lines
23 KiB
C++
|
||
#include <mmo/ArcadiaServer.h>
|
||
#include <toolkit/ILock.h>
|
||
#include <toolkit/XEnv.h>
|
||
#include <toolkit/khash.h>
|
||
#include <logging/FileLog.h>
|
||
|
||
#include "LogClient/LogClient.h"
|
||
#include "ErrorCode/ErrorCode.h"
|
||
|
||
#include "StructItem.h"
|
||
#include "GameAllocator.h"
|
||
#include "Constant.h"
|
||
#include "GameContent.h"
|
||
#include "GameDBManager.h"
|
||
#include "StructPlayer.h"
|
||
#include "DB_Commands.h"
|
||
#include "GameRule.h"
|
||
#include "StructSummon.h"
|
||
#include "StructPet.h"
|
||
#include "GameProc.h"
|
||
|
||
|
||
static volatile LONG s_ItemCnt;
|
||
static std::vector< ItemBaseServer > s_vItemBase;
|
||
static KHash< size_t, hashPr_mod_basic<ItemBase::ItemCode> > s_hsItemCode;
|
||
static KHash< size_t, hashPr_string_nocase > s_hsItemName;
|
||
static std::vector< StructItem* > s_vPendingItemForDelete;
|
||
static XCriticalSection s_lockForItemDelete( "s_lockForItemDelete" );
|
||
static KHash< ItemBase::ItemCode, hashPr_mod_basic<ItemBase::ItemCode> > s_hsItemConvertingTable;
|
||
|
||
static StructItem *s_pDummyItem;
|
||
|
||
bool StructItem::RegisterItemBase( const ItemBaseServer & base )
|
||
{
|
||
// 이미 존재하면 수정
|
||
if( s_hsItemCode.has( base.nCode ) )
|
||
{
|
||
size_t idx;
|
||
s_hsItemCode.lookup( base.nCode, idx );
|
||
s_vItemBase[idx] = base;
|
||
|
||
return true;
|
||
}
|
||
|
||
s_vItemBase.push_back( base );
|
||
|
||
s_hsItemCode.add( base.nCode, s_vItemBase.size()-1 );
|
||
s_hsItemName.add( GameContent::GetString( base.nNameId ), s_vItemBase.size()-1 );
|
||
|
||
return true;
|
||
}
|
||
|
||
bool StructItem::RegisterConvertingInfo( ItemBase::ItemCode _key, ItemBase::ItemCode _value )
|
||
{
|
||
if( s_hsItemConvertingTable.add( _key, _value ) == NULL )
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
const char* StructItem::GetName() const
|
||
{
|
||
return GameContent::GetString( GetItemBase().nNameId );
|
||
}
|
||
|
||
std::string StructItem::GetNameInGame()
|
||
{
|
||
std::string strName( GameContent::GetString( GetItemBase().nNameId ) );
|
||
|
||
std::string strGameName;
|
||
|
||
int cnt = 0;
|
||
for( std::string::iterator it = strName.begin() ; it != strName.end() ; ++it )
|
||
{
|
||
if( (*it) == '\n' || (*it) == '\r' || (*it) == '\n' )
|
||
continue;
|
||
|
||
if( (*it) == '<' )
|
||
{
|
||
++cnt;
|
||
continue;
|
||
}
|
||
|
||
if( (*it) == '>' )
|
||
{
|
||
--cnt;
|
||
continue;
|
||
}
|
||
|
||
if( cnt <= 0 )
|
||
strGameName += (*it);
|
||
}
|
||
|
||
return strGameName;
|
||
}
|
||
|
||
bool StructItem::IsValidItemCode( ItemBase::ItemCode code )
|
||
{
|
||
size_t idx;
|
||
if( !s_hsItemCode.lookup( code, idx ) )
|
||
{
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
ItemBaseServer & StructItem::GetItemBase( ItemBase::ItemCode c )
|
||
{
|
||
static ItemBaseServer gold;
|
||
if( !c )
|
||
{
|
||
return gold;
|
||
}
|
||
|
||
size_t idx;
|
||
if( !s_hsItemCode.lookup( c, idx ) )
|
||
{
|
||
FILELOG( "invalid item code[%d]", c );
|
||
return gold;
|
||
}
|
||
|
||
return s_vItemBase[ idx ];
|
||
}
|
||
|
||
ItemBase::ItemCode StructItem::GetItemCode( const char *szItemName )
|
||
{
|
||
size_t idx;
|
||
|
||
if( s_hsItemName.lookup( szItemName, idx ) ) return s_vItemBase[idx].nCode;
|
||
|
||
return 0;
|
||
}
|
||
|
||
StructItem* StructItem::AllocGold( const StructGold & gold,ItemInstance::GenerateCode gcode )
|
||
{
|
||
return AllocItem( 0, 0, gold.GetRawData(), gcode, 0 );
|
||
}
|
||
|
||
StructItem* StructItem::FindItem( AR_HANDLE handle )
|
||
{
|
||
return static_cast< StructItem* >( GameObject::raw_get( handle ) );
|
||
}
|
||
|
||
StructItem* StructItem::AllocItem( ItemUID uid,
|
||
ItemBase::ItemCode code,
|
||
const __int64 & cnt,
|
||
ItemInstance::GenerateCode info,
|
||
int level,
|
||
int enhance,
|
||
int flag,
|
||
ItemBase::ItemCode socket_0,
|
||
ItemBase::ItemCode socket_1,
|
||
ItemBase::ItemCode socket_2,
|
||
ItemBase::ItemCode socket_3,
|
||
int awaken_sid,
|
||
int identified_sid,
|
||
int remain_time,
|
||
const unsigned char & elemental_effect_type,
|
||
const time_t & elemental_effect_expire_time,
|
||
const int elemental_effect_attack_point,
|
||
const int elemental_effect_magic_point,
|
||
const ItemBase::ItemCode appearance_code,
|
||
int summon_code,
|
||
int extra_item_effect) // Fraun Sky Accessories 7/12/2025
|
||
{
|
||
// 돈 객체를 생성하는 경우 (아이템 코드가 0) cnt가 0일 수 있다.
|
||
assert( cnt > 0 || code == 0 );
|
||
|
||
StructItem* p;
|
||
|
||
InterlockedIncrement( &s_ItemCnt );
|
||
|
||
struct _myIntializer : GameAllocateFunctor
|
||
{
|
||
virtual void operator()( void * pObj, AR_HANDLE handle )
|
||
{
|
||
new (pObj) StructItem( handle );
|
||
}
|
||
};
|
||
|
||
AR_HANDLE handle = allocItemStruct( &p, _myIntializer() );
|
||
// _oprint( "ALLOC ITEM : %08X\n", p );
|
||
|
||
if( handle == 0 )
|
||
{
|
||
s_pDummyItem = p;
|
||
|
||
handle = allocItemStruct( &p, _myIntializer() );
|
||
}
|
||
//new (p) StructItem( handle );
|
||
|
||
assert( code >= 0 );
|
||
|
||
p->m_pItemBase = &GetItemBase( code );
|
||
|
||
p->m_Instance.UID = uid;
|
||
p->m_Instance.Code = code;
|
||
p->m_Instance.PreviousUID = 0;
|
||
p->m_Instance.nCount = cnt;
|
||
p->m_Instance.nLevel = ( level == -1 ) ? p->m_pItemBase->nLevel : level ; // If not set, use the value from ItemResource
|
||
|
||
if( p->m_Instance.nLevel <= 0 )
|
||
{
|
||
// Money can have level 0 – since it's not a real item, it doesn't matter either way
|
||
assert( p->m_pItemBase->nCode == 0 );
|
||
p->m_Instance.nLevel = 1;
|
||
}
|
||
p->m_Instance.nEnhance = ( enhance == -1 ) ? p->m_pItemBase->nEnhance : enhance ; // If not set, use the value from ItemResource
|
||
if( p->m_Instance.nEnhance < 0 )
|
||
{
|
||
assert( 0 );
|
||
p->m_Instance.nEnhance = 0;
|
||
}
|
||
if( p->m_pItemBase->nGroup == ItemBase::GROUP_SKILLCARD && !p->m_Instance.nEnhance ) p->m_Instance.nEnhance = 1; // Skill cards have a default value of 1
|
||
p->m_Instance.GenerateInfo = info;
|
||
p->m_Instance.nWearInfo = static_cast< ItemBase::ItemWearType >( -1 );
|
||
|
||
p->m_Instance.OwnerHandle = 0;
|
||
p->m_Instance.nOwnerUID = 0;
|
||
|
||
p->m_Instance.Socket[0] = socket_0;
|
||
p->m_Instance.Socket[1] = socket_1;
|
||
p->m_Instance.Socket[2] = socket_2;
|
||
p->m_Instance.Socket[3] = socket_3;
|
||
p->m_Instance.RandomOption[ ItemInstance::AWAKEN ].nSID = awaken_sid;
|
||
p->m_Instance.RandomOption[ ItemInstance::IDENTIFIED ].nSID = identified_sid;
|
||
p->m_Instance.cElementalEffectType = elemental_effect_type;
|
||
p->m_Instance.tElementalEffectExpire = elemental_effect_expire_time;
|
||
p->m_Instance.nElementalEffectAttackPoint = elemental_effect_attack_point;
|
||
p->m_Instance.nElementalEffectMagicPoint = elemental_effect_magic_point;
|
||
p->m_Instance.nAppearanceCode = appearance_code;
|
||
p->m_Instance.nSummonCode = summon_code;
|
||
p->m_Instance.nAdditionalItemEffect = extra_item_effect; // Fraun Sky Accessories 7/12/2025
|
||
|
||
p->m_Instance.nCurrentEtherealDurability = ( p->IsEtherealStone() ) ? 0 : p->GetItemBase().nEtherealDurability;
|
||
p->m_Instance.nCurrentEndurance = p->GetItemBase().nEndurance;
|
||
|
||
p->m_hBindedTarget = 0;
|
||
|
||
p->GetInstanceFlag().CopyFrom( ( flag == -1 ) ? &p->m_pItemBase->nInstanceFlag : &flag );
|
||
|
||
if( remain_time == -1 )
|
||
{
|
||
if( p->IsExpireItem() )
|
||
{
|
||
p->m_Instance.tExpire = time( NULL ) + p->GetItemBase().available_time;
|
||
}
|
||
else
|
||
{
|
||
p->m_Instance.tExpire = 0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if( p->GetItemBase().decrease_type == ItemBase::DECREASE_ALWAYS )
|
||
{
|
||
p->m_Instance.tExpire = remain_time;
|
||
}
|
||
else
|
||
{
|
||
p->m_Instance.tExpire = time( NULL ) + remain_time;
|
||
}
|
||
}
|
||
|
||
if( !p->IsJoinable() && !p->IsGold() && p->m_Instance.nCount > 1 )
|
||
p->m_Instance.nCount = 1;
|
||
|
||
// Item conversion table. Converts old codes to the designated codes
|
||
if( s_hsItemConvertingTable.has( code ) )
|
||
{
|
||
ItemBase::ItemCode nCode = -1;
|
||
s_hsItemConvertingTable.lookup( code, nCode );
|
||
p->m_Instance.Code = nCode;
|
||
p->m_pItemBase = &GetItemBase( nCode );
|
||
|
||
if( uid )
|
||
{
|
||
// If a UID exists, we need to change the value in the DB as well, so a log is left.
|
||
// Inside AllocItem, there's no owner information, so we can't record the n1, n2 columns (which store the item's owner info) — that's a drawback..
|
||
LOG::Log11N4S( LM_ITEM_CONVERT, 0,
|
||
0, p->GetItemEnhance() * 100 + p->GetItemLevel(),
|
||
code, nCode,
|
||
p->GetCount(), 0,
|
||
0, 0,
|
||
0, p->GetItemUID(),
|
||
"", 0,
|
||
"", 0,
|
||
"", 0,
|
||
"ITEM_CONVERT_BY_ALLOC_ITEM", LOG::STR_NTS );
|
||
p->DBQuery( new DB_UpdateItemCode( p ) );
|
||
}
|
||
}
|
||
|
||
return p;
|
||
}
|
||
|
||
void StructItem::PendFreeItem_( StructItem* p, const char* function, const int line )
|
||
{
|
||
static int cnt;
|
||
static AR_TIME prev_proc_time = GetArTime();
|
||
|
||
if( function != NULL )
|
||
{
|
||
p->m_deleteFunction = function;
|
||
p->m_deleteLine = line;
|
||
}
|
||
|
||
// 소환수/펫 아이템이 삭제될 때 함께 삭제되도록 ArcadiaServer::Instance().DeleteObject 를 호출하려면 s_lockForItemDelete가 풀린 상태여야 함
|
||
StructCreature * pSummonPet = NULL;
|
||
{
|
||
THREAD_SYNCRONIZE( &s_lockForItemDelete );
|
||
|
||
//_oprint( "PEND FREE ITEM : %08X\n", p );
|
||
|
||
if( std::find( s_vPendingItemForDelete.begin(), s_vPendingItemForDelete.end(), p ) != s_vPendingItemForDelete.end() )
|
||
{
|
||
assert( 0 );
|
||
|
||
return;
|
||
}
|
||
|
||
s_vPendingItemForDelete.push_back( p );
|
||
p->bIsDeleteRequested = true;
|
||
|
||
// 소환수 카드이고 테이밍 되어 있던 카드일 경우 소환수 제거
|
||
if( p->IsSummonCard() && p->GetSummonStruct() )
|
||
{
|
||
// 역소환 처리가 된 후에 아이템을 삭제해야 하지만, 어디선가 소환된 소환수의 카드를 삭제 시도한 경우
|
||
if( p->GetSummonStruct()->IsInWorld() )
|
||
{
|
||
assert( 0 );
|
||
|
||
// 급한 대로 월드에서 제거
|
||
RemoveSummonFromWorld( p->GetSummonStruct() );
|
||
}
|
||
|
||
pSummonPet = p->GetSummonStruct();
|
||
}
|
||
// 펫 카드일 경우 소속되어 있던 펫 제거
|
||
else if( p->IsPetCage() && p->GetPetStruct() )
|
||
{
|
||
// 역소환 처리가 된 후에 아이템을 삭제해야 하지만, 어디선가 소환된 소환수의 카드를 삭제 시도한 경우
|
||
if( p->GetPetStruct()->IsInWorld() )
|
||
{
|
||
assert( 0 );
|
||
|
||
// 급한 대로 월드에서 제거
|
||
RemovePetFromWorld( p->GetPetStruct() );
|
||
}
|
||
|
||
pSummonPet = p->GetPetStruct();
|
||
}
|
||
|
||
#ifdef _DEBUG
|
||
// 임시
|
||
deletePendingItem();
|
||
#endif // _DEBUG
|
||
|
||
if( ++cnt > 10 )
|
||
{
|
||
cnt = 0;
|
||
AR_TIME t = GetArTime();
|
||
|
||
if( prev_proc_time + 100 < t )
|
||
{
|
||
prev_proc_time = t;
|
||
deletePendingItem();
|
||
}
|
||
}
|
||
}
|
||
|
||
if( pSummonPet )
|
||
{
|
||
ArcadiaServer::Instance().DeleteObject( pSummonPet );
|
||
}
|
||
}
|
||
|
||
void StructItem::deletePendingItem()
|
||
{
|
||
std::vector< StructItem* > vTmp;
|
||
|
||
std::vector< StructItem* >::iterator it;
|
||
|
||
for( it = s_vPendingItemForDelete.begin(); it != s_vPendingItemForDelete.end(); ++it )
|
||
{
|
||
if( !(*it)->IsDeleteable() )
|
||
{
|
||
vTmp.push_back( *it );
|
||
continue;
|
||
}
|
||
|
||
//_oprint( "DELETE ITEM : %08X\n", (*it) );
|
||
freeItem( *it );
|
||
}
|
||
|
||
s_vPendingItemForDelete.swap( vTmp );
|
||
}
|
||
|
||
void StructItem::InitItemSystem()
|
||
{
|
||
ENV().Bind( "game.item_count", (int*)&s_ItemCnt );
|
||
}
|
||
|
||
void StructItem::DeInitItemSystem()
|
||
{
|
||
if( s_pDummyItem )
|
||
freeItemStruct( s_pDummyItem );
|
||
|
||
THREAD_SYNCRONIZE( &s_lockForItemDelete );
|
||
|
||
while( !s_vPendingItemForDelete.empty() ) deletePendingItem();
|
||
}
|
||
|
||
void StructItem::freeItem( StructItem* p )
|
||
{
|
||
// _oprint( "DEL ITEM : %08X\n", p );
|
||
|
||
InterlockedDecrement( &s_ItemCnt );
|
||
|
||
prepareFreeItemStruct( p );
|
||
|
||
p->StructItem::~StructItem();
|
||
|
||
//memset( p, 0xaa, sizeof(StructItem) ); // 임시, 디버깅용
|
||
|
||
freeItemStruct( p );
|
||
}
|
||
|
||
bool StructItem::SetItemUID( ItemUID uid )
|
||
{
|
||
if( m_Instance.UID ) return false;
|
||
m_Instance.UID = uid;
|
||
return true;
|
||
}
|
||
|
||
bool StructItem::ChangeItemCode( ItemBase::ItemCode code )
|
||
{
|
||
// 잘못 된 아이템 코드일 경우 안 바꿔줌
|
||
ItemBaseServer *pBase = &GetItemBase( code );
|
||
if( pBase->nCode == 0 )
|
||
return false;
|
||
|
||
m_pItemBase = pBase;
|
||
m_Instance.Code = code;
|
||
|
||
// 업데이트 플래그는 건들지않는다. (위 단 핸들러에서 판단)
|
||
DBQuery( new DB_UpdateItemCode( this ) );
|
||
return true;
|
||
}
|
||
|
||
StructItem::StructItem( AR_HANDLE handle )
|
||
: m_bQueryLock( "StructItem::m_bQueryLock" )
|
||
{
|
||
m_nArObjectType = ArObject::STATIC_OBJECT;
|
||
|
||
m_bIsVirtualItem = false;
|
||
m_bIsEventDrop = false;
|
||
m_Instance.nLevel = 1;
|
||
m_Instance.nEnhance = 1;
|
||
|
||
m_hHandle = handle;
|
||
|
||
m_nAccountID = 0;
|
||
//m_nOwnSummonUID = -1;
|
||
|
||
m_unInventoryIndex = 0; // 리스트에서의 위치
|
||
|
||
m_nDBUpdateFlag = 0;
|
||
|
||
m_nDropTime = 0;
|
||
|
||
m_pSummon = NULL;
|
||
m_pPet = NULL;
|
||
|
||
m_lQueryList.clear();
|
||
|
||
memset( &m_ItemPickupOrder, 0, sizeof( m_ItemPickupOrder ) );
|
||
|
||
#ifdef _MEM_USAGE_DEBUG
|
||
XSEH::IncreaseAllocCount( "StructItem" );
|
||
#endif
|
||
|
||
m_deleteFunction = NULL;
|
||
m_deleteLine = 0xFFFFFFFF;
|
||
}
|
||
|
||
StructItem::~StructItem()
|
||
{
|
||
#ifdef _MEM_USAGE_DEBUG
|
||
XSEH::DecreaseAllocCount( "StructItem" );
|
||
#endif
|
||
}
|
||
|
||
bool StructItem::ProcDelete()
|
||
{
|
||
if( bIsDeleted ) assert( 0 );
|
||
|
||
StructItem::freeItem( this );
|
||
|
||
return true;
|
||
}
|
||
|
||
bool StructItem::IsWearable() const
|
||
{
|
||
// 장착 불가 아이템
|
||
if( GetWearType() == ItemBase::WEAR_CANTWEAR && !IsChaosStone() )
|
||
return false;
|
||
|
||
// 강화 실패작
|
||
if( GetInstanceFlag().IsOn( ItemInstance::ITEM_FLAG_FAILED ) )
|
||
return false;
|
||
|
||
// 에테리얼 내구도 소모로 인한 파괴
|
||
if( GetMaxEtherealDurability() > 0 && GetCurrentEtherealDurability() <= 0 )
|
||
return false;
|
||
|
||
// 랜덤 옵션 아이템은 오픈하지 않았다면 장착 불가
|
||
if( IsRandomizable() && !IsIdentified() )
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
int StructItem::GetMaxSocketCount() const
|
||
{
|
||
int nSocketCount = GetItemBase().nSocketCount;
|
||
|
||
if( IsRandomizable() )
|
||
{
|
||
if( IsIdentified() )
|
||
{
|
||
// 밸트 소켓과 랜덤옵션의 소켓과는 다르다.
|
||
if( IsBelt() == false )
|
||
{
|
||
for( int i = 0 ; i < ItemInstance::MAX_RANDOM_OPTION_NUMBER ; ++i )
|
||
{
|
||
if( GetIdentifiedOptionType( i ) == ITEM_EFFECT_PASSIVE::INC_SOCKET_COUNT_A || GetIdentifiedOptionType( i ) == ITEM_EFFECT_PASSIVE::INC_SOCKET_COUNT_B )
|
||
{
|
||
nSocketCount += GetIdentifiedOptionValue1( i );
|
||
}
|
||
}
|
||
if( nSocketCount > ItemBase::MAX_SOCKET_NUMBER )
|
||
nSocketCount = ItemBase::MAX_SOCKET_NUMBER;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 미확인 랜덤 옵션
|
||
nSocketCount = 0;
|
||
}
|
||
}
|
||
|
||
return nSocketCount;
|
||
}
|
||
|
||
int StructItem::GetUsingSocketCount() const
|
||
{
|
||
int nUsableSocketNumber = GetMaxSocketCount();
|
||
|
||
if( !nUsableSocketNumber )
|
||
return 0;
|
||
|
||
int nCount = 0;
|
||
for( int i = 0 ; i < nUsableSocketNumber ; ++i )
|
||
{
|
||
ItemBase::ItemCode nSocketCode = GetSocketCode( i );
|
||
|
||
if( nSocketCode )
|
||
++nCount;
|
||
}
|
||
|
||
return nCount;
|
||
}
|
||
|
||
// Instance에 박힌 SummonCode 우선 (베이스, 인스턴스 서몬 코드 둘다 있으면 인스턴스 정보로 줌)
|
||
int StructItem::GetSummonCode() const
|
||
{
|
||
if( m_Instance.nSummonCode )
|
||
return m_Instance.nSummonCode;
|
||
|
||
return m_pItemBase->nSummonId;
|
||
}
|
||
|
||
|
||
void StructItem::SetItemEnhance( int enhance )
|
||
{
|
||
if( m_Instance.nEnhance != enhance )
|
||
{
|
||
m_Instance.nEnhance = enhance;
|
||
GameObject* object = GameObject::raw_get( GetOwnerHandle() );
|
||
if( object && object->IsPlayer() )
|
||
{
|
||
static_cast< StructPlayer* >( object )->UpdateQuestStatusByItemEnhance( GetItemCode() );
|
||
}
|
||
TurnOnUpdateFlag();
|
||
}
|
||
}
|
||
|
||
const int StructItem::ProcEtherealDurabilityConsumption( const int nBaseConsumption, const c_fixed10 & fConsumeRate, const c_fixed10 & fEnvironmentalConsumeRate )
|
||
{
|
||
// 상급 아이템이 아니거나 에테리얼 내구도가 처음부터 0이거나 남은 에테리얼 내구도가 0이면 불가
|
||
// 혹은 벨트 장착용 보스 카드
|
||
if( ( !( GetItemGrade() && GetMaxEtherealDurability() && GetCurrentEtherealDurability() ) && !IsEquipmentOnBelt() ) || GameRule::bItemDurabilitySwitch) // Credits to AziaMafia: Durability consumption switch
|
||
return 0;
|
||
|
||
// 소모율 계산 마무리 + 기본 소모량 적용
|
||
c_fixed10 fConsumption( fConsumeRate + GameRule::GetEtherealDurabilityConsumeRateByItem( GetItemRank(), GetItemGrade() ) );
|
||
fConsumption *= nBaseConsumption;
|
||
|
||
// 상황에 따른 소모율 적용
|
||
fConsumption *= fEnvironmentalConsumeRate;
|
||
fConsumption *= std::min( 1 / GameRule::nEtherealDurabilityConsumptionRate, 1.f );
|
||
|
||
// 소모될 에테리얼 내구도가 0 이하면 패스
|
||
if( static_cast< int >( fConsumption ) <= 0 )
|
||
return 0;
|
||
|
||
int nPrevEtherealDurability = GetCurrentEtherealDurability();
|
||
|
||
|
||
AddCurrentEtherealDurability( -1 * fConsumption );
|
||
|
||
return nPrevEtherealDurability - GetCurrentEtherealDurability();
|
||
}
|
||
|
||
const int StructItem::GetMaxEtherealDurability() const
|
||
{
|
||
float pEnhanceDurability[ 30 ] =
|
||
{
|
||
1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f, 1.7f, 1.8f, 1.9f,
|
||
2.0f, 2.2f, 2.4f, 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, 3.6f, 4.0f,
|
||
5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.f, 10.f, 10.f, 10.f, 10.f
|
||
};
|
||
|
||
// 크리처 카드는 강화 정도에 따라 내구도가 다르다.
|
||
if( IsSummonCard() )
|
||
return GameContent::GetCreatureEnhanceInfo( GetItemEnhance() )->card_durability;
|
||
if( IsEquipment() && GetItemEnhance() > 0 && GetItemEnhance() <= 29 )
|
||
return GetItemBase().nEtherealDurability * pEnhanceDurability[ GetItemEnhance() - 1 ];
|
||
|
||
return GetItemBase().nEtherealDurability;
|
||
}
|
||
|
||
void StructItem::SetCurrentEndurance( int n )
|
||
{
|
||
int nPrevEndurance = m_Instance.nCurrentEndurance;
|
||
m_Instance.nCurrentEndurance = std::max( std::min( n, GetMaxEndurance() ), 0 );
|
||
if( nPrevEndurance != m_Instance.nCurrentEndurance )
|
||
{
|
||
TurnOnUpdateFlag();
|
||
}
|
||
}
|
||
|
||
|
||
int StructItem::GetMaxEndurance() const
|
||
{
|
||
int nUsableSocketNumber = GetMaxSocketCount();
|
||
|
||
if( !nUsableSocketNumber )
|
||
return GetItemBase().nEndurance;
|
||
|
||
int nMaxEndurance = 0;
|
||
for( int i = 0 ; i < nUsableSocketNumber ; ++i )
|
||
{
|
||
ItemBase::ItemCode nSocketCode = GetSocketCode( i );
|
||
|
||
if( !nSocketCode )
|
||
continue;
|
||
|
||
nMaxEndurance += GetItemBase( nSocketCode ).nEndurance;
|
||
}
|
||
|
||
return ( nMaxEndurance ) ? nMaxEndurance : GetItemBase().nEndurance;
|
||
}
|
||
|
||
int StructItem::GetLevelLimit() const
|
||
{
|
||
return std::max( GameRule::GetItemLevelLimitByRank( GetItemRank() ), GetItemBase().nMinLevel );
|
||
}
|
||
|
||
int StructItem::GetRecommendLevel() const
|
||
{
|
||
return GameRule::GetItemRecommendLevel( GetItemRank(), GetItemLevel(), GetItemBase().nMinLevel );
|
||
}
|
||
|
||
void StructItem::SetInstanceFlagOn( int idx )
|
||
{
|
||
if( m_Instance.Flag.IsOn( idx ) )
|
||
return;
|
||
TurnOnUpdateFlag();
|
||
m_Instance.Flag.On( idx );
|
||
}
|
||
|
||
void StructItem::SetInstanceFlagOff( int idx )
|
||
{
|
||
if( !m_Instance.Flag.IsOn( idx ) )
|
||
return;
|
||
TurnOnUpdateFlag();
|
||
m_Instance.Flag.Off( idx );
|
||
}
|
||
|
||
void StructItem::SetBindTarget( struct StructCreature *pTarget, const bool bSkipDBUpdate )
|
||
{
|
||
m_Instance.Socket[0] = pTarget ? ( pTarget->IsPlayer() ? pTarget->GetSID() : 0 ) : 0;
|
||
m_Instance.Socket[1] = pTarget ? ( pTarget->IsSummon() ? pTarget->GetSID() : 0 ) : 0;
|
||
m_hBindedTarget = pTarget ? pTarget->GetHandle() : 0;
|
||
|
||
if( !bSkipDBUpdate )
|
||
TurnOnUpdateFlag();
|
||
}
|
||
|
||
// DB 에 아이템의 소유자를 변경해야 하는 쿼리가 미결되었는데 덜썩 지워버리면 낭패이므로
|
||
// 쿼리가 다 끝나기 전에는 삭제되지 않도록 한다.
|
||
bool StructItem::IsDeleteable()
|
||
{
|
||
THREAD_SYNCRONIZE( m_bQueryLock );
|
||
|
||
if( !GameObject::IsDeleteable() ) return false;
|
||
|
||
if( m_lQueryList.empty() ) return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
void StructItem::DBQuery( GameDBManager::DBProc *pWork )
|
||
{
|
||
THREAD_SYNCRONIZE( m_bQueryLock );
|
||
|
||
if( m_lQueryList.empty() )
|
||
{
|
||
DB().Push( pWork );
|
||
}
|
||
|
||
m_lQueryList.push_back( pWork );
|
||
}
|
||
|
||
void StructItem::onEndQuery()
|
||
{
|
||
THREAD_SYNCRONIZE( m_bQueryLock );
|
||
|
||
m_lQueryList.pop_front();
|
||
|
||
if( !m_lQueryList.empty() )
|
||
{
|
||
DB().Push( m_lQueryList.front() );
|
||
}
|
||
}
|
||
|
||
void StructItem::CopyFrom( StructItem* pFrom )
|
||
{
|
||
AR_HANDLE hOldOwnerHandle = m_Instance.OwnerHandle;
|
||
int nOldOwner = m_Instance.UID;
|
||
|
||
mv = pFrom->mv;
|
||
layer = pFrom->layer;
|
||
m_Instance = pFrom->m_Instance;
|
||
|
||
m_Instance.UID = 0;
|
||
|
||
m_Instance.nOwnerUID = nOldOwner;
|
||
m_Instance.OwnerHandle = hOldOwnerHandle;
|
||
}
|
||
|
||
const bool StructItem::IsReplaceable( const ItemBase::ItemCode code, const bool reset_remain_time )
|
||
{
|
||
// 동종의 아이템은 무조건 실패(시간 제한 재설정은 SetRemainTime 함수를 이용해야 함)
|
||
if( code == m_Instance.Code )
|
||
return false;
|
||
|
||
ItemBaseServer *pNewItemBase = &GetItemBase( code );
|
||
|
||
// 기존에 시간 제한이 있던 아이템이었을 경우 영구템으로 변경하는데 남은 시간을 초기화하지 않아야하는 경우라면 불가능
|
||
if( IsExpireItem() && !reset_remain_time && pNewItemBase->decrease_type == ItemBase::PERMANENT )
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 중첩 가능 아이템을 중첩 불가 아이템으로 바꾸는데 기존 수량이 1개가 아니면 불가능
|
||
if( !pNewItemBase->Flag.IsOn( ItemBase::FLAG_JOIN ) && GetCount() != 1 )
|
||
return false;
|
||
|
||
// 아이템 생성 당시 기본 InstanceFlag 가 다른 아이템으로는 변경 불가능(아이템 생성 이후 어떻게 바뀌었는지가 날아감 -_ -;)
|
||
if( pNewItemBase->nInstanceFlag != GetItemBase().nInstanceFlag )
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool StructItem::IsExpireItem() const
|
||
{
|
||
if( GetItemBase().decrease_type == ItemBase::DECREASE_ON_GAME || GetItemBase().decrease_type == ItemBase::DECREASE_ALWAYS )
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
const unsigned short StructItem::SetElementalEffect( const unsigned char & cType, const time_t & tExpire )
|
||
{
|
||
if( !IsWeapon() )
|
||
{
|
||
return RESULT_NOT_ACTABLE;
|
||
}
|
||
|
||
m_Instance.cElementalEffectType = cType;
|
||
m_Instance.tElementalEffectExpire = tExpire;
|
||
|
||
// 속성 이펙트 적용 시 기존에 있던 추가 성능은 제거됨
|
||
m_Instance.nElementalEffectAttackPoint = 0;
|
||
m_Instance.nElementalEffectMagicPoint = 0;
|
||
|
||
return RESULT_SUCCESS;
|
||
}
|
||
|
||
const unsigned short StructItem::SetElementalEffectAttackPoint( const int nAttackPoint )
|
||
{
|
||
if( !IsWeapon() || !GetElementalEffectType() )
|
||
{
|
||
return RESULT_NOT_ACTABLE;
|
||
}
|
||
|
||
// 속성 이펙트 추가 공격력 설정
|
||
m_Instance.nElementalEffectAttackPoint = nAttackPoint;
|
||
|
||
return RESULT_SUCCESS;
|
||
}
|
||
|
||
const unsigned short StructItem::SetElementalEffectMagicPoint( const int nMagicPoint )
|
||
{
|
||
if( !IsWeapon() || !GetElementalEffectType() )
|
||
{
|
||
return RESULT_NOT_ACTABLE;
|
||
}
|
||
|
||
// 속성 이펙트 추가 마력 설정
|
||
m_Instance.nElementalEffectMagicPoint = nMagicPoint;
|
||
|
||
return RESULT_SUCCESS;
|
||
}
|
||
|
||
void StructItem::ClearElementalEffect()
|
||
{
|
||
// 속성 이펙트 초기화
|
||
m_Instance.cElementalEffectType = 0;
|
||
m_Instance.tElementalEffectExpire = 0;
|
||
|
||
// 추가 공격력/마력 성능 초기화
|
||
m_Instance.nElementalEffectAttackPoint = 0;
|
||
m_Instance.nElementalEffectMagicPoint = 0;
|
||
}
|
||
|
||
bool StructItem::SetRandomOption( ItemInstance::RANDOM_TYPE eType, ItemInstance::RANDOM_OPTION & RandomOption )
|
||
{
|
||
SetRandomSID( eType, RandomOption.nSID );
|
||
|
||
m_Instance.RandomOption[ eType ].nRandomType = eType;
|
||
|
||
for( int nCnt = 0 ; nCnt < ItemInstance::MAX_RANDOM_OPTION_NUMBER; ++nCnt )
|
||
{
|
||
m_Instance.RandomOption[ eType ].OptionInfo[ nCnt ].nType = RandomOption.OptionInfo[ nCnt ].nType;
|
||
m_Instance.RandomOption[ eType ].OptionInfo[ nCnt ].fValue1 = RandomOption.OptionInfo[ nCnt ].fValue1;
|
||
m_Instance.RandomOption[ eType ].OptionInfo[ nCnt ].fValue2 = RandomOption.OptionInfo[ nCnt ].fValue2;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
ItemInstance::RANDOM_OPTION* StructItem::GetRandomOption( ItemInstance::RANDOM_TYPE eType )
|
||
{
|
||
if( eType < 0 || eType >= ItemInstance::MAX )
|
||
return NULL;
|
||
|
||
return &( m_Instance.RandomOption[ eType ] );
|
||
}
|
||
|
||
// Fraun Sky Accessories 7/12/2025
|
||
bool StructItem::ChangeAdditionalItemEffect(ItemBase::ItemCode code, int nItemEffectID)
|
||
{
|
||
ItemBaseServer* pBase = &GetItemBase(code);
|
||
if (pBase->nCode == 0)
|
||
return false;
|
||
|
||
m_pItemBase = pBase;
|
||
m_Instance.Code = code;
|
||
m_Instance.nAdditionalItemEffect = nItemEffectID;
|
||
|
||
|
||
DBQuery(new DB_UpdateItemEffect(this));
|
||
return true;
|
||
} |