diff --git a/src/game/char.cpp b/src/game/char.cpp index dd1982a..5ebf55d 100644 --- a/src/game/char.cpp +++ b/src/game/char.cpp @@ -138,6 +138,8 @@ void CHARACTER::Initialize() CountDrops = 0; LastDropTime = 0; + m_dwPickupWindowStart = 0; + m_iPickupWindowCount = 0; m_iLastPMPulse = 0; m_iPMCounter = 0; diff --git a/src/game/char.h b/src/game/char.h index b585f4d..a87b161 100644 --- a/src/game/char.h +++ b/src/game/char.h @@ -2049,6 +2049,8 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider public: int LastDropTime; int CountDrops; + DWORD m_dwPickupWindowStart; + int m_iPickupWindowCount; void ClearPMCounter(void) { m_iPMCounter = 0; } void IncreasePMCounter(void) { m_iPMCounter++; } void SetLastPMPulse(void); diff --git a/src/game/char_item.cpp b/src/game/char_item.cpp index b4f7fe6..dbc592c 100644 --- a/src/game/char_item.cpp +++ b/src/game/char_item.cpp @@ -5841,6 +5841,29 @@ void CHARACTER::GiveGold(int iAmount) bool CHARACTER::PickupItem(DWORD dwVID) { + if (!CanHandleItem()) + return false; + + const DWORD dwNow = get_dword_time(); + + if (m_dwPickupWindowStart == 0 || dwNow - m_dwPickupWindowStart >= 1000) + { + m_dwPickupWindowStart = dwNow; + m_iPickupWindowCount = 0; + } + + ++m_iPickupWindowCount; + + if (m_iPickupWindowCount > 25) + { + char szDetail[96]; + snprintf(szDetail, sizeof(szDetail), "count=%d window_ms=%u", m_iPickupWindowCount, dwNow - m_dwPickupWindowStart); + RecordAntiCheatViolation("PICKUP_RATE", MIN(8, 1 + (m_iPickupWindowCount - 25) / 5), szDetail, m_iPickupWindowCount > 45); + + if (m_iPickupWindowCount > 45) + return false; + } + LPITEM item = ITEM_MANAGER::instance().FindByVID(dwVID); if (IsObserverMode()) @@ -5849,104 +5872,114 @@ bool CHARACTER::PickupItem(DWORD dwVID) if (!item || !item->GetSectree()) return false; - if (item->DistanceValid(this)) + const int iDist = DISTANCE_APPROX(item->GetX() - GetX(), item->GetY() - GetY()); + + if (!item->DistanceValid(this)) { - if (item->IsOwnership(this)) + char szDetail[128]; + snprintf(szDetail, sizeof(szDetail), "item=%u dist=%d owner=%u", item->GetVID(), iDist, item->GetOwnershipPID()); + RecordAntiCheatViolation("PICKUP_DISTANCE", iDist > 900 ? 12 : 4, szDetail, iDist > 900); + return false; + } + + if (item->IsOwnership(this)) + { + // 만약 주으려 하는 아이템이 엘크라면 + if (item->GetType() == ITEM_ELK) { - // 만약 주으려 하는 아이템이 엘크라면 - if (item->GetType() == ITEM_ELK) - { - GiveGold(item->GetCount()); - item->RemoveFromGround(); + GiveGold(item->GetCount()); + item->RemoveFromGround(); - M2_DESTROY_ITEM(item); + M2_DESTROY_ITEM(item); - Save(); - } - // 평범한 아이템이라면 - else + Save(); + } + // 평범한 아이템이라면 + else + { + if (item->IsStackable() && !IS_SET(item->GetAntiFlag(), ITEM_ANTIFLAG_STACK)) { - if (item->IsStackable() && !IS_SET(item->GetAntiFlag(), ITEM_ANTIFLAG_STACK)) + BYTE bCount = item->GetCount(); + + for (int i = 0; i < INVENTORY_MAX_NUM; ++i) { - BYTE bCount = item->GetCount(); + LPITEM item2 = GetInventoryItem(i); - for (int i = 0; i < INVENTORY_MAX_NUM; ++i) + if (!item2) + continue; + + if (item2->GetVnum() == item->GetVnum()) { - LPITEM item2 = GetInventoryItem(i); + int j; - if (!item2) + for (j = 0; j < ITEM_SOCKET_MAX_NUM; ++j) + if (item2->GetSocket(j) != item->GetSocket(j)) + break; + + if (j != ITEM_SOCKET_MAX_NUM) continue; - if (item2->GetVnum() == item->GetVnum()) + BYTE bCount2 = MIN(200 - item2->GetCount(), bCount); + bCount -= bCount2; + + item2->SetCount(item2->GetCount() + bCount2); + + if (bCount == 0) { - int j; - - for (j = 0; j < ITEM_SOCKET_MAX_NUM; ++j) - if (item2->GetSocket(j) != item->GetSocket(j)) - break; - - if (j != ITEM_SOCKET_MAX_NUM) - continue; - - BYTE bCount2 = MIN(200 - item2->GetCount(), bCount); - bCount -= bCount2; - - item2->SetCount(item2->GetCount() + bCount2); - - if (bCount == 0) - { - ItemGetPacket(item2->GetVnum(), item2->GetCount()); - M2_DESTROY_ITEM(item); - if (item2->GetType() == ITEM_QUEST) - quest::CQuestManager::instance().PickupItem (GetPlayerID(), item2); - return true; - } + ItemGetPacket(item2->GetVnum(), item2->GetCount()); + M2_DESTROY_ITEM(item); + if (item2->GetType() == ITEM_QUEST) + quest::CQuestManager::instance().PickupItem (GetPlayerID(), item2); + return true; } } - - item->SetCount(bCount); } - int iEmptyCell; - if (item->IsDragonSoul()) - { - if ((iEmptyCell = GetEmptyDragonSoulInventory(item)) == -1) - { - sys_log(0, "No empty ds inventory pid %u size %ud itemid %u", GetPlayerID(), item->GetSize(), item->GetID()); - ChatPacket(CHAT_TYPE_INFO, LC_TEXT("소지하고 있는 아이템이 너무 많습니다.")); - return false; - } - } - else - { - if ((iEmptyCell = GetEmptyInventory(item->GetSize())) == -1) - { - sys_log(0, "No empty inventory pid %u size %ud itemid %u", GetPlayerID(), item->GetSize(), item->GetID()); - ChatPacket(CHAT_TYPE_INFO, LC_TEXT("소지하고 있는 아이템이 너무 많습니다.")); - return false; - } - } - - item->RemoveFromGround(); - - if (item->IsDragonSoul()) - item->AddToCharacter(this, TItemPos(DRAGON_SOUL_INVENTORY, iEmptyCell)); - else - item->AddToCharacter(this, TItemPos(INVENTORY, iEmptyCell)); - - char szHint[32+1]; - snprintf(szHint, sizeof(szHint), "%s %u %u", item->GetName(), item->GetCount(), item->GetOriginalVnum()); - LogManager::instance().ItemLog(this, item, "GET", szHint); - ItemGetPacket(item->GetVnum(), item->GetCount()); - - if (item->GetType() == ITEM_QUEST) - quest::CQuestManager::instance().PickupItem (GetPlayerID(), item); + item->SetCount(bCount); } - //Motion(MOTION_PICKUP); - return true; + int iEmptyCell; + if (item->IsDragonSoul()) + { + if ((iEmptyCell = GetEmptyDragonSoulInventory(item)) == -1) + { + sys_log(0, "No empty ds inventory pid %u size %ud itemid %u", GetPlayerID(), item->GetSize(), item->GetID()); + ChatPacket(CHAT_TYPE_INFO, LC_TEXT("소지하고 있는 아이템이 너무 많습니다.")); + return false; + } + } + else + { + if ((iEmptyCell = GetEmptyInventory(item->GetSize())) == -1) + { + sys_log(0, "No empty inventory pid %u size %ud itemid %u", GetPlayerID(), item->GetSize(), item->GetID()); + ChatPacket(CHAT_TYPE_INFO, LC_TEXT("소지하고 있는 아이템이 너무 많습니다.")); + return false; + } + } + + item->RemoveFromGround(); + + if (item->IsDragonSoul()) + item->AddToCharacter(this, TItemPos(DRAGON_SOUL_INVENTORY, iEmptyCell)); + else + item->AddToCharacter(this, TItemPos(INVENTORY, iEmptyCell)); + + char szHint[32+1]; + snprintf(szHint, sizeof(szHint), "%s %u %u", item->GetName(), item->GetCount(), item->GetOriginalVnum()); + LogManager::instance().ItemLog(this, item, "GET", szHint); + ItemGetPacket(item->GetVnum(), item->GetCount()); + + if (item->GetType() == ITEM_QUEST) + quest::CQuestManager::instance().PickupItem (GetPlayerID(), item); } - else if (!IS_SET(item->GetAntiFlag(), ITEM_ANTIFLAG_GIVE | ITEM_ANTIFLAG_DROP) && GetParty()) + + //Motion(MOTION_PICKUP); + return true; + } + else if (item->HasOwnership()) + { + if (!IS_SET(item->GetAntiFlag(), ITEM_ANTIFLAG_GIVE | ITEM_ANTIFLAG_DROP) && GetParty()) { // 다른 파티원 소유권 아이템을 주으려고 한다면 NPartyPickupDistribute::FFindOwnership funcFindOwnership(item); @@ -5955,11 +5988,19 @@ bool CHARACTER::PickupItem(DWORD dwVID) LPCHARACTER owner = funcFindOwnership.owner; + if (!owner) + { + char szDetail[160]; + snprintf(szDetail, sizeof(szDetail), "item=%u owner_pid=%u party=%d", item->GetVID(), item->GetOwnershipPID(), GetParty() ? 1 : 0); + RecordAntiCheatViolation("PICKUP_OWNERSHIP", 6, szDetail, true); + return false; + } + int iEmptyCell; if (item->IsDragonSoul()) { - if (!(owner && (iEmptyCell = owner->GetEmptyDragonSoulInventory(item)) != -1)) + if ((iEmptyCell = owner->GetEmptyDragonSoulInventory(item)) == -1) { owner = this; @@ -5972,7 +6013,7 @@ bool CHARACTER::PickupItem(DWORD dwVID) } else { - if (!(owner && (iEmptyCell = owner->GetEmptyInventory(item->GetSize())) != -1)) + if ((iEmptyCell = owner->GetEmptyInventory(item->GetSize())) == -1) { owner = this; @@ -6008,6 +6049,10 @@ bool CHARACTER::PickupItem(DWORD dwVID) return true; } + + char szDetail[160]; + snprintf(szDetail, sizeof(szDetail), "item=%u owner_pid=%u party=%d", item->GetVID(), item->GetOwnershipPID(), GetParty() ? 1 : 0); + RecordAntiCheatViolation("PICKUP_OWNERSHIP", 4, szDetail); } return false; diff --git a/src/game/item.h b/src/game/item.h index a085119..db90f04 100644 --- a/src/game/item.h +++ b/src/game/item.h @@ -139,6 +139,8 @@ class CItem : public CEntity bool GetSkipSave() { return m_bSkipSave; } bool IsOwnership(LPCHARACTER ch); + bool HasOwnership() const { return m_pkOwnershipEvent != NULL && m_dwOwnershipPID != 0; } + DWORD GetOwnershipPID() const { return m_dwOwnershipPID; } void SetOwnership(LPCHARACTER ch, int iSec = 10); void SetOwnershipEvent(LPEVENT pkEvent);