ソースを参照

Initial release of 'itemshop' and 'pointshop'
-- Suggestion: http://rathena.org/board/topic/84169-specialty-shops/
-- Credits: Aleos & Akinari for first diff file that I used to finish this and testing/debugging (well, I don't know the original ones, just tell us or just do pull request if you know the first creator.)
-- How to use?
---> -%TAB%itemshop%TAB%<NPC Name>%TAB%<sprite id>,<costitemid>,<itemid>:<price>{,<itemid>:<price>...}
---> -%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>,<itemid>:<price>{,<itemid>:<price>...}

Signed-off-by: Cahyadi Ramadhan Togihon <house.bad@gmail.com>

Cahyadi Ramadhan Togihon 11 年 前
コミット
32fd3ee303
7 ファイル変更189 行追加62 行削除
  1. 12 5
      conf/msg_conf/map_msg.conf
  2. 19 6
      doc/script_commands.txt
  3. 1 1
      src/map/map.h
  4. 147 42
      src/map/npc.c
  5. 2 0
      src/map/npc.h
  6. 3 3
      src/map/pc.c
  7. 5 5
      src/map/script.c

+ 12 - 5
conf/msg_conf/map_msg.conf

@@ -676,10 +676,6 @@
 678: You are no longer the Guild Master.
 678: You are no longer the Guild Master.
 679: You have become the Guild Master!
 679: You have become the Guild Master!
 680: You have been recovered!
 680: You have been recovered!
-681: Item %d has been removed from your inventory.
-682: Item %d has been removed from your cart.
-683: Item %d has been removed from your storage.
-//684-899 free
 
 
 681: Rune Knight T
 681: Rune Knight T
 682: Warlock T
 682: Warlock T
@@ -707,7 +703,18 @@
 706: Player '%s' is now VIP for %d years, %d months, %d days, %d hours and %d minutes.
 706: Player '%s' is now VIP for %d years, %d months, %d days, %d hours and %d minutes.
 707: You are VIP until: %s
 707: You are VIP until: %s
 708: The player is now VIP until: %s
 708: The player is now VIP until: %s
-//709-899 free
+
+709: Item %d has been removed from your inventory.
+710: Item %d has been removed from your cart.
+711: Item %d has been removed from your storage.
+
+//Item shop
+712: You do not have enough %s (%d).
+713: You do not have enough '%s'.
+714: Item Shop List: %s (%d)
+715: Point Shop List: '%s'
+716: Your '%s' now: %d
+//717-899 free
 
 
 //------------------------------------
 //------------------------------------
 // More atcommands message
 // More atcommands message

+ 19 - 6
doc/script_commands.txt

@@ -268,11 +268,17 @@ normally mean it's pointless since it can't do anything, but there are
 exceptions, mostly related to running scripts at specified time, which is what 
 exceptions, mostly related to running scripts at specified time, which is what 
 these floating NPC objects are for. More on that below.
 these floating NPC objects are for. More on that below.
 
 
-** Define a shop/cashshop NPC.
+** Define a shop/cashshop/itemshop/pointshop NPC.
 
 
 -%TAB%shop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>{,<itemid>:<price>...}
 -%TAB%shop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>{,<itemid>:<price>...}
 <map name>,<x>,<y>,<facing>%TAB%shop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>{,<itemid>:<price>...}
 <map name>,<x>,<y>,<facing>%TAB%shop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>{,<itemid>:<price>...}
 
 
+-%TAB%itemshop%TAB%<NPC Name>%TAB%<sprite id>,<costitemid>,<itemid>:<price>{,<itemid>:<price>...}
+<map name>,<x>,<y>,<facing>%TAB%itemshop%TAB%<NPC Name>%TAB%<sprite id>,<costitemid>,<itemid>:<price>{,<itemid>:<price>...}
+
+-%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>,<itemid>:<price>{,<itemid>:<price>...}
+<map name>,<x>,<y>,<facing>%TAB%pointshop%TAB%<NPC Name>%TAB%<sprite id>,<costvariable>,<itemid>:<price>{,<itemid>:<price>...}
+
 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 
@@ -291,17 +297,24 @@ This type of shop will not allow you to sell items at it, you may only
 purchase items here. The layout used to define sale items still count, and
 purchase items here. The layout used to define sale items still count, and
 "<price>" refers to how many points will be spent purchasing the them.
 "<price>" refers to how many points will be spent purchasing the them.
 
 
-** Define an warp/shop/cashshop/NPC duplicate.
+Since trunk rX you can alternatively use "itemshop" or "pointshop" in place
+of "shop" to use the Shop interface, allowing you to buy items with a specific
+item or special points from a variable. 'pointshop' only supported variable types
+are permanent character variables, temporary character variables, permanent
+local account variables, or permanent global account variables. These variables
+must be of integer type, not string.
+
+** Define an warp/shop/cashshop/itemshop/pointshop/NPC duplicate.
 
 
 warp: <map name>,<x>,<y>,<facing>%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<spanx>,<spany>
 warp: <map name>,<x>,<y>,<facing>%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<spanx>,<spany>
-shop/cashshop/npc: -%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>
-shop/cashshop/npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>
+shop/cashshop/itemshop/pointshop/npc: -%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>
+shop/cashshop/itemshop/pointshop/npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>
 npc: -%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>
 npc: -%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>
 npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>
 npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<label>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>
 
 
-This will duplicate an warp/shop/cashshop/NPC referred to by 'label'.
+This will duplicate an warp/shop/cashshop/itemshop/pointshop/NPC referred to by 'label'.
 Warp duplicates inherit the target location.
 Warp duplicates inherit the target location.
-Shop/cashshop duplicates inherit the item list.
+Shop/cashshop/itemshop/pointshop duplicates inherit the item list.
 NPC duplicates inherit the script code.
 NPC duplicates inherit the script code.
 The rest (name, location, facing, sprite ID, span/trigger area)
 The rest (name, location, facing, sprite ID, span/trigger area)
 is obtained from the definition of the duplicate (not inherited).
 is obtained from the definition of the duplicate (not inherited).

+ 1 - 1
src/map/map.h

@@ -261,7 +261,7 @@ enum bl_type {
 //For common mapforeach calls. Since pets cannot be affected, they aren't included here yet.
 //For common mapforeach calls. Since pets cannot be affected, they aren't included here yet.
 #define BL_CHAR (BL_PC|BL_MOB|BL_HOM|BL_MER|BL_ELEM)
 #define BL_CHAR (BL_PC|BL_MOB|BL_HOM|BL_MER|BL_ELEM)
 
 
-enum npc_subtype { WARP, SHOP, SCRIPT, CASHSHOP, TOMB };
+enum npc_subtype { WARP, SHOP, SCRIPT, CASHSHOP, ITEMSHOP, POINTSHOP, TOMB };
 
 
 enum {
 enum {
 	RC_FORMLESS=0,
 	RC_FORMLESS=0,

+ 147 - 42
src/map/npc.c

@@ -12,6 +12,7 @@
 #include "../common/db.h"
 #include "../common/db.h"
 #include "../common/socket.h"
 #include "../common/socket.h"
 #include "map.h"
 #include "map.h"
+#include "mapreg.h"
 #include "log.h"
 #include "log.h"
 #include "clif.h"
 #include "clif.h"
 #include "intif.h"
 #include "intif.h"
@@ -1212,11 +1213,28 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd)
 	//Hidden/Disabled npc.
 	//Hidden/Disabled npc.
 	if (nd->class_ < 0 || nd->sc.option&(OPTION_INVISIBLE|OPTION_HIDE))
 	if (nd->class_ < 0 || nd->sc.option&(OPTION_INVISIBLE|OPTION_HIDE))
 		return 1;
 		return 1;
-
+	
 	switch(nd->subtype) {
 	switch(nd->subtype) {
 		case SHOP:
 		case SHOP:
 			clif_npcbuysell(sd,nd->bl.id);
 			clif_npcbuysell(sd,nd->bl.id);
 			break;
 			break;
+		case ITEMSHOP:
+			{
+				char output[CHAT_SIZE_MAX];
+				struct item_data *id = itemdb_exists(nd->u.shop.itemshop_nameid);
+				memset(output,'\0',sizeof(output));
+				sprintf(output,msg_txt(sd,714),id->jname,id->nameid); // Item Shop List: %s (%d)
+				clif_broadcast(&sd->bl,output,strlen(output) + 1,0x10,SELF);
+				clif_npcbuysell(sd,nd->bl.id);
+			} break;
+		case POINTSHOP:
+			{
+				char output[CHAT_SIZE_MAX];
+				memset(output,'\0',sizeof(output));
+				sprintf(output,msg_txt(sd,715),nd->u.shop.pointshop_str); // Point Shop List: '%s'
+				clif_broadcast(&sd->bl,output,strlen(output) + 1,0x10,SELF);
+				clif_npcbuysell(sd,nd->bl.id);
+			} break;
 		case CASHSHOP:
 		case CASHSHOP:
 			clif_cashshop_show(sd,nd);
 			clif_cashshop_show(sd,nd);
 			break;
 			break;
@@ -1283,7 +1301,7 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type)
 	if ((nd = npc_checknear(sd,map_id2bl(id))) == NULL)
 	if ((nd = npc_checknear(sd,map_id2bl(id))) == NULL)
 		return 1;
 		return 1;
 
 
-	if (nd->subtype!=SHOP) {
+	if (nd->subtype != SHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP) {
 		ShowError("no such shop npc : %d\n",id);
 		ShowError("no such shop npc : %d\n",id);
 		if (sd->npc_id == id)
 		if (sd->npc_id == id)
 			sd->npc_id=0;
 			sd->npc_id=0;
@@ -1504,7 +1522,8 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 {
 {
 	struct npc_data* nd;
 	struct npc_data* nd;
 	double z;
 	double z;
-	int i,j,w,skill,new_;
+	int i,j,k,w,skill,new_,count = 0;
+	char output[CHAT_SIZE_MAX];
 
 
 	nullpo_retr(3, sd);
 	nullpo_retr(3, sd);
 	nullpo_retr(3, item_list);
 	nullpo_retr(3, item_list);
@@ -1512,7 +1531,7 @@ 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 != SHOP )
+	if( nd->subtype != SHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP )
 		return 3;
 		return 3;
 
 
 	z = 0;
 	z = 0;
@@ -1573,14 +1592,74 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 	if( nd->master_nd != NULL ) //Script-based shops.
 	if( nd->master_nd != NULL ) //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);
 
 
-	if( z > (double)sd->status.zeny )
-		return 1;	// Not enough Zeny
+	switch(nd->subtype) {
+		case SHOP:
+			if (z > (double)sd->status.zeny)
+				return 1;	// Not enough Zeny
+			break;
+		case ITEMSHOP:
+			for (k = 0; k < MAX_INVENTORY; k++) {
+				if (sd->status.inventory[k].nameid == nd->u.shop.itemshop_nameid)
+					count += sd->status.inventory[k].amount;
+			}
+			if (z > (double)count) {
+				struct item_data *id = itemdb_exists(nd->u.shop.itemshop_nameid);
+
+				sprintf(output,msg_txt(sd,712),id->jname,id->nameid); // You do not have enough %s %d.
+				clif_colormes(sd,color_table[COLOR_RED],output);
+				return 1;
+			}
+			break;
+		case POINTSHOP:
+			switch(nd->u.shop.pointshop_str[0]) {
+				case '#':
+					if (nd->u.shop.pointshop_str[1] == '#')
+						count = pc_readaccountreg2(sd, nd->u.shop.pointshop_str);
+					else
+						count = pc_readaccountreg(sd, nd->u.shop.pointshop_str);
+					break;
+				case '@':
+				default:
+					count = pc_readglobalreg(sd, nd->u.shop.pointshop_str);
+					break;
+			}
+			if (z > (double)count) {
+				sprintf(output,msg_txt(sd,713),nd->u.shop.pointshop_str); // You do not have enough '%s'.
+				clif_colormes(sd,color_table[COLOR_RED],output);
+				return 1;
+			}
+			break;
+	}
+
 	if( w + sd->weight > sd->max_weight )
 	if( w + sd->weight > sd->max_weight )
 		return 2;	// Too heavy
 		return 2;	// Too heavy
 	if( pc_inventoryblank(sd) < new_ )
 	if( pc_inventoryblank(sd) < new_ )
 		return 3;	// Not enough space to store items
 		return 3;	// Not enough space to store items
 
 
-	pc_payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);
+	switch(nd->subtype) {
+		case SHOP:
+			pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
+			break;
+		case ITEMSHOP:
+			pc_delitem(sd, pc_search_inventory(sd, nd->u.shop.itemshop_nameid), (int)z, 0, 0, LOG_TYPE_NPC);
+			break;
+		case POINTSHOP:
+			switch(nd->u.shop.pointshop_str[0]) {
+				case '#':
+					if (nd->u.shop.pointshop_str[1] == '#')
+						pc_setaccountreg2(sd, nd->u.shop.pointshop_str, count - (int)z);
+					else
+						pc_setaccountreg(sd, nd->u.shop.pointshop_str, count - (int)z);
+					break;
+				case '@':
+				default:
+					pc_setglobalreg(sd, nd->u.shop.pointshop_str, count - (int)z);
+					break;
+			}
+			sprintf(output,msg_txt(sd,716),nd->u.shop.pointshop_str,count - (int)z); // Your '%s' now: %d
+			clif_disp_onlyself(sd,output,strlen(output)+1);
+			break;
+	}
 
 
 	for( i = 0; i < n; ++i )
 	for( i = 0; i < n; ++i )
 	{
 	{
@@ -1688,7 +1767,7 @@ int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list)
 	nullpo_retr(1, sd);
 	nullpo_retr(1, sd);
 	nullpo_retr(1, item_list);
 	nullpo_retr(1, item_list);
 
 
-	if( ( nd = npc_checknear(sd, map_id2bl(sd->npc_shopid)) ) == NULL || nd->subtype != SHOP )
+	if( ( nd = npc_checknear(sd, map_id2bl(sd->npc_shopid)) ) == NULL || nd->subtype != SHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP )
 	{
 	{
 		return 1;
 		return 1;
 	}
 	}
@@ -1857,7 +1936,7 @@ int npc_unload(struct npc_data* nd, bool single) {
 		}
 		}
 	}
 	}
 
 
-	if( (nd->subtype == SHOP || nd->subtype == CASHSHOP) && nd->src_id == 0) //src check for duplicate shops [Orcao]
+	if( (nd->subtype == SHOP || nd->subtype == CASHSHOP || nd->subtype == ITEMSHOP || nd->subtype == POINTSHOP) && 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 == SCRIPT ) {
 	else if( nd->subtype == SCRIPT ) {
 		struct s_mapiterator* iter;
 		struct s_mapiterator* iter;
@@ -2195,8 +2274,8 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
 	//TODO: could be rewritten to NOT need this temp array [ultramage]
 	//TODO: could be rewritten to NOT need this temp array [ultramage]
 	#define MAX_SHOPITEM 100
 	#define MAX_SHOPITEM 100
 	struct npc_item_list items[MAX_SHOPITEM];
 	struct npc_item_list items[MAX_SHOPITEM];
-	char *p;
-	int x, y, dir, m, i;
+	char *p, point_str[32];
+	int x, y, dir, m, i, nameid = 0;
 	struct npc_data *nd;
 	struct npc_data *nd;
 	enum npc_subtype type;
 	enum npc_subtype type;
 
 
@@ -2224,40 +2303,76 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
 
 
 	if( !strcasecmp(w2,"cashshop") )
 	if( !strcasecmp(w2,"cashshop") )
 		type = CASHSHOP;
 		type = CASHSHOP;
+	else if( !strcasecmp(w2,"itemshop") )
+		type = ITEMSHOP;
+	else if( !strcasecmp(w2,"pointshop") )
+		type = POINTSHOP;
 	else
 	else
 		type = SHOP;
 		type = SHOP;
 
 
 	p = strchr(w4,',');
 	p = strchr(w4,',');
-	for( i = 0; i < ARRAYLENGTH(items) && p; ++i )
-	{
+	memset(point_str,'\0',sizeof(point_str));
+
+	switch(type) {
+		case ITEMSHOP: {
+			struct item_data* tmp;
+			if (sscanf(p,",%d,",&nameid) < 1) {
+				ShowError("npc_parse_shop: Invalid item cost 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);
+				return strchr(start,'\n'); // skip and continue
+			}
+			if ((tmp = itemdb_exists(nameid)) == NULL) {
+				ShowWarning("npc_parse_shop: Invalid item ID cost in file '%s', line '%d' (id '%d').\n", filepath, strline(buffer,start-buffer), nameid);
+				return strchr(start,'\n'); // skip and continue
+			}
+			p = strchr(p+1,',');
+			break;
+		}
+		case POINTSHOP: {
+			if (sscanf(p, ",%32[^,],",point_str) < 1) {
+				ShowError("npc_parse_shop: Invalid item cost 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);
+				return strchr(start,'\n'); // skip and continue
+			}
+			switch(point_str[0]) {
+				case '$':
+				case '.':
+				case '\'':
+					ShowWarning("npc_parse_shop: Invalid item cost variable type (must be permanent character or account based) 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);
+					return strchr(start,'\n'); // skip and continue
+					break;
+			}
+			if (point_str[strlen(point_str) - 1] == '$') {
+				ShowWarning("npc_parse_shop: Invalid item cost variable type (must be integer) 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);
+				return strchr(start,'\n'); // skip and continue
+			}
+			p = strchr(p+1,',');
+			break;
+		}
+	}
+
+	for( i = 0; i < ARRAYLENGTH(items) && p; ++i ) {
 		int nameid, value;
 		int nameid, value;
 		struct item_data* id;
 		struct item_data* id;
-		if( sscanf(p, ",%d:%d", &nameid, &value) != 2 )
-		{
+		if( sscanf(p, ",%d:%d", &nameid, &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);
 			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;
 			break;
 		}
 		}
 
 
-		if( (id = itemdb_exists(nameid)) == NULL )
-		{
+		if( (id = itemdb_exists(nameid)) == NULL ) {
 			ShowWarning("npc_parse_shop: Invalid sell item in file '%s', line '%d' (id '%d').\n", filepath, strline(buffer,start-buffer), nameid);
 			ShowWarning("npc_parse_shop: Invalid sell item in file '%s', line '%d' (id '%d').\n", filepath, strline(buffer,start-buffer), nameid);
 			p = strchr(p+1,',');
 			p = strchr(p+1,',');
 			continue;
 			continue;
 		}
 		}
 
 
-		if( value < 0 )
-		{
+		if( value < 0 ) {
 			if( type == SHOP ) value = id->value_buy;
 			if( type == SHOP ) 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 == SHOP && value == 0 )
-		{ // NPC selling items for free!
+		if( (type == SHOP || type == ITEMSHOP || type == POINTSHOP) && value == 0 ) { // NPC selling items for free!
 			ShowWarning("npc_parse_shop: Item %s [%d] is being sold for FREE in file '%s', line '%d'.\n",
 			ShowWarning("npc_parse_shop: Item %s [%d] is being sold for FREE in file '%s', line '%d'.\n",
 				id->name, nameid, filepath, strline(buffer,start-buffer));
 				id->name, nameid, filepath, strline(buffer,start-buffer));
 		}
 		}
-		if( type == SHOP && value*0.75 < id->value_sell*1.24 )
-		{// Exploit possible: you can buy and sell back with profit
+		if( type == SHOP && value*0.75 < id->value_sell*1.24 ) { // Exploit possible: you can buy and sell back with profit
 			ShowWarning("npc_parse_shop: Item %s [%d] 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 [%d] discounted buying price (%d->%d) is less than overcharged selling price (%d->%d) at file '%s', line '%d'.\n",
 				id->name, nameid, value, (int)(value*0.75), id->value_sell, (int)(id->value_sell*1.24), filepath, strline(buffer,start-buffer));
 				id->name, nameid, value, (int)(value*0.75), id->value_sell, (int)(id->value_sell*1.24), filepath, strline(buffer,start-buffer));
 		}
 		}
@@ -2269,8 +2384,7 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
 		items[i].value = value;
 		items[i].value = value;
 		p = strchr(p+1,',');
 		p = strchr(p+1,',');
 	}
 	}
-	if( i == 0 )
-	{
+	if( i == 0 ) {
 		ShowWarning("npc_parse_shop: Ignoring empty shop in file '%s', line '%d'.\n", filepath, strline(buffer,start-buffer));
 		ShowWarning("npc_parse_shop: Ignoring empty shop in file '%s', line '%d'.\n", filepath, strline(buffer,start-buffer));
 		return strchr(start,'\n');// continue
 		return strchr(start,'\n');// continue
 	}
 	}
@@ -2279,6 +2393,8 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
 	CREATE(nd->u.shop.shop_item, struct npc_item_list, i);
 	CREATE(nd->u.shop.shop_item, struct npc_item_list, i);
 	memcpy(nd->u.shop.shop_item, items, sizeof(struct npc_item_list)*i);
 	memcpy(nd->u.shop.shop_item, items, sizeof(struct npc_item_list)*i);
 	nd->u.shop.count = i;
 	nd->u.shop.count = i;
+	nd->u.shop.itemshop_nameid = nameid; // Item shop currency
+	safestrncpy(nd->u.shop.pointshop_str,point_str,strlen(point_str)+1); // Point shop currency
 	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;
@@ -2306,7 +2422,6 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
 		map_addiddb(&nd->bl);
 		map_addiddb(&nd->bl);
 	}
 	}
 	strdb_put(npcname_db, nd->exname, nd);
 	strdb_put(npcname_db, nd->exname, nd);
-
 	return strchr(start,'\n');// continue
 	return strchr(start,'\n');// continue
 }
 }
 
 
@@ -2592,7 +2707,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==SHOP || type==CASHSHOP || type==SCRIPT) && strcmp(w1, "-") == 0 ) {// floating shop/chashshop/script
+	if( (type == SHOP || type == CASHSHOP || type == ITEMSHOP || type == POINTSHOP || type == SCRIPT) && strcmp(w1, "-") == 0 ) {// floating shop/chashshop/itemshop/pointshop/script
 		x = y = dir = 0;
 		x = y = dir = 0;
 		m = -1;
 		m = -1;
 	} else {
 	} else {
@@ -2640,6 +2755,8 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch
 
 
 		case SHOP:
 		case SHOP:
 		case CASHSHOP:
 		case CASHSHOP:
+		case ITEMSHOP:
+		case POINTSHOP:
 			++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;
@@ -3642,34 +3759,22 @@ void 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")) && count > 3 )
-		{
+		else if( (!strcasecmp(w2,"shop") || !strcasecmp(w2,"cashshop") || !strcasecmp(w2,"itemshop") || !strcasecmp(w2,"pointshop")) && 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 )
 				p = npc_parse_function(w1, w2, w3, w4, p, buffer, filepath);
 				p = npc_parse_function(w1, w2, w3, w4, p, buffer, filepath);
 			else
 			else
 				p = npc_parse_script(w1,w2,w3,w4, p, buffer, filepath,runOnInit);
 				p = npc_parse_script(w1,w2,w3,w4, p, buffer, filepath,runOnInit);
 		}
 		}
 		else if( (i=0, sscanf(w2,"duplicate%n",&i), (i > 0 && w2[i] == '(')) && count > 3 )
 		else if( (i=0, sscanf(w2,"duplicate%n",&i), (i > 0 && w2[i] == '(')) && count > 3 )
-		{
 			p = npc_parse_duplicate(w1,w2,w3,w4, p, buffer, filepath);
 			p = npc_parse_duplicate(w1,w2,w3,w4, p, buffer, filepath);
-		}
 		else if( (strcmpi(w2,"monster") == 0 || strcmpi(w2,"boss_monster") == 0) && count > 3 )
 		else if( (strcmpi(w2,"monster") == 0 || strcmpi(w2,"boss_monster") == 0) && count > 3 )
-		{
 			p = npc_parse_mob(w1, w2, w3, w4, p, buffer, filepath);
 			p = npc_parse_mob(w1, w2, w3, w4, p, buffer, filepath);
-		}
 		else if( strcmpi(w2,"mapflag") == 0 && count >= 3 )
 		else if( strcmpi(w2,"mapflag") == 0 && count >= 3 )
-		{
 			p = npc_parse_mapflag(w1, w2, trim(w3), trim(w4), p, buffer, filepath);
 			p = npc_parse_mapflag(w1, w2, trim(w3), trim(w4), p, buffer, filepath);
-		}
-		else
-		{
+		else {
 			ShowError("npc_parsesrcfile: Unable to parse, probably a missing or extra TAB in file '%s', line '%d'. Skipping line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,p-buffer), w1, w2, w3, w4);
 			ShowError("npc_parsesrcfile: Unable to parse, probably a missing or extra TAB in file '%s', line '%d'. Skipping line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,p-buffer), w1, w2, w3, w4);
 			p = strchr(p,'\n');// skip and continue
 			p = strchr(p,'\n');// skip and continue
 		}
 		}

+ 2 - 0
src/map/npc.h

@@ -58,6 +58,8 @@ struct npc_data {
 		struct {
 		struct {
 			struct npc_item_list* shop_item;
 			struct npc_item_list* shop_item;
 			int count;
 			int count;
+			int itemshop_nameid; // Item Shop cost item ID
+			char pointshop_str[32]; // Point Shop cost variable name
 		} shop;
 		} shop;
 		struct {
 		struct {
 			short xs,ys; // OnTouch area radius
 			short xs,ys; // OnTouch area radius

+ 3 - 3
src/map/pc.c

@@ -9075,7 +9075,7 @@ int pc_check_available_item(struct map_session_data *sd) {
 			it = sd->status.inventory[i].nameid;
 			it = sd->status.inventory[i].nameid;
 
 
 			if( it && !itemdb_available(it) ) {
 			if( it && !itemdb_available(it) ) {
-				sprintf(output, msg_txt(sd, 681), it); // Item %d has been removed from your inventory.
+				sprintf(output, msg_txt(sd, 709), it); // Item %d has been removed from your inventory.
 				clif_displaymessage(sd->fd, output);
 				clif_displaymessage(sd->fd, output);
 				ShowWarning("Removed invalid/disabled item id %d from inventory (amount=%d, char_id=%d).\n", it, sd->status.inventory[i].amount, sd->status.char_id);
 				ShowWarning("Removed invalid/disabled item id %d from inventory (amount=%d, char_id=%d).\n", it, sd->status.inventory[i].amount, sd->status.char_id);
 				pc_delitem(sd, i, sd->status.inventory[i].amount, 0, 0, LOG_TYPE_OTHER);
 				pc_delitem(sd, i, sd->status.inventory[i].amount, 0, 0, LOG_TYPE_OTHER);
@@ -9088,7 +9088,7 @@ int pc_check_available_item(struct map_session_data *sd) {
 			it = sd->status.cart[i].nameid;
 			it = sd->status.cart[i].nameid;
 
 
 			if( it && !itemdb_available(it) ) {
 			if( it && !itemdb_available(it) ) {
-				sprintf(output, msg_txt(sd, 682), it); // Item %d has been removed from your cart.
+				sprintf(output, msg_txt(sd, 710), it); // Item %d has been removed from your cart.
 				clif_displaymessage(sd->fd, output);
 				clif_displaymessage(sd->fd, output);
 				ShowWarning("Removed invalid/disabled item id %d from cart (amount=%d, char_id=%d).\n", it, sd->status.cart[i].amount, sd->status.char_id);
 				ShowWarning("Removed invalid/disabled item id %d from cart (amount=%d, char_id=%d).\n", it, sd->status.cart[i].amount, sd->status.char_id);
 				pc_cart_delitem(sd, i, sd->status.cart[i].amount, 0, LOG_TYPE_OTHER);
 				pc_cart_delitem(sd, i, sd->status.cart[i].amount, 0, LOG_TYPE_OTHER);
@@ -9101,7 +9101,7 @@ int pc_check_available_item(struct map_session_data *sd) {
 			it = sd->status.storage.items[i].nameid;
 			it = sd->status.storage.items[i].nameid;
 
 
 			if( it && !itemdb_available(it) ) {
 			if( it && !itemdb_available(it) ) {
-				sprintf(output, msg_txt(sd, 683), it); // Item %d has been removed from your storage.
+				sprintf(output, msg_txt(sd, 711), it); // Item %d has been removed from your storage.
 				clif_displaymessage(sd->fd, output);
 				clif_displaymessage(sd->fd, output);
 				ShowWarning("Removed invalid/disabled item id %d from storage (amount=%d, char_id=%d).\n", it, sd->status.storage.items[i].amount, sd->status.char_id);
 				ShowWarning("Removed invalid/disabled item id %d from storage (amount=%d, char_id=%d).\n", it, sd->status.storage.items[i].amount, sd->status.char_id);
 				storage_delitem(sd, i, sd->status.storage.items[i].amount);
 				storage_delitem(sd, i, sd->status.storage.items[i].amount);

+ 5 - 5
src/map/script.c

@@ -14955,14 +14955,14 @@ BUILDIN_FUNC(callshop)
 	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 != SHOP && nd->subtype != CASHSHOP) )
+	if( !nd || nd->bl.type != BL_NPC || (nd->subtype != SHOP && nd->subtype != CASHSHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP) )
 	{
 	{
 		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 == SHOP )
+	if( nd->subtype == SHOP || nd->subtype == ITEMSHOP || nd->subtype == 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;
@@ -14989,7 +14989,7 @@ BUILDIN_FUNC(npcshopitem)
 	int n, i;
 	int n, i;
 	int amount;
 	int amount;
 
 
-	if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) )
+	if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP ) )
 	{	//Not found.
 	{	//Not found.
 		script_pushint(st,0);
 		script_pushint(st,0);
 		return 0;
 		return 0;
@@ -15018,7 +15018,7 @@ BUILDIN_FUNC(npcshopadditem)
 	int n, i;
 	int n, i;
 	int amount;
 	int amount;
 
 
-	if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) )
+	if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP ) )
 	{	//Not found.
 	{	//Not found.
 		script_pushint(st,0);
 		script_pushint(st,0);
 		return 0;
 		return 0;
@@ -15049,7 +15049,7 @@ BUILDIN_FUNC(npcshopdelitem)
 	int amount;
 	int amount;
 	int size;
 	int size;
 
 
-	if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) )
+	if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP && nd->subtype != ITEMSHOP && nd->subtype != POINTSHOP ) )
 	{	//Not found.
 	{	//Not found.
 		script_pushint(st,0);
 		script_pushint(st,0);
 		return 0;
 		return 0;