Merge pull request #364 from rathena/feature/market_shop

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 here-and-there).
* There's new table, see `upgrade_20150327_market.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).
This commit is contained in:
Cydh Ramdh 2015-03-27 21:40:01 +07:00
commit 40c63f403f
13 changed files with 738 additions and 94 deletions

View File

@ -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

View File

@ -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.

View File

@ -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 0x09D5,-1
0x09D8,2,dull,0 //npcmarketclosed 0x09D6,-1,npcmarketpurchase,2:4:6
0x09D7,-1
0x09D8,2,npcmarketclosed,0
0x09DF,7 0x09DF,7
//Add new packets here //Add new packets here

View File

@ -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>}}}}};

View File

@ -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;

View File

@ -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;

View File

@ -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; uint16 n = (RFIFOW(fd,info->pos[0])-4) /4;
unsigned short* item_list = (unsigned short*)RFIFOP(fd,info->pos[1]);
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 {

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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++ ) 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_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].qty, &key_amount);
script_setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i*2], &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. /**
/// * Shop buylist
/// @param item_list 'n' pairs <amount,itemid> * @param sd Player who attempt to buy
/// @return result code for clif_parse_NpcBuyListSend * @param n Number of item will be bought
int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) * @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 ) for( i = 0; i < n; ++i ) {
{ unsigned short nameid, amount;
unsigned short nameid; int value;
int amount, 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].nameid == shop[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 == 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]; #if PACKETVER >= 20131223
nameid = item_list[i*2+1] = nd->u.shop.shop_item[j].nameid; //item_avail replacement if (nd->subtype == NPCTYPE_MARKETSHOP) {
value = nd->u.shop.shop_item[j].value; 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 ) if( !itemdb_isstackable(nameid) && amount > 1 ) { //Exploit? You can't buy more than 1 of equipment types o.O
{ //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 ) if( nd->master_nd ) { // Script-controlled shops decide by themselves, what can be bought and for what price.
{// 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 ) for( i = 0; i < n; ++i ) {
{ unsigned short nameid = item_list[i].nameid;
unsigned short nameid = item_list[i*2+1]; unsigned short amount = item_list[i].qty;
int amount = item_list[i*2+0];
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; unsigned short nameid2, qty = 0;
int value; 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 ) { switch(type) {
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); case NPCTYPE_SHOP:
break; 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);
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);
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();

View File

@ -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); uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list);
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);
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

View File

@ -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 ) ) if( !nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP ) ) { // Not found.
{ //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 ) ) if (!nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP)) { // Not found.
{ //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 ) ) if (!nd || ( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP)) { // Not found.
{ //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},