Browse Source

NPC Market Shop support
* New shop script definition: `<map name>,<x>,<y>,<facing>%TAB%marketshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>:<quantity>{,<itemid>:<price>:<quantity>...}`
* Added script command to update shop NPC: 'npcshopupdate "<name>",<itemid>,<price>{,<stock>}'
* Added NPCMarketDB (DBMap) for market data persistance method.
* Added `market_table` definition for market table in conf/inter_athena.conf.
* Thank to @aleos89, @Lemongrass, @icxbb-xx, merged HerculesWS/Hercules@cf19b26.

NOTES:
* Minimum client needed 2013-12-23 (but this client has bugs there-and-there).
* There's new table, see `upgrade_20150327.sql`.
* Market shop doesn't support discount.
* Added items by script `npcshopitem` or `npchopadditem` will be assumed as persistance items, will be loaded on next script reload or server start even market_shop NPC does't list them (unless NPC is not exists, entries will be removed).

Signed-off-by: Cydh Ramdh <house.bad@gmail.com>

Cydh Ramdh 10 years ago
parent
commit
7d929a06a3

+ 1 - 0
conf/inter_athena.conf

@@ -138,6 +138,7 @@ mob_skill_db2_db: mob_skill_db2
 mapreg_db: mapreg
 mapreg_db: mapreg
 vending_db: vendings
 vending_db: vendings
 vending_items_db: vending_items
 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 item_db, mob_db and mob_skill_db for the map server? (yes/no)
 use_sql_db: no
 use_sql_db: no

+ 1 - 1
conf/msg_conf/map_msg.conf

@@ -537,7 +537,7 @@
 532: Shadow Right Accessory, 
 532: Shadow Right Accessory, 
 533: Shadow Left Accessory, 
 533: Shadow Left Accessory, 
 
 
-//534: // Free
+534: Shop is out of stock! Please come back later.
 
 
 // Bot detect messages (currently unused)
 // 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.
 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.

+ 5 - 3
db/packet_db.txt

@@ -2310,10 +2310,12 @@ packet_keys: 0x631C511C,0x111C111C,0x111C111C // [Shakto]
 //0x097E,12 //ZC_UPDATE_RANKING_POINT
 //0x097E,12 //ZC_UPDATE_RANKING_POINT
 0x09B4,6,dull,0 //Cash Shop - Special Tab
 0x09B4,6,dull,0 //Cash Shop - Special Tab
 0x09CE,102,itemmonster,2
 0x09CE,102,itemmonster,2
-0x09D4,2,dull,0 //npcshopclosed
+0x09D4,2,npcshopclosed,0
 //NPC Market
 //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
 0x09DF,7
 
 
 //Add new packets here
 //Add new packets here

+ 21 - 2
doc/script_commands.txt

@@ -282,6 +282,8 @@ these floating NPC objects are for. More on that below.
 -%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
 -%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
 <map name>,<x>,<y>,<facing>%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
 <map name>,<x>,<y>,<facing>%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
 
 
+<map name>,<x>,<y>,<facing>%TAB%marketshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>:<quantity>{,<itemid>:<price>:<quantity>...}
+
 This will define a shop NPC, which, when triggered (which can only be done by 
 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 
 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 
 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.
 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 <qty>
+   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.
 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 <qty>
+   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.
 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 "<name>",<item_id>,<price>{,<stock>}
+
+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 "<chatroom name>",<limit>{,"<event label>"{,<trigger>{,<required zeny>{,<min lvl>{,<max lvl>}}}}};
 *waitingroom "<chatroom name>",<limit>{,"<event label>"{,<trigger>{,<required zeny>{,<min lvl>{,<max lvl>}}}}};

+ 33 - 0
sql-files/main.sql

@@ -695,6 +695,10 @@ CREATE TABLE IF NOT EXISTS `storage` (
   KEY `account_id` (`account_id`)
   KEY `account_id` (`account_id`)
 ) ENGINE=MyISAM;
 ) ENGINE=MyISAM;
 
 
+--
+-- Table structure for table `interreg`
+--
+
 CREATE TABLE IF NOT EXISTS `interreg` (
 CREATE TABLE IF NOT EXISTS `interreg` (
   `varname` varchar(11) NOT NULL,
   `varname` varchar(11) NOT NULL,
   `value` varchar(20) 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'
   `icon` SMALLINT(3) NOT NULL DEFAULT '-1'
 ) ENGINE=InnoDB;
 ) ENGINE=InnoDB;
 
 
+--
+-- Table structure for table `vending_items`
+--
+
 CREATE TABLE IF NOT EXISTS `vending_items` (
 CREATE TABLE IF NOT EXISTS `vending_items` (
   `vending_id` int(10) unsigned NOT NULL,
   `vending_id` int(10) unsigned NOT NULL,
   `index` smallint(5) 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
   `price` int(10) unsigned NOT NULL
 ) ENGINE=MyISAM;
 ) ENGINE=MyISAM;
 
 
+--
+-- Table structure for table `vendings`
+--
+
 CREATE TABLE IF NOT EXISTS `vendings` (
 CREATE TABLE IF NOT EXISTS `vendings` (
   `id` int(10) unsigned NOT NULL,
   `id` int(10) unsigned NOT NULL,
   `account_id` int(11) unsigned NOT NULL,
   `account_id` int(11) unsigned NOT NULL,
@@ -738,6 +750,10 @@ CREATE TABLE IF NOT EXISTS `vendings` (
   PRIMARY KEY (`id`)
   PRIMARY KEY (`id`)
 ) ENGINE=MyISAM;
 ) ENGINE=MyISAM;
 
 
+--
+-- Table structure for table `buyingstore_items`
+--
+
 CREATE TABLE IF NOT EXISTS `buyingstore_items` (
 CREATE TABLE IF NOT EXISTS `buyingstore_items` (
   `buyingstore_id` int(10) unsigned NOT NULL,
   `buyingstore_id` int(10) unsigned NOT NULL,
   `index` smallint(5) 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
   `price` int(10) unsigned NOT NULL
 ) ENGINE=MyISAM;
 ) ENGINE=MyISAM;
 
 
+--
+-- Table structure for table `buyingstores`
+--
+
 CREATE TABLE IF NOT EXISTS `buyingstores` (
 CREATE TABLE IF NOT EXISTS `buyingstores` (
   `id` int(10) unsigned NOT NULL,
   `id` int(10) unsigned NOT NULL,
   `account_id` int(11) 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,
   `autotrade` tinyint(4) NOT NULL,
   PRIMARY KEY (`id`)
   PRIMARY KEY (`id`)
 ) ENGINE=MyISAM;
 ) 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;

+ 12 - 0
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;

+ 133 - 5
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).
 /// Displays an NPC dialog message (ZC_SAY_DIALOG).
 /// 00b4 <packet len>.W <npc id>.L <message>.?B
 /// 00b4 <packet len>.W <npc id>.L <message>.?B
 /// Client behavior (dialog window):
 /// 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)
 void clif_parse_NpcBuyListSend(int fd, struct map_session_data* sd)
 {
 {
 	struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
 	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;
 	int result;
 
 
 	if( sd->state.trading || !sd->npc_shopid )
 	if( sd->state.trading || !sd->npc_shopid )
 		result = 1;
 		result = 1;
 	else
 	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.
 	sd->npc_shopid = 0; //Clear shop data.
-
 	clif_npc_buy_result(sd, result);
 	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,
 		0,  0,  0,  0,  0,  0,  6,  4,  6,  4,  0,  0,  0,  0,  0,  0,
 	//#0x09C0
 	//#0x09C0
 		0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 23,  0,  0,  0,102,  0,
 		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,
 		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_client_version, "clientversion"},
 		{ clif_parse_blocking_playcancel, "booking_playcancel"},
 		{ clif_parse_blocking_playcancel, "booking_playcancel"},
 		{ clif_parse_ranklist, "ranklist"},
 		{ clif_parse_ranklist, "ranklist"},
+		/* Market NPC */
+		{ clif_parse_NPCMarketClosed, "npcmarketclosed" },
+		{ clif_parse_NPCMarketPurchase, "npcmarketpurchase" },
 		{NULL,NULL}
 		{NULL,NULL}
 	};
 	};
 	struct {
 	struct {

+ 3 - 0
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_npcbuysell(struct map_session_data* sd, int id);	//self
 void clif_buylist(struct map_session_data *sd, struct npc_data *nd);	//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_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_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_scriptnext(struct map_session_data *sd,int npcid);	//self
 void clif_scriptclose(struct map_session_data *sd, int npcid);	//self
 void clif_scriptclose(struct map_session_data *sd, int npcid);	//self

+ 3 - 0
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 mob_skill_db2_db[32] = "mob_skill_db2";
 char vendings_db[32] = "vendings";
 char vendings_db[32] = "vendings";
 char vending_items_db[32] = "vending_items";
 char vending_items_db[32] = "vending_items";
+char market_table[32] = "market";
 
 
 // log database
 // log database
 char log_db_ip[32] = "127.0.0.1";
 char log_db_ip[32] = "127.0.0.1";
@@ -3759,6 +3760,8 @@ int inter_config_read(char *cfgName)
 			strcpy( vendings_db, w2 );
 			strcpy( vendings_db, w2 );
 		else if( strcmpi( w1, "vending_items_db" ) == 0 )
 		else if( strcmpi( w1, "vending_items_db" ) == 0 )
 			strcpy( vending_items_db, w2 );
 			strcpy( vending_items_db, w2 );
+		else if (strcmpi(w1, "market_table") == 0)
+			strcpy(market_table, w2);
 		else
 		else
 		//Map Server SQL DB
 		//Map Server SQL DB
 		if(strcmpi(w1,"map_server_ip")==0)
 		if(strcmpi(w1,"map_server_ip")==0)

+ 3 - 1
src/map/map.h

@@ -312,7 +312,8 @@ enum npc_subtype {
 	NPCTYPE_CASHSHOP, /// Cashshop
 	NPCTYPE_CASHSHOP, /// Cashshop
 	NPCTYPE_ITEMSHOP, /// Itemshop
 	NPCTYPE_ITEMSHOP, /// Itemshop
 	NPCTYPE_POINTSHOP, /// Pointshop
 	NPCTYPE_POINTSHOP, /// Pointshop
-	NPCTYPE_TOMB /// Monster tomb
+	NPCTYPE_TOMB, /// Monster tomb
+	NPCTYPE_MARKETSHOP, /// Marketshop
 };
 };
 
 
 enum e_race {
 enum e_race {
@@ -993,6 +994,7 @@ extern char mob_skill_db_re_db[32];
 extern char mob_skill_db2_db[32];
 extern char mob_skill_db2_db[32];
 extern char vendings_db[32];
 extern char vendings_db[32];
 extern char vending_items_db[32];
 extern char vending_items_db[32];
+extern char market_table[32];
 
 
 void do_shutdown(void);
 void do_shutdown(void);
 
 

+ 366 - 59
src/map/npc.c

@@ -40,6 +40,20 @@ static int npc_mob=0;
 static int npc_delay_mob=0;
 static int npc_delay_mob=0;
 static int npc_cache_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.
 /// Returns a new npc id that isn't being used in id_db.
 /// Fatal error if nothing is available.
 /// Fatal error if nothing is available.
 int npc_get_new_npc_id(void) {
 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:
 		case NPCTYPE_CASHSHOP:
 			clif_cashshop_show(sd,nd);
 			clif_cashshop_show(sd,nd);
 			break;
 			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:
 		case NPCTYPE_SCRIPT:
 			run_script(nd->u.scr.script,0,sd->bl.id,nd->bl.id);
 			run_script(nd->u.scr.script,0,sd->bl.id,nd->bl.id);
 			break;
 			break;
@@ -1318,7 +1352,7 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type)
 	sd->state.callshop = 0;
 	sd->state.callshop = 0;
 	sd->npc_shopid = id;
 	sd->npc_shopid = id;
 
 
-	if (type==0) {
+	if (type == 0) {
 		clif_buylist(sd,nd);
 		clif_buylist(sd,nd);
 	} else {
 	} else {
 		clif_selllist(sd);
 		clif_selllist(sd);
@@ -1410,9 +1444,14 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, uns
 	return 0;
 	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];
 	char npc_ev[EVENT_NAME_LENGTH];
 	int i;
 	int i;
 	int key_nameid = 0;
 	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);
 	script_cleararray_pc(sd, "@bought_quantity", (void*)0);
 
 
 	// save list of bought items
 	// 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
 	// invoke event
@@ -1519,16 +1557,20 @@ int npc_cashshop_buy(struct map_session_data *sd, unsigned short nameid, int amo
 	return 0;
 	return 0;
 }
 }
 
 
-/// Player item purchase from npc shop.
-///
-/// @param item_list 'n' pairs <amount,itemid>
-/// @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_data* nd;
+	struct npc_item_list *shop = NULL;
 	double z;
 	double z;
 	int i,j,k,w,skill,new_,count = 0;
 	int i,j,k,w,skill,new_,count = 0;
 	char output[CHAT_SIZE_MAX];
 	char output[CHAT_SIZE_MAX];
+	uint8 market_index[MAX_INVENTORY];
 
 
 	nullpo_retr(3, sd);
 	nullpo_retr(3, sd);
 	nullpo_retr(3, item_list);
 	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));
 	nd = npc_checknear(sd,map_id2bl(sd->npc_shopid));
 	if( nd == NULL )
 	if( nd == NULL )
 		return 3;
 		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;
 		return 3;
 
 
 	z = 0;
 	z = 0;
 	w = 0;
 	w = 0;
 	new_ = 0;
 	new_ = 0;
+
+	shop = nd->u.shop.shop_item;
+
+	memset(market_index, 0, sizeof(market_index));
 	// process entries in buy list, one by one
 	// 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
 		// find this entry in the shop's sell list
 		ARR_FIND( 0, nd->u.shop.count, j,
 		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 )
 		if( j == nd->u.shop.count )
 			return 3; // no such item in shop
 			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) )
 		if( !itemdb_exists(nameid) )
 			return 3; // item no longer in itemdb
 			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",
 			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);
 				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;
 			continue;
 		}
 		}
 
 
-		switch( pc_checkadditem(sd,nameid,amount) )
-		{
+		switch( pc_checkadditem(sd,nameid,amount) ) {
 			case CHKADDITEM_EXIST:
 			case CHKADDITEM_EXIST:
 				break;
 				break;
 
 
@@ -1596,11 +1648,12 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 		w += itemdb_weight(nameid) * amount;
 		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);
 		return npc_buylist_sub(sd,n,item_list,nd->master_nd);
 
 
 	switch(nd->subtype) {
 	switch(nd->subtype) {
 		case NPCTYPE_SHOP:
 		case NPCTYPE_SHOP:
+		case NPCTYPE_MARKETSHOP:
 			if (z > (double)sd->status.zeny)
 			if (z > (double)sd->status.zeny)
 				return 1;	// Not enough Zeny
 				return 1;	// Not enough Zeny
 			break;
 			break;
@@ -1645,6 +1698,7 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 
 
 	switch(nd->subtype) {
 	switch(nd->subtype) {
 		case NPCTYPE_SHOP:
 		case NPCTYPE_SHOP:
+		case NPCTYPE_MARKETSHOP:
 			pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
 			pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
 			break;
 			break;
 		case NPCTYPE_ITEMSHOP:
 		case NPCTYPE_ITEMSHOP:
@@ -1666,16 +1720,24 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 			break;
 			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;
 		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)
 		if (itemdb_type(nameid) == IT_PETEGG)
 			pet_create_egg(sd, nameid);
 			pet_create_egg(sd, nameid);
-		else
-		{
+		else {
 			memset(&item_tmp,0,sizeof(item_tmp));
 			memset(&item_tmp,0,sizeof(item_tmp));
 			item_tmp.nameid = nameid;
 			item_tmp.nameid = nameid;
 			item_tmp.identify = 1;
 			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
 	// 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);
 		uint16 sk_idx = skill_get_index(MC_DISCOUNT);
 		if( sd->status.skill[sk_idx].flag >= SKILL_FLAG_REPLACED_LV_0 )
 		if( sd->status.skill[sk_idx].flag >= SKILL_FLAG_REPLACED_LV_0 )
 			skill = 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.;
 			z = z * (double)skill * (double)battle_config.shop_exp/10000.;
 			if( z < 1 )
 			if( z < 1 )
 				z = 1;
 				z = 1;
@@ -1708,7 +1768,6 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 	return 0;
 	return 0;
 }
 }
 
 
-
 /// npc_selllist for script-controlled shops
 /// 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)
 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 <index,amount>
 /// @param item_list 'n' pairs <index,amount>
 /// @return result code for clif_parse_NpcSellListSend
 /// @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;
 	double z;
 	int i,skill;
 	int i,skill;
@@ -1957,7 +2016,7 @@ int npc_unload(struct npc_data* nd, bool single) {
 	if( single && nd->bl.m != -1 )
 	if( single && nd->bl.m != -1 )
 		map_remove_questinfo(nd->bl.m, nd);
 		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);
 		aFree(nd->u.shop.shop_item);
 	else if( nd->subtype == NPCTYPE_SCRIPT ) {
 	else if( nd->subtype == NPCTYPE_SCRIPT ) {
 		struct s_mapiterator* iter;
 		struct s_mapiterator* iter;
@@ -2375,8 +2434,9 @@ static const char* npc_parse_warp(char* w1, char* w2, char* w3, char* w4, const
  * <map name>,<x>,<y>,<facing>%TAB%cashshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>{,<itemid>:<price>...}
  * <map name>,<x>,<y>,<facing>%TAB%cashshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>{,<itemid>:<price>...}
  * <map name>,<x>,<y>,<facing>%TAB%itemshop%TAB%<NPC Name>%TAB%<sprite id>,<costitemid>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
  * <map name>,<x>,<y>,<facing>%TAB%itemshop%TAB%<NPC Name>%TAB%<sprite id>,<costitemid>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
  * <map name>,<x>,<y>,<facing>%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
  * <map name>,<x>,<y>,<facing>%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>{:<discount>},<itemid>:<price>{,<itemid>:<price>...}
+ * <map name>,<x>,<y>,<facing>%TAB%marketshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>:<quantity>{,<itemid>:<price>:<quantity>...}
  * @param w1 : word 1 before tab (<from map name>,<x>,<y>,<facing>)
  * @param w1 : word 1 before tab (<from map name>,<x>,<y>,<facing>)
- * @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 (<NPC Name>)
  * @param w3 : word 3 before tab (<NPC Name>)
  * @param w4 : word 4 before tab (<sprited id>,<shop definition...>)
  * @param w4 : word 4 before tab (<sprited id>,<shop definition...>)
  * @param start : index to start parsing
  * @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;
 		type = NPCTYPE_ITEMSHOP;
 	else if( !strcasecmp(w2,"pointshop") )
 	else if( !strcasecmp(w2,"pointshop") )
 		type = NPCTYPE_POINTSHOP;
 		type = NPCTYPE_POINTSHOP;
+	else if( !strcasecmp(w2, "marketshop") )
+		type = NPCTYPE_MARKETSHOP;
 	else
 	else
 		type = NPCTYPE_SHOP;
 		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,',');
 			p = strchr(p+1,',');
 			break;
 			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:
 		default:
 			is_discount = 1;
 			is_discount = 1;
 			break;
 			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;
 	nd->u.shop.count = 0;
 	while ( p ) {
 	while ( p ) {
-		unsigned short nameid2;
-		int value;
+		unsigned short nameid2, qty = 0;
+		int value, i = 0;
 		struct item_data* id;
 		struct item_data* id;
+		bool skip = false;
+
 		if( p == NULL )
 		if( p == NULL )
 			break;
 			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 ) {
 		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);
 			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,',');
 			p = strchr(p+1,',');
 			continue;
 			continue;
 		}
 		}
 		if( value < 0 ) {
 		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
 			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",
 			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));
 				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",
 			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));
 				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
 		//for logs filters, atcommands and iteminfo script command
 		if( id->maxchance == 0 )
 		if( id->maxchance == 0 )
 			id->maxchance = -1; // -1 would show that the item's sold in NPC Shop
 			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].nameid = nameid2;
 		nd->u.shop.shop_item[nd->u.shop.count].value = value;
 		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++;
 		nd->u.shop.count++;
 		p = strchr(p+1,',');
 		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
 		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->u.shop.discount = is_discount;
 	}
 	}
+
 	nd->bl.prev = nd->bl.next = NULL;
 	nd->bl.prev = nd->bl.next = NULL;
 	nd->bl.m = m;
 	nd->bl.m = m;
 	nd->bl.x = x;
 	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;
 	++npc_shop;
 	nd->bl.type = BL_NPC;
 	nd->bl.type = BL_NPC;
 	nd->subtype = type;
 	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 )
 	if( m >= 0 )
 	{// normal shop npc
 	{// normal shop npc
 		map_addnpc(m,nd);
 		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;
 	type = dnd->subtype;
 
 
 	// get placement
 	// 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;
 		x = y = dir = 0;
 		m = -1;
 		m = -1;
 	} else {
 	} 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_CASHSHOP:
 		case NPCTYPE_ITEMSHOP:
 		case NPCTYPE_ITEMSHOP:
 		case NPCTYPE_POINTSHOP:
 		case NPCTYPE_POINTSHOP:
+		case NPCTYPE_MARKETSHOP:
 			++npc_shop;
 			++npc_shop;
 			nd->u.shop.shop_item = dnd->u.shop.shop_item;
 			nd->u.shop.shop_item = dnd->u.shop.shop_item;
 			nd->u.shop.count = dnd->u.shop.count;
 			nd->u.shop.count = dnd->u.shop.count;
@@ -3059,6 +3177,176 @@ int npc_instanceinit(struct npc_data* nd)
 	return 0;
 	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
 //Set mapcell CELL_NPC to trigger event later
 void npc_setcells(struct npc_data* nd)
 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 )
 		if( strcasecmp(w2,"warp") == 0 && count > 3 )
 			p = npc_parse_warp(w1,w2,w3,w4, p, buffer, filepath);
 			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);
 			p = npc_parse_shop(w1,w2,w3,w4, p, buffer, filepath);
 		else if( strcasecmp(w2,"script") == 0 && count > 3 ) {
 		else if( strcasecmp(w2,"script") == 0 && count > 3 ) {
 			if( strcasecmp(w1,"function") == 0 )
 			if( strcasecmp(w1,"function") == 0 )
@@ -4057,6 +4345,10 @@ int npc_reload(void) {
 
 
 	//Remove all npcs/mobs. [Skotlex]
 	//Remove all npcs/mobs. [Skotlex]
 
 
+#if PACKETVER >= 20131223
+	npc_market_fromsql();
+#endif
+
 	iter = mapit_geteachiddb();
 	iter = mapit_geteachiddb();
 	for( bl = (struct block_list*)mapit_first(iter); mapit_exists(iter); bl = (struct block_list*)mapit_next(iter) ) {
 	for( bl = (struct block_list*)mapit_first(iter); mapit_exists(iter); bl = (struct block_list*)mapit_next(iter) ) {
 		switch(bl->type) {
 		switch(bl->type) {
@@ -4130,6 +4422,10 @@ int npc_reload(void) {
 	if(!CheckForCharServer()){
 	if(!CheckForCharServer()){
 		ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInit"));
 		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;
 	return 0;
 }
 }
 
 
@@ -4170,6 +4466,9 @@ void do_final_npc(void) {
 	ev_db->destroy(ev_db, NULL);
 	ev_db->destroy(ev_db, NULL);
 	npcname_db->destroy(npcname_db, NULL);
 	npcname_db->destroy(npcname_db, NULL);
 	npc_path_db->destroy(npc_path_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);
 	ers_destroy(timer_event_ers);
 	npc_clearsrcfile();
 	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);
 	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);
 	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);
 	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);
 	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));
 	memset(script_event, 0, sizeof(script_event));
 	npc_read_event_script();
 	npc_read_event_script();
 
 
+#if PACKETVER >= 20131223
+	npc_market_checkall();
+#endif
+
 	//Debug function to locate all endless loop warps.
 	//Debug function to locate all endless loop warps.
 	if (battle_config.warp_point_debug)
 	if (battle_config.warp_point_debug)
 		npc_debug_warps();
 		npc_debug_warps();

+ 20 - 2
src/map/npc.h

@@ -15,13 +15,26 @@ struct view_data;
 struct npc_timerevent_list {
 struct npc_timerevent_list {
 	int timer,pos;
 	int timer,pos;
 };
 };
+
 struct npc_label_list {
 struct npc_label_list {
 	char name[NAME_LENGTH];
 	char name[NAME_LENGTH];
 	int pos;
 	int pos;
 };
 };
+
+/// Item list for NPC sell/buy list
 struct npc_item_list {
 struct npc_item_list {
 	unsigned short nameid;
 	unsigned short nameid;
 	unsigned int value;
 	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 {
 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);
 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);
 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_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);
 void npc_parse_mob2(struct spawn_data* mob);
 bool npc_viewisid(const char * viewid);
 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);
 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);
 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);
 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
 #ifdef SECURE_NPCTIMEOUT
 	int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data);
 	int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data);
 #endif
 #endif

+ 137 - 21
src/map/script.c

@@ -15628,28 +15628,45 @@ BUILDIN_FUNC(callshop)
 		return 0;
 		return 0;
 	}
 	}
 	shopname = script_getstr(st, 2);
 	shopname = script_getstr(st, 2);
-	if( script_hasdata(st,3) )
+	if (script_hasdata(st,3))
 		flag = script_getnum(st,3);
 		flag = script_getnum(st,3);
 	nd = npc_name2id(shopname);
 	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);
 		ShowError("buildin_callshop: Shop [%s] not found (or NPC is not shop type)\n", shopname);
 		script_pushint(st,0);
 		script_pushint(st,0);
 		return 1;
 		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)
 		// flag the user as using a valid script call for opening the shop (for floating NPCs)
 		sd->state.callshop = 1;
 		sd->state.callshop = 1;
 
 
-		switch( flag )
-		{
+		switch (flag) {
 			case 1: npc_buysellsel(sd,nd->bl.id,0); break; //Buy window
 			case 1: npc_buysellsel(sd,nd->bl.id,0); break; //Buy window
 			case 2: npc_buysellsel(sd,nd->bl.id,1); break; //Sell window
 			case 2: npc_buysellsel(sd,nd->bl.id,1); break; //Sell window
 			default: clif_npcbuysell(sd,nd->bl.id); break; //Show menu
 			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
 	else
 		clif_cashshop_show(sd, nd);
 		clif_cashshop_show(sd, nd);
 
 
@@ -15664,22 +15681,35 @@ BUILDIN_FUNC(npcshopitem)
 	struct npc_data* nd = npc_name2id(npcname);
 	struct npc_data* nd = npc_name2id(npcname);
 	int n, i;
 	int n, i;
 	int amount;
 	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);
 		script_pushint(st,0);
 		return 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
 	// get the count of new entries
-	amount = (script_lastdata(st)-2)/2;
+	amount = (script_lastdata(st)-2)/offs;
 
 
 	// generate new shop item list
 	// generate new shop item list
 	RECREATE(nd->u.shop.shop_item, struct npc_item_list, amount);
 	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].nameid = script_getnum(st,i);
 		nd->u.shop.shop_item[n].value = script_getnum(st,i+1);
 		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;
 	nd->u.shop.count = n;
 
 
@@ -15693,19 +15723,47 @@ BUILDIN_FUNC(npcshopadditem)
 	struct npc_data* nd = npc_name2id(npcname);
 	struct npc_data* nd = npc_name2id(npcname);
 	int n, i;
 	int n, i;
 	int amount;
 	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);
 		script_pushint(st,0);
 		return 0;
 		return 0;
 	}
 	}
 
 
+	if (nd->subtype == NPCTYPE_MARKETSHOP)
+		offs = 3;
+
 	// get the count of new entries
 	// 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
 	// append new items to existing shop item list
 	RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+amount);
 	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].nameid = script_getnum(st,i);
 		nd->u.shop.shop_item[n].value = script_getnum(st,i+1);
 		nd->u.shop.shop_item[n].value = script_getnum(st,i+1);
@@ -15724,7 +15782,7 @@ BUILDIN_FUNC(npcshopdelitem)
 	int amount;
 	int amount;
 	int size;
 	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);
 		script_pushint(st,0);
 		return 0;
 		return 0;
 	}
 	}
@@ -15738,7 +15796,12 @@ BUILDIN_FUNC(npcshopdelitem)
 
 
 		ARR_FIND( 0, size, n, nd->u.shop.shop_item[n].nameid == nameid );
 		ARR_FIND( 0, size, n, nd->u.shop.shop_item[n].nameid == nameid );
 		if( n < size ) {
 		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--;
 			size--;
 		}
 		}
 	}
 	}
@@ -15750,7 +15813,10 @@ BUILDIN_FUNC(npcshopdelitem)
 	return SCRIPT_CMD_SUCCESS;
 	return SCRIPT_CMD_SUCCESS;
 }
 }
 
 
-//Sets a script to attach to a shop npc.
+/**
+ * Sets a script to attach to a shop npc.
+ * npcshopattach "<npc_name>";
+ **/
 BUILDIN_FUNC(npcshopattach)
 BUILDIN_FUNC(npcshopattach)
 {
 {
 	const char* npcname = script_getstr(st,2);
 	const char* npcname = script_getstr(st,2);
@@ -15760,8 +15826,7 @@ BUILDIN_FUNC(npcshopattach)
 	if( script_hasdata(st,3) )
 	if( script_hasdata(st,3) )
 		flag = script_getnum(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);
 		script_pushint(st,0);
 		return 0;
 		return 0;
 	}
 	}
@@ -19804,6 +19869,55 @@ BUILDIN_FUNC(mergeitem) {
 	return SCRIPT_CMD_SUCCESS;
 	return SCRIPT_CMD_SUCCESS;
 }
 }
 
 
+/**
+ * Update an entry from NPC shop.
+ * npcshopupdate "<name>",<item_id>,<price>{,<stock>}
+ **/
+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"
 #include "../custom/script.inc"
 
 
 // declarations that were supposed to be exported from npc_chat.c
 // 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(countspiritball,"?"),
 	BUILDIN_DEF(mergeitem,"?"),
 	BUILDIN_DEF(mergeitem,"?"),
 
 
+	BUILDIN_DEF(npcshopupdate,"sii?"),
+
 #include "../custom/script_def.inc"
 #include "../custom/script_def.inc"
 
 
 	{NULL,NULL,NULL},
 	{NULL,NULL,NULL},