/*========================================== * Adds an item/qty to the trade window *------------------------------------------*/ void trade_tradeadditem(struct map_session_data *sd, short index, short amount) { struct map_session_data *target_sd; struct item *item; int trade_i, trade_weight; int src_lv, dst_lv; nullpo_retv(sd); if( !sd->state.trading || sd->state.deal_locked > 0 ) return; //Can't add stuff. if( (target_sd = map_id2sd(sd->trade_partner)) == NULL ) { trade_tradecancel(sd); return; } if( amount == 0 ) { //Why do this.. ~.~ just send an ack, the item won't display on the trade window. clif_tradeitemok(sd, index, 0); return; } index -= 2; // 0 is for zeny, 1 is unknown. Gravity, go figure... //Item checks... if( index < 0 || index >= MAX_INVENTORY ) return; if( amount < 0 || amount > sd->status.inventory[index].amount ) return; item = &sd->status.inventory[index]; src_lv = pc_isGM(sd); dst_lv = pc_isGM(target_sd); if( !itemdb_cantrade(item, src_lv, dst_lv) && //Can't trade (pc_get_partner(sd) != target_sd || !itemdb_canpartnertrade(item, src_lv, dst_lv)) ) //Can't partner-trade { clif_displaymessage (sd->fd, msg_txt(260)); clif_tradeitemok(sd, index+2, 1); 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); 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); return; } if( sd->deal.item[trade_i].index == index ) { //The same item as before is being readjusted. if( sd->deal.item[trade_i].amount + amount > sd->status.inventory[index].amount ) { //packet deal exploit check amount = sd->status.inventory[index].amount - sd->deal.item[trade_i].amount; trade_weight = sd->inventory_data[index]->weight * amount; } sd->deal.item[trade_i].amount += amount; } else { //New deal item sd->deal.item[trade_i].index = index; sd->deal.item[trade_i].amount = amount; } sd->deal.weight += trade_weight; clif_tradeitemok(sd, index+2, 0); // Return the index as it was received clif_tradeadditem(sd, target_sd, index+2, amount); }
/*========================================== * Adds an item/qty to the trade window *------------------------------------------*/ void trade_tradeadditem(struct map_session_data *sd, short index, short amount) { struct map_session_data *target_sd; struct item *item; struct homun_data *hd; char atcmd_output[CHAT_SIZE_MAX]; int trade_i, trade_weight; int src_lv, dst_lv; int i, p, tid, amttsd = 0, amtt = 0; hd = sd->hd; nullpo_retv(sd); if( !sd->state.trading || sd->state.deal_locked > 0 ) return; //Can't add stuff. if( (target_sd = map_id2sd(sd->trade_partner)) == NULL ) { trade_tradecancel(sd); return; } if( amount == 0 ) { //Why do this.. ~.~ just send an ack, the item won't display on the trade window. clif_tradeitemok(sd, index, 0); return; } index -= 2; // 0 is for zeny, 1 is unknown. Gravity, go figure... //Item checks... if( index < 0 || index >= MAX_INVENTORY ) return; if( amount < 0 || amount > sd->status.inventory[index].amount ) return; item = &sd->status.inventory[index]; src_lv = pc_isGM(sd); dst_lv = pc_isGM(target_sd); if( !itemdb_cantrade(item, src_lv, dst_lv) && //Can't trade (pc_get_partner(sd) != target_sd || !itemdb_canpartnertrade(item, src_lv, dst_lv)) ) //Can't partner-trade { clif_displaymessage (sd->fd, msg_txt(260)); clif_tradeitemok(sd, index+2, 1); return; } if( item->expire_time ) { // Rental System clif_displaymessage (sd->fd, msg_txt(260)); clif_tradeitemok(sd, index+2, 1); 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); return; } //-------------| Bloquear Donates dias de semana |-------------// by: [Brunno Thadeu] //Abaixo, defini-se os dias (Falta testar isso, creio eu que 1 seja domingo, 2 segunda etc..) // if(t->tm_wday != 1 && t->tm_wday != 7){ //Abaixo, defini-se o range de ID's dos Donates. // if(sd->status.inventory[index].nameid >= 5000 && sd->status.inventory[index].nameid <= 5500) { // clif_tradeitemok(sd, index+2, 1); // return; // } // } //--------------------------------------------------------------// if(sd->status.inventory[index].nameid == 690) return ; if(sd->status.inventory[index].nameid >= 8031 && sd->status.inventory[index].nameid <= 8181) { for(i = 8031; i <= 8181; i++) amttsd += pc_countitem(target_sd, i); for(i = 0; i < 10; i++) { tid = sd->status.inventory[sd->deal.item[i].index].nameid; if(tid >= 8031 && tid <= 8181) amtt += sd->deal.item[i].amount; } p = 6; if((hd = target_sd->hd) == NULL ){ p = 7; } if((amttsd + amount + amtt) >= p) { clif_tradeitemok(sd, index+2, 1); return; } } if(sd->status.inventory[index].nameid >= 8031 && sd->status.inventory[index].nameid <= 8181){ snprintf(atcmd_output, sizeof(atcmd_output) ,"Pokemon ID: %d ", MakeDWord(sd->status.inventory[index].card[1], sd->status.inventory[index].card[2])); clif_displaymessage(target_sd->fd, atcmd_output); } 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); return; } if( sd->deal.item[trade_i].index == index ) { //The same item as before is being readjusted. if( sd->deal.item[trade_i].amount + amount > sd->status.inventory[index].amount ) { //packet deal exploit check amount = sd->status.inventory[index].amount - sd->deal.item[trade_i].amount; trade_weight = sd->inventory_data[index]->weight * amount; } sd->deal.item[trade_i].amount += amount; } else { //New deal item sd->deal.item[trade_i].index = index; sd->deal.item[trade_i].amount = amount; } sd->deal.weight += trade_weight; clif_tradeitemok(sd, index+2, 0); // Return the index as it was received clif_tradeadditem(sd, target_sd, index+2, amount); }
/*========================================== * Open shop * data := {<index>.w <amount>.w <value>.l}[count] *------------------------------------------*/ void vending_openvending(struct map_session_data* sd, const char* message, bool flag, const uint8* data, int count) { int i, j, char_id; int vending_skill_lvl; nullpo_retv(sd); if( !flag ) // cancelled return; // nothing to do if (pc_istrading(sd)) return; // can't have 2 shops at once vending_skill_lvl = pc_checkskill(sd, MC_VENDING); // skill level and cart check if( !vending_skill_lvl || !pc_iscarton(sd) ) { clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return; } // check number of items in shop if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) { // invalid item count clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return; } // filter out invalid items i = 0; for( j = 0; j < count; j++ ) { short index = *(uint16*)(data + 8*j + 0); short amount = *(uint16*)(data + 8*j + 2); unsigned int value = *(uint32*)(data + 8*j + 4); index -= 2; // offset adjustment (client says that the first cart position is 2) if( index < 0 || index >= MAX_CART // invalid position || pc_cartitem_amount(sd, index, amount) < 0 // invalid item or insufficient quantity //NOTE: official server does not do any of the following checks! || !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 || sd->status.cart[index].bound // Can't Trade Account bound items || ( sd->status.cart[index].card[0] == CARD0_CREATE && (char_id = MakeDWord(sd->status.cart[index].card[2],sd->status.cart[index].card[3])) > 0 && ((battle_config.bg_reserved_char_id && char_id == battle_config.bg_reserved_char_id) || (battle_config.ancient_reserved_char_id && char_id == battle_config.ancient_reserved_char_id)) ) || !itemdb_cantrade(&sd->status.cart[index], pc_isGM(sd), pc_isGM(sd)) ) // untradeable item continue; sd->vending[i].index = index; sd->vending[i].amount = amount; sd->vending[i].value = cap_value(value, 0, (unsigned int)battle_config.vending_max_value); i++; // item successfully added } if( i != j ) clif_displaymessage (sd->fd, msg_txt(266)); //"Some of your items cannot be vended and were removed from the shop." if( i == 0 ) { // no valid item found clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); // custom reply packet return; } sd->state.vending = true; sd->vender_id = vending_getuid(); sd->vend_num = i; safestrncpy(sd->message, message, MESSAGE_SIZE); pc_stop_walking(sd,1); clif_openvending(sd,sd->bl.id,sd->vending); clif_showvendingboard(&sd->bl,message,0); if( battle_config.channel_announces&16 && server_channel[CHN_VENDING] ) { char chat_message[256]; sprintf(chat_message, msg_txt(820), server_channel[CHN_VENDING]->name, sd->status.name, sd->message, map[sd->bl.m].name, sd->bl.x, sd->bl.y); clif_channel_message(server_channel[CHN_VENDING], chat_message, 27); } if( map[sd->bl.m].flag.vending_cell ) map_setcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_NOBOARDS, false); }
void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count) { int zeny = 0; unsigned int i, weight, listidx, k; struct map_session_data* pl_sd; if( count == 0 ) {// nothing to do return; } if( !battle_config.feature_buying_store || pc_istrading(sd) ) {// not allowed to sell clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( !pc_can_give_items(sd) ) {// custom: GM is not allowed to sell clif->message(sd->fd, msg_txt(246)); clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( ( pl_sd = map->id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id ) { // not online, not buying or not same store clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( !searchstore->queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) ) {// out of view range clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } searchstore->clearremote(sd); if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit ) {// buyer lost zeny in the mean time? fix the limit pl_sd->buyingstore.zenylimit = pl_sd->status.zeny; } weight = pl_sd->weight; // check item list for( i = 0; i < count; i++ ) {// itemlist: <index>.W <name id>.W <amount>.W unsigned short nameid, amount; int index; index = RBUFW(itemlist,i*6+0)-2; nameid = RBUFW(itemlist,i*6+2); amount = RBUFW(itemlist,i*6+4); if( i ) {// duplicate check. as the client does this too, only malicious intent should be caught here ARR_FIND( 0, i, k, RBUFW(itemlist,k*6+0)-2 == index ); if( k != i ) {// duplicate ShowWarning("Encontrado item duplicado na lista de venda!! (ANT.ID: %hu | ANT.QNT: %hu |ID: %hu | QNT: %hu | AID: %d | CID: %d).\n", RBUFW(itemlist,k*6+2), RBUFW(itemlist,k*6+4), nameid, amount, sd->status.account_id, sd->status.char_id); clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } } if( index < 0 || index >= ARRAYLENGTH(sd->status.inventory) || sd->inventory_data[index] == NULL || sd->status.inventory[index].nameid != nameid || sd->status.inventory[index].amount < amount ) {// invalid input clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } 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; } ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 ) {// there is no such item or the buyer has already bought all of them clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } if( pl_sd->buyingstore.items[listidx].amount < amount ) {// buyer does not need that much of the item clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid); return; } if( pc->checkadditem(pl_sd, nameid, amount) == ADDITEM_OVERAMOUNT ) {// buyer does not have enough space for this item clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } if( amount*(unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight-weight ) {// normally this is not supposed to happen, as the total weight is // checked upon creation, but the buyer could have gained items clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } weight+= amount*sd->inventory_data[index]->weight; if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny ) {// buyer does not have enough zeny clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid); return; } zeny+= amount*pl_sd->buyingstore.items[listidx].price; } // process item list for( i = 0; i < count; i++ ) {// itemlist: <index>.W <name id>.W <amount>.W unsigned short nameid, amount; int index; index = RBUFW(itemlist,i*6+0)-2; nameid = RBUFW(itemlist,i*6+2); amount = RBUFW(itemlist,i*6+4); ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); zeny = amount*pl_sd->buyingstore.items[listidx].price; // move item pc->additem(pl_sd, &sd->status.inventory[index], amount, LOG_TYPE_BUYING_STORE); pc->delitem(sd, index, amount, 1, 0, LOG_TYPE_BUYING_STORE); pl_sd->buyingstore.items[listidx].amount-= amount; // pay up pc->payzeny(pl_sd, zeny, LOG_TYPE_BUYING_STORE, sd); pc->getzeny(sd, zeny, LOG_TYPE_BUYING_STORE, pl_sd); pl_sd->buyingstore.zenylimit-= zeny; // notify clients clif->buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price); clif->buyingstore_update_item(pl_sd, nameid, amount); } if( map->save_settings&128 ) { chrif->save(sd, 0); chrif->save(pl_sd, 0); } // check whether or not there is still something to buy ARR_FIND( 0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0 ); if( i == pl_sd->buyingstore.slots ) {// everything was bought clif->buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS); } else if( pl_sd->buyingstore.zenylimit == 0 ) {// zeny limit reached clif->buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY); } else {// continue buying return; } // cannot continue buying buyingstore_close(pl_sd); // remove auto-trader if( pl_sd->state.autotrade ) { map->quit(pl_sd); } }
/*========================================== * Adds an item/qty to the trade window [rewrite by Skotlex] *------------------------------------------ */ void trade_tradeadditem(struct map_session_data *sd, int index, int amount) { struct map_session_data *target_sd; int trade_i, trade_weight, nameid; nullpo_retv(sd); if (!sd->state.trading || (target_sd = map_id2sd(sd->trade_partner)) == NULL || sd->state.deal_locked > 0) return; //Can't add stuff. if (index == 0) { //Adding Zeny if (amount >= 0 && amount <= MAX_ZENY && amount <= sd->status.zeny && // check amount (target_sd->status.zeny + amount) <= MAX_ZENY) // fix positiv overflow { //Check Ok sd->deal.zeny = amount; clif_tradeadditem(sd, target_sd, 0, amount); } else //Cancel Transaction trade_tradecancel(sd); return; } //Add an Item index = index -2; //Why the actual index used is -2? //Item checks... if (index < 0 || index > MAX_INVENTORY) return; if (amount < 0 || amount > sd->status.inventory[index].amount) return; nameid = sd->inventory_data[index]->nameid; if (!itemdb_cantrade(nameid, pc_isGM(sd), pc_isGM(target_sd)) && //Can't trade (pc_get_partner(sd) != target_sd || !itemdb_canpartnertrade(nameid, pc_isGM(sd), pc_isGM(target_sd)))) //Can't partner-trade { clif_displaymessage (sd->fd, msg_txt(260)); return; } for(trade_i = 0; trade_i < 10; trade_i++) { //Locate a trade position if (sd->deal.item[trade_i].index == index || sd->deal.item[trade_i].amount == 0) break; } if (trade_i >= 10) //No space left 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, 1); return; } if (sd->deal.item[trade_i].index == index) { //The same item as before is being readjusted. if (sd->deal.item[trade_i].amount + amount > sd->status.inventory[index].amount) { //packet deal exploit check amount = sd->status.inventory[index].amount - sd->deal.item[trade_i].amount; trade_weight = sd->inventory_data[index]->weight * amount; } sd->deal.item[trade_i].amount += amount; } else { //New deal item sd->deal.item[trade_i].index = index; sd->deal.item[trade_i].amount = amount; } sd->deal.weight += trade_weight; if (impossible_trade_check(sd)) { // check exploit (trade more items that you have) trade_tradecancel(sd); return; } clif_tradeitemok(sd, index+2, 0); // Return the index as it was received clif_tradeadditem(sd, target_sd, index+2, amount); //index fix }
/*========================================== * Open shop * data := {<index>.w <amount>.w <value>.l}[count] *------------------------------------------*/ void vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count) { int i, j; int vending_skill_lvl; nullpo_retv(sd); if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd)) return; // can't open vendings lying dead || didn't use via the skill (wpe/hack) || can't have 2 shops at once vending_skill_lvl = pc->checkskill(sd, MC_VENDING); // skill level and cart check if( !vending_skill_lvl || !pc_iscarton(sd) ) { clif->skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return; } // check number of items in shop if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) { // invalid item count clif->skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return; } // filter out invalid items i = 0; for( j = 0; j < count; j++ ) { short index = *(uint16*)(data + 8*j + 0); short amount = *(uint16*)(data + 8*j + 2); unsigned int value = *(uint32*)(data + 8*j + 4); index -= 2; // offset adjustment (client says that the first cart position is 2) if( index < 0 || index >= MAX_CART // invalid position || pc->cartitem_amount(sd, index, amount) < 0 // invalid item or insufficient quantity //NOTE: official server does not do any of the following checks! || !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 continue; sd->vending[i].index = index; sd->vending[i].amount = amount; sd->vending[i].value = cap_value(value, 0, (unsigned int)battle_config.vending_max_value); i++; // item successfully added } if( i != j ) clif->message (sd->fd, msg_txt(266)); //"Some of your items cannot be vended and were removed from the shop." if( i == 0 ) { // no valid item found clif->skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); // custom reply packet return; } sd->state.prevend = sd->state.workinprogress = 0; sd->state.vending = true; sd->vender_id = getid(); sd->vend_num = i; safestrncpy(sd->message, message, MESSAGE_SIZE); clif->openvending(sd,sd->bl.id,sd->vending); clif->showvendingboard(&sd->bl,message,0); idb_put(vending->db, sd->vender_id, sd); }
/** * Player setup a new shop * @param sd : player opening the shop * @param message : shop title * @param data : itemlist data * data := {<index>.w <amount>.w <value>.l}[count] * @param count : number of different items * @param at Autotrader info, or NULL if requetsed not from autotrade persistance * @return 0 If success, 1 - Cannot open (die, not state.prevend, trading), 2 - No cart, 3 - Count issue, 4 - No valid item found */ int8 vending_openvending(struct map_session_data *sd, const char *message, const uint8 *data, int count, struct s_autotrader *at) { int i, j; int vending_skill_lvl; char message_sql[MESSAGE_SIZE * 2]; StringBuf buf; nullpo_retr(1, sd); if( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd) ) return 1; //Can't open vendings lying dead || didn't use via the skill (wpe/hack) || can't have 2 shops at once vending_skill_lvl = pc_checkskill(sd, MC_VENDING); //Skill level and cart check if( !vending_skill_lvl || !pc_iscarton(sd) ) { clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0, 0); return 2; } //Check number of items in shop if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) { //Invalid item count clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0, 0); return 3; } if( save_settings&CHARSAVE_VENDING ) // Avoid invalid data from saving chrif_save(sd, CSAVE_INVENTORY|CSAVE_CART); //Filter out invalid items i = 0; for( j = 0; j < count; j++ ) { short index = *(uint16 *)(data + 8 * j + 0); short amount = *(uint16 *)(data + 8 * j + 2); unsigned int value = *(uint32 *)(data + 8 * j + 4); index -= 2; //Offset adjustment (client says that the first cart position is 2) if( index < 0 || index >= MAX_CART || //Invalid position pc_cartitem_amount(sd, index, amount) < 0 || //Invalid item or insufficient quantity //NOTE: Official server does not do any of the following checks! !sd->cart.u.items_cart[index].identify || //Unidentified item sd->cart.u.items_cart[index].attribute || //Broken item sd->cart.u.items_cart[index].expire_time || //It should not be in the cart but just in case (sd->cart.u.items_cart[index].bound && !pc_can_give_bounded_items(sd)) || //Can't trade account bound items and has no permission !itemdb_cantrade(&sd->cart.u.items_cart[index], pc_get_group_level(sd), pc_get_group_level(sd)) ) //Untradeable item continue; sd->vending[i].index = index; sd->vending[i].amount = amount; sd->vending[i].value = umin(value, (unsigned int)battle_config.vending_max_value); i++; //Item successfully added } if( i != j ) clif_displaymessage(sd->fd, msg_txt(266)); // "Some of your items cannot be vended and were removed from the shop." if( i == 0 ) { //No valid item found clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0, 0); //Custom reply packet return 4; } sd->state.prevend = 0; sd->state.vending = 1; sd->vender_id = vending_getuid(); sd->vend_num = i; safestrncpy(sd->message, message, MESSAGE_SIZE); Sql_EscapeString(mmysql_handle, message_sql, sd->message); if( Sql_Query(mmysql_handle, "INSERT INTO `%s`(`id`,`account_id`,`char_id`,`sex`,`map`,`x`,`y`,`title`,`autotrade`,`body_direction`,`head_direction`,`sit`) VALUES(%d, %d, %d, '%c', '%s', %d, %d, '%s', %d, '%d', '%d', '%d');", vendings_db, sd->vender_id, sd->status.account_id, sd->status.char_id, (!sd->status.sex ? 'F' : 'M'), map[sd->bl.m].name, sd->bl.x, sd->bl.y, message_sql, sd->state.autotrade, (at ? at->dir : sd->ud.dir), (at ? at->head_dir : sd->head_dir), (at ? at->sit : pc_issit(sd))) != SQL_SUCCESS ) Sql_ShowDebug(mmysql_handle); StringBuf_Init(&buf); StringBuf_Printf(&buf, "INSERT INTO `%s`(`vending_id`,`index`,`cartinventory_id`,`amount`,`price`) VALUES", vending_items_db); for( j = 0; j < i; j++ ) { StringBuf_Printf(&buf, "(%d,%d,%d,%d,%d)", sd->vender_id, j, sd->cart.u.items_cart[sd->vending[j].index].id, sd->vending[j].amount, sd->vending[j].value); if( j < i - 1 ) StringBuf_AppendStr(&buf, ","); } if( SQL_ERROR == Sql_QueryStr(mmysql_handle, StringBuf_Value(&buf)) ) Sql_ShowDebug(mmysql_handle); StringBuf_Destroy(&buf); clif_openvending(sd, sd->bl.id, sd->vending); clif_showvendingboard(&sd->bl, message, 0); idb_put(vending_db, sd->status.char_id, sd); return 0; }
/** * Start transaction * @param sd Player/Seller * @param account_id Buyer account ID * @param *itemlist List of sold items { <index>.W, <nameid>.W, <amount>.W }* * @param count Number of item on the itemlist */ void buyingstore_trade(struct map_session_data *sd, int account_id, unsigned int buyer_id, const uint8 *itemlist, unsigned int count) { int zeny = 0; unsigned int i, weight, listidx, k; struct map_session_data *pl_sd; nullpo_retv(sd); if( count == 0 ) // Nothing to do return; if( !battle_config.feature_buying_store || pc_istrading(sd) ) { // Not allowed to sell clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( !pc_can_give_items(sd) ) { // Custom: GM is not allowed to sell clif_displaymessage(sd->fd, msg_txt(246)); clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } // Not online, not buying or not same store if( (pl_sd = map_id2sd(account_id)) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id ) { clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( !searchstore_queryremote(sd, account_id) && (sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE)) ) { // Out of view range clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } searchstore_clearremote(sd); if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit ) // Buyer lost zeny in the mean time? Fix the limit pl_sd->buyingstore.zenylimit = pl_sd->status.zeny; weight = pl_sd->weight; // Check item list for( i = 0; i < count; i++ ) { // itemlist: <index>.W <name id>.W <amount>.W unsigned short nameid, amount; int index; index = RBUFW(itemlist,i * 6 + 0) - 2; nameid = RBUFW(itemlist,i * 6 + 2); amount = RBUFW(itemlist,i * 6 + 4); if( i ) { // Duplicate check. as the client does this too, only malicious intent should be caught here ARR_FIND(0, i, k, RBUFW(itemlist,k * 6 + 0) - 2 == index); if( k != i ) { // Duplicate ShowWarning("buyingstore_trade: Found duplicate item on selling list (prevnameid=%hu, prevamount=%hu, nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", RBUFW(itemlist,k * 6 + 2), RBUFW(itemlist,k * 6 + 4), nameid, amount, sd->status.account_id, sd->status.char_id); clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } } if( index < 0 || index >= ARRAYLENGTH(sd->status.inventory) || sd->inventory_data[index] == NULL || sd->status.inventory[index].nameid != nameid || sd->status.inventory[index].amount < amount ) { // Invalid input clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } if( sd->status.inventory[index].expire_time || (sd->status.inventory[index].bound && !pc_can_give_bounded_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; } ARR_FIND(0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid); // There is no such item or the buyer has already bought all of them if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 ) { clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } // Buyer does not need that much of the item if( pl_sd->buyingstore.items[listidx].amount < amount ) { clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid); return; } // Buyer does not have enough space for this item if( pc_checkadditem(pl_sd, nameid, amount) == CHKADDITEM_OVERAMOUNT ) { clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } // Normally this is not supposed to happen, as the total weight is // checked upon creation, but the buyer could have gained items if( amount * (unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight - weight ) { clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } weight += amount * sd->inventory_data[index]->weight; // Buyer does not have enough zeny if( amount * pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit - zeny ) { clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid); return; } zeny += amount * pl_sd->buyingstore.items[listidx].price; } // Process item list for( i = 0; i < count; i++ ) { // itemlist: <index>.W <name id>.W <amount>.W unsigned short nameid, amount; int index; index = RBUFW(itemlist,i * 6 + 0) - 2; nameid = RBUFW(itemlist,i * 6 + 2); amount = RBUFW(itemlist,i * 6 + 4); ARR_FIND(0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid); zeny = amount * pl_sd->buyingstore.items[listidx].price; // Move item pc_additem(pl_sd, &sd->status.inventory[index], amount, LOG_TYPE_BUYING_STORE); pc_delitem(sd, index, amount, 1, 0, LOG_TYPE_BUYING_STORE); pl_sd->buyingstore.items[listidx].amount -= amount; if( pl_sd->buyingstore.items[listidx].amount > 0 ) { if( Sql_Query(mmysql_handle, "UPDATE `%s` SET `amount` = %d WHERE `buyingstore_id` = %d AND `index` = %d;", buyingstore_items_db, pl_sd->buyingstore.items[listidx].amount, pl_sd->buyer_id, listidx) != SQL_SUCCESS ) Sql_ShowDebug(mmysql_handle); } else { if( Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `buyingstore_id` = %d AND `index` = %d;", buyingstore_items_db, pl_sd->buyer_id, listidx) != SQL_SUCCESS ) Sql_ShowDebug(mmysql_handle); } // Pay up pc_payzeny(pl_sd, zeny, LOG_TYPE_BUYING_STORE, sd); pc_getzeny(sd, zeny, LOG_TYPE_BUYING_STORE, pl_sd); pl_sd->buyingstore.zenylimit -= zeny; // Notify clients clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price); clif_buyingstore_update_item(pl_sd, nameid, amount); } if( save_settings&128 ) { chrif_save(sd, 0); chrif_save(pl_sd, 0); } // Check whether or not there is still something to buy ARR_FIND(0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0); if( i == pl_sd->buyingstore.slots ) // Everything was bought clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS); else if( pl_sd->buyingstore.zenylimit == 0 ) // Zeny limit reached clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY); else { // Continue buying if( Sql_Query(mmysql_handle, "UPDATE `%s` SET `limit` = %d WHERE `id` = %d;", buyingstores_db, pl_sd->buyingstore.zenylimit, pl_sd->buyer_id) != SQL_SUCCESS ) Sql_ShowDebug(mmysql_handle); return; } // Cannot continue buying buyingstore_close(pl_sd); // Remove auto-trader if( pl_sd->state.autotrade ) map_quit(pl_sd); }
/*========================================== * Open shop * data := {<index>.w <amount>.w <value>.l}[count] *------------------------------------------*/ void vending_openvending(struct map_session_data* sd, const char* message, bool flag, const uint8* data, int count) { int i, j; int vending_skill_lvl; nullpo_retv(sd); if( !flag ) // cancelled return; // nothing to do if (pc_istrading(sd)) return; // can't have 2 shops at once vending_skill_lvl = pc_checkskill(sd, MC_VENDING); // skill level and cart check if( !vending_skill_lvl || !pc_iscarton(sd) ) { clif_skill_fail(sd, MC_VENDING, 0, 0); return; } // check number of items in shop if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) { // invalid item count clif_skill_fail(sd, MC_VENDING, 0, 0); return; } // filter out invalid items i = 0; for( j = 0; j < count; j++ ) { int index = *(uint16*)(data + 8*j + 0); unsigned int amount = *(uint16*)(data + 8*j + 2); unsigned int value = *(uint32*)(data + 8*j + 4); index -= 2; // offset adjustment (client says that the first cart position is 2) if( index < 0 || index >= MAX_CART // invalid position || pc_cartitem_amount(sd, index, amount) < 0 // invalid item or insufficient quantity //NOTE: official server does not do any of the following checks! || !sd->status.cart[index].identify // unidentified item || sd->status.cart[index].attribute == 1 // broken item || !itemdb_cantrade(&sd->status.cart[index], pc_isGM(sd), pc_isGM(sd)) ) // untradeable item continue; sd->vending[i].index = index; sd->vending[i].amount = amount; sd->vending[i].value = cap_value(value, 1, (unsigned int)battle_config.vending_max_value); i++; // item successfully added } if( i != j ) clif_displaymessage (sd->fd, msg_txt(266)); //"Some of your items cannot be vended and were removed from the shop." if( i == 0 ) { // no valid item found clif_skill_fail(sd, MC_VENDING, 0, 0); // custom reply packet return; } sd->vender_id = sd->bl.id; sd->vend_num = i; safestrncpy(sd->message, message, MESSAGE_SIZE); pc_stop_walking(sd,1); clif_openvending(sd,sd->vender_id,sd->vending); clif_showvendingboard(&sd->bl,message,0); }