Initial release of the cash shop sales (#1825)

Added a permission for the cashshop sales

Thanks to @Angelic234 and everyone else who tested this feature while it was sleeping waiting in a pull request.
Thanks to @aleos89, @secretdataz and @lighta for reviewing and commenting.
This commit is contained in:
Lemongrass3110 2017-02-15 14:04:18 +01:00 committed by GitHub
parent 7cf081c24b
commit aaa4ea919e
15 changed files with 749 additions and 20 deletions

View File

@ -293,6 +293,7 @@ groups: (
item_unconditional: false
bypass_stat_onclone: true
bypass_max_stat: true
cashshop_sale: true
/* all_permission: true */
}
}

View File

@ -143,6 +143,7 @@ renewal-mob_skill_table: mob_skill_db_re
mob_skill2_table: mob_skill_db2
renewal-mob_skill2_table: mob_skill_db2_re
mapreg_table: mapreg
sales_table: sales
vending_table: vendings
vending_items_table: vending_items
market_table: market

View File

@ -13,6 +13,7 @@
// 5: Buff
// 6: Heal
// 7: Other
// 8: Sale
//
// Price:
// Item cost, in cash points (#CASHPOINTS).

View File

@ -2322,7 +2322,6 @@ packet_keys: 0x631C511C,0x111C111C,0x111C111C // [Shakto]
0x08A4,36,storagepassword,2:4:20
//New Packets
//0x097E,12 //ZC_UPDATE_RANKING_POINT
0x09B4,6,dull,0 //Cash Shop - Special Tab
0x09CE,102,itemmonster,2
0x09D4,2,npcshopclosed,0
//NPC Market
@ -2336,6 +2335,19 @@ packet_keys: 0x631C511C,0x111C111C,0x111C111C // [Shakto]
0x098A,-1
0x098D,-1,clanchat,2:4
0x098E,-1
// Sale
0x09AC,-1,salesearch,2:4:8
0x09AD,8
0x09AE,17,saleadd,2:6:8:12:16
0x09AF,4
0x09B0,8,saleremove,2:6
0x09B1,4
0x09B2,8
0x09B3,4
0x09B4,6,saleopen,2
0x09BC,6,saleclose,2
0x09C3,8,salerefresh,2:6
0x09C4,8
// New Packet
0x097A,-1 // ZC_ALL_QUEST_LIST2

View File

@ -13,6 +13,7 @@
// 5: Buff
// 6: Heal
// 7: Other
// 8: Sale
//
// Price:
// Item cost, in cash points (#CASHPOINTS).

View File

@ -13,6 +13,7 @@
// 5: Buff
// 6: Heal
// 7: Other
// 8: Sale
//
// Price:
// Item cost, in cash points (#CASHPOINTS).

View File

@ -813,6 +813,18 @@ CREATE TABLE IF NOT EXISTS `mercenary_owner` (
PRIMARY KEY (`char_id`)
) ENGINE=MyISAM;
-- ----------------------------
-- Table structure for `sales`
-- ----------------------------
CREATE TABLE IF NOT EXISTS `sales` (
`nameid` smallint(5) unsigned NOT NULL,
`start` datetime NOT NULL,
`end` datetime NOT NULL,
`amount` int(11) NOT NULL,
PRIMARY KEY (`nameid`)
) ENGINE=MyISAM;
--
-- Table structure for table `sc_data`
--

View File

@ -0,0 +1,11 @@
-- ----------------------------
-- Table structure for `sales`
-- ----------------------------
CREATE TABLE IF NOT EXISTS `sales` (
`nameid` smallint(5) unsigned NOT NULL,
`start` datetime NOT NULL,
`end` datetime NOT NULL,
`amount` int(11) NOT NULL,
PRIMARY KEY (`nameid`)
) ENGINE=MyISAM;

View File

@ -31,6 +31,9 @@
/// Check if the client needs delete_date as remaining time and not the actual delete_date (actually it was tested for clients since 2013)
#define PACKETVER_CHAR_DELETEDATE (PACKETVER > 20130000 && PACKETVER < 20141016) || PACKETVER >= 20150826
// Check if the specified packetvresion supports the cashshop sale system
#define PACKETVER_SUPPORTS_SALES PACKETVER>=20131223
///Remove/Comment this line to disable sc_data saving. [Skotlex]
#define ENABLE_SC_SAVING
/** Remove/Comment this line to disable server-side hot-key saving support [Skotlex]

View File

@ -11,11 +11,15 @@
#include <string.h> // memset
#include <stdlib.h> // atoi
struct cash_item_db cash_shop_items[CASHSHOP_TAB_SEARCH];
struct cash_item_db cash_shop_items[CASHSHOP_TAB_MAX];
#if PACKETVER_SUPPORTS_SALES
struct sale_item_db sale_items;
#endif
bool cash_shop_defined = false;
extern char item_cash_table[32];
extern char item_cash2_table[32];
extern char sales_table[32];
/*
* Reads one line from database and assigns it to RAM.
@ -35,7 +39,7 @@ static bool cashshop_parse_dbrow(char* fields[], int columns, int current) {
return 0;
}
if( tab > CASHSHOP_TAB_SEARCH ){
if( tab >= CASHSHOP_TAB_MAX ){
ShowWarning( "cashshop_parse_dbrow: Invalid tab %d in line '%d', skipping...\n", tab, current );
return 0;
}else if( price < 1 ){
@ -139,16 +143,314 @@ static int cashshop_read_db_sql( void ){
return 0;
}
#if PACKETVER_SUPPORTS_SALES
static bool sale_parse_dbrow( char* fields[], int columns, int current ){
unsigned short nameid = atoi(fields[0]);
int start = atoi(fields[1]), end = atoi(fields[2]), amount = atoi(fields[3]), i;
time_t now = time(NULL);
struct sale_item_data* sale_item = NULL;
if( !itemdb_exists(nameid) ){
ShowWarning( "sale_parse_dbrow: Invalid ID %hu in line '%d', skipping...\n", nameid, current );
return false;
}
ARR_FIND( 0, cash_shop_items[CASHSHOP_TAB_SALE].count, i, cash_shop_items[CASHSHOP_TAB_SALE].item[i]->nameid == nameid );
if( i == cash_shop_items[CASHSHOP_TAB_SALE].count ){
ShowWarning( "sale_parse_dbrow: ID %hu is not registered in the limited tab in line '%d', skipping...\n", nameid, current );
return false;
}
// Check if the end is after the start
if( start >= end ){
ShowWarning( "sale_parse_dbrow: Sale for item %hu was ignored, because the timespan was not correct.\n", nameid );
return false;
}
// Check if it is already in the past
if( end < now ){
ShowWarning( "sale_parse_dbrow: An outdated sale for item %hu was ignored.\n", nameid );
return false;
}
// Check if there is already an entry
sale_item = sale_find_item(nameid,false);
if( sale_item == NULL ){
RECREATE(sale_items.item, struct sale_item_data *, ++sale_items.count);
CREATE(sale_items.item[sale_items.count - 1], struct sale_item_data, 1);
sale_item = sale_items.item[sale_items.count - 1];
}
sale_item->nameid = nameid;
sale_item->start = start;
sale_item->end = end;
sale_item->amount = amount;
sale_item->timer_start = INVALID_TIMER;
sale_item->timer_end = INVALID_TIMER;
return true;
}
static void sale_read_db_sql( void ){
uint32 lines = 0, count = 0;
if( SQL_ERROR == Sql_Query( mmysql_handle, "SELECT `nameid`, UNIX_TIMESTAMP(`start`), UNIX_TIMESTAMP(`end`), `amount` FROM `%s` WHERE `end` > now()", sales_table ) ){
Sql_ShowDebug(mmysql_handle);
return;
}
while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ){
char* str[4];
int i;
lines++;
for( i = 0; i < 4; i++ ){
Sql_GetData( mmysql_handle, i, &str[i], NULL );
if( str[i] == NULL ){
str[i] = "";
}
}
if( !sale_parse_dbrow( str, 4, lines ) ){
ShowError( "sale_read_db_sql: Cannot process table '%s' at line '%d', skipping...\n", sales_table, lines );
continue;
}
count++;
}
Sql_FreeResult(mmysql_handle);
ShowStatus( "Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, sales_table );
}
static int sale_end_timer( int tid, unsigned int tick, int id, intptr_t data ){
struct sale_item_data* sale_item = (struct sale_item_data*)data;
// Remove the timer so the sale end is not sent out again
delete_timer( sale_item->timer_end, sale_end_timer );
sale_item->timer_end = INVALID_TIMER;
clif_sale_end( sale_item, NULL, ALL_CLIENT );
sale_remove_item( sale_item->nameid );
return 1;
}
static int sale_start_timer( int tid, unsigned int tick, int id, intptr_t data ){
struct sale_item_data* sale_item = (struct sale_item_data*)data;
clif_sale_start( sale_item, NULL, ALL_CLIENT );
clif_sale_amount( sale_item, NULL, ALL_CLIENT );
// Clear the start timer
if( sale_item->timer_start != INVALID_TIMER ){
delete_timer( sale_item->timer_start, sale_start_timer );
sale_item->timer_start = INVALID_TIMER;
}
// Init sale end
sale_item->timer_end = add_timer( gettick() + (unsigned int)( sale_item->end - time(NULL) ) * 1000, sale_end_timer, 0, (intptr_t)sale_item );
return 1;
}
enum e_sale_add_result sale_add_item( uint16 nameid, int32 count, time_t from, time_t to ){
int i;
struct sale_item_data* sale_item;
// Check if the item exists in the sales tab
ARR_FIND( 0, cash_shop_items[CASHSHOP_TAB_SALE].count, i, cash_shop_items[CASHSHOP_TAB_SALE].item[i]->nameid == nameid );
// Item does not exist in the sales tab
if( i == cash_shop_items[CASHSHOP_TAB_SALE].count ){
return SALE_ADD_FAILED;
}
// Adding a sale in the past is not possible
if( from < time(NULL) ){
return SALE_ADD_FAILED;
}
// The end has to be after the start
if( from >= to ){
return SALE_ADD_FAILED;
}
// Amount has to be positive - this should be limited from the client too
if( count == 0 ){
return SALE_ADD_FAILED;
}
// Check if a sale of this item already exists
if( sale_find_item(nameid, false) ){
return SALE_ADD_DUPLICATE;
}
if( SQL_ERROR == Sql_Query(mmysql_handle, "INSERT INTO `%s`(`nameid`,`start`,`end`,`amount`) VALUES ( '%d', FROM_UNIXTIME(%d), FROM_UNIXTIME(%d), '%d' )", sales_table, nameid, (uint32)from, (uint32)to, count) ){
Sql_ShowDebug(mmysql_handle);
return SALE_ADD_FAILED;
}
RECREATE(sale_items.item, struct sale_item_data *, ++sale_items.count);
CREATE(sale_items.item[sale_items.count - 1], struct sale_item_data, 1);
sale_item = sale_items.item[sale_items.count - 1];
sale_item->nameid = nameid;
sale_item->start = from;
sale_item->end = to;
sale_item->amount = count;
sale_item->timer_start = add_timer( gettick() + (unsigned int)(from - time(NULL)) * 1000, sale_start_timer, 0, (intptr_t)sale_item );
sale_item->timer_end = INVALID_TIMER;
return SALE_ADD_SUCCESS;
}
bool sale_remove_item( uint16 nameid ){
struct sale_item_data* sale_item;
int i;
// Check if there is an entry for this item id
if( !sale_find_item(nameid, false) ){
return false;
}
// Delete it from the database
if( SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `nameid` = '%d'", sales_table, nameid ) ){
Sql_ShowDebug(mmysql_handle);
return false;
}
// Check if the sale is currently running
sale_item = sale_find_item(nameid, true);
if( sale_item != NULL && sale_item->timer_end != INVALID_TIMER ){
// Notify all clients that the sale has ended
clif_sale_end(sale_item, NULL, ALL_CLIENT);
}
if( sale_item->timer_start != INVALID_TIMER ){
delete_timer(sale_item->timer_start, sale_start_timer);
sale_item->timer_start = INVALID_TIMER;
}
if( sale_item->timer_end != INVALID_TIMER ){
delete_timer(sale_item->timer_end, sale_end_timer);
sale_item->timer_end = INVALID_TIMER;
}
// Find the original pointer in the array
ARR_FIND( 0, sale_items.count, i, sale_items.item[i] == sale_item );
// Is there still any entry left?
if( --sale_items.count > 0 ){
// fill the hole by moving the rest
for( ; i < sale_items.count; i++ ){
memcpy( sale_items.item[i], sale_items.item[i + 1], sizeof(struct sale_item_data) );
}
aFree(sale_items.item[i]);
RECREATE(sale_items.item, struct sale_item_data *, sale_items.count);
}else{
aFree(sale_items.item[0]);
aFree(sale_items.item);
sale_items.item = NULL;
}
return true;
}
struct sale_item_data* sale_find_item( uint16 nameid, bool onsale ){
int i;
struct sale_item_data* sale_item;
time_t now = time(NULL);
ARR_FIND( 0, sale_items.count, i, sale_items.item[i]->nameid == nameid );
// No item with the specified item id was found
if( i == sale_items.count ){
return NULL;
}
sale_item = sale_items.item[i];
// No need to check any further
if( !onsale ){
return sale_item;
}
// The sale is in the future
if( sale_items.item[i]->start > now ){
return NULL;
}
// The sale was in the past
if( sale_items.item[i]->end < now ){
return NULL;
}
// The amount has been used up already
if( sale_items.item[i]->amount == 0 ){
return NULL;
}
// Return the sale item
return sale_items.item[i];
}
void sale_notify_login( struct map_session_data* sd ){
int i;
for( i = 0; i < sale_items.count; i++ ){
if( sale_items.item[i]->timer_end != INVALID_TIMER ){
clif_sale_start( sale_items.item[i], &sd->bl, SELF );
clif_sale_amount( sale_items.item[i], &sd->bl, SELF );
}
}
}
#endif
/*
* Determines whether to read TXT or SQL database
* based on 'db_use_sqldbs' in conf/map_athena.conf.
*/
static void cashshop_read_db( void ){
#if PACKETVER_SUPPORTS_SALES
int i;
time_t now = time(NULL);
#endif
if( db_use_sqldbs ){
cashshop_read_db_sql();
} else {
cashshop_read_db_txt();
}
#if PACKETVER_SUPPORTS_SALES
sale_read_db_sql();
// Clean outdated sales
if( SQL_ERROR == Sql_Query(mmysql_handle, "DELETE FROM `%s` WHERE `end` < FROM_UNIXTIME(%d)", sales_table, (uint32)now ) ){
Sql_ShowDebug(mmysql_handle);
}
// Init next sale start, if there is any
for( i = 0; i < sale_items.count; i++ ){
struct sale_item_data* it = sale_items.item[i];
if( it->start > now ){
it->timer_start = add_timer( gettick() + (unsigned int)( it->start - time(NULL) ) * 1000, sale_start_timer, 0, (intptr_t)it );
}else{
sale_start_timer( 0, gettick(), 0, (intptr_t)it );
}
}
#endif
}
/** Attempts to purchase a cashshop item from the list.
@ -164,6 +466,9 @@ bool cashshop_buylist( struct map_session_data* sd, uint32 kafrapoints, int n, u
uint32 totalcash = 0;
uint32 totalweight = 0;
int i,new_;
#if PACKETVER_SUPPORTS_SALES
struct sale_item_data* sale;
#endif
if( sd == NULL || item_list == NULL || !cash_shop_defined){
clif_cashshop_result( sd, 0, CASHSHOP_RESULT_ERROR_UNKNOWN );
@ -178,10 +483,10 @@ bool cashshop_buylist( struct map_session_data* sd, uint32 kafrapoints, int n, u
for( i = 0; i < n; ++i ){
unsigned short nameid = *( item_list + i * 5 );
uint32 quantity = *( item_list + i * 5 + 2 );
uint16 tab = *( item_list + i * 5 + 4 );
uint8 tab = (uint8)*( item_list + i * 5 + 4 );
int j;
if( tab > CASHSHOP_TAB_SEARCH ){
if( tab >= CASHSHOP_TAB_MAX ){
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
return false;
}
@ -203,6 +508,32 @@ bool cashshop_buylist( struct map_session_data* sd, uint32 kafrapoints, int n, u
quantity = *( item_list + i * 5 + 2 ) = 1;
}
if( quantity > 99 ){
// Client blocks buying more than 99 items of the same type at the same time, this means someone forged a packet with a higher quantity
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
return false;
}
#if PACKETVER_SUPPORTS_SALES
if( tab == CASHSHOP_TAB_SALE ){
sale = sale_find_item( nameid, true );
if( sale == NULL ){
// Client tried to buy an item from sale that was not even on sale
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
return false;
}
if( sale->amount < quantity ){
// Client tried to buy a higher quantity than is available
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
// Maybe he did not get refreshed in time -> do it now
clif_sale_amount( sale, &sd->bl, SELF );
return false;
}
}
#endif
switch( pc_checkadditem( sd, nameid, quantity ) ){
case CHKADDITEM_EXIST:
break;
@ -236,6 +567,7 @@ bool cashshop_buylist( struct map_session_data* sd, uint32 kafrapoints, int n, u
for( i = 0; i < n; ++i ){
unsigned short nameid = *( item_list + i * 5 );
uint32 quantity = *( item_list + i * 5 + 2 );
uint16 tab = *(item_list + i * 5 + 4);
struct item_data *id = itemdb_search(nameid);
if (!id)
@ -270,6 +602,24 @@ bool cashshop_buylist( struct map_session_data* sd, uint32 kafrapoints, int n, u
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_RUNE_OVERCOUNT );
return false;
}
#if PACKETVER_SUPPORTS_SALES
if( tab == CASHSHOP_TAB_SALE ){
uint32 new_amount = sale->amount - get_amt;
if( new_amount == 0 ){
sale_remove_item(sale->nameid);
}else{
if( SQL_ERROR == Sql_Query( mmysql_handle, "UPDATE `%s` SET `amount` = '%d' WHERE `nameid` = '%d'", sales_table, new_amount, nameid ) ){
Sql_ShowDebug(mmysql_handle);
}
sale->amount = new_amount;
clif_sale_amount(sale, NULL, ALL_CLIENT);
}
}
#endif
}
}
}
@ -293,13 +643,38 @@ void cashshop_reloaddb( void ){
void do_final_cashshop( void ){
int tab, i;
for( tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_SEARCH; tab++ ){
for( tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_MAX; tab++ ){
for( i = 0; i < cash_shop_items[tab].count; i++ ){
aFree( cash_shop_items[tab].item[i] );
}
aFree( cash_shop_items[tab].item );
}
memset( cash_shop_items, 0, sizeof( cash_shop_items ) );
#if PACKETVER_SUPPORTS_SALES
if( sale_items.count > 0 ){
for( i = 0; i < sale_items.count; i++ ){
struct sale_item_data* it = sale_items.item[i];
if( it->timer_start != INVALID_TIMER ){
delete_timer( it->timer_start, sale_start_timer );
it->timer_start = INVALID_TIMER;
}
if( it->timer_end != INVALID_TIMER ){
delete_timer( it->timer_end, sale_end_timer );
it->timer_end = INVALID_TIMER;
}
aFree(it);
}
aFree(sale_items.item);
sale_items.item = NULL;
sale_items.count = 0;
}
#endif
}
/*

View File

@ -16,14 +16,17 @@ bool cashshop_buylist( struct map_session_data* sd, uint32 kafrapoints, int n, u
enum CASH_SHOP_TAB_CODE
{
CASHSHOP_TAB_NEW = 0x0,
CASHSHOP_TAB_POPULAR = 0x1,
CASHSHOP_TAB_LIMITED = 0x2,
CASHSHOP_TAB_RENTAL = 0x3,
CASHSHOP_TAB_PERPETUITY = 0x4,
CASHSHOP_TAB_BUFF = 0x5,
CASHSHOP_TAB_RECOVERY = 0x6,
CASHSHOP_TAB_ETC = 0x7,
CASHSHOP_TAB_SEARCH = 0x8
CASHSHOP_TAB_POPULAR,
CASHSHOP_TAB_LIMITED,
CASHSHOP_TAB_RENTAL,
CASHSHOP_TAB_PERPETUITY,
CASHSHOP_TAB_BUFF,
CASHSHOP_TAB_RECOVERY,
CASHSHOP_TAB_ETC,
#if PACKETVER_SUPPORTS_SALES
CASHSHOP_TAB_SALE,
#endif
CASHSHOP_TAB_MAX
};
// PACKET_ZC_SE_PC_BUY_CASHITEM_RESULT
@ -54,7 +57,39 @@ struct cash_item_db{
uint32 count;
};
extern struct cash_item_db cash_shop_items[CASHSHOP_TAB_SEARCH];
extern struct cash_item_db cash_shop_items[CASHSHOP_TAB_MAX];
extern bool cash_shop_defined;
enum e_sale_add_result {
SALE_ADD_SUCCESS = 0,
SALE_ADD_FAILED = 1,
SALE_ADD_DUPLICATE = 2
};
struct sale_item_data{
// Data
uint16 nameid;
time_t start;
time_t end;
uint32 amount;
// Timers
int timer_start;
int timer_end;
};
struct sale_item_db{
struct sale_item_data** item;
uint32 count;
};
#if PACKETVER_SUPPORTS_SALES
extern struct sale_item_db sale_items;
struct sale_item_data* sale_find_item(uint16 nameid, bool onsale);
enum e_sale_add_result sale_add_item(uint16 nameid, int32 count, time_t from, time_t to);
bool sale_remove_item(uint16 nameid);
void sale_notify_login( struct map_session_data* sd );
#endif
#endif /* _CASHSHOP_H_ */

View File

@ -15403,7 +15403,7 @@ void clif_parse_CashShopReqTab(int fd, struct map_session_data *sd) {
short tab = RFIFOW(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
int j;
if( tab < 0 || tab > CASHSHOP_TAB_SEARCH )
if( tab < 0 || tab >= CASHSHOP_TAB_MAX )
return;
WFIFOHEAD(fd, 10 + ( cash_shop_items[tab].count * 6 ) );
@ -15425,7 +15425,7 @@ void clif_parse_CashShopReqTab(int fd, struct map_session_data *sd) {
void clif_cashshop_list( int fd ){
int tab;
for( tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_SEARCH; tab++ ){
for( tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_MAX; tab++ ){
int length = 8 + cash_shop_items[tab].count * 6;
int i, offset;
@ -15448,6 +15448,9 @@ void clif_cashshop_list( int fd ){
void clif_parse_cashshop_list_request( int fd, struct map_session_data* sd ){
if( !sd->status.cashshop_sent ) {
clif_cashshop_list( fd );
#if PACKETVER_SUPPORTS_SALES
sale_notify_login(sd);
#endif
sd->status.cashshop_sent = true;
}
}
@ -18872,6 +18875,261 @@ void clif_hat_effect_single( struct map_session_data* sd, uint16 effectId, bool
#endif
}
/// Notify the client that a sale has started
/// 09b2 <item id>.W <remaining time>.L (ZC_NOTIFY_BARGAIN_SALE_SELLING)
void clif_sale_start( struct sale_item_data* sale_item, struct block_list* bl, enum send_target target ){
#if PACKETVER_SUPPORTS_SALES
unsigned char buf[8];
WBUFW(buf, 0) = 0x9b2;
WBUFW(buf, 2) = sale_item->nameid;
WBUFL(buf, 4) = (uint32)(sale_item->end - time(NULL)); // time in S
clif_send(buf, 8, bl, target);
#endif
}
/// Notify the clien that a sale has ended
/// 09b3 <item id>.W (ZC_NOTIFY_BARGAIN_SALE_CLOSE)
void clif_sale_end( struct sale_item_data* sale_item, struct block_list* bl, enum send_target target ){
#if PACKETVER_SUPPORTS_SALES
unsigned char buf[4];
WBUFW(buf, 0) = 0x9b3;
WBUFW(buf, 2) = sale_item->nameid;
clif_send(buf, 4, bl, target);
#endif
}
/// Update the remaining amount of a sale item.
/// 09c4 <item id>.W <amount>.L (ZC_ACK_COUNT_BARGAIN_SALE_ITEM)
void clif_sale_amount( struct sale_item_data* sale_item, struct block_list* bl, enum send_target target ){
#if PACKETVER_SUPPORTS_SALES
unsigned char buf[8];
WBUFW(buf, 0) = 0x9c4;
WBUFW(buf, 2) = sale_item->nameid;
WBUFL(buf, 4) = sale_item->amount;
clif_send(buf, 8, bl, target);
#endif
}
/// The client requested a refresh of the current remaining count of a sale item
/// 09ac <account id>.L <item id>.W (CZ_REQ_CASH_BARGAIN_SALE_ITEM_INFO)
void clif_parse_sale_refresh( int fd, struct map_session_data* sd ){
#if PACKETVER_SUPPORTS_SALES
struct sale_item_data* sale;
if( RFIFOL(fd, 2) != sd->status.account_id ){
return;
}
sale = sale_find_item( RFIFOW(fd, 6), true );
if( sale == NULL ){
return;
}
clif_sale_amount(sale, &sd->bl, SELF);
#endif
}
/// Opens the sale administration window on the client
/// 09b5 (ZC_OPEN_BARGAIN_SALE_TOOL)
void clif_sale_open( struct map_session_data* sd ){
#if PACKETVER_SUPPORTS_SALES
int fd = sd->fd;
// TODO: do we want state tracking?
WFIFOHEAD(fd, 2);
WFIFOW(fd, 0) = 0x9b5;
WFIFOSET(fd, 2);
#endif
}
/// Client request to open the sale administration window.
/// This is sent by /limitedsale
/// 09b4 <account id>.L (CZ_OPEN_BARGAIN_SALE_TOOL)
void clif_parse_sale_open( int fd, struct map_session_data* sd ){
#if PACKETVER_SUPPORTS_SALES
nullpo_retv(sd);
if( RFIFOL(fd, 2) != sd->status.account_id ){
return;
}
if( !pc_has_permission( sd, PC_PERM_CASHSHOP_SALE ) ){
return;
}
clif_sale_open(sd);
#endif
}
/// Closes the sale administration window on the client.
/// 09bd (ZC_CLOSE_BARGAIN_SALE_TOOL)
void clif_sale_close(struct map_session_data* sd) {
#if PACKETVER_SUPPORTS_SALES
int fd = sd->fd;
WFIFOHEAD(fd, 2);
WFIFOW(fd, 0) = 0x9bd;
WFIFOSET(fd, 2);
#endif
}
/// Client request to close the sale administration window.
/// 09bc (CZ_CLOSE_BARGAIN_SALE_TOOL)
void clif_parse_sale_close(int fd, struct map_session_data* sd) {
#if PACKETVER_SUPPORTS_SALES
nullpo_retv(sd);
if( RFIFOL(fd, 2) != sd->status.account_id ){
return;
}
// TODO: do we want state tracking?
clif_sale_close(sd);
#endif
}
/// Reply to a item search request for item sale administration.
/// 09ad <result>.W <item id>.W <price>.L (ZC_ACK_CASH_BARGAIN_SALE_ITEM_INFO)
void clif_sale_search_reply( struct map_session_data* sd, struct cash_item_data* item ){
#if PACKETVER_SUPPORTS_SALES
int fd = sd->fd;
WFIFOHEAD(fd, 10);
WFIFOW(fd, 0) = 0x9ad;
if( item != NULL ){
WFIFOW(fd, 2) = 0;
WFIFOW(fd, 4) = item->nameid;
WFIFOL(fd, 6) = item->price;
}else{
WFIFOW(fd, 2) = 1;
WFIFOW(fd, 4) = 0;
WFIFOL(fd, 6) = 0;
}
WFIFOSET(fd, 10);
#endif
}
/// Search request for an item sale administration.
/// 09ac <length>.W <account id>.L <item name>.?B (CZ_REQ_CASH_BARGAIN_SALE_ITEM_INFO)
void clif_parse_sale_search( int fd, struct map_session_data* sd ){
#if PACKETVER_SUPPORTS_SALES
char item_name[ITEM_NAME_LENGTH];
struct item_data *id = NULL;
nullpo_retv(sd);
if( RFIFOL(fd, 4) != sd->status.account_id ){
return;
}
if( !pc_has_permission( sd, PC_PERM_CASHSHOP_SALE ) ){
return;
}
safestrncpy( item_name, RFIFOCP(fd, 8), min(RFIFOW(fd, 2) - 7, ITEM_NAME_LENGTH) );
id = itemdb_searchname(item_name);
if( id ){
int i;
for( i = 0; i < cash_shop_items[CASHSHOP_TAB_SALE].count; i++ ){
if( cash_shop_items[CASHSHOP_TAB_SALE].item[i]->nameid == id->nameid ){
clif_sale_search_reply( sd, cash_shop_items[CASHSHOP_TAB_SALE].item[i] );
return;
}
}
}
// not found
clif_sale_search_reply( sd, NULL );
#endif
}
/// Reply if an item was successfully put on sale or not.
/// 09af <result>.W (ZC_ACK_APPLY_BARGAIN_SALE_ITEM)
void clif_sale_add_reply( struct map_session_data* sd, enum e_sale_add_result result ){
#if PACKETVER_SUPPORTS_SALES
int fd = sd->fd;
WFIFOHEAD(fd, 4);
WFIFOW(fd, 0) = 0x9af;
WFIFOW(fd, 2) = (uint16)result;
WFIFOSET(fd, 4);
#endif
}
/// A client request to put an item on sale.
/// 09ae <account id>.L <item id>.W <amount>.L <start time>.L <hours on sale>.B (CZ_REQ_APPLY_BARGAIN_SALE_ITEM)
void clif_parse_sale_add( int fd, struct map_session_data* sd ){
#if PACKETVER_SUPPORTS_SALES
int32 count;
int16 nameid;
int startTime;
int endTime;
uint8 sellingHours;
nullpo_retv(sd);
if( RFIFOL(fd, 2) != sd->status.account_id ){
return;
}
if( !pc_has_permission( sd, PC_PERM_CASHSHOP_SALE ) ){
return;
}
nameid = RFIFOW(fd, 6);
count = RFIFOL(fd, 8);
startTime = RFIFOL(fd, 12);
sellingHours = RFIFOB(fd, 16);
endTime = startTime + sellingHours * 60 * 60;
clif_sale_add_reply( sd, sale_add_item(nameid,count,startTime,endTime) );
#endif
}
/// Reply to an item removal from sale.
/// 09b1 <result>.W (ZC_ACK_REMOVE_BARGAIN_SALE_ITEM)
void clif_sale_remove_reply( struct map_session_data* sd, bool failed ){
#if PACKETVER_SUPPORTS_SALES
int fd = sd->fd;
WFIFOHEAD(fd, 4);
WFIFOW(fd, 0) = 0x9b1;
WFIFOW(fd, 2) = failed;
WFIFOSET(fd, 4);
#endif
}
/// Request to remove an item from sale.
/// 09b0 <account id>.L <item id>.W (CZ_REQ_REMOVE_BARGAIN_SALE_ITEM)
void clif_parse_sale_remove( int fd, struct map_session_data* sd ){
#if PACKETVER_SUPPORTS_SALES
nullpo_retv(sd);
if( RFIFOL(fd, 2) != sd->status.account_id ){
return;
}
if( !pc_has_permission( sd, PC_PERM_CASHSHOP_SALE ) ){
return;
}
clif_sale_remove_reply(sd, !sale_remove_item(RFIFOW(fd, 6)));
#endif
}
/*==========================================
* Main client packet processing function
*------------------------------------------*/
@ -19258,10 +19516,10 @@ void packetdb_readdb(bool reload)
//#0x0980
7, 0, 0, 29, 28, 0, 0, 0, 6, 2, -1, 0, 0, -1, -1, 0,
31, 0, 0, 0, 0, 0, 0, -1, 8, 11, 9, 8, 0, 0, 0, 22,
0, 0, 0, 0, 0, 0, 12, 10, 14, 10, 14, 6, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 6, 4, 6, 4, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 12, 10, 14, 10, 14, 6, -1, 8, 17, 4,
8, 4, 8, 4, 6, 0, 6, 4, 6, 4, 0, 0, 6, 0, 0, 0,
//#0x09C0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 17, 0, 0,102, 0,
0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 23, 17, 0, 0,102, 0,
0, 0, 0, 0, 2, 0, -1, -1, 2, 0, 0, -1, -1, -1, 0, 7,
0, 0, 0, 0, 0, 18, 22, 3, 11, 0, 11, -1, 0, 3, 11, 0,
0, 11, 12, 11, 0, 0, 0, 75, -1,143, 0, 0, 0, -1, -1, -1,
@ -19517,6 +19775,13 @@ void packetdb_readdb(bool reload)
{ clif_parse_SelectCart, "selectcart" },
// Clan System
{ clif_parse_clan_chat, "clanchat" },
// Sale
{ clif_parse_sale_search, "salesearch" },
{ clif_parse_sale_add, "saleadd" },
{ clif_parse_sale_remove, "saleremove" },
{ clif_parse_sale_open, "saleopen" },
{ clif_parse_sale_close, "saleclose" },
{ clif_parse_sale_refresh, "salerefresh" },
{NULL,NULL}
};
struct {

View File

@ -31,6 +31,7 @@ struct battleground_data;
struct quest;
struct party_booking_ad_info;
enum e_party_member_withdraw;
struct sale_item_data;
#include <stdarg.h>
enum { // packet DB
@ -983,6 +984,11 @@ void clif_clan_message(struct clan *clan,const char *mes,int len);
void clif_clan_onlinecount( struct clan* clan );
void clif_clan_leave( struct map_session_data* sd );
// Bargain Tool
void clif_sale_start(struct sale_item_data* sale_item, struct block_list* bl, enum send_target target);
void clif_sale_end(struct sale_item_data* sale_item, struct block_list* bl, enum send_target target);
void clif_sale_amount(struct sale_item_data* sale_item, struct block_list* bl, enum send_target target);
/**
* Color Table
**/

View File

@ -74,6 +74,7 @@ char mob2_table[32] = "mob_db2";
char mob_skill_table[32] = "mob_skill_db";
char mob_skill2_table[32] = "mob_skill_db2";
#endif
char sales_table[32] = "sales";
char vendings_table[32] = "vendings";
char vending_items_table[32] = "vending_items";
char market_table[32] = "market";
@ -4022,6 +4023,8 @@ int inter_config_read(char *cfgName)
strcpy(roulette_table, w2);
else if (strcmpi(w1, "market_table") == 0)
strcpy(market_table, w2);
else if (strcmpi(w1, "sales_table") == 0)
strcpy(sales_table, w2);
else
//Map Server SQL DB
if(strcmpi(w1,"map_server_ip")==0)

View File

@ -49,6 +49,7 @@ enum e_pc_permission {
PC_PERM_ENABLE_COMMAND = 0x01000000,
PC_PERM_BYPASS_STAT_ONCLONE = 0x02000000,
PC_PERM_BYPASS_MAX_STAT = 0x04000000,
PC_PERM_CASHSHOP_SALE = 0x08000000,
//.. add other here
PC_PERM_ALLPERMISSION = 0xFFFFFFFF,
};
@ -84,6 +85,7 @@ static const struct {
{ "command_enable",PC_PERM_ENABLE_COMMAND },
{ "bypass_stat_onclone",PC_PERM_BYPASS_STAT_ONCLONE },
{ "bypass_max_stat",PC_PERM_BYPASS_MAX_STAT },
{ "cashshop_sale", PC_PERM_CASHSHOP_SALE },
{ "all_permission", PC_PERM_ALLPERMISSION },
};