Converted cash shop database to YAML (#6299)

Co-authored-by: Atemo <Atemo@users.noreply.github.com>
Co-authored-by: Aleos <aleos89@users.noreply.github.com>
This commit is contained in:
Lemongrass3110 2023-01-05 18:30:07 +01:00 committed by GitHub
parent 503b57dbef
commit 9dda166c0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 407 additions and 319 deletions

View File

@ -147,8 +147,6 @@ item_table: item_db
renewal-item_table: item_db_re
item2_table: item_db2
renewal-item2_table: item_db2_re
item_cash_table: item_cash_db
item_cash2_table: item_cash_db2
mob_table: mob_db
renewal-mob_table: mob_db_re
mob2_table: mob_db2

View File

@ -0,0 +1,33 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Item Cash Database
###########################################################################
#
# Item Cash Settings
#
###########################################################################
# - Tab Cash shop tab. Available tabs are New, Hot, Limited, Rental, Permanent, Scrolls, Consumables, Other, Sale.
# Items: List of possible items.
# - Item Item name.
# Price Item cost in cash points (#CASHPOINTS).
###########################################################################
Header:
Type: ITEM_CASH_DB
Version: 1

View File

@ -1,19 +0,0 @@
// Cash Shop Database
// Contains the items sold in the ingame cash shop.
//
// Structure of Database:
// Type,ItemID,Price
//
// Type:
// 0: New
// 1: Hot
// 2: Limited
// 3: Rental
// 4: Gear
// 5: Buff
// 6: Heal
// 7: Other
// 8: Sale
//
// Price:
// Item cost, in cash points (#CASHPOINTS).

37
db/item_cash.yml Normal file
View File

@ -0,0 +1,37 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Item Cash Database
###########################################################################
#
# Item Cash Settings
#
###########################################################################
# - Tab Cash shop tab. Available tabs are New, Hot, Limited, Rental, Permanent, Scrolls, Consumables, Other, Sale.
# Items: List of possible items.
# - Item Item name.
# Price Item cost in cash points (#CASHPOINTS).
###########################################################################
Header:
Type: ITEM_CASH_DB
Version: 1
Footer:
Imports:
- Path: db/import/item_cash.yml

View File

@ -1,19 +0,0 @@
// Cash Shop Database
// Contains the items sold in the ingame cash shop.
//
// Structure of Database:
// Type,ItemID,Price
//
// Type:
// 0: New
// 1: Hot
// 2: Limited
// 3: Rental
// 4: Gear
// 5: Buff
// 6: Heal
// 7: Other
// 8: Sale
//
// Price:
// Item cost, in cash points (#CASHPOINTS).

View File

@ -1,19 +0,0 @@
// Cash Shop Database
// Contains the items sold in the ingame cash shop.
//
// Structure of Database:
// Type,ItemID,Price
//
// Type:
// 0: New
// 1: Hot
// 2: Limited
// 3: Rental
// 4: Gear
// 5: Buff
// 6: Heal
// 7: Other
// 8: Sale
//
// Price:
// Item cost, in cash points (#CASHPOINTS).

12
doc/yaml/db/item_cash.yml Normal file
View File

@ -0,0 +1,12 @@
###########################################################################
# Item Cash Database
###########################################################################
#
# Item Cash Settings
#
###########################################################################
# - Tab Cash shop tab. Available tabs are New, Hot, Limited, Rental, Permanent, Scrolls, Consumables, Other, Sale.
# Items: List of possible items.
# - Item Item name.
# Price Item cost in cash points (#CASHPOINTS).
###########################################################################

View File

@ -21,8 +21,6 @@ Note: The schema name is defined in `conf/inter_athena.conf::log_db_db`.
If your server is setup to read SQL database data, import the following into the main schema:
Note: If `conf/inter_athena.conf::use_sql_db` is set to yes continue with these imports else these can be skipped. Not all files have to be imported, only the ones that apply to the same mode as the server being ran.
* item_cash_db.sql - Used for client's cash shop.
* item_cash_db2.sql - Used for client's cash shop (import).
* item_db.sql - Contains __pre-renewal__ item data table structure.
* item_db_equip.sql - Contains __pre-renewal__ equipment item data.
* item_db_etc.sql - Contains __pre-renewal__ etcetera item data.

View File

@ -1,7 +0,0 @@
DROP TABLE IF EXISTS `item_cash_db`;
CREATE TABLE `item_cash_db` (
`tab` smallint(6) NOT NULL,
`item_id` int(10) unsigned NOT NULL,
`price` mediumint(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`tab`,`item_id`)
) ENGINE=MyISAM;

View File

@ -1,7 +0,0 @@
DROP TABLE IF EXISTS `item_cash_db2`;
CREATE TABLE `item_cash_db2` (
`tab` smallint(6) NOT NULL,
`item_id` int(10) unsigned NOT NULL,
`price` mediumint(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`tab`,`item_id`)
) ENGINE=MyISAM;

View File

@ -81,6 +81,12 @@ typedef uint32 t_itemid;
#define MAX_AMOUNT 30000 ////Max amount of a single stacked item
#define MAX_ZENY INT_MAX ///Max zeny
#define MAX_BANK_ZENY SINT32_MAX ///Max zeny in Bank
#ifndef MAX_CASHPOINT
#define MAX_CASHPOINT INT_MAX
#endif
#ifndef MAX_KAFRAPOINT
#define MAX_KAFRAPOINT INT_MAX
#endif
#define MAX_FAME 1000000000 ///Max fame points
#define MAX_CART 100 ///Maximum item in cart
#define MAX_SKILL 1454 ///Maximum skill can be hold by Player, Homunculus, & Mercenary (skill list) AND skill_db limit

View File

@ -15,144 +15,132 @@
#include "pc.hpp" // s_map_session_data
#include "pet.hpp" // pet_create_egg
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.
* return
* 0 = failure
* 1 = success
*/
static bool cashshop_parse_dbrow(char* fields[], int columns, int current) {
uint16 tab = atoi(fields[0]);
t_itemid nameid = strtoul(fields[1], nullptr, 10);
uint32 price = atoi(fields[2]);
int j;
struct cash_item_data* cid;
const std::string CashShopDatabase::getDefaultLocation(){
return std::string( db_path ) + "/item_cash.yml";
}
if( !item_db.exists( nameid ) ){
ShowWarning( "cashshop_parse_dbrow: Invalid ID %u in line '%d', skipping...\n", nameid, current );
uint64 CashShopDatabase::parseBodyNode( const ryml::NodeRef& node ){
std::string name;
if( !this->asString( node, "Tab", name ) ){
return 0;
}
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 ){
ShowWarning( "cashshop_parse_dbrow: Invalid price %d in line '%d', skipping...\n", price, current );
std::string tab_constant = "CASHSHOP_TAB_" + name;
int64 constant;
if( !script_get_constant( tab_constant.c_str(), &constant ) ){
this->invalidWarning( node["Tab"], "Invalid tab %s, skipping.\n", tab_constant.c_str() );
return 0;
}
ARR_FIND( 0, cash_shop_items[tab].count, j, nameid == cash_shop_items[tab].item[j]->nameid );
if( j == cash_shop_items[tab].count ){
RECREATE( cash_shop_items[tab].item, struct cash_item_data *, ++cash_shop_items[tab].count );
CREATE( cash_shop_items[tab].item[ cash_shop_items[tab].count - 1], struct cash_item_data, 1 );
cid = cash_shop_items[tab].item[ cash_shop_items[tab].count - 1];
}else{
cid = cash_shop_items[tab].item[j];
if( constant < CASHSHOP_TAB_NEW || constant >= CASHSHOP_TAB_MAX ){
this->invalidWarning( node["Tab"], "Tab %" PRId64 " is out of range, skipping.\n", constant );
return 0;
}
cid->nameid = nameid;
cid->price = price;
cash_shop_defined = true;
e_cash_shop_tab tab = static_cast<e_cash_shop_tab>( constant );
std::shared_ptr<s_cash_item_tab> entry = this->find( tab );
bool exists = entry != nullptr;
if( !exists ){
if( !this->nodesExist( node, { "Items" } ) ){
return 0;
}
entry = std::make_shared<s_cash_item_tab>();
entry->tab = tab;
}
for( const ryml::NodeRef& it : node["Items"] ){
std::string item_name;
if( !this->asString( it, "Item", item_name ) ){
return 0;
}
std::shared_ptr<item_data> item = item_db.search_aegisname( item_name.c_str() );
if( item == nullptr ){
this->invalidWarning( it["Item"], "Cash item %s does not exist, skipping.\n", item_name.c_str() );
continue;
}
std::shared_ptr<s_cash_item> cash_item = nullptr;
bool cash_item_exists = false;
for( std::shared_ptr<s_cash_item> cash_it : entry->items ){
if( cash_it->nameid == item->nameid ){
cash_item = cash_it;
cash_item_exists = true;
break;
}
}
if( !cash_item_exists ){
cash_item = std::make_shared<s_cash_item>();
cash_item->nameid = item->nameid;
}
uint32 price;
if( !this->asUInt32( it, "Price", price ) ){
return 0;
}
if( price == 0 ){
this->invalidWarning( it["Price"], "Price has to be greater than zero." );
return 0;
}
if( price > MAX_CASHPOINT ){
this->invalidWarning( it["Price"], "Price has to be lower than MAX_CASHPOINT(%d).", MAX_CASHPOINT );
return 0;
}
cash_item->price = price;
if( !cash_item_exists ){
entry->items.push_back( cash_item );
}
}
if( !exists ){
this->put( tab, entry );
}
return 1;
}
/*
* Reads database from TXT format,
* parses lines and sends them to parse_dbrow.
*/
static void cashshop_read_db_txt( void ){
const char* dbsubpath[] = {
"",
"/" DBIMPORT,
};
int fi;
std::shared_ptr<s_cash_item> CashShopDatabase::findItemInTab( e_cash_shop_tab tab, t_itemid nameid ){
std::shared_ptr<s_cash_item_tab> cash_tab = this->find( tab );
for( fi = 0; fi < ARRAYLENGTH( dbsubpath ); ++fi ){
uint8 n1 = (uint8)(strlen(db_path)+strlen(dbsubpath[fi])+1);
uint8 n2 = (uint8)(strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[fi])+1);
char* dbsubpath1 = (char*)aMalloc(n1+1);
char* dbsubpath2 = (char*)aMalloc(n2+1);
if(fi==0) {
safesnprintf(dbsubpath1,n1,"%s%s",db_path,dbsubpath[fi]);
safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[fi]);
}
else {
safesnprintf(dbsubpath1,n1,"%s%s",db_path,dbsubpath[fi]);
safesnprintf(dbsubpath2,n1,"%s%s",db_path,dbsubpath[fi]);
}
sv_readdb(dbsubpath2, "item_cash_db.txt", ',', 3, 3, -1, &cashshop_parse_dbrow, fi > 0);
aFree(dbsubpath1);
aFree(dbsubpath2);
}
}
/*
* Reads database from SQL format,
* parses line and sends them to parse_dbrow.
*/
static int cashshop_read_db_sql( void ){
const char* cash_db_name[] = { item_cash_table, item_cash2_table };
int fi;
for( fi = 0; fi < ARRAYLENGTH( cash_db_name ); ++fi ){
uint32 lines = 0, count = 0;
if( SQL_ERROR == Sql_Query( mmysql_handle, "SELECT `tab`, `item_id`, `price` FROM `%s`", cash_db_name[fi] ) ){
Sql_ShowDebug( mmysql_handle );
continue;
}
while( SQL_SUCCESS == Sql_NextRow( mmysql_handle ) ){
char* str[3];
char dummy[256] = "";
int i;
++lines;
for( i = 0; i < 3; ++i ){
Sql_GetData( mmysql_handle, i, &str[i], NULL );
if( str[i] == NULL ){
str[i] = dummy;
}
}
if( !cashshop_parse_dbrow( str, 3, lines ) ) {
ShowError("cashshop_read_db_sql: Cannot process table '%s' at line '%d', skipping...\n", cash_db_name[fi], lines);
continue;
}
++count;
}
Sql_FreeResult( mmysql_handle );
ShowStatus( "Done reading '" CL_WHITE "%u" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'.\n", count, cash_db_name[fi] );
if( cash_tab == nullptr ){
return nullptr;
}
return 0;
for( std::shared_ptr<s_cash_item> cash_it : cash_tab->items ){
if( cash_it->nameid == nameid ){
return cash_it;
}
}
return nullptr;
}
CashShopDatabase cash_shop_db;
#if PACKETVER_SUPPORTS_SALES
static bool sale_parse_dbrow( char* fields[], int columns, int current ){
t_itemid nameid = strtoul(fields[0], nullptr, 10);
int start = atoi(fields[1]), end = atoi(fields[2]), amount = atoi(fields[3]), i;
int start = atoi(fields[1]), end = atoi(fields[2]), amount = atoi(fields[3]);
time_t now = time(NULL);
struct sale_item_data* sale_item = NULL;
@ -161,10 +149,9 @@ static bool sale_parse_dbrow( char* fields[], int columns, int 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 %u is not registered in the limited tab in line '%d', skipping...\n", nameid, current );
// Check if the item exists in the sales tab
if( cash_shop_db.findItemInTab( CASHSHOP_TAB_SALE, nameid ) == nullptr ){
ShowWarning( "sale_parse_dbrow: ID %u is not registered in the Sale tab in line '%d', skipping...\n", nameid, current );
return false;
}
@ -268,14 +255,8 @@ static TIMER_FUNC(sale_start_timer){
}
enum e_sale_add_result sale_add_item( t_itemid 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 ){
if( cash_shop_db.findItemInTab( CASHSHOP_TAB_SALE, nameid ) == nullptr ){
return SALE_ADD_FAILED;
}
@ -306,7 +287,7 @@ enum e_sale_add_result sale_add_item( t_itemid nameid, int32 count, time_t from,
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];
struct sale_item_data* sale_item = sale_items.item[sale_items.count - 1];
sale_item->nameid = nameid;
sale_item->start = from;
@ -421,23 +402,13 @@ void sale_notify_login( map_session_data* sd ){
}
#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 ){
cash_shop_db.load();
#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
@ -472,7 +443,7 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
uint32 totalweight = 0;
int i,new_;
if( sd == NULL || item_list == NULL || !cash_shop_defined){
if( sd == NULL || item_list == NULL ){
clif_cashshop_result( sd, 0, CASHSHOP_RESULT_ERROR_UNKNOWN );
return false;
}else if( sd->state.trading ){
@ -486,21 +457,19 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
t_itemid nameid = item_list[i].itemId;
uint32 quantity = item_list[i].amount;
uint16 tab = item_list[i].tab;
int j;
if( tab >= CASHSHOP_TAB_MAX ){
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
return false;
}
ARR_FIND( 0, cash_shop_items[tab].count, j, nameid == cash_shop_items[tab].item[j]->nameid || nameid == itemdb_viewid(cash_shop_items[tab].item[j]->nameid) );
std::shared_ptr<s_cash_item> cash_item = cash_shop_db.findItemInTab( static_cast<e_cash_shop_tab>( tab ), nameid );
if( j == cash_shop_items[tab].count ){
if( cash_item == nullptr ){
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKONWN_ITEM );
return false;
}
nameid = item_list[i].itemId = cash_shop_items[tab].item[j]->nameid; //item_avail replacement
std::shared_ptr<item_data> id = item_db.find(nameid);
@ -515,8 +484,8 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
return false;
}
#if PACKETVER_SUPPORTS_SALES
if( tab == CASHSHOP_TAB_SALE ){
#if PACKETVER_SUPPORTS_SALES
struct sale_item_data* sale = sale_find_item( nameid, true );
if( sale == NULL ){
@ -532,8 +501,11 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
clif_sale_amount( sale, &sd->bl, SELF );
return false;
}
}
#else
clif_cashshop_result( sd, nameid, CASHSHOP_RESULT_ERROR_UNKNOWN );
return false;
#endif
}
switch( pc_checkadditem( sd, nameid, quantity ) ){
case CHKADDITEM_EXIST:
@ -548,7 +520,7 @@ bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct P
return false;
}
totalcash += cash_shop_items[tab].item[j]->price * quantity;
totalcash += cash_item->price * quantity;
totalweight += itemdb_weight( nameid ) * quantity;
}
@ -664,19 +636,11 @@ void cashshop_reloaddb( void ){
* Closes all and cleanup.
*/
void do_final_cashshop( void ){
int tab, i;
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 ) );
cash_shop_db.clear();
#if PACKETVER_SUPPORTS_SALES
if( sale_items.count > 0 ){
for( i = 0; i < sale_items.count; i++ ){
for( int i = 0; i < sale_items.count; i++ ){
struct sale_item_data* it = sale_items.item[i];
if( it->timer_start != INVALID_TIMER ){
@ -702,10 +666,7 @@ void do_final_cashshop( void ){
/*
* Initializes cashshop class.
* return
* 0 : success
*/
void do_init_cashshop( void ){
cash_shop_defined = false;
cashshop_read_db();
}

View File

@ -4,9 +4,13 @@
#ifndef CASHSHOP_HPP
#define CASHSHOP_HPP
#include <memory> // std::shared_ptr
#include <vector> // std::vector
#include "../config/core.hpp"
#include "../common/cbasetypes.hpp" // uint16, uint32
#include "../common/database.hpp" // TypesafeYamlDatabase
#include "../common/mmo.hpp" // t_itemid
#include "../common/timer.hpp" // ShowWarning, ShowStatus
@ -17,20 +21,17 @@ void do_final_cashshop( void );
void cashshop_reloaddb( void );
bool cashshop_buylist( map_session_data* sd, uint32 kafrapoints, int n, struct PACKET_CZ_SE_PC_BUY_CASHITEM_LIST_sub* item_list );
// Taken from AEGIS
enum CASH_SHOP_TAB_CODE
{
// Taken from AEGIS (CASH_SHOP_TAB_CODE)
enum e_cash_shop_tab : uint16{
CASHSHOP_TAB_NEW = 0x0,
CASHSHOP_TAB_POPULAR,
CASHSHOP_TAB_HOT,
CASHSHOP_TAB_LIMITED,
CASHSHOP_TAB_RENTAL,
CASHSHOP_TAB_PERPETUITY,
CASHSHOP_TAB_BUFF,
CASHSHOP_TAB_RECOVERY,
CASHSHOP_TAB_ETC,
#if PACKETVER_SUPPORTS_SALES
CASHSHOP_TAB_PERMANENT,
CASHSHOP_TAB_SCROLLS,
CASHSHOP_TAB_CONSUMABLES,
CASHSHOP_TAB_OTHER,
CASHSHOP_TAB_SALE,
#endif
CASHSHOP_TAB_MAX
};
@ -52,18 +53,30 @@ enum CASHSHOP_BUY_RESULT
CASHSHOP_RESULT_ERROR_BUSY = 0xc,
};
struct cash_item_data{
struct s_cash_item{
t_itemid nameid;
uint32 price;
};
struct cash_item_db{
struct cash_item_data** item;
uint32 count;
struct s_cash_item_tab{
e_cash_shop_tab tab;
std::vector<std::shared_ptr<s_cash_item>> items;
};
extern struct cash_item_db cash_shop_items[CASHSHOP_TAB_MAX];
extern bool cash_shop_defined;
class CashShopDatabase : public TypesafeYamlDatabase<e_cash_shop_tab, s_cash_item_tab>{
public:
CashShopDatabase() : TypesafeYamlDatabase( "ITEM_CASH_DB", 1 ){
}
const std::string getDefaultLocation();
uint64 parseBodyNode( const ryml::NodeRef& node );
// Additional
std::shared_ptr<s_cash_item> findItemInTab( e_cash_shop_tab tab, t_itemid nameid );
};
extern CashShopDatabase cash_shop_db;
enum e_sale_add_result {
SALE_ADD_SUCCESS = 0,

View File

@ -17000,26 +17000,46 @@ void clif_parse_cashshop_close( int fd, map_session_data* sd ){
//0846 <tabid>.W (CZ_REQ_SE_CASH_TAB_CODE))
//08c0 <len>.W <openIdentity>.L <itemcount>.W (ZC_ACK_SE_CASH_ITEM_LIST2)
void clif_parse_CashShopReqTab(int fd, map_session_data *sd) {
#if PACKETVER_MAIN_NUM >= 20100824 || PACKETVER_RE_NUM >= 20100824 || defined(PACKETVER_ZERO)
struct PACKET_CZ_REQ_SE_CASH_TAB_CODE* p_in = (struct PACKET_CZ_REQ_SE_CASH_TAB_CODE*)RFIFOP( fd, 0 );
// TODO: most likely wrong answer packet. Most likely HEADER_ZC_ACK_SE_CASH_ITEM_LIST (0x847) would be correct [Lemongrass]
// [4144] packet exists only in 2011 and was dropped after
#if PACKETVER >= 20110222 && PACKETVER < 20120000
short tab = RFIFOW(fd, packet_db[RFIFOW(fd,0)].pos[0]);
int j;
std::shared_ptr<s_cash_item_tab> tab = cash_shop_db.find( static_cast<e_cash_shop_tab>( p_in->tab ) );
if( tab < 0 || tab >= CASHSHOP_TAB_MAX )
if( tab == nullptr ){
return;
WFIFOHEAD(fd, 10 + ( cash_shop_items[tab].count * 6 ) );
WFIFOW(fd, 0) = 0x8c0;
WFIFOW(fd, 2) = 10 + ( cash_shop_items[tab].count * 6 );
WFIFOL(fd, 4) = tab;
WFIFOW(fd, 8) = cash_shop_items[tab].count;
for( j = 0; j < cash_shop_items[tab].count; j++ ) {
WFIFOW(fd, 10 + ( 6 * j ) ) = client_nameid( cash_shop_items[tab].item[j]->nameid );
WFIFOL(fd, 12 + ( 6 * j ) ) = cash_shop_items[tab].item[j]->price;
}
WFIFOSET(fd, 10 + ( cash_shop_items[tab].count * 6 ));
// Skip empty tabs, the client only expects filled ones
if( tab->items.empty() ){
return;
}
#if !(PACKETVER_SUPPORTS_SALES)
if( tab->tab == CASHSHOP_TAB_SALE ){
return;
}
#endif
struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2* p = (struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2*)packet_buffer;
p->packetType = HEADER_ZC_ACK_SE_CASH_ITEM_LIST2;
p->packetLength = sizeof( struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2 );
p->tab = tab->tab;
p->count = 0;
for( std::shared_ptr<s_cash_item> item : tab->items ){
p->items[p->count].itemId = client_nameid( item->nameid );
p->items[p->count].price = item->price;
p->packetLength += sizeof( struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2_sub );
p->count++;
}
clif_send( p, p->packetLength, &sd->bl, SELF );
#endif
#endif
}
@ -17027,44 +17047,46 @@ void clif_parse_CashShopReqTab(int fd, map_session_data *sd) {
void clif_cashshop_list( map_session_data* sd ){
nullpo_retv( sd );
int fd = sd->fd;
for( const auto& pair : cash_shop_db ){
std::shared_ptr<s_cash_item_tab> tab = pair.second;
if( !session_isActive( fd ) ){
return;
}
for( int tab = CASHSHOP_TAB_NEW; tab < CASHSHOP_TAB_MAX; tab++ ){
// Skip empty tabs, the client only expects filled ones
if( cash_shop_items[tab].count == 0 ){
if( tab->items.empty() ){
continue;
}
int len = sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM ) + ( cash_shop_items[tab].count * sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM_sub ) );
WFIFOHEAD( fd, len );
struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *p = (struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *)WFIFOP( fd, 0 );
#if !(PACKETVER_SUPPORTS_SALES)
if( tab->tab == CASHSHOP_TAB_SALE ){
continue;
}
#endif
p->packetType = 0x8ca;
p->packetLength = len;
p->count = cash_shop_items[tab].count;
p->tabNum = tab;
struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *p = (struct PACKET_ZC_ACK_SCHEDULER_CASHITEM *)packet_buffer;
for( int i = 0; i < cash_shop_items[tab].count; i++ ){
p->items[i].itemId = client_nameid( cash_shop_items[tab].item[i]->nameid );
p->items[i].price = cash_shop_items[tab].item[i]->price;
p->packetType = HEADER_ZC_ACK_SCHEDULER_CASHITEM;
p->count = 0;
p->tabNum = tab->tab;
for( std::shared_ptr<s_cash_item> item : tab->items ){
p->items[p->count].itemId = client_nameid( item->nameid );
p->items[p->count].price = item->price;
#ifdef ENABLE_CASHSHOP_PREVIEW_PATCH
struct item_data* id = itemdb_search( cash_shop_items[tab].item[i]->nameid );
struct item_data* id = itemdb_search( item->nameid );
if( id == nullptr ){
p->items[i].location = 0;
p->items[i].viewSprite = 0;
p->items[p->count].location = 0;
p->items[p->count].viewSprite = 0;
}else{
p->items[i].location = pc_equippoint_sub( sd, id );
p->items[i].viewSprite = id->look;
p->items[p->count].location = pc_equippoint_sub( sd, id );
p->items[p->count].viewSprite = id->look;
}
#endif
p->count++;
}
WFIFOSET( fd, len );
p->packetLength = sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM ) + p->count * sizeof( struct PACKET_ZC_ACK_SCHEDULER_CASHITEM_sub );
clif_send( p, p->packetLength, &sd->bl, SELF );
}
}
@ -21178,7 +21200,7 @@ void clif_parse_sale_close(int fd, map_session_data* sd) {
/// 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( map_session_data* sd, struct cash_item_data* item ){
void clif_sale_search_reply( map_session_data* sd, std::shared_ptr<s_cash_item> item ){
#if PACKETVER_SUPPORTS_SALES
struct PACKET_ZC_ACK_CASH_BARGAIN_SALE_ITEM_INFO p;
@ -21218,19 +21240,13 @@ void clif_parse_sale_search( int fd, map_session_data* sd ){
std::shared_ptr<item_data> id = item_db.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
if( id == nullptr ){
clif_sale_search_reply( sd, nullptr );
return;
}
// not found
clif_sale_search_reply( sd, NULL );
clif_sale_search_reply( sd, cash_shop_db.findItemInTab( CASHSHOP_TAB_SALE, id->nameid ) );
#endif
}

View File

@ -1747,6 +1747,10 @@
parseable_packet(0x0843,6,clif_parse_GMRemove2,2);
#endif
#if PACKETVER_MAIN_NUM >= 20100824 || PACKETVER_RE_NUM >= 20100824 || defined(PACKETVER_ZERO)
parseable_packet( HEADER_CZ_REQ_SE_CASH_TAB_CODE, sizeof( struct PACKET_CZ_REQ_SE_CASH_TAB_CODE ), clif_parse_CashShopReqTab, 0 );
#endif
// 2010-11-24aRagexeRE
#if PACKETVER >= 20101124
parseable_packet(0x0288,-1,clif_parse_npccashshop_buy,2,4,8,10);
@ -1783,7 +1787,6 @@
parseable_packet(0x0439,8,clif_parse_UseItem,2,4);
packet(0x08d2,10);
packet(0x08d1,7);
parseable_packet(0x0846,4,clif_parse_CashShopReqTab,2); //2011-07-18
#endif
// 2011-11-02aRagexe

View File

@ -333,7 +333,7 @@
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\homun_skill_tree.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homun_skill_tree.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\homunculus_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homunculus_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\instance_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\instance_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_cash_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_cash_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_cash.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_cash.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_combos.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_combos.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_enchant.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_enchant.yml')" />

View File

@ -70,8 +70,6 @@ int db_use_sqldbs = 0;
char barter_table[32] = "barter";
char buyingstores_table[32] = "buyingstores";
char buyingstore_items_table[32] = "buyingstore_items";
char item_cash_table[32] = "item_cash_db";
char item_cash2_table[32] = "item_cash_db2";
#ifdef RENEWAL
char item_table[32] = "item_db_re";
char item2_table[32] = "item_db2_re";
@ -4162,10 +4160,6 @@ int inter_config_read(const char *cfgName)
safestrncpy(mob_skill_table,w2,sizeof(mob_skill_table));
else if(strcmpi(w1,"mob_skill2_table")==0)
safestrncpy(mob_skill2_table,w2,sizeof(mob_skill2_table));
else if( strcmpi( w1, "item_cash_table" ) == 0 )
safestrncpy( item_cash_table, w2, sizeof(item_cash_table) );
else if( strcmpi( w1, "item_cash2_table" ) == 0 )
safestrncpy( item_cash2_table, w2, sizeof(item_cash2_table) );
else if( strcmpi( w1, "vending_db" ) == 0 )
safestrncpy( vendings_table, w2, sizeof(vendings_table) );
else if( strcmpi( w1, "vending_items_table" ) == 0 )

View File

@ -453,6 +453,28 @@ struct PACKET_ZC_PARTY_JOIN_REQ_ACK_FROM_MASTER{
uint32 refused;
} __attribute__((packed));
struct PACKET_CZ_REQ_SE_CASH_TAB_CODE{
int16 packetType;
int16 tab;
} __attribute__((packed));
struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2_sub{
#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
uint32 itemId;
#else
uint16 itemId;
#endif
int32 price;
} __attribute__((packed));
struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2{
int16 packetType;
int16 packetLength;
uint32 tab;
int16 count;
struct PACKET_ZC_ACK_SE_CASH_ITEM_LIST2_sub items[];
} __attribute__((packed));
// NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute
#if !defined( sun ) && ( !defined( __NETBSD__ ) || __NetBSD_Version__ >= 600000000 )
#pragma pack( pop )
@ -489,6 +511,9 @@ DEFINE_PACKET_HEADER(ZC_CASH_ITEM_DELETE, 0x299)
DEFINE_PACKET_HEADER(ZC_NOTIFY_BIND_ON_EQUIP, 0x2d3)
DEFINE_PACKET_HEADER(ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER, 0x824)
DEFINE_PACKET_HEADER(CZ_SSILIST_ITEM_CLICK, 0x83c)
DEFINE_PACKET_HEADER(CZ_REQ_SE_CASH_TAB_CODE, 0x846)
DEFINE_PACKET_HEADER(ZC_ACK_SE_CASH_ITEM_LIST2, 0x8c0)
DEFINE_PACKET_HEADER(ZC_ACK_SCHEDULER_CASHITEM, 0x8ca)
DEFINE_PACKET_HEADER(ZC_CLEAR_DIALOG, 0x8d6)
DEFINE_PACKET_HEADER(ZC_ENTRY_QUEUE_INIT, 0x90e);
DEFINE_PACKET_HEADER(ZC_BANKING_CHECK, 0x9a6)

View File

@ -5623,7 +5623,7 @@ int pc_paycash(map_session_data *sd, int price, int points, e_log_pick_type type
int cash;
nullpo_retr(-1,sd);
points = cap_value(points, 0, MAX_ZENY); //prevent command UB
points = cap_value(points, 0, MAX_KAFRAPOINT); //prevent command UB
cash = price-points;
@ -5669,14 +5669,14 @@ int pc_getcash(map_session_data *sd, int cash, int points, e_log_pick_type type)
nullpo_retr(-1,sd);
cash = cap_value(cash, 0, MAX_ZENY); //prevent command UB
points = cap_value(points, 0, MAX_ZENY); //prevent command UB
cash = cap_value(cash, 0, MAX_CASHPOINT); //prevent command UB
points = cap_value(points, 0, MAX_KAFRAPOINT); //prevent command UB
if( cash > 0 )
{
if( cash > MAX_ZENY-sd->cashPoints )
if( cash > MAX_CASHPOINT-sd->cashPoints )
{
ShowWarning("pc_getcash: Cash point overflow (cash=%d, have cash=%d, account_id=%d, char_id=%d).\n", cash, sd->cashPoints, sd->status.account_id, sd->status.char_id);
cash = MAX_ZENY-sd->cashPoints;
cash = MAX_CASHPOINT-sd->cashPoints;
}
pc_setaccountreg(sd, add_str(CASHPOINT_VAR), sd->cashPoints+cash);
@ -5693,10 +5693,10 @@ int pc_getcash(map_session_data *sd, int cash, int points, e_log_pick_type type)
if( points > 0 )
{
if( points > MAX_ZENY-sd->kafraPoints )
if( points > MAX_KAFRAPOINT-sd->kafraPoints )
{
ShowWarning("pc_getcash: Kafra point overflow (points=%d, have points=%d, account_id=%d, char_id=%d).\n", points, sd->kafraPoints, sd->status.account_id, sd->status.char_id);
points = MAX_ZENY-sd->kafraPoints;
points = MAX_KAFRAPOINT-sd->kafraPoints;
}
pc_setaccountreg(sd, add_str(KAFRAPOINT_VAR), sd->kafraPoints+points);
@ -10278,16 +10278,16 @@ bool pc_setparam(map_session_data *sd,int64 type,int64 val_tmp)
if (val < 0)
return false;
if (!sd->state.connect_new)
log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_CASH, -(sd->cashPoints - cap_value(val, 0, MAX_ZENY)));
sd->cashPoints = cap_value(val, 0, MAX_ZENY);
log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_CASH, -(sd->cashPoints - cap_value(val, 0, MAX_CASHPOINT)));
sd->cashPoints = cap_value(val, 0, MAX_CASHPOINT);
pc_setaccountreg(sd, add_str(CASHPOINT_VAR), sd->cashPoints);
return true;
case SP_KAFRAPOINTS:
if (val < 0)
return false;
if (!sd->state.connect_new)
log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_KAFRA, -(sd->kafraPoints - cap_value(val, 0, MAX_ZENY)));
sd->kafraPoints = cap_value(val, 0, MAX_ZENY);
log_cash(sd, LOG_TYPE_SCRIPT, LOG_CASH_TYPE_KAFRA, -(sd->kafraPoints - cap_value(val, 0, MAX_KAFRAPOINT)));
sd->kafraPoints = cap_value(val, 0, MAX_KAFRAPOINT);
pc_setaccountreg(sd, add_str(KAFRAPOINT_VAR), sd->kafraPoints);
return true;
case SP_PCDIECOUNTER:

View File

@ -35,6 +35,7 @@
#include "atcommand.hpp"
#include "battle.hpp"
#include "battleground.hpp"
#include "cashshop.hpp"
#include "channel.hpp"
#include "chat.hpp"
#include "chrif.hpp"

View File

@ -9927,6 +9927,17 @@
export_constant(ENCHANTGRADE_A);
export_constant(MAX_ENCHANTGRADE);
/* cash shop tabs */
export_constant(CASHSHOP_TAB_NEW);
export_constant(CASHSHOP_TAB_HOT);
export_constant(CASHSHOP_TAB_LIMITED);
export_constant(CASHSHOP_TAB_RENTAL);
export_constant(CASHSHOP_TAB_PERMANENT);
export_constant(CASHSHOP_TAB_SCROLLS);
export_constant(CASHSHOP_TAB_CONSUMABLES);
export_constant(CASHSHOP_TAB_OTHER);
export_constant(CASHSHOP_TAB_SALE);
#undef export_constant
#undef export_constant2
#undef export_parameter

View File

@ -540,6 +540,12 @@ bool Csv2YamlTool::initialize( int argc, char* argv[] ){
return false;
}
if( !process( "ITEM_CASH_DB", 1, root_paths, "item_cash_db", []( const std::string& path, const std::string& name_ext ) -> bool {
return sv_readdb( path.c_str(), name_ext.c_str(), ',', 3, 3, -1, &cashshop_parse_dbrow, false );
}, "item_cash" ) ){
return 0;
}
// TODO: add implementations ;-)
return true;
@ -4918,6 +4924,52 @@ static bool itemdb_read_combos(const char* file) {
return true;
}
// Copied and adjusted from cashshop.cpp
static bool cashshop_parse_dbrow( char* fields[], int columns, int current ){
uint16 tab = atoi( fields[0] );
t_itemid nameid = strtoul( fields[1], nullptr, 10 );
uint32 price = atoi( fields[2] );
std::string* item_name = util::umap_find( aegis_itemnames, nameid );
if( item_name == nullptr ){
ShowWarning( "cashshop_parse_dbrow: Invalid item id %u.\n", nameid );
return false;
}
if( tab >= CASHSHOP_TAB_MAX ){
ShowWarning( "cashshop_parse_dbrow: Invalid tab %d in line '%d', skipping...\n", tab, current );
return false;
}else if( price < 1 ){
ShowWarning( "cashshop_parse_dbrow: Invalid price %d in line '%d', skipping...\n", price, current );
return false;
}
const char* constant_ptr = constant_lookup( tab, "CASHSHOP_TAB_" );
if( constant_ptr == nullptr ){
ShowError( "cashshop_parse_dbrow: CASHSHOP_TAB constant for tab %hu was not found, skipping...\n", tab );
return false;
}
std::string constant = constant_ptr;
constant.erase( 0, 13 );
constant = name2Upper( constant );
body << YAML::BeginMap;
body << YAML::Key << "Tab" << YAML::Value << constant;
body << YAML::Key << "Items";
body << YAML::BeginSeq;
body << YAML::BeginMap;
body << YAML::Key << "Item" << YAML::Value << *item_name;
body << YAML::Key << "Price" << YAML::Value << price;
body << YAML::EndMap;
body << YAML::EndSeq;
body << YAML::EndMap;
return true;
}
int main( int argc, char *argv[] ){
return main_core<Csv2YamlTool>( argc, argv );
}

View File

@ -530,5 +530,6 @@ static bool mercenary_readdb(char* str[], int columns, int current);
static bool pc_readdb_skilltree(char* str[], int columns, int current);
static bool pc_readdb_skilltree_yaml(void);
static bool itemdb_read_combos(const char* file);
static bool cashshop_parse_dbrow( char* fields[], int columns, int current );
#endif /* CSV2YAML_HPP */

View File

@ -40,6 +40,7 @@
#include "../map/achievement.hpp"
#include "../map/battle.hpp"
#include "../map/battleground.hpp"
#include "../map/cashshop.hpp"
#include "../map/channel.hpp"
#include "../map/chat.hpp"
#include "../map/date.hpp"

View File

@ -37,6 +37,7 @@
#include "../map/achievement.hpp"
#include "../map/battle.hpp"
#include "../map/battleground.hpp"
#include "../map/cashshop.hpp"
#include "../map/channel.hpp"
#include "../map/chat.hpp"
#include "../map/date.hpp"

View File

@ -8,8 +8,6 @@ set MYSQL_PWD=%DB_ROOTPW%
%MYSQL% -u %DB_ROOT% -e "CREATE DATABASE %DB_NAME%;"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\main.sql"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\logs.sql"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_cash_db.sql"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_cash_db2.sql"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_db.sql"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_db_usable.sql"
%MYSQL% -u %DB_ROOT% %DB_NAME% -e "source sql-files\item_db_equip.sql"

View File

@ -17,8 +17,6 @@ DB_PASS=ragnarok
mysql -u $DB_ROOT -p$DB_ROOTPW -e "CREATE DATABASE $DB_NAME;" || aborterror "Unable to create database."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/main.sql || aborterror "Unable to import main database."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/logs.sql || aborterror "Unable to import logs database."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_cash_db.sql || aborterror "Unable to import cash item table."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_cash_db2.sql || aborterror "Unable to import cash item 2 table."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_db.sql || aborterror "Unable to import pre-renewal item table structure."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_db_usable.sql || aborterror "Unable to import pre-renewal usable item table."
mysql -u $DB_ROOT -p$DB_ROOTPW $DB_NAME < sql-files/item_db_equip.sql || aborterror "Unable to import pre-renewal equip item table."