summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/mmo.h2
-rw-r--r--src/map/Makefile.in6
-rw-r--r--src/map/atcommand.c4
-rw-r--r--src/map/battle.c1
-rw-r--r--src/map/battle.h1
-rw-r--r--src/map/buyingstore.c359
-rw-r--r--src/map/buyingstore.h29
-rw-r--r--src/map/chat.c7
-rw-r--r--src/map/clif.c323
-rw-r--r--src/map/clif.h14
-rw-r--r--src/map/itemdb.c28
-rw-r--r--src/map/itemdb.h1
-rw-r--r--src/map/log.h7
-rw-r--r--src/map/mail.c2
-rw-r--r--src/map/pc.h15
-rw-r--r--src/map/script.c18
-rw-r--r--src/map/skill.c6
-rw-r--r--src/map/trade.c4
-rw-r--r--src/map/unit.c2
19 files changed, 808 insertions, 21 deletions
diff --git a/src/common/mmo.h b/src/common/mmo.h
index 181f972e5..fa41c99d8 100644
--- a/src/common/mmo.h
+++ b/src/common/mmo.h
@@ -84,7 +84,7 @@
#define MAX_ZENY 1000000000
#define MAX_FAME 1000000000
#define MAX_CART 100
-#define MAX_SKILL 1020
+#define MAX_SKILL 2536
#define GLOBAL_REG_NUM 256
#define ACCOUNT_REG_NUM 64
#define ACCOUNT_REG2_NUM 16
diff --git a/src/map/Makefile.in b/src/map/Makefile.in
index cf6a29d6c..cec75c69b 100644
--- a/src/map/Makefile.in
+++ b/src/map/Makefile.in
@@ -17,7 +17,8 @@ MAP_OBJ = map.o chrif.o clif.o pc.o status.o npc.o \
npc_chat.o chat.o path.o itemdb.o mob.o script.o \
storage.o skill.o atcommand.o battle.o battleground.o \
intif.o trade.o party.o vending.o guild.o pet.o \
- log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o
+ log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o \
+ buyingstore.o
MAP_TXT_OBJ = $(MAP_OBJ:%=obj_txt/%) \
obj_txt/mapreg_txt.o
MAP_SQL_OBJ = $(MAP_OBJ:%=obj_sql/%) \
@@ -26,7 +27,8 @@ MAP_H = map.h chrif.h clif.h pc.h status.h npc.h \
chat.h itemdb.h mob.h script.h path.h \
storage.h skill.h atcommand.h battle.h battleground.h \
intif.h trade.h party.h vending.h guild.h pet.h \
- log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h
+ log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h \
+ buyingstore.h
HAVE_MYSQL=@HAVE_MYSQL@
ifeq ($(HAVE_MYSQL),yes)
diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index 3035487da..6abee5223 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -1150,7 +1150,7 @@ ACMD_FUNC(storage)
{
nullpo_retr(-1, sd);
- if (sd->npc_id || sd->vender_id || sd->state.trading || sd->state.storage_flag)
+ if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.trading || sd->state.storage_flag)
return -1;
if (storage_storageopen(sd) == 1)
@@ -1177,7 +1177,7 @@ ACMD_FUNC(guildstorage)
return -1;
}
- if (sd->npc_id || sd->vender_id || sd->state.trading)
+ if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.trading)
return -1;
if (sd->state.storage_flag == 1) {
diff --git a/src/map/battle.c b/src/map/battle.c
index fd9bc90d6..aebb93d56 100644
--- a/src/map/battle.c
+++ b/src/map/battle.c
@@ -4007,6 +4007,7 @@ static const struct _battle_data {
{ "client_reshuffle_dice", &battle_config.client_reshuffle_dice, 0, 0, 1, },
{ "client_sort_storage", &battle_config.client_sort_storage, 0, 0, 1, },
{ "gm_check_minlevel", &battle_config.gm_check_minlevel, 60, 0, 100, },
+ { "feature_buying_store", &battle_config.feature_buying_store, 1, 0, 1, },
// BattleGround Settings
{ "bg_update_interval", &battle_config.bg_update_interval, 1000, 100, INT_MAX, },
{ "bg_short_attack_damage_rate", &battle_config.bg_short_damage_rate, 80, 0, INT_MAX, },
diff --git a/src/map/battle.h b/src/map/battle.h
index a3e35bcee..f11040c8a 100644
--- a/src/map/battle.h
+++ b/src/map/battle.h
@@ -481,6 +481,7 @@ extern struct Battle_Config
int client_reshuffle_dice; // Reshuffle /dice
int client_sort_storage;
int gm_check_minlevel; // min GM level for /check
+ int feature_buying_store;
// [BattleGround Settings]
int bg_update_interval;
diff --git a/src/map/buyingstore.c b/src/map/buyingstore.c
new file mode 100644
index 000000000..21629e641
--- /dev/null
+++ b/src/map/buyingstore.c
@@ -0,0 +1,359 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/db.h" // ARR_FIND
+#include "../common/showmsg.h" // ShowWarning
+#include "../common/socket.h" // RBUF*
+#include "../common/strlib.h" // safestrncpy
+#include "atcommand.h" // msg_txt
+#include "battle.h" // battle_config.*
+#include "buyingstore.h" // struct s_buyingstore
+#include "clif.h" // clif_buyingstore_*
+#include "log.h" // log_pick_pc, log_zeny
+#include "pc.h" // struct map_session_data
+
+
+/// constants (client-side restrictions)
+#define BUYINGSTORE_MAX_PRICE 99990000
+#define BUYINGSTORE_MAX_AMOUNT 9999
+
+
+/// failure constants for clif functions
+enum e_buyingstore_failure
+{
+ BUYINGSTORE_CREATE = 1, // "Failed to open buying store."
+ BUYINGSTORE_CREATE_OVERWEIGHT = 2, // "Total amount of then possessed items exceeds the weight limit by %d. Please re-enter."
+ BUYINGSTORE_TRADE_BUYER_ZENY = 3, // "All items within the buy limit were purchased."
+ BUYINGSTORE_TRADE_BUYER_NO_ITEMS = 4, // "All items were purchased."
+ BUYINGSTORE_TRADE_SELLER_FAILED = 5, // "The deal has failed."
+ BUYINGSTORE_TRADE_SELLER_COUNT = 6, // "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy."
+ BUYINGSTORE_TRADE_SELLER_ZENY = 7, // "The trade failed, because the buyer is lacking required balance."
+ BUYINGSTORE_CREATE_NO_INFO = 8, // "No sale (purchase) information available."
+};
+
+
+static unsigned int buyingstore_nextid = 0;
+
+
+/// Returns unique buying store id
+static unsigned int buyingstore_getuid(void)
+{
+ return buyingstore_nextid++;
+}
+
+
+bool buyingstore_setup(struct map_session_data* sd, unsigned char slots)
+{
+ if( !battle_config.feature_buying_store || sd->vender_id || sd->state.buyingstore || sd->state.trading || slots == 0 )
+ {
+ return false;
+ }
+
+ if( slots > MAX_BUYINGSTORE_SLOTS )
+ {
+ ShowWarning("buyingstore_setup: Requested %d slots, but server supports only %d slots.\n", (int)slots, MAX_BUYINGSTORE_SLOTS);
+ slots = MAX_BUYINGSTORE_SLOTS;
+ }
+
+ sd->buyingstore.slots = slots;
+ clif_buyingstore_open(sd);
+
+ return true;
+}
+
+
+void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count)
+{
+ unsigned int i, weight, listidx;
+ struct item_data* id;
+
+ if( !result || count == 0 )
+ {// canceled, or no items
+ return;
+ }
+
+ if( !battle_config.feature_buying_store || pc_istrading(sd) || sd->buyingstore.slots == 0 || count > sd->buyingstore.slots || zenylimit <= 0 || zenylimit > sd->status.zeny || !storename[0] )
+ {// disabled or invalid input
+ sd->buyingstore.slots = 0;
+ clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0);
+ return;
+ }
+
+ if( !pc_can_give_items(pc_isGM(sd)) )
+ {// custom: GM is not allowed to buy (give zeny)
+ sd->buyingstore.slots = 0;
+ clif_displaymessage(sd->fd, msg_txt(246));
+ clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0);
+ return;
+ }
+
+ weight = sd->weight;
+
+ // check item list
+ for( i = 0; i < count; i++ )
+ {// itemlist: <name id>.W <amount>.W <price>.L
+ unsigned short nameid, amount;
+ int price, idx;
+
+ nameid = RBUFW(itemlist,i*8+0);
+ amount = RBUFW(itemlist,i*8+2);
+ price = RBUFL(itemlist,i*8+4);
+
+ if( ( id = itemdb_exists(nameid) ) == NULL || amount == 0 )
+ {// invalid input
+ break;
+ }
+
+ if( price <= 0 || price > BUYINGSTORE_MAX_PRICE )
+ {// invalid price: unlike vending, items cannot be bought at 0 Zeny
+ break;
+ }
+
+ if( !id->flag.buyingstore || !itemdb_cantrade_sub(id, pc_isGM(sd), pc_isGM(sd)) || ( idx = pc_search_inventory(sd, nameid) ) == -1 )
+ {// restrictions: allowed, no character-bound items and at least one must be owned
+ break;
+ }
+
+ if( sd->status.inventory[idx].amount+amount > BUYINGSTORE_MAX_AMOUNT )
+ {// too many items of same kind
+ break;
+ }
+
+ if( i )
+ {// duplicate check. as the client does this too, only malicious intent should be caught here
+ ARR_FIND( 0, i, listidx, sd->buyingstore.items[i].nameid == nameid );
+ if( listidx != i )
+ {// duplicate
+ ShowWarning("buyingstore_create: Found duplicate item on buying list (nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", nameid, amount, sd->status.account_id, sd->status.char_id);
+ break;
+ }
+ }
+
+ weight+= id->weight*amount;
+ sd->buyingstore.items[i].nameid = nameid;
+ sd->buyingstore.items[i].amount = amount;
+ sd->buyingstore.items[i].price = price;
+ }
+
+ if( i != count )
+ {// invalid item/amount/price
+ sd->buyingstore.slots = 0;
+ clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0);
+ return;
+ }
+
+ if( (sd->max_weight*90)/100 < weight )
+ {// not able to carry all wanted items without getting overweight (90%)
+ sd->buyingstore.slots = 0;
+ clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE_OVERWEIGHT, weight);
+ return;
+ }
+
+ // success
+ sd->state.buyingstore = true;
+ sd->buyer_id = buyingstore_getuid();
+ sd->buyingstore.zenylimit = zenylimit;
+ sd->buyingstore.slots = i; // store actual amount of items
+ safestrncpy(sd->message, storename, sizeof(sd->message));
+ clif_buyingstore_myitemlist(sd);
+ clif_buyingstore_entry(sd);
+}
+
+
+void buyingstore_close(struct map_session_data* sd)
+{
+ if( sd->state.buyingstore )
+ {
+ // invalidate data
+ sd->state.buyingstore = false;
+ memset(&sd->buyingstore, 0, sizeof(sd->buyingstore));
+
+ // notify other players
+ clif_buyingstore_disappear_entry(sd);
+ }
+}
+
+
+void buyingstore_open(struct map_session_data* sd, int account_id)
+{
+ struct map_session_data* pl_sd;
+
+ if( !battle_config.feature_buying_store || pc_istrading(sd) )
+ {// not allowed to sell
+ return;
+ }
+
+ if( !pc_can_give_items(pc_isGM(sd)) )
+ {// custom: GM is not allowed to sell
+ clif_displaymessage(sd->fd, msg_txt(246));
+ return;
+ }
+
+ if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore )
+ {// not online or not buying
+ return;
+ }
+
+ // success
+ clif_buyingstore_itemlist(sd, pl_sd);
+}
+
+
+void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count)
+{
+ short blankslots[MAX_SLOTS]; // used when checking whether or not an item's card slots are blank
+ int zeny = 0;
+ unsigned int i, weight, listidx, k;
+ struct map_session_data* pl_sd;
+
+ if( count == 0 )
+ {// nothing to do
+ return;
+ }
+
+ if( !battle_config.feature_buying_store || pc_istrading(sd) )
+ {// not allowed to sell
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
+ return;
+ }
+
+ if( !pc_can_give_items(pc_isGM(sd)) )
+ {// custom: GM is not allowed to sell
+ clif_displaymessage(sd->fd, msg_txt(246));
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
+ return;
+ }
+
+ if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id )
+ {// not online, not buying or not same store
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
+ return;
+ }
+
+ if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit )
+ {// buyer lost zeny in the mean time? fix the limit
+ pl_sd->buyingstore.zenylimit = pl_sd->status.zeny;
+ }
+ weight = pl_sd->weight;
+ memset(blankslots, 0, sizeof(blankslots));
+
+ // check item list
+ for( i = 0; i < count; i++ )
+ {// itemlist: <index>.W <name id>.W <amount>.W
+ unsigned short nameid, amount;
+ int index;
+
+ index = RBUFW(itemlist,i*6+0)-2;
+ nameid = RBUFW(itemlist,i*6+2);
+ amount = RBUFW(itemlist,i*6+4);
+
+ if( i )
+ {// duplicate check. as the client does this too, only malicious intent should be caught here
+ ARR_FIND( 0, i, k, RBUFW(itemlist,k*6+0)-2 == index );
+ if( k != i )
+ {// duplicate
+ ShowWarning("buyingstore_trade: Found duplicate item on selling list (prevnameid=%hu, prevamount=%hu, nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n",
+ RBUFW(itemlist,k*6+2), RBUFW(itemlist,k*6+4), nameid, amount, sd->status.account_id, sd->status.char_id);
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
+ return;
+ }
+ }
+
+ if( index < 0 || index >= ARRAYLENGTH(sd->status.inventory) || sd->inventory_data[index] == NULL || sd->status.inventory[index].nameid != nameid || sd->status.inventory[index].amount < amount )
+ {// invalid input
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
+ return;
+ }
+
+ if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc_isGM(sd), pc_isGM(pl_sd)) || memcmp(sd->status.inventory[index].card, blankslots, sizeof(blankslots)) )
+ {// non-tradable item
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
+ return;
+ }
+
+ ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid );
+ if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 )
+ {// there is no such item or the buyer has already bought all of them
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
+ return;
+ }
+
+ if( pl_sd->buyingstore.items[listidx].amount < amount )
+ {// buyer does not need that much of the item
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid);
+ return;
+ }
+
+ if( pc_checkadditem(pl_sd, nameid, amount) == ADDITEM_OVERAMOUNT )
+ {// buyer does not have enough space for this item
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
+ return;
+ }
+
+ if( amount*(unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight-weight )
+ {// normally this is not supposed to happen, as the total weight is
+ // checked upon creation, but the buyer could have gained items
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
+ return;
+ }
+ weight+= amount*sd->inventory_data[index]->weight;
+
+ if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny )
+ {// buyer does not have enough zeny
+ clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid);
+ return;
+ }
+ zeny+= amount*pl_sd->buyingstore.items[listidx].price;
+ }
+
+ // process item list
+ for( i = 0; i < count; i++ )
+ {// itemlist: <index>.W <name id>.W <amount>.W
+ unsigned short nameid, amount;
+ int index;
+
+ index = RBUFW(itemlist,i*6+0)-2;
+ nameid = RBUFW(itemlist,i*6+2);
+ amount = RBUFW(itemlist,i*6+4);
+
+ ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid );
+ zeny = amount*pl_sd->buyingstore.items[listidx].price;
+
+ // log
+ if( log_config.enable_logs&LOG_BUYING_STORE )
+ {
+ log_pick_pc(sd, "B", nameid, -((int)amount), &sd->status.inventory[index]);
+ log_pick_pc(pl_sd, "B", nameid, amount, &sd->status.inventory[index]);
+ }
+ if( log_config.zeny )
+ log_zeny(sd, "B", pl_sd, zeny);
+
+ // move item
+ pc_additem(pl_sd, &sd->status.inventory[index], amount);
+ pc_delitem(sd, index, amount, 1, 0);
+ pl_sd->buyingstore.items[listidx].amount-= amount;
+
+ // pay up
+ pc_payzeny(pl_sd, zeny);
+ pc_getzeny(sd, zeny);
+ pl_sd->buyingstore.zenylimit-= zeny;
+
+ // notify clients
+ clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price);
+ clif_buyingstore_update_item(pl_sd, nameid, amount);
+ }
+
+ // check whether or not there is still something to buy
+ ARR_FIND( 0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0 );
+ if( i == pl_sd->buyingstore.slots )
+ {// everything was bought
+ clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS);
+ buyingstore_close(pl_sd);
+ }
+ else if( pl_sd->buyingstore.zenylimit == 0 )
+ {// zeny limit reached
+ clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY);
+ buyingstore_close(pl_sd);
+ }
+}
diff --git a/src/map/buyingstore.h b/src/map/buyingstore.h
new file mode 100644
index 000000000..473fdb01a
--- /dev/null
+++ b/src/map/buyingstore.h
@@ -0,0 +1,29 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef _BUYINGSTORE_H_
+#define _BUYINGSTORE_H_
+
+#define MAX_BUYINGSTORE_SLOTS 5
+
+struct s_buyingstore_item
+{
+ int price;
+ unsigned short amount;
+ unsigned short nameid;
+};
+
+struct s_buyingstore
+{
+ struct s_buyingstore_item items[MAX_BUYINGSTORE_SLOTS];
+ int zenylimit;
+ unsigned char slots;
+};
+
+bool buyingstore_setup(struct map_session_data* sd, unsigned char slots);
+void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count);
+void buyingstore_close(struct map_session_data* sd);
+void buyingstore_open(struct map_session_data* sd, int account_id);
+void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count);
+
+#endif // _BUYINGSTORE_H_
diff --git a/src/map/chat.c b/src/map/chat.c
index 96ec4c20d..4ba784b2e 100644
--- a/src/map/chat.c
+++ b/src/map/chat.c
@@ -69,6 +69,11 @@ int chat_createpcchat(struct map_session_data* sd, const char* title, const char
if( sd->chatID )
return 0; //Prevent people abusing the chat system by creating multiple chats, as pointed out by End of Exam. [Skotlex]
+ if( sd->vender_id || sd->state.buyingstore )
+ {// not chat, when you already have a store open
+ return 0;
+ }
+
if( map[sd->bl.m].flag.nochat )
{
clif_displaymessage(sd->fd, msg_txt(281));
@@ -108,7 +113,7 @@ int chat_joinchat(struct map_session_data* sd, int chatid, const char* pass)
nullpo_ret(sd);
cd = (struct chat_data*)map_id2bl(chatid);
- if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->vender_id || sd->chatID || cd->users >= cd->limit )
+ if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->vender_id || sd->state.buyingstore || sd->chatID || cd->users >= cd->limit )
{
clif_joinchatfail(sd,0);
return 0;
diff --git a/src/map/clif.c b/src/map/clif.c
index 59693d235..1bf87aec5 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -56,7 +56,10 @@ struct Clif_Config {
struct s_packet_db packet_db[MAX_PACKET_VER + 1][MAX_PACKET_DB + 1];
//Converts item type in case of pet eggs.
-#define itemtype(a) (a == IT_PETEGG)?IT_WEAPON:a
+inline int itemtype(int type)
+{
+ return ( type == IT_PETEGG ) ? IT_WEAPON : type;
+}
#define WBUFPOS(p,pos,x,y,dir) \
do { \
@@ -3644,6 +3647,9 @@ static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_d
if(dstsd->vender_id)
clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd);
+ if( dstsd->state.buyingstore )
+ clif_buyingstore_entry_single(sd, dstsd);
+
if(dstsd->spiritball > 0)
clif_spiritball_single(sd->fd, dstsd);
@@ -4072,6 +4078,8 @@ int clif_outsight(struct block_list *bl,va_list ap)
}
if(sd->vender_id)
clif_closevendingboard(bl,tsd->fd);
+ if( sd->state.buyingstore )
+ clif_buyingstore_disappear_entry_single(tsd, sd);
break;
case BL_ITEM:
clif_clearflooritem((struct flooritem_data*)bl,tsd->fd);
@@ -12851,7 +12859,7 @@ void clif_Auction_openwindow(struct map_session_data *sd)
{
int fd = sd->fd;
- if( sd->state.storage_flag || sd->vender_id || sd->state.trading )
+ if( sd->state.storage_flag || sd->vender_id || sd->state.buyingstore || sd->state.trading )
return;
WFIFOHEAD(fd,12);
@@ -14033,6 +14041,308 @@ void clif_showdigit(struct map_session_data* sd, unsigned char type, int value)
WFIFOSET(sd->fd, packet_len(0x1b1));
}
+
+/// Buying Store System
+///
+
+/// Opens preparation window for buying store (ZC_OPEN_BUYING_STORE)
+/// 0810 <slots>.B
+void clif_buyingstore_open(struct map_session_data* sd)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x810));
+ WFIFOW(fd,0) = 0x810;
+ WFIFOB(fd,2) = sd->buyingstore.slots;
+ WFIFOSET(fd,packet_len(0x810));
+}
+
+
+/// Request to create a buying store (CZ_REQ_OPEN_BUYING_STORE)
+/// 0811 <packet len>.W <limit zeny>.L <result>.B <store name>.80B { <name id>.W <amount>.W <price>.L }*
+/// result:
+/// 0 = cancel
+/// 1 = open
+static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd)
+{
+ const unsigned int blocksize = 8;
+ uint8* itemlist;
+ char storename[MESSAGE_SIZE];
+ unsigned char result;
+ int zenylimit;
+ unsigned int count, packet_len;
+ struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
+
+ packet_len = RFIFOW(fd,info->pos[0]);
+
+ // TODO: Make this check global for all variable length packets.
+ if( packet_len < 89 )
+ {// minimum packet length
+ ShowError("clif_parse_ReqOpenBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 89, packet_len, sd->bl.id);
+ return;
+ }
+
+ zenylimit = RFIFOL(fd,info->pos[1]);
+ result = RFIFOL(fd,info->pos[2]);
+ safestrncpy(storename, (const char*)RFIFOP(fd,info->pos[3]), sizeof(storename));
+ itemlist = RFIFOP(fd,info->pos[4]);
+
+ // so that buyingstore_create knows, how many elements it has access to
+ packet_len-= info->pos[4];
+
+ if( packet_len%blocksize )
+ {
+ ShowError("clif_parse_ReqOpenBuyingStore: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize);
+ return;
+ }
+ count = packet_len/blocksize;
+
+ buyingstore_create(sd, zenylimit, result, storename, itemlist, count);
+}
+
+
+/// Notification, that the requested buying store could not be created (ZC_FAILED_OPEN_BUYING_STORE_TO_BUYER)
+/// 0812 <result>.W <total weight>.L
+/// result:
+/// 1 = "Failed to open buying store." (0x6cd, MSI_BUYINGSTORE_OPEN_FAILED)
+/// 2 = "Total amount of then possessed items exceeds the weight limit by <weight/10-maxweight*90%>. Please re-enter." (0x6ce, MSI_BUYINGSTORE_OVERWEIGHT)
+/// 8 = "No sale (purchase) information available." (0x705)
+/// other = nothing
+void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x812));
+ WFIFOW(fd,0) = 0x812;
+ WFIFOW(fd,2) = result;
+ WFIFOL(fd,4) = weight;
+ WFIFOSET(fd,packet_len(0x812));
+}
+
+
+/// Notification, that the requested buying store was created (ZC_MYITEMLIST_BUYING_STORE)
+/// 0813 <packet len>.W <account id>.L <limit zeny>.L { <price>.L <count>.W <type>.B <name id>.W }*
+void clif_buyingstore_myitemlist(struct map_session_data* sd)
+{
+ int fd = sd->fd;
+ unsigned int i;
+
+ WFIFOHEAD(fd,12+sd->buyingstore.slots*9);
+ WFIFOW(fd,0) = 0x813;
+ WFIFOW(fd,2) = 12+sd->buyingstore.slots*9;
+ WFIFOL(fd,4) = sd->bl.id;
+ WFIFOL(fd,8) = sd->buyingstore.zenylimit;
+
+ for( i = 0; i < sd->buyingstore.slots; i++ )
+ {
+ WFIFOL(fd,12+i*9) = sd->buyingstore.items[i].price;
+ WFIFOW(fd,16+i*9) = sd->buyingstore.items[i].amount;
+ WFIFOB(fd,18+i*9) = itemtype(itemdb_type(sd->buyingstore.items[i].nameid));
+ WFIFOW(fd,19+i*9) = sd->buyingstore.items[i].nameid;
+ }
+
+ WFIFOSET(fd,WFIFOW(fd,2));
+}
+
+
+/// Notifies clients in area of a buying store (ZC_BUYING_STORE_ENTRY)
+/// 0814 <account id>.L <store name>.80B
+void clif_buyingstore_entry(struct map_session_data* sd)
+{
+ uint8 buf[86];
+
+ WBUFW(buf,0) = 0x814;
+ WBUFL(buf,2) = sd->bl.id;
+ memcpy(WBUFP(buf,6), sd->message, MESSAGE_SIZE);
+
+ clif_send(buf, packet_len(0x814), &sd->bl, AREA_WOS);
+}
+void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x814));
+ WFIFOW(fd,0) = 0x814;
+ WFIFOL(fd,2) = pl_sd->bl.id;
+ memcpy(WFIFOP(fd,6), pl_sd->message, MESSAGE_SIZE);
+ WFIFOSET(fd,packet_len(0x814));
+}
+
+
+/// Request to close own buying store (CZ_REQ_CLOSE_BUYING_STORE)
+/// 0815
+static void clif_parse_ReqCloseBuyingStore(int fd, struct map_session_data* sd)
+{
+ buyingstore_close(sd);
+}
+
+
+/// Notifies clients in area that a buying store was closed (ZC_DISAPPEAR_BUYING_STORE_ENTRY)
+/// 0816 <account id>.L
+void clif_buyingstore_disappear_entry(struct map_session_data* sd)
+{
+ uint8 buf[6];
+
+ WBUFW(buf,0) = 0x816;
+ WBUFL(buf,2) = sd->bl.id;
+
+ clif_send(buf, packet_len(0x816), &sd->bl, AREA_WOS);
+}
+void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x816));
+ WFIFOW(fd,0) = 0x816;
+ WFIFOL(fd,2) = pl_sd->bl.id;
+ WFIFOSET(fd,packet_len(0x816));
+}
+
+
+/// Request to open someone else's buying store (CZ_REQ_CLICK_TO_BUYING_STORE)
+/// 0817 <account id>.L
+static void clif_parse_ReqClickBuyingStore(int fd, struct map_session_data* sd)
+{
+ int account_id;
+
+ account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
+
+ buyingstore_open(sd, account_id);
+}
+
+
+/// Sends buying store item list (ZC_ACK_ITEMLIST_BUYING_STORE)
+/// 0818 <packet len>.W <account id>.L <store id>.L <limit zeny>.L { <price>.L <amount>.W <type>.B <name id>.W }*
+void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd)
+{
+ int fd = sd->fd;
+ unsigned int i;
+
+ WFIFOHEAD(fd,16+pl_sd->buyingstore.slots*9);
+ WFIFOW(fd,0) = 0x818;
+ WFIFOW(fd,2) = 16+pl_sd->buyingstore.slots*9;
+ WFIFOL(fd,4) = pl_sd->bl.id;
+ WFIFOL(fd,8) = pl_sd->buyer_id;
+ WFIFOL(fd,12) = pl_sd->buyingstore.zenylimit;
+
+ for( i = 0; i < pl_sd->buyingstore.slots; i++ )
+ {
+ WFIFOL(fd,16+i*9) = pl_sd->buyingstore.items[i].price;
+ WFIFOW(fd,20+i*9) = pl_sd->buyingstore.items[i].amount; // TODO: Figure out, if no longer needed items (amount == 0) are listed on official.
+ WFIFOB(fd,22+i*9) = itemtype(itemdb_type(pl_sd->buyingstore.items[i].nameid));
+ WFIFOW(fd,23+i*9) = pl_sd->buyingstore.items[i].nameid;
+ }
+
+ WFIFOSET(fd,WFIFOW(fd,2));
+}
+
+
+/// Request to sell items to a buying store (CZ_REQ_TRADE_BUYING_STORE)
+/// 0819 <packet len>.W <account id>.L <store id>.L { <index>.W <name id>.W <amount>.W }*
+static void clif_parse_ReqTradeBuyingStore(int fd, struct map_session_data* sd)
+{
+ const unsigned int blocksize = 6;
+ uint8* itemlist;
+ int account_id;
+ unsigned int count, packet_len, buyer_id;
+ struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
+
+ packet_len = RFIFOW(fd,info->pos[0]);
+
+ if( packet_len < 12 )
+ {// minimum packet length
+ ShowError("clif_parse_ReqTradeBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 12, packet_len, sd->bl.id);
+ return;
+ }
+
+ account_id = RFIFOL(fd,info->pos[1]);
+ buyer_id = RFIFOL(fd,info->pos[2]);
+ itemlist = RFIFOP(fd,info->pos[3]);
+
+ // so that buyingstore_trade knows, how many elements it has access to
+ packet_len-= info->pos[3];
+
+ if( packet_len%blocksize )
+ {
+ ShowError("clif_parse_ReqTradeBuyingStore: Unexpected item list size %u (account_id=%d, buyer_id=%d, block size=%u)\n", packet_len, sd->bl.id, account_id, blocksize);
+ return;
+ }
+ count = packet_len/blocksize;
+
+ buyingstore_trade(sd, account_id, buyer_id, itemlist, count);
+}
+
+
+/// Notifies the buyer, that the buying store has been closed due to a post-trade condition (ZC_FAILED_TRADE_BUYING_STORE_TO_BUYER)
+/// 081a <result>.W
+/// result:
+/// 3 = "All items within the buy limit were purchased." (0x6cf, MSI_BUYINGSTORE_TRADE_OVERLIMITZENY)
+/// 4 = "All items were purchased." (0x6d0, MSI_BUYINGSTORE_TRADE_BUYCOMPLETE)
+/// other = nothing
+void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x81a));
+ WFIFOW(fd,0) = 0x81a;
+ WFIFOW(fd,2) = result;
+ WFIFOSET(fd,packet_len(0x81a));
+}
+
+
+/// Updates the zeny limit and an item in the buying store item list (ZC_UPDATE_ITEM_FROM_BUYING_STORE)
+/// 081b <name id>.W <amount>.W <limit zeny>.L
+void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x81b));
+ WFIFOW(fd,0) = 0x81b;
+ WFIFOW(fd,2) = nameid;
+ WFIFOW(fd,4) = amount; // amount of nameid received
+ WFIFOW(fd,6) = sd->buyingstore.zenylimit;
+ WFIFOSET(fd,packet_len(0x81b));
+}
+
+
+/// Deletes item from inventory, that was sold to a buying store (ZC_ITEM_DELETE_BUYING_STORE)
+/// 081c <index>.W <amount>.W <price>.L
+/// message:
+/// "%s (%d) were sold at %dz." (0x6d2, MSI_BUYINGSTORE_TRADE_SELLCOMPLETE)
+///
+/// @note This function has to be called _instead_ of clif_delitem/clif_dropitem.
+void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x81c));
+ WFIFOW(fd,0) = 0x81c;
+ WFIFOW(fd,2) = index+2;
+ WFIFOW(fd,4) = amount;
+ WFIFOL(fd,6) = price; // price per item, client calculates total Zeny by itself
+ WFIFOSET(fd,packet_len(0x81c));
+}
+
+
+/// Notifies the seller, that a buying store trade failed (ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER)
+/// 0824 <result>.W <name id>.W
+/// result:
+/// 5 = "The deal has failed." (0x39, MSI_DEAL_FAIL)
+/// 6 = "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." (0x6d3, MSI_BUYINGSTORE_TRADE_OVERCOUNT)
+/// 7 = "The trade failed, because the buyer is lacking required balance." (0x6d1, MSI_BUYINGSTORE_TRADE_LACKBUYERZENY)
+/// other = nothing
+void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid)
+{
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd,packet_len(0x824));
+ WFIFOW(fd,0) = 0x824;
+ WFIFOW(fd,2) = result;
+ WFIFOW(fd,4) = nameid;
+ WFIFOSET(fd,packet_len(0x824));
+}
+
+
/*==========================================
* パケットデバッグ
*------------------------------------------*/
@@ -14435,8 +14745,8 @@ static int packetdb_readdb(void)
#else // for Party booking ( PACKETVER >= 20091229 )
-1, -1, 18, 4, 8, 6, 2, 4, 14, 50, 18, 6, 2, 3, 14, 20,
#endif
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, -1, 8, -1, 86, 2, 6, 6, -1, -1, 4, 10, 10, 0, 0, 0,
+ 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
struct {
@@ -14628,6 +14938,11 @@ static int packetdb_readdb(void)
{clif_parse_PartyBookingDeleteReq,"bookingdelreq"},
#endif
{clif_parse_PVPInfo,"pvpinfo"},
+ // Buying Store
+ {clif_parse_ReqOpenBuyingStore,"reqopenbuyingstore"},
+ {clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"},
+ {clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"},
+ {clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"},
{NULL,NULL}
};
diff --git a/src/map/clif.h b/src/map/clif.h
index 50dc76c72..67516e538 100644
--- a/src/map/clif.h
+++ b/src/map/clif.h
@@ -605,4 +605,18 @@ void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_boo
void clif_showdigit(struct map_session_data* sd, unsigned char type, int value);
+/// Buying Store System
+void clif_buyingstore_open(struct map_session_data* sd);
+void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight);
+void clif_buyingstore_myitemlist(struct map_session_data* sd);
+void clif_buyingstore_entry(struct map_session_data* sd);
+void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd);
+void clif_buyingstore_disappear_entry(struct map_session_data* sd);
+void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd);
+void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd);
+void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result);
+void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount);
+void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price);
+void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid);
+
#endif /* _CLIF_H_ */
diff --git a/src/map/itemdb.c b/src/map/itemdb.c
index 98f9a90d1..d8a8bb2cc 100644
--- a/src/map/itemdb.c
+++ b/src/map/itemdb.c
@@ -663,6 +663,33 @@ static bool itemdb_read_itemdelay(char* str[], int columns, int current)
return true;
}
+
+/// Reads items allowed to be sold in buying stores
+static bool itemdb_read_buyingstore(char* fields[], int columns, int current)
+{// <nameid>
+ int nameid;
+ struct item_data* id;
+
+ nameid = atoi(fields[0]);
+
+ if( ( id = itemdb_exists(nameid) ) == NULL )
+ {
+ ShowWarning("itemdb_read_buyingstore: Invalid item id %d.\n", nameid);
+ return false;
+ }
+
+ if( !itemdb_isstackable2(id) )
+ {
+ ShowWarning("itemdb_read_buyingstore: Non-stackable item id %d cannot be enabled for buying store.\n", nameid);
+ return false;
+ }
+
+ id->flag.buyingstore = true;
+
+ return true;
+}
+
+
/*======================================
* Applies gender restrictions according to settings. [Skotlex]
*======================================*/
@@ -979,6 +1006,7 @@ static void itemdb_read(void)
sv_readdb(db_path, "item_noequip.txt", ',', 2, 2, -1, &itemdb_read_noequip);
sv_readdb(db_path, "item_trade.txt", ',', 3, 3, -1, &itemdb_read_itemtrade);
sv_readdb(db_path, "item_delay.txt", ',', 2, 2, MAX_ITEMDELAYS, &itemdb_read_itemdelay);
+ sv_readdb(db_path, "item_buyingstore.txt", ',', 1, 1, -1, &itemdb_read_buyingstore);
}
/*==========================================
diff --git a/src/map/itemdb.h b/src/map/itemdb.h
index b5bcb84ea..463061175 100644
--- a/src/map/itemdb.h
+++ b/src/map/itemdb.h
@@ -76,6 +76,7 @@ struct item_data {
unsigned delay_consume : 1; // Signifies items that are not consumed immediately upon double-click [Skotlex]
unsigned trade_restriction : 7; //Item restrictions mask [Skotlex]
unsigned autoequip: 1;
+ unsigned buyingstore : 1;
} flag;
short gm_lv_trade_override; //GM-level to override trade_restriction
};
diff --git a/src/map/log.h b/src/map/log.h
index 9dfc0c0ea..f88236489 100644
--- a/src/map/log.h
+++ b/src/map/log.h
@@ -36,9 +36,10 @@ typedef enum log_what {
LOG_USED_ITEMS = 0x0100, // used by player
LOG_MVP_PRIZE = 0x0200,
LOG_COMMAND_ITEMS = 0x0400, // created/deleted through @/# commands
- LOG_STORAGE_ITEMS = 0x0800, // placed/retrieved from storage
- LOG_GSTORAGE_ITEMS = 0x1000, // placed/retrieved from guild storage
- LOG_MAILS = 0x2000 // mail system transactions
+ LOG_STORAGE_ITEMS = 0x0800, // placed/retrieved from storage
+ LOG_GSTORAGE_ITEMS = 0x1000, // placed/retrieved from guild storage
+ LOG_MAILS = 0x2000, // mail system transactions
+ LOG_BUYING_STORE = 0x4000, // buying store transactions
} log_what;
extern struct Log_Config {
diff --git a/src/map/mail.c b/src/map/mail.c
index f0869185a..c866bfb60 100644
--- a/src/map/mail.c
+++ b/src/map/mail.c
@@ -162,7 +162,7 @@ int mail_openmail(struct map_session_data *sd)
{
nullpo_ret(sd);
- if( sd->state.storage_flag || sd->vender_id || sd->state.trading )
+ if( sd->state.storage_flag || sd->vender_id || sd->state.buyingstore || sd->state.trading )
return 0;
clif_Mail_window(sd->fd, 0);
diff --git a/src/map/pc.h b/src/map/pc.h
index c48defbf5..328e43047 100644
--- a/src/map/pc.h
+++ b/src/map/pc.h
@@ -7,6 +7,7 @@
#include "../common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus
#include "../common/timer.h" // INVALID_TIMER
#include "battle.h" // battle_config
+#include "buyingstore.h" // struct s_buyingstore
#include "itemdb.h" // MAX_ITEMGROUP
#include "map.h" // RC_MAX
#include "pc.h" // struct map_session_data
@@ -128,6 +129,7 @@ struct map_session_data {
unsigned doridori : 1;
unsigned ignoreAll : 1;
unsigned debug_remove_map : 1; // temporary state to track double remove_map's [FlavioJS]
+ unsigned buyingstore : 1;
unsigned short autoloot;
unsigned short autolootid; // [Zephyrus]
unsigned noks : 3; // [Zeph Kill Steal Protection]
@@ -356,6 +358,9 @@ struct map_session_data {
char message[MESSAGE_SIZE];
struct s_vending vending[MAX_VENDING];
+ unsigned int buyer_id; // uid of open buying store
+ struct s_buyingstore buyingstore;
+
struct pet_data *pd;
struct homun_data *hd; // [blackhole89]
struct mercenary_data *md;
@@ -413,8 +418,8 @@ struct map_session_data {
const char* debug_func;
};
-//Update this max as necessary. 54 is the value needed for Super Baby currently
-#define MAX_SKILL_TREE 54
+//Update this max as necessary. 55 is the value needed for Super Baby currently
+#define MAX_SKILL_TREE 55
//Total number of classes (for data storage)
#define CLASS_COUNT (JOB_MAX - JOB_NOVICE_HIGH + JOB_MAX_BASIC)
@@ -515,9 +520,9 @@ extern int duel_count;
#define pc_setsit(sd) ( (sd)->state.dead_sit = (sd)->vd.dead_sit = 2 )
#define pc_isdead(sd) ( (sd)->state.dead_sit == 1 )
#define pc_issit(sd) ( (sd)->vd.dead_sit == 2 )
-#define pc_isidle(sd) ( (sd)->chatID || (sd)->vender_id || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share )
-#define pc_istrading(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.trading )
-#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->chatID || (sd)->sc.opt1 || (sd)->state.trading || (sd)->state.storage_flag )
+#define pc_isidle(sd) ( (sd)->chatID || (sd)->vender_id || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share )
+#define pc_istrading(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.buyingstore || (sd)->state.trading )
+#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.buyingstore || (sd)->chatID || (sd)->sc.opt1 || (sd)->state.trading || (sd)->state.storage_flag )
#define pc_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) )
#define pc_setchatid(sd,n) ( (sd)->chatID = n )
#define pc_ishiding(sd) ( (sd)->sc.option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) )
diff --git a/src/map/script.c b/src/map/script.c
index bbb0041de..d66edcd84 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -14807,6 +14807,23 @@ BUILDIN_FUNC(pushpc)
return 0;
}
+
+/// Invokes buying store preparation window
+/// buyingstore <slots>;
+BUILDIN_FUNC(buyingstore)
+{
+ struct map_session_data* sd;
+
+ if( ( sd = script_rid2sd(st) ) == NULL )
+ {
+ return 0;
+ }
+
+ buyingstore_setup(sd, script_getnum(st,2));
+ return 0;
+}
+
+
// declarations that were supposed to be exported from npc_chat.c
#ifdef PCRE_SUPPORT
BUILDIN_FUNC(defpattern);
@@ -15168,6 +15185,7 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(areamobuseskill,"siiiiviiiii"),
BUILDIN_DEF(progressbar,"si"),
BUILDIN_DEF(pushpc,"ii"),
+ BUILDIN_DEF(buyingstore,"i"),
// WoE SE
BUILDIN_DEF(agitstart2,""),
BUILDIN_DEF(agitend2,""),
diff --git a/src/map/skill.c b/src/map/skill.c
index ffb5a1826..160beeaef 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -5692,6 +5692,12 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, in
case ALL_WEWISH:
clif_skill_nodamage(src,bl,skillid,skilllv,1);
break;
+ case ALL_BUYING_STORE:
+ if( sd )
+ {// players only, skill allows 5 buying slots
+ clif_skill_nodamage(src, bl, skillid, skilllv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS));
+ }
+ break;
default:
ShowWarning("skill_castend_nodamage_id: Unknown skill used:%d\n",skillid);
clif_skill_nodamage(src,bl,skillid,skilllv,1);
diff --git a/src/map/trade.c b/src/map/trade.c
index ba01826f2..bedbb9451 100644
--- a/src/map/trade.c
+++ b/src/map/trade.c
@@ -138,8 +138,8 @@ void trade_tradeack(struct map_session_data *sd, int type)
}
//Check if you can start trade.
- if (sd->npc_id || sd->vender_id || sd->state.storage_flag ||
- tsd->npc_id || tsd->vender_id || tsd->state.storage_flag)
+ if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.storage_flag ||
+ tsd->npc_id || tsd->vender_id || tsd->state.buyingstore || tsd->state.storage_flag)
{ //Fail
clif_tradestart(sd, 2);
clif_tradestart(tsd, 2);
diff --git a/src/map/unit.c b/src/map/unit.c
index 7abfde12b..3f2b2094f 100644
--- a/src/map/unit.c
+++ b/src/map/unit.c
@@ -803,6 +803,7 @@ int unit_can_move(struct block_list *bl)
if (sd && (
pc_issit(sd) ||
sd->vender_id ||
+ sd->state.buyingstore ||
sd->state.blockedmove
))
return 0; //Can't move
@@ -1872,6 +1873,7 @@ int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file,
trade_tradecancel(sd);
if(sd->vender_id)
vending_closevending(sd);
+ buyingstore_close(sd);
if(sd->state.storage_flag == 1)
storage_storage_quit(sd,0);
else if (sd->state.storage_flag == 2)