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:
parent
7cf081c24b
commit
aaa4ea919e
@ -293,6 +293,7 @@ groups: (
|
||||
item_unconditional: false
|
||||
bypass_stat_onclone: true
|
||||
bypass_max_stat: true
|
||||
cashshop_sale: true
|
||||
/* all_permission: true */
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -13,6 +13,7 @@
|
||||
// 5: Buff
|
||||
// 6: Heal
|
||||
// 7: Other
|
||||
// 8: Sale
|
||||
//
|
||||
// Price:
|
||||
// Item cost, in cash points (#CASHPOINTS).
|
||||
|
@ -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
|
||||
|
@ -13,6 +13,7 @@
|
||||
// 5: Buff
|
||||
// 6: Heal
|
||||
// 7: Other
|
||||
// 8: Sale
|
||||
//
|
||||
// Price:
|
||||
// Item cost, in cash points (#CASHPOINTS).
|
||||
|
@ -13,6 +13,7 @@
|
||||
// 5: Buff
|
||||
// 6: Heal
|
||||
// 7: Other
|
||||
// 8: Sale
|
||||
//
|
||||
// Price:
|
||||
// Item cost, in cash points (#CASHPOINTS).
|
||||
|
@ -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`
|
||||
--
|
||||
|
11
sql-files/upgrades/upgrade_20170215.sql
Normal file
11
sql-files/upgrades/upgrade_20170215.sql
Normal 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;
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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_ */
|
||||
|
275
src/map/clif.c
275
src/map/clif.c
@ -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 {
|
||||
|
@ -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
|
||||
**/
|
||||
|
@ -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)
|
||||
|
@ -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 },
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user