Kutu yok openko'da asset ve biraz işçilik istiyor. Harita konusunda ben altyapının güncellenmesi gerektiğini biliyorum ntf, dxt eksik formatlar vs. Fakat ben breth piana haritasını hiç denemedim gidip tak çalıştır çalıştırırsınız demeyin bana çalışmaz demiştin diye eski versiyon diye uyumlu çıkma ihtimali de mevcut.
Harita için de breth ile myko tanıtımı yapan bir sw'nin clientinden alın 2 dklik iş

beceremezseniz yardımcı olurum..
İlk olarak istediğiniz kutu güncellemesi; (rehberi yazmakla uğraşamayacağım için kendi sourcemden yapay zeka aracına rehber hazırlattım)
Mob ölüm kutusu (drop box) — eksiksiz rehber
Bu sistem Knight Online’ın orijinal protokolünde zaten var. İstemci WIZ_ITEM_DROP (0x23), WIZ_BUNDLE_OPEN_REQ (0x24), WIZ_ITEM_GET (0x26) paketlerini ve UIDroppedItemDlg penceresini hazır barındırır. Yani istemciye dokunmadan, sadece sunucu doğru paketi yollarsa kutu çalışır. Çoğu kişinin takıldığı yer burası: istemcide arıyor, oysa eksik AIServer/GameServer hattında.
1) Sistem nasıl akıyor
[AIServer] mob ölür → CNpc::GiveNpcHaveItem()
│ AG_NPC_GIVE_ITEM (opcode 8): killerID, npcID, zone, rx, rz, x, z, y, count, (item,count)*
▼
[GameServer] CAISocket::RecvNpcGiveItem()
│ _LOOT_BUNDLE oluştur → bölgeye ekle, nBundleID = map->m_wBundle++
│ kesene (partideyse partiye) WIZ_ITEM_DROP(0x23): npcID, bundleID
▼
[Client] MsgRecv_ItemBundleDrop()
│ corpse->m_iDroppedItemID = bundleID → CPlayerNPC::Tick() kutuyu çizer
│ oyuncu kutuya tıklar → WIZ_BUNDLE_OPEN_REQ(0x24) + bundleID
▼
[GameServer] CUser::BundleOpenReq() → WIZ_BUNDLE_OPEN_REQ geri: 6 slotluk (itemID,count)*
▼
[Client] MsgRecv_ItemBundleOpen() → UIDroppedItemDlg listesi
│ iteme tıkla → WIZ_ITEM_GET(0x26) + bundleID + itemID
▼
[GameServer] CUser::ItemGet() → envanter/altın ver, bundle'dan düş
son item ise → bölgeye WIZ_ITEM_DROP(npcID, 0) = "kutu gitti, fade ol"
2) Paket sözleşmesi (birebir)
| Yön | Opcode | İçerik |
|---|
| AI → GS | AG_NPC_GIVE_ITEM = 8 | int16 killerID, int16 npcID, uint8 zone, int16 rx, int16 rz, float x, float z, float y, uint8 count, (int32 itemID, int16 count) * n |
| GS → Client | WIZ_ITEM_DROP = 0x23 | int16 npcID, uint32 bundleID |
| Client → GS | WIZ_BUNDLE_OPEN_REQ = 0x24 | uint32 bundleID |
| GS → Client | WIZ_BUNDLE_OPEN_REQ = 0x24 | (uint32 itemID, int16 count) * 6 — eksik slotlar (0,0) ile doldurulmak ZORUNDA |
| Client → GS | WIZ_ITEM_GET = 0x26 | uint32 bundleID, uint32 itemID |
| GS → Client | WIZ_ITEM_GET = 0x26 | uint8 result + result’a göre ek alanlar |
WIZ_ITEM_GET sonuç kodları (istemci bunları bekliyor): 0=LootError, 1=LootSolo, 2=PartyCoinDistribution, 3=PartyNotification, 4=PartyItemGivenAway, 5=PartyItemGivenToUs, 6=NoRoom
3) Sunucu tarafı — yapılacaklar
3a) Veri yapısı (GameDefine.h)
#define LOOT_ITEMS 6
struct _LOOT_ITEM {
uint32_t nItemID;
uint16_t sCount;
};
struct _LOOT_BUNDLE {
uint32_t nBundleID;
int16_t sNpcID = 0; // kutunun sahibi ceset; kutu gidince istemci cesedi fade eder
std::vector<_LOOT_ITEM> Items;
float x, z, y;
time_t tDropTime;
};
constexpr time_t TIME_LOOT_BUNDLE_REMAIN = 15; // loot'suz kutu ömrü (sn)
3b) Haritaya kutu ekleme (Map.h/.cpp)
Her harita kendi artan sayacını tutar; ID 1’den başlar (0 = “kutu yok” sinyali, sakın kullanma):
uint32_t m_wBundle = 1; // C3DMap içinde
bool C3DMap::RegionItemAdd(uint16_t rx, uint16_t rz, _LOOT_BUNDLE* pBundle)
{
if (pBundle == nullptr) return false;
Guard lock(m_lock);
CRegion* pRegion = GetRegion(rx, rz);
if (pRegion == nullptr) return false;
pBundle->nBundleID = m_wBundle++;
pRegion->m_RegionItemArray.PutData(pBundle->nBundleID, pBundle);
if (m_wBundle > ZONEITEM_MAX) m_wBundle = 1;
return true;
}
RegionItemRemove: bundle’dan item’i siler, boş kalınca bundle’ı bölgeden siler. Silme işlemi m_RegionItemArray.m_lock altında yapılmalı — yoksa çok thread’li erişimde heap corruption (0xC0000374) yersin.
3c) AIServer — mob ölünce listeyi yolla (Npc.cpp)
if (bSendDeathPacket) {
SendDeathAnimation(pKiller);
SendExpToUserList();
if (isShowBox()) // anıt / guard tower / scarecrow / hapishane vb. HARİÇ
GiveNpcHaveItem();
}
GiveNpcHaveItem() içinde kesen oyuncunun ID’si (m_sMaxDamageUserid) ve mobun bölge koordinatı (GetRegionX/Z) pakete konur. K_MONSTER_ITEM tablosundan 5 slota drop şansı uygulanır; altın TYPE_MONEY_SID ile gider.

Burada iki klasik tuzak var:
- K_MONSTER_ITEM slot0/slot1 iki ayrı formatta olabiliyor: biri MAKE_ITEM_GROUP indeksi, diğeri doğrudan item kodu. Grup bulunamazsa kodu olduğu gibi kullan (fallback yoksa o slot sessizce düşer, drop gelmez).
- Çalışan AIServer.exe eski derlenmişse drop hiç üretilmez. Drop kodu değiştiyse AIServer’ı yeniden derleyip yeniden başlat.
3d) GameServer — bundle oluştur (AISocket.cpp)
void CAISocket::RecvNpcGiveItem(Packet& pkt)
{
Packet result(WIZ_ITEM_DROP);
int16_t sUid, sNid, regionx, regionz;
float fX, fZ, fY;
uint8_t byCount, bZone;
int nItemNumber[NPC_HAVE_ITEM_LIST];
int16_t sCount[NPC_HAVE_ITEM_LIST];
pkt >> sUid >> sNid >> bZone >> regionx >> regionz >> fX >> fZ >> fY >> byCount;
for (int i = 0; i < byCount; i++) pkt >> nItemNumber
>> sCount;
C3DMap* pMap = g_pMain->GetZoneByID(bZone);
if (pMap == nullptr || sUid < 0 || sUid >= MAX_USER) return;
auto* pBundle = new _LOOT_BUNDLE;
pBundle->sNpcID = sNid; // ceset eşleşmesi BUNUNLA olur
pBundle->tDropTime = UNIXTIME;
pBundle->x = fX; pBundle->z = fZ; pBundle->y = fY;
for (int i = 0; i < byCount; i++) {
if (g_pMain->GetItemPtr(nItemNumber) == nullptr) continue;
_LOOT_ITEM item(nItemNumber, sCount);
if (nItemNumber == ITEM_GOLD) { /* coin event çarpanı, USHRT_MAX clamp */ }
pBundle->Items.push_back(item);
}
if (!pMap->RegionItemAdd(regionx, regionz, pBundle)) { delete pBundle; return; }
CUser* pUser = g_pMain->GetUserPtr(sUid);
if (pUser == nullptr) return;
result << sNid << pBundle->nBundleID; // int16 npcID + uint32 bundleID
if (!pUser->isInParty()) pUser->Send(&result);
else g_pMain->Send_PartyMember(pUser->GetPartyID(), &result);
}
3e) GameServer — kutu açma ve item alma (User.cpp)
// HandlePacket içinde:
case WIZ_BUNDLE_OPEN_REQ: BundleOpenReq(pkt); return true;
case WIZ_ITEM_GET: ItemGet(pkt); return true;
void CUser::BundleOpenReq(Packet& pkt)
{
Packet result(WIZ_BUNDLE_OPEN_REQ);
uint32_t bundle_index = pkt.read<uint32_t>();
C3DMap* pMap = GetMap(); CRegion* pRegion = GetRegion();
if (pMap == nullptr || pRegion == nullptr || bundle_index < 1 || isDead()) return;
Guard lock(pRegion->m_RegionItemArray.m_lock);
_LOOT_BUNDLE* pBundle = pRegion->m_RegionItemArray.GetData(bundle_index);
if (pBundle == nullptr || !isInRange(pBundle->x, pBundle->z, MAX_LOOT_RANGE)) return;
foreach (itr, pBundle->Items) result << itr->nItemID << itr->sCount;
for (uint32_t i = pBundle->Items.size(); i < LOOT_ITEMS; i++)
result << uint32_t(0) << uint16_t(0); // dolgu ŞART
Send(&result);
}
ItemGet() içinde:
- Oyuncunun bölgesi + 8 komşu bölgede bundle’ı ara (oyuncu bölge sınırının öbür tarafında olabilir!).
- bundleID + itemID doğrula, menzil kontrolü (bAutoLoot ? RANGE_50M : MAX_LOOT_RANGE).
- Altın ise: partide değilse hepsi sana, partideyse level oranıyla dağıtılır.
- Item ise: FindSlotForItem + CheckWeight → yer yoksa LootNoRoom dön, item’i silme.
- Item’i envantere yaz, sonra RegionItemRemove.
- Bundle boşaldıysa bölgeye haber ver:
if (bBundleNowEmpty && sNpcID != 0) {
Packet notify(WIZ_ITEM_DROP);
notify << sNpcID << uint32_t(0); // 0 = kutu gitti
SendToRegion(¬ify);
}
3f) Zaman aşımı süpürgesi (GameServerDlg.cpp)
Timer_CheckGameEvents() içinden saniyede bir çağır:
void CGameServerDlg::LootBundleSweep()
{
// her zon, her bölge:
if (pBundle && (UNIXTIME - pBundle->tDropTime) >= TIME_LOOT_BUNDLE_REMAIN) {
int16_t sNpcID = pBundle->sNpcID; float fX = pBundle->x, fZ = pBundle->z;
delete pBundle;
it = regionItems.m_UserTypeMap.erase(it);
if (sNpcID != 0) {
Packet notify(WIZ_ITEM_DROP);
notify << sNpcID << uint32_t(0);
Send_NearRegion(¬ify, pMap, rx, rz, fX, fZ);
}
}
}
Süpürge yoksa: hem bellek sızıntısı, hem loot’lanmayan kutuların sonsuza kadar ortada kalması.
4) İstemci tarafı
Değiştirilecek bir şey yok. Sadece kontrol et:
- CPlayerNPC kutuyu Misc\itembox.n3shape’ten yüklüyor — asset mevcut olmalı.
- MsgRecv_ItemBundleDrop cesedi NPCGetByID / CorpseGetByID ile bulabilmeli. Mob ölür ölmez sunucudan siliniyorsa kutu hiç görünmez; ceset kutu boşalana kadar durmalı.
- GameDef.h: MAX_ITEM_BUNDLE_DROP_PACKET = 6 (GS’nin doldurduğu slot sayısıyla aynı olmalı).
Eğimli arazide kutu yarı gömülüyorsa: kutunun XZ’sinde GetHeightWithTerrain(pos.x, pos.z) ile zemin Y’sini al, üstüne küçük bir pay ekle (+0.15 gibi).
5) “Çalışmıyor” diyorsan sırayla bak
| # | Belirti | Sebep |
|---|
| 1 | Kutu hiç çıkmıyor | AIServer drop üretmiyor (eski exe / sPercent=0 / grup fallback yok) |
| 2 | Kutu çıkıp hemen kayboluyor | Ceset erken siliniyor veya bundleID == 0 yollanıyor |
| 3 | Kutu var, tıklayınca boş | GS→client (0,0) dolgusu eksik |
| 4 | Tıklayınca hiç açılmıyor | isInRange(MAX_LOOT_RANGE) çok dar (11 birim) |
| 5 | Item alınmıyor, hata yok | ItemGet bölge araması sadece kendi bölgesinde |
| 6 | Nadir crash 0xC0000374 | m_RegionItemArray.m_lock kilidi yok |
| 7 | Kutu sonsuz duruyor | LootBundleSweep() çağrılmıyor |
6) Test listesi
- Tek başına mob kes → kutu çıkıyor mu, 15 sn sonra kayboluyor mu?
- Kutuya bas → item listesi geliyor mu, item envantere giriyor mu?
- Son item alınınca ceset fade oluyor mu?
- Partide: drop kesene mi gidiyor, altın bölünüyor mu?
- Envanter dolu → “yer yok” hatası veriyor, item yerde kalıyor mu?
- Bölge sınırında loot → komşu bölgeden alınabiliyor mu?
- Anıt / guard tower / scarecrow ölünce kutu çıkmamalı (isShowBox()).