diff --git a/conf/inter_athena.conf b/conf/inter_athena.conf index 7251264f00..41ae45ca13 100644 --- a/conf/inter_athena.conf +++ b/conf/inter_athena.conf @@ -138,6 +138,7 @@ mob_skill_db2_db: mob_skill_db2 mapreg_db: mapreg vending_db: vendings vending_items_db: vending_items +market_table: market // Use SQL item_db, mob_db and mob_skill_db for the map server? (yes/no) use_sql_db: no diff --git a/conf/msg_conf/map_msg.conf b/conf/msg_conf/map_msg.conf index c6f01f5e46..07501ee83d 100644 --- a/conf/msg_conf/map_msg.conf +++ b/conf/msg_conf/map_msg.conf @@ -537,7 +537,7 @@ 532: Shadow Right Accessory, 533: Shadow Left Accessory, -//534: // Free +534: Shop is out of stock! Please come back later. // Bot detect messages (currently unused) 535: Possible use of BOT (99%% of chance) or modified client by '%s' (account: %d, char_id: %d). This player ask your name when you are hidden. diff --git a/db/packet_db.txt b/db/packet_db.txt index 4c6db531ed..2d48a91b13 100644 --- a/db/packet_db.txt +++ b/db/packet_db.txt @@ -2310,10 +2310,12 @@ packet_keys: 0x631C511C,0x111C111C,0x111C111C // [Shakto] //0x097E,12 //ZC_UPDATE_RANKING_POINT 0x09B4,6,dull,0 //Cash Shop - Special Tab 0x09CE,102,itemmonster,2 -0x09D4,2,dull,0 //npcshopclosed +0x09D4,2,npcshopclosed,0 //NPC Market -0x09D6,-1,dull,0 //npcmarketpurchase -0x09D8,2,dull,0 //npcmarketclosed +0x09D5,-1 +0x09D6,-1,npcmarketpurchase,2:4:6 +0x09D7,-1 +0x09D8,2,npcmarketclosed,0 0x09DF,7 //Add new packets here diff --git a/doc/script_commands.txt b/doc/script_commands.txt index b3f564cf32..c5577cc7ee 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -282,6 +282,8 @@ these floating NPC objects are for. More on that below. -%TAB%pointshop%TAB%%TAB%,{:},:{,:...} ,,,%TAB%pointshop%TAB%%TAB%,{:},:{,:...} +,,,%TAB%marketshop%TAB%%TAB%,::{,::...} + This will define a shop NPC, which, when triggered (which can only be done by clicking) will cause a shop window to come up. No code whatsoever runs in shop NPCs and you can't change the prices otherwise than by editing the script @@ -6239,7 +6241,10 @@ specified will be for sale. The function returns 1 if shop was updated successfully, or 0 if not found. -Note that you cannot use -1 to specify default selling price! +NOTES: + - That you cannot use -1 to specify default selling price! + - If attached shop type is market shop, need an extra param after price, it's + and make sure don't add duplication item! --------------------------------------- @@ -6251,7 +6256,10 @@ appear twice on the sell list. The function returns 1 if shop was updated successfully, or 0 if not found. -Note that you cannot use -1 to specify default selling price! +NOTES: + - That you cannot use -1 to specify default selling price! + - If attached shop type is market shop, need an extra param after price, it's + and make sure don't add duplication item! --------------------------------------- @@ -6282,6 +6290,17 @@ override any other script that may be already attached. The function returns 0 if the shop was not found, 1 otherwise. +NOTES: + - If attached shop type is market shop, will be default to call the 'buy' window. + +--------------------------------------- + +*npcshopupdate "",,{,} + +Update an entry from shop. If price is 0 means don't change the price, maybe used for +marketshop to update the stock quantity. Except marketshop type, 'stock' value means +nothing. + --------------------------------------- *waitingroom "",{,""{,{,{,{,}}}}}; diff --git a/sql-files/main.sql b/sql-files/main.sql index 5c29335504..fae8094fac 100644 --- a/sql-files/main.sql +++ b/sql-files/main.sql @@ -695,6 +695,10 @@ CREATE TABLE IF NOT EXISTS `storage` ( KEY `account_id` (`account_id`) ) ENGINE=MyISAM; +-- +-- Table structure for table `interreg` +-- + CREATE TABLE IF NOT EXISTS `interreg` ( `varname` varchar(11) NOT NULL, `value` varchar(20) NOT NULL, @@ -714,6 +718,10 @@ CREATE TABLE IF NOT EXISTS `bonus_script` ( `icon` SMALLINT(3) NOT NULL DEFAULT '-1' ) ENGINE=InnoDB; +-- +-- Table structure for table `vending_items` +-- + CREATE TABLE IF NOT EXISTS `vending_items` ( `vending_id` int(10) unsigned NOT NULL, `index` smallint(5) unsigned NOT NULL, @@ -722,6 +730,10 @@ CREATE TABLE IF NOT EXISTS `vending_items` ( `price` int(10) unsigned NOT NULL ) ENGINE=MyISAM; +-- +-- Table structure for table `vendings` +-- + CREATE TABLE IF NOT EXISTS `vendings` ( `id` int(10) unsigned NOT NULL, `account_id` int(11) unsigned NOT NULL, @@ -738,6 +750,10 @@ CREATE TABLE IF NOT EXISTS `vendings` ( PRIMARY KEY (`id`) ) ENGINE=MyISAM; +-- +-- Table structure for table `buyingstore_items` +-- + CREATE TABLE IF NOT EXISTS `buyingstore_items` ( `buyingstore_id` int(10) unsigned NOT NULL, `index` smallint(5) unsigned NOT NULL, @@ -746,6 +762,10 @@ CREATE TABLE IF NOT EXISTS `buyingstore_items` ( `price` int(10) unsigned NOT NULL ) ENGINE=MyISAM; +-- +-- Table structure for table `buyingstores` +-- + CREATE TABLE IF NOT EXISTS `buyingstores` ( `id` int(10) unsigned NOT NULL, `account_id` int(11) unsigned NOT NULL, @@ -762,3 +782,16 @@ CREATE TABLE IF NOT EXISTS `buyingstores` ( `autotrade` tinyint(4) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM; + +-- +-- Table `market` for market shop persistency +-- + +CREATE TABLE IF NOT EXISTS `market` ( + `name` varchar(32) NOT NULL DEFAULT '', + `nameid` SMALLINT(5) UNSIGNED NOT NULL, + `price` INT(11) UNSIGNED NOT NULL, + `amount` SMALLINT(5) UNSIGNED NOT NULL, + `flag` TINYINT(2) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`name`,`nameid`) +) ENGINE = MyISAM; diff --git a/sql-files/upgrades/upgrade_20150327_market.sql b/sql-files/upgrades/upgrade_20150327_market.sql new file mode 100644 index 0000000000..8ca000ef16 --- /dev/null +++ b/sql-files/upgrades/upgrade_20150327_market.sql @@ -0,0 +1,12 @@ +-- +-- Table `market` for market shop persistency +-- + +CREATE TABLE IF NOT EXISTS `market` ( + `name` varchar(32) NOT NULL DEFAULT '', + `nameid` SMALLINT(5) UNSIGNED NOT NULL, + `price` INT(11) UNSIGNED NOT NULL, + `amount` SMALLINT(5) UNSIGNED NOT NULL, + `flag` TINYINT(2) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`name`,`nameid`) +) ENGINE = MyISAM; diff --git a/src/map/clif.c b/src/map/clif.c index f4446179dd..af14aa5cab 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -1856,6 +1856,133 @@ void clif_selllist(struct map_session_data *sd) } +/** + * Presents list of items, that can be sold to a Market shop. + * @author: Ind and Yommy + **/ +void clif_npc_market_open(struct map_session_data *sd, struct npc_data *nd) { +#if PACKETVER >= 20131223 + struct npc_item_list *shop = nd->u.shop.shop_item; + unsigned short shop_size = nd->u.shop.count, i, c, cmd = 0x9d5; + struct item_data *id = NULL; + struct s_packet_db *info; + int fd; + + nullpo_retv(sd); + + if (sd->state.trading) + return; + + info = &packet_db[sd->packet_ver][cmd]; + if (!info || info->len == 0) + return; + + fd = sd->fd; + + WFIFOHEAD(fd, 4 + shop_size * 13); + WFIFOW(fd,0) = cmd; + + for (i = 0, c = 0; i < shop_size; i++) { + if (shop[i].nameid && (id = itemdb_exists(shop[i].nameid))) { + WFIFOW(fd, 4+c*13) = shop[i].nameid; + WFIFOB(fd, 6+c*13) = itemtype(id->nameid); + WFIFOL(fd, 7+c*13) = shop[i].value; + WFIFOL(fd,11+c*13) = shop[i].qty; + WFIFOW(fd,15+c*13) = (id->view_id > 0) ? id->view_id : id->nameid; + c++; + } + } + + WFIFOW(fd,2) = 4 + c*13; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->state.trading = 1; +#endif +} + + +/// Closes the Market shop window. +void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + sd->npc_shopid = 0; + sd->state.trading = 0; +} + + +/// Purchase item from Market shop. +void clif_npc_market_purchase_ack(struct map_session_data *sd, uint8 res, uint8 n, struct s_npc_buy_list *list) { +#if PACKETVER >= 20131223 + unsigned short cmd = 0x9d7, len = 0; + struct npc_data* nd; + uint8 result = (res == 0 ? 1 : 0); + int fd = 0; + struct s_packet_db *info; + + nullpo_retv(sd); + nullpo_retv((nd = map_id2nd(sd->npc_shopid))); + + info = &packet_db[sd->packet_ver][cmd]; + if (!info || info->len == 0) + return; + + fd = sd->fd; + len = 5 + 8*n; + + WFIFOHEAD(fd, len); + WFIFOW(fd, 0) = cmd; + WFIFOW(fd, 2) = len; + + if (result) { + uint8 i, j; + struct npc_item_list *shop = nd->u.shop.shop_item; + unsigned short count = nd->u.shop.count; + + for (i = 0; i < n; i++) { + WFIFOW(fd, 5+i*8) = list[i].nameid; + WFIFOW(fd, 7+i*8) = list[i].qty; + + ARR_FIND(0, count, j, list[i].nameid == shop[j].nameid); + WFIFOL(fd, 9+i*8) = (j != count) ? shop[j].value : 0; + } + } + + WFIFOB(fd, 4) = n; + WFIFOSET(fd, len); +#endif +} + + +/// Purchase item from Market shop. +void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd) { +#if PACKETVER >= 20131223 + struct s_packet_db* info; + struct s_npc_buy_list *item_list; + uint16 cmd = RFIFOW(fd,0), len = 0, i = 0; + uint8 res = 0, n = 0; + + nullpo_retv(sd); + + if (!sd->npc_shopid) + return; + + info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + if (!info || info->len == 0) + return; + len = RFIFOW(fd,info->pos[0]); + n = (len-4) / 6; + + CREATE(item_list, struct s_npc_buy_list, n); + for (i = 0; i < n; i++) { + item_list[i].nameid = RFIFOW(fd,info->pos[1]+i*6); + item_list[i].qty = (uint16)min(RFIFOL(fd,info->pos[2]+i*6),USHRT_MAX); + } + + res = npc_buylist(sd, n, item_list); + clif_npc_market_purchase_ack(sd, res, n, item_list); + aFree(item_list); +#endif +} + + /// Displays an NPC dialog message (ZC_SAY_DIALOG). /// 00b4 .W .L .?B /// Client behavior (dialog window): @@ -10930,17 +11057,15 @@ void clif_npc_buy_result(struct map_session_data* sd, unsigned char result) void clif_parse_NpcBuyListSend(int fd, struct map_session_data* sd) { struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; - int n = (RFIFOW(fd,info->pos[0])-4) /4; - unsigned short* item_list = (unsigned short*)RFIFOP(fd,info->pos[1]); + uint16 n = (RFIFOW(fd,info->pos[0])-4) /4; int result; if( sd->state.trading || !sd->npc_shopid ) result = 1; else - result = npc_buylist(sd,n,item_list); + result = npc_buylist(sd, n, (struct s_npc_buy_list*)RFIFOP(fd,info->pos[1])); sd->npc_shopid = 0; //Clear shop data. - clif_npc_buy_result(sd, result); } @@ -17989,7 +18114,7 @@ void packetdb_readdb(bool reload) 0, 0, 0, 0, 0, 0, 6, 4, 6, 4, 0, 0, 0, 0, 0, 0, //#0x09C0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0,102, 0, - 0, 0, 0, 0, 2, 0, -1, 0, 2, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 0, 2, 0, -1, -1, 2, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; @@ -18214,6 +18339,9 @@ void packetdb_readdb(bool reload) { clif_parse_client_version, "clientversion"}, { clif_parse_blocking_playcancel, "booking_playcancel"}, { clif_parse_ranklist, "ranklist"}, + /* Market NPC */ + { clif_parse_NPCMarketClosed, "npcmarketclosed" }, + { clif_parse_NPCMarketPurchase, "npcmarketpurchase" }, {NULL,NULL} }; struct { diff --git a/src/map/clif.h b/src/map/clif.h index 8e4f97e3aa..d74ad39532 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -455,6 +455,9 @@ void clif_fixpos(struct block_list *bl); // area void clif_npcbuysell(struct map_session_data* sd, int id); //self void clif_buylist(struct map_session_data *sd, struct npc_data *nd); //self void clif_selllist(struct map_session_data *sd); //self +void clif_npc_market_open(struct map_session_data *sd, struct npc_data *nd); +void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd); +void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd); void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes); //self void clif_scriptnext(struct map_session_data *sd,int npcid); //self void clif_scriptclose(struct map_session_data *sd, int npcid); //self diff --git a/src/map/map.c b/src/map/map.c index 1e4ec44128..d9c0b0bf32 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -69,6 +69,7 @@ char mob_skill_db_re_db[32] = "mob_skill_db_re"; char mob_skill_db2_db[32] = "mob_skill_db2"; char vendings_db[32] = "vendings"; char vending_items_db[32] = "vending_items"; +char market_table[32] = "market"; // log database char log_db_ip[32] = "127.0.0.1"; @@ -3759,6 +3760,8 @@ int inter_config_read(char *cfgName) strcpy( vendings_db, w2 ); else if( strcmpi( w1, "vending_items_db" ) == 0 ) strcpy( vending_items_db, w2 ); + else if (strcmpi(w1, "market_table") == 0) + strcpy(market_table, w2); else //Map Server SQL DB if(strcmpi(w1,"map_server_ip")==0) diff --git a/src/map/map.h b/src/map/map.h index 973a80abfc..da7d81c1eb 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -312,7 +312,8 @@ enum npc_subtype { NPCTYPE_CASHSHOP, /// Cashshop NPCTYPE_ITEMSHOP, /// Itemshop NPCTYPE_POINTSHOP, /// Pointshop - NPCTYPE_TOMB /// Monster tomb + NPCTYPE_TOMB, /// Monster tomb + NPCTYPE_MARKETSHOP, /// Marketshop }; enum e_race { @@ -993,6 +994,7 @@ extern char mob_skill_db_re_db[32]; extern char mob_skill_db2_db[32]; extern char vendings_db[32]; extern char vending_items_db[32]; +extern char market_table[32]; void do_shutdown(void); diff --git a/src/map/npc.c b/src/map/npc.c index b577a31f91..6be28b7574 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -40,6 +40,20 @@ static int npc_mob=0; static int npc_delay_mob=0; static int npc_cache_mob=0; +// Market Shop +#if PACKETVER >= 20131223 +struct s_npc_market { + struct npc_item_list *list; + char exname[NAME_LENGTH+1]; + uint16 count; +}; +static DBMap *NPCMarketDB; /// Stock persistency! Temporary market stocks from `market` table. struct s_npc_market, key: NPC exname +static void npc_market_checkall(void); +static void npc_market_fromsql(void); +#define npc_market_delfromsql(exname,nameid) (npc_market_delfromsql_((exname), (nameid), false)) +#define npc_market_clearfromsql(exname) (npc_market_delfromsql_((exname), 0, true)) +#endif + /// Returns a new npc id that isn't being used in id_db. /// Fatal error if nothing is available. int npc_get_new_npc_id(void) { @@ -1224,6 +1238,26 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd) case NPCTYPE_CASHSHOP: clif_cashshop_show(sd,nd); break; + case NPCTYPE_MARKETSHOP: +#if PACKETVER >= 20131223 + { + unsigned short i; + + for (i = 0; i < nd->u.shop.count; i++) { + if (nd->u.shop.shop_item[i].qty) + break; + } + + if (i == nd->u.shop.count) { + clif_colormes(sd, color_table[COLOR_RED], msg_txt(sd, 534)); + return false; + } + + sd->npc_shopid = nd->bl.id; + clif_npc_market_open(sd, nd); + } +#endif + break; case NPCTYPE_SCRIPT: run_script(nd->u.scr.script,0,sd->bl.id,nd->bl.id); break; @@ -1318,7 +1352,7 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type) sd->state.callshop = 0; sd->npc_shopid = id; - if (type==0) { + if (type == 0) { clif_buylist(sd,nd); } else { clif_selllist(sd); @@ -1410,9 +1444,14 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, uns return 0; } -//npc_buylist for script-controlled shops. -static int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd) -{ +/** + * npc_buylist for script-controlled shops. + * @param sd Player who bought + * @param n Number of item + * @param item_list List of item + * @param nd Attached NPC + **/ +static int npc_buylist_sub(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list, struct npc_data* nd) { char npc_ev[EVENT_NAME_LENGTH]; int i; int key_nameid = 0; @@ -1423,10 +1462,9 @@ static int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* i script_cleararray_pc(sd, "@bought_quantity", (void*)0); // save list of bought items - for( i = 0; i < n; i++ ) - { - script_setarray_pc(sd, "@bought_nameid", i, (void*)(intptr_t)item_list[i*2+1], &key_nameid); - script_setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i*2], &key_amount); + for (i = 0; i < n; i++) { + script_setarray_pc(sd, "@bought_nameid", i, (void*)(intptr_t)item_list[i].nameid, &key_nameid); + script_setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i].qty, &key_amount); } // invoke event @@ -1519,16 +1557,20 @@ int npc_cashshop_buy(struct map_session_data *sd, unsigned short nameid, int amo return 0; } -/// Player item purchase from npc shop. -/// -/// @param item_list 'n' pairs -/// @return result code for clif_parse_NpcBuyListSend -int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) -{ +/** + * Shop buylist + * @param sd Player who attempt to buy + * @param n Number of item will be bought + * @param *item_list List of item will be bought + * @return result code for clif_parse_NpcBuyListSend/clif_npc_market_purchase_ack + **/ +uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list) { struct npc_data* nd; + struct npc_item_list *shop = NULL; double z; int i,j,k,w,skill,new_,count = 0; char output[CHAT_SIZE_MAX]; + uint8 market_index[MAX_INVENTORY]; nullpo_retr(3, sd); nullpo_retr(3, item_list); @@ -1536,48 +1578,58 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) nd = npc_checknear(sd,map_id2bl(sd->npc_shopid)); if( nd == NULL ) return 3; - if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP ) + if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP ) + return 3; + if (!item_list || !n) return 3; z = 0; w = 0; new_ = 0; + + shop = nd->u.shop.shop_item; + + memset(market_index, 0, sizeof(market_index)); // process entries in buy list, one by one - for( i = 0; i < n; ++i ) - { - unsigned short nameid; - int amount, value; + for( i = 0; i < n; ++i ) { + unsigned short nameid, amount; + int value; // find this entry in the shop's sell list ARR_FIND( 0, nd->u.shop.count, j, - item_list[i*2+1] == nd->u.shop.shop_item[j].nameid || //Normal items - item_list[i*2+1] == itemdb_viewid(nd->u.shop.shop_item[j].nameid) //item_avail replacement + item_list[i].nameid == shop[j].nameid || //Normal items + item_list[i].nameid == itemdb_viewid(shop[j].nameid) //item_avail replacement ); if( j == nd->u.shop.count ) return 3; // no such item in shop - amount = item_list[i*2+0]; - nameid = item_list[i*2+1] = nd->u.shop.shop_item[j].nameid; //item_avail replacement - value = nd->u.shop.shop_item[j].value; +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) { + if (item_list[i].qty > shop[j].qty) + return 3; + market_index[i] = j; + } +#endif + + amount = item_list[i].qty; + nameid = item_list[i].nameid = shop[j].nameid; //item_avail replacement + value = shop[j].value; if( !itemdb_exists(nameid) ) return 3; // item no longer in itemdb - if( !itemdb_isstackable(nameid) && amount > 1 ) - { //Exploit? You can't buy more than 1 of equipment types o.O + if( !itemdb_isstackable(nameid) && amount > 1 ) { //Exploit? You can't buy more than 1 of equipment types o.O ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %hu!\n", sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); - amount = item_list[i*2+0] = 1; + amount = item_list[i].qty = 1; } - if( nd->master_nd ) - {// Script-controlled shops decide by themselves, what can be bought and for what price. + if( nd->master_nd ) { // Script-controlled shops decide by themselves, what can be bought and for what price. continue; } - switch( pc_checkadditem(sd,nameid,amount) ) - { + switch( pc_checkadditem(sd,nameid,amount) ) { case CHKADDITEM_EXIST: break; @@ -1596,11 +1648,12 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) w += itemdb_weight(nameid) * amount; } - if( nd->master_nd != NULL ) //Script-based shops. + if (nd->master_nd) //Script-based shops. return npc_buylist_sub(sd,n,item_list,nd->master_nd); switch(nd->subtype) { case NPCTYPE_SHOP: + case NPCTYPE_MARKETSHOP: if (z > (double)sd->status.zeny) return 1; // Not enough Zeny break; @@ -1645,6 +1698,7 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) switch(nd->subtype) { case NPCTYPE_SHOP: + case NPCTYPE_MARKETSHOP: pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL); break; case NPCTYPE_ITEMSHOP: @@ -1666,16 +1720,24 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) break; } - for( i = 0; i < n; ++i ) - { - unsigned short nameid = item_list[i*2+1]; - int amount = item_list[i*2+0]; + for( i = 0; i < n; ++i ) { + unsigned short nameid = item_list[i].nameid; + unsigned short amount = item_list[i].qty; struct item item_tmp; +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) { + j = market_index[i]; + if (amount > shop[j].qty) + return 1; + shop[j].qty -= amount; + npc_market_tosql(nd->exname, &shop[j]); + } +#endif + if (itemdb_type(nameid) == IT_PETEGG) pet_create_egg(sd, nameid); - else - { + else { memset(&item_tmp,0,sizeof(item_tmp)); item_tmp.nameid = nameid; item_tmp.identify = 1; @@ -1685,14 +1747,12 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) } // custom merchant shop exp bonus - if( battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0 ) - { + if( battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0 ) { uint16 sk_idx = skill_get_index(MC_DISCOUNT); if( sd->status.skill[sk_idx].flag >= SKILL_FLAG_REPLACED_LV_0 ) skill = sd->status.skill[sk_idx].flag - SKILL_FLAG_REPLACED_LV_0; - if( skill > 0 ) - { + if( skill > 0 ) { z = z * (double)skill * (double)battle_config.shop_exp/10000.; if( z < 1 ) z = 1; @@ -1708,7 +1768,6 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) return 0; } - /// npc_selllist for script-controlled shops static int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd) { @@ -1771,7 +1830,7 @@ static int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* /// /// @param item_list 'n' pairs /// @return result code for clif_parse_NpcSellListSend -int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) +uint8 npc_selllist(struct map_session_data* sd, int n, unsigned short *item_list) { double z; int i,skill; @@ -1957,7 +2016,7 @@ int npc_unload(struct npc_data* nd, bool single) { if( single && nd->bl.m != -1 ) map_remove_questinfo(nd->bl.m, nd); - if( (nd->subtype == NPCTYPE_SHOP || nd->subtype == NPCTYPE_CASHSHOP || nd->subtype == NPCTYPE_ITEMSHOP || nd->subtype == NPCTYPE_POINTSHOP) && nd->src_id == 0) //src check for duplicate shops [Orcao] + if( (nd->subtype == NPCTYPE_SHOP || nd->subtype == NPCTYPE_CASHSHOP || nd->subtype == NPCTYPE_ITEMSHOP || nd->subtype == NPCTYPE_POINTSHOP || nd->subtype == NPCTYPE_MARKETSHOP) && nd->src_id == 0) //src check for duplicate shops [Orcao] aFree(nd->u.shop.shop_item); else if( nd->subtype == NPCTYPE_SCRIPT ) { struct s_mapiterator* iter; @@ -2375,8 +2434,9 @@ static const char* npc_parse_warp(char* w1, char* w2, char* w3, char* w4, const * ,,,%TAB%cashshop%TAB%%TAB%,:{,:...} * ,,,%TAB%itemshop%TAB%%TAB%,{:},:{,:...} * ,,,%TAB%pointshop%TAB%%TAB%,{:},:{,:...} + * ,,,%TAB%marketshop%TAB%%TAB%,::{,::...} * @param w1 : word 1 before tab (,,,) - * @param w2 : word 2 before tab (shop|cashshop|itemshop|pointshop), keyword that sent us in this parsing + * @param w2 : word 2 before tab (shop|cashshop|itemshop|pointshop|marketshop), keyword that sent us in this parsing * @param w3 : word 3 before tab () * @param w4 : word 4 before tab (,) * @param start : index to start parsing @@ -2420,6 +2480,8 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const type = NPCTYPE_ITEMSHOP; else if( !strcasecmp(w2,"pointshop") ) type = NPCTYPE_POINTSHOP; + else if( !strcasecmp(w2, "marketshop") ) + type = NPCTYPE_MARKETSHOP; else type = NPCTYPE_SHOP; @@ -2459,6 +2521,14 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const p = strchr(p+1,','); break; } + case NPCTYPE_MARKETSHOP: +#if PACKETVER < 20131223 + ShowError("npc_parse_shop: (MARKETSHOP) Feature is disabled, need client 20131223 or newer. Ignoring file '%s', line '%d\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer, start - buffer), w1, w2, w3, w4); + return strchr(start, '\n'); // skip and continue +#else + is_discount = 0; + break; +#endif default: is_discount = 1; break; @@ -2468,25 +2538,43 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const nd->u.shop.count = 0; while ( p ) { - unsigned short nameid2; - int value; + unsigned short nameid2, qty = 0; + int value, i = 0; struct item_data* id; + bool skip = false; + if( p == NULL ) break; - if( sscanf(p, ",%hu:%d", &nameid2, &value) != 2 ) { - ShowError("npc_parse_shop: Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); - break; + switch(type) { + case NPCTYPE_SHOP: + if (sscanf(p, ",%hu:%d", &nameid2, &value) != 2) { + ShowError("npc_parse_shop: (SHOP) Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer, start - buffer), w1, w2, w3, w4); + skip = true; + } + break; + case NPCTYPE_MARKETSHOP: +#if PACKETVER >= 20131223 + if (sscanf(p, ",%hu:%d:%hu", &nameid2, &value, &qty) != 3) { + ShowError("npc_parse_shop: (MARKETSHOP) Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer, start - buffer), w1, w2, w3, w4); + skip = true; + } +#endif + break; } + + if (skip) + break; + if( (id = itemdb_exists(nameid2)) == NULL ) { ShowWarning("npc_parse_shop: Invalid sell item in file '%s', line '%d' (id '%hu').\n", filepath, strline(buffer,start-buffer), nameid2); p = strchr(p+1,','); continue; } if( value < 0 ) { - if( type == NPCTYPE_SHOP ) value = id->value_buy; + if (type == NPCTYPE_SHOP || type == NPCTYPE_MARKETSHOP) value = id->value_buy; else value = 0; // Cashshop doesn't have a "buy price" in the item_db } - if( (type == NPCTYPE_SHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP) && value == 0 ) { // NPC selling items for free! + if (value == 0 && (type == NPCTYPE_SHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP || type == NPCTYPE_MARKETSHOP)) { // NPC selling items for free! ShowWarning("npc_parse_shop: Item %s [%hu] is being sold for FREE in file '%s', line '%d'.\n", id->name, nameid2, filepath, strline(buffer,start-buffer)); } @@ -2494,17 +2582,37 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const ShowWarning("npc_parse_shop: Item %s [%hu] discounted buying price (%d->%d) is less than overcharged selling price (%d->%d) at file '%s', line '%d'.\n", id->name, nameid2, value, (int)(value*0.75), id->value_sell, (int)(id->value_sell*1.24), filepath, strline(buffer,start-buffer)); } + if (type == NPCTYPE_MARKETSHOP && (!qty || qty > UINT16_MAX)) { + ShowWarning("npc_parse_shop: Item %s [%hu] is stocked with invalid value %d, changed to 1. File '%s', line '%d'.\n", + id->name, nameid2, filepath, strline(buffer,start-buffer)); + qty = 1; + } //for logs filters, atcommands and iteminfo script command if( id->maxchance == 0 ) id->maxchance = -1; // -1 would show that the item's sold in NPC Shop + +#if PACKETVER >= 20131223 + if (nd->u.shop.count && type == NPCTYPE_MARKETSHOP) { + // Duplicate entry? Replace the value + ARR_FIND(0, nd->u.shop.count, i, nd->u.shop.shop_item[i].nameid == nameid); + if (i != nd->u.shop.count) { + nd->u.shop.shop_item[i].qty = qty; + nd->u.shop.shop_item[i].value = value; + p = strchr(p+1,','); + continue; + } + } +#endif - if (nd->u.shop.count > 0) - RECREATE(nd->u.shop.shop_item, struct npc_item_list,nd->u.shop.count+1); - else - CREATE(nd->u.shop.shop_item, struct npc_item_list,1); + RECREATE(nd->u.shop.shop_item, struct npc_item_list,nd->u.shop.count+1); nd->u.shop.shop_item[nd->u.shop.count].nameid = nameid2; nd->u.shop.shop_item[nd->u.shop.count].value = value; +#if PACKETVER >= 20131223 + nd->u.shop.shop_item[nd->u.shop.count].flag = 0; + if (type == NPCTYPE_MARKETSHOP) + nd->u.shop.shop_item[nd->u.shop.count].qty = qty; +#endif nd->u.shop.count++; p = strchr(p+1,','); } @@ -2519,6 +2627,7 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const else if (type == NPCTYPE_POINTSHOP) safestrncpy(nd->u.shop.pointshop_str,point_str,strlen(point_str)+1); // Point shop currency nd->u.shop.discount = is_discount; } + nd->bl.prev = nd->bl.next = NULL; nd->bl.m = m; nd->bl.x = x; @@ -2531,6 +2640,14 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const ++npc_shop; nd->bl.type = BL_NPC; nd->subtype = type; +#if PACKETVER >= 20131223 + // Insert market data to table + if (nd->subtype == NPCTYPE_MARKETSHOP) { + uint16 i; + for (i = 0; i < nd->u.shop.count; i++) + npc_market_tosql(nd->exname, &nd->u.shop.shop_item[i]); + } +#endif if( m >= 0 ) {// normal shop npc map_addnpc(m,nd); @@ -2857,7 +2974,7 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch type = dnd->subtype; // get placement - if( (type == NPCTYPE_SHOP || type == NPCTYPE_CASHSHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP || type == NPCTYPE_SCRIPT) && strcmp(w1, "-") == 0 ) {// floating shop/chashshop/itemshop/pointshop/script + if ((type == NPCTYPE_SHOP || type == NPCTYPE_CASHSHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP || type == NPCTYPE_SCRIPT || type == NPCTYPE_MARKETSHOP) && strcmp(w1, "-") == 0) {// floating shop/chashshop/itemshop/pointshop/script x = y = dir = 0; m = -1; } else { @@ -2908,6 +3025,7 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch case NPCTYPE_CASHSHOP: case NPCTYPE_ITEMSHOP: case NPCTYPE_POINTSHOP: + case NPCTYPE_MARKETSHOP: ++npc_shop; nd->u.shop.shop_item = dnd->u.shop.shop_item; nd->u.shop.count = dnd->u.shop.count; @@ -3059,6 +3177,176 @@ int npc_instanceinit(struct npc_data* nd) return 0; } +#if PACKETVER >= 20131223 +/** + * Saves persistent NPC Market Data into SQL + * @param exname NPC exname + * @param nameid Item ID + * @param qty Stock + **/ +void npc_market_tosql(const char *exname, struct npc_item_list *list) { + SqlStmt* stmt = SqlStmt_Malloc(mmysql_handle); + if (SQL_ERROR == SqlStmt_Prepare(stmt, "REPLACE INTO `%s` (`name`,`nameid`,`price`,`amount`,`flag`) VALUES ('%s','%hu','%d','%hu','%"PRIu8"')", + market_table, exname, list->nameid, list->value, list->qty, list->flag) || + SQL_ERROR == SqlStmt_Execute(stmt)) + SqlStmt_ShowDebug(stmt); + SqlStmt_Free(stmt); +} + +/** + * Removes persistent NPC Market Data from SQL + * @param exname NPC exname + * @param nameid Item ID + * @param clear True: will removes all records related with the NPC + **/ +void npc_market_delfromsql_(const char *exname, unsigned short nameid, bool clear) { + SqlStmt* stmt = SqlStmt_Malloc(mmysql_handle); + if (clear) { + if( SQL_ERROR == SqlStmt_Prepare(stmt, "DELETE FROM `%s` WHERE `name`='%s'", market_table, exname) || + SQL_ERROR == SqlStmt_Execute(stmt)) + SqlStmt_ShowDebug(stmt); + } else { + if (SQL_ERROR == SqlStmt_Prepare(stmt, "DELETE FROM `%s` WHERE `name`='%s' AND `nameid`='%d' LIMIT 1", market_table, exname, nameid) || + SQL_ERROR == SqlStmt_Execute(stmt)) + SqlStmt_ShowDebug(stmt); + } + SqlStmt_Free(stmt); +} + +/** + * Check NPC Market Shop for each entry + **/ +static int npc_market_checkall_sub(DBKey key, DBData *data, va_list ap) { + struct s_npc_market *market = (struct s_npc_market *)db_data2ptr(data); + struct npc_data *nd = NULL; + uint16 i; + + if (!market) + return 1; + + nd = npc_name2id(market->exname); + if (!nd) { + ShowInfo("npc_market_checkall_sub: NPC '%s' not found, removing...\n", market->exname); + npc_market_clearfromsql(market->exname); + return 1; + } + else if (nd->subtype != NPCTYPE_MARKETSHOP || !nd->u.shop.shop_item || !nd->u.shop.count ) { + ShowError("npc_market_checkall_sub: NPC '%s' is not proper for market, removing...\n", nd->exname); + npc_market_clearfromsql(nd->exname); + return 1; + } + + if (!market->count || !market->list) + return 1; + + for (i = 0; i < market->count; i++) { + struct npc_item_list *list = &market->list[i]; + uint16 j; + + if (!list->nameid || !itemdb_exists(list->nameid)) { + ShowError("npc_market_checkall_sub: NPC '%s' sells invalid item '%hu', deleting...\n", nd->exname, list->nameid); + npc_market_delfromsql(nd->exname, list->nameid); + continue; + } + + // Reloading stock from `market` table + ARR_FIND(0, nd->u.shop.count, j, nd->u.shop.shop_item[j].nameid == list->nameid); + if (j != nd->u.shop.count) { + nd->u.shop.shop_item[j].value = list->value; + nd->u.shop.shop_item[j].qty = list->qty; + nd->u.shop.shop_item[j].flag = list->flag; + npc_market_tosql(nd->exname, &nd->u.shop.shop_item[j]); + continue; + } + + if (list->flag&1) { // Item added by npcshopitem/npcshopadditem, add new entry + RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+1); + nd->u.shop.shop_item[j].nameid = list->nameid; + nd->u.shop.shop_item[j].value = list->value; + nd->u.shop.shop_item[j].qty = list->qty; + nd->u.shop.shop_item[j].flag = list->flag; + nd->u.shop.count++; + npc_market_tosql(nd->exname, &nd->u.shop.shop_item[j]); + } + else { // Removing "out-of-date" entry + ShowError("npc_market_checkall_sub: NPC '%s' does not sell item %hu (qty %hu), deleting...\n", nd->exname, list->nameid, list->qty); + npc_market_delfromsql(nd->exname, list->nameid); + } + } + + return 0; +} + +/** + * Clear NPC market single entry + **/ +static int npc_market_free(DBKey key, DBData *data, va_list ap) { + struct s_npc_market *market = (struct s_npc_market *)db_data2ptr(data); + if (!market) + return 0; + if (market->list) { + aFree(market->list); + market->list = NULL; + } + aFree(market); + return 1; +} + +/** + * Check all existing NPC Market Shop after first loading map-server or after reloading scripts. + * Overwrite stocks from NPC by using stock entries from `market` table. + **/ +static void npc_market_checkall(void) { + if (!db_size(NPCMarketDB)) + return; + else { + ShowInfo("Checking '"CL_WHITE"%d"CL_RESET"' NPC Markets...\n", db_size(NPCMarketDB)); + NPCMarketDB->foreach(NPCMarketDB, npc_market_checkall_sub); + ShowStatus("Done checking '"CL_WHITE"%d"CL_RESET"' NPC Markets.\n", db_size(NPCMarketDB)); + NPCMarketDB->clear(NPCMarketDB, npc_market_free); + } +} + +/** + * Loads persistent NPC Market Data from SQL, use the records after NPCs init'd to reuse the stock values. + **/ +static void npc_market_fromsql(void) { + uint32 count = 0; + + if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT `name`,`nameid`,`price`,`amount`,`flag` FROM `%s` ORDER BY `name`", market_table)) { + Sql_ShowDebug(mmysql_handle); + return; + } + + while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) { + char *data; + struct s_npc_market *market; + struct npc_item_list list; + + Sql_GetData(mmysql_handle, 0, &data, NULL); + + if (!(market = (struct s_npc_market *)strdb_get(NPCMarketDB,data))) { + CREATE(market, struct s_npc_market, 1); + market->count = 0; + safestrncpy(market->exname, data, strlen(data)+1); + strdb_put(NPCMarketDB, market->exname, market); + } + + Sql_GetData(mmysql_handle, 1, &data, NULL); list.nameid = atoi(data); + Sql_GetData(mmysql_handle, 2, &data, NULL); list.value = atoi(data); + Sql_GetData(mmysql_handle, 3, &data, NULL); list.qty = atoi(data); + Sql_GetData(mmysql_handle, 4, &data, NULL); list.flag = atoi(data); + + RECREATE(market->list, struct npc_item_list, market->count+1); + market->list[market->count++] = list; + count++; + } + Sql_FreeResult(mmysql_handle); + + ShowStatus("Done loading '"CL_WHITE"%d"CL_RESET"' entries for '"CL_WHITE"%d"CL_RESET"' NPC Markets from '"CL_WHITE"%s"CL_RESET"' table.\n", count, db_size(NPCMarketDB), market_table); +} +#endif + //Set mapcell CELL_NPC to trigger event later void npc_setcells(struct npc_data* nd) { @@ -3928,7 +4216,7 @@ int npc_parsesrcfile(const char* filepath, bool runOnInit) if( strcasecmp(w2,"warp") == 0 && count > 3 ) p = npc_parse_warp(w1,w2,w3,w4, p, buffer, filepath); - else if( (!strcasecmp(w2,"shop") || !strcasecmp(w2,"cashshop") || !strcasecmp(w2,"itemshop") || !strcasecmp(w2,"pointshop")) && count > 3 ) + else if( (!strcasecmp(w2,"shop") || !strcasecmp(w2,"cashshop") || !strcasecmp(w2,"itemshop") || !strcasecmp(w2,"pointshop") || !strcasecmp(w2,"marketshop") ) && count > 3 ) p = npc_parse_shop(w1,w2,w3,w4, p, buffer, filepath); else if( strcasecmp(w2,"script") == 0 && count > 3 ) { if( strcasecmp(w1,"function") == 0 ) @@ -4057,6 +4345,10 @@ int npc_reload(void) { //Remove all npcs/mobs. [Skotlex] +#if PACKETVER >= 20131223 + npc_market_fromsql(); +#endif + iter = mapit_geteachiddb(); for( bl = (struct block_list*)mapit_first(iter); mapit_exists(iter); bl = (struct block_list*)mapit_next(iter) ) { switch(bl->type) { @@ -4130,6 +4422,10 @@ int npc_reload(void) { if(!CheckForCharServer()){ ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInit")); } + +#if PACKETVER >= 20131223 + npc_market_checkall(); +#endif return 0; } @@ -4170,6 +4466,9 @@ void do_final_npc(void) { ev_db->destroy(ev_db, NULL); npcname_db->destroy(npcname_db, NULL); npc_path_db->destroy(npc_path_db, NULL); +#if PACKETVER >= 20131223 + NPCMarketDB->destroy(NPCMarketDB, npc_market_free); +#endif ers_destroy(timer_event_ers); npc_clearsrcfile(); } @@ -4226,6 +4525,10 @@ void do_init_npc(void){ ev_db = strdb_alloc((DBOptions)(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA),2*NAME_LENGTH+2+1); npcname_db = strdb_alloc(DB_OPT_BASE,NAME_LENGTH); npc_path_db = strdb_alloc(DB_OPT_BASE|DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA,80); +#if PACKETVER >= 20131223 + NPCMarketDB = strdb_alloc(DB_OPT_BASE, NAME_LENGTH+1); + npc_market_fromsql(); +#endif timer_event_ers = ers_new(sizeof(struct timer_event_data),"clif.c::timer_event_ers",ERS_OPT_NONE); @@ -4248,6 +4551,10 @@ void do_init_npc(void){ memset(script_event, 0, sizeof(script_event)); npc_read_event_script(); +#if PACKETVER >= 20131223 + npc_market_checkall(); +#endif + //Debug function to locate all endless loop warps. if (battle_config.warp_point_debug) npc_debug_warps(); diff --git a/src/map/npc.h b/src/map/npc.h index ed6eac3834..39587a3fc1 100644 --- a/src/map/npc.h +++ b/src/map/npc.h @@ -15,13 +15,26 @@ struct view_data; struct npc_timerevent_list { int timer,pos; }; + struct npc_label_list { char name[NAME_LENGTH]; int pos; }; + +/// Item list for NPC sell/buy list struct npc_item_list { unsigned short nameid; unsigned int value; +#if PACKETVER >= 20131223 + unsigned short qty; ///< Stock counter (Market shop) + uint8 flag; ///< 1: Item added by npcshopitem/npcshopadditem, force load! (Market shop) +#endif +}; + +/// List of bought/sold item for NPC shops +struct s_npc_buy_list { + unsigned short qty; ///< Amount of item will be bought + unsigned short nameid; ///< ID of item will be bought }; struct npc_data { @@ -126,8 +139,8 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd); int npc_scriptcont(struct map_session_data* sd, int id, bool closing); struct npc_data* npc_checknear(struct map_session_data* sd, struct block_list* bl); int npc_buysellsel(struct map_session_data* sd, int id, int type); -int npc_buylist(struct map_session_data* sd,int n, unsigned short* item_list); -int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list); +uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list); +uint8 npc_selllist(struct map_session_data* sd, int n, unsigned short *item_list); void npc_parse_mob2(struct spawn_data* mob); bool npc_viewisid(const char * viewid); struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y); @@ -179,6 +192,11 @@ extern struct npc_data* fake_nd; int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, unsigned short* item_list); bool npc_shop_discount(enum npc_subtype type, bool discount); +#if PACKETVER >= 20131223 +void npc_market_tosql(const char *exname, struct npc_item_list *list); +void npc_market_delfromsql_(const char *exname, unsigned short nameid, bool clear); +#endif + #ifdef SECURE_NPCTIMEOUT int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data); #endif diff --git a/src/map/script.c b/src/map/script.c index 960f799766..e5fa02c525 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -15628,28 +15628,45 @@ BUILDIN_FUNC(callshop) return 0; } shopname = script_getstr(st, 2); - if( script_hasdata(st,3) ) + if (script_hasdata(st,3)) flag = script_getnum(st,3); nd = npc_name2id(shopname); - if( !nd || nd->bl.type != BL_NPC || (nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP) ) - { + if( !nd || nd->bl.type != BL_NPC || (nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP) ) { ShowError("buildin_callshop: Shop [%s] not found (or NPC is not shop type)\n", shopname); script_pushint(st,0); return 1; } - if( nd->subtype == NPCTYPE_SHOP || nd->subtype == NPCTYPE_ITEMSHOP || nd->subtype == NPCTYPE_POINTSHOP ) - { + if (nd->subtype == NPCTYPE_SHOP || nd->subtype == NPCTYPE_ITEMSHOP || nd->subtype == NPCTYPE_POINTSHOP) { // flag the user as using a valid script call for opening the shop (for floating NPCs) sd->state.callshop = 1; - switch( flag ) - { + switch (flag) { case 1: npc_buysellsel(sd,nd->bl.id,0); break; //Buy window case 2: npc_buysellsel(sd,nd->bl.id,1); break; //Sell window default: clif_npcbuysell(sd,nd->bl.id); break; //Show menu } } +#if PACKETVER >= 20131223 + else if (nd->subtype == NPCTYPE_MARKETSHOP) { + unsigned short i; + + for (i = 0; i < nd->u.shop.count; i++) { + if (nd->u.shop.shop_item[i].qty) + break; + } + + if (i == nd->u.shop.count) { + clif_colormes(sd, color_table[COLOR_RED], msg_txt(sd, 534)); + return false; + } + + sd->npc_shopid = nd->bl.id; + clif_npc_market_open(sd, nd); + script_pushint(st,1); + return SCRIPT_CMD_SUCCESS; + } +#endif else clif_cashshop_show(sd, nd); @@ -15664,22 +15681,35 @@ BUILDIN_FUNC(npcshopitem) struct npc_data* nd = npc_name2id(npcname); int n, i; int amount; + uint16 offs = 2; - if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP ) ) - { //Not found. + if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP ) ) { // Not found. script_pushint(st,0); return 0; } +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) { + offs = 3; + npc_market_delfromsql_(nd->exname, 0, true); + } +#endif + // get the count of new entries - amount = (script_lastdata(st)-2)/2; + amount = (script_lastdata(st)-2)/offs; // generate new shop item list RECREATE(nd->u.shop.shop_item, struct npc_item_list, amount); - for( n = 0, i = 3; n < amount; n++, i+=2 ) - { + for (n = 0, i = 3; n < amount; n++, i+=offs) { nd->u.shop.shop_item[n].nameid = script_getnum(st,i); nd->u.shop.shop_item[n].value = script_getnum(st,i+1); +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) { + nd->u.shop.shop_item[n].qty = script_getnum(st,i+2); + nd->u.shop.shop_item[n].flag = 1; + npc_market_tosql(nd->exname, &nd->u.shop.shop_item[n]); + } +#endif } nd->u.shop.count = n; @@ -15693,19 +15723,47 @@ BUILDIN_FUNC(npcshopadditem) struct npc_data* nd = npc_name2id(npcname); int n, i; int amount; + uint16 offs = 2; - if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP ) ) - { //Not found. + if (!nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP)) { // Not found. script_pushint(st,0); return 0; } + if (nd->subtype == NPCTYPE_MARKETSHOP) + offs = 3; + // get the count of new entries - amount = (script_lastdata(st)-2)/2; + amount = (script_lastdata(st)-2)/offs; + +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) { + for (n = 0, i = 3; n < amount; n++, i += offs) { + uint16 nameid = script_getnum(st,i), j; + + // Check existing entries + ARR_FIND(0, nd->u.shop.count, j, nd->u.shop.shop_item[j].nameid == nameid); + if (j == nd->u.shop.count) { + RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+1); + j = nd->u.shop.count; + nd->u.shop.shop_item[j].flag = 1; + nd->u.shop.count++; + } + + nd->u.shop.shop_item[j].nameid = nameid; + nd->u.shop.shop_item[j].value = script_getnum(st,i+1); + nd->u.shop.shop_item[j].qty = script_getnum(st,i+2); + + npc_market_tosql(nd->exname, &nd->u.shop.shop_item[j]); + } + script_pushint(st,1); + return SCRIPT_CMD_SUCCESS; + } +#endif // append new items to existing shop item list RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+amount); - for( n = nd->u.shop.count, i = 3; n < nd->u.shop.count+amount; n++, i+=2 ) + for (n = nd->u.shop.count, i = 3; n < nd->u.shop.count+amount; n++, i+=offs) { nd->u.shop.shop_item[n].nameid = script_getnum(st,i); nd->u.shop.shop_item[n].value = script_getnum(st,i+1); @@ -15724,7 +15782,7 @@ BUILDIN_FUNC(npcshopdelitem) int amount; int size; - if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP ) ) { // Not found. + if (!nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP)) { // Not found. script_pushint(st,0); return 0; } @@ -15738,7 +15796,12 @@ BUILDIN_FUNC(npcshopdelitem) ARR_FIND( 0, size, n, nd->u.shop.shop_item[n].nameid == nameid ); if( n < size ) { - memmove(&nd->u.shop.shop_item[n], &nd->u.shop.shop_item[n+1], sizeof(nd->u.shop.shop_item[0])*(size-n)); + if (n+1 != size) + memmove(&nd->u.shop.shop_item[n], &nd->u.shop.shop_item[n+1], sizeof(nd->u.shop.shop_item[0])*(size-n)); +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) + npc_market_delfromsql_(nd->exname, nameid, false); +#endif size--; } } @@ -15750,7 +15813,10 @@ BUILDIN_FUNC(npcshopdelitem) return SCRIPT_CMD_SUCCESS; } -//Sets a script to attach to a shop npc. +/** + * Sets a script to attach to a shop npc. + * npcshopattach ""; + **/ BUILDIN_FUNC(npcshopattach) { const char* npcname = script_getstr(st,2); @@ -15760,8 +15826,7 @@ BUILDIN_FUNC(npcshopattach) if( script_hasdata(st,3) ) flag = script_getnum(st,3); - if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP ) ) - { //Not found. + if (!nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP)) { // Not found. script_pushint(st,0); return 0; } @@ -19804,6 +19869,55 @@ BUILDIN_FUNC(mergeitem) { return SCRIPT_CMD_SUCCESS; } +/** + * Update an entry from NPC shop. + * npcshopupdate "",,{,} + **/ +BUILDIN_FUNC(npcshopupdate) { + const char* npcname = script_getstr(st, 2); + struct npc_data* nd = npc_name2id(npcname); + uint16 nameid = script_getnum(st, 3); + int price = script_getnum(st, 4); + uint16 stock = 0; + int i; + + if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP ) ) { // Not found. + script_pushint(st,0); + return SCRIPT_CMD_FAILURE; + } + + if (!nd->u.shop.count) { + ShowError("buildin_npcshopupdate: Attempt to update empty shop from '%s'.\n", nd->exname); + script_pushint(st,0); + return SCRIPT_CMD_FAILURE; + } + + if (nd->subtype == NPCTYPE_MARKETSHOP) { + FETCH(5, stock); + } + else if ((price = cap_value(price, 0, INT_MAX)) == 0) { // Nothing to do here... + script_pushint(st,1); + return SCRIPT_CMD_SUCCESS; + } + + for (i = 0; i < nd->u.shop.count; i++) { + if (nd->u.shop.shop_item[i].nameid == nameid) { + + if (price != 0) + nd->u.shop.shop_item[i].value = price; +#if PACKETVER >= 20131223 + if (nd->subtype == NPCTYPE_MARKETSHOP) { + nd->u.shop.shop_item[i].qty = stock; + npc_market_tosql(nd->exname, &nd->u.shop.shop_item[i]); + } +#endif + } + } + + script_pushint(st,1); + return SCRIPT_CMD_SUCCESS; +} + #include "../custom/script.inc" // declarations that were supposed to be exported from npc_chat.c @@ -20354,6 +20468,8 @@ struct script_function buildin_func[] = { BUILDIN_DEF(countspiritball,"?"), BUILDIN_DEF(mergeitem,"?"), + BUILDIN_DEF(npcshopupdate,"sii?"), + #include "../custom/script_def.inc" {NULL,NULL,NULL},