Files
m2dev-server-src/src/game/item.cpp
eccentricride-sudo 5a593b7fbf Revert onaftercreated function for those having issues with it
Since some of you said there s an issue with the fix on onaftercreateditem, causing errors. To my understanding the issue is timing, the item_del is sent at the wrong time when client isn t ready yet. A small delay of 1 sec seems to fix it without any issues nor syserrs. I'll do a summarized explanation of what it does below: When real time expire function is called it checks in which phase the player is currently in, if in phase_loading(where crash occurs) it calls the event again after one second. If not in phase loading then it skips the block and deletes the item immediately. If you're wondering if it can be exploited it cannot. No desc -> player disconnecting during loading phase item doesn't reach the actual deletion phase, logs in again onaftercreateditem is called -> calls startrealtimeexpireevent which starts the event again.
2026-01-08 07:44:41 -08:00

2106 lines
46 KiB
C++

#include "stdafx.h"
#include "utils.h"
#include "config.h"
#include "char.h"
#include "desc.h"
#include "sectree_manager.h"
#include "packet.h"
#include "protocol.h"
#include "log.h"
#include "skill.h"
#include "unique_item.h"
#include "profiler.h"
#include "marriage.h"
#include "item_addon.h"
#include "locale_service.h"
#include "item.h"
#include "item_manager.h"
#include "affect.h"
#include "DragonSoul.h"
#include "buff_on_attributes.h"
#include "belt_inventory_helper.h"
#include "common/VnumHelper.h"
CItem::CItem(DWORD dwVnum)
: m_dwVnum(dwVnum), m_bWindow(0), m_dwID(0), m_bEquipped(false), m_dwVID(0), m_wCell(0), m_dwCount(0), m_lFlag(0), m_dwLastOwnerPID(0),
m_bExchanging(false), m_pkDestroyEvent(NULL), m_pkUniqueExpireEvent(NULL), m_pkTimerBasedOnWearExpireEvent(NULL), m_pkRealTimeExpireEvent(NULL),
m_pkExpireEvent(NULL),
m_pkAccessorySocketExpireEvent(NULL), m_pkOwnershipEvent(NULL), m_dwOwnershipPID(0), m_bSkipSave(false), m_isLocked(false),
m_dwMaskVnum(0), m_dwSIGVnum (0)
{
memset( &m_alSockets, 0, sizeof(m_alSockets) );
memset( &m_aAttr, 0, sizeof(m_aAttr) );
}
CItem::~CItem()
{
Destroy();
}
void CItem::Initialize()
{
CEntity::Initialize(ENTITY_ITEM);
m_bWindow = RESERVED_WINDOW;
m_pOwner = NULL;
m_dwID = 0;
m_bEquipped = false;
m_dwVID = m_wCell = m_dwCount = m_lFlag = 0;
m_pProto = NULL;
m_bExchanging = false;
memset(&m_alSockets, 0, sizeof(m_alSockets));
memset(&m_aAttr, 0, sizeof(m_aAttr));
m_pkDestroyEvent = NULL;
m_pkOwnershipEvent = NULL;
m_dwOwnershipPID = 0;
m_pkUniqueExpireEvent = NULL;
m_pkTimerBasedOnWearExpireEvent = NULL;
m_pkRealTimeExpireEvent = NULL;
m_pkAccessorySocketExpireEvent = NULL;
m_bSkipSave = false;
m_dwLastOwnerPID = 0;
}
void CItem::Destroy()
{
event_cancel(&m_pkDestroyEvent);
event_cancel(&m_pkOwnershipEvent);
event_cancel(&m_pkUniqueExpireEvent);
event_cancel(&m_pkTimerBasedOnWearExpireEvent);
event_cancel(&m_pkRealTimeExpireEvent);
event_cancel(&m_pkAccessorySocketExpireEvent);
CEntity::Destroy();
if (GetSectree())
GetSectree()->RemoveEntity(this);
}
EVENTFUNC(item_destroy_event)
{
item_event_info* info = dynamic_cast<item_event_info*>( event->info );
if ( info == NULL )
{
sys_err( "item_destroy_event> <Factor> Null pointer" );
return 0;
}
LPITEM pkItem = info->item;
if (pkItem->GetOwner())
sys_err("item_destroy_event: Owner exist. (item %s owner %s)", pkItem->GetName(), pkItem->GetOwner()->GetName());
pkItem->SetDestroyEvent(NULL);
M2_DESTROY_ITEM(pkItem);
return 0;
}
void CItem::SetDestroyEvent(LPEVENT pkEvent)
{
m_pkDestroyEvent = pkEvent;
}
void CItem::StartDestroyEvent(int iSec)
{
if (m_pkDestroyEvent)
return;
item_event_info* info = AllocEventInfo<item_event_info>();
info->item = this;
SetDestroyEvent(event_create(item_destroy_event, info, PASSES_PER_SEC(iSec)));
}
void CItem::EncodeInsertPacket(LPENTITY ent)
{
LPDESC d;
if (!(d = ent->GetDesc()))
return;
const PIXEL_POSITION & c_pos = GetXYZ();
struct packet_item_ground_add pack;
pack.bHeader = HEADER_GC_ITEM_GROUND_ADD;
pack.x = c_pos.x;
pack.y = c_pos.y;
pack.z = c_pos.z;
pack.dwVnum = GetVnum();
pack.dwVID = m_dwVID;
//pack.count = m_dwCount;
d->Packet(&pack, sizeof(pack));
if (m_pkOwnershipEvent != NULL)
{
item_event_info * info = dynamic_cast<item_event_info *>(m_pkOwnershipEvent->info);
if ( info == NULL )
{
sys_err( "CItem::EncodeInsertPacket> <Factor> Null pointer" );
return;
}
TPacketGCItemOwnership p;
p.bHeader = HEADER_GC_ITEM_OWNERSHIP;
p.dwVID = m_dwVID;
strlcpy(p.szName, info->szOwnerName, sizeof(p.szName));
d->Packet(&p, sizeof(TPacketGCItemOwnership));
}
}
void CItem::EncodeRemovePacket(LPENTITY ent)
{
LPDESC d;
if (!(d = ent->GetDesc()))
return;
struct packet_item_ground_del pack;
pack.bHeader = HEADER_GC_ITEM_GROUND_DEL;
pack.dwVID = m_dwVID;
d->Packet(&pack, sizeof(pack));
sys_log(2, "Item::EncodeRemovePacket %s to %s", GetName(), ((LPCHARACTER) ent)->GetName());
}
void CItem::SetProto(const TItemTable * table)
{
assert(table != NULL);
m_pProto = table;
SetFlag(m_pProto->dwFlags);
}
void CItem::UsePacketEncode(LPCHARACTER ch, LPCHARACTER victim, struct packet_item_use *packet)
{
if (!GetVnum())
return;
packet->header = HEADER_GC_ITEM_USE;
packet->ch_vid = ch->GetVID();
packet->victim_vid = victim->GetVID();
packet->Cell = TItemPos(GetWindow(), m_wCell);
packet->vnum = GetVnum();
}
void CItem::RemoveFlag(long bit)
{
REMOVE_BIT(m_lFlag, bit);
}
void CItem::AddFlag(long bit)
{
SET_BIT(m_lFlag, bit);
}
void CItem::UpdatePacket()
{
if (!m_pOwner || !m_pOwner->GetDesc())
return;
TPacketGCItemUpdate pack;
pack.header = HEADER_GC_ITEM_UPDATE;
pack.Cell = TItemPos(GetWindow(), m_wCell);
pack.count = m_dwCount;
for (int i = 0; i < ITEM_SOCKET_MAX_NUM; ++i)
pack.alSockets[i] = m_alSockets[i];
thecore_memcpy(pack.aAttr, GetAttributes(), sizeof(pack.aAttr));
sys_log(2, "UpdatePacket %s -> %s", GetName(), m_pOwner->GetName());
m_pOwner->GetDesc()->Packet(&pack, sizeof(pack));
}
DWORD CItem::GetCount()
{
if (GetType() == ITEM_ELK) return MIN(m_dwCount, INT_MAX);
else
{
return MIN(m_dwCount, 200);
}
}
bool CItem::SetCount(DWORD count)
{
if (GetType() == ITEM_ELK)
{
m_dwCount = MIN(count, INT_MAX);
}
else
{
m_dwCount = MIN(count, ITEM_MAX_COUNT);
}
if (count == 0 && m_pOwner)
{
if (GetSubType() == USE_ABILITY_UP || GetSubType() == USE_POTION || GetVnum() == 70020)
{
LPCHARACTER pOwner = GetOwner();
WORD wCell = GetCell();
RemoveFromCharacter();
if (!IsDragonSoul())
{
LPITEM pItem = pOwner->FindSpecifyItem(GetVnum());
if (NULL != pItem)
{
pOwner->ChainQuickslotItem(pItem, QUICKSLOT_TYPE_ITEM, wCell);
}
else
{
pOwner->SyncQuickslot(QUICKSLOT_TYPE_ITEM, wCell, 255);
}
}
M2_DESTROY_ITEM(this);
}
else
{
if (!IsDragonSoul())
{
m_pOwner->SyncQuickslot(QUICKSLOT_TYPE_ITEM, m_wCell, 255);
}
M2_DESTROY_ITEM(RemoveFromCharacter());
}
return false;
}
UpdatePacket();
Save();
return true;
}
LPITEM CItem::RemoveFromCharacter()
{
if (!m_pOwner)
{
sys_err("Item::RemoveFromCharacter owner null");
return (this);
}
LPCHARACTER pOwner = m_pOwner;
if (m_bEquipped) // 장착되었는가?
{
Unequip();
//pOwner->UpdatePacket();
SetWindow(RESERVED_WINDOW);
Save();
return (this);
}
else
{
if (GetWindow() != SAFEBOX && GetWindow() != MALL)
{
if (IsDragonSoul())
{
if (m_wCell >= DRAGON_SOUL_INVENTORY_MAX_NUM)
sys_err("CItem::RemoveFromCharacter: pos >= DRAGON_SOUL_INVENTORY_MAX_NUM");
else
pOwner->SetItem(TItemPos(m_bWindow, m_wCell), NULL);
}
else
{
TItemPos cell(INVENTORY, m_wCell);
if (false == cell.IsDefaultInventoryPosition() && false == cell.IsBeltInventoryPosition()) // 아니면 소지품에?
sys_err("CItem::RemoveFromCharacter: Invalid Item Position");
else
{
// MR-3: Auto-deactivate auto potions before selling to vendors/removing
// Auto potion deactivation for NPC sell and similar removals
// Only deactivate if not from safebox or mall
if (GetWindow() != SAFEBOX && GetWindow() != MALL)
{
// Check for auto potion vnums (50200, 50201, 50202)
DWORD vnum = GetVnum();
if (vnum == 50200 || vnum == 50201 || vnum == 50202)
{
// Set socket 0 to 0 (deactivate)
SetSocket(0, 0);
}
}
// MR-3: -- END OF -- Auto-deactivate auto potions before selling to vendors/removing
pOwner->SetItem(cell, NULL);
}
}
}
m_pOwner = NULL;
m_wCell = 0;
SetWindow(RESERVED_WINDOW);
Save();
return (this);
}
}
bool CItem::AddToCharacter(LPCHARACTER ch, TItemPos Cell)
{
assert(GetSectree() == NULL);
assert(m_pOwner == NULL);
WORD pos = Cell.cell;
BYTE window_type = Cell.window_type;
if (INVENTORY == window_type)
{
if (m_wCell >= INVENTORY_MAX_NUM && BELT_INVENTORY_SLOT_START > m_wCell)
{
sys_err("CItem::AddToCharacter: cell overflow: %s to %s cell %d", m_pProto->szName, ch->GetName(), m_wCell);
return false;
}
}
else if (DRAGON_SOUL_INVENTORY == window_type)
{
if (m_wCell >= DRAGON_SOUL_INVENTORY_MAX_NUM)
{
sys_err("CItem::AddToCharacter: cell overflow: %s to %s cell %d", m_pProto->szName, ch->GetName(), m_wCell);
return false;
}
}
if (ch->GetDesc())
m_dwLastOwnerPID = ch->GetPlayerID();
event_cancel(&m_pkDestroyEvent);
ch->SetItem(TItemPos(window_type, pos), this);
m_pOwner = ch;
Save();
return true;
}
LPITEM CItem::RemoveFromGround()
{
if (GetSectree())
{
SetOwnership(NULL);
GetSectree()->RemoveEntity(this);
ViewCleanup();
Save();
}
return (this);
}
bool CItem::AddToGround(long lMapIndex, const PIXEL_POSITION & pos, bool skipOwnerCheck)
{
if (0 == lMapIndex)
{
sys_err("wrong map index argument: %d", lMapIndex);
return false;
}
if (GetSectree())
{
sys_err("sectree already assigned");
return false;
}
if (!skipOwnerCheck && m_pOwner)
{
sys_err("owner pointer not null");
return false;
}
LPSECTREE tree = SECTREE_MANAGER::instance().Get(lMapIndex, pos.x, pos.y);
if (!tree)
{
sys_err("cannot find sectree by %dx%d", pos.x, pos.y);
return false;
}
//tree->Touch();
SetWindow(GROUND);
SetXYZ(pos.x, pos.y, pos.z);
tree->InsertEntity(this);
UpdateSectree();
Save();
return true;
}
bool CItem::DistanceValid(LPCHARACTER ch)
{
if (!GetSectree())
return false;
int iDist = DISTANCE_APPROX(GetX() - ch->GetX(), GetY() - ch->GetY());
if (iDist > 300)
return false;
return true;
}
bool CItem::CanUsedBy(LPCHARACTER ch)
{
// Anti flag check
switch (ch->GetJob())
{
case JOB_WARRIOR:
if (GetAntiFlag() & ITEM_ANTIFLAG_WARRIOR)
return false;
break;
case JOB_ASSASSIN:
if (GetAntiFlag() & ITEM_ANTIFLAG_ASSASSIN)
return false;
break;
case JOB_SHAMAN:
if (GetAntiFlag() & ITEM_ANTIFLAG_SHAMAN)
return false;
break;
case JOB_SURA:
if (GetAntiFlag() & ITEM_ANTIFLAG_SURA)
return false;
break;
}
return true;
}
int CItem::FindEquipCell(LPCHARACTER ch, int iCandidateCell)
{
// 코스츔 아이템(ITEM_COSTUME)은 WearFlag 없어도 됨. (sub type으로 착용위치 구분. 귀찮게 또 wear flag 줄 필요가 있나..)
// 용혼석(ITEM_DS, ITEM_SPECIAL_DS)도 SUB_TYPE으로 구분. 신규 반지, 벨트는 ITEM_TYPE으로 구분 -_-
if ((0 == GetWearFlag() || ITEM_TOTEM == GetType()) && ITEM_COSTUME != GetType() && ITEM_DS != GetType() && ITEM_SPECIAL_DS != GetType() && ITEM_RING != GetType() && ITEM_BELT != GetType())
return -1;
// 용혼석 슬롯을 WEAR로 처리할 수가 없어서(WEAR는 최대 32개까지 가능한데 용혼석을 추가하면 32가 넘는다.)
// 인벤토리의 특정 위치((INVENTORY_MAX_NUM + WEAR_MAX_NUM)부터 (INVENTORY_MAX_NUM + WEAR_MAX_NUM + DRAGON_SOUL_DECK_MAX_NUM * DS_SLOT_MAX - 1)까지)를
// 용혼석 슬롯으로 정함.
// return 할 때에, INVENTORY_MAX_NUM을 뺀 이유는,
// 본래 WearCell이 INVENTORY_MAX_NUM를 빼고 return 하기 때문.
if (GetType() == ITEM_DS || GetType() == ITEM_SPECIAL_DS)
{
if (iCandidateCell < 0)
{
return WEAR_MAX_NUM + GetSubType();
}
else
{
for (int i = 0; i < DRAGON_SOUL_DECK_MAX_NUM; i++)
{
if (WEAR_MAX_NUM + i * DS_SLOT_MAX + GetSubType() == iCandidateCell)
{
return iCandidateCell;
}
}
return -1;
}
}
else if (GetType() == ITEM_COSTUME)
{
if (GetSubType() == COSTUME_BODY)
return WEAR_COSTUME_BODY;
else if (GetSubType() == COSTUME_HAIR)
return WEAR_COSTUME_HAIR;
}
else if (GetType() == ITEM_RING)
{
if (ch->GetWear(WEAR_RING1))
return WEAR_RING2;
else
return WEAR_RING1;
}
else if (GetType() == ITEM_BELT)
return WEAR_BELT;
else if (GetWearFlag() & WEARABLE_BODY)
return WEAR_BODY;
else if (GetWearFlag() & WEARABLE_HEAD)
return WEAR_HEAD;
else if (GetWearFlag() & WEARABLE_FOOTS)
return WEAR_FOOTS;
else if (GetWearFlag() & WEARABLE_WRIST)
return WEAR_WRIST;
else if (GetWearFlag() & WEARABLE_WEAPON)
return WEAR_WEAPON;
else if (GetWearFlag() & WEARABLE_SHIELD)
return WEAR_SHIELD;
else if (GetWearFlag() & WEARABLE_NECK)
return WEAR_NECK;
else if (GetWearFlag() & WEARABLE_EAR)
return WEAR_EAR;
else if (GetWearFlag() & WEARABLE_ARROW)
return WEAR_ARROW;
else if (GetWearFlag() & WEARABLE_UNIQUE)
{
if (ch->GetWear(WEAR_UNIQUE1))
return WEAR_UNIQUE2;
else
return WEAR_UNIQUE1;
}
// 수집 퀘스트를 위한 아이템이 박히는곳으로 한번 박히면 절대 뺼수 없다.
else if (GetWearFlag() & WEARABLE_ABILITY)
{
if (!ch->GetWear(WEAR_ABILITY1))
{
return WEAR_ABILITY1;
}
else if (!ch->GetWear(WEAR_ABILITY2))
{
return WEAR_ABILITY2;
}
else if (!ch->GetWear(WEAR_ABILITY3))
{
return WEAR_ABILITY3;
}
else if (!ch->GetWear(WEAR_ABILITY4))
{
return WEAR_ABILITY4;
}
else if (!ch->GetWear(WEAR_ABILITY5))
{
return WEAR_ABILITY5;
}
else if (!ch->GetWear(WEAR_ABILITY6))
{
return WEAR_ABILITY6;
}
else if (!ch->GetWear(WEAR_ABILITY7))
{
return WEAR_ABILITY7;
}
else if (!ch->GetWear(WEAR_ABILITY8))
{
return WEAR_ABILITY8;
}
else
{
return -1;
}
}
return -1;
}
void CItem::ModifyPoints(bool bAdd)
{
int accessoryGrade;
// 무기와 갑옷만 소켓을 적용시킨다.
if (false == IsAccessoryForSocket())
{
if (m_pProto->bType == ITEM_WEAPON || m_pProto->bType == ITEM_ARMOR)
{
// 소켓이 속성강화에 사용되는 경우 적용하지 않는다 (ARMOR_WRIST ARMOR_NECK ARMOR_EAR)
for (int i = 0; i < ITEM_SOCKET_MAX_NUM; ++i)
{
DWORD dwVnum;
if ((dwVnum = GetSocket(i)) <= 2)
continue;
TItemTable * p = ITEM_MANAGER::instance().GetTable(dwVnum);
if (!p)
{
sys_err("cannot find table by vnum %u", dwVnum);
continue;
}
if (ITEM_METIN == p->bType)
{
//m_pOwner->ApplyPoint(p->alValues[0], bAdd ? p->alValues[1] : -p->alValues[1]);
for (int i = 0; i < ITEM_APPLY_MAX_NUM; ++i)
{
if (p->aApplies[i].bType == APPLY_NONE)
continue;
if (p->aApplies[i].bType == APPLY_SKILL)
m_pOwner->ApplyPoint(p->aApplies[i].bType, bAdd ? p->aApplies[i].lValue : p->aApplies[i].lValue ^ 0x00800000);
else
m_pOwner->ApplyPoint(p->aApplies[i].bType, bAdd ? p->aApplies[i].lValue : -p->aApplies[i].lValue);
}
}
}
}
accessoryGrade = 0;
}
else
{
accessoryGrade = MIN(GetAccessorySocketGrade(), ITEM_ACCESSORY_SOCKET_MAX_NUM);
}
for (int i = 0; i < ITEM_APPLY_MAX_NUM; ++i)
{
if (m_pProto->aApplies[i].bType == APPLY_NONE)
continue;
long value = m_pProto->aApplies[i].lValue;
if (m_pProto->aApplies[i].bType == APPLY_SKILL)
{
m_pOwner->ApplyPoint(m_pProto->aApplies[i].bType, bAdd ? value : value ^ 0x00800000);
}
else
{
if (0 != accessoryGrade)
value += MAX(accessoryGrade, value * aiAccessorySocketEffectivePct[accessoryGrade] / 100);
m_pOwner->ApplyPoint(m_pProto->aApplies[i].bType, bAdd ? value : -value);
}
}
// 초승달의 반지, 할로윈 사탕, 행복의 반지, 영원한 사랑의 펜던트의 경우
// 기존의 하드 코딩으로 강제로 속성을 부여했지만,
// 그 부분을 제거하고 special item group 테이블에서 속성을 부여하도록 변경하였다.
// 하지만 하드 코딩되어있을 때 생성된 아이템이 남아있을 수도 있어서 특수처리 해놓는다.
// 이 아이템들의 경우, 밑에 ITEM_UNIQUE일 때의 처리로 속성이 부여되기 때문에,
// 아이템에 박혀있는 attribute는 적용하지 않고 넘어간다.
if (true == CItemVnumHelper::IsRamadanMoonRing(GetVnum()) || true == CItemVnumHelper::IsHalloweenCandy(GetVnum())
|| true == CItemVnumHelper::IsHappinessRing(GetVnum()) || true == CItemVnumHelper::IsLovePendant(GetVnum()))
{
// Do not anything.
}
else
{
for (int i = 0; i < ITEM_ATTRIBUTE_MAX_NUM; ++i)
{
if (GetAttributeType(i))
{
const TPlayerItemAttribute& ia = GetAttribute(i);
if (ia.bType == APPLY_SKILL)
m_pOwner->ApplyPoint(ia.bType, bAdd ? ia.sValue : ia.sValue ^ 0x00800000);
else
m_pOwner->ApplyPoint(ia.bType, bAdd ? ia.sValue : -ia.sValue);
}
}
}
switch (m_pProto->bType)
{
case ITEM_PICK:
case ITEM_ROD:
{
if (bAdd)
{
if (m_wCell == (WORD)INVENTORY_MAX_NUM + (WORD)WEAR_WEAPON)
m_pOwner->SetPart(PART_WEAPON, GetVnum());
}
else
{
if (m_wCell == (WORD)INVENTORY_MAX_NUM + (WORD)WEAR_WEAPON)
m_pOwner->SetPart(PART_WEAPON, m_pOwner->GetOriginalPart(PART_WEAPON));
}
}
break;
case ITEM_WEAPON:
{
if (bAdd)
{
if (m_wCell == (WORD)INVENTORY_MAX_NUM + (WORD)WEAR_WEAPON)
m_pOwner->SetPart(PART_WEAPON, GetVnum());
}
else
{
if (m_wCell == (WORD)INVENTORY_MAX_NUM + (WORD)WEAR_WEAPON)
m_pOwner->SetPart(PART_WEAPON, m_pOwner->GetOriginalPart(PART_WEAPON));
}
}
break;
case ITEM_ARMOR:
{
// 코스츔 body를 입고있다면 armor는 벗던 입던 상관 없이 비주얼에 영향을 주면 안 됨.
if (0 != m_pOwner->GetWear(WEAR_COSTUME_BODY))
break;
if (GetSubType() == ARMOR_BODY || GetSubType() == ARMOR_HEAD || GetSubType() == ARMOR_FOOTS || GetSubType() == ARMOR_SHIELD)
{
if (bAdd)
{
if (GetProto()->bSubType == ARMOR_BODY)
m_pOwner->SetPart(PART_MAIN, GetVnum());
}
else
{
if (GetProto()->bSubType == ARMOR_BODY)
m_pOwner->SetPart(PART_MAIN, m_pOwner->GetOriginalPart(PART_MAIN));
}
}
}
break;
// 코스츔 아이템 입었을 때 캐릭터 parts 정보 세팅. 기존 스타일대로 추가함..
case ITEM_COSTUME:
{
DWORD toSetValue = this->GetVnum();
EParts toSetPart = PART_MAX_NUM;
// 갑옷 코스츔
if (GetSubType() == COSTUME_BODY)
{
toSetPart = PART_MAIN;
if (false == bAdd)
{
// 코스츔 갑옷을 벗었을 때 원래 갑옷을 입고 있었다면 그 갑옷으로 look 세팅, 입지 않았다면 default look
const CItem* pArmor = m_pOwner->GetWear(WEAR_BODY);
toSetValue = (NULL != pArmor) ? pArmor->GetVnum() : m_pOwner->GetOriginalPart(PART_MAIN);
}
}
// 헤어 코스츔
else if (GetSubType() == COSTUME_HAIR)
{
toSetPart = PART_HAIR;
// 코스츔 헤어는 shape값을 item proto의 value3에 세팅하도록 함. 특별한 이유는 없고 기존 갑옷(ARMOR_BODY)의 shape값이 프로토의 value3에 있어서 헤어도 같이 value3으로 함.
// [NOTE] 갑옷은 아이템 vnum을 보내고 헤어는 shape(value3)값을 보내는 이유는.. 기존 시스템이 그렇게 되어있음...
toSetValue = (true == bAdd) ? this->GetValue(3) : 0;
}
if (PART_MAX_NUM != toSetPart)
{
m_pOwner->SetPart((BYTE)toSetPart, toSetValue);
m_pOwner->UpdatePacket();
}
}
break;
case ITEM_UNIQUE:
{
if (0 != GetSIGVnum())
{
const CSpecialItemGroup* pItemGroup = ITEM_MANAGER::instance().GetSpecialItemGroup(GetSIGVnum());
if (NULL == pItemGroup)
break;
DWORD dwAttrVnum = pItemGroup->GetAttrVnum(GetVnum());
const CSpecialAttrGroup* pAttrGroup = ITEM_MANAGER::instance().GetSpecialAttrGroup(dwAttrVnum);
if (NULL == pAttrGroup)
break;
for (itertype (pAttrGroup->m_vecAttrs) it = pAttrGroup->m_vecAttrs.begin(); it != pAttrGroup->m_vecAttrs.end(); it++)
{
m_pOwner->ApplyPoint(it->apply_type, bAdd ? it->apply_value : -it->apply_value);
}
}
}
break;
}
}
bool CItem::IsEquipable() const
{
switch (this->GetType())
{
case ITEM_COSTUME:
case ITEM_ARMOR:
case ITEM_WEAPON:
case ITEM_ROD:
case ITEM_PICK:
case ITEM_UNIQUE:
case ITEM_DS:
case ITEM_SPECIAL_DS:
case ITEM_RING:
case ITEM_BELT:
return true;
}
return false;
}
// return false on error state
bool CItem::EquipTo(LPCHARACTER ch, BYTE bWearCell)
{
if (!ch)
{
sys_err("EquipTo: nil character");
return false;
}
// 용혼석 슬롯 index는 WEAR_MAX_NUM 보다 큼.
if (IsDragonSoul())
{
if (bWearCell < WEAR_MAX_NUM || bWearCell >= WEAR_MAX_NUM + (DWORD)DRAGON_SOUL_DECK_MAX_NUM * (DWORD)DS_SLOT_MAX)
{
sys_err("EquipTo: invalid dragon soul cell (this: #%d %s wearflag: %d cell: %d)", GetOriginalVnum(), GetName(), GetSubType(), bWearCell - WEAR_MAX_NUM);
return false;
}
}
else
{
if (bWearCell >= WEAR_MAX_NUM)
{
sys_err("EquipTo: invalid wear cell (this: #%d %s wearflag: %d cell: %d)", GetOriginalVnum(), GetName(), GetWearFlag(), bWearCell);
return false;
}
}
if (ch->GetWear(bWearCell))
{
sys_err("EquipTo: item already exist (this: #%d %s cell: %d %s)", GetOriginalVnum(), GetName(), bWearCell, ch->GetWear(bWearCell)->GetName());
return false;
}
if (GetOwner())
RemoveFromCharacter();
ch->SetWear(bWearCell, this); // 여기서 패킷 나감
m_pOwner = ch;
m_bEquipped = true;
m_wCell = INVENTORY_MAX_NUM + bWearCell;
// Original immun
// DWORD dwImmuneFlag = 0;
// for (int i = 0; i < WEAR_MAX_NUM; ++i)
// if (m_pOwner->GetWear(i))
// SET_BIT(dwImmuneFlag, m_pOwner->GetWear(i)->m_pProto->dwImmuneFlag);
// m_pOwner->SetImmuneFlag(dwImmuneFlag);
//P3NG3R immun bug fix
DWORD dwImmuneFlag = 0;
LPITEM item = NULL;
for (int i = 0; i < WEAR_MAX_NUM; ++i)
{
if ((item = m_pOwner->GetWear(i)))
{
if (item->GetImmuneFlag() != 0)
SET_BIT(dwImmuneFlag, item->GetImmuneFlag());
if (item->GetAttributeCount() > 0)
{
if (item->HasAttr(APPLY_IMMUNE_STUN))
SET_BIT(dwImmuneFlag, IMMUNE_STUN);
if (item->HasAttr(APPLY_IMMUNE_SLOW))
SET_BIT(dwImmuneFlag, IMMUNE_SLOW);
if (item->HasAttr(APPLY_IMMUNE_FALL))
SET_BIT(dwImmuneFlag, IMMUNE_FALL);
}
}
}
m_pOwner->SetImmuneFlag(dwImmuneFlag);
//P3NG3R immun fix end
if (IsDragonSoul())
{
DSManager::instance().ActivateDragonSoul(this);
}
else
{
ModifyPoints(true);
StartUniqueExpireEvent();
if (-1 != GetProto()->cLimitTimerBasedOnWearIndex)
StartTimerBasedOnWearExpireEvent();
// ACCESSORY_REFINE
StartAccessorySocketExpireEvent();
// END_OF_ACCESSORY_REFINE
}
ch->BuffOnAttr_AddBuffsFromItem(this);
m_pOwner->ComputeBattlePoints();
m_pOwner->UpdatePacket();
Save();
return (true);
}
bool CItem::Unequip()
{
if (!m_pOwner || GetCell() < INVENTORY_MAX_NUM)
{
// ITEM_OWNER_INVALID_PTR_BUG
sys_err("%s %u m_pOwner %p, GetCell %d",
GetName(), GetID(), get_pointer(m_pOwner), GetCell());
// END_OF_ITEM_OWNER_INVALID_PTR_BUG
return false;
}
if (this != m_pOwner->GetWear(GetCell() - INVENTORY_MAX_NUM))
{
sys_err("m_pOwner->GetWear() != this");
return false;
}
//신규 말 아이템 제거시 처리
if (IsRideItem())
ClearMountAttributeAndAffect();
if (IsDragonSoul())
{
DSManager::instance().DeactivateDragonSoul(this);
}
else
{
ModifyPoints(false);
}
StopUniqueExpireEvent();
if (-1 != GetProto()->cLimitTimerBasedOnWearIndex)
StopTimerBasedOnWearExpireEvent();
// ACCESSORY_REFINE
StopAccessorySocketExpireEvent();
// END_OF_ACCESSORY_REFINE
m_pOwner->BuffOnAttr_RemoveBuffsFromItem(this);
m_pOwner->SetWear(GetCell() - INVENTORY_MAX_NUM, NULL);
// Original immun
// DWORD dwImmuneFlag = 0;
// for (int i = 0; i < WEAR_MAX_NUM; ++i)
// if (m_pOwner->GetWear(i))
// SET_BIT(dwImmuneFlag, m_pOwner->GetWear(i)->m_pProto->dwImmuneFlag);
// m_pOwner->SetImmuneFlag(dwImmuneFlag);
//P3NG3R immun bug fix
DWORD dwImmuneFlag = 0;
LPITEM item = NULL;
for (int i = 0; i < WEAR_MAX_NUM; ++i)
{
if ((item = m_pOwner->GetWear(i)))
{
if (item->GetImmuneFlag() != 0)
SET_BIT(dwImmuneFlag, item->GetImmuneFlag());
if (item->GetAttributeCount() > 0)
{
if (item->HasAttr(APPLY_IMMUNE_STUN))
SET_BIT(dwImmuneFlag, IMMUNE_STUN);
if (item->HasAttr(APPLY_IMMUNE_SLOW))
SET_BIT(dwImmuneFlag, IMMUNE_SLOW);
if (item->HasAttr(APPLY_IMMUNE_FALL))
SET_BIT(dwImmuneFlag, IMMUNE_FALL);
}
}
}
m_pOwner->SetImmuneFlag(dwImmuneFlag);
//P3NG3R immun fix end
m_pOwner->ComputeBattlePoints();
m_pOwner->UpdatePacket();
m_pOwner = NULL;
m_wCell = 0;
m_bEquipped = false;
return true;
}
long CItem::GetValue(DWORD idx)
{
assert(idx < ITEM_VALUES_MAX_NUM);
return GetProto()->alValues[idx];
}
void CItem::SetExchanging(bool bOn)
{
m_bExchanging = bOn;
}
void CItem::Save()
{
if (m_bSkipSave)
return;
ITEM_MANAGER::instance().DelayedSave(this);
}
bool CItem::CreateSocket(BYTE bSlot, BYTE bGold)
{
assert(bSlot < ITEM_SOCKET_MAX_NUM);
if (m_alSockets[bSlot] != 0)
{
sys_err("Item::CreateSocket : socket already exist %s %d", GetName(), bSlot);
return false;
}
if (bGold)
m_alSockets[bSlot] = 2;
else
m_alSockets[bSlot] = 1;
UpdatePacket();
Save();
return true;
}
void CItem::SetSockets(const int32_t* c_al)
{
thecore_memcpy(m_alSockets, c_al, sizeof(m_alSockets));
Save();
}
void CItem::SetSocket(int i, int32_t v, bool bLog)
{
assert(i < ITEM_SOCKET_MAX_NUM);
m_alSockets[i] = v;
UpdatePacket();
Save();
if (bLog)
LogManager::instance().ItemLog(i, v, 0, GetID(), "SET_SOCKET", "", "", GetOriginalVnum());
}
int CItem::GetGold()
{
if (IS_SET(GetFlag(), ITEM_FLAG_COUNT_PER_1GOLD))
{
if (GetProto()->dwGold == 0)
return GetCount();
else
return GetCount() / GetProto()->dwGold;
}
else
return GetProto()->dwGold;
}
int CItem::GetShopBuyPrice()
{
return GetProto()->dwShopBuyPrice;
}
bool CItem::IsOwnership(LPCHARACTER ch)
{
if (!m_pkOwnershipEvent)
return true;
return m_dwOwnershipPID == ch->GetPlayerID() ? true : false;
}
EVENTFUNC(ownership_event)
{
item_event_info* info = dynamic_cast<item_event_info*>( event->info );
if ( info == NULL )
{
sys_err( "ownership_event> <Factor> Null pointer" );
return 0;
}
LPITEM pkItem = info->item;
pkItem->SetOwnershipEvent(NULL);
TPacketGCItemOwnership p;
p.bHeader = HEADER_GC_ITEM_OWNERSHIP;
p.dwVID = pkItem->GetVID();
p.szName[0] = '\0';
pkItem->PacketAround(&p, sizeof(p));
return 0;
}
void CItem::SetOwnershipEvent(LPEVENT pkEvent)
{
m_pkOwnershipEvent = pkEvent;
}
void CItem::SetOwnership(LPCHARACTER ch, int iSec)
{
if (!ch)
{
if (m_pkOwnershipEvent)
{
event_cancel(&m_pkOwnershipEvent);
m_dwOwnershipPID = 0;
TPacketGCItemOwnership p;
p.bHeader = HEADER_GC_ITEM_OWNERSHIP;
p.dwVID = m_dwVID;
p.szName[0] = '\0';
PacketAround(&p, sizeof(p));
}
return;
}
if (m_pkOwnershipEvent)
return;
if (true == LC_IsEurope())
{
if (iSec <= 10)
iSec = 30;
}
m_dwOwnershipPID = ch->GetPlayerID();
item_event_info* info = AllocEventInfo<item_event_info>();
strlcpy(info->szOwnerName, ch->GetName(), sizeof(info->szOwnerName));
info->item = this;
SetOwnershipEvent(event_create(ownership_event, info, PASSES_PER_SEC(iSec)));
TPacketGCItemOwnership p;
p.bHeader = HEADER_GC_ITEM_OWNERSHIP;
p.dwVID = m_dwVID;
strlcpy(p.szName, ch->GetName(), sizeof(p.szName));
PacketAround(&p, sizeof(p));
}
int CItem::GetSocketCount()
{
for (int i = 0; i < ITEM_SOCKET_MAX_NUM; i++)
{
if (GetSocket(i) == 0)
return i;
}
return ITEM_SOCKET_MAX_NUM;
}
bool CItem::AddSocket()
{
int count = GetSocketCount();
if (count == ITEM_SOCKET_MAX_NUM)
return false;
m_alSockets[count] = 1;
return true;
}
void CItem::AlterToSocketItem(int iSocketCount)
{
if (iSocketCount >= ITEM_SOCKET_MAX_NUM)
{
sys_log(0, "Invalid Socket Count %d, set to maximum", ITEM_SOCKET_MAX_NUM);
iSocketCount = ITEM_SOCKET_MAX_NUM;
}
for (int i = 0; i < iSocketCount; ++i)
SetSocket(i, 1);
}
void CItem::AlterToMagicItem()
{
int idx = GetAttributeSetIndex();
if (idx < 0)
return;
// Appeariance Second Third
// Weapon 50 20 5
// Armor 30 10 2
// Acc 20 10 1
int iSecondPct;
int iThirdPct;
if (g_iUseLocale)
{
switch (GetType())
{
case ITEM_WEAPON:
iSecondPct = 20;
iThirdPct = 5;
break;
case ITEM_ARMOR:
case ITEM_COSTUME:
if (GetSubType() == ARMOR_BODY)
{
iSecondPct = 10;
iThirdPct = 2;
}
else
{
iSecondPct = 10;
iThirdPct = 1;
}
break;
default:
return;
}
}
else
{
switch (GetType())
{
case ITEM_WEAPON:
iSecondPct = 30;
iThirdPct = 15;
break;
case ITEM_ARMOR:
case ITEM_COSTUME:
if (GetSubType() == ARMOR_BODY)
{
iSecondPct = 20;
iThirdPct = 10;
}
else
{
iSecondPct = 10;
iThirdPct = 5;
}
break;
default:
return;
}
}
// 100% 확률로 좋은 속성 하나
PutAttribute(aiItemMagicAttributePercentHigh);
if (number(1, 100) <= iSecondPct)
PutAttribute(aiItemMagicAttributePercentLow);
if (number(1, 100) <= iThirdPct)
PutAttribute(aiItemMagicAttributePercentLow);
}
DWORD CItem::GetRefineFromVnum()
{
return ITEM_MANAGER::instance().GetRefineFromVnum(GetVnum());
}
int CItem::GetRefineLevel()
{
const char* name = GetBaseName();
char* p = const_cast<char*>(strrchr(name, '+'));
if (!p)
return 0;
int rtn = 0;
str_to_number(rtn, p+1);
const char* locale_name = GetName();
p = const_cast<char*>(strrchr(locale_name, '+'));
if (p)
{
int locale_rtn = 0;
str_to_number(locale_rtn, p+1);
if (locale_rtn != rtn)
{
sys_err("refine_level_based_on_NAME(%d) is not equal to refine_level_based_on_LOCALE_NAME(%d).", rtn, locale_rtn);
}
}
return rtn;
}
bool CItem::IsPolymorphItem()
{
return GetType() == ITEM_POLYMORPH;
}
EVENTFUNC(unique_expire_event)
{
item_event_info* info = dynamic_cast<item_event_info*>( event->info );
if ( info == NULL )
{
sys_err( "unique_expire_event> <Factor> Null pointer" );
return 0;
}
LPITEM pkItem = info->item;
if (pkItem->GetValue(2) == 0)
{
if (pkItem->GetSocket(ITEM_SOCKET_UNIQUE_REMAIN_TIME) <= 1)
{
sys_log(0, "UNIQUE_ITEM: expire %s %u", pkItem->GetName(), pkItem->GetID());
pkItem->SetUniqueExpireEvent(NULL);
ITEM_MANAGER::instance().RemoveItem(pkItem, "UNIQUE_EXPIRE");
return 0;
}
else
{
pkItem->SetSocket(ITEM_SOCKET_UNIQUE_REMAIN_TIME, pkItem->GetSocket(ITEM_SOCKET_UNIQUE_REMAIN_TIME) - 1);
return PASSES_PER_SEC(60);
}
}
else
{
time_t cur = get_global_time();
if (pkItem->GetSocket(ITEM_SOCKET_UNIQUE_REMAIN_TIME) <= cur)
{
pkItem->SetUniqueExpireEvent(NULL);
ITEM_MANAGER::instance().RemoveItem(pkItem, "UNIQUE_EXPIRE");
return 0;
}
else
{
// 게임 내에 시간제 아이템들이 빠릿빠릿하게 사라지지 않는 버그가 있어
// 수정
// by rtsummit
if (pkItem->GetSocket(ITEM_SOCKET_UNIQUE_REMAIN_TIME) - cur < 600)
return PASSES_PER_SEC(pkItem->GetSocket(ITEM_SOCKET_UNIQUE_REMAIN_TIME) - cur);
else
return PASSES_PER_SEC(600);
}
}
}
// 시간 후불제
// timer를 시작할 때에 시간 차감하는 것이 아니라,
// timer가 발화할 때에 timer가 동작한 시간 만큼 시간 차감을 한다.
EVENTFUNC(timer_based_on_wear_expire_event)
{
item_event_info* info = dynamic_cast<item_event_info*>( event->info );
if ( info == NULL )
{
sys_err( "expire_event <Factor> Null pointer" );
return 0;
}
LPITEM pkItem = info->item;
int remain_time = pkItem->GetSocket(ITEM_SOCKET_REMAIN_SEC) - processing_time/passes_per_sec;
if (remain_time <= 0)
{
sys_log(0, "ITEM EXPIRED : expired %s %u", pkItem->GetName(), pkItem->GetID());
pkItem->SetTimerBasedOnWearExpireEvent(NULL);
pkItem->SetSocket(ITEM_SOCKET_REMAIN_SEC, 0);
// 일단 timer based on wear 용혼석은 시간 다 되었다고 없애지 않는다.
if (pkItem->IsDragonSoul())
{
DSManager::instance().DeactivateDragonSoul(pkItem);
}
else
{
ITEM_MANAGER::instance().RemoveItem(pkItem, "TIMER_BASED_ON_WEAR_EXPIRE");
}
return 0;
}
pkItem->SetSocket(ITEM_SOCKET_REMAIN_SEC, remain_time);
return PASSES_PER_SEC (MIN (60, remain_time));
}
void CItem::SetUniqueExpireEvent(LPEVENT pkEvent)
{
m_pkUniqueExpireEvent = pkEvent;
}
void CItem::SetTimerBasedOnWearExpireEvent(LPEVENT pkEvent)
{
m_pkTimerBasedOnWearExpireEvent = pkEvent;
}
EVENTFUNC(real_time_expire_event)
{
const item_vid_event_info* info = reinterpret_cast<const item_vid_event_info*>(event->info);
if (NULL == info)
return 0;
const LPITEM item = ITEM_MANAGER::instance().FindByVID( info->item_vid );
if (NULL == item)
return 0;
const time_t current = get_global_time();
if (current > item->GetSocket(0))
{
LPCHARACTER owner = item->GetOwner();
//##########################################
if (owner && owner->GetDesc())
{
LPDESC d = owner->GetDesc();
if (d->IsPhase(PHASE_LOADING))
{
return PASSES_PER_SEC(1);
}
}
//##########################################
switch (item->GetVnum())
{
if(item->IsNewMountItem())
{
if (item->GetSocket(2) != 0)
item->ClearMountAttributeAndAffect();
}
break;
}
ITEM_MANAGER::instance().RemoveItem(item, "REAL_TIME_EXPIRE");
return 0;
}
return PASSES_PER_SEC(1);
}
void CItem::StartRealTimeExpireEvent()
{
if (m_pkRealTimeExpireEvent)
return;
for (int i=0 ; i < ITEM_LIMIT_MAX_NUM ; i++)
{
if (LIMIT_REAL_TIME == GetProto()->aLimits[i].bType || LIMIT_REAL_TIME_START_FIRST_USE == GetProto()->aLimits[i].bType)
{
item_vid_event_info* info = AllocEventInfo<item_vid_event_info>();
info->item_vid = GetVID();
m_pkRealTimeExpireEvent = event_create( real_time_expire_event, info, PASSES_PER_SEC(1));
sys_log(0, "REAL_TIME_EXPIRE: StartRealTimeExpireEvent");
return;
}
}
}
bool CItem::IsRealTimeItem()
{
if(!GetProto())
return false;
for (int i=0 ; i < ITEM_LIMIT_MAX_NUM ; i++)
{
if (LIMIT_REAL_TIME == GetProto()->aLimits[i].bType)
return true;
}
return false;
}
void CItem::StartUniqueExpireEvent()
{
if (GetType() != ITEM_UNIQUE)
return;
if (m_pkUniqueExpireEvent)
return;
//기간제 아이템일 경우 시간제 아이템은 동작하지 않는다
if (IsRealTimeItem())
return;
// HARD CODING
if (GetVnum() == UNIQUE_ITEM_HIDE_ALIGNMENT_TITLE)
m_pOwner->ShowAlignment(false);
int iSec = GetSocket(ITEM_SOCKET_UNIQUE_SAVE_TIME);
if (iSec == 0)
iSec = 60;
else
iSec = MIN(iSec, 60);
SetSocket(ITEM_SOCKET_UNIQUE_SAVE_TIME, 0);
item_event_info* info = AllocEventInfo<item_event_info>();
info->item = this;
SetUniqueExpireEvent(event_create(unique_expire_event, info, PASSES_PER_SEC(iSec)));
}
// 시간 후불제
// timer_based_on_wear_expire_event 설명 참조
void CItem::StartTimerBasedOnWearExpireEvent()
{
if (m_pkTimerBasedOnWearExpireEvent)
return;
//기간제 아이템일 경우 시간제 아이템은 동작하지 않는다
if (IsRealTimeItem())
return;
if (-1 == GetProto()->cLimitTimerBasedOnWearIndex)
return;
int iSec = GetSocket(0);
// 남은 시간을 분단위로 끊기 위해...
if (0 != iSec)
{
iSec %= 60;
if (0 == iSec)
iSec = 60;
}
item_event_info* info = AllocEventInfo<item_event_info>();
info->item = this;
SetTimerBasedOnWearExpireEvent(event_create(timer_based_on_wear_expire_event, info, PASSES_PER_SEC(iSec)));
}
void CItem::StopUniqueExpireEvent()
{
if (!m_pkUniqueExpireEvent)
return;
if (GetValue(2) != 0) // 게임시간제 이외의 아이템은 UniqueExpireEvent를 중단할 수 없다.
return;
// HARD CODING
if (GetVnum() == UNIQUE_ITEM_HIDE_ALIGNMENT_TITLE)
m_pOwner->ShowAlignment(true);
SetSocket(ITEM_SOCKET_UNIQUE_SAVE_TIME, event_time(m_pkUniqueExpireEvent) / passes_per_sec);
event_cancel(&m_pkUniqueExpireEvent);
ITEM_MANAGER::instance().SaveSingleItem(this);
}
void CItem::StopTimerBasedOnWearExpireEvent()
{
if (!m_pkTimerBasedOnWearExpireEvent)
return;
int remain_time = GetSocket(ITEM_SOCKET_REMAIN_SEC) - event_processing_time(m_pkTimerBasedOnWearExpireEvent) / passes_per_sec;
SetSocket(ITEM_SOCKET_REMAIN_SEC, remain_time);
event_cancel(&m_pkTimerBasedOnWearExpireEvent);
ITEM_MANAGER::instance().SaveSingleItem(this);
}
void CItem::ApplyAddon(int iAddonType)
{
CItemAddonManager::instance().ApplyAddonTo(iAddonType, this);
}
int CItem::GetSpecialGroup() const
{
return ITEM_MANAGER::instance().GetSpecialGroupFromItem(GetVnum());
}
//
// 악세서리 소켓 처리.
//
bool CItem::IsAccessoryForSocket()
{
return (m_pProto->bType == ITEM_ARMOR && (m_pProto->bSubType == ARMOR_WRIST || m_pProto->bSubType == ARMOR_NECK || m_pProto->bSubType == ARMOR_EAR)) ||
(m_pProto->bType == ITEM_BELT); // 2013년 2월 새로 추가된 '벨트' 아이템의 경우 기획팀에서 악세서리 소켓 시스템을 그대로 이용하자고 함.
}
void CItem::SetAccessorySocketGrade(int iGrade)
{
SetSocket(0, MINMAX(0, iGrade, GetAccessorySocketMaxGrade()));
int iDownTime = aiAccessorySocketDegradeTime[GetAccessorySocketGrade()];
//if (test_server)
// iDownTime /= 60;
SetAccessorySocketDownGradeTime(iDownTime);
}
void CItem::SetAccessorySocketMaxGrade(int iMaxGrade)
{
SetSocket(1, MINMAX(0, iMaxGrade, ITEM_ACCESSORY_SOCKET_MAX_NUM));
}
void CItem::SetAccessorySocketDownGradeTime(DWORD time)
{
SetSocket(2, time);
if (test_server && GetOwner())
GetOwner()->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("%s에서 소켓 빠질때까지 남은 시간 %d"), GetName(), time);
}
EVENTFUNC(accessory_socket_expire_event)
{
item_vid_event_info* info = dynamic_cast<item_vid_event_info*>( event->info );
if ( info == NULL )
{
sys_err( "accessory_socket_expire_event> <Factor> Null pointer" );
return 0;
}
LPITEM item = ITEM_MANAGER::instance().FindByVID(info->item_vid);
if (item->GetAccessorySocketDownGradeTime() <= 1)
{
degrade:
item->SetAccessorySocketExpireEvent(NULL);
item->AccessorySocketDegrade();
return 0;
}
else
{
int iTime = item->GetAccessorySocketDownGradeTime() - 60;
if (iTime <= 1)
goto degrade;
item->SetAccessorySocketDownGradeTime(iTime);
if (iTime > 60)
return PASSES_PER_SEC(60);
else
return PASSES_PER_SEC(iTime);
}
}
void CItem::StartAccessorySocketExpireEvent()
{
if (!IsAccessoryForSocket())
return;
if (m_pkAccessorySocketExpireEvent)
return;
if (GetAccessorySocketMaxGrade() == 0)
return;
if (GetAccessorySocketGrade() == 0)
return;
int iSec = GetAccessorySocketDownGradeTime();
SetAccessorySocketExpireEvent(NULL);
if (iSec <= 1)
iSec = 5;
else
iSec = MIN(iSec, 60);
item_vid_event_info* info = AllocEventInfo<item_vid_event_info>();
info->item_vid = GetVID();
SetAccessorySocketExpireEvent(event_create(accessory_socket_expire_event, info, PASSES_PER_SEC(iSec)));
}
void CItem::StopAccessorySocketExpireEvent()
{
if (!m_pkAccessorySocketExpireEvent)
return;
if (!IsAccessoryForSocket())
return;
int new_time = GetAccessorySocketDownGradeTime() - (60 - event_time(m_pkAccessorySocketExpireEvent) / passes_per_sec);
event_cancel(&m_pkAccessorySocketExpireEvent);
if (new_time <= 1)
{
AccessorySocketDegrade();
}
else
{
SetAccessorySocketDownGradeTime(new_time);
}
}
bool CItem::IsRideItem()
{
if (ITEM_UNIQUE == GetType() && UNIQUE_SPECIAL_RIDE == GetSubType())
return true;
if (ITEM_UNIQUE == GetType() && UNIQUE_SPECIAL_MOUNT_RIDE == GetSubType())
return true;
return false;
}
bool CItem::IsRamadanRing()
{
if (GetVnum() == UNIQUE_ITEM_RAMADAN_RING)
return true;
return false;
}
void CItem::ClearMountAttributeAndAffect()
{
LPCHARACTER ch = GetOwner();
ch->RemoveAffect(AFFECT_MOUNT);
ch->RemoveAffect(AFFECT_MOUNT_BONUS);
ch->MountVnum(0);
ch->PointChange(POINT_ST, 0);
ch->PointChange(POINT_DX, 0);
ch->PointChange(POINT_HT, 0);
ch->PointChange(POINT_IQ, 0);
}
// fixme
// 이거 지금은 안쓴데... 근데 혹시나 싶어서 남겨둠.
// by rtsummit
bool CItem::IsNewMountItem()
{
switch(GetVnum())
{
case 76000: case 76001: case 76002: case 76003:
case 76004: case 76005: case 76006: case 76007:
case 76008: case 76009: case 76010: case 76011:
case 76012: case 76013: case 76014:
return true;
}
return false;
}
void CItem::SetAccessorySocketExpireEvent(LPEVENT pkEvent)
{
m_pkAccessorySocketExpireEvent = pkEvent;
}
void CItem::AccessorySocketDegrade()
{
if (GetAccessorySocketGrade() > 0)
{
LPCHARACTER ch = GetOwner();
if (ch)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("%s에 박혀있던 보석이 사라집니다."), GetName());
}
ModifyPoints(false);
SetAccessorySocketGrade(GetAccessorySocketGrade()-1);
ModifyPoints(true);
int iDownTime = aiAccessorySocketDegradeTime[GetAccessorySocketGrade()];
if (test_server)
iDownTime /= 60;
SetAccessorySocketDownGradeTime(iDownTime);
if (iDownTime)
StartAccessorySocketExpireEvent();
}
}
// ring에 item을 박을 수 있는지 여부를 체크해서 리턴
static const bool CanPutIntoRing(LPITEM ring, LPITEM item)
{
const DWORD vnum = item->GetVnum();
return false;
}
bool CItem::CanPutInto(LPITEM item)
{
if (item->GetType() == ITEM_BELT)
return this->GetSubType() == USE_PUT_INTO_BELT_SOCKET;
else if(item->GetType() == ITEM_RING)
return CanPutIntoRing(item, this);
else if (item->GetType() != ITEM_ARMOR)
return false;
DWORD vnum = item->GetVnum();
struct JewelAccessoryInfo
{
DWORD jewel;
DWORD wrist;
DWORD neck;
DWORD ear;
};
const static JewelAccessoryInfo infos[] = {
{ 50634, 14420, 16220, 17220 },
{ 50635, 14500, 16500, 17500 },
{ 50636, 14520, 16520, 17520 },
{ 50637, 14540, 16540, 17540 },
{ 50638, 14560, 16560, 17560 },
};
DWORD item_type = (item->GetVnum() / 10) * 10;
for (int i = 0; i < sizeof(infos) / sizeof(infos[0]); i++)
{
const JewelAccessoryInfo& info = infos[i];
switch(item->GetSubType())
{
case ARMOR_WRIST:
if (info.wrist == item_type)
{
if (info.jewel == GetVnum())
{
return true;
}
else
{
return false;
}
}
break;
case ARMOR_NECK:
if (info.neck == item_type)
{
if (info.jewel == GetVnum())
{
return true;
}
else
{
return false;
}
}
break;
case ARMOR_EAR:
if (info.ear == item_type)
{
if (info.jewel == GetVnum())
{
return true;
}
else
{
return false;
}
}
break;
}
}
if (item->GetSubType() == ARMOR_WRIST)
vnum -= 14000;
else if (item->GetSubType() == ARMOR_NECK)
vnum -= 16000;
else if (item->GetSubType() == ARMOR_EAR)
vnum -= 17000;
else
return false;
DWORD type = vnum / 20;
if (type < 0 || type > 11)
{
type = (vnum - 170) / 20;
if (50623 + type != GetVnum())
return false;
else
return true;
}
else if (item->GetVnum() >= 16210 && item->GetVnum() <= 16219)
{
if (50625 != GetVnum())
return false;
else
return true;
}
else if (item->GetVnum() >= 16230 && item->GetVnum() <= 16239)
{
if (50626 != GetVnum())
return false;
else
return true;
}
return 50623 + type == GetVnum();
}
bool CItem::CheckItemUseLevel(int nLevel)
{
for (int i = 0; i < ITEM_LIMIT_MAX_NUM; ++i)
{
if (this->m_pProto->aLimits[i].bType == LIMIT_LEVEL)
{
if (this->m_pProto->aLimits[i].lValue > nLevel) return false;
else return true;
}
}
return true;
}
long CItem::FindApplyValue(BYTE bApplyType)
{
if (m_pProto == NULL)
return 0;
for (int i = 0; i < ITEM_APPLY_MAX_NUM; ++i)
{
if (m_pProto->aApplies[i].bType == bApplyType)
return m_pProto->aApplies[i].lValue;
}
return 0;
}
void CItem::CopySocketTo(LPITEM pItem)
{
for (int i = 0; i < ITEM_SOCKET_MAX_NUM; ++i)
{
pItem->m_alSockets[i] = m_alSockets[i];
}
}
int CItem::GetAccessorySocketGrade()
{
return MINMAX(0, GetSocket(0), GetAccessorySocketMaxGrade());
}
int CItem::GetAccessorySocketMaxGrade()
{
return MINMAX(0, GetSocket(1), ITEM_ACCESSORY_SOCKET_MAX_NUM);
}
int CItem::GetAccessorySocketDownGradeTime()
{
return MINMAX(0, GetSocket(2), aiAccessorySocketDegradeTime[GetAccessorySocketGrade()]);
}
void CItem::AttrLog()
{
const char * pszIP = NULL;
if (GetOwner() && GetOwner()->GetDesc())
pszIP = GetOwner()->GetDesc()->GetHostName();
for (int i = 0; i < ITEM_SOCKET_MAX_NUM; ++i)
{
if (m_alSockets[i])
{
LogManager::instance().ItemLog(i, m_alSockets[i], 0, GetID(), "INFO_SOCKET", "", pszIP ? pszIP : "", GetOriginalVnum());
}
}
for (int i = 0; i<ITEM_ATTRIBUTE_MAX_NUM; ++i)
{
int type = m_aAttr[i].bType;
int value = m_aAttr[i].sValue;
if (type)
LogManager::instance().ItemLog(i, type, value, GetID(), "INFO_ATTR", "", pszIP ? pszIP : "", GetOriginalVnum());
}
}
int CItem::GetLevelLimit()
{
for (int i = 0; i < ITEM_LIMIT_MAX_NUM; ++i)
{
if (this->m_pProto->aLimits[i].bType == LIMIT_LEVEL)
{
return this->m_pProto->aLimits[i].lValue;
}
}
return 0;
}
bool CItem::OnAfterCreatedItem()
{
// 아이템을 한 번이라도 사용했다면, 그 이후엔 사용 중이지 않아도 시간이 차감되는 방식
if (-1 != this->GetProto()->cLimitRealTimeFirstUseIndex)
{
// Socket1에 아이템의 사용 횟수가 기록되어 있으니, 한 번이라도 사용한 아이템은 타이머를 시작한다.
if (0 != GetSocket(1))
{
StartRealTimeExpireEvent();
}
}
return true;
}
bool CItem::IsDragonSoul()
{
return GetType() == ITEM_DS;
}
int CItem::GiveMoreTime_Per(float fPercent)
{
if (IsDragonSoul())
{
DWORD duration = DSManager::instance().GetDuration(this);
int remain_sec = GetSocket(ITEM_SOCKET_REMAIN_SEC);
int given_time = fPercent * duration / 100;
if (remain_sec == duration)
return false;
if ((given_time + remain_sec) >= duration)
{
SetSocket(ITEM_SOCKET_REMAIN_SEC, duration);
return duration - remain_sec;
}
else
{
SetSocket(ITEM_SOCKET_REMAIN_SEC, given_time + remain_sec);
return given_time;
}
}
// 우선 용혼석에 관해서만 하도록 한다.
else
return 0;
}
int CItem::GiveMoreTime_Fix(DWORD dwTime)
{
if (IsDragonSoul())
{
DWORD duration = DSManager::instance().GetDuration(this);
int remain_sec = GetSocket(ITEM_SOCKET_REMAIN_SEC);
if (remain_sec == duration)
return false;
if ((dwTime + remain_sec) >= duration)
{
SetSocket(ITEM_SOCKET_REMAIN_SEC, duration);
return duration - remain_sec;
}
else
{
SetSocket(ITEM_SOCKET_REMAIN_SEC, dwTime + remain_sec);
return dwTime;
}
}
// 우선 용혼석에 관해서만 하도록 한다.
else
return 0;
}
int CItem::GetDuration()
{
if(!GetProto())
return -1;
for (int i=0 ; i < ITEM_LIMIT_MAX_NUM ; i++)
{
if (LIMIT_REAL_TIME == GetProto()->aLimits[i].bType)
return GetProto()->aLimits[i].lValue;
}
if (-1 != GetProto()->cLimitTimerBasedOnWearIndex)
return GetProto()->aLimits[GetProto()->cLimitTimerBasedOnWearIndex].lValue;
return -1;
}
bool CItem::IsSameSpecialGroup(const LPITEM item) const
{
// 서로 VNUM이 같다면 같은 그룹인 것으로 간주
if (this->GetVnum() == item->GetVnum())
return true;
if (GetSpecialGroup() && (item->GetSpecialGroup() == GetSpecialGroup()))
return true;
return false;
}