From 0f3a9dfaf79b0227944f2f4052dd48be3b2588f4 Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Fri, 28 Dec 2018 20:05:19 +0300 Subject: Add packet CZ_NPC_BARTER_PURCHASE --- src/map/clif.c | 34 +++++++++++++ src/map/clif.h | 9 ++++ src/map/npc.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++ src/map/npc.h | 1 + src/map/packets.h | 1 + src/map/packets_struct.h | 20 ++++++++ 6 files changed, 189 insertions(+) diff --git a/src/map/clif.c b/src/map/clif.c index 1d0ced6c0..2eb60e2e3 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -11578,6 +11578,10 @@ static void clif_parse_NpcBuySellSelected(int fd, struct map_session_data *sd) /// 3 = "Out of the maximum capacity, you have too many items." /// 9 = "Amounts are exceeded the possession of the item is not available for purchase." /// 10 = "Props open-air store sales will be traded in RODEX" +/// 11 = "The exchange failed." +/// 12 = "The exchange was well done." +/// 13 = "The item is already sold and out of stock." +/// 14 = "There is not enough goods to exchange." static void clif_npc_buy_result(struct map_session_data *sd, unsigned char result) { int fd; @@ -22256,6 +22260,35 @@ static void clif_npc_barter_open(struct map_session_data *sd, struct npc_data *n #endif } +static void clif_parse_NPCBarterPurchase(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); +static void clif_parse_NPCBarterPurchase(int fd, struct map_session_data *sd) +{ +#if PACKETVER_ZERO_NUM >= 20181226 + const struct PACKET_CZ_NPC_BARTER_PURCHASE *p = RP2PTR(fd); + int count = (p->packetLength - sizeof(struct PACKET_CZ_NPC_BARTER_PURCHASE)) / sizeof p->list[0]; + struct barteritemlist item_list; + + Assert_retv(count >= 0 && count <= sd->status.inventorySize); + + VECTOR_INIT(item_list); + VECTOR_ENSURE(item_list, count, 1); + + for (int i = 0; i < count; i++) { + struct barter_itemlist_entry entry = { 0 }; + entry.addId = p->list[i].itemId; + entry.addAmount = p->list[i].amount; + entry.removeIndex = p->list[i].invIndex - 2; + + VECTOR_PUSH(item_list, entry); + } + + int response = npc->barter_buylist(sd, &item_list); + clif->npc_buy_result(sd, response); + + VECTOR_CLEAR(item_list); +#endif +} + /*========================================== * Main client packet processing function *------------------------------------------*/ @@ -23444,4 +23477,5 @@ void clif_defaults(void) clif->npc_barter_open = clif_npc_barter_open; clif->pNPCBarterClosed = clif_parse_NPCBarterClosed; + clif->pNPCBarterPurchase = clif_parse_NPCBarterPurchase; } diff --git a/src/map/clif.h b/src/map/clif.h index 44db8047f..3ed6e8ea1 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -665,6 +665,14 @@ struct stylist_data_entry { }; VECTOR_DECL(struct stylist_data_entry) stylist_data[MAX_STYLIST_TYPE]; +struct barter_itemlist_entry { + int addId; + int addAmount; + int removeIndex; +}; + +VECTOR_STRUCT_DECL(barteritemlist, struct barter_itemlist_entry); + /** * Stylist Shop Responds **/ @@ -1589,6 +1597,7 @@ struct clif_interface { void (*pReqRemainTime) (int fd, struct map_session_data *sd); void (*npc_barter_open) (struct map_session_data *sd, struct npc_data *nd); void (*pNPCBarterClosed) (int fd, struct map_session_data *sd); + void (*pNPCBarterPurchase) (int fd, struct map_session_data *sd); }; #ifdef HERCULES_CORE diff --git a/src/map/npc.c b/src/map/npc.c index 4c4cd0bb5..7f003c321 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -2117,6 +2117,129 @@ static int npc_market_buylist(struct map_session_data *sd, struct itemlist *item return 0; } +/** + * Processes incoming npc barter purchase list + **/ +static int npc_barter_buylist(struct map_session_data *sd, struct barteritemlist *item_list) +{ + struct npc_data* nd; + struct npc_item_list *shop = NULL; + int w, new_; + unsigned short shop_size = 0; + + nullpo_retr(1, sd); + nullpo_retr(1, item_list); + + nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)); + + if (nd == NULL || nd->subtype != SCRIPT || VECTOR_LENGTH(*item_list) == 0 || !nd->u.scr.shop || nd->u.scr.shop->type != NST_BARTER) + return 11; + + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + + w = 0; + new_ = 0; + + int items[MAX_INVENTORY] = { 0 }; + + // process entries in buy list, one by one + for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) { + struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); + + const int n = entry->removeIndex; + if (n < 0 || n >= sd->status.inventorySize) + return 11; // wrong inventory index + + if (!itemdb->exists(entry->addId)) + return 13; // item no longer in itemdb + + const int removeId = sd->status.inventory[n].nameid; + int j = shop_size; + // find this entry in the shop's sell list + ARR_FIND(0, shop_size, j, (entry->addId == shop[j].nameid || entry->addId == itemdb_viewid(shop[j].nameid)) && + (removeId == shop[j].value || removeId == itemdb_viewid(shop[j].value))); + if (j == shop_size) + return 13; // no such item in shop + + const int removeAmount = shop[j].value2; + + if (entry->addAmount > (int)shop[j].qty) + return 14; // not enough item amount in shop + + if (removeAmount * entry->addAmount > sd->status.inventory[n].amount) + return 14; // not enough item amount in inventory + + items[n] += removeAmount * entry->addAmount; + + if (items[n] > sd->status.inventory[n].amount) + return 14; // not enough item amount in inventory + + entry->addId = shop[j].nameid; //item_avail replacement + + npc_market_qty[i] = j; + + if (!itemdb->isstackable(entry->addId) && entry->addAmount > 1) { + //Exploit? You can't buy more than 1 of equipment types o.O + ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of non-stackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, entry->addAmount, entry->addId); + entry->addAmount = 1; + } + + switch (pc->checkadditem(sd, entry->addId, entry->addAmount)) { + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new_++; + break; + case ADDITEM_OVERAMOUNT: /* TODO find official response for this */ + return 1; + } + + w += itemdb_weight(entry->addId) * entry->addAmount; + w -= itemdb_weight(removeId) * removeAmount; + } + + if (w + sd->weight > sd->max_weight) + return 2; // Too heavy + + if (pc->inventoryblank(sd) < new_) + return 3; // Not enough space to store items + + for (int i = 0; i < sd->status.inventorySize; ++i) { + const int removeAmountTotal = items[i]; + if (removeAmountTotal == 0) + continue; + if (pc->delitem(sd, i, removeAmountTotal, 0, DELITEM_SOLD, LOG_TYPE_NPC) != 0) { + return 11; // unknown exploit + } + } + + for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) { + struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); + const int shopIdx = npc_market_qty[i]; + + if (entry->addAmount > (int)shop[shopIdx].qty) /* wohoo someone tampered with the packet. */ + return 14; + + shop[shopIdx].qty -= entry->addAmount; + + // TODO: save to sql + + if (itemdb_type(entry->addId) == IT_PETEGG) { + pet->create_egg(sd, entry->addId); + } else { + struct item item_tmp; + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = entry->addId; + item_tmp.identify = 1; + pc->additem(sd, &item_tmp, entry->addAmount, LOG_TYPE_NPC); + } + } + + return 12; +} + /// npc_selllist for script-controlled shops static int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd) { @@ -5308,6 +5431,7 @@ void npc_defaults(void) npc->trader_pay = npc_trader_pay; npc->trader_update = npc_trader_update; npc->market_buylist = npc_market_buylist; + npc->barter_buylist = npc_barter_buylist; npc->trader_open = npc_trader_open; npc->market_fromsql = npc_market_fromsql; npc->market_tosql = npc_market_tosql; diff --git a/src/map/npc.h b/src/map/npc.h index 2cd0c0e29..bd16938a3 100644 --- a/src/map/npc.h +++ b/src/map/npc.h @@ -310,6 +310,7 @@ struct npc_interface { bool (*trader_pay) (struct npc_data *nd, struct map_session_data *sd, int price, int points); void (*trader_update) (int master); int (*market_buylist) (struct map_session_data *sd, struct itemlist *item_list); + int (*barter_buylist) (struct map_session_data *sd, struct barteritemlist *item_list); bool (*trader_open) (struct map_session_data *sd, struct npc_data *nd); void (*market_fromsql) (void); void (*market_tosql) (struct npc_data *nd, unsigned short index); diff --git a/src/map/packets.h b/src/map/packets.h index aa9f041d3..44a49b387 100644 --- a/src/map/packets.h +++ b/src/map/packets.h @@ -1923,6 +1923,7 @@ packet(0x96e,clif->ackmergeitems); #endif #if PACKETVER_ZERO_NUM >= 20181226 + packet(0x0b0f,clif->pNPCBarterPurchase); packet(0x0b12,clif->pNPCBarterClosed); #endif diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h index 82ae82eaf..d20b20bee 100644 --- a/src/map/packets_struct.h +++ b/src/map/packets_struct.h @@ -3091,6 +3091,26 @@ struct PACKET_CZ_NPC_BARTER_CLOSE { DEFINE_PACKET_HEADER(CZ_NPC_BARTER_CLOSE, 0x0b12); #endif +#if PACKETVER_ZERO_NUM >= 20181226 +struct PACKET_CZ_NPC_BARTER_PURCHASE_sub { +#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114 + uint32 itemId; +#else + uint16 itemId; +#endif + uint32 amount; + uint16 invIndex; + uint32 shopIndex; +} __attribute__((packed)); + +struct PACKET_CZ_NPC_BARTER_PURCHASE { + int16 packetType; + int16 packetLength; + struct PACKET_CZ_NPC_BARTER_PURCHASE_sub list[]; +} __attribute__((packed)); +DEFINE_PACKET_HEADER(CZ_NPC_BARTER_PURCHASE, 0x0b0f); +#endif + #if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute #pragma pack(pop) #endif // not NetBSD < 6 / Solaris -- cgit v1.2.3-60-g2f50