unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount) { if( pc_istrading(sd) ) return 1; if( idx == 0 ) { // Zeny Transfer if( amount < 0 || !pc_can_give_items(sd) ) return 1; if( amount > sd->status.zeny ) amount = sd->status.zeny; sd->mail.zeny = amount; // clif->updatestatus(sd, SP_ZENY); return 0; } else { // Item Transfer idx -= 2; mail->removeitem(sd, 0); if( idx < 0 || idx >= MAX_INVENTORY ) return 1; 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)) || (sd->status.inventory[idx].bound && !pc_can_give_bound_items(sd)) ) return 1; sd->mail.index = idx; sd->mail.nameid = sd->status.inventory[idx].nameid; sd->mail.amount = amount; return 0; } }
/*========================================== * Abrir loja * 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; // nao pode abrir vendas deitado morto || não usou via habilidade (wpe/hack) || não pode ter 2 lojas ao mesmo tempo vending_skill_lvl = pc->checkskill(sd, MC_VENDING); // check de nivel de habilidade e carrinho if( !vending_skill_lvl || !pc_iscarton(sd) ) { clif->skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return; } // checka numero de itens na loja if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) { // contagem de item invalida clif->skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return; } // filtrar itens invalidos 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; // ajuste de equilibrio (cliente diz que a posição do primeiro carrinho é 2) if( index < 0 || index >= MAX_CART // posição inválida || pc->cartitem_amount(sd, index, amount) < 0 // item invalido ou quantidade insuficiente //NOT: servidores oficiais não fazem nenhum dos checks abaixo! || !sd->status.cart[index].identify // item não-identficado || sd->status.cart[index].attribute == 1 // item quebrado || sd->status.cart[index].expire_time // Isso não deveria estar no carrinho mas apenas no caso de estar || (sd->status.cart[index].bound && !pc_can_give_bound_items(sd)) // não pode trocar itens de recompensa, permissão w/o || !itemdb_cantrade(&sd->status.cart[index], pc_get_group_level(sd), pc_get_group_level(sd)) ) // Itens não-trocaveis 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 adicionado com sucesso } if( i != j ) clif->message (sd->fd, msg_txt(266)); //"Alguns dos seus itens não pode ser vendido e foram removidos da loja." if( i == 0 ) { // nenhum item válido encontrado clif->skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); // packet de resposta personalizada 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->status.char_id, sd); }
/*========================================== * 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 || (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; sd->vending[i].amount = amount; #if VERSION == -1 sd->vending[i].value = cap_value(value, 0, (unsigned int)battle_config.vending_max_value_ot); #else sd->vending[i].value = cap_value(value, 0, (unsigned int)battle_config.vending_max_value); #endif 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.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->status.char_id, sd); }
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->message(sd->fd, msg_sd(sd,246)); // Your GM level doesn't authorize you to perform this action. 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("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_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, DELITEM_NORMAL, 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 *------------------------------------------*/ 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->cancel(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, TIO_SUCCESS); 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_get_group_level(sd); dst_lv = pc_get_group_level(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->message (sd->fd, msg_txt(260)); 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, 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, 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, TIO_OVERWEIGHT); 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, TIO_SUCCESS); // Return the index as it was received clif->tradeadditem(sd, target_sd, index+2, amount); }
/// Attaches an item to a message being written /// @param sd : The player who's writting /// @param idx : the inventory idx of the item /// @param amount : Amount of the item to be attached void rodex_add_item(struct map_session_data *sd, int16 idx, int16 amount) { int i; bool is_stack = false; nullpo_retv(sd); if (idx < 0 || idx >= MAX_INVENTORY) { clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_FATAL_ERROR); return; } if (amount < 0 || amount > sd->status.inventory[idx].amount) { clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_FATAL_ERROR); return; } if (!pc_can_give_items(sd) || sd->status.inventory[idx].expire_time || !itemdb_canmail(&sd->status.inventory[idx], pc_get_group_level(sd)) || (sd->status.inventory[idx].bound && !pc_can_give_bound_items(sd))) { clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_NOT_TRADEABLE); return; } if (itemdb->isstackable(sd->status.inventory[idx].nameid) == 1) { for (i = 0; i < RODEX_MAX_ITEM; ++i) { if (sd->rodex.tmp.items[i].idx == idx) { if (sd->status.inventory[idx].nameid == sd->rodex.tmp.items[i].item.nameid && sd->status.inventory[idx].unique_id == sd->rodex.tmp.items[i].item.unique_id) { is_stack = true; break; } } } if (i == RODEX_MAX_ITEM && sd->rodex.tmp.items_count < RODEX_MAX_ITEM) { ARR_FIND(0, RODEX_MAX_ITEM, i, sd->rodex.tmp.items[i].idx == 0); } } else if (sd->rodex.tmp.items_count < RODEX_MAX_ITEM) { ARR_FIND(0, RODEX_MAX_ITEM, i, sd->rodex.tmp.items[i].idx == 0); } else { i = RODEX_MAX_ITEM; } if (i == RODEX_MAX_ITEM) { clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_NO_SPACE); return; } if (sd->rodex.tmp.items[i].item.amount + amount > sd->status.inventory[idx].amount) { clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_FATAL_ERROR); return; } if (sd->rodex.tmp.weight + sd->inventory_data[idx]->weight * amount > RODEX_WEIGHT_LIMIT) { clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_FATAL_ERROR); return; } sd->rodex.tmp.items[i].idx = idx; sd->rodex.tmp.weight += sd->inventory_data[idx]->weight * amount; if (is_stack == false) { sd->rodex.tmp.items[i].item = sd->status.inventory[idx]; sd->rodex.tmp.items[i].item.amount = amount; sd->rodex.tmp.items_count++; } else { sd->rodex.tmp.items[i].item.amount += amount; } sd->rodex.tmp.type |= MAIL_TYPE_ITEM; clif->rodex_add_item_result(sd, idx, amount, RODEX_ADD_ITEM_SUCCESS); }