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:
parent
4ad35d82bd
commit
e40da669ed
@ -127,6 +127,7 @@ clan_table: clan
|
||||
clan_alliance_table: clan_alliance
|
||||
|
||||
// Map Database Tables
|
||||
barter_table: barter
|
||||
buyingstore_table: buyingstores
|
||||
buyingstore_items_table: buyingstore_items
|
||||
item_table: item_db
|
||||
|
@ -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>...}
|
||||
|
||||
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
|
||||
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
|
||||
|
52
npc/barters.yml
Normal file
52
npc/barters.yml
Normal 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
46
npc/custom/barters.yml
Normal 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
|
46
npc/re/merchants/barters.yml
Normal file
46
npc/re/merchants/barters.yml
Normal 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
|
@ -166,12 +166,13 @@ CREATE TABLE IF NOT EXISTS `npclog` (
|
||||
# (Z) Merged Items
|
||||
# (Q)uest
|
||||
# Private Airs(H)ip
|
||||
# Barter Shop (J)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `picklog` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`time` datetime NOT NULL,
|
||||
`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',
|
||||
`amount` int(11) NOT NULL default '1',
|
||||
`refine` tinyint(3) unsigned NOT NULL default '0',
|
||||
@ -215,13 +216,14 @@ CREATE TABLE IF NOT EXISTS `picklog` (
|
||||
# (E)Mail
|
||||
# (B)uying Store
|
||||
# Ban(K) Transactions
|
||||
# Barter Shop (J)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `zenylog` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`time` datetime NOT NULL,
|
||||
`char_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',
|
||||
`map` varchar(11) NOT NULL default '',
|
||||
PRIMARY KEY (`id`),
|
||||
|
@ -90,6 +90,17 @@ CREATE TABLE IF NOT EXISTS `auction` (
|
||||
PRIMARY KEY (`auction_id`)
|
||||
) 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`
|
||||
--
|
||||
|
10
sql-files/upgrades/upgrade_20220121.sql
Normal file
10
sql-files/upgrades/upgrade_20220121.sql
Normal 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;
|
7
sql-files/upgrades/upgrade_20220121_logs.sql
Normal file
7
sql-files/upgrades/upgrade_20220121_logs.sql
Normal 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'
|
||||
;
|
@ -107,6 +107,9 @@ typedef uint32 t_itemid;
|
||||
#define DB_NAME_LEN 256 //max len of dbs
|
||||
#define MAX_CLAN 500
|
||||
#define MAX_CLANALLIANCE 6
|
||||
#ifndef MAX_BARTER_REQUIREMENTS
|
||||
#define MAX_BARTER_REQUIREMENTS 5
|
||||
#endif
|
||||
|
||||
#ifdef RENEWAL
|
||||
#define MAX_WEAPON_LEVEL 5
|
||||
|
336
src/map/clif.cpp
336
src/map/clif.cpp
@ -2312,7 +2312,7 @@ void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd) {
|
||||
|
||||
/// Purchase item from Market shop.
|
||||
/// 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
|
||||
nullpo_retv( sd );
|
||||
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;
|
||||
|
||||
#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
|
||||
p->result = ( res == 0 ? 1 : 0 );
|
||||
p->result = ( res == e_purchase_result::PURCHASE_SUCCEED ? 1 : 0 );
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
aFree( list );
|
||||
@ -12299,14 +12299,13 @@ void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd)
|
||||
/// 12 = "The exchange was well done."
|
||||
/// 13 = "The item is already sold and out of stock."
|
||||
/// 14 = "There is not enough goods to exchange."
|
||||
void clif_npc_buy_result(struct map_session_data* sd, unsigned char result)
|
||||
{
|
||||
int fd = sd->fd;
|
||||
void clif_npc_buy_result( struct map_session_data* sd, e_purchase_result result ){
|
||||
struct PACKET_ZC_PC_PURCHASE_RESULT p = {};
|
||||
|
||||
WFIFOHEAD(fd,packet_len(0xca));
|
||||
WFIFOW(fd,0) = 0xca;
|
||||
WFIFOB(fd,2) = result;
|
||||
WFIFOSET(fd,packet_len(0xca));
|
||||
p.packetType = HEADER_ZC_PC_PURCHASE_RESULT;
|
||||
p.result = (uint8)result;
|
||||
|
||||
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 );
|
||||
|
||||
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 )
|
||||
result = 1;
|
||||
result = e_purchase_result::PURCHASE_FAIL_MONEY;
|
||||
else
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
@ -22704,6 +22703,315 @@ void clif_parse_inventory_expansion_reject( int fd, struct map_session_data* sd
|
||||
#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
|
||||
*------------------------------------------*/
|
||||
|
@ -186,6 +186,27 @@ enum e_bossmap_info {
|
||||
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
|
||||
extern struct s_packet_db packet_db[MAX_PACKET_DB+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 );
|
||||
|
||||
// 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 */
|
||||
|
@ -2407,6 +2407,11 @@
|
||||
parseable_packet( HEADER_CZ_INVENTORY_EXPAND_REJECTED, sizeof( struct PACKET_CZ_INVENTORY_EXPAND_REJECTED ), clif_parse_inventory_expansion_reject, 0 );
|
||||
#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
|
||||
parseable_packet( 0x0B1C, sizeof( struct PACKET_CZ_PING ), clif_parse_dull, 0 );
|
||||
#endif
|
||||
@ -2429,6 +2434,11 @@
|
||||
parseable_packet( 0x0b4c, 2, clif_parse_dull, 0 );
|
||||
#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
|
||||
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 );
|
||||
|
@ -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_QUEST: return 'Q'; // (Q)uest Item
|
||||
case LOG_TYPE_PRIVATE_AIRSHIP: return 'H'; // Private Airs(H)ip
|
||||
case LOG_TYPE_BARTER: return 'J'; // Barter Shop
|
||||
}
|
||||
|
||||
// should not get here, fallback
|
||||
|
@ -26,35 +26,36 @@ enum e_log_chat_type : uint8
|
||||
|
||||
enum e_log_pick_type : uint32
|
||||
{
|
||||
LOG_TYPE_NONE = 0,
|
||||
LOG_TYPE_TRADE = 0x000001,
|
||||
LOG_TYPE_VENDING = 0x000002,
|
||||
LOG_TYPE_PICKDROP_PLAYER = 0x000004,
|
||||
LOG_TYPE_PICKDROP_MONSTER = 0x000008,
|
||||
LOG_TYPE_NPC = 0x000010,
|
||||
LOG_TYPE_SCRIPT = 0x000020,
|
||||
LOG_TYPE_STEAL = 0x000040,
|
||||
LOG_TYPE_CONSUME = 0x000080,
|
||||
LOG_TYPE_PRODUCE = 0x000100,
|
||||
LOG_TYPE_MVP = 0x000200,
|
||||
LOG_TYPE_COMMAND = 0x000400,
|
||||
LOG_TYPE_STORAGE = 0x000800,
|
||||
LOG_TYPE_GSTORAGE = 0x001000,
|
||||
LOG_TYPE_MAIL = 0x002000,
|
||||
LOG_TYPE_AUCTION = 0x004000,
|
||||
LOG_TYPE_BUYING_STORE = 0x008000,
|
||||
LOG_TYPE_OTHER = 0x010000,
|
||||
LOG_TYPE_CASH = 0x020000,
|
||||
LOG_TYPE_BANK = 0x040000,
|
||||
LOG_TYPE_BOUND_REMOVAL = 0x080000,
|
||||
LOG_TYPE_ROULETTE = 0x100000,
|
||||
LOG_TYPE_MERGE_ITEM = 0x200000,
|
||||
LOG_TYPE_QUEST = 0x400000,
|
||||
LOG_TYPE_PRIVATE_AIRSHIP = 0x800000,
|
||||
LOG_TYPE_NONE = 0x0000000,
|
||||
LOG_TYPE_TRADE = 0x0000001,
|
||||
LOG_TYPE_VENDING = 0x0000002,
|
||||
LOG_TYPE_PICKDROP_PLAYER = 0x0000004,
|
||||
LOG_TYPE_PICKDROP_MONSTER = 0x0000008,
|
||||
LOG_TYPE_NPC = 0x0000010,
|
||||
LOG_TYPE_SCRIPT = 0x0000020,
|
||||
LOG_TYPE_STEAL = 0x0000040,
|
||||
LOG_TYPE_CONSUME = 0x0000080,
|
||||
LOG_TYPE_PRODUCE = 0x0000100,
|
||||
LOG_TYPE_MVP = 0x0000200,
|
||||
LOG_TYPE_COMMAND = 0x0000400,
|
||||
LOG_TYPE_STORAGE = 0x0000800,
|
||||
LOG_TYPE_GSTORAGE = 0x0001000,
|
||||
LOG_TYPE_MAIL = 0x0002000,
|
||||
LOG_TYPE_AUCTION = 0x0004000,
|
||||
LOG_TYPE_BUYING_STORE = 0x0008000,
|
||||
LOG_TYPE_OTHER = 0x0010000,
|
||||
LOG_TYPE_CASH = 0x0020000,
|
||||
LOG_TYPE_BANK = 0x0040000,
|
||||
LOG_TYPE_BOUND_REMOVAL = 0x0080000,
|
||||
LOG_TYPE_ROULETTE = 0x0100000,
|
||||
LOG_TYPE_MERGE_ITEM = 0x0200000,
|
||||
LOG_TYPE_QUEST = 0x0400000,
|
||||
LOG_TYPE_PRIVATE_AIRSHIP = 0x0800000,
|
||||
LOG_TYPE_BARTER = 0x1000000,
|
||||
// combinations
|
||||
LOG_TYPE_LOOT = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
|
||||
// all
|
||||
LOG_TYPE_ALL = 0xFFFFFF,
|
||||
LOG_TYPE_ALL = 0xFFFFFFF,
|
||||
};
|
||||
|
||||
enum e_log_cash_type : uint8
|
||||
|
@ -63,6 +63,7 @@ Sql* mmysql_handle;
|
||||
Sql* qsmysql_handle; /// For query_sql
|
||||
|
||||
int db_use_sqldbs = 0;
|
||||
char barter_table[32] = "barter";
|
||||
char buyingstores_table[32] = "buyingstores";
|
||||
char buyingstore_items_table[32] = "buyingstore_items";
|
||||
char item_cash_table[32] = "item_cash_db";
|
||||
@ -4186,7 +4187,9 @@ int inter_config_read(const char *cfgName)
|
||||
}
|
||||
#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) );
|
||||
else if( strcmpi( w1, "buyingstore_items_table" ) == 0 )
|
||||
safestrncpy( buyingstore_items_table, w2, sizeof(buyingstore_items_table) );
|
||||
|
@ -295,6 +295,7 @@ enum npc_subtype : uint8{
|
||||
NPCTYPE_POINTSHOP, /// Pointshop
|
||||
NPCTYPE_TOMB, /// Monster tomb
|
||||
NPCTYPE_MARKETSHOP, /// Marketshop
|
||||
NPCTYPE_BARTER, /// Barter
|
||||
};
|
||||
|
||||
enum e_race : int8{
|
||||
@ -1220,6 +1221,7 @@ extern Sql* mmysql_handle;
|
||||
extern Sql* qsmysql_handle;
|
||||
extern Sql* logmysql_handle;
|
||||
|
||||
extern char barter_table[32];
|
||||
extern char buyingstores_table[32];
|
||||
extern char buyingstore_items_table[32];
|
||||
extern char item_table[32];
|
||||
|
758
src/map/npc.cpp
758
src/map/npc.cpp
@ -121,6 +121,10 @@ struct script_event_s{
|
||||
// Holds pointers to the commonly executed scripts for speedup. [Skotlex]
|
||||
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(){
|
||||
return std::string(db_path) + "/stylist.yml";
|
||||
}
|
||||
@ -386,6 +390,451 @@ uint64 StylistDatabase::parseBodyNode( const YAML::Node &node ){
|
||||
|
||||
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.
|
||||
* @param class_: NPC class ID
|
||||
@ -1735,6 +2184,14 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd)
|
||||
case NPCTYPE_TOMB:
|
||||
run_tomb(sd,nd);
|
||||
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;
|
||||
@ -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
|
||||
* @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_item_list *shop = NULL;
|
||||
double z;
|
||||
int i,j,k,w,skill,new_;
|
||||
uint8 market_index[MAX_INVENTORY];
|
||||
|
||||
nullpo_retr(3, sd);
|
||||
nullpo_retr(3, item_list);
|
||||
nullpo_retr(e_purchase_result::PURCHASE_FAIL_COUNT, sd);
|
||||
nullpo_retr(e_purchase_result::PURCHASE_FAIL_COUNT, item_list);
|
||||
|
||||
nd = npc_checknear(sd,map_id2bl(sd->npc_shopid));
|
||||
if( nd == NULL )
|
||||
return 3;
|
||||
return e_purchase_result::PURCHASE_FAIL_COUNT;
|
||||
if( nd->subtype != NPCTYPE_SHOP && nd->subtype != NPCTYPE_MARKETSHOP )
|
||||
return 3;
|
||||
return e_purchase_result::PURCHASE_FAIL_COUNT;
|
||||
if (!item_list || !n)
|
||||
return 3;
|
||||
return e_purchase_result::PURCHASE_FAIL_COUNT;
|
||||
|
||||
z = 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 )
|
||||
return 3; // no such item in shop
|
||||
return e_purchase_result::PURCHASE_FAIL_COUNT; // no such item in shop
|
||||
|
||||
#if PACKETVER >= 20131223
|
||||
if (nd->subtype == NPCTYPE_MARKETSHOP) {
|
||||
if (item_list[i].qty > shop[j].qty)
|
||||
return 3;
|
||||
return e_purchase_result::PURCHASE_FAIL_COUNT;
|
||||
market_index[i] = j;
|
||||
}
|
||||
#endif
|
||||
@ -2287,7 +2744,7 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
|
||||
id = itemdb_exists(nameid);
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
case CHKADDITEM_OVERAMOUNT:
|
||||
return 2;
|
||||
return e_purchase_result::PURCHASE_FAIL_WEIGHT;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (nd->master_nd) //Script-based shops.
|
||||
return npc_buylist_sub(sd,n,item_list,nd->master_nd);
|
||||
if (nd->master_nd){ //Script-based shops.
|
||||
npc_buylist_sub(sd,n,item_list,nd->master_nd);
|
||||
return e_purchase_result::PURCHASE_SUCCEED;
|
||||
}
|
||||
|
||||
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 )
|
||||
return 2; // Too heavy
|
||||
return e_purchase_result::PURCHASE_FAIL_WEIGHT; // Too heavy
|
||||
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);
|
||||
|
||||
@ -2339,7 +2798,7 @@ uint8 npc_buylist(struct map_session_data* sd, uint16 n, struct s_npc_buy_list *
|
||||
if (nd->subtype == NPCTYPE_MARKETSHOP) {
|
||||
j = market_index[i];
|
||||
if (amount > shop[j].qty)
|
||||
return 1;
|
||||
return e_purchase_result::PURCHASE_FAIL_MONEY;
|
||||
shop[j].qty -= amount;
|
||||
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
|
||||
@ -2573,6 +3032,268 @@ uint8 npc_selllist(struct map_session_data* sd, int n, unsigned short *item_list
|
||||
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
|
||||
//This doesn't remove it from map_db
|
||||
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);
|
||||
|
||||
stylist_db.reload();
|
||||
barter_db.reload();
|
||||
|
||||
//Re-read the NPC Script Events cache.
|
||||
npc_read_event_script();
|
||||
@ -5119,6 +5841,7 @@ void do_final_npc(void) {
|
||||
NPCMarketDB->destroy(NPCMarketDB, npc_market_free);
|
||||
#endif
|
||||
stylist_db.clear();
|
||||
barter_db.clear();
|
||||
ers_destroy(timer_event_ers);
|
||||
ers_destroy(npc_sc_display_ers);
|
||||
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);
|
||||
|
||||
stylist_db.load();
|
||||
barter_db.load();
|
||||
|
||||
// set up the events cache
|
||||
npc_read_event_script();
|
||||
|
@ -4,8 +4,13 @@
|
||||
#ifndef NPC_HPP
|
||||
#define NPC_HPP
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "../common/database.hpp"
|
||||
#include "../common/timer.hpp"
|
||||
|
||||
#include "clif.hpp" //
|
||||
#include "map.hpp" // struct block_list
|
||||
#include "status.hpp" // struct status_change
|
||||
#include "unit.hpp" // struct unit_data
|
||||
@ -85,6 +90,51 @@ public:
|
||||
|
||||
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 {
|
||||
e_questinfo_types icon;
|
||||
e_questinfo_markcolor color;
|
||||
@ -153,6 +203,9 @@ struct npc_data {
|
||||
char killer_name[NAME_LENGTH];
|
||||
int spawn_timer;
|
||||
} tomb;
|
||||
struct {
|
||||
bool extended;
|
||||
} barter;
|
||||
} u;
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
|
@ -32,6 +32,11 @@
|
||||
#pragma pack( push, 1 )
|
||||
#endif
|
||||
|
||||
struct PACKET_ZC_PC_PURCHASE_RESULT{
|
||||
int16 packetType;
|
||||
uint8 result;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PACKET_CZ_REQ_MAKINGARROW{
|
||||
int16 packetType;
|
||||
#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_BROADCAST, 0x9a)
|
||||
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_ACK_TOUSESKILL, 0x110)
|
||||
DEFINE_PACKET_HEADER(CZ_REQMAKINGITEM, 0x18e)
|
||||
|
@ -383,6 +383,8 @@ struct map_session_data {
|
||||
bool cashshop_open;
|
||||
bool sale_open;
|
||||
bool stylist_open;
|
||||
bool barter_open;
|
||||
bool barter_extended_open;
|
||||
unsigned int block_action : 10;
|
||||
bool refineui_open;
|
||||
t_itemid inventory_expansion_confirmation;
|
||||
@ -1055,7 +1057,8 @@ extern JobDatabase job_db;
|
||||
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)
|
||||
|| 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
|
||||
static bool pc_cant_act( struct map_session_data* sd ){
|
||||
|
@ -17511,7 +17511,7 @@ BUILDIN_FUNC(callshop)
|
||||
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 && 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);
|
||||
script_pushint(st,0);
|
||||
return SCRIPT_CMD_FAILURE;
|
||||
@ -17547,7 +17547,16 @@ BUILDIN_FUNC(callshop)
|
||||
return SCRIPT_CMD_SUCCESS;
|
||||
}
|
||||
#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);
|
||||
|
||||
sd->npc_shopid = nd->bl.id;
|
||||
|
Loading…
x
Reference in New Issue
Block a user