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
vending_db: vendings
vending_items_db: vending_items
market_table: market
// Use SQL item_db, mob_db and mob_skill_db for the map server? (yes/no)
use_sql_db: no

View File

@ -537,7 +537,7 @@
532: Shadow Right Accessory,
533: Shadow Left Accessory,
//534: // Free
534: Shop is out of stock! Please come back later.
// Bot detect messages (currently unused)
535: Possible use of BOT (99%% of chance) or modified client by '%s' (account: %d, char_id: %d). This player ask your name when you are hidden.

View File

@ -2310,10 +2310,12 @@ packet_keys: 0x631C511C,0x111C111C,0x111C111C // [Shakto]
//0x097E,12 //ZC_UPDATE_RANKING_POINT
0x09B4,6,dull,0 //Cash Shop - Special Tab
0x09CE,102,itemmonster,2
0x09D4,2,dull,0 //npcshopclosed
0x09D4,2,npcshopclosed,0
//NPC Market
0x09D6,-1,dull,0 //npcmarketpurchase
0x09D8,2,dull,0 //npcmarketclosed
0x09D5,-1
0x09D6,-1,npcmarketpurchase,2:4:6
0x09D7,-1
0x09D8,2,npcmarketclosed,0
0x09DF,7
//Add new packets here

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>...}
<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
clicking) will cause a shop window to come up. No code whatsoever runs in shop
NPCs and you can't change the prices otherwise than by editing the script
@ -6239,7 +6241,10 @@ specified will be for sale.
The function returns 1 if shop was updated successfully, or 0 if not found.
Note that you cannot use -1 to specify default selling price!
NOTES:
- That you cannot use -1 to specify default selling price!
- If attached shop type is market shop, need an extra param after price, it's <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.
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.
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>}}}}};

View File

@ -695,6 +695,10 @@ CREATE TABLE IF NOT EXISTS `storage` (
KEY `account_id` (`account_id`)
) ENGINE=MyISAM;
--
-- Table structure for table `interreg`
--
CREATE TABLE IF NOT EXISTS `interreg` (
`varname` varchar(11) NOT NULL,
`value` varchar(20) NOT NULL,
@ -714,6 +718,10 @@ CREATE TABLE IF NOT EXISTS `bonus_script` (
`icon` SMALLINT(3) NOT NULL DEFAULT '-1'
) ENGINE=InnoDB;
--
-- Table structure for table `vending_items`
--
CREATE TABLE IF NOT EXISTS `vending_items` (
`vending_id` int(10) unsigned NOT NULL,
`index` smallint(5) unsigned NOT NULL,
@ -722,6 +730,10 @@ CREATE TABLE IF NOT EXISTS `vending_items` (
`price` int(10) unsigned NOT NULL
) ENGINE=MyISAM;
--
-- Table structure for table `vendings`
--
CREATE TABLE IF NOT EXISTS `vendings` (
`id` int(10) unsigned NOT NULL,
`account_id` int(11) unsigned NOT NULL,
@ -738,6 +750,10 @@ CREATE TABLE IF NOT EXISTS `vendings` (
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
--
-- Table structure for table `buyingstore_items`
--
CREATE TABLE IF NOT EXISTS `buyingstore_items` (
`buyingstore_id` int(10) unsigned NOT NULL,
`index` smallint(5) unsigned NOT NULL,
@ -746,6 +762,10 @@ CREATE TABLE IF NOT EXISTS `buyingstore_items` (
`price` int(10) unsigned NOT NULL
) ENGINE=MyISAM;
--
-- Table structure for table `buyingstores`
--
CREATE TABLE IF NOT EXISTS `buyingstores` (
`id` int(10) unsigned NOT NULL,
`account_id` int(11) unsigned NOT NULL,
@ -762,3 +782,16 @@ CREATE TABLE IF NOT EXISTS `buyingstores` (
`autotrade` tinyint(4) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
--
-- Table `market` for market shop persistency
--
CREATE TABLE IF NOT EXISTS `market` (
`name` varchar(32) NOT NULL DEFAULT '',
`nameid` SMALLINT(5) UNSIGNED NOT NULL,
`price` INT(11) UNSIGNED NOT NULL,
`amount` SMALLINT(5) UNSIGNED NOT NULL,
`flag` TINYINT(2) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`name`,`nameid`)
) ENGINE = MyISAM;

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).
/// 00b4 <packet len>.W <npc id>.L <message>.?B
/// Client behavior (dialog window):
@ -10930,17 +11057,15 @@ void clif_npc_buy_result(struct map_session_data* sd, unsigned char result)
void clif_parse_NpcBuyListSend(int fd, struct map_session_data* sd)
{
struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
int n = (RFIFOW(fd,info->pos[0])-4) /4;
unsigned short* item_list = (unsigned short*)RFIFOP(fd,info->pos[1]);
uint16 n = (RFIFOW(fd,info->pos[0])-4) /4;
int result;
if( sd->state.trading || !sd->npc_shopid )
result = 1;
else
result = npc_buylist(sd,n,item_list);
result = npc_buylist(sd, n, (struct s_npc_buy_list*)RFIFOP(fd,info->pos[1]));
sd->npc_shopid = 0; //Clear shop data.
clif_npc_buy_result(sd, result);
}
@ -17989,7 +18114,7 @@ void packetdb_readdb(bool reload)
0, 0, 0, 0, 0, 0, 6, 4, 6, 4, 0, 0, 0, 0, 0, 0,
//#0x09C0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0,102, 0,
0, 0, 0, 0, 2, 0, -1, 0, 2, 0, 0, 0, 0, 0, 0, 7,
0, 0, 0, 0, 2, 0, -1, -1, 2, 0, 0, 0, 0, 0, 0, 7,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
@ -18214,6 +18339,9 @@ void packetdb_readdb(bool reload)
{ clif_parse_client_version, "clientversion"},
{ clif_parse_blocking_playcancel, "booking_playcancel"},
{ clif_parse_ranklist, "ranklist"},
/* Market NPC */
{ clif_parse_NPCMarketClosed, "npcmarketclosed" },
{ clif_parse_NPCMarketPurchase, "npcmarketpurchase" },
{NULL,NULL}
};
struct {

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_buylist(struct map_session_data *sd, struct npc_data *nd); //self
void clif_selllist(struct map_session_data *sd); //self
void clif_npc_market_open(struct map_session_data *sd, struct npc_data *nd);
void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd);
void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd);
void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes); //self
void clif_scriptnext(struct map_session_data *sd,int npcid); //self
void clif_scriptclose(struct map_session_data *sd, int npcid); //self

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

View File

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

View File

@ -40,6 +40,20 @@ static int npc_mob=0;
static int npc_delay_mob=0;
static int npc_cache_mob=0;
// Market Shop
#if PACKETVER >= 20131223
struct s_npc_market {
struct npc_item_list *list;
char exname[NAME_LENGTH+1];
uint16 count;
};
static DBMap *NPCMarketDB; /// Stock persistency! Temporary market stocks from `market` table. struct s_npc_market, key: NPC exname
static void npc_market_checkall(void);
static void npc_market_fromsql(void);
#define npc_market_delfromsql(exname,nameid) (npc_market_delfromsql_((exname), (nameid), false))
#define npc_market_clearfromsql(exname) (npc_market_delfromsql_((exname), 0, true))
#endif
/// Returns a new npc id that isn't being used in id_db.
/// Fatal error if nothing is available.
int npc_get_new_npc_id(void) {
@ -1224,6 +1238,26 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd)
case NPCTYPE_CASHSHOP:
clif_cashshop_show(sd,nd);
break;
case NPCTYPE_MARKETSHOP:
#if PACKETVER >= 20131223
{
unsigned short i;
for (i = 0; i < nd->u.shop.count; i++) {
if (nd->u.shop.shop_item[i].qty)
break;
}
if (i == nd->u.shop.count) {
clif_colormes(sd, color_table[COLOR_RED], msg_txt(sd, 534));
return false;
}
sd->npc_shopid = nd->bl.id;
clif_npc_market_open(sd, nd);
}
#endif
break;
case NPCTYPE_SCRIPT:
run_script(nd->u.scr.script,0,sd->bl.id,nd->bl.id);
break;
@ -1318,7 +1352,7 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type)
sd->state.callshop = 0;
sd->npc_shopid = id;
if (type==0) {
if (type == 0) {
clif_buylist(sd,nd);
} else {
clif_selllist(sd);
@ -1410,9 +1444,14 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, uns
return 0;
}
//npc_buylist for script-controlled shops.
static int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd)
{
/**
* npc_buylist for script-controlled shops.
* @param sd Player who bought
* @param n Number of item
* @param item_list List of item
* @param nd Attached NPC
**/
static int npc_buylist_sub(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list, struct npc_data* nd) {
char npc_ev[EVENT_NAME_LENGTH];
int i;
int key_nameid = 0;
@ -1423,10 +1462,9 @@ static int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* i
script_cleararray_pc(sd, "@bought_quantity", (void*)0);
// save list of bought items
for( i = 0; i < n; i++ )
{
script_setarray_pc(sd, "@bought_nameid", i, (void*)(intptr_t)item_list[i*2+1], &key_nameid);
script_setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i*2], &key_amount);
for (i = 0; i < n; i++) {
script_setarray_pc(sd, "@bought_nameid", i, (void*)(intptr_t)item_list[i].nameid, &key_nameid);
script_setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i].qty, &key_amount);
}
// invoke event
@ -1519,16 +1557,20 @@ int npc_cashshop_buy(struct map_session_data *sd, unsigned short nameid, int amo
return 0;
}
/// Player item purchase from npc shop.
///
/// @param item_list 'n' pairs <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_item_list *shop = NULL;
double z;
int i,j,k,w,skill,new_,count = 0;
char output[CHAT_SIZE_MAX];
uint8 market_index[MAX_INVENTORY];
nullpo_retr(3, sd);
nullpo_retr(3, item_list);
@ -1536,48 +1578,58 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
nd = npc_checknear(sd,map_id2bl(sd->npc_shopid));
if( nd == NULL )
return 3;
if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP )
if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP )
return 3;
if (!item_list || !n)
return 3;
z = 0;
w = 0;
new_ = 0;
shop = nd->u.shop.shop_item;
memset(market_index, 0, sizeof(market_index));
// process entries in buy list, one by one
for( i = 0; i < n; ++i )
{
unsigned short nameid;
int amount, value;
for( i = 0; i < n; ++i ) {
unsigned short nameid, amount;
int value;
// find this entry in the shop's sell list
ARR_FIND( 0, nd->u.shop.count, j,
item_list[i*2+1] == nd->u.shop.shop_item[j].nameid || //Normal items
item_list[i*2+1] == itemdb_viewid(nd->u.shop.shop_item[j].nameid) //item_avail replacement
item_list[i].nameid == shop[j].nameid || //Normal items
item_list[i].nameid == itemdb_viewid(shop[j].nameid) //item_avail replacement
);
if( j == nd->u.shop.count )
return 3; // no such item in shop
amount = item_list[i*2+0];
nameid = item_list[i*2+1] = nd->u.shop.shop_item[j].nameid; //item_avail replacement
value = nd->u.shop.shop_item[j].value;
#if PACKETVER >= 20131223
if (nd->subtype == NPCTYPE_MARKETSHOP) {
if (item_list[i].qty > shop[j].qty)
return 3;
market_index[i] = j;
}
#endif
amount = item_list[i].qty;
nameid = item_list[i].nameid = shop[j].nameid; //item_avail replacement
value = shop[j].value;
if( !itemdb_exists(nameid) )
return 3; // item no longer in itemdb
if( !itemdb_isstackable(nameid) && amount > 1 )
{ //Exploit? You can't buy more than 1 of equipment types o.O
if( !itemdb_isstackable(nameid) && amount > 1 ) { //Exploit? You can't buy more than 1 of equipment types o.O
ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %hu!\n",
sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid);
amount = item_list[i*2+0] = 1;
amount = item_list[i].qty = 1;
}
if( nd->master_nd )
{// Script-controlled shops decide by themselves, what can be bought and for what price.
if( nd->master_nd ) { // Script-controlled shops decide by themselves, what can be bought and for what price.
continue;
}
switch( pc_checkadditem(sd,nameid,amount) )
{
switch( pc_checkadditem(sd,nameid,amount) ) {
case CHKADDITEM_EXIST:
break;
@ -1596,11 +1648,12 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
w += itemdb_weight(nameid) * amount;
}
if( nd->master_nd != NULL ) //Script-based shops.
if (nd->master_nd) //Script-based shops.
return npc_buylist_sub(sd,n,item_list,nd->master_nd);
switch(nd->subtype) {
case NPCTYPE_SHOP:
case NPCTYPE_MARKETSHOP:
if (z > (double)sd->status.zeny)
return 1; // Not enough Zeny
break;
@ -1645,6 +1698,7 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
switch(nd->subtype) {
case NPCTYPE_SHOP:
case NPCTYPE_MARKETSHOP:
pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
break;
case NPCTYPE_ITEMSHOP:
@ -1666,16 +1720,24 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
break;
}
for( i = 0; i < n; ++i )
{
unsigned short nameid = item_list[i*2+1];
int amount = item_list[i*2+0];
for( i = 0; i < n; ++i ) {
unsigned short nameid = item_list[i].nameid;
unsigned short amount = item_list[i].qty;
struct item item_tmp;
#if PACKETVER >= 20131223
if (nd->subtype == NPCTYPE_MARKETSHOP) {
j = market_index[i];
if (amount > shop[j].qty)
return 1;
shop[j].qty -= amount;
npc_market_tosql(nd->exname, &shop[j]);
}
#endif
if (itemdb_type(nameid) == IT_PETEGG)
pet_create_egg(sd, nameid);
else
{
else {
memset(&item_tmp,0,sizeof(item_tmp));
item_tmp.nameid = nameid;
item_tmp.identify = 1;
@ -1685,14 +1747,12 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
}
// custom merchant shop exp bonus
if( battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0 )
{
if( battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0 ) {
uint16 sk_idx = skill_get_index(MC_DISCOUNT);
if( sd->status.skill[sk_idx].flag >= SKILL_FLAG_REPLACED_LV_0 )
skill = sd->status.skill[sk_idx].flag - SKILL_FLAG_REPLACED_LV_0;
if( skill > 0 )
{
if( skill > 0 ) {
z = z * (double)skill * (double)battle_config.shop_exp/10000.;
if( z < 1 )
z = 1;
@ -1708,7 +1768,6 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
return 0;
}
/// npc_selllist for script-controlled shops
static int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd)
{
@ -1771,7 +1830,7 @@ static int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short*
///
/// @param item_list 'n' pairs <index,amount>
/// @return result code for clif_parse_NpcSellListSend
int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list)
uint8 npc_selllist(struct map_session_data* sd, int n, unsigned short *item_list)
{
double z;
int i,skill;
@ -1957,7 +2016,7 @@ int npc_unload(struct npc_data* nd, bool single) {
if( single && nd->bl.m != -1 )
map_remove_questinfo(nd->bl.m, nd);
if( (nd->subtype == NPCTYPE_SHOP || nd->subtype == NPCTYPE_CASHSHOP || nd->subtype == NPCTYPE_ITEMSHOP || nd->subtype == NPCTYPE_POINTSHOP) && nd->src_id == 0) //src check for duplicate shops [Orcao]
if( (nd->subtype == NPCTYPE_SHOP || nd->subtype == NPCTYPE_CASHSHOP || nd->subtype == NPCTYPE_ITEMSHOP || nd->subtype == NPCTYPE_POINTSHOP || nd->subtype == NPCTYPE_MARKETSHOP) && nd->src_id == 0) //src check for duplicate shops [Orcao]
aFree(nd->u.shop.shop_item);
else if( nd->subtype == NPCTYPE_SCRIPT ) {
struct s_mapiterator* iter;
@ -2375,8 +2434,9 @@ static const char* npc_parse_warp(char* w1, char* w2, char* w3, char* w4, const
* <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%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 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 w4 : word 4 before tab (<sprited id>,<shop definition...>)
* @param start : index to start parsing
@ -2420,6 +2480,8 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
type = NPCTYPE_ITEMSHOP;
else if( !strcasecmp(w2,"pointshop") )
type = NPCTYPE_POINTSHOP;
else if( !strcasecmp(w2, "marketshop") )
type = NPCTYPE_MARKETSHOP;
else
type = NPCTYPE_SHOP;
@ -2459,6 +2521,14 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
p = strchr(p+1,',');
break;
}
case NPCTYPE_MARKETSHOP:
#if PACKETVER < 20131223
ShowError("npc_parse_shop: (MARKETSHOP) Feature is disabled, need client 20131223 or newer. Ignoring file '%s', line '%d\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer, start - buffer), w1, w2, w3, w4);
return strchr(start, '\n'); // skip and continue
#else
is_discount = 0;
break;
#endif
default:
is_discount = 1;
break;
@ -2468,25 +2538,43 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
nd->u.shop.count = 0;
while ( p ) {
unsigned short nameid2;
int value;
unsigned short nameid2, qty = 0;
int value, i = 0;
struct item_data* id;
bool skip = false;
if( p == NULL )
break;
if( sscanf(p, ",%hu:%d", &nameid2, &value) != 2 ) {
ShowError("npc_parse_shop: Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4);
break;
switch(type) {
case NPCTYPE_SHOP:
if (sscanf(p, ",%hu:%d", &nameid2, &value) != 2) {
ShowError("npc_parse_shop: (SHOP) Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer, start - buffer), w1, w2, w3, w4);
skip = true;
}
break;
case NPCTYPE_MARKETSHOP:
#if PACKETVER >= 20131223
if (sscanf(p, ",%hu:%d:%hu", &nameid2, &value, &qty) != 3) {
ShowError("npc_parse_shop: (MARKETSHOP) Invalid item definition in file '%s', line '%d'. Ignoring the rest of the line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer, start - buffer), w1, w2, w3, w4);
skip = true;
}
#endif
break;
}
if (skip)
break;
if( (id = itemdb_exists(nameid2)) == NULL ) {
ShowWarning("npc_parse_shop: Invalid sell item in file '%s', line '%d' (id '%hu').\n", filepath, strline(buffer,start-buffer), nameid2);
p = strchr(p+1,',');
continue;
}
if( value < 0 ) {
if( type == NPCTYPE_SHOP ) value = id->value_buy;
if (type == NPCTYPE_SHOP || type == NPCTYPE_MARKETSHOP) value = id->value_buy;
else value = 0; // Cashshop doesn't have a "buy price" in the item_db
}
if( (type == NPCTYPE_SHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP) && value == 0 ) { // NPC selling items for free!
if (value == 0 && (type == NPCTYPE_SHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP || type == NPCTYPE_MARKETSHOP)) { // NPC selling items for free!
ShowWarning("npc_parse_shop: Item %s [%hu] is being sold for FREE in file '%s', line '%d'.\n",
id->name, nameid2, filepath, strline(buffer,start-buffer));
}
@ -2494,17 +2582,37 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
ShowWarning("npc_parse_shop: Item %s [%hu] discounted buying price (%d->%d) is less than overcharged selling price (%d->%d) at file '%s', line '%d'.\n",
id->name, nameid2, value, (int)(value*0.75), id->value_sell, (int)(id->value_sell*1.24), filepath, strline(buffer,start-buffer));
}
if (type == NPCTYPE_MARKETSHOP && (!qty || qty > UINT16_MAX)) {
ShowWarning("npc_parse_shop: Item %s [%hu] is stocked with invalid value %d, changed to 1. File '%s', line '%d'.\n",
id->name, nameid2, filepath, strline(buffer,start-buffer));
qty = 1;
}
//for logs filters, atcommands and iteminfo script command
if( id->maxchance == 0 )
id->maxchance = -1; // -1 would show that the item's sold in NPC Shop
#if PACKETVER >= 20131223
if (nd->u.shop.count && type == NPCTYPE_MARKETSHOP) {
// Duplicate entry? Replace the value
ARR_FIND(0, nd->u.shop.count, i, nd->u.shop.shop_item[i].nameid == nameid);
if (i != nd->u.shop.count) {
nd->u.shop.shop_item[i].qty = qty;
nd->u.shop.shop_item[i].value = value;
p = strchr(p+1,',');
continue;
}
}
#endif
if (nd->u.shop.count > 0)
RECREATE(nd->u.shop.shop_item, struct npc_item_list,nd->u.shop.count+1);
else
CREATE(nd->u.shop.shop_item, struct npc_item_list,1);
RECREATE(nd->u.shop.shop_item, struct npc_item_list,nd->u.shop.count+1);
nd->u.shop.shop_item[nd->u.shop.count].nameid = nameid2;
nd->u.shop.shop_item[nd->u.shop.count].value = value;
#if PACKETVER >= 20131223
nd->u.shop.shop_item[nd->u.shop.count].flag = 0;
if (type == NPCTYPE_MARKETSHOP)
nd->u.shop.shop_item[nd->u.shop.count].qty = qty;
#endif
nd->u.shop.count++;
p = strchr(p+1,',');
}
@ -2519,6 +2627,7 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
else if (type == NPCTYPE_POINTSHOP) safestrncpy(nd->u.shop.pointshop_str,point_str,strlen(point_str)+1); // Point shop currency
nd->u.shop.discount = is_discount;
}
nd->bl.prev = nd->bl.next = NULL;
nd->bl.m = m;
nd->bl.x = x;
@ -2531,6 +2640,14 @@ static const char* npc_parse_shop(char* w1, char* w2, char* w3, char* w4, const
++npc_shop;
nd->bl.type = BL_NPC;
nd->subtype = type;
#if PACKETVER >= 20131223
// Insert market data to table
if (nd->subtype == NPCTYPE_MARKETSHOP) {
uint16 i;
for (i = 0; i < nd->u.shop.count; i++)
npc_market_tosql(nd->exname, &nd->u.shop.shop_item[i]);
}
#endif
if( m >= 0 )
{// normal shop npc
map_addnpc(m,nd);
@ -2857,7 +2974,7 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch
type = dnd->subtype;
// get placement
if( (type == NPCTYPE_SHOP || type == NPCTYPE_CASHSHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP || type == NPCTYPE_SCRIPT) && strcmp(w1, "-") == 0 ) {// floating shop/chashshop/itemshop/pointshop/script
if ((type == NPCTYPE_SHOP || type == NPCTYPE_CASHSHOP || type == NPCTYPE_ITEMSHOP || type == NPCTYPE_POINTSHOP || type == NPCTYPE_SCRIPT || type == NPCTYPE_MARKETSHOP) && strcmp(w1, "-") == 0) {// floating shop/chashshop/itemshop/pointshop/script
x = y = dir = 0;
m = -1;
} else {
@ -2908,6 +3025,7 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch
case NPCTYPE_CASHSHOP:
case NPCTYPE_ITEMSHOP:
case NPCTYPE_POINTSHOP:
case NPCTYPE_MARKETSHOP:
++npc_shop;
nd->u.shop.shop_item = dnd->u.shop.shop_item;
nd->u.shop.count = dnd->u.shop.count;
@ -3059,6 +3177,176 @@ int npc_instanceinit(struct npc_data* nd)
return 0;
}
#if PACKETVER >= 20131223
/**
* Saves persistent NPC Market Data into SQL
* @param exname NPC exname
* @param nameid Item ID
* @param qty Stock
**/
void npc_market_tosql(const char *exname, struct npc_item_list *list) {
SqlStmt* stmt = SqlStmt_Malloc(mmysql_handle);
if (SQL_ERROR == SqlStmt_Prepare(stmt, "REPLACE INTO `%s` (`name`,`nameid`,`price`,`amount`,`flag`) VALUES ('%s','%hu','%d','%hu','%"PRIu8"')",
market_table, exname, list->nameid, list->value, list->qty, list->flag) ||
SQL_ERROR == SqlStmt_Execute(stmt))
SqlStmt_ShowDebug(stmt);
SqlStmt_Free(stmt);
}
/**
* Removes persistent NPC Market Data from SQL
* @param exname NPC exname
* @param nameid Item ID
* @param clear True: will removes all records related with the NPC
**/
void npc_market_delfromsql_(const char *exname, unsigned short nameid, bool clear) {
SqlStmt* stmt = SqlStmt_Malloc(mmysql_handle);
if (clear) {
if( SQL_ERROR == SqlStmt_Prepare(stmt, "DELETE FROM `%s` WHERE `name`='%s'", market_table, exname) ||
SQL_ERROR == SqlStmt_Execute(stmt))
SqlStmt_ShowDebug(stmt);
} else {
if (SQL_ERROR == SqlStmt_Prepare(stmt, "DELETE FROM `%s` WHERE `name`='%s' AND `nameid`='%d' LIMIT 1", market_table, exname, nameid) ||
SQL_ERROR == SqlStmt_Execute(stmt))
SqlStmt_ShowDebug(stmt);
}
SqlStmt_Free(stmt);
}
/**
* Check NPC Market Shop for each entry
**/
static int npc_market_checkall_sub(DBKey key, DBData *data, va_list ap) {
struct s_npc_market *market = (struct s_npc_market *)db_data2ptr(data);
struct npc_data *nd = NULL;
uint16 i;
if (!market)
return 1;
nd = npc_name2id(market->exname);
if (!nd) {
ShowInfo("npc_market_checkall_sub: NPC '%s' not found, removing...\n", market->exname);
npc_market_clearfromsql(market->exname);
return 1;
}
else if (nd->subtype != NPCTYPE_MARKETSHOP || !nd->u.shop.shop_item || !nd->u.shop.count ) {
ShowError("npc_market_checkall_sub: NPC '%s' is not proper for market, removing...\n", nd->exname);
npc_market_clearfromsql(nd->exname);
return 1;
}
if (!market->count || !market->list)
return 1;
for (i = 0; i < market->count; i++) {
struct npc_item_list *list = &market->list[i];
uint16 j;
if (!list->nameid || !itemdb_exists(list->nameid)) {
ShowError("npc_market_checkall_sub: NPC '%s' sells invalid item '%hu', deleting...\n", nd->exname, list->nameid);
npc_market_delfromsql(nd->exname, list->nameid);
continue;
}
// Reloading stock from `market` table
ARR_FIND(0, nd->u.shop.count, j, nd->u.shop.shop_item[j].nameid == list->nameid);
if (j != nd->u.shop.count) {
nd->u.shop.shop_item[j].value = list->value;
nd->u.shop.shop_item[j].qty = list->qty;
nd->u.shop.shop_item[j].flag = list->flag;
npc_market_tosql(nd->exname, &nd->u.shop.shop_item[j]);
continue;
}
if (list->flag&1) { // Item added by npcshopitem/npcshopadditem, add new entry
RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+1);
nd->u.shop.shop_item[j].nameid = list->nameid;
nd->u.shop.shop_item[j].value = list->value;
nd->u.shop.shop_item[j].qty = list->qty;
nd->u.shop.shop_item[j].flag = list->flag;
nd->u.shop.count++;
npc_market_tosql(nd->exname, &nd->u.shop.shop_item[j]);
}
else { // Removing "out-of-date" entry
ShowError("npc_market_checkall_sub: NPC '%s' does not sell item %hu (qty %hu), deleting...\n", nd->exname, list->nameid, list->qty);
npc_market_delfromsql(nd->exname, list->nameid);
}
}
return 0;
}
/**
* Clear NPC market single entry
**/
static int npc_market_free(DBKey key, DBData *data, va_list ap) {
struct s_npc_market *market = (struct s_npc_market *)db_data2ptr(data);
if (!market)
return 0;
if (market->list) {
aFree(market->list);
market->list = NULL;
}
aFree(market);
return 1;
}
/**
* Check all existing NPC Market Shop after first loading map-server or after reloading scripts.
* Overwrite stocks from NPC by using stock entries from `market` table.
**/
static void npc_market_checkall(void) {
if (!db_size(NPCMarketDB))
return;
else {
ShowInfo("Checking '"CL_WHITE"%d"CL_RESET"' NPC Markets...\n", db_size(NPCMarketDB));
NPCMarketDB->foreach(NPCMarketDB, npc_market_checkall_sub);
ShowStatus("Done checking '"CL_WHITE"%d"CL_RESET"' NPC Markets.\n", db_size(NPCMarketDB));
NPCMarketDB->clear(NPCMarketDB, npc_market_free);
}
}
/**
* Loads persistent NPC Market Data from SQL, use the records after NPCs init'd to reuse the stock values.
**/
static void npc_market_fromsql(void) {
uint32 count = 0;
if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT `name`,`nameid`,`price`,`amount`,`flag` FROM `%s` ORDER BY `name`", market_table)) {
Sql_ShowDebug(mmysql_handle);
return;
}
while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) {
char *data;
struct s_npc_market *market;
struct npc_item_list list;
Sql_GetData(mmysql_handle, 0, &data, NULL);
if (!(market = (struct s_npc_market *)strdb_get(NPCMarketDB,data))) {
CREATE(market, struct s_npc_market, 1);
market->count = 0;
safestrncpy(market->exname, data, strlen(data)+1);
strdb_put(NPCMarketDB, market->exname, market);
}
Sql_GetData(mmysql_handle, 1, &data, NULL); list.nameid = atoi(data);
Sql_GetData(mmysql_handle, 2, &data, NULL); list.value = atoi(data);
Sql_GetData(mmysql_handle, 3, &data, NULL); list.qty = atoi(data);
Sql_GetData(mmysql_handle, 4, &data, NULL); list.flag = atoi(data);
RECREATE(market->list, struct npc_item_list, market->count+1);
market->list[market->count++] = list;
count++;
}
Sql_FreeResult(mmysql_handle);
ShowStatus("Done loading '"CL_WHITE"%d"CL_RESET"' entries for '"CL_WHITE"%d"CL_RESET"' NPC Markets from '"CL_WHITE"%s"CL_RESET"' table.\n", count, db_size(NPCMarketDB), market_table);
}
#endif
//Set mapcell CELL_NPC to trigger event later
void npc_setcells(struct npc_data* nd)
{
@ -3928,7 +4216,7 @@ int npc_parsesrcfile(const char* filepath, bool runOnInit)
if( strcasecmp(w2,"warp") == 0 && count > 3 )
p = npc_parse_warp(w1,w2,w3,w4, p, buffer, filepath);
else if( (!strcasecmp(w2,"shop") || !strcasecmp(w2,"cashshop") || !strcasecmp(w2,"itemshop") || !strcasecmp(w2,"pointshop")) && count > 3 )
else if( (!strcasecmp(w2,"shop") || !strcasecmp(w2,"cashshop") || !strcasecmp(w2,"itemshop") || !strcasecmp(w2,"pointshop") || !strcasecmp(w2,"marketshop") ) && count > 3 )
p = npc_parse_shop(w1,w2,w3,w4, p, buffer, filepath);
else if( strcasecmp(w2,"script") == 0 && count > 3 ) {
if( strcasecmp(w1,"function") == 0 )
@ -4057,6 +4345,10 @@ int npc_reload(void) {
//Remove all npcs/mobs. [Skotlex]
#if PACKETVER >= 20131223
npc_market_fromsql();
#endif
iter = mapit_geteachiddb();
for( bl = (struct block_list*)mapit_first(iter); mapit_exists(iter); bl = (struct block_list*)mapit_next(iter) ) {
switch(bl->type) {
@ -4130,6 +4422,10 @@ int npc_reload(void) {
if(!CheckForCharServer()){
ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc_event_doall("OnInterIfInit"));
}
#if PACKETVER >= 20131223
npc_market_checkall();
#endif
return 0;
}
@ -4170,6 +4466,9 @@ void do_final_npc(void) {
ev_db->destroy(ev_db, NULL);
npcname_db->destroy(npcname_db, NULL);
npc_path_db->destroy(npc_path_db, NULL);
#if PACKETVER >= 20131223
NPCMarketDB->destroy(NPCMarketDB, npc_market_free);
#endif
ers_destroy(timer_event_ers);
npc_clearsrcfile();
}
@ -4226,6 +4525,10 @@ void do_init_npc(void){
ev_db = strdb_alloc((DBOptions)(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA),2*NAME_LENGTH+2+1);
npcname_db = strdb_alloc(DB_OPT_BASE,NAME_LENGTH);
npc_path_db = strdb_alloc(DB_OPT_BASE|DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA,80);
#if PACKETVER >= 20131223
NPCMarketDB = strdb_alloc(DB_OPT_BASE, NAME_LENGTH+1);
npc_market_fromsql();
#endif
timer_event_ers = ers_new(sizeof(struct timer_event_data),"clif.c::timer_event_ers",ERS_OPT_NONE);
@ -4248,6 +4551,10 @@ void do_init_npc(void){
memset(script_event, 0, sizeof(script_event));
npc_read_event_script();
#if PACKETVER >= 20131223
npc_market_checkall();
#endif
//Debug function to locate all endless loop warps.
if (battle_config.warp_point_debug)
npc_debug_warps();

View File

@ -15,13 +15,26 @@ struct view_data;
struct npc_timerevent_list {
int timer,pos;
};
struct npc_label_list {
char name[NAME_LENGTH];
int pos;
};
/// Item list for NPC sell/buy list
struct npc_item_list {
unsigned short nameid;
unsigned int value;
#if PACKETVER >= 20131223
unsigned short qty; ///< Stock counter (Market shop)
uint8 flag; ///< 1: Item added by npcshopitem/npcshopadditem, force load! (Market shop)
#endif
};
/// List of bought/sold item for NPC shops
struct s_npc_buy_list {
unsigned short qty; ///< Amount of item will be bought
unsigned short nameid; ///< ID of item will be bought
};
struct npc_data {
@ -126,8 +139,8 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd);
int npc_scriptcont(struct map_session_data* sd, int id, bool closing);
struct npc_data* npc_checknear(struct map_session_data* sd, struct block_list* bl);
int npc_buysellsel(struct map_session_data* sd, int id, int type);
int npc_buylist(struct map_session_data* sd,int n, unsigned short* item_list);
int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list);
uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list);
uint8 npc_selllist(struct map_session_data* sd, int n, unsigned short *item_list);
void npc_parse_mob2(struct spawn_data* mob);
bool npc_viewisid(const char * viewid);
struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y);
@ -179,6 +192,11 @@ extern struct npc_data* fake_nd;
int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, unsigned short* item_list);
bool npc_shop_discount(enum npc_subtype type, bool discount);
#if PACKETVER >= 20131223
void npc_market_tosql(const char *exname, struct npc_item_list *list);
void npc_market_delfromsql_(const char *exname, unsigned short nameid, bool clear);
#endif
#ifdef SECURE_NPCTIMEOUT
int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data);
#endif

View File

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