Initial release of barter shops (#6508)

Fixes #5062

Thanks to @Atemo and @aleos89

Co-authored-by: Atemo <Atemo@users.noreply.github.com>
Co-authored-by: Aleos <aleos89@users.noreply.github.com>
This commit is contained in:
Lemongrass3110 2022-01-21 00:59:16 +01:00 committed by GitHub
parent 4ad35d82bd
commit e40da669ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1390 additions and 64 deletions

View File

@ -127,6 +127,7 @@ clan_table: clan
clan_alliance_table: clan_alliance clan_alliance_table: clan_alliance
// Map Database Tables // Map Database Tables
barter_table: barter
buyingstore_table: buyingstores buyingstore_table: buyingstores
buyingstore_items_table: buyingstore_items buyingstore_items_table: buyingstore_items
item_table: item_db item_table: item_db

View File

@ -293,6 +293,8 @@ these floating NPC objects are for. More on that below.
<map name>,<x>,<y>,<facing>%TAB%marketshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>:<quantity>{,<itemid>:<price>:<quantity>...} <map name>,<x>,<y>,<facing>%TAB%marketshop%TAB%<NPC Name>%TAB%<sprite id>,<itemid>:<price>:<quantity>{,<itemid>:<price>:<quantity>...}
Note: Additionally barter shops can be defined in npc/barters.yml
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

52
npc/barters.yml Normal file
View File

@ -0,0 +1,52 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Barter Database
###########################################################################
#
# Barter Settings
#
###########################################################################
# - Name NPC name.
# Map Map name. (Default: not on a map)
# X Map x coordinate. (Default: 0)
# Y Map y coordinate. (Default: 0)
# Direction Direction the NPC is looking. (Default: North)
# Sprite Sprite name of the NPC. (Default: FakeNpc)
# Items: List of sold items.
# - Index Index of the item inside the shop. (0-...)
# Maximum index depends on client.
# Item Aegis name of the item.
# Stock Amount of item in stock. 0 means unlimited. (Default: 0)
# Zeny Cost of them item in Zeny. (Default: 0)
# RequiredItems: List of required items (Optional)
# - Index Index of the required item. (0-4)
# Item Aegis name of required item.
# Amount Amount of required item. (Default: 1)
# Refine Refine level of required item. (Default: 0)
###########################################################################
Header:
Type: BARTER_DB
Version: 1
Footer:
Imports:
- Path: npc/re/merchants/barters.yml
Mode: Renewal
- Path: npc/custom/barters.yml

46
npc/custom/barters.yml Normal file
View File

@ -0,0 +1,46 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Barter Database
###########################################################################
#
# Barter Settings
#
###########################################################################
# - Name NPC name.
# Map Map name. (Default: not on a map)
# X Map x coordinate. (Default: 0)
# Y Map y coordinate. (Default: 0)
# Direction Direction the NPC is looking. (Default: North)
# Sprite Sprite name of the NPC. (Default: FakeNpc)
# Items: List of sold items.
# - Index Index of the item inside the shop. (0-...)
# Maximum index depends on client.
# Item Aegis name of the item.
# Stock Amount of item in stock. 0 means unlimited. (Default: 0)
# Zeny Cost of them item in Zeny. (Default: 0)
# RequiredItems: List of required items (Optional)
# - Index Index of the required item. (0-4)
# Item Aegis name of required item.
# Amount Amount of required item. (Default: 1)
# Refine Refine level of required item. (Default: 0)
###########################################################################
Header:
Type: BARTER_DB
Version: 1

View File

@ -0,0 +1,46 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Barter Database
###########################################################################
#
# Barter Settings
#
###########################################################################
# - Name NPC name.
# Map Map name. (Default: not on a map)
# X Map x coordinate. (Default: 0)
# Y Map y coordinate. (Default: 0)
# Direction Direction the NPC is looking. (Default: North)
# Sprite Sprite name of the NPC. (Default: FakeNpc)
# Items: List of sold items.
# - Index Index of the item inside the shop. (0-...)
# Maximum index depends on client.
# Item Aegis name of the item.
# Stock Amount of item in stock. 0 means unlimited. (Default: 0)
# Zeny Cost of them item in Zeny. (Default: 0)
# RequiredItems: List of required items (Optional)
# - Index Index of the required item. (0-4)
# Item Aegis name of required item.
# Amount Amount of required item. (Default: 1)
# Refine Refine level of required item. (Default: 0)
###########################################################################
Header:
Type: BARTER_DB
Version: 1

View File

@ -166,12 +166,13 @@ CREATE TABLE IF NOT EXISTS `npclog` (
# (Z) Merged Items # (Z) Merged Items
# (Q)uest # (Q)uest
# Private Airs(H)ip # Private Airs(H)ip
# Barter Shop (J)
CREATE TABLE IF NOT EXISTS `picklog` ( CREATE TABLE IF NOT EXISTS `picklog` (
`id` int(11) NOT NULL auto_increment, `id` int(11) NOT NULL auto_increment,
`time` datetime NOT NULL, `time` datetime NOT NULL,
`char_id` int(11) NOT NULL default '0', `char_id` int(11) NOT NULL default '0',
`type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y','Z','Q','H') NOT NULL default 'P', `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y','Z','Q','H','J') NOT NULL default 'P',
`nameid` int(10) unsigned NOT NULL default '0', `nameid` int(10) unsigned NOT NULL default '0',
`amount` int(11) NOT NULL default '1', `amount` int(11) NOT NULL default '1',
`refine` tinyint(3) unsigned NOT NULL default '0', `refine` tinyint(3) unsigned NOT NULL default '0',
@ -215,13 +216,14 @@ CREATE TABLE IF NOT EXISTS `picklog` (
# (E)Mail # (E)Mail
# (B)uying Store # (B)uying Store
# Ban(K) Transactions # Ban(K) Transactions
# Barter Shop (J)
CREATE TABLE IF NOT EXISTS `zenylog` ( CREATE TABLE IF NOT EXISTS `zenylog` (
`id` int(11) NOT NULL auto_increment, `id` int(11) NOT NULL auto_increment,
`time` datetime NOT NULL, `time` datetime NOT NULL,
`char_id` int(11) NOT NULL default '0', `char_id` int(11) NOT NULL default '0',
`src_id` int(11) NOT NULL default '0', `src_id` int(11) NOT NULL default '0',
`type` enum('T','V','P','M','S','N','D','C','A','E','I','B','K') NOT NULL default 'S', `type` enum('T','V','P','M','S','N','D','C','A','E','I','B','K','J') NOT NULL default 'S',
`amount` int(11) NOT NULL default '0', `amount` int(11) NOT NULL default '0',
`map` varchar(11) NOT NULL default '', `map` varchar(11) NOT NULL default '',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),

View File

@ -90,6 +90,17 @@ CREATE TABLE IF NOT EXISTS `auction` (
PRIMARY KEY (`auction_id`) PRIMARY KEY (`auction_id`)
) ENGINE=MyISAM; ) ENGINE=MyISAM;
--
-- Table `barter` for barter shop persistency
--
CREATE TABLE IF NOT EXISTS `barter` (
`name` varchar(50) NOT NULL DEFAULT '',
`index` SMALLINT(5) UNSIGNED NOT NULL,
`amount` SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (`name`,`index`)
) ENGINE=MyISAM;
-- --
-- Table structure for `db_roulette` -- Table structure for `db_roulette`
-- --

View File

@ -0,0 +1,10 @@
--
-- Table `barter` for barter shop persistency
--
CREATE TABLE IF NOT EXISTS `barter` (
`name` varchar(50) NOT NULL DEFAULT '',
`index` SMALLINT(5) UNSIGNED NOT NULL,
`amount` SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (`name`,`index`)
) ENGINE=MyISAM;

View File

@ -0,0 +1,7 @@
ALTER TABLE `picklog`
MODIFY `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y','Z','Q','H','J') NOT NULL default 'P'
;
ALTER TABLE `zenylog`
MODIFY `type` enum('T','V','P','M','S','N','D','C','A','E','I','B','K','J') NOT NULL default 'S'
;

View File

@ -107,6 +107,9 @@ typedef uint32 t_itemid;
#define DB_NAME_LEN 256 //max len of dbs #define DB_NAME_LEN 256 //max len of dbs
#define MAX_CLAN 500 #define MAX_CLAN 500
#define MAX_CLANALLIANCE 6 #define MAX_CLANALLIANCE 6
#ifndef MAX_BARTER_REQUIREMENTS
#define MAX_BARTER_REQUIREMENTS 5
#endif
#ifdef RENEWAL #ifdef RENEWAL
#define MAX_WEAPON_LEVEL 5 #define MAX_WEAPON_LEVEL 5

View File

@ -2312,7 +2312,7 @@ void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd) {
/// Purchase item from Market shop. /// Purchase item from Market shop.
/// 0x9d7 <packet len>.W <count>.B { <name id>.W <qty>.W <price>.L }* (ZC_NPC_MARKET_PURCHASE_RESULT) /// 0x9d7 <packet len>.W <count>.B { <name id>.W <qty>.W <price>.L }* (ZC_NPC_MARKET_PURCHASE_RESULT)
void clif_npc_market_purchase_ack(struct map_session_data *sd, uint8 res, uint8 n, struct s_npc_buy_list *list) { void clif_npc_market_purchase_ack(struct map_session_data *sd, e_purchase_result res, uint8 n, struct s_npc_buy_list *list) {
#if PACKETVER >= 20131223 #if PACKETVER >= 20131223
nullpo_retv( sd ); nullpo_retv( sd );
nullpo_retv( list ); nullpo_retv( list );
@ -2328,9 +2328,9 @@ void clif_npc_market_purchase_ack(struct map_session_data *sd, uint8 res, uint8
p->PacketType = HEADER_ZC_NPC_MARKET_PURCHASE_RESULT; p->PacketType = HEADER_ZC_NPC_MARKET_PURCHASE_RESULT;
#if PACKETVER_MAIN_NUM >= 20190807 || PACKETVER_RE_NUM >= 20190807 || PACKETVER_ZERO_NUM >= 20190814 #if PACKETVER_MAIN_NUM >= 20190807 || PACKETVER_RE_NUM >= 20190807 || PACKETVER_ZERO_NUM >= 20190814
p->result = ( res == 0 ? 0 : -1 ); p->result = ( res == e_purchase_result::PURCHASE_SUCCEED ? 0 : -1 );
#else #else
p->result = ( res == 0 ? 1 : 0 ); p->result = ( res == e_purchase_result::PURCHASE_SUCCEED ? 1 : 0 );
#endif #endif
int count = 0; int count = 0;
@ -2381,7 +2381,7 @@ void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd) {
list[i].qty = p->list[i].qty; list[i].qty = p->list[i].qty;
} }
uint8 res = npc_buylist( sd, count, list ); e_purchase_result res = npc_buylist( sd, count, list );
clif_npc_market_purchase_ack( sd, res, count, list ); clif_npc_market_purchase_ack( sd, res, count, list );
aFree( list ); aFree( list );
@ -12299,14 +12299,13 @@ void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd)
/// 12 = "The exchange was well done." /// 12 = "The exchange was well done."
/// 13 = "The item is already sold and out of stock." /// 13 = "The item is already sold and out of stock."
/// 14 = "There is not enough goods to exchange." /// 14 = "There is not enough goods to exchange."
void clif_npc_buy_result(struct map_session_data* sd, unsigned char result) void clif_npc_buy_result( struct map_session_data* sd, e_purchase_result result ){
{ struct PACKET_ZC_PC_PURCHASE_RESULT p = {};
int fd = sd->fd;
WFIFOHEAD(fd,packet_len(0xca)); p.packetType = HEADER_ZC_PC_PURCHASE_RESULT;
WFIFOW(fd,0) = 0xca; p.result = (uint8)result;
WFIFOB(fd,2) = result;
WFIFOSET(fd,packet_len(0xca)); clif_send( &p, sizeof( p ), &sd->bl, SELF );
} }
@ -12316,10 +12315,10 @@ void clif_parse_NpcBuyListSend( int fd, struct map_session_data* sd ){
const struct PACKET_CZ_PC_PURCHASE_ITEMLIST *p = (struct PACKET_CZ_PC_PURCHASE_ITEMLIST *)RFIFOP( fd, 0 ); const struct PACKET_CZ_PC_PURCHASE_ITEMLIST *p = (struct PACKET_CZ_PC_PURCHASE_ITEMLIST *)RFIFOP( fd, 0 );
uint16 n = ( p->packetLength - sizeof(struct PACKET_CZ_PC_PURCHASE_ITEMLIST) ) / sizeof( struct PACKET_CZ_PC_PURCHASE_ITEMLIST_sub ); uint16 n = ( p->packetLength - sizeof(struct PACKET_CZ_PC_PURCHASE_ITEMLIST) ) / sizeof( struct PACKET_CZ_PC_PURCHASE_ITEMLIST_sub );
int result; e_purchase_result result;
if( sd->state.trading || !sd->npc_shopid ) if( sd->state.trading || !sd->npc_shopid )
result = 1; result = e_purchase_result::PURCHASE_FAIL_MONEY;
else else
result = npc_buylist( sd, n, (struct s_npc_buy_list*)p->items ); result = npc_buylist( sd, n, (struct s_npc_buy_list*)p->items );
@ -22277,7 +22276,7 @@ void clif_parse_refineui_refine( int fd, struct map_session_data* sd ){
// Try to pay for the refine // Try to pay for the refine
if( pc_payzeny( sd, cost->zeny, LOG_TYPE_CONSUME, NULL ) ){ if( pc_payzeny( sd, cost->zeny, LOG_TYPE_CONSUME, NULL ) ){
clif_npc_buy_result( sd, 1 ); // "You do not have enough zeny." clif_npc_buy_result( sd, e_purchase_result::PURCHASE_FAIL_MONEY ); // "You do not have enough zeny."
return; return;
} }
@ -22704,6 +22703,315 @@ void clif_parse_inventory_expansion_reject( int fd, struct map_session_data* sd
#endif #endif
} }
void clif_barter_open( struct map_session_data& sd, struct npc_data& nd ){
#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
if( nd.subtype != NPCTYPE_BARTER || nd.u.barter.extended || sd.state.barter_open ){
return;
}
std::shared_ptr<s_npc_barter> barter = barter_db.find( nd.exname );
if( barter == nullptr ){
return;
}
sd.state.barter_open = true;
struct PACKET_ZC_NPC_BARTER_OPEN* p = (struct PACKET_ZC_NPC_BARTER_OPEN*)packet_buffer;
p->packetType = HEADER_ZC_NPC_BARTER_OPEN;
p->packetLength = (int16)sizeof( struct PACKET_ZC_NPC_BARTER_OPEN );
int16 count = 0;
for( const auto& itemPair : barter->items ){
struct PACKET_ZC_NPC_BARTER_OPEN_sub* item = &p->list[count];
struct item_data* id = itemdb_exists( itemPair.second->nameid );
item->nameid = client_nameid( id->nameid );
item->type = itemtype( id->nameid );
if( itemPair.second->stockLimited ){
item->amount = itemPair.second->stock;
}else{
item->amount = -1;
}
item->weight = id->weight;
item->index = itemPair.second->index;
#if PACKETVER_MAIN_NUM >= 20210203 || PACKETVER_RE_NUM >= 20211103
item->viewSprite = id->look;
item->location = pc_equippoint_sub( &sd, id );
#endif
// Use a loop if someone did not start with index 0
for( const auto& requirementPair : itemPair.second->requirements ){
std::shared_ptr<s_npc_barter_requirement> requirement = requirementPair.second;
item->currencyNameid = client_nameid( requirement->nameid );
item->currencyAmount = requirement->amount;
// It is a normal barter, cancel after first entry
break;
}
p->packetLength += (int16)( sizeof( *item ) );
count++;
}
clif_send( p, p->packetLength, &sd.bl, SELF );
#endif
}
void clif_parse_barter_close( int fd, struct map_session_data* sd ){
#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
if( sd->state.barter_open ){
sd->npc_shopid = 0;
sd->state.barter_open = false;
}
#endif
}
void clif_parse_barter_buy( int fd, struct map_session_data* sd ){
#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
// No shop open
if( sd->npc_shopid == 0 || !sd->state.barter_open ){
return;
}
struct npc_data* nd = map_id2nd( sd->npc_shopid );
// Unknown shop
if( nd == nullptr ){
return;
}
// Not a barter
if( nd->subtype != NPCTYPE_BARTER ){
return;
}
// It is an extended barter
if( nd->u.barter.extended ){
return;
}
std::shared_ptr<s_npc_barter> barter = barter_db.find( nd->exname );
if( barter == nullptr ){
return;
}
struct PACKET_CZ_NPC_BARTER_PURCHASE* p = (struct PACKET_CZ_NPC_BARTER_PURCHASE*)RFIFOP( fd, 0 );
uint16 entries = ( p->packetLength - sizeof( struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE ) ) / sizeof( struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE_sub );
// Empty purchase list
if( entries == 0 ){
return;
}
std::vector<s_barter_purchase> purchases;
purchases.reserve( entries );
// Make sure each shop index and target item id is only used once
for( int i = 0; i < entries; i++ ){
std::shared_ptr<s_npc_barter_item> item = util::map_find( barter->items, (uint16)p->list[i].shopIndex );
// Invalid shop index
if( item == nullptr ){
return;
}
for( int j = i + 1; j < entries; j++ ){
// Same shop index
if( p->list[i].shopIndex == p->list[j].shopIndex ){
return;
}
std::shared_ptr<s_npc_barter_item> item2 = util::map_find( barter->items, (uint16)p->list[j].shopIndex );
// Invalid shop index
if( item2 == nullptr ){
return;
}
// Same target item id
if( item->nameid == item2->nameid ){
return;
}
}
s_barter_purchase purchase = {};
purchase.item = item;
purchase.amount = p->list[i].amount;
purchases.push_back( purchase );
}
clif_npc_buy_result( sd, npc_barter_purchase( *sd, barter, purchases ) );
#endif
}
void clif_barter_extended_open( struct map_session_data& sd, struct npc_data& nd ){
#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
if( nd.subtype != NPCTYPE_BARTER || !nd.u.barter.extended || sd.state.barter_extended_open ){
return;
}
std::shared_ptr<s_npc_barter> barter = barter_db.find( nd.exname );
if( barter == nullptr ){
return;
}
sd.state.barter_extended_open = true;
struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN* p = (struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN*)packet_buffer;
p->packetType = HEADER_ZC_NPC_EXPANDED_BARTER_OPEN;
p->packetLength = (int16)sizeof( struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN );
p->items_count = 0;
for( const auto& itemPair : barter->items ){
// Needs dynamic calculation, because of variable currencies
struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub* item = (struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub*)( ( (uint8*)p ) + p->packetLength );
struct item_data* id = itemdb_exists( itemPair.second->nameid );
item->nameid = client_nameid( id->nameid );
item->type = itemtype( id->nameid );
if( itemPair.second->stockLimited ){
item->amount = itemPair.second->stock;
}else{
item->amount = -1;
}
item->weight = id->weight;
item->index = itemPair.second->index;
item->zeny = itemPair.second->price;
#if PACKETVER_MAIN_NUM >= 20210203 || PACKETVER_RE_NUM >= 20211103
item->viewSprite = id->look;
item->location = pc_equippoint_sub( &sd, id );
#endif
p->packetLength += (int16)( sizeof( *item ) - sizeof( item->currencies ) );
p->items_count++;
item->currency_count = 0;
for( const auto& requirementPair : itemPair.second->requirements ){
// Needs dynamic calculation, because of variable currencies
struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2* req = (struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2*)( ( (uint8*)p ) + p->packetLength );
std::shared_ptr<s_npc_barter_requirement> requirement = requirementPair.second;
req->nameid = requirement->nameid;
if( requirement->refine >= 0 ){
req->refine_level = requirement->refine;
}else{
req->refine_level = 0;
}
req->amount = requirement->amount;
req->type = itemtype( requirement->nameid );
p->packetLength += (int16)( sizeof( *req ) );
item->currency_count++;
}
}
clif_send( p, p->packetLength, &sd.bl, SELF );
#endif
}
void clif_parse_barter_extended_close( int fd, struct map_session_data* sd ){
#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
if( sd->state.barter_extended_open ){
sd->npc_shopid = 0;
sd->state.barter_extended_open = false;
}
#endif
}
void clif_parse_barter_extended_buy( int fd, struct map_session_data* sd ){
#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
// No shop open
if( sd->npc_shopid == 0 || !sd->state.barter_extended_open ){
return;
}
struct npc_data* nd = map_id2nd( sd->npc_shopid );
// Unknown shop
if( nd == nullptr ){
return;
}
// Not a barter
if( nd->subtype != NPCTYPE_BARTER ){
return;
}
// Not an extended barter
if( !nd->u.barter.extended ){
return;
}
std::shared_ptr<s_npc_barter> barter = barter_db.find( nd->exname );
if( barter == nullptr ){
return;
}
struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE* p = (struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE*)RFIFOP( fd, 0 );
uint16 entries = ( p->packetLength - sizeof( struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE ) ) / sizeof( struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE_sub );
// Empty purchase list
if( entries == 0 ){
return;
}
std::vector<s_barter_purchase> purchases;
purchases.reserve( entries );
// Make sure each shop index and target item id is only used once
for( int i = 0; i < entries; i++ ){
std::shared_ptr<s_npc_barter_item> item = util::map_find( barter->items, (uint16)p->list[i].shopIndex );
// Invalid shop index
if( item == nullptr ){
return;
}
for( int j = i + 1; j < entries; j++ ){
// Same shop index
if( p->list[i].shopIndex == p->list[j].shopIndex ){
return;
}
std::shared_ptr<s_npc_barter_item> item2 = util::map_find( barter->items, (uint16)p->list[j].shopIndex );
// Invalid shop index
if( item2 == nullptr ){
return;
}
// Same target item id
if( item->nameid == item2->nameid ){
return;
}
}
s_barter_purchase purchase = {};
purchase.item = item;
purchase.amount = p->list[i].amount;
purchases.push_back( purchase );
}
clif_npc_buy_result( sd, npc_barter_purchase( *sd, barter, purchases ) );
#endif
}
/*========================================== /*==========================================
* Main client packet processing function * Main client packet processing function
*------------------------------------------*/ *------------------------------------------*/

View File

@ -186,6 +186,27 @@ enum e_bossmap_info {
BOSS_INFO_DEAD, BOSS_INFO_DEAD,
}; };
enum class e_purchase_result : uint8{
PURCHASE_SUCCEED = 0x0,
PURCHASE_FAIL_MONEY,
PURCHASE_FAIL_WEIGHT,
PURCHASE_FAIL_COUNT,
PURCHASE_FAIL_STOCK,
PURCHASE_FAIL_ITEM_EXCHANGING,
PURCHASE_FAIL_INVALID_MCSTORE,
PURCHASE_FAIL_OPEN_MCSTORE_ITEMLIST,
PURCHASE_FAIL_GIVE_MONEY,
PURCHASE_FAIL_EACHITEM_COUNT,
// Unknown names
PURCHASE_FAIL_RODEX,
PURCHASE_FAIL_EXCHANGE_FAILED,
PURCHASE_FAIL_EXCHANGE_DONE,
PURCHASE_FAIL_STOCK_EMPTY,
PURCHASE_FAIL_GOODS,
// End unknown names
PURCHASE_FAIL_ADD = 0xff,
};
#define packet_len(cmd) packet_db[cmd].len #define packet_len(cmd) packet_db[cmd].len
extern struct s_packet_db packet_db[MAX_PACKET_DB+1]; extern struct s_packet_db packet_db[MAX_PACKET_DB+1];
extern int packet_db_ack[MAX_ACK_FUNC + 1]; extern int packet_db_ack[MAX_ACK_FUNC + 1];
@ -1161,4 +1182,8 @@ void clif_parse_skill_toid( struct map_session_data* sd, uint16 skill_id, uint16
void clif_inventory_expansion_info( struct map_session_data* sd ); void clif_inventory_expansion_info( struct map_session_data* sd );
// Barter System
void clif_barter_open( struct map_session_data& sd, struct npc_data& nd );
void clif_barter_extended_open( struct map_session_data& sd, struct npc_data& nd );
#endif /* CLIF_HPP */ #endif /* CLIF_HPP */

View File

@ -2407,6 +2407,11 @@
parseable_packet( HEADER_CZ_INVENTORY_EXPAND_REJECTED, sizeof( struct PACKET_CZ_INVENTORY_EXPAND_REJECTED ), clif_parse_inventory_expansion_reject, 0 ); parseable_packet( HEADER_CZ_INVENTORY_EXPAND_REJECTED, sizeof( struct PACKET_CZ_INVENTORY_EXPAND_REJECTED ), clif_parse_inventory_expansion_reject, 0 );
#endif #endif
#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
parseable_packet( HEADER_CZ_NPC_BARTER_PURCHASE, -1, clif_parse_barter_buy, 0 );
parseable_packet( HEADER_CZ_NPC_BARTER_CLOSE, sizeof( struct PACKET_CZ_NPC_BARTER_CLOSE ), clif_parse_barter_close, 0 );
#endif
#if PACKETVER_MAIN_NUM >= 20190227 || PACKETVER_RE_NUM >= 20190220 || PACKETVER_ZERO_NUM >= 20190220 #if PACKETVER_MAIN_NUM >= 20190227 || PACKETVER_RE_NUM >= 20190220 || PACKETVER_ZERO_NUM >= 20190220
parseable_packet( 0x0B1C, sizeof( struct PACKET_CZ_PING ), clif_parse_dull, 0 ); parseable_packet( 0x0B1C, sizeof( struct PACKET_CZ_PING ), clif_parse_dull, 0 );
#endif #endif
@ -2429,6 +2434,11 @@
parseable_packet( 0x0b4c, 2, clif_parse_dull, 0 ); parseable_packet( 0x0b4c, 2, clif_parse_dull, 0 );
#endif #endif
#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
parseable_packet( HEADER_CZ_NPC_EXPANDED_BARTER_PURCHASE, -1, clif_parse_barter_extended_buy, 0 );
parseable_packet( HEADER_CZ_NPC_EXPANDED_BARTER_CLOSE, sizeof( struct PACKET_CZ_NPC_EXPANDED_BARTER_CLOSE ), clif_parse_barter_extended_close, 0 );
#endif
#if PACKETVER >= 20191224 #if PACKETVER >= 20191224
parseable_packet( HEADER_CZ_SE_CASHSHOP_OPEN2, sizeof( struct PACKET_CZ_SE_CASHSHOP_OPEN2 ), clif_parse_cashshop_open_request, 0 ); parseable_packet( HEADER_CZ_SE_CASHSHOP_OPEN2, sizeof( struct PACKET_CZ_SE_CASHSHOP_OPEN2 ), clif_parse_cashshop_open_request, 0 );
parseable_packet( HEADER_CZ_REQ_ITEMREPAIR2, sizeof( struct PACKET_CZ_REQ_ITEMREPAIR2 ), clif_parse_RepairItem, 0 ); parseable_packet( HEADER_CZ_REQ_ITEMREPAIR2, sizeof( struct PACKET_CZ_REQ_ITEMREPAIR2 ), clif_parse_RepairItem, 0 );

View File

@ -85,6 +85,7 @@ static char log_picktype2char(e_log_pick_type type)
case LOG_TYPE_MERGE_ITEM: return 'Z'; // Merged Item case LOG_TYPE_MERGE_ITEM: return 'Z'; // Merged Item
case LOG_TYPE_QUEST: return 'Q'; // (Q)uest Item case LOG_TYPE_QUEST: return 'Q'; // (Q)uest Item
case LOG_TYPE_PRIVATE_AIRSHIP: return 'H'; // Private Airs(H)ip case LOG_TYPE_PRIVATE_AIRSHIP: return 'H'; // Private Airs(H)ip
case LOG_TYPE_BARTER: return 'J'; // Barter Shop
} }
// should not get here, fallback // should not get here, fallback

View File

@ -26,35 +26,36 @@ enum e_log_chat_type : uint8
enum e_log_pick_type : uint32 enum e_log_pick_type : uint32
{ {
LOG_TYPE_NONE = 0, LOG_TYPE_NONE = 0x0000000,
LOG_TYPE_TRADE = 0x000001, LOG_TYPE_TRADE = 0x0000001,
LOG_TYPE_VENDING = 0x000002, LOG_TYPE_VENDING = 0x0000002,
LOG_TYPE_PICKDROP_PLAYER = 0x000004, LOG_TYPE_PICKDROP_PLAYER = 0x0000004,
LOG_TYPE_PICKDROP_MONSTER = 0x000008, LOG_TYPE_PICKDROP_MONSTER = 0x0000008,
LOG_TYPE_NPC = 0x000010, LOG_TYPE_NPC = 0x0000010,
LOG_TYPE_SCRIPT = 0x000020, LOG_TYPE_SCRIPT = 0x0000020,
LOG_TYPE_STEAL = 0x000040, LOG_TYPE_STEAL = 0x0000040,
LOG_TYPE_CONSUME = 0x000080, LOG_TYPE_CONSUME = 0x0000080,
LOG_TYPE_PRODUCE = 0x000100, LOG_TYPE_PRODUCE = 0x0000100,
LOG_TYPE_MVP = 0x000200, LOG_TYPE_MVP = 0x0000200,
LOG_TYPE_COMMAND = 0x000400, LOG_TYPE_COMMAND = 0x0000400,
LOG_TYPE_STORAGE = 0x000800, LOG_TYPE_STORAGE = 0x0000800,
LOG_TYPE_GSTORAGE = 0x001000, LOG_TYPE_GSTORAGE = 0x0001000,
LOG_TYPE_MAIL = 0x002000, LOG_TYPE_MAIL = 0x0002000,
LOG_TYPE_AUCTION = 0x004000, LOG_TYPE_AUCTION = 0x0004000,
LOG_TYPE_BUYING_STORE = 0x008000, LOG_TYPE_BUYING_STORE = 0x0008000,
LOG_TYPE_OTHER = 0x010000, LOG_TYPE_OTHER = 0x0010000,
LOG_TYPE_CASH = 0x020000, LOG_TYPE_CASH = 0x0020000,
LOG_TYPE_BANK = 0x040000, LOG_TYPE_BANK = 0x0040000,
LOG_TYPE_BOUND_REMOVAL = 0x080000, LOG_TYPE_BOUND_REMOVAL = 0x0080000,
LOG_TYPE_ROULETTE = 0x100000, LOG_TYPE_ROULETTE = 0x0100000,
LOG_TYPE_MERGE_ITEM = 0x200000, LOG_TYPE_MERGE_ITEM = 0x0200000,
LOG_TYPE_QUEST = 0x400000, LOG_TYPE_QUEST = 0x0400000,
LOG_TYPE_PRIVATE_AIRSHIP = 0x800000, LOG_TYPE_PRIVATE_AIRSHIP = 0x0800000,
LOG_TYPE_BARTER = 0x1000000,
// combinations // combinations
LOG_TYPE_LOOT = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME, LOG_TYPE_LOOT = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
// all // all
LOG_TYPE_ALL = 0xFFFFFF, LOG_TYPE_ALL = 0xFFFFFFF,
}; };
enum e_log_cash_type : uint8 enum e_log_cash_type : uint8

View File

@ -63,6 +63,7 @@ Sql* mmysql_handle;
Sql* qsmysql_handle; /// For query_sql Sql* qsmysql_handle; /// For query_sql
int db_use_sqldbs = 0; int db_use_sqldbs = 0;
char barter_table[32] = "barter";
char buyingstores_table[32] = "buyingstores"; char buyingstores_table[32] = "buyingstores";
char buyingstore_items_table[32] = "buyingstore_items"; char buyingstore_items_table[32] = "buyingstore_items";
char item_cash_table[32] = "item_cash_db"; char item_cash_table[32] = "item_cash_db";
@ -4186,7 +4187,9 @@ int inter_config_read(const char *cfgName)
} }
#undef RENEWALPREFIX #undef RENEWALPREFIX
if( strcmpi( w1, "buyingstore_db" ) == 0 ) if( strcmpi( w1, "barter_table" ) == 0 )
safestrncpy( barter_table, w2, sizeof(barter_table) );
else if( strcmpi( w1, "buyingstore_db" ) == 0 )
safestrncpy( buyingstores_table, w2, sizeof(buyingstores_table) ); safestrncpy( buyingstores_table, w2, sizeof(buyingstores_table) );
else if( strcmpi( w1, "buyingstore_items_table" ) == 0 ) else if( strcmpi( w1, "buyingstore_items_table" ) == 0 )
safestrncpy( buyingstore_items_table, w2, sizeof(buyingstore_items_table) ); safestrncpy( buyingstore_items_table, w2, sizeof(buyingstore_items_table) );

View File

@ -295,6 +295,7 @@ enum npc_subtype : uint8{
NPCTYPE_POINTSHOP, /// Pointshop NPCTYPE_POINTSHOP, /// Pointshop
NPCTYPE_TOMB, /// Monster tomb NPCTYPE_TOMB, /// Monster tomb
NPCTYPE_MARKETSHOP, /// Marketshop NPCTYPE_MARKETSHOP, /// Marketshop
NPCTYPE_BARTER, /// Barter
}; };
enum e_race : int8{ enum e_race : int8{
@ -1220,6 +1221,7 @@ extern Sql* mmysql_handle;
extern Sql* qsmysql_handle; extern Sql* qsmysql_handle;
extern Sql* logmysql_handle; extern Sql* logmysql_handle;
extern char barter_table[32];
extern char buyingstores_table[32]; extern char buyingstores_table[32];
extern char buyingstore_items_table[32]; extern char buyingstore_items_table[32];
extern char item_table[32]; extern char item_table[32];

View File

@ -121,6 +121,10 @@ struct script_event_s{
// Holds pointers to the commonly executed scripts for speedup. [Skotlex] // Holds pointers to the commonly executed scripts for speedup. [Skotlex]
std::map<enum npce_event, std::vector<struct script_event_s>> script_event; std::map<enum npce_event, std::vector<struct script_event_s>> script_event;
// Static functions
static struct npc_data* npc_create_npc( int16 m, int16 x, int16 y );
static void npc_parsename( struct npc_data* nd, const char* name, const char* start, const char* buffer, const char* filepath );
const std::string StylistDatabase::getDefaultLocation(){ const std::string StylistDatabase::getDefaultLocation(){
return std::string(db_path) + "/stylist.yml"; return std::string(db_path) + "/stylist.yml";
} }
@ -386,6 +390,451 @@ uint64 StylistDatabase::parseBodyNode( const YAML::Node &node ){
StylistDatabase stylist_db; StylistDatabase stylist_db;
const std::string BarterDatabase::getDefaultLocation(){
return "npc/barters.yml";
}
uint64 BarterDatabase::parseBodyNode( const YAML::Node& node ){
std::string npcname;
if( !this->asString( node, "Name", npcname ) ){
return 0;
}
std::shared_ptr<s_npc_barter> barter = this->find( npcname );
bool exists = barter != nullptr;
if( !exists ){
barter = std::make_shared<s_npc_barter>();
barter->name = npcname;
}
if( this->nodeExists( node, "Map" ) ){
std::string map;
if( !this->asString( node, "Map", map ) ){
return 0;
}
uint16 index = mapindex_name2idx( map.c_str(), nullptr );
if( index == 0 ){
this->invalidWarning( node["Map"], "barter_parseBodyNode: Unknown mapname %s, skipping.\n", map.c_str());
return 0;
}
barter->m = map_mapindex2mapid( index );
// Skip silently if the map is not on this map-server
if( barter->m < 0 ){
return 1;
}
}else{
if( !exists ){
barter->m = -1;
}
}
struct map_data* mapdata = nullptr;
if( barter->m >= 0 ){
mapdata = map_getmapdata( barter->m );
}
if( this->nodeExists( node, "X" ) ){
uint16 x;
if( !this->asUInt16( node, "X", x ) ){
return 0;
}
if( mapdata == nullptr ){
this->invalidWarning( node["X"], "barter_parseBodyNode: Barter NPC is not on a map. Ignoring X coordinate...\n" );
x = 0;
}else if( x >= mapdata->xs ){
this->invalidWarning( node["X"], "barter_parseBodyNode: X coordinate %hu is out of bound %hu...\n", x, mapdata->xs );
return 0;
}
barter->x = x;
}else{
if( !exists ){
barter->x = 0;
}
}
if( this->nodeExists( node, "Y" ) ){
uint16 y;
if( !this->asUInt16( node, "Y", y ) ){
return 0;
}
if( mapdata == nullptr ){
this->invalidWarning( node["Y"], "barter_parseBodyNode: Barter NPC is not on a map. Ignoring Y coordinate...\n" );
y = 0;
}else if( y >= mapdata->ys ){
this->invalidWarning( node["Y"], "barter_parseBodyNode: Y coordinate %hu is out of bound %hu...\n", y, mapdata->ys );
return 0;
}
barter->y = y;
}else{
if( !exists ){
barter->y = 0;
}
}
if( this->nodeExists( node, "Direction" ) ){
std::string direction_name;
if( !this->asString( node, "Direction", direction_name ) ){
return 0;
}
int64 constant;
if( !script_get_constant( ( "DIR_" + direction_name ).c_str(), &constant ) ){
this->invalidWarning( node["Direction"], "barter_parseBodyNode: Unknown direction %s, skipping.\n", direction_name.c_str() );
return 0;
}
if( constant < DIR_NORTH || constant >= DIR_MAX ){
this->invalidWarning( node["Direction"], "barter_parseBodyNode: Invalid direction %s, defaulting to North.\n", direction_name.c_str() );
constant = DIR_NORTH;
}
barter->dir = (uint8)constant;
}else{
if( !exists ){
barter->dir = (uint8)DIR_NORTH;
}
}
if( this->nodeExists( node, "Sprite" ) ){
std::string sprite_name;
if( !this->asString( node, "Sprite", sprite_name ) ){
return 0;
}
int64 constant;
if( !script_get_constant( sprite_name.c_str(), &constant ) ){
this->invalidWarning( node["Sprite"], "barter_parseBodyNode: Unknown sprite name %s, skipping.\n", sprite_name.c_str());
return 0;
}
if( constant != JT_FAKENPC && !npcdb_checkid( constant ) ){
this->invalidWarning( node["Sprite"], "barter_parseBodyNode: Invalid sprite name %s, skipping.\n", sprite_name.c_str());
return 0;
}
barter->sprite = (int16)constant;
}else{
if( !exists ){
barter->sprite = JT_FAKENPC;
}
}
if( this->nodeExists( node, "Items" ) ){
for( const YAML::Node& itemNode : node["Items"] ){
uint16 index;
if( !this->asUInt16( itemNode, "Index", index ) ){
return 0;
}
std::shared_ptr<s_npc_barter_item> item = util::map_find( barter->items, index );
bool item_exists = item != nullptr;
if( !item_exists ){
if( !this->nodesExist( itemNode, { "Item" } ) ){
return 0;
}
item = std::make_shared<s_npc_barter_item>();
item->index = index;
}
if( this->nodeExists( itemNode, "Item" ) ){
std::string aegis_name;
if( !this->asString( itemNode, "Item", aegis_name ) ){
return 0;
}
std::shared_ptr<item_data> id = item_db.search_aegisname( aegis_name.c_str() );
if( id == nullptr ){
this->invalidWarning( itemNode["Item"], "barter_parseBodyNode: Unknown item %s.\n", aegis_name.c_str() );
return 0;
}
item->nameid = id->nameid;
}
if( this->nodeExists( itemNode, "Stock" ) ){
uint32 stock;
if( !this->asUInt32( itemNode, "Stock", stock ) ){
return 0;
}
item->stock = stock;
item->stockLimited = ( stock > 0 );
}else{
if( !item_exists ){
item->stock = 0;
item->stockLimited = false;
}
}
if( this->nodeExists( itemNode, "Zeny" ) ){
uint32 zeny;
if( !this->asUInt32( itemNode, "Zeny", zeny ) ){
return 0;
}
if( zeny > MAX_ZENY ){
this->invalidWarning( itemNode["Zeny"], "barter_parseBodyNode: Zeny price %u is above MAX_ZENY (%u), capping...\n", zeny, MAX_ZENY );
zeny = MAX_ZENY;
}
item->price = zeny;
}else{
if( !item_exists ){
item->price = 0;
}
}
if( this->nodeExists( itemNode, "RequiredItems" ) ){
for( const YAML::Node& requiredItemNode : itemNode["RequiredItems"] ){
uint16 requirement_index;
if( !this->asUInt16( requiredItemNode, "Index", requirement_index ) ){
return 0;
}
if( requirement_index >= MAX_BARTER_REQUIREMENTS ){
this->invalidWarning( requiredItemNode["Index"], "barter_parseBodyNode: Index %hu is out of bounds. Barters support up to %d requirements.\n", requirement_index, MAX_BARTER_REQUIREMENTS );
return 0;
}
std::shared_ptr<s_npc_barter_requirement> requirement = util::map_find( item->requirements, requirement_index );
bool requirement_exists = requirement != nullptr;
if( !requirement_exists ){
if( !this->nodesExist( requiredItemNode, { "Item" } ) ){
return 0;
}
requirement = std::make_shared<s_npc_barter_requirement>();
requirement->index = requirement_index;
}
if( this->nodeExists( requiredItemNode, "Item" ) ){
std::string aegis_name;
if( !this->asString( requiredItemNode, "Item", aegis_name ) ){
return 0;
}
std::shared_ptr<item_data> data = item_db.search_aegisname( aegis_name.c_str() );
if( data == nullptr ){
this->invalidWarning( requiredItemNode["Item"], "barter_parseBodyNode: Unknown required item %s.\n", aegis_name.c_str() );
return 0;
}
requirement->nameid = data->nameid;
}
if( this->nodeExists( requiredItemNode, "Amount" ) ){
uint16 amount;
if( !this->asUInt16( requiredItemNode, "Amount", amount ) ){
return 0;
}
if( amount > MAX_AMOUNT ){
this->invalidWarning( requiredItemNode["Amount"], "barter_parseBodyNode: Amount %hu is too high, capping to %hu...\n", amount, MAX_AMOUNT );
amount = MAX_AMOUNT;
}
requirement->amount = amount;
}else{
if( !requirement_exists ){
requirement->amount = 1;
}
}
if( this->nodeExists( requiredItemNode, "Refine" ) ){
std::shared_ptr<item_data> data = item_db.find( requirement->nameid );
if( data->flag.no_refine ){
this->invalidWarning( requiredItemNode["Refine"], "barter_parseBodyNode: Item %s is not refineable.\n", data->name.c_str() );
return 0;
}
int16 refine;
if( !this->asInt16( requiredItemNode, "Refine", refine ) ){
return 0;
}
if( refine > MAX_REFINE ){
this->invalidWarning( requiredItemNode["Amount"], "barter_parseBodyNode: Refine %hd is too high, capping to %d.\n", refine, MAX_REFINE );
refine = MAX_REFINE;
}
requirement->refine = (int8)refine;
}else{
if( !requirement_exists ){
requirement->refine = -1;
}
}
if( !requirement_exists ){
item->requirements[requirement->index] = requirement;
}
}
}
if( !item_exists ){
barter->items[index] = item;
}
}
}
if( !exists ){
this->put( npcname, barter );
}
return 1;
}
void BarterDatabase::loadingFinished(){
for( const auto& pair : *this ){
#if !( PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114 )
ShowError( "Barter system is not supported by your packet version.\n" );
return;
#endif
std::shared_ptr<s_npc_barter> barter = pair.second;
struct npc_data* nd = npc_create_npc( barter->m, barter->x, barter->y );
npc_parsename( nd, barter->name.c_str(), nullptr, nullptr, __FILE__ ":" QUOTE(__LINE__) );
nd->class_ = barter->sprite;
nd->speed = 200;
nd->bl.type = BL_NPC;
nd->subtype = NPCTYPE_BARTER;
nd->u.barter.extended = false;
// Check if it has to use the extended barter feature or not
for( const auto& itemPair : barter->items ){
// Normal barter cannot have zeny requirements
if( itemPair.second->price > 0 ){
nd->u.barter.extended = true;
break;
}
// Normal barter needs to have exchange items defined
if( itemPair.second->requirements.empty() ){
nd->u.barter.extended = true;
break;
}
// Normal barter can only exchange 1:1
if( itemPair.second->requirements.size() > 1 ){
nd->u.barter.extended = true;
break;
}
// Normal barter cannot handle refine
for( const auto& requirement : itemPair.second->requirements ){
if( requirement.second->refine >= 0 ){
nd->u.barter.extended = true;
break;
}
}
// Check if a refine requirement has been set in the loop above
if( nd->u.barter.extended ){
break;
}
}
#if !( PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127 )
if( nd->u.barter.extended ){
ShowError( "Barter %s uses extended mechanics but this is not supported by the current packet version.\n", nd->name );
continue;
}
#endif
if( nd->bl.m >= 0 ){
map_addnpc( nd->bl.m, nd );
npc_setcells( nd );
// Couldn't add on map
if( map_addblock( &nd->bl ) ){
continue;
}
status_change_init( &nd->bl );
unit_dataset( &nd->bl );
nd->ud.dir = barter->dir;
if( nd->class_ != JT_FAKENPC ){
status_set_viewdata( &nd->bl, nd->class_ );
if( map_getmapdata( nd->bl.m )->users ){
clif_spawn( &nd->bl );
}
}
}else{
map_addiddb( &nd->bl );
}
strdb_put( npcname_db, nd->exname, nd );
for( const auto& itemPair : barter->items ){
if( itemPair.second->stockLimited ){
if( Sql_Query( mmysql_handle, "SELECT `amount` FROM `%s` WHERE `name` = '%s' AND `index` = '%hu'", barter_table, barter->name.c_str(), itemPair.first ) != SQL_SUCCESS ){
Sql_ShowDebug( mmysql_handle );
continue;
}
// Previous amount found
if( SQL_SUCCESS == Sql_NextRow( mmysql_handle ) ){
char* data;
Sql_GetData( mmysql_handle, 0, &data, nullptr );
itemPair.second->stock = strtoul( data, nullptr, 10 );
}
Sql_FreeResult( mmysql_handle );
// Save or refresh the amount
if( Sql_Query( mmysql_handle, "REPLACE INTO `%s` (`name`,`index`,`amount`) VALUES ( '%s', '%hu', '%hu' )", barter_table, barter->name.c_str(), itemPair.first, itemPair.second->stock ) != SQL_SUCCESS ){
Sql_ShowDebug( mmysql_handle );
}
}else{
if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `name` = '%s' AND `index` = '%hu'", barter_table, barter->name.c_str(), itemPair.first ) != SQL_SUCCESS ){
Sql_ShowDebug( mmysql_handle );
}
}
}
}
}
BarterDatabase barter_db;
/** /**
* Returns the viewdata for normal NPC classes. * Returns the viewdata for normal NPC classes.
* @param class_: NPC class ID * @param class_: NPC class ID
@ -1735,6 +2184,14 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd)
case NPCTYPE_TOMB: case NPCTYPE_TOMB:
run_tomb(sd,nd); run_tomb(sd,nd);
break; break;
case NPCTYPE_BARTER:
sd->npc_shopid = nd->bl.id;
if( nd->u.barter.extended ){
clif_barter_extended_open( *sd, *nd );
}else{
clif_barter_open( *sd, *nd );
}
break;
} }
return 0; return 0;
@ -2232,23 +2689,23 @@ static int npc_buylist_sub(struct map_session_data* sd, uint16 n, struct s_npc_b
* @param item_list: List of items * @param item_list: List of items
* @return result code for clif_parse_NpcBuyListSend/clif_npc_market_purchase_ack * @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) { e_purchase_result 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; struct npc_item_list *shop = NULL;
double z; double z;
int i,j,k,w,skill,new_; int i,j,k,w,skill,new_;
uint8 market_index[MAX_INVENTORY]; uint8 market_index[MAX_INVENTORY];
nullpo_retr(3, sd); nullpo_retr(e_purchase_result::PURCHASE_FAIL_COUNT, sd);
nullpo_retr(3, item_list); nullpo_retr(e_purchase_result::PURCHASE_FAIL_COUNT, 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 e_purchase_result::PURCHASE_FAIL_COUNT;
if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_MARKETSHOP ) if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_MARKETSHOP )
return 3; return e_purchase_result::PURCHASE_FAIL_COUNT;
if (!item_list || !n) if (!item_list || !n)
return 3; return e_purchase_result::PURCHASE_FAIL_COUNT;
z = 0; z = 0;
w = 0; w = 0;
@ -2271,12 +2728,12 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
); );
if( j == nd->u.shop.count ) if( j == nd->u.shop.count )
return 3; // no such item in shop return e_purchase_result::PURCHASE_FAIL_COUNT; // no such item in shop
#if PACKETVER >= 20131223 #if PACKETVER >= 20131223
if (nd->subtype == NPCTYPE_MARKETSHOP) { if (nd->subtype == NPCTYPE_MARKETSHOP) {
if (item_list[i].qty > shop[j].qty) if (item_list[i].qty > shop[j].qty)
return 3; return e_purchase_result::PURCHASE_FAIL_COUNT;
market_index[i] = j; market_index[i] = j;
} }
#endif #endif
@ -2287,7 +2744,7 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
id = itemdb_exists(nameid); id = itemdb_exists(nameid);
if( !id ) if( !id )
return 3; // item no longer in itemdb return e_purchase_result::PURCHASE_FAIL_COUNT; // item no longer in itemdb
if( !itemdb_isstackable2(id) && amount > 1 ) { //Exploit? You can't buy more than 1 of equipment types o.O if( !itemdb_isstackable2(id) && 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 %u!\n", ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %u!\n",
@ -2308,7 +2765,7 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
break; break;
case CHKADDITEM_OVERAMOUNT: case CHKADDITEM_OVERAMOUNT:
return 2; return e_purchase_result::PURCHASE_FAIL_WEIGHT;
} }
if (npc_shop_discount(nd)) if (npc_shop_discount(nd))
@ -2318,16 +2775,18 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
w += itemdb_weight(nameid) * amount; w += itemdb_weight(nameid) * amount;
} }
if (nd->master_nd) //Script-based shops. if (nd->master_nd){ //Script-based shops.
return npc_buylist_sub(sd,n,item_list,nd->master_nd); npc_buylist_sub(sd,n,item_list,nd->master_nd);
return e_purchase_result::PURCHASE_SUCCEED;
}
if (z > (double)sd->status.zeny) if (z > (double)sd->status.zeny)
return 1; // Not enough Zeny return e_purchase_result::PURCHASE_FAIL_MONEY; // Not enough Zeny
if( w + sd->weight > sd->max_weight ) if( w + sd->weight > sd->max_weight )
return 2; // Too heavy return e_purchase_result::PURCHASE_FAIL_WEIGHT; // Too heavy
if( pc_inventoryblank(sd) < new_ ) if( pc_inventoryblank(sd) < new_ )
return 3; // Not enough space to store items return e_purchase_result::PURCHASE_FAIL_COUNT; // Not enough space to store items
pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL); pc_payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
@ -2339,7 +2798,7 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
if (nd->subtype == NPCTYPE_MARKETSHOP) { if (nd->subtype == NPCTYPE_MARKETSHOP) {
j = market_index[i]; j = market_index[i];
if (amount > shop[j].qty) if (amount > shop[j].qty)
return 1; return e_purchase_result::PURCHASE_FAIL_MONEY;
shop[j].qty -= amount; shop[j].qty -= amount;
npc_market_tosql(nd->exname, &shop[j]); npc_market_tosql(nd->exname, &shop[j]);
} }
@ -2378,7 +2837,7 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
} }
} }
return 0; return e_purchase_result::PURCHASE_SUCCEED;
} }
/// npc_selllist for script-controlled shops /// npc_selllist for script-controlled shops
@ -2573,6 +3032,268 @@ uint8 npc_selllist(struct map_session_data* sd, int n, unsigned short *item_list
return 0; return 0;
} }
e_purchase_result npc_barter_purchase( struct map_session_data& sd, std::shared_ptr<s_npc_barter> barter, std::vector<s_barter_purchase>& purchases ){
uint64 requiredZeny = 0;
uint32 requiredWeight = 0;
uint32 reducedWeight = 0;
uint16 requiredSlots = 0;
uint32 requiredItems[MAX_INVENTORY] = { 0 };
for( s_barter_purchase& purchase : purchases ){
purchase.data = itemdb_exists( purchase.item->nameid );
if( purchase.data == nullptr ){
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
uint32 amount = purchase.amount;
if( purchase.item->stockLimited && purchase.item->stock < amount ){
return e_purchase_result::PURCHASE_FAIL_STOCK_EMPTY;
}
char result = pc_checkadditem( &sd, purchase.item->nameid, amount );
if( result == CHKADDITEM_OVERAMOUNT ){
return e_purchase_result::PURCHASE_FAIL_COUNT;
}else if( result == CHKADDITEM_NEW ){
requiredSlots += purchase.data->inventorySlotNeeded( amount );
}
requiredZeny += ( purchase.item->price * amount );
requiredWeight += ( purchase.data->weight * amount );
for( const auto& requirementPair : purchase.item->requirements ){
std::shared_ptr<s_npc_barter_requirement> requirement = requirementPair.second;
item_data* id = itemdb_exists( requirement->nameid );
if( id == nullptr ){
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
if( itemdb_isstackable2( id ) ){
int j;
for( j = 0; j < MAX_INVENTORY; j++ ){
if( sd.inventory.u.items_inventory[j].nameid == requirement->nameid ){
// Equipped items are not taken into account
if( sd.inventory.u.items_inventory[j].equip != 0 ){
continue;
}
// Items in equip switch are not taken into account
if( sd.inventory.u.items_inventory[j].equipSwitch != 0 ){
continue;
}
// Server is configured to hide favorite items on selling
if( battle_config.hide_fav_sell && sd.inventory.u.items_inventory[j].favorite ){
continue;
}
// Actually stackable items should never be refinable, but who knows...
if( requirement->refine >= 0 && sd.inventory.u.items_inventory[j].refine != requirement->refine ){
// Refine does not match, continue with next item
continue;
}
// Found a match, accumulate required amount
requiredItems[j] += requirement->amount * amount;
// Check if there are still enough items available
if( requiredItems[j] > sd.inventory.u.items_inventory[j].amount ){
return e_purchase_result::PURCHASE_FAIL_GOODS;
}
// Cancel the loop
break;
}
}
// Required item not found
if( j == MAX_INVENTORY ){
return e_purchase_result::PURCHASE_FAIL_GOODS;
}
}else{
for( int i = 0; i < requirement->amount; i++ ){
int j;
for( j = 0; j < MAX_INVENTORY; j++ ){
if( sd.inventory.u.items_inventory[j].nameid == requirement->nameid ){
// Equipped items are not taken into account
if( sd.inventory.u.items_inventory[j].equip != 0 ){
continue;
}
// Items in equip switch are not taken into account
if( sd.inventory.u.items_inventory[j].equipSwitch != 0 ){
continue;
}
// Server is configured to hide favorite items on selling
if( battle_config.hide_fav_sell && sd.inventory.u.items_inventory[j].favorite ){
continue;
}
// If necessary, check if the refine rate matches
if( requirement->refine >= 0 && sd.inventory.u.items_inventory[j].refine != requirement->refine ){
// Refine does not match, continue with next item
continue;
}
// Found a match, since it is not stackable, check if it was already taken
if( requiredItems[j] > 0 ){
// Item was already taken, try to find another match
continue;
}
// Mark it as taken
requiredItems[j] = 1;
// Cancel the loop
break;
}
}
// Required item not found
if( j == MAX_INVENTORY ){
// Maybe the refine level did not match
if( requirement->refine >= 0 ){
int refine;
// Try to find a higher refine level, going from the next lowest to the highest possible
for( refine = requirement->refine + 1; refine <= MAX_REFINE; refine++ ){
for( j = 0; j < MAX_INVENTORY; j++ ){
if( sd.inventory.u.items_inventory[j].nameid == requirement->nameid ){
// Equipped items are not taken into account
if( sd.inventory.u.items_inventory[j].equip != 0 ){
continue;
}
// Items in equip switch are not taken into account
if( sd.inventory.u.items_inventory[j].equipSwitch != 0 ){
continue;
}
// Server is configured to hide favorite items on selling
if( battle_config.hide_fav_sell && sd.inventory.u.items_inventory[j].favorite ){
continue;
}
// If necessary, check if the refine rate matches
if( requirement->refine >= 0 && sd.inventory.u.items_inventory[j].refine != refine ){
// Refine does not match, continue with next item
continue;
}
// Found a match, since it is not stackable, check if it was already taken
if( requiredItems[j] > 0 ){
// Item was already taken, try to find another match
continue;
}
// Mark it as taken
requiredItems[j] = 1;
// Cancel the loop
break;
}
}
// If a match was found, make sure to cancel the loop
if( j < MAX_INVENTORY ){
// Cancel the loop
break;
}
}
// No matching entry found
if( refine > MAX_REFINE ){
return e_purchase_result::PURCHASE_FAIL_GOODS;
}
}else{
return e_purchase_result::PURCHASE_FAIL_GOODS;
}
}
}
}
reducedWeight += ( purchase.amount * requirement->amount * id->weight );
}
}
// Check if there is enough Zeny
if( sd.status.zeny < requiredZeny ){
return e_purchase_result::PURCHASE_FAIL_MONEY;
}
// Check if there is enough Weight Limit
if( ( sd.weight + requiredWeight - reducedWeight ) > sd.max_weight ){
return e_purchase_result::PURCHASE_FAIL_WEIGHT;
}
if( pc_inventoryblank( &sd ) < requiredSlots ){
return e_purchase_result::PURCHASE_FAIL_COUNT;
}
for( int i = 0; i < MAX_INVENTORY; i++ ){
if( requiredItems[i] > 0 ){
if( pc_delitem( &sd, i, requiredItems[i], 0, 0, LOG_TYPE_BARTER ) != 0 ){
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
}
}
if( pc_payzeny( &sd, (int)requiredZeny, LOG_TYPE_BARTER, nullptr ) != 0 ){
return e_purchase_result::PURCHASE_FAIL_MONEY;
}
for( s_barter_purchase& purchase : purchases ){
if( purchase.item->stockLimited ){
purchase.item->stock -= purchase.amount;
if( Sql_Query( mmysql_handle, "REPLACE INTO `%s` (`name`,`index`,`amount`) VALUES ( '%s', '%hu', '%hu' )", barter_table, barter->name.c_str(), purchase.item->index, purchase.item->stock ) != SQL_SUCCESS ){
Sql_ShowDebug( mmysql_handle );
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
}
if( itemdb_isstackable2( purchase.data ) ){
struct item it = {};
it.nameid = purchase.item->nameid;
it.identify = true;
if( pc_additem( &sd, &it, purchase.amount, LOG_TYPE_BARTER ) != ADDITEM_SUCCESS ){
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
}else{
if( purchase.data->type == IT_PETEGG ){
for( int i = 0; i < purchase.amount; i++ ){
if( !pet_create_egg( &sd, purchase.item->nameid ) ){
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
}
}else{
for( int i = 0; i < purchase.amount; i++ ){
struct item it = {};
it.nameid = purchase.item->nameid;
it.identify = true;
if( pc_additem( &sd, &it, 1, LOG_TYPE_BARTER ) != ADDITEM_SUCCESS ){
return e_purchase_result::PURCHASE_FAIL_EXCHANGE_FAILED;
}
}
}
}
}
return e_purchase_result::PURCHASE_SUCCEED;
}
//Atempt to remove an npc from a map //Atempt to remove an npc from a map
//This doesn't remove it from map_db //This doesn't remove it from map_db
int npc_remove_map(struct npc_data* nd) int npc_remove_map(struct npc_data* nd)
@ -5051,6 +5772,7 @@ int npc_reload(void) {
npc_id - npc_new_min, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); npc_id - npc_new_min, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob);
stylist_db.reload(); stylist_db.reload();
barter_db.reload();
//Re-read the NPC Script Events cache. //Re-read the NPC Script Events cache.
npc_read_event_script(); npc_read_event_script();
@ -5119,6 +5841,7 @@ void do_final_npc(void) {
NPCMarketDB->destroy(NPCMarketDB, npc_market_free); NPCMarketDB->destroy(NPCMarketDB, npc_market_free);
#endif #endif
stylist_db.clear(); stylist_db.clear();
barter_db.clear();
ers_destroy(timer_event_ers); ers_destroy(timer_event_ers);
ers_destroy(npc_sc_display_ers); ers_destroy(npc_sc_display_ers);
npc_clearsrcfile(); npc_clearsrcfile();
@ -5205,6 +5928,7 @@ void do_init_npc(void){
npc_id - START_NPC_NUM, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); npc_id - START_NPC_NUM, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob);
stylist_db.load(); stylist_db.load();
barter_db.load();
// set up the events cache // set up the events cache
npc_read_event_script(); npc_read_event_script();

View File

@ -4,8 +4,13 @@
#ifndef NPC_HPP #ifndef NPC_HPP
#define NPC_HPP #define NPC_HPP
#include <map>
#include <vector>
#include "../common/database.hpp"
#include "../common/timer.hpp" #include "../common/timer.hpp"
#include "clif.hpp" //
#include "map.hpp" // struct block_list #include "map.hpp" // struct block_list
#include "status.hpp" // struct status_change #include "status.hpp" // struct status_change
#include "unit.hpp" // struct unit_data #include "unit.hpp" // struct unit_data
@ -85,6 +90,51 @@ public:
extern StylistDatabase stylist_db; extern StylistDatabase stylist_db;
struct s_npc_barter_requirement{
uint16 index;
t_itemid nameid;
uint16 amount;
int8 refine;
};
struct s_npc_barter_item{
uint16 index;
t_itemid nameid;
bool stockLimited;
uint32 stock;
uint32 price;
std::map<uint16, std::shared_ptr<s_npc_barter_requirement>> requirements;
};
struct s_npc_barter{
std::string name;
int16 m;
uint16 x;
uint16 y;
uint8 dir;
int16 sprite;
std::map<uint16, std::shared_ptr<s_npc_barter_item>> items;
};
class BarterDatabase : public TypesafeYamlDatabase<std::string, s_npc_barter>{
public:
BarterDatabase() : TypesafeYamlDatabase( "BARTER_DB", 1 ){
}
const std::string getDefaultLocation();
uint64 parseBodyNode( const YAML::Node& node );
void loadingFinished();
};
extern BarterDatabase barter_db;
struct s_barter_purchase{
std::shared_ptr<s_npc_barter_item> item;
uint32 amount;
item_data* data;
};
struct s_questinfo { struct s_questinfo {
e_questinfo_types icon; e_questinfo_types icon;
e_questinfo_markcolor color; e_questinfo_markcolor color;
@ -153,6 +203,9 @@ struct npc_data {
char killer_name[NAME_LENGTH]; char killer_name[NAME_LENGTH];
int spawn_timer; int spawn_timer;
} tomb; } tomb;
struct {
bool extended;
} barter;
} u; } u;
struct sc_display_entry **sc_display; struct sc_display_entry **sc_display;
@ -1414,9 +1467,10 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd);
bool npc_scriptcont(struct map_session_data* sd, int id, bool closing); bool 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);
uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list); e_purchase_result npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list);
static int npc_buylist_sub(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list, struct npc_data* nd); static int npc_buylist_sub(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *item_list, struct npc_data* nd);
uint8 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);
e_purchase_result npc_barter_purchase( struct map_session_data& sd, std::shared_ptr<s_npc_barter> barter, std::vector<s_barter_purchase>& purchases );
void npc_parse_mob2(struct spawn_data* mob); void npc_parse_mob2(struct spawn_data* mob);
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);
int npc_globalmessage(const char* name,const char* mes); int npc_globalmessage(const char* name,const char* mes);

View File

@ -32,6 +32,11 @@
#pragma pack( push, 1 ) #pragma pack( push, 1 )
#endif #endif
struct PACKET_ZC_PC_PURCHASE_RESULT{
int16 packetType;
uint8 result;
} __attribute__((packed));
struct PACKET_CZ_REQ_MAKINGARROW{ struct PACKET_CZ_REQ_MAKINGARROW{
int16 packetType; int16 packetType;
#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114 #if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
@ -235,6 +240,7 @@ struct PACKET_CZ_REQ_STYLE_CLOSE{
DEFINE_PACKET_HEADER(ZC_NOTIFY_CHAT, 0x8d) DEFINE_PACKET_HEADER(ZC_NOTIFY_CHAT, 0x8d)
DEFINE_PACKET_HEADER(ZC_BROADCAST, 0x9a) DEFINE_PACKET_HEADER(ZC_BROADCAST, 0x9a)
DEFINE_PACKET_HEADER(ZC_ITEM_ENTRY, 0x9d) DEFINE_PACKET_HEADER(ZC_ITEM_ENTRY, 0x9d)
DEFINE_PACKET_HEADER(ZC_PC_PURCHASE_RESULT, 0xca)
DEFINE_PACKET_HEADER(ZC_MVP_GETTING_ITEM, 0x10a) DEFINE_PACKET_HEADER(ZC_MVP_GETTING_ITEM, 0x10a)
DEFINE_PACKET_HEADER(ZC_ACK_TOUSESKILL, 0x110) DEFINE_PACKET_HEADER(ZC_ACK_TOUSESKILL, 0x110)
DEFINE_PACKET_HEADER(CZ_REQMAKINGITEM, 0x18e) DEFINE_PACKET_HEADER(CZ_REQMAKINGITEM, 0x18e)

View File

@ -383,6 +383,8 @@ struct map_session_data {
bool cashshop_open; bool cashshop_open;
bool sale_open; bool sale_open;
bool stylist_open; bool stylist_open;
bool barter_open;
bool barter_extended_open;
unsigned int block_action : 10; unsigned int block_action : 10;
bool refineui_open; bool refineui_open;
t_itemid inventory_expansion_confirmation; t_itemid inventory_expansion_confirmation;
@ -1055,7 +1057,8 @@ extern JobDatabase job_db;
static bool pc_cant_act2( struct map_session_data* sd ){ static bool pc_cant_act2( struct map_session_data* sd ){
return sd->state.vending || sd->state.buyingstore || (sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING) return sd->state.vending || sd->state.buyingstore || (sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING)
|| sd->state.trading || sd->state.storage_flag || sd->state.prevend || sd->state.refineui_open || sd->state.trading || sd->state.storage_flag || sd->state.prevend || sd->state.refineui_open
|| sd->state.stylist_open || sd->state.inventory_expansion_confirmation || sd->npc_shopid; || sd->state.stylist_open || sd->state.inventory_expansion_confirmation || sd->npc_shopid
|| sd->state.barter_open || sd->state.barter_extended_open;
} }
// equals pc_cant_act2 and additionally checks for chat rooms and npcs // equals pc_cant_act2 and additionally checks for chat rooms and npcs
static bool pc_cant_act( struct map_session_data* sd ){ static bool pc_cant_act( struct map_session_data* sd ){

View File

@ -17511,7 +17511,7 @@ BUILDIN_FUNC(callshop)
if (script_hasdata(st,3)) if (script_hasdata(st,3))
flag = script_getnum(st,3); flag = script_getnum(st,3);
nd = npc_name2id(shopname); nd = npc_name2id(shopname);
if( !nd || nd->bl.type != BL_NPC || (nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_CASHSHOP && nd->subtype != NPCTYPE_ITEMSHOP && nd->subtype != NPCTYPE_POINTSHOP && nd->subtype != NPCTYPE_MARKETSHOP) ) { 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 && nd->subtype != NPCTYPE_BARTER) ) {
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 SCRIPT_CMD_FAILURE; return SCRIPT_CMD_FAILURE;
@ -17547,7 +17547,16 @@ BUILDIN_FUNC(callshop)
return SCRIPT_CMD_SUCCESS; return SCRIPT_CMD_SUCCESS;
} }
#endif #endif
else else if( nd->subtype == NPCTYPE_BARTER ){
// flag the user as using a valid script call for opening the shop (for floating NPCs)
sd->state.callshop = 1;
if( nd->u.barter.extended ){
clif_barter_extended_open( *sd, *nd );
}else{
clif_barter_open( *sd, *nd );
}
}else
clif_cashshop_show(sd, nd); clif_cashshop_show(sd, nd);
sd->npc_shopid = nd->bl.id; sd->npc_shopid = nd->bl.id;