/*========================================== * When quitting the game. *------------------------------------------*/ void storage_storage_quit(struct map_session_data* sd, int flag) { nullpo_retv(sd); if (save_settings&4) chrif_save(sd, flag); //Invokes the storage saving as well. sd->state.storage_flag = 0; }
/** * Make player close his storage * @author : [massdriller] / modified by [Valaris] * @param sd : player */ void storage_storageclose(struct map_session_data *sd) { nullpo_retv(sd); clif_storageclose(sd); if (save_settings&4) chrif_save(sd, 0); //Invokes the storage saving as well sd->state.storage_flag = 0; }
int quest_change(TBL_PC * sd, int qid1, int qid2) { int i, j; if( quest_check(sd, qid2, HAVEQUEST) >= 0 ) { ShowError("quest_change: Character %d already has quest %d.\n", sd->status.char_id, qid2); return -1; } if( quest_check(sd, qid1, HAVEQUEST) < 0 ) { ShowError("quest_change: Character %d doesn't have quest %d.\n", sd->status.char_id, qid1); return -1; } if( (j = quest_search_db(qid2)) < 0 ) { ShowError("quest_change: quest %d not found in DB.\n",qid2); return -1; } ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == qid1); if(i == sd->avail_quests) { ShowError("quest_change: Character %d has completed quests %d.\n", sd->status.char_id, qid1); return -1; } memset(&sd->quest_log[i], 0, sizeof(struct quest)); sd->quest_log[i].quest_id = quest_db[j].id; if( quest_db[j].time ) sd->quest_log[i].time = (unsigned int)(time(NULL) + quest_db[j].time); sd->quest_log[i].state = Q_ACTIVE; sd->quest_index[i] = j; sd->save_quest = true; clif_quest_delete(sd, qid1); clif_quest_add(sd, &sd->quest_log[i], sd->quest_index[i]); if( save_settings&64 ) chrif_save(sd,0); return 0; }
/** * Close storage for player then save it * @param sd : player */ void gstorage_storageclose(struct map_session_data *sd) { struct guild_storage *stor; nullpo_retv(sd); nullpo_retv(stor = gstorage_get_storage(sd->status.guild_id)); clif_storageclose(sd); if (stor->opened) { if (save_settings&4) chrif_save(sd, 0); //This one also saves the storage [Skotlex] else gstorage_storagesave(sd->status.account_id, sd->status.guild_id, 0); stor->opened = 0; } sd->state.storage_flag = 0; }
int quest_add(TBL_PC * sd, int quest_id) { int i, j; if( sd->num_quests >= MAX_QUEST_DB ) { ShowError("quest_add: Character %d has got all the quests.(max quests: %d)\n", sd->status.char_id, MAX_QUEST_DB); return 1; } if( quest_check(sd, quest_id, HAVEQUEST) >= 0 ) { ShowError("quest_add: Character %d already has quest %d.\n", sd->status.char_id, quest_id); return -1; } if( (j = quest_search_db(quest_id)) < 0 ) { ShowError("quest_add: quest %d not found in DB.\n", quest_id); return -1; } i = sd->avail_quests; memmove(&sd->quest_log[i+1], &sd->quest_log[i], sizeof(struct quest)*(sd->num_quests-sd->avail_quests)); memmove(sd->quest_index+i+1, sd->quest_index+i, sizeof(int)*(sd->num_quests-sd->avail_quests)); memset(&sd->quest_log[i], 0, sizeof(struct quest)); sd->quest_log[i].quest_id = quest_db[j].id; if( quest_db[j].time ) sd->quest_log[i].time = (unsigned int)(time(NULL) + quest_db[j].time); sd->quest_log[i].state = Q_ACTIVE; sd->quest_index[i] = j; sd->num_quests++; sd->avail_quests++; sd->save_quest = true; clif_quest_add(sd, &sd->quest_log[i], sd->quest_index[i]); if( save_settings&64 ) chrif_save(sd,0); return 0; }
/*========================================== * When quitting the game. *------------------------------------------ */ int storage_storage_quit(struct map_session_data *sd, int flag) { struct storage *stor; nullpo_retr(0, sd); nullpo_retr(0, stor=account2storage2(sd->status.account_id)); if (stor->storage_status) { if (save_settings&4) chrif_save(sd, flag); //Invokes the storage saving as well. else storage_storage_save(sd->status.account_id, flag); } stor->storage_status = 0; sd->state.storage_flag = 0; return 0; }
/** * Close storage for player then save it * @param sd : player */ void storage_guild_storageclose(struct map_session_data* sd) { struct s_storage *stor; nullpo_retv(sd); nullpo_retv(stor = guild2storage2(sd->status.guild_id)); clif_storageclose(sd); if (stor->status) { if (save_settings&CHARSAVE_STORAGE) chrif_save(sd, 0); //This one also saves the storage. [Skotlex] else storage_guild_storagesave(sd->status.account_id, sd->status.guild_id,0); stor->status = false; } sd->state.storage_flag = 0; }
/** * Begin hatching a pet. * @param sd : player requesting * @param pet : pet requesting */ int pet_birth_process(struct map_session_data *sd, struct s_pet *pet) { nullpo_retr(1, sd); Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->master == sd); if(sd->status.pet_id && pet->incubate == 1) { sd->status.pet_id = 0; return 1; } pet->incubate = 0; pet->account_id = sd->status.account_id; pet->char_id = sd->status.char_id; sd->status.pet_id = pet->pet_id; if(pet_data_init(sd, pet)) { sd->status.pet_id = 0; return 1; } intif_save_petdata(sd->status.account_id,pet); if (save_settings&8) chrif_save(sd,0); //is it REALLY Needed to save the char for hatching a pet? [Skotlex] if(sd->bl.prev != NULL) { if(map_addblock(&sd->pd->bl)) return 1; clif_spawn(&sd->pd->bl); clif_send_petdata(sd,sd->pd, 0,0); clif_send_petdata(sd,sd->pd, 5,battle_config.pet_hair_style); clif_pet_equip_area(sd->pd); clif_send_petstatus(sd); } Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->master == sd); return 0; }
/** * Adds a quest to the player's list. * New quest will be added as Q_ACTIVE. * @param sd : Player's data * @param quest_id : ID of the quest to add. * @return 0 in case of success, nonzero otherwise */ int quest_add(TBL_PC *sd, int quest_id) { int n; struct quest_db *qi = quest_db(quest_id); if( qi == &quest_dummy ) { ShowError("quest_add: quest %d not found in DB.\n", quest_id); return -1; } if( quest_check(sd, quest_id, HAVEQUEST) >= 0 ) { ShowError("quest_add: Character %d already has quest %d.\n", sd->status.char_id, quest_id); return -1; } n = sd->avail_quests; //Insertion point sd->num_quests++; sd->avail_quests++; RECREATE(sd->quest_log, struct quest, sd->num_quests); //The character has some completed quests, make room before them so that they will stay at the end of the array if( sd->avail_quests != sd->num_quests ) memmove(&sd->quest_log[n + 1], &sd->quest_log[n], sizeof(struct quest) * (sd->num_quests-sd->avail_quests)); memset(&sd->quest_log[n], 0, sizeof(struct quest)); sd->quest_log[n].quest_id = qi->id; if( qi->time ) sd->quest_log[n].time = (unsigned int)(time(NULL) + qi->time); sd->quest_log[n].state = Q_ACTIVE; sd->save_quest = true; clif_quest_add(sd, &sd->quest_log[n]); clif_quest_update_objective(sd, &sd->quest_log[n]); if( save_settings&CHARSAVE_QUEST ) chrif_save(sd,0); return 0; }
/*========================================== * 性別変化終了 (modified by Yor) *------------------------------------------ */ static void chrif_changedsex(Session *, const Packet_Fixed<0x2b0d>& fixed) { dumb_ptr<map_session_data> sd; AccountId acc = fixed.account_id; SEX sex = fixed.sex; if (battle_config.etc_log) PRINTF("chrif_changedsex %d.\n"_fmt, acc); sd = map_id2sd(account_to_block(acc)); if (acc) { if (sd != nullptr && sd->status.sex != sex) { if (sd->status.sex == SEX::MALE) sd->sex = sd->status.sex = SEX::FEMALE; else if (sd->status.sex == SEX::FEMALE) sd->sex = sd->status.sex = SEX::MALE; // to avoid any problem with equipment and invalid sex, equipment is unequiped. for (IOff0 i : IOff0::iter()) { if (sd->status.inventory[i].nameid && bool(sd->status.inventory[i].equip)) pc_unequipitem(sd, i, CalcStatus::NOW); } // save character chrif_save(sd); sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters // do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it) clif_displaymessage(sd->sess, "Your sex has been changed (need disconexion by the server)..."_s); clif_setwaitclose(sd->sess); // forced to disconnect for the change } } else { if (sd != nullptr) { PRINTF("chrif_changedsex failed.\n"_fmt); } } }
int pet_birth_process(struct map_session_data *sd, struct s_pet *pet) { char pet_output[1024]; // Declaracion de char para Invocacion Pet's [Tab] nullpo_retr(1, sd); Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->msd == sd); if(sd->status.pet_id && pet->incuvate == 1) { sd->status.pet_id = 0; return 1; } pet->incuvate = 0; pet->account_id = sd->status.account_id; pet->char_id = sd->status.char_id; sd->status.pet_id = pet->pet_id; if(pet_data_init(sd, pet)) { sd->status.pet_id = 0; return 1; } intif_save_petdata(sd->status.account_id,pet); if (save_settings&8) chrif_save(sd,0); //is it REALLY Needed to save the char for hatching a pet? [Skotlex] if(sd->bl.prev != NULL) { map_addblock(&sd->pd->bl); clif_spawn(&sd->pd->bl); clif_send_petdata(sd,sd->pd, 0,0); clif_send_petdata(sd,sd->pd, 5,battle_config.pet_hair_style); clif_pet_equip_area(sd->pd); clif_send_petstatus(sd); } Assert((sd->status.pet_id == 0 || sd->pd == 0) || sd->pd->msd == sd); clif_misceffect(&sd->pd->bl, 0); // Efecto 1 de nacimiento [Tab] clif_misceffect(&sd->pd->bl, 344); // Efecto 2 de nacimiento [Tab] sprintf(pet_output,"Get Out %s... NOW!",pet->name); // Cuidado aca con el nombre del pet clif_displaymessage(sd->fd, pet_output); // Frase nacimiento [Tab] return 0; }
//Close storage for sd and save it int storage_guild_storageclose(struct map_session_data* sd) { struct guild_storage *stor; nullpo_ret(sd); nullpo_ret(stor=guild2storage2(sd->status.guild_id)); clif_storageclose(sd); if (stor->storage_status) { if (save_settings&4) chrif_save(sd, 0); //This one also saves the storage. [Skotlex] else storage_guild_storagesave(sd->status.account_id, sd->status.guild_id,0); stor->storage_status=0; } sd->state.storage_flag = 0; return 0; }
/*========================================== * カプラ倉庫を閉じて保存 *------------------------------------------ */ void storage_storageclose(struct map_session_data *sd) { struct storage *stor; nullpo_retv(sd); nullpo_retv(stor = (struct storage *)numdb_search(storage_db,sd->status.account_id)); if(stor->dirty) { storage_sortitem(stor->store_item, MAX_STORAGE, &stor->sortkey, battle_config.personal_storage_sort); intif_send_storage(stor); if(battle_config.save_player_when_storage_closed) { chrif_save(sd,0); } stor->dirty = 0; } stor->storage_status = 0; sd->state.storage_flag = 0; clif_storageclose(sd); return; }
/** * This can still happen (client times out while waiting for char to confirm auth data) * @see DBApply */ int auth_db_cleanup_sub(DBKey key, DBData *data, va_list ap) { struct auth_node *node = DB->data2ptr(data); const char* states[] = { "Login", "Logout", "Map change" }; if(DIFF_TICK(gettick(),node->node_created)>60000) { switch (node->state) { case ST_LOGOUT: //Re-save attempt (->sd should never be null here). node->node_created = gettick(); //Refresh tick (avoid char-server load if connection is really bad) chrif_save(node->sd, 1); break; default: //Clear data. any connected players should have timed out by now. ShowInfo("auth_db: Node (state %s) timed out for %d:%d\n", states[node->state], node->account_id, node->char_id); chrif_char_offline_nsd(node->account_id, node->char_id); chrif_auth_delete(node->account_id, node->char_id, node->state); break; } return 1; } return 0; }
/*========================================== * 性別変化終了 (modified by Yor) *------------------------------------------ */ static void chrif_changedsex(Session *, const Packet_Fixed<0x2b0d>& fixed) { dumb_ptr<map_session_data> sd; AccountId acc = fixed.account_id; SEX sex = fixed.sex; if (battle_config.etc_log) PRINTF("chrif_changedsex %d.\n"_fmt, acc); sd = map_id2sd(account_to_block(acc)); if (acc) { if (sd != nullptr && sd->status.sex != sex) { sd->sex = sd->status.sex = sex; // to avoid any problem with equipment and invalid sex, equipment is unequiped. for (IOff0 i : IOff0::iter()) { if (sd->status.inventory[i].nameid && bool(sd->status.inventory[i].equip)) pc_unequipitem(sd, i, CalcStatus::LATER); } pc_calcstatus(sd, 0); // save character chrif_save(sd); sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters // do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it) clif_fixpcpos(sd); // use clif_set0078_main_1d8 to send new sex to the client } } else { if (sd != nullptr) { PRINTF("chrif_changedsex failed.\n"_fmt); } } }
int quest_update_status(TBL_PC * sd, int quest_id, quest_state status) { int i; //Only status of active and inactive quests can be updated. Completed quests can't (for now). [Inkfish] ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == quest_id); if(i == sd->avail_quests) { ShowError("quest_update_status: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id); return -1; } sd->quest_log[i].state = status; sd->save_quest = true; if( status < Q_COMPLETE ) { clif_quest_update_status(sd, quest_id, (bool)status); return 0; } achievement_validate_quest(sd,quest_id); if( i != (--sd->avail_quests) ) { struct quest tmp_quest; memcpy(&tmp_quest, &sd->quest_log[i],sizeof(struct quest)); memcpy(&sd->quest_log[i], &sd->quest_log[sd->avail_quests],sizeof(struct quest)); memcpy(&sd->quest_log[sd->avail_quests], &tmp_quest,sizeof(struct quest)); } clif_quest_delete(sd, quest_id); if( save_settings&64 ) chrif_save(sd,0); return 0; }
//This can still happen (client times out while waiting for char to confirm auth data) int auth_db_cleanup_sub(DBKey key,void *data,va_list ap) { struct auth_node *node=(struct auth_node*)data; const char* states[] = { "Iniciar Sessao", "Encerrar Sessao", "Mudanca de Mapa" }; if(DIFF_TICK(gettick(),node->node_created)>60000) { switch (node->state) { case ST_LOGOUT: //Re-save attempt (->sd should never be null here). node->node_created = gettick(); //Refresh tick (avoid char-server load if connection is really bad) chrif_save(node->sd, 1); break; default: //Clear data. any connected players should have timed out by now. ShowInfo("auth_db: No (estado %s) tempo esgotado para %d:%d\n", states[node->state], node->account_id, node->char_id); chrif_char_offline_nsd(node->account_id, node->char_id); chrif_auth_delete(node->account_id, node->char_id, node->state); break; } return 1; } return 0; }
/** * Updates a quest's state. * Only status of active and inactive quests can be updated. Completed quests can't (for now). * @param sd : Character's data * @param quest_id : Quest ID to update * @param qs : New quest state * @return 0 in case of success, nonzero otherwise * @author [Inkfish] */ int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status) { int i; ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == quest_id); if( i == sd->avail_quests ) { ShowError("quest_update_status: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id); return -1; } sd->quest_log[i].state = status; sd->save_quest = true; if( status < Q_COMPLETE ) { clif_quest_update_status(sd, quest_id, status == Q_ACTIVE ? true : false); return 0; } achievement_validate_quest(sd,quest_id); // The quest is complete, so it needs to be moved to the completed quests block at the end of the array. if( i < (--sd->avail_quests) ) { struct quest tmp_quest; memcpy(&tmp_quest, &sd->quest_log[i], sizeof(struct quest)); memcpy(&sd->quest_log[i], &sd->quest_log[sd->avail_quests], sizeof(struct quest)); memcpy(&sd->quest_log[sd->avail_quests], &tmp_quest, sizeof(struct quest)); } clif_quest_delete(sd, quest_id); if( save_settings&64 ) chrif_save(sd,0); return 0; }
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 = iMap->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 || !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( iMap->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 ) { iMap->quit(pl_sd); } }
/** * Player setup a new shop * @param sd : player opening the shop * @param message : shop title * @param data : itemlist data \n * data := {<index>.w <amount>.w <value>.l}[count] * @param count : number of different items * @return 0 If success, 1 - Cannot open (die, not state.prevend, trading), 2 - No cart, 3 - Count issue, 4 - Cart data isn't saved yet, 5 - No valid item found */ char vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count) { int i, j; int vending_skill_lvl; char message_sql[MESSAGE_SIZE*2]; nullpo_retr(false,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); 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); return 3; } if (save_settings&2) // Avoid invalid data from saving chrif_save(sd, 0); // 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_bounded_items(sd)) // can't trade account bound items and has no 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; sd->vending[i].value = min(value, (unsigned int)battle_config.vending_max_value); // Player just moved item to cart and we don't have the correct cart ID yet. if (sd->status.cart[sd->vending[i].index].id == 0) { struct item_data *idb = itemdb_search(sd->status.cart[index].nameid); char msg[256]; sprintf(msg, msg_txt(sd, 733), idb->jname); clif_displaymessage(sd->fd, msg); clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return 4; } i++; // item successfully added } if( i != j ) clif_displaymessage (sd->fd, msg_txt(sd,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 5; } sd->state.prevend = 0; sd->state.vending = true; 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 == 0 ? 'F' : 'M', map[sd->bl.m].name, sd->bl.x, sd->bl.y, message_sql, sd->state.autotrade, sd->ud.dir, sd->head_dir, pc_issit(sd) ) != SQL_SUCCESS ){ Sql_ShowDebug(mmysql_handle); } for( i = 0; i < count; i++ ) { if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`vending_id`,`index`,`cartinventory_id`,`amount`,`price`) VALUES( %d, %d, %d, %d, %d );", vending_items_db, sd->vender_id, i, sd->status.cart[sd->vending[i].index].id, sd->vending[i].amount, sd->vending[i].value ) != SQL_SUCCESS ){ Sql_ShowDebug(mmysql_handle); } } 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; }
/** * Open vending for Autotrader * @param sd Player as autotrader */ void vending_reopen( struct map_session_data* sd ){ nullpo_retv(sd); // Ready to open vending for this char if ( autotrader_count > 0 && autotraders){ uint16 i; uint8 *data, *p, fail = 0; uint16 j, count; ARR_FIND(0,autotrader_count,i,autotraders[i] && autotraders[i]->char_id == sd->status.char_id); if (i >= autotrader_count) { return; } // Init vending data for autotrader CREATE(data, uint8, autotraders[i]->count * 8); for (j = 0, p = data, count = autotraders[i]->count; j < autotraders[i]->count; j++) { struct s_autotrade_entry *entry = autotraders[i]->entries[j]; uint16 *index = (uint16*)(p + 0); uint16 *amount = (uint16*)(p + 2); uint32 *value = (uint32*)(p + 4); // Find item position in cart ARR_FIND(0, MAX_CART, entry->index, sd->status.cart[entry->index].id == entry->cartinventory_id); if (entry->index == MAX_CART) { count--; continue; } *index = entry->index + 2; *amount = itemdb_isstackable(sd->status.cart[entry->index].nameid) ? entry->amount : 1; *value = entry->price; p += 8; } // Set him into a hacked prevend state sd->state.prevend = 1; // Make sure abort all NPCs npc_event_dequeue(sd); pc_cleareventtimer(sd); // Open the vending again if( (fail = vending_openvending(sd, autotraders[i]->title, data, count)) == 0 ){ // Set him to autotrade if (Sql_Query( mmysql_handle, "UPDATE `%s` SET `autotrade` = 1, `body_direction` = '%d', `head_direction` = '%d', `sit` = '%d' " "WHERE `id` = %d;", vendings_db, autotraders[i]->dir, autotraders[i]->head_dir, autotraders[i]->sit, sd->vender_id ) != SQL_SUCCESS ) { Sql_ShowDebug( mmysql_handle ); } // Make vendor look perfect pc_setdir(sd, autotraders[i]->dir, autotraders[i]->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); if( autotraders[i]->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); } // Immediate save chrif_save(sd, 3); ShowInfo("Loaded vending for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items at "CL_WHITE"%s (%d,%d)"CL_RESET"\n", sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y); }else{ // Failed to open the vending, set him offline ShowError("Failed (%d) to load autotrade vending data for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items\n", fail, sd->status.name, count ); map_quit( sd ); } aFree(data); //If the last autotrade is loaded, clear autotraders [Cydh] if (++autotrader_loaded_count >= autotrader_count) do_final_vending_autotrade(); } }
/*========================================== * Purchase item(s) from a shop *------------------------------------------*/ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count) { int i, j, cursor, w, new_ = 0, blank, vend_list[MAX_VENDING]; double z; struct s_vending vending[MAX_VENDING]; // against duplicate packets struct map_session_data* vsd = map_id2sd(aid); char output[256]; nullpo_retv(sd); if( vsd == NULL || !vsd->state.vending || vsd->bl.id == sd->bl.id ) return; // invalid shop if( vsd->vender_id != uid ) {// shop has changed clif_buyvending(sd, 0, 0, 6); // store information was incorrect return; } if( !searchstore_queryremote(sd, aid) && ( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) ) return; // shop too far away searchstore_clearremote(sd); if( count < 1 || count > MAX_VENDING || count > vsd->vend_num ) return; // invalid amount of purchased items blank = pc_inventoryblank(sd); //number of free cells in the buyer's inventory // duplicate item in vending to check hacker with multiple packets memcpy(&vending, &vsd->vending, sizeof(vsd->vending)); // copy vending list // some checks z = 0.; // zeny counter w = 0; // weight counter for( i = 0; i < count; i++ ) { short amount = *(uint16*)(data + 4*i + 0); short idx = *(uint16*)(data + 4*i + 2); idx -= 2; if( amount <= 0 ) return; // check of item index in the cart if( idx < 0 || idx >= MAX_CART ) return; ARR_FIND( 0, vsd->vend_num, j, vsd->vending[j].index == idx ); if( j == vsd->vend_num ) return; //picked non-existing item else vend_list[i] = j; z += ((double)vsd->vending[j].value * (double)amount); if( !vsd->vend_coin || vsd->vend_coin == battle_config.vending_zeny_id ) { // Normal Vending - Zeny Option if( z > (double)sd->status.zeny || z < 0. || z > (double)MAX_ZENY ) { clif_buyvending(sd, idx, amount, 1); // you don't have enough zeny return; } if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) { clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // too much zeny = overflow return; } } else if( battle_config.vending_cash_id && vsd->vend_coin == battle_config.vending_cash_id ) { // Cash Shop if( z > (double)sd->cashPoints || z < 0. || z > (double)MAX_ZENY ) { sprintf(output,msg_txt(915),itemdb_jname(vsd->vend_coin)); clif_displaymessage(sd->fd,output); return; } if( z + (double)vsd->cashPoints > (double)MAX_ZENY && !battle_config.vending_over_max ) { sprintf(output,msg_txt(916),itemdb_jname(vsd->vend_coin)); clif_displaymessage(sd->fd,output); return; } } w += itemdb_weight(vsd->status.cart[idx].nameid) * amount; if( w + sd->weight > sd->max_weight ) { clif_buyvending(sd, idx, amount, 2); // you can not buy, because overweight return; } //Check to see if cart/vend info is in sync. if( vending[j].amount > vsd->status.cart[idx].amount ) vending[j].amount = vsd->status.cart[idx].amount; // if they try to add packets (example: get twice or more 2 apples if marchand has only 3 apples). // here, we check cumulative amounts if( vending[j].amount < amount ) { // send more quantity is not a hack (an other player can have buy items just before) clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // not enough quantity return; } vending[j].amount -= amount; switch( pc_checkadditem(sd, vsd->status.cart[idx].nameid, amount) ) { case ADDITEM_EXIST: break; //We'd add this item to the existing one (in buyers inventory) case ADDITEM_NEW: new_++; if (new_ > blank) return; //Buyer has no space in his inventory break; case ADDITEM_OVERAMOUNT: return; //too many items } } // Payments if( !vsd->vend_coin || vsd->vend_coin == battle_config.vending_zeny_id ) { if( log_config.zeny > 0 ) log_zeny(vsd, "V", sd, (int)z); //Logs (V)ending Zeny [Lupus] pc_payzeny(sd, (int)z); if( battle_config.vending_tax || (vsd->state.autotrade && battle_config.at_tax && !pc_isPremium(vsd)) ) z -= z * ((battle_config.vending_tax + ((vsd->state.autotrade && !pc_isPremium(vsd)) ? battle_config.at_tax : 0)) / 10000.); pc_getzeny(vsd, (int)z); } else if( battle_config.vending_cash_id && vsd->vend_coin == battle_config.vending_cash_id ) { pc_paycash(sd,(int)z,0); pc_getcash(vsd,(int)z,0); } else { if( z < 0. || (i = pc_search_inventory(sd,vsd->vend_coin)) < 0 || z > (double)sd->status.inventory[i].amount ) { sprintf(output,msg_txt(915),itemdb_jname(vsd->vend_coin)); clif_displaymessage(sd->fd,output); return; } switch( pc_checkadditem(vsd,vsd->vend_coin,(int)z) ) { case ADDITEM_NEW: if( pc_inventoryblank(vsd) > 0 ) break; case ADDITEM_OVERAMOUNT: sprintf(output,msg_txt(916),itemdb_jname(vsd->vend_coin)); clif_displaymessage(sd->fd,output); return; } pc_additem(vsd,&sd->status.inventory[i],(int)z); pc_delitem(sd,i,(int)z,0,6); } for( i = 0; i < count; i++ ) { short amount = *(uint16*)(data + 4*i + 0); short idx = *(uint16*)(data + 4*i + 2); idx -= 2; //Logs sold (V)ending items [Lupus] if(log_config.enable_logs&0x4) { log_pick_pc(vsd, "V", vsd->status.cart[idx].nameid, -amount, &vsd->status.cart[idx], vsd->status.cart[idx].serial ); log_pick_pc( sd, "V", vsd->status.cart[idx].nameid, amount, &vsd->status.cart[idx], vsd->status.cart[idx].serial ); } // vending item pc_additem(sd, &vsd->status.cart[idx], amount); vsd->vending[vend_list[i]].amount -= amount; pc_cart_delitem(vsd, idx, amount, 0); clif_vendingreport(vsd, idx, amount); //print buyer's name if( battle_config.buyer_name ) { char temp[256]; sprintf(temp, msg_txt(265), sd->status.name); clif_disp_onlyself(vsd,temp,strlen(temp)); } } // compact the vending list for( i = 0, cursor = 0; i < vsd->vend_num; i++ ) { if( vsd->vending[i].amount == 0 ) continue; if( cursor != i ) // speedup { vsd->vending[cursor].index = vsd->vending[i].index; vsd->vending[cursor].amount = vsd->vending[i].amount; vsd->vending[cursor].value = vsd->vending[i].value; } cursor++; } vsd->vend_num = cursor; //Always save BOTH: buyer and customer if( save_settings&2 ) { chrif_save(sd,0); chrif_save(vsd,0); } //check for @AUTOTRADE users [durf] if( vsd->state.autotrade ) { //see if there is anything left in the shop ARR_FIND( 0, vsd->vend_num, i, vsd->vending[i].amount > 0 ); if( i == vsd->vend_num ) { //Close Vending (this was automatically done by the client, we have to do it manually for autovenders) [Skotlex] vending_closevending(vsd); map_quit(vsd); //They have no reason to stay around anymore, do they? } } }
/** * Purchase item(s) from a shop * @param sd : buyer player session * @param aid : account id of vender * @param uid : shop unique id * @param data : items data who would like to purchase \n * data := {<index>.w <amount>.w }[count] * @param count : number of different items he's trying to buy */ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count) { int i, j, cursor, w, new_ = 0, blank, vend_list[MAX_VENDING]; double z; struct s_vending vending[MAX_VENDING]; // against duplicate packets struct map_session_data* vsd = map_id2sd(aid); nullpo_retv(sd); if( vsd == NULL || !vsd->state.vending || vsd->bl.id == sd->bl.id ) return; // invalid shop if( vsd->vender_id != uid ) { // shop has changed clif_buyvending(sd, 0, 0, 6); // store information was incorrect return; } if( !searchstore_queryremote(sd, aid) && ( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) ) return; // shop too far away searchstore_clearremote(sd); if( count < 1 || count > MAX_VENDING || count > vsd->vend_num ) return; // invalid amount of purchased items blank = pc_inventoryblank(sd); //number of free cells in the buyer's inventory // duplicate item in vending to check hacker with multiple packets memcpy(&vending, &vsd->vending, sizeof(vsd->vending)); // copy vending list // some checks z = 0.; // zeny counter w = 0; // weight counter for( i = 0; i < count; i++ ) { short amount = *(uint16*)(data + 4*i + 0); short idx = *(uint16*)(data + 4*i + 2); idx -= 2; if( amount <= 0 ) return; // check of item index in the cart if( idx < 0 || idx >= MAX_CART ) return; ARR_FIND( 0, vsd->vend_num, j, vsd->vending[j].index == idx ); if( j == vsd->vend_num ) return; //picked non-existing item else vend_list[i] = j; z += ((double)vsd->vending[j].value * (double)amount); if( z > (double)sd->status.zeny || z < 0. || z > (double)MAX_ZENY ) { clif_buyvending(sd, idx, amount, 1); // you don't have enough zeny return; } if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) { clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // too much zeny = overflow return; } w += itemdb_weight(vsd->status.cart[idx].nameid) * amount; if( w + sd->weight > sd->max_weight ) { clif_buyvending(sd, idx, amount, 2); // you can not buy, because overweight return; } //Check to see if cart/vend info is in sync. if( vending[j].amount > vsd->status.cart[idx].amount ) vending[j].amount = vsd->status.cart[idx].amount; // if they try to add packets (example: get twice or more 2 apples if marchand has only 3 apples). // here, we check cumulative amounts if( vending[j].amount < amount ) { // send more quantity is not a hack (an other player can have buy items just before) clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // not enough quantity return; } vending[j].amount -= amount; switch( pc_checkadditem(sd, vsd->status.cart[idx].nameid, amount) ) { case CHKADDITEM_EXIST: break; //We'd add this item to the existing one (in buyers inventory) case CHKADDITEM_NEW: new_++; if (new_ > blank) return; //Buyer has no space in his inventory break; case CHKADDITEM_OVERAMOUNT: return; //too many items } } pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd); if( battle_config.vending_tax ) z -= z * (battle_config.vending_tax/10000.); pc_getzeny(vsd, (int)z, LOG_TYPE_VENDING, sd); for( i = 0; i < count; i++ ) { short amount = *(uint16*)(data + 4*i + 0); short idx = *(uint16*)(data + 4*i + 2); idx -= 2; // vending item pc_additem(sd, &vsd->status.cart[idx], amount, LOG_TYPE_VENDING); vsd->vending[vend_list[i]].amount -= amount; if( vsd->vending[vend_list[i]].amount ){ if( Sql_Query( mmysql_handle, "UPDATE `%s` SET `amount` = %d WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_db, vsd->vending[vend_list[i]].amount, vsd->vender_id, vsd->status.cart[idx].id ) != SQL_SUCCESS ){ Sql_ShowDebug( mmysql_handle ); } }else{ if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_db, vsd->vender_id, vsd->status.cart[idx].id ) != SQL_SUCCESS ){ Sql_ShowDebug( mmysql_handle ); } } pc_cart_delitem(vsd, idx, amount, 0, LOG_TYPE_VENDING); clif_vendingreport(vsd, idx, amount); //print buyer's name if( battle_config.buyer_name ) { char temp[256]; sprintf(temp, msg_txt(sd,265), sd->status.name); clif_disp_onlyself(vsd,temp,strlen(temp)); } } // compact the vending list for( i = 0, cursor = 0; i < vsd->vend_num; i++ ) { if( vsd->vending[i].amount == 0 ) continue; if( cursor != i ) { // speedup vsd->vending[cursor].index = vsd->vending[i].index; vsd->vending[cursor].amount = vsd->vending[i].amount; vsd->vending[cursor].value = vsd->vending[i].value; } cursor++; } vsd->vend_num = cursor; //Always save BOTH: customer (buyer) and vender if( save_settings&2 ) { chrif_save(sd,0); chrif_save(vsd,0); } //check for @AUTOTRADE users [durf] if( vsd->state.autotrade ) { //see if there is anything left in the shop ARR_FIND( 0, vsd->vend_num, i, vsd->vending[i].amount > 0 ); if( i == vsd->vend_num ) { //Close Vending (this was automatically done by the client, we have to do it manually for autovenders) [Skotlex] vending_closevending(vsd); map_quit(vsd); //They have no reason to stay around anymore, do they? } } }
/** * Open buyingstore for Autotrader * @param sd Player as autotrader */ void buyingstore_reopen( struct map_session_data* sd ){ nullpo_retv(sd); // Ready to open buyingstore for this char if ( autotrader_count > 0 && autotraders){ uint16 i; uint8 *data, *p, fail = 0; uint16 j, count; ARR_FIND(0,autotrader_count,i,autotraders[i] && autotraders[i]->char_id == sd->status.char_id); if (i >= autotrader_count) { return; } // Init buyingstore data for autotrader CREATE(data, uint8, autotraders[i]->count * 8); for (j = 0, p = data, count = autotraders[i]->count; j < autotraders[i]->count; j++) { struct s_autotrade_entry *entry = autotraders[i]->entries[j]; unsigned short *item_id = (uint16*)(p + 0); uint16 *amount = (uint16*)(p + 2); uint32 *price = (uint32*)(p + 4); *item_id = entry->item_id; *amount = entry->amount; *price = entry->price; p += 8; } // Make sure abort all NPCs npc_event_dequeue(sd); pc_cleareventtimer(sd); // Open the buyingstore again if( (fail = buyingstore_setup( sd, (unsigned char)autotraders[i]->count )) == 0 && (fail = buyingstore_create( sd, autotraders[i]->limit, 1, autotraders[i]->title, data, autotraders[i]->count )) == 0 ) { ShowInfo("Loaded buyingstore for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items at "CL_WHITE"%s (%d,%d)"CL_RESET"\n", sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y); // Set him to autotrade if (Sql_Query( mmysql_handle, "UPDATE `%s` SET `autotrade` = 1, `body_direction` = '%d', `head_direction` = '%d', `sit` = '%d' " "WHERE `id` = %d;", buyingstores_db, autotraders[i]->dir, autotraders[i]->head_dir, autotraders[i]->sit, sd->buyer_id ) != SQL_SUCCESS ) { Sql_ShowDebug( mmysql_handle ); } // Make buyer look perfect pc_setdir(sd, autotraders[i]->dir, autotraders[i]->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); if( autotraders[i]->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); } // Immediate save chrif_save(sd, 3); }else{ // Failed to open the buyingstore, set him offline ShowError("Failed (%d) to load autotrade buyingstore data for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items\n", fail, sd->status.name, count ); map_quit( sd ); } aFree(data); //If the last autotrade is loaded, clear autotraders [Cydh] if (++autotrader_loaded_count >= autotrader_count) do_final_buyingstore_autotrade(); } }
/*========================================== * Žæˆø‹–‘ø(trade‰Ÿ‚µ) *------------------------------------------*/ void trade_tradecommit(struct map_session_data *sd) { struct map_session_data *tsd; int trade_i; int flag; if (!sd->state.trading || !sd->state.deal_locked) //Locked should be 1 (pressed ok) before you can press trade. return; if ((tsd = map_id2sd(sd->trade_partner)) == NULL) { trade_tradecancel(sd); return; } sd->state.deal_locked = 2; if (tsd->state.deal_locked < 2) return; //Not yet time for trading. //Now is a good time (to save on resources) to check that the trade can indeed be made and it's not exploitable. // check exploit (trade more items that you have) if (impossible_trade_check(sd)) { trade_tradecancel(sd); return; } // check exploit (trade more items that you have) if (impossible_trade_check(tsd)) { trade_tradecancel(tsd); return; } // check for full inventory (can not add traded items) if (!trade_check(sd,tsd)) { // check the both players trade_tradecancel(sd); return; } // trade is accepted and correct. for( trade_i = 0; trade_i < 10; trade_i++ ) { int n; if (sd->deal.item[trade_i].amount) { n = sd->deal.item[trade_i].index; flag = pc_additem(tsd, &sd->status.inventory[n], sd->deal.item[trade_i].amount); if (flag == 0) { //Logs (T)rade [Lupus] if(log_config.enable_logs&0x2) { log_pick_pc(sd, "T", sd->status.inventory[n].nameid, -(sd->deal.item[trade_i].amount), &sd->status.inventory[n]); log_pick_pc(tsd, "T", sd->status.inventory[n].nameid, sd->deal.item[trade_i].amount, &sd->status.inventory[n]); } pc_delitem(sd, n, sd->deal.item[trade_i].amount, 1); } else clif_additem(sd, n, sd->deal.item[trade_i].amount, 0); sd->deal.item[trade_i].index = 0; sd->deal.item[trade_i].amount = 0; } if (tsd->deal.item[trade_i].amount) { n = tsd->deal.item[trade_i].index; flag = pc_additem(sd, &tsd->status.inventory[n], tsd->deal.item[trade_i].amount); if (flag == 0) { //Logs (T)rade [Lupus] if(log_config.enable_logs&0x2) { log_pick_pc(tsd, "T", tsd->status.inventory[n].nameid, -(tsd->deal.item[trade_i].amount), &tsd->status.inventory[n]); log_pick_pc(sd, "T", tsd->status.inventory[n].nameid, tsd->deal.item[trade_i].amount, &tsd->status.inventory[n]); } pc_delitem(tsd, n, tsd->deal.item[trade_i].amount, 1); } else clif_additem(tsd, n, tsd->deal.item[trade_i].amount, 0); tsd->deal.item[trade_i].index = 0; tsd->deal.item[trade_i].amount = 0; } } if( sd->deal.zeny || tsd->deal.zeny ) { sd->status.zeny += tsd->deal.zeny - sd->deal.zeny; tsd->status.zeny += sd->deal.zeny - tsd->deal.zeny; //Logs Zeny (T)rade [Lupus] if( sd->deal.zeny && log_config.zeny ) log_zeny(tsd, "T", sd, sd->deal.zeny); if( tsd->deal.zeny && log_config.zeny ) log_zeny(sd, "T", tsd, tsd->deal.zeny); sd->deal.zeny = 0; tsd->deal.zeny = 0; clif_updatestatus(sd, SP_ZENY); clif_updatestatus(tsd, SP_ZENY); } sd->state.deal_locked = 0; sd->trade_partner = 0; sd->state.trading = 0; tsd->state.deal_locked = 0; tsd->trade_partner = 0; tsd->state.trading = 0; clif_tradecompleted(sd, 0); clif_tradecompleted(tsd, 0); // save both player to avoid crash: they always have no advantage/disadvantage between the 2 players if (save_settings&1) { chrif_save(sd,0); chrif_save(tsd,0); } }
/** * Open buyingstore for Autotrader * @param sd Player as autotrader */ void buyingstore_reopen( struct map_session_data* sd ){ struct s_autotrader *at = NULL; int8 fail = -1; nullpo_retv(sd); // Ready to open buyingstore for this char if ((at = (struct s_autotrader *)uidb_get(buyingstore_autotrader_db, sd->status.char_id)) && at->count && at->entries) { uint8 *data, *p; uint16 j, count; // Init buyingstore data for autotrader CREATE(data, uint8, at->count * 8); for (j = 0, p = data, count = at->count; j < at->count; j++) { struct s_autotrade_entry *entry = at->entries[j]; unsigned short *item_id = (uint16*)(p + 0); uint16 *amount = (uint16*)(p + 2); uint32 *price = (uint32*)(p + 4); *item_id = entry->item_id; *amount = entry->amount; *price = entry->price; p += 8; } sd->state.autotrade = 1; // Make sure abort all NPCs npc_event_dequeue(sd); pc_cleareventtimer(sd); // Open the buyingstore again if( (fail = buyingstore_setup( sd, (unsigned char)at->count )) == 0 && (fail = buyingstore_create( sd, at->limit, 1, at->title, data, at->count, at )) == 0 ) { // Make buyer look perfect pc_setdir(sd, at->dir, at->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); if( at->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); } // Immediate save chrif_save(sd, CSAVE_AUTOTRADE); ShowInfo("Buyingstore loaded for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items at "CL_WHITE"%s (%d,%d)"CL_RESET"\n", sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y); } aFree(data); } if (at) { buyingstore_autotrader_remove(at, true); if (db_size(buyingstore_autotrader_db) == 0) buyingstore_autotrader_db->clear(buyingstore_autotrader_db, buyingstore_autotrader_free); } if (fail != 0) { ShowError("buyingstore_reopen: (Error:%d) Load failed for autotrader '"CL_WHITE"%s"CL_RESET"' (CID=%/AID=%d)\n", fail, sd->status.name, sd->status.char_id, sd->status.account_id); map_quit(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 - Cart data isn't saved yet, 5 - 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, k, n; int vending_skill_lvl; char message_sql[MESSAGE_SIZE*2]; int item_bad_price[MAX_VENDING]; StringBuf buf; struct item_data *item; nullpo_retr(false,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); 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); return 3; } if (save_settings&CHARSAVE_VENDING) // Avoid invalid data from saving chrif_save(sd, 0); // filter out invalid items i = k = 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_bounded_items(sd)) // can't trade account bound items and has no permission || !itemdb_cantrade(&sd->status.cart[index], pc_get_group_level(sd), pc_get_group_level(sd)) ) // untradeable item continue; item = itemdb_search(sd->status.cart[index].nameid); if (item->value_buy_min > 0 && value > item->value_buy_min) { if (battle_config.vending_price_min_overflow > 0 ) { if (value > item->value_buy_min + (item->value_buy_min * (battle_config.vending_price_min_overflow / 10000.))) { item_bad_price[k++] = sd->status.cart[index].nameid; continue; } } else { item_bad_price[k++] = sd->status.cart[index].nameid; continue; } } sd->vending[i].index = index; sd->vending[i].amount = amount; sd->vending[i].value = min(value, (unsigned int)battle_config.vending_max_value); // Player just moved item to cart and we don't have the correct cart ID yet. if (sd->status.cart[sd->vending[i].index].id == 0) { char msg[256]; snprintf(msg, 256, "äÍà·çÁ %s ÂѧäÁèä´éºÑ¹·Ö¡. ¡ÃسÒÍÍ¡à¢éÒãËÁè à¾×èÍãËéäÍà·çÁ·Ó¡ÒÃ૿ŧÃéÒ¹¤éÒ", item->jname); clif_displaymessage(sd->fd, msg); clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); return 4; } i++; // item successfully added } //if( i != j ) //clif_displaymessage (sd->fd, msg_txt(sd,266)); //"Some of your items cannot be vended and were removed from the shop." for (n = 0; n < k; n++) { char msg[512]; item = itemdb_search(item_bad_price[n]); if (battle_config.vending_price_min_overflow > 0) sprintf(msg, "%s µÑé§ÃÒ¤Ò¢ÒÂÊÙ§¡ÇèÒ·Õè NPC ÁÕ¢ÒÂÍÂÙèà¡Ô¹ %d%% ¨Ð¶Ù¡µÑ´ÍÍ¡¨Ò¡ÃÒ¡ÒÃà¾×èÍ»éͧ¡Ñ¹¡ÒÃâ¡è§ÃÒ¤Ò", item->jname, battle_config.vending_price_min_overflow/100); else sprintf(msg, "%s µÑé§ÃÒ¤Ò¢ÒÂÊÙ§¡ÇèÒ·Õè NPC ÁÕ¢ÒÂÍÂÙè ¨Ð¶Ù¡µÑ´ÍÍ¡¨Ò¡ÃÒ¡ÒÃà¾×èÍ»éͧ¡Ñ¹¡ÒÃâ¡è§ÃÒ¤Ò", item->jname); clif_displaymessage(sd->fd, msg); } if( i == 0 ) { // no valid item found clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); // custom reply packet return 5; } sd->state.prevend = 0; sd->state.vending = true; 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 == 0 ? '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 (i = 0; i < count; i++) { StringBuf_Printf(&buf, "(%d,%d,%d,%d,%d)", sd->vender_id, i, sd->status.cart[sd->vending[i].index].id, sd->vending[i].amount, sd->vending[i].value); if (i < count-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; }
/** * Open vending for Autotrader * @param sd Player as autotrader */ void vending_reopen( struct map_session_data* sd ) { struct s_autotrader *at = NULL; int8 fail = -1; nullpo_retv(sd); // Open vending for this autotrader if ((at = uidb_get(vending_autotrader_db, sd->status.char_id)) && at->count && at->entries) { uint8 *data, *p; uint16 j, count; // Init vending data for autotrader CREATE(data, uint8, at->count * 8); for (j = 0, p = data, count = at->count; j < at->count; j++) { struct s_autotrade_entry *entry = at->entries[j]; uint16 *index = (uint16*)(p + 0); uint16 *amount = (uint16*)(p + 2); uint32 *value = (uint32*)(p + 4); // Find item position in cart ARR_FIND(0, MAX_CART, entry->index, sd->status.cart[entry->index].id == entry->cartinventory_id); if (entry->index == MAX_CART) { count--; continue; } *index = entry->index + 2; *amount = itemdb_isstackable(sd->status.cart[entry->index].nameid) ? entry->amount : 1; *value = entry->price; p += 8; } sd->state.prevend = 1; // Set him into a hacked prevend state sd->state.autotrade = 1; // Make sure abort all NPCs npc_event_dequeue(sd); pc_cleareventtimer(sd); // Open the vending again if( (fail = vending_openvending(sd, at->title, data, count, at)) == 0 ) { // Make vendor look perfect pc_setdir(sd, at->dir, at->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); if( at->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); } // Immediate save chrif_save(sd, 3); ShowInfo("Vending loaded for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items at "CL_WHITE"%s (%d,%d)"CL_RESET"\n", sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y); } aFree(data); } if (at) { vending_autotrader_remove(at, true); if (db_size(vending_autotrader_db) == 0) vending_autotrader_db->clear(vending_autotrader_db, vending_autotrader_free); } if (fail != 0) { ShowError("vending_reopen: (Error:%d) Load failed for autotrader '"CL_WHITE"%s"CL_RESET"' (CID=%/AID=%d)\n", fail, sd->status.name, sd->status.char_id, sd->status.account_id); map_quit(sd); } }
/*========================================== * Žæˆø‹–‘ø(trade‰Ÿ‚µ) *------------------------------------------*/ void trade_tradecommit (struct map_session_data *sd) { struct map_session_data *tsd; int trade_i; int flag; if (!sd->state.trading || !sd->state.deal_locked) //Locked should be 1 (pressed ok) before you can press trade. return; if ( (tsd = map_id2sd (sd->trade_partner)) == NULL) { trade_tradecancel (sd); return; } sd->state.deal_locked = 2; if (tsd->state.deal_locked < 2) return; //Not yet time for trading. //Now is a good time (to save on resources) to check that the trade can indeed be made and it's not exploitable. // check exploit (trade more items that you have) if (impossible_trade_check (sd)) { trade_tradecancel (sd); return; } // check exploit (trade more items that you have) if (impossible_trade_check (tsd)) { trade_tradecancel (tsd); return; } // check for full inventory (can not add traded items) if (!trade_check (sd, tsd)) { // check the both players trade_tradecancel (sd); return; } // trade is accepted and correct. for (trade_i = 0; trade_i < 10; trade_i++) { int n; if (sd->deal.item[trade_i].amount) { n = sd->deal.item[trade_i].index; flag = pc_additem (tsd, &sd->status.inventory[n], sd->deal.item[trade_i].amount, LOG_TYPE_TRADE); if (flag == 0) pc_delitem (sd, n, sd->deal.item[trade_i].amount, 1, 6, LOG_TYPE_TRADE); else clif_additem (sd, n, sd->deal.item[trade_i].amount, 0); sd->deal.item[trade_i].index = 0; sd->deal.item[trade_i].amount = 0; } if (tsd->deal.item[trade_i].amount) { n = tsd->deal.item[trade_i].index; flag = pc_additem (sd, &tsd->status.inventory[n], tsd->deal.item[trade_i].amount, LOG_TYPE_TRADE); if (flag == 0) pc_delitem (tsd, n, tsd->deal.item[trade_i].amount, 1, 6, LOG_TYPE_TRADE); else clif_additem (tsd, n, tsd->deal.item[trade_i].amount, 0); tsd->deal.item[trade_i].index = 0; tsd->deal.item[trade_i].amount = 0; } } if (sd->deal.zeny) { pc_payzeny (sd , sd->deal.zeny, LOG_TYPE_TRADE, tsd); pc_getzeny (tsd, sd->deal.zeny, LOG_TYPE_TRADE, sd); sd->deal.zeny = 0; sd->deal.zeny = 0; } if (tsd->deal.zeny) { pc_payzeny (tsd, tsd->deal.zeny, LOG_TYPE_TRADE, sd); pc_getzeny (sd , tsd->deal.zeny, LOG_TYPE_TRADE, tsd); tsd->deal.zeny = 0; } sd->state.deal_locked = 0; sd->trade_partner = 0; sd->state.trading = 0; tsd->state.deal_locked = 0; tsd->trade_partner = 0; tsd->state.trading = 0; clif_tradecompleted (sd, 0); clif_tradecompleted (tsd, 0); // save both player to avoid crash: they always have no advantage/disadvantage between the 2 players if (save_settings & 1) { chrif_save (sd, 0); chrif_save (tsd, 0); } }
/*========================================== * 取引許諾(trade押し) *------------------------------------------ */ void trade_tradecommit(struct map_session_data *sd) { struct map_session_data *target_sd; nullpo_retv(sd); target_sd = map_id2sd(sd->trade.partner); if(target_sd && target_sd->bl.prev) { if (sd->state.deal_locked >= 1 && target_sd->state.deal_locked >= 1) { // both have pressed 'ok' if (sd->state.deal_locked < 2) // set locked to 2 sd->state.deal_locked = 2; if(target_sd->state.deal_locked == 2) { // the other one pressed 'trade' too int trade_i, idx, flag; // checks quantity of items if (!trade_check(sd, target_sd)) { // this function do like the real trade, but with virtual inventories trade_tradecancel(sd); return; } // check zenys if (sd->trade.zeny > sd->status.zeny || target_sd->trade.zeny > target_sd->status.zeny || sd->status.zeny - sd->trade.zeny + target_sd->trade.zeny > MAX_ZENY || target_sd->status.zeny - target_sd->trade.zeny + sd->trade.zeny > MAX_ZENY) { trade_tradecancel(sd); return; } // do trade // ---- of items for(trade_i = 0; trade_i < MAX_DEAL_ITEMS; trade_i++) { if(sd->trade.item_amount[trade_i] != 0) { idx = sd->trade.item_index[trade_i] - 2; if (itemdb_isdropable(sd->status.inventory[idx].nameid)) { flag = pc_additem(target_sd, &sd->status.inventory[idx], sd->trade.item_amount[trade_i]); if(flag==0) pc_delitem(sd, idx, sd->trade.item_amount[trade_i], 1, 0); else clif_additem(sd, idx, sd->trade.item_amount[trade_i], 0); } sd->trade.item_amount[trade_i] = 0; } sd->trade.item_index[trade_i] = 0; if(target_sd->trade.item_amount[trade_i] != 0) { idx = target_sd->trade.item_index[trade_i] - 2; if (itemdb_isdropable(target_sd->status.inventory[idx].nameid)) { flag = pc_additem(sd, &target_sd->status.inventory[idx], target_sd->trade.item_amount[trade_i]); if(flag==0) pc_delitem(target_sd, idx, target_sd->trade.item_amount[trade_i], 1, 0); else clif_additem(target_sd, idx, target_sd->trade.item_amount[trade_i], 0); } target_sd->trade.item_amount[trade_i] = 0; } target_sd->trade.item_index[trade_i] = 0; } // ---- of zenys if(sd->trade.zeny || target_sd->trade.zeny) { sd->status.zeny = sd->status.zeny - sd->trade.zeny + target_sd->trade.zeny; target_sd->status.zeny = target_sd->status.zeny - target_sd->trade.zeny + sd->trade.zeny; clif_updatestatus(sd, SP_ZENY); clif_updatestatus(target_sd, SP_ZENY); } sd->trade.zeny = 0; target_sd->trade.zeny = 0; // clean up variables sd->state.deal_locked = 0; sd->state.deal_mode = 0; sd->trade.partner = 0; target_sd->state.deal_locked = 0; target_sd->state.deal_mode = 0; target_sd->trade.partner = 0; clif_tradecompleted(sd,0); clif_tradecompleted(target_sd,0); // save both player to avoid crash: they always have no advantage/disadvantage between the 2 players chrif_save(sd,0); chrif_save(target_sd,0); } } } else { trade_tradecancel(sd); } return; }