diff options
Diffstat (limited to 'src/map')
-rw-r--r-- | src/map/atcommand.c | 72 | ||||
-rw-r--r-- | src/map/buyingstore.c | 4 | ||||
-rw-r--r-- | src/map/clif.c | 27 | ||||
-rw-r--r-- | src/map/clif.h | 8 | ||||
-rw-r--r-- | src/map/guild.c | 35 | ||||
-rw-r--r-- | src/map/guild.h | 2 | ||||
-rw-r--r-- | src/map/intif.c | 67 | ||||
-rw-r--r-- | src/map/intif.h | 2 | ||||
-rw-r--r-- | src/map/mail.c | 3 | ||||
-rw-r--r-- | src/map/party.c | 3 | ||||
-rw-r--r-- | src/map/pc.c | 88 | ||||
-rw-r--r-- | src/map/pc.h | 6 | ||||
-rw-r--r-- | src/map/pc_groups.c | 1 | ||||
-rw-r--r-- | src/map/pc_groups.h | 1 | ||||
-rw-r--r-- | src/map/script.c | 115 | ||||
-rw-r--r-- | src/map/script.h | 1 | ||||
-rw-r--r-- | src/map/storage.c | 17 | ||||
-rw-r--r-- | src/map/trade.c | 23 | ||||
-rw-r--r-- | src/map/vending.c | 3 |
19 files changed, 405 insertions, 73 deletions
diff --git a/src/map/atcommand.c b/src/map/atcommand.c index 20fcdd79c..316e5a39b 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -1087,22 +1087,29 @@ ACMD(heal) /*========================================== * @item command (usage: @item <name/id_of_item> <quantity>) (modified by [Yor] for pet_egg) + * @itembound command (usage: @itembound <name/id_of_item> <quantity> <bound type>) (revised by [Mhalicot]) *------------------------------------------*/ ACMD(item) { char item_name[100]; - int number = 0, item_id, flag = 0; + int number = 0, item_id, flag = 0, bound = 0; struct item item_tmp; struct item_data *item_data; int get_count, i; memset(item_name, '\0', sizeof(item_name)); - - if (!message || !*message || ( - sscanf(message, "\"%99[^\"]\" %d", item_name, &number) < 1 && - sscanf(message, "%99s %d", item_name, &number) < 1 - )) { - clif->message(fd, msg_txt(983)); // Please enter an item name or ID (usage: @item <item name/ID> <quantity>). + + if (!strcmpi(command+1,"itembound") && (!message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d %d", item_name, &number, &bound) < 2 && + sscanf(message, "%99s %d %d", item_name, &number, &bound) < 2 + ))) { + clif->message(fd, msg_txt(295)); // Please enter an item name or ID (usage: @itembound <item name/ID> <quantity> <bound_type>). + return false; + } else if (!message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d", item_name, &number) < 1 && + sscanf(message, "%99s %d", item_name, &number) < 1 )) + { + clif->message(fd, msg_txt(983)); // Please enter an item name or ID (usage: @item <item name/ID> <quantity>). return false; } @@ -1116,11 +1123,21 @@ ACMD(item) return false; } + if(!strcmpi(command+1,"itembound") && !(bound >= IBT_MIN && bound <= IBT_MAX) ) { + clif->message(fd, msg_txt(298)); // Invalid bound type + return false; + } + item_id = item_data->nameid; get_count = number; //Check if it's stackable. - if (!itemdb->isstackable2(item_data)) - get_count = 1; + if (!itemdb->isstackable2(item_data)) { + if( bound && (item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) ) { + clif->message(fd, msg_txt(498)); // Cannot create bounded pet eggs or pet armors. + return false; + } + get_count = 1; + } for (i = 0; i < number; i += get_count) { // if not pet egg @@ -1128,6 +1145,7 @@ ACMD(item) memset(&item_tmp, 0, sizeof(item_tmp)); item_tmp.nameid = item_id; item_tmp.identify = 1; + item_tmp.bound = (unsigned char)bound; if ((flag = pc->additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND))) clif->additem(sd, 0, 0, flag); @@ -1140,21 +1158,27 @@ ACMD(item) } /*========================================== - * + * @item2 and @itembound2 command (revised by [Mhalicot]) *------------------------------------------*/ ACMD(item2) { struct item item_tmp; struct item_data *item_data; char item_name[100]; - int item_id, number = 0; + int item_id, number = 0, bound = 0; int identify = 0, refine = 0, attr = 0; int c1 = 0, c2 = 0, c3 = 0, c4 = 0; memset(item_name, '\0', sizeof(item_name)); - if (!message || !*message || ( - sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 && + if (!strcmpi(command+1,"itembound2") && (!message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4, &bound) < 10 && + sscanf(message, "%99s %d %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4, &bound) < 10 ))) { + clif->message(fd, msg_txt(296)); // Please enter all parameters (usage: @itembound2 <item name/ID> <quantity> + clif->message(fd, msg_txt(297)); // <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4> <bound_type>). + return false; + } else if ( !message || !*message || ( + sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 && sscanf(message, "%99s %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 )) { clif->message(fd, msg_txt(984)); // Please enter all parameters (usage: @item2 <item name/ID> <quantity> @@ -1164,7 +1188,12 @@ ACMD(item2) if (number <= 0) number = 1; - + + if( !strcmpi(command+1,"itembound2") && !(bound >= IBT_MIN && bound <= IBT_MAX) ) { + clif->message(fd, msg_txt(298)); // Invalid bound type + return false; + } + item_id = 0; if ((item_data = itemdb->search_name(item_name)) != NULL || (item_data = itemdb->exists(atoi(item_name))) != NULL) @@ -1175,9 +1204,14 @@ ACMD(item2) int loop, get_count, i; loop = 1; get_count = number; - if (item_data->type == IT_WEAPON || item_data->type == IT_ARMOR || - item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) { - loop = number; + if( !strcmpi(command+1,"itembound2") ) + bound = 1; + if( !itemdb->isstackable2(item_data) ) { + if( bound && (item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) ) { + clif->message(fd, msg_txt(498)); // Cannot create bounded pet eggs or pet armors. + return false; + } + loop = number; get_count = 1; if (item_data->type == IT_PETEGG) { identify = 1; @@ -1197,10 +1231,12 @@ ACMD(item2) item_tmp.identify = identify; item_tmp.refine = refine; item_tmp.attribute = attr; + item_tmp.bound = (unsigned char)bound; item_tmp.card[0] = c1; item_tmp.card[1] = c2; item_tmp.card[2] = c3; item_tmp.card[3] = c4; + if ((flag = pc->additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND))) clif->additem(sd, 0, 0, flag); } @@ -9334,6 +9370,8 @@ void atcommand_basecommands(void) { ACMD_DEF(heal), ACMD_DEF(item), ACMD_DEF(item2), + ACMD_DEF2("itembound", item), + ACMD_DEF2("itembound2", item2), ACMD_DEF(itemreset), ACMD_DEF(clearstorage), ACMD_DEF(cleargstorage), diff --git a/src/map/buyingstore.c b/src/map/buyingstore.c index 2a9e6a88e..a9f1412ed 100644 --- a/src/map/buyingstore.c +++ b/src/map/buyingstore.c @@ -290,8 +290,8 @@ void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int return; } - if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc->get_group_level(sd), pc->get_group_level(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore->blankslots, sizeof(buyingstore->blankslots)) ) - {// non-tradable item + if( sd->status.inventory[index].expire_time || (sd->status.inventory[index].bound && !pc->can_give_bound_items(sd)) || !itemdb_cantrade(&sd->status.inventory[index], pc->get_group_level(sd), pc->get_group_level(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore->blankslots, sizeof(buyingstore->blankslots)) ) + {// non-tradable item clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } diff --git a/src/map/clif.c b/src/map/clif.c index c099d5a1b..70b81ffab 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -1804,6 +1804,9 @@ void clif_selllist(struct map_session_data *sd) if( sd->status.inventory[i].expire_time ) continue; // Cannot Sell Rental Items + + if( sd->status.inventory[i].bound && !pc->can_give_bound_items(sd)) + continue; // Don't allow sale of bound items val=sd->inventory_data[i]->value_sell; if( val < 0 ) @@ -2229,7 +2232,10 @@ void clif_additem(struct map_session_data *sd, int n, int amount, int fail) { p.HireExpireDate = sd->status.inventory[n].expire_time; #endif #if PACKETVER >= 20071002 - p.bindOnEquipType = 0; // unused + /* why restrict the flag to non-stackable? because this is the only packet allows stackable to, + * show the color, and therefore it'd be inconsistent with the rest (aka it'd show yellow, you relog/refresh and boom its gone) + */ + p.bindOnEquipType = sd->status.inventory[n].bound && !itemdb->isstackable2(sd->inventory_data[n]) ? 2 : 0; #endif } p.result = (unsigned char)fail; @@ -2341,7 +2347,7 @@ void clif_item_equip(short idx, struct EQUIPITEM_INFO *p, struct item *i, struct #endif #if PACKETVER >= 20080102 - p->bindOnEquipType = 0; + p->bindOnEquipType = i->bound ? 2 : 0; #endif #if PACKETVER >= 20100629 @@ -15063,8 +15069,9 @@ void clif_parse_Auction_setitem(int fd, struct map_session_data *sd) if( !pc->can_give_items(sd) || sd->status.inventory[idx].expire_time || !sd->status.inventory[idx].identify || - !itemdb_canauction(&sd->status.inventory[idx],pc->get_group_level(sd)) ) { // Quest Item or something else - clif->auction_setitem(sd->fd, idx, true); + !itemdb_canauction(&sd->status.inventory[idx],pc->get_group_level(sd)) || // Quest Item or something else + (sd->status.inventory[idx].bound && !pc->can_give_bound_items(sd)) ) { + clif->auction_setitem(sd->fd, idx, true); return; } @@ -15140,12 +15147,11 @@ void clif_parse_Auction_register(int fd, struct map_session_data *sd) return; } - // Auction checks... if( sd->status.zeny < (auction.hours * battle_config.auction_feeperhour) ) { - clif->auction_message(fd, 5); // You do not have enough zeny to pay the Auction Fee. + clif_Auction_message(fd, 5); // You do not have enough zeny to pay the Auction Fee. return; } - + if( auction.buynow > battle_config.auction_maximumprice ) { // Zeny Limits auction.buynow = battle_config.auction_maximumprice; @@ -15171,6 +15177,13 @@ void clif_parse_Auction_register(int fd, struct map_session_data *sd) return; } + // Auction checks... + if( sd->status.inventory[sd->auction.index].bound && !pc->can_give_bound_items(sd) ) { + clif->message(sd->fd, msg_txt(293)); + clif->auction_message(fd, 2); // The auction has been canceled + return; + } + safestrncpy(auction.item_name, item->jname, sizeof(auction.item_name)); auction.type = item->type; memcpy(&auction.item, &sd->status.inventory[sd->auction.index], sizeof(struct item)); diff --git a/src/map/clif.h b/src/map/clif.h index 710cb6590..cc222d8aa 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -450,6 +450,14 @@ enum e_UNEQUIP_ITEM_ACK { #endif }; +enum e_trade_item_ok { + TIO_SUCCESS = 0x0, + TIO_OVERWEIGHT = 0x1, + TIO_CANCEL = 0x2, + /* feedback-friendly code that causes the client not to display a error message */ + TIO_INDROCKS = 0x9, +}; + /** * Structures **/ diff --git a/src/map/guild.c b/src/map/guild.c index fcdcddab2..cba568bd8 100644 --- a/src/map/guild.c +++ b/src/map/guild.c @@ -865,6 +865,11 @@ int guild_member_withdraw(int guild_id, int account_id, int char_id, int flag, c online_member_sd = guild->getavailablesd(g); if(online_member_sd == NULL) return 0; // noone online to inform + +#ifdef GP_BOUND_ITEMS + //Guild bound item check + guild->retrieveitembound(char_id,account_id,guild_id); +#endif if(!flag) clif->guild_leave(online_member_sd, name, mes); @@ -899,6 +904,28 @@ int guild_member_withdraw(int guild_id, int account_id, int char_id, int flag, c return 0; } +void guild_retrieveitembound(int char_id,int aid,int guild_id) { +#ifdef GP_BOUND_ITEMS + TBL_PC *sd = map->id2sd(aid); + if(sd){ //Character is online + pc->bound_clear(sd,IBT_GUILD); + } else { //Character is offline, ask char server to do the job + struct guild_storage *gstor = gstorage->id2storage2(guild_id); + if(gstor && gstor->storage_status == 1) { //Someone is in guild storage, close them + struct s_mapiterator* iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit->first(iter); mapit->exists(iter); sd = (TBL_PC*)mapit->next(iter) ) { + if(sd->status.guild_id == guild_id && sd->state.storage_flag == 2) { + gstorage->close(sd); + break; + } + } + mapit->free(iter); + } + intif->itembound_req(char_id,aid,guild_id); + } +#endif +} + int guild_send_memberinfoshort(struct map_session_data *sd,int online) { // cleaned up [LuzZza] struct guild *g; @@ -1813,7 +1840,7 @@ int guild_break(struct map_session_data *sd,char *name) { struct guild *g; struct unit_data *ud; int i; - + nullpo_ret(sd); if( (g=sd->guild)==NULL ) @@ -1855,6 +1882,10 @@ int guild_break(struct map_session_data *sd,char *name) { skill->del_unitgroup(groups[i],ALC_MARK); } } + +#ifdef GP_BOUND_ITEMS + pc->bound_clear(sd,IBT_GUILD); +#endif intif->guild_break(g->guild_id); return 1; @@ -2328,4 +2359,6 @@ void guild_defaults(void) { guild->check_member = guild_check_member; guild->get_alliance_count = guild_get_alliance_count; guild->castle_reconnect_sub = guild_castle_reconnect_sub; + /* */ + guild->retrieveitembound = guild_retrieveitembound; } diff --git a/src/map/guild.h b/src/map/guild.h index 8da9036e6..1a04a98ef 100644 --- a/src/map/guild.h +++ b/src/map/guild.h @@ -147,6 +147,8 @@ struct guild_interface { void (*flags_clear) (void); /* guild aura */ void (*aura_refresh) (struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); + /* item bound [Mhalicot]*/ + void (*retrieveitembound) (int char_id,int aid,int guild_id); /* */ int (*payexp_timer) (int tid, int64 tick, int id, intptr_t data); TBL_PC* (*sd_check) (int guild_id, int account_id, int char_id); diff --git a/src/map/intif.c b/src/map/intif.c index f31ab0f5a..e6ff91af7 100644 --- a/src/map/intif.c +++ b/src/map/intif.c @@ -982,15 +982,18 @@ void intif_parse_LoadGuildStorage(int fd) { struct guild_storage *gstor; struct map_session_data *sd; - int guild_id; + int guild_id, flag; guild_id = RFIFOL(fd,8); + flag = RFIFOL(fd,12); if(guild_id <= 0) return; sd=map->id2sd( RFIFOL(fd,4) ); - if(sd==NULL){ - ShowError("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4)); - return; + if( flag ){ //If flag != 0, we attach a player and open the storage + if(sd==NULL){ + ShowError("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4)); + return; + } } gstor=gstorage->id2storage(guild_id); if(!gstor) { @@ -998,21 +1001,22 @@ void intif_parse_LoadGuildStorage(int fd) return; } if (gstor->storage_status == 1) { // Already open.. lets ignore this update - ShowWarning("intif_parse_LoadGuildStorage: storage received for a client already open (User %d:%d)\n", sd->status.account_id, sd->status.char_id); + ShowWarning("intif_parse_LoadGuildStorage: storage received for a client already open (User %d:%d)\n", flag?sd->status.account_id:0, flag?sd->status.char_id:0); return; } if (gstor->dirty) { // Already have storage, and it has been modified and not saved yet! Exploit! [Skotlex] - ShowWarning("intif_parse_LoadGuildStorage: received storage for an already modified non-saved storage! (User %d:%d)\n", sd->status.account_id, sd->status.char_id); + ShowWarning("intif_parse_LoadGuildStorage: received storage for an already modified non-saved storage! (User %d:%d)\n", flag?sd->status.account_id:0, flag?sd->status.char_id:0); return; } - if( RFIFOW(fd,2)-12 != sizeof(struct guild_storage) ){ - ShowError("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-12 , sizeof(struct guild_storage)); - gstor->storage_status = 0; + if( RFIFOW(fd,2)-13 != sizeof(struct guild_storage) ){ + ShowError("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-13 , sizeof(struct guild_storage)); + gstor->storage_status = 0; return; } - memcpy(gstor,RFIFOP(fd,12),sizeof(struct guild_storage)); - gstorage->open(sd); + memcpy(gstor,RFIFOP(fd,13),sizeof(struct guild_storage)); + if( flag ) + gstorage->open(sd); } // ACK guild_storage saved @@ -2005,7 +2009,34 @@ void intif_parse_MessageToFD(int fd) { return; } +/*========================================== + * Item Bound System [Xantara][Mhalicot] + *------------------------------------------*/ +void intif_itembound_req(int char_id,int aid,int guild_id) { +#ifdef GP_BOUND_ITEMS + struct guild_storage *gstor = gstorage->id2storage2(guild_id); + WFIFOHEAD(inter_fd,12); + WFIFOW(inter_fd,0) = 0x3056; + WFIFOL(inter_fd,2) = char_id; + WFIFOL(inter_fd,6) = aid; + WFIFOW(inter_fd,10) = guild_id; + WFIFOSET(inter_fd,12); + if(gstor) + gstor->lock = 1; //Lock for retrieval process +#endif +} + +//3856 +void intif_parse_Itembound_ack(int fd) { +#ifdef GP_BOUND_ITEMS + struct guild_storage *gstor; + int guild_id = RFIFOW(fd,6); + gstor = gstorage->id2storage2(guild_id); + if(gstor) + gstor->lock = 0; //Unlock now that operation is completed +#endif +} //----------------------------------------------------------------- // Communication from the inter server // Return a 0 (false) if there were any errors. @@ -2088,7 +2119,14 @@ int intif_parse(int fd) case 0x3853: intif->pAuctionClose(fd); break; case 0x3854: intif->pAuctionMessage(fd); break; case 0x3855: intif->pAuctionBid(fd); break; - + //Bound items + case 0x3856: +#ifdef GP_BOUND_ITEMS + intif->pItembound_ack(fd); +#else + ShowWarning("intif_parse: Received 0x3856 with GP_BOUND_ITEMS disabled !!!\n") +#endif + break; // Mercenary System case 0x3870: intif->pMercenaryReceived(fd); break; case 0x3871: intif->pMercenaryDeleted(fd); break; @@ -2127,7 +2165,7 @@ void intif_defaults(void) { 39,-1,15,15, 14,19, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3820 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, //0x3830 -1, 0, 0,14, 0, 0, 0, 0, -1,74,-1,11, 11,-1, 0, 0, //0x3840 - -1,-1, 7, 7, 7,11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] + -1,-1, 7, 7, 7,11, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] itembound[Akinari] -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3860 Quests [Kevin] [Inkfish] -1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, 3, 0, //0x3870 Mercenaries [Zephyrus] / Elemental [pakpil] 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3880 @@ -2216,6 +2254,8 @@ void intif_defaults(void) { /* */ intif->CheckForCharServer = CheckForCharServer; /* */ + intif->itembound_req = intif_itembound_req; + /* parse functions */ intif->pWisMessage = intif_parse_WisMessage; intif->pWisEnd = intif_parse_WisEnd; intif->pWisToGM_sub = mapif_parse_WisToGM_sub; @@ -2263,6 +2303,7 @@ void intif_defaults(void) { intif->pAuctionClose = intif_parse_AuctionClose; intif->pAuctionMessage = intif_parse_AuctionMessage; intif->pAuctionBid = intif_parse_AuctionBid; + intif->pItembound_ack = intif_parse_Itembound_ack; intif->pMercenaryReceived = intif_parse_MercenaryReceived; intif->pMercenaryDeleted = intif_parse_MercenaryDeleted; intif->pMercenarySaved = intif_parse_MercenarySaved; diff --git a/src/map/intif.h b/src/map/intif.h index 768e735de..5e996b6fe 100644 --- a/src/map/intif.h +++ b/src/map/intif.h @@ -75,6 +75,7 @@ struct intif_interface { int (*guild_emblem) (int guild_id, int len, const char *data); int (*guild_castle_dataload) (int num, int *castle_ids); int (*guild_castle_datasave) (int castle_id, int index, int value); + void (*itembound_req) (int char_id, int aid, int guild_id); int (*request_petdata) (int account_id, int char_id, int pet_id); int (*save_petdata) (int account_id, struct s_pet *p); int (*delete_petdata) (int pet_id); @@ -161,6 +162,7 @@ struct intif_interface { void (*pAuctionClose) (int fd); void (*pAuctionMessage) (int fd); void (*pAuctionBid) (int fd); + void (*pItembound_ack) (int fd); void (*pMercenaryReceived) (int fd); void (*pMercenaryDeleted) (int fd); void (*pMercenarySaved) (int fd); diff --git a/src/map/mail.c b/src/map/mail.c index 2378cbe2a..007f7592d 100644 --- a/src/map/mail.c +++ b/src/map/mail.c @@ -82,7 +82,8 @@ unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount) { if( amount < 0 || amount > sd->status.inventory[idx].amount ) return 1; if( !pc->can_give_items(sd) || sd->status.inventory[idx].expire_time || - !itemdb_canmail(&sd->status.inventory[idx],pc->get_group_level(sd)) ) + !itemdb_canmail(&sd->status.inventory[idx],pc->get_group_level(sd)) || + (sd->status.inventory[idx].bound && !pc->can_give_bound_items(sd)) ) return 1; sd->mail.index = idx; diff --git a/src/map/party.c b/src/map/party.c index 6caf3db23..0a7467162 100644 --- a/src/map/party.c +++ b/src/map/party.c @@ -547,6 +547,9 @@ int party_member_withdraw(int party_id, int account_id, int char_id) } if( sd && sd->status.party_id == party_id && sd->status.char_id == char_id ) { +#ifdef GP_BOUND_ITEMS + pc->bound_clear(sd,IBT_PARTY); +#endif sd->status.party_id = 0; clif->charnameupdate(sd); //Update name display [Skotlex] //TODO: hp bars should be cleared too diff --git a/src/map/pc.c b/src/map/pc.c index fcd96a85c..07fba6e57 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -568,6 +568,14 @@ bool pc_can_give_items(struct map_session_data *sd) return pc->has_permission(sd, PC_PERM_TRADE); } +/** + * Determines if player can give / drop / trade / vend bounded items + */ +bool pc_can_give_bound_items(struct map_session_data *sd) +{ + return pc->has_permission(sd, PC_PERM_TRADE_BOUND); +} + /*========================================== * prepares character for saving. *------------------------------------------*/ @@ -1190,7 +1198,12 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim * Check if player have any item cooldowns on **/ pc->itemcd_do(sd,true); - + +#ifdef GP_BOUND_ITEMS + if( sd->status.party_id == 0 ) + pc->bound_clear(sd,IBT_PARTY); +#endif + /* [Ind/Hercules] */ sd->sc_display = NULL; sd->sc_display_count = 0; @@ -3942,13 +3955,35 @@ int pc_additem(struct map_session_data *sd,struct item *item_data,int amount,e_l if(sd->weight + w > sd->max_weight) return 2; + if( item_data->bound ) { + switch( (enum e_item_bound_type)item_data->bound ) { + case IBT_CHARACTER: + case IBT_ACCOUNT: + break; /* no restrictions */ + case IBT_PARTY: + if( !sd->status.party_id ) { + ShowError("pc_additem: can't add party_bound item to character without party!\n"); + ShowError("pc_additem: %s - x%d %s (%d)\n",sd->status.name,amount,data->jname,data->nameid); + return 7;/* need proper code? */ + } + break; + case IBT_GUILD: + if( !sd->status.guild_id ) { + ShowError("pc_additem: can't add guild_bound item to character without guild!\n"); + ShowError("pc_additem: %s - x%d %s (%d)\n",sd->status.name,amount,data->jname,data->nameid); + return 7;/* need proper code? */ + } + break; + } + } + i = MAX_INVENTORY; if( itemdb->isstackable2(data) && item_data->expire_time == 0 ) { // Stackable | Non Rental for( i = 0; i < MAX_INVENTORY; i++ ) { - if( sd->status.inventory[i].nameid == item_data->nameid && memcmp(&sd->status.inventory[i].card, &item_data->card, sizeof(item_data->card)) == 0 ) + if( sd->status.inventory[i].nameid == item_data->nameid && sd->status.inventory[i].bound == item_data->bound && memcmp(&sd->status.inventory[i].card, &item_data->card, sizeof(item_data->card)) == 0 ) { if( amount > MAX_AMOUNT - sd->status.inventory[i].amount || ( data->stack.inventory && amount > data->stack.amount - sd->status.inventory[i].amount ) ) return 5; @@ -4500,8 +4535,8 @@ int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amoun return 1; } - if( !itemdb_cancartstore(item_data, pc->get_group_level(sd)) ) - { // Check item trade restrictions [Skotlex] + if( !itemdb_cancartstore(item_data, pc->get_group_level(sd)) || (item_data->bound > IBT_ACCOUNT && !pc->can_give_bound_items(sd))) + { // Check item trade restrictions [Skotlex] clif->message (sd->fd, msg_txt(264)); return 1;/* TODO: there is no official response to this? */ } @@ -4513,8 +4548,8 @@ int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amoun if( itemdb->isstackable2(data) && !item_data->expire_time ) { ARR_FIND( 0, MAX_CART, i, - sd->status.cart[i].nameid == item_data->nameid && - sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] && + sd->status.cart[i].nameid == item_data->nameid && sd->status.cart[i].bound == item_data->bound && + sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] && sd->status.cart[i].card[2] == item_data->card[2] && sd->status.cart[i].card[3] == item_data->card[3] ); }; @@ -4647,7 +4682,39 @@ int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount) return flag; } - +void pc_bound_clear(struct map_session_data *sd, enum e_item_bound_type type) { + int i; + + switch( type ) { + /* both restricted to inventory */ + case IBT_PARTY: + case IBT_CHARACTER: + for( i = 0; i < MAX_INVENTORY; i++ ){ + if( sd->status.inventory[i].bound == type ) { + pc->delitem(sd,i,sd->status.inventory[i].amount,0,1,LOG_TYPE_OTHER); + } + } + break; + case IBT_ACCOUNT: + ShowError("Helllo! You reached pc_bound_clear for IBT_ACCOUNT, unfortunately no scenario was expected for this!\n"); + break; + case IBT_GUILD: { + struct guild_storage *gstor = gstorage->id2storage(sd->status.guild_id); + + for( i = 0; i < MAX_INVENTORY; i++ ){ + if(sd->status.inventory[i].bound == type) { + if( gstor ) + gstorage->additem(sd,gstor,&sd->status.inventory[i],sd->status.inventory[i].amount); + pc->delitem(sd,i,sd->status.inventory[i].amount,0,1,gstor?LOG_TYPE_GSTORAGE:LOG_TYPE_OTHER); + } + } + if( gstor ) + gstorage->close(sd); + } + break; + } + +} /*========================================== * Display item stolen msg to player sd *------------------------------------------*/ @@ -7991,8 +8058,8 @@ int pc_setmadogear(TBL_PC* sd, int flag) *------------------------------------------*/ int pc_candrop(struct map_session_data *sd, struct item *item) { - if( item && item->expire_time ) - return 0; + if( item && (item->expire_time || (item->bound && !pc->can_give_bound_items(sd))) ) + return 0; if( !pc->can_give_items(sd) ) //check if this GM level can drop items return 0; return (itemdb_isdropable(item, pc->get_group_level(sd))); @@ -10328,6 +10395,7 @@ void pc_defaults(void) { pc->class2idx = pc_class2idx; pc->get_group_level = pc_get_group_level; pc->can_give_items = pc_can_give_items; + pc->can_give_bound_items = pc_can_give_bound_items; pc->can_use_command = pc_can_use_command; pc->has_permission = pc_has_permission; @@ -10560,4 +10628,6 @@ void pc_defaults(void) { pc->rental_expire = pc_rental_expire; pc->scdata_received = pc_scdata_received; + + pc->bound_clear = pc_bound_clear; } diff --git a/src/map/pc.h b/src/map/pc.h index af03aa2f3..32ca214c0 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -750,7 +750,8 @@ struct pc_interface { int (*get_group_level) (struct map_session_data *sd); //int (*getrefinebonus) (int lv,int type); FIXME: This function does not exist, nor it is ever called bool (*can_give_items) (struct map_session_data *sd); - + bool (*can_give_bound_items) (struct map_session_data *sd); + bool (*can_use_command) (struct map_session_data *sd, const char *command); bool (*has_permission) (struct map_session_data *sd, unsigned int permission); int (*set_group) (struct map_session_data *sd, int group_id); @@ -789,6 +790,7 @@ struct pc_interface { int (*additem) (struct map_session_data *sd,struct item *item_data,int amount,e_log_pick_type log_type); int (*getzeny) (struct map_session_data *sd,int zeny, enum e_log_pick_type type, struct map_session_data *tsd); int (*delitem) (struct map_session_data *sd,int n,int amount,int type, short reason, e_log_pick_type log_type); + // Special Shop System int (*paycash) (struct map_session_data *sd, int price, int points); int (*getcash) (struct map_session_data *sd, int cash, int points); @@ -981,6 +983,8 @@ struct pc_interface { void (*rental_expire) (struct map_session_data *sd, int i); void (*scdata_received) (struct map_session_data *sd); + + void (*bound_clear) (struct map_session_data *sd, enum e_item_bound_type type); }; struct pc_interface *pc; diff --git a/src/map/pc_groups.c b/src/map/pc_groups.c index 78d111b1c..f95878e97 100644 --- a/src/map/pc_groups.c +++ b/src/map/pc_groups.c @@ -417,6 +417,7 @@ void do_init_pc_groups(void) { { "disable_pvp", PC_PERM_DISABLE_PVP }, { "disable_commands_when_dead", PC_PERM_DISABLE_CMD_DEAD }, { "hchsys_admin", PC_PERM_HCHSYS_ADMIN }, + { "can_trade_bound", PC_PERM_TRADE_BOUND }, }; unsigned char i, len = ARRAYLENGTH(pc_g_defaults); diff --git a/src/map/pc_groups.h b/src/map/pc_groups.h index 28c82d619..943fb7fa5 100644 --- a/src/map/pc_groups.h +++ b/src/map/pc_groups.h @@ -30,6 +30,7 @@ enum e_pc_permission { PC_PERM_DISABLE_PVP = 0x080000, // #20 PC_PERM_DISABLE_CMD_DEAD = 0x100000, PC_PERM_HCHSYS_ADMIN = 0x200000, + PC_PERM_TRADE_BOUND = 0x400000, }; // Cached config settings for quick lookup diff --git a/src/map/script.c b/src/map/script.c index b87020253..53b5bbe16 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -3819,6 +3819,17 @@ int script_reload(void) { return 0; } +/* returns name of current function being run, from within the stack [Ind/Hercules] */ +const char *script_getfuncname(struct script_state *st) { + struct script_data *data; + + data = &st->stack->stack_data[st->start]; + + if( data->type == C_NAME && script->str_data[data->u.num].type == C_FUNC ) + return script->get_str(data->u.num); + + return NULL; +} //----------------------------------------------------------------------------- // buildin functions @@ -5803,10 +5814,13 @@ BUILDIN(checkweight2) /*========================================== * getitem <item id>,<amount>{,<account ID>}; * getitem "<item name>",<amount>{,<account ID>}; + * + * getitembound <item id>,<amount>,<type>{,<account ID>}; + * getitembound "<item id>",<amount>,<type>{,<account ID>}; *------------------------------------------*/ BUILDIN(getitem) { - int nameid,amount,get_count,i,flag = 0; + int nameid,amount,get_count,i,flag = 0, offset = 0; struct item it; TBL_PC *sd; struct script_data *data; @@ -5818,7 +5832,7 @@ BUILDIN(getitem) {// "<item name>" const char *name=script->conv_str(st,data); if( (item_data = itemdb->search_name(name)) == NULL ){ - ShowError("buildin_getitem: Nonexistant item %s requested.\n", name); + ShowError("buildin_%s: Nonexistant item %s requested.\n", script->getfuncname(st), name); return false; //No item created. } nameid=item_data->nameid; @@ -5830,11 +5844,11 @@ BUILDIN(getitem) flag = 1; } if( nameid <= 0 || !(item_data = itemdb->exists(nameid)) ){ - ShowError("buildin_getitem: Nonexistant item %d requested.\n", nameid); + ShowError("buildin_%s: Nonexistant item %d requested.\n", script->getfuncname(st), nameid); return false; //No item created. } } else { - ShowError("buildin_getitem: invalid data type for argument #1 (%d).", data->type); + ShowError("buildin_%s: invalid data type for argument #1 (%d).", script->getfuncname(st), data->type); return false; } @@ -5844,13 +5858,28 @@ BUILDIN(getitem) memset(&it,0,sizeof(it)); it.nameid=nameid; + if(!flag) it.identify=1; else it.identify=itemdb->isidentified2(item_data); - if( script_hasdata(st,4) ) - sd=map->id2sd(script_getnum(st,4)); // <Account ID> + if( !strcmp(script->getfuncname(st),"getitembound") ) { + int bound = script_getnum(st,4); + if( bound < IBT_MIN || bound > IBT_MAX ) { //Not a correct bound type + ShowError("script_getitembound: Not a correct bound type! Type=%d\n",bound); + return false; + } + if( item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR ) { + ShowError("script_getitembound: can't bind a pet egg/armor!\n",bound); + return false; + } + it.bound = (unsigned char)bound; + offset += 1; + } + + if( script_hasdata(st,4+offset) ) + sd=map->id2sd(script_getnum(st,4+offset)); // <Account ID> else sd=script->rid2sd(st); // Attached player @@ -5882,15 +5911,24 @@ BUILDIN(getitem) *------------------------------------------*/ BUILDIN(getitem2) { - int nameid,amount,get_count,i,flag = 0; - int iden,ref,attr,c1,c2,c3,c4; + int nameid,amount,get_count,i,flag = 0, offset = 0; + int iden,ref,attr,c1,c2,c3,c4, bound = 0; struct item_data *item_data; struct item item_tmp; TBL_PC *sd; struct script_data *data; - if( script_hasdata(st,11) ) - sd=map->id2sd(script_getnum(st,11)); // <Account ID> + if( !strcmp(script->getfuncname(st),"getitembound2") ) { + bound = script_getnum(st,11); + if( bound < IBT_MIN || bound > IBT_MAX ) { //Not a correct bound type + ShowError("script_getitembound2: Not a correct bound type! Type=%d\n",bound); + return false; + } + offset += 1; + } + + if( script_hasdata(st,11+offset) ) + sd=map->id2sd(script_getnum(st,11+offset)); // <Account ID> else sd=script->rid2sd(st); // Attached player @@ -5918,6 +5956,11 @@ BUILDIN(getitem2) c3=(short)script_getnum(st,9); c4=(short)script_getnum(st,10); + if( bound && (itemdb_type(nameid) == IT_PETEGG || itemdb_type(nameid) == IT_PETARMOR) ) { + ShowError("script_getitembound2: can't bind a pet egg/armor!\n",bound); + return false; + } + if(nameid<0) { // Invalide nameid nameid = -nameid; flag = 1; @@ -5947,6 +5990,7 @@ BUILDIN(getitem2) item_tmp.identify=0; item_tmp.refine=ref; item_tmp.attribute=attr; + item_tmp.bound=(unsigned char)bound; item_tmp.card[0]=(short)c1; item_tmp.card[1]=(short)c2; item_tmp.card[2]=(short)c3; @@ -6022,6 +6066,7 @@ BUILDIN(rentitem) it.nameid = nameid; it.identify = 1; it.expire_time = (unsigned int)(time(NULL) + seconds); + it.bound = 0; if( (flag = pc->additem(sd, &it, 1, LOG_TYPE_SCRIPT)) ) { @@ -10919,6 +10964,7 @@ BUILDIN(successremovecards) { item_tmp.refine = sd->status.inventory[i].refine; item_tmp.attribute = sd->status.inventory[i].attribute; item_tmp.expire_time = sd->status.inventory[i].expire_time; + item_tmp.bound = sd->status.inventory[i].bound; for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) item_tmp.card[j]=sd->status.inventory[i].card[j]; @@ -10992,7 +11038,8 @@ BUILDIN(failedremovecards) { item_tmp.refine = sd->status.inventory[i].refine; item_tmp.attribute = sd->status.inventory[i].attribute; item_tmp.expire_time = sd->status.inventory[i].expire_time; - + item_tmp.bound = sd->status.inventory[i].bound; + for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) item_tmp.card[j]=sd->status.inventory[i].card[j]; @@ -11642,7 +11689,8 @@ BUILDIN(getinventorylist) pc->setreg(sd,reference_uid(script->add_str(card_var), j),sd->status.inventory[i].card[k]); } pc->setreg(sd,reference_uid(script->add_str("@inventorylist_expire"), j),sd->status.inventory[i].expire_time); - j++; + pc->setreg(sd,reference_uid(script->add_str("@inventorylist_bound"), j),sd->status.inventory[i].bound); + j++; } } pc->setreg(sd,script->add_str("@inventorylist_count"),j); @@ -17598,6 +17646,41 @@ BUILDIN(bg_join_team) { return true; } +/*==============[Mhalicot]================== + * countbound {<type>}; + * Creates an array of bounded item IDs + * Returns amount of items found + * Type: + * 1 - Account Bound + * 2 - Guild Bound + * 3 - Party Bound + * 4 - Character Bound + *------------------------------------------*/ +BUILDIN(countbound) +{ + int i, type, j=0, k=0; + TBL_PC *sd; + + if( (sd = script->rid2sd(st)) == NULL ) + return false; + + type = script_hasdata(st,2)?script_getnum(st,2):0; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && ( + (!type && sd->status.inventory[i].bound > 0) || + (type && sd->status.inventory[i].bound == type) + )) { + pc->setreg(sd,reference_uid(script->add_str("@bound_items"), k),sd->status.inventory[i].nameid); + k++; + j += sd->status.inventory[i].amount; + } + } + + script_pushint(st,j); + return 0; +} + /* bg_match_over( arena_name {, optional canceled } ) */ /* returns 0 when successful, 1 otherwise */ BUILDIN(bg_match_over) { @@ -18179,6 +18262,13 @@ void script_parse_builtin(void) { BUILDIN_DEF(unbindatcmd, "s"), BUILDIN_DEF(useatcmd, "s"), + /** + * Item bound [Xantara] [Akinari] [Mhalicot/Hercules] + **/ + BUILDIN_DEF2(getitem,"getitembound","vii?"), + BUILDIN_DEF2(getitem2,"getitembound2","viiiiiiiii?"), + BUILDIN_DEF(countbound, "?"), + //Quest Log System [Inkfish] BUILDIN_DEF(questinfo, "ii??"), BUILDIN_DEF(setquest, "i"), @@ -18469,6 +18559,7 @@ void script_defaults(void) { script->buildin_mobuseskill_sub = buildin_mobuseskill_sub; script->cleanfloor_sub = script_cleanfloor_sub; script->run_func = run_func; + script->getfuncname = script_getfuncname; /* script_config base */ script->config.warn_func_mismatch_argtypes = 1; diff --git a/src/map/script.h b/src/map/script.h index 6aebc8a30..a846365dd 100644 --- a/src/map/script.h +++ b/src/map/script.h @@ -630,6 +630,7 @@ struct script_interface { int (*buildin_mobuseskill_sub) (struct block_list *bl, va_list ap); int (*cleanfloor_sub) (struct block_list *bl, va_list ap); int (*run_func) (struct script_state *st); + const char *(*getfuncname) (struct script_state *st); }; struct script_interface *script; diff --git a/src/map/storage.c b/src/map/storage.c index 45a80867d..db85b3c98 100644 --- a/src/map/storage.c +++ b/src/map/storage.c @@ -107,7 +107,8 @@ int compare_item(struct item *a, struct item *b) a->identify == b->identify && a->refine == b->refine && a->attribute == b->attribute && - a->expire_time == b->expire_time ) + a->expire_time == b->expire_time && + a->bound == b->bound ) { int i; for (i = 0; i < MAX_SLOTS && (a->card[i] == b->card[i]); i++); @@ -140,6 +141,11 @@ int storage_additem(struct map_session_data* sd, struct item* item_data, int amo return 1; } + if( item_data->bound > IBT_ACCOUNT && !pc->can_give_bound_items(sd) ) { + clif->message(sd->fd, msg_txt(294)); + return 1; + } + if( itemdb->isstackable2(data) ) {//Stackable for( i = 0; i < MAX_STORAGE; i++ ) @@ -430,11 +436,16 @@ int guild_storage_additem(struct map_session_data* sd, struct guild_storage* sto } if( !itemdb_canguildstore(item_data, pc->get_group_level(sd)) || item_data->expire_time ) - { //Check if item is storable. [Skotlex] + { //Check if item is storable. [Skotlex] clif->message (sd->fd, msg_txt(264)); return 1; } + if( item_data->bound && item_data->bound != IBT_GUILD && !pc->can_give_bound_items(sd) ) { + clif->message(sd->fd, msg_txt(294)); + return 1; + } + if(itemdb->isstackable2(data)){ //Stackable for(i=0;i<MAX_GUILD_STORAGE;i++){ if(compare_item(&stor->items[i], item_data)) { @@ -520,6 +531,8 @@ int storage_guild_storageadd(struct map_session_data* sd, int index, int amount) if(gstorage->additem(sd,stor,&sd->status.inventory[index],amount)==0) pc->delitem(sd,index,amount,0,4,LOG_TYPE_GSTORAGE); + else + clif->dropitem(sd, index,0); return 1; } diff --git a/src/map/trade.c b/src/map/trade.c index 8dd30371b..ffd1336f5 100644 --- a/src/map/trade.c +++ b/src/map/trade.c @@ -335,7 +335,7 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount) if( amount == 0 ) { //Why do this.. ~.~ just send an ack, the item won't display on the trade window. - clif->tradeitemok(sd, index, 0); + clif->tradeitemok(sd, index, TIO_SUCCESS); return; } @@ -354,29 +354,38 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount) (pc->get_partner(sd) != target_sd || !itemdb_canpartnertrade(item, src_lv, dst_lv)) ) //Can't partner-trade { clif->message (sd->fd, msg_txt(260)); - clif->tradeitemok(sd, index+2, 1); + clif->tradeitemok(sd, index+2, TIO_INDROCKS); return; } if( item->expire_time ) { // Rental System clif->message (sd->fd, msg_txt(260)); - clif->tradeitemok(sd, index+2, 1); + clif->tradeitemok(sd, index+2, TIO_INDROCKS); return; } + if( item->bound && + !( item->bound == IBT_GUILD && sd->status.guild_id == target_sd->status.guild_id ) && + !( item->bound == IBT_PARTY && sd->status.party_id == target_sd->status.party_id ) + && !pc->can_give_bound_items(sd) ) { + clif->message(sd->fd, msg_txt(293)); + clif->tradeitemok(sd, index+2, TIO_INDROCKS); + return; + } + //Locate a trade position ARR_FIND( 0, 10, trade_i, sd->deal.item[trade_i].index == index || sd->deal.item[trade_i].amount == 0 ); if( trade_i == 10 ) //No space left { - clif->tradeitemok(sd, index+2, 1); + clif->tradeitemok(sd, index+2, TIO_OVERWEIGHT); return; } trade_weight = sd->inventory_data[index]->weight * amount; if( target_sd->weight + sd->deal.weight + trade_weight > target_sd->max_weight ) { //fail to add item -- the player was over weighted. - clif->tradeitemok(sd, index+2, 1); + clif->tradeitemok(sd, index+2, TIO_OVERWEIGHT); return; } @@ -396,7 +405,7 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount) } sd->deal.weight += trade_weight; - clif->tradeitemok(sd, index+2, 0); // Return the index as it was received + clif->tradeitemok(sd, index+2, TIO_SUCCESS); // Return the index as it was received clif->tradeadditem(sd, target_sd, index+2, amount); } @@ -440,7 +449,7 @@ void trade_tradeok(struct map_session_data *sd) { return; } sd->state.deal_locked = 1; - clif->tradeitemok(sd, 0, 0); + clif->tradeitemok(sd, 0, TIO_SUCCESS); clif->tradedeal_lock(sd, 0); clif->tradedeal_lock(target_sd, 1); } diff --git a/src/map/vending.c b/src/map/vending.c index c7a2dfa68..f16a208e4 100644 --- a/src/map/vending.c +++ b/src/map/vending.c @@ -257,7 +257,8 @@ void vending_openvending(struct map_session_data* sd, const char* message, const || !sd->status.cart[index].identify // unidentified item || sd->status.cart[index].attribute == 1 // broken item || sd->status.cart[index].expire_time // It should not be in the cart but just in case - || !itemdb_cantrade(&sd->status.cart[index], pc->get_group_level(sd), pc->get_group_level(sd)) ) // untradeable item + || (sd->status.cart[index].bound && !pc->can_give_bound_items(sd)) // can't trade bound items w/o permission + || !itemdb_cantrade(&sd->status.cart[index], pc->get_group_level(sd), pc->get_group_level(sd)) ) // untradeable item continue; sd->vending[i].index = index; |