#include #include #include #include #include #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 > 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 > 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; }