Forráskód Böngészése

Initial release of adventure agency (#7224)

Thanks to @Dia, @OptimusM, @Balferian and @ecdarreola
Lemongrass3110 2 éve
szülő
commit
39d5a02a31

+ 2 - 1
conf/inter_athena.conf

@@ -158,6 +158,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
+partybookings_table: party_bookings
 sales_table: sales
 vending_table: vendings
 vending_items_table: vending_items
@@ -166,7 +167,7 @@ roulette_table: db_roulette
 guild_storage_log: guild_storage_log
 
 // Web Database Tables
-// NOTE: The web server reads the login (login) and char (guild) tables, so it needs
+// NOTE: The web server reads the login (login) and char (party,guild) tables and map (party_bookings), so it needs
 //       the ability to connect to those databases.
 guild_emblems: guild_emblems
 user_configs: user_configs

+ 21 - 0
sql-files/main.sql

@@ -973,6 +973,27 @@ CREATE TABLE IF NOT EXISTS `party` (
   PRIMARY KEY  (`party_id`)
 ) ENGINE=MyISAM;
 
+--
+-- Table structure for table `party_bookings`
+--
+
+CREATE TABLE IF NOT EXISTS `party_bookings` (
+  `world_name` varchar(32) NOT NULL,
+  `account_id` int(11) unsigned NOT NULL,
+  `char_id` int(11) unsigned NOT NULL,
+  `char_name` varchar(23) NOT NULL,
+  `purpose` smallint(5) unsigned NOT NULL DEFAULT '0',
+  `assist` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `damagedealer` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `healer` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `tanker` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `minimum_level` smallint(5) unsigned NOT NULL,
+  `maximum_level` smallint(5) unsigned NOT NULL,
+  `comment` varchar(255) NOT NULL DEFAULT '',
+  `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`world_name`, `account_id`, `char_id`)
+) ENGINE=MyISAM;
+
 --
 -- Table structure for table `pet`
 --

+ 20 - 0
sql-files/upgrades/upgrade_20221220.sql

@@ -0,0 +1,20 @@
+--
+-- Table structure for table `party_bookings`
+--
+
+CREATE TABLE IF NOT EXISTS `party_bookings` (
+  `world_name` varchar(32) NOT NULL,
+  `account_id` int(11) unsigned NOT NULL,
+  `char_id` int(11) unsigned NOT NULL,
+  `char_name` varchar(23) NOT NULL,
+  `purpose` smallint(5) unsigned NOT NULL DEFAULT '0',
+  `assist` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `damagedealer` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `healer` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `tanker` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `minimum_level` smallint(5) unsigned NOT NULL,
+  `maximum_level` smallint(5) unsigned NOT NULL,
+  `comment` varchar(255) NOT NULL DEFAULT '',
+  `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`world_name`, `account_id`, `char_id`)
+) ENGINE=MyISAM;

+ 120 - 0
src/map/clif.cpp

@@ -24853,6 +24853,126 @@ void clif_dynamicnpc_result( map_session_data& sd, e_dynamicnpc_result result ){
 #endif
 }
 
+void clif_partybooking_ask( map_session_data* sd, map_session_data* joining_sd ){
+#if PACKETVER >= 20191204
+	struct PACKET_ZC_PARTY_REQ_MASTER_TO_JOIN p = { 0 };
+
+	p.packetType = HEADER_ZC_PARTY_REQ_MASTER_TO_JOIN;
+	p.CID = joining_sd->status.char_id;
+	p.AID = joining_sd->status.account_id;
+	safestrncpy( p.name, joining_sd->status.name, NAME_LENGTH );
+	p.x = joining_sd->status.base_level;
+	p.y = joining_sd->status.class_;
+
+	clif_send( &p, sizeof( p ), &sd->bl, SELF );
+#endif
+}
+
+void clif_parse_partybooking_join( int fd, map_session_data* sd ){
+#if PACKETVER >= 20191204
+	struct PACKET_CZ_PARTY_REQ_MASTER_TO_JOIN* p = (struct PACKET_CZ_PARTY_REQ_MASTER_TO_JOIN*)RFIFOP( fd, 0 );
+
+	// Character is already in a party
+	if( sd->status.party_id != 0 ){
+		return;
+	}
+
+	map_session_data* tsd = map_charid2sd( p->CID );
+
+	// Target player is offline
+	if( tsd == nullptr ){
+		return;
+	}
+
+	if( tsd->status.account_id != p->AID ){
+		return;
+	}
+
+	struct s_party_booking_requirement requirement;
+
+	if( !party_booking_load( tsd->status.account_id, tsd->status.char_id, &requirement ) ){
+		return;
+	}
+
+	if( sd->status.base_level < requirement.minimum_level ){
+		return;
+	}
+
+	if( sd->status.base_level > requirement.maximum_level ){
+		return;
+	}
+
+	// Already requested to join this party
+	if( util::vector_exists( tsd->party_booking_requests, sd->status.char_id ) ){
+		return;
+	}
+
+	// Store information that the player tried to join the party
+	tsd->party_booking_requests.push_back( sd->status.char_id );
+
+	clif_partybooking_ask( tsd, sd );
+#endif
+}
+
+void clif_partybooking_reply( map_session_data* sd, map_session_data* party_leader_sd, bool accepted ){
+#if PACKETVER >= 20191204
+	struct PACKET_ZC_PARTY_JOIN_REQ_ACK_FROM_MASTER p = { 0 };
+
+	if( party_leader_sd->status.party_id == 0 ){
+		return;
+	}
+
+	struct party_data* party = party_search( party_leader_sd->status.party_id );
+
+	if( party == nullptr ){
+		return;
+	}
+
+	p.packetType = HEADER_ZC_PARTY_JOIN_REQ_ACK_FROM_MASTER;
+	safestrncpy( p.player_name, party_leader_sd->status.name, NAME_LENGTH );
+	safestrncpy( p.party_name, party->party.name, NAME_LENGTH );
+	p.AID = party_leader_sd->status.account_id;
+	p.refused = !accepted;
+
+	clif_send( &p, sizeof( p ), &sd->bl, SELF );
+#endif
+}
+
+void clif_parse_partybooking_reply( int fd, map_session_data* sd ){
+#if PACKETVER >= 20191204
+	struct PACKET_CZ_PARTY_REQ_ACK_MASTER_TO_JOIN* p = (struct PACKET_CZ_PARTY_REQ_ACK_MASTER_TO_JOIN*)RFIFOP( fd, 0 );
+
+	map_session_data* tsd = map_charid2sd( p->CID );
+
+	// Target player is offline
+	if( tsd == nullptr ){
+		return;
+	}
+
+	if( tsd->status.account_id != p->AID ){
+		return;
+	}
+
+	// Check if the player even requested to join the party
+	if( !util::vector_exists( sd->party_booking_requests, p->CID ) ){
+		return;
+	}
+
+	util::vector_erase_if_exists( sd->party_booking_requests, p->CID );
+
+	// Only party leaders can reply
+	if( !party_isleader( sd ) ){
+		return;
+	}
+
+	if( p->accept ){
+		party_join( tsd, sd->status.party_id );
+	}
+
+	clif_partybooking_reply( tsd, sd, p->accept );
+#endif
+}
+
 /*==========================================
  * Main client packet processing function
  *------------------------------------------*/

+ 5 - 0
src/map/clif_packetdb.hpp

@@ -2409,6 +2409,11 @@
 	parseable_packet( HEADER_CZ_REQ_ITEMREPAIR2, sizeof( struct PACKET_CZ_REQ_ITEMREPAIR2 ), clif_parse_RepairItem, 0 );
 #endif
 
+#if PACKETVER >= 20191204
+	parseable_packet( HEADER_CZ_PARTY_REQ_MASTER_TO_JOIN, sizeof( struct PACKET_CZ_PARTY_REQ_MASTER_TO_JOIN ), clif_parse_partybooking_join, 0 );
+	parseable_packet( HEADER_CZ_PARTY_REQ_ACK_MASTER_TO_JOIN, sizeof( struct PACKET_CZ_PARTY_REQ_ACK_MASTER_TO_JOIN ), clif_parse_partybooking_reply, 0 );
+#endif
+
 #if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
 	parseable_packet( HEADER_CZ_UNCONFIRMED_TSTATUS_UP, sizeof( PACKET_CZ_UNCONFIRMED_TSTATUS_UP ), clif_parse_traitstatus_up, 0 );
 	parseable_packet( HEADER_CZ_GRADE_ENCHANT_SELECT_EQUIPMENT, sizeof( struct PACKET_CZ_GRADE_ENCHANT_SELECT_EQUIPMENT ), clif_parse_enchantgrade_add, 0 );

+ 3 - 0
src/map/map.cpp

@@ -91,6 +91,7 @@ char sales_table[32] = "sales";
 char vendings_table[32] = "vendings";
 char vending_items_table[32] = "vending_items";
 char market_table[32] = "market";
+char partybookings_table[32] = "party_bookings";
 char roulette_table[32] = "db_roulette";
 char guild_storage_log_table[32] = "guild_storage_log";
 
@@ -4169,6 +4170,8 @@ int inter_config_read(const char *cfgName)
 			safestrncpy( vendings_table, w2, sizeof(vendings_table) );
 		else if( strcmpi( w1, "vending_items_table" ) == 0 )
 			safestrncpy(vending_items_table, w2, sizeof(vending_items_table));
+		else if( strcmpi( w1, "partybookings_table" ) == 0 )
+			safestrncpy(partybookings_table, w2, sizeof(partybookings_table));
 		else if( strcmpi(w1, "roulette_table") == 0)
 			safestrncpy(roulette_table, w2, sizeof(roulette_table));
 		else if (strcmpi(w1, "market_table") == 0)

+ 1 - 0
src/map/map.hpp

@@ -1268,6 +1268,7 @@ extern char mob_skill2_table[32];
 extern char vendings_table[32];
 extern char vending_items_table[32];
 extern char market_table[32];
+extern char partybookings_table[32];
 extern char roulette_table[32];
 extern char guild_storage_log_table[32];
 

+ 34 - 0
src/map/packets.hpp

@@ -423,6 +423,36 @@ struct PACKET_ZC_DYNAMICNPC_CREATE_RESULT{
 	int32 result;
 } __attribute__((packed));
 
+struct PACKET_CZ_PARTY_REQ_MASTER_TO_JOIN{
+	int16 packetType;
+	uint32 CID;
+	uint32 AID;
+} __attribute__((packed));
+
+struct PACKET_ZC_PARTY_REQ_MASTER_TO_JOIN{
+	int16 packetType;
+	uint32 CID;
+	uint32 AID;
+	char name[NAME_LENGTH];
+	uint16 x;
+	uint16 y;
+} __attribute__((packed));
+
+struct PACKET_CZ_PARTY_REQ_ACK_MASTER_TO_JOIN{
+	int16 packetType;
+	uint32 CID;
+	uint32 AID;
+	uint8 accept;
+} __attribute__((packed));
+
+struct PACKET_ZC_PARTY_JOIN_REQ_ACK_FROM_MASTER{
+	int16 packetType;
+	char player_name[NAME_LENGTH];
+	char party_name[NAME_LENGTH];
+	uint32 AID;
+	uint32 refused;
+} __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 )
@@ -480,6 +510,10 @@ DEFINE_PACKET_HEADER(CZ_REQ_STYLE_CHANGE, 0xa46)
 DEFINE_PACKET_HEADER(ZC_STYLE_CHANGE_RES, 0xa47)
 DEFINE_PACKET_HEADER(CZ_REQ_STYLE_CLOSE, 0xa48)
 DEFINE_PACKET_HEADER(ZC_GROUP_ISALIVE, 0xab2)
+DEFINE_PACKET_HEADER(CZ_PARTY_REQ_MASTER_TO_JOIN, 0x0ae6)
+DEFINE_PACKET_HEADER(ZC_PARTY_REQ_MASTER_TO_JOIN, 0x0ae7)
+DEFINE_PACKET_HEADER(CZ_PARTY_REQ_ACK_MASTER_TO_JOIN, 0x0af8)
+DEFINE_PACKET_HEADER(ZC_PARTY_JOIN_REQ_ACK_FROM_MASTER, 0x0afa)
 DEFINE_PACKET_HEADER(CZ_REQ_STYLE_CHANGE2, 0xafc)
 DEFINE_PACKET_HEADER(ZC_REMOVE_EFFECT, 0x0b0d)
 DEFINE_PACKET_HEADER(CZ_UNCONFIRMED_TSTATUS_UP, 0x0b24)

+ 100 - 0
src/map/party.cpp

@@ -18,6 +18,7 @@
 #include "achievement.hpp"
 #include "atcommand.hpp"	//msg_txt()
 #include "battle.hpp"
+#include "chrif.hpp" // charserver_name
 #include "clif.hpp"
 #include "instance.hpp"
 #include "intif.hpp"
@@ -452,6 +453,105 @@ int party_invite(map_session_data *sd,map_session_data *tsd)
 	return 1;
 }
 
+bool party_isleader( map_session_data* sd ){
+	if( sd == nullptr ){
+		return false;
+	}
+
+	if( sd->status.party_id == 0 ){
+		return false;
+	}
+
+	struct party_data* party = party_search( sd->status.party_id );
+
+	if( party == nullptr ){
+		return false;
+	}
+
+	for( int i = 0; i < MAX_PARTY; i++ ){
+		if( party->party.member[i].char_id == sd->status.char_id ){
+			return party->party.member[i].leader != 0;
+		}
+	}
+
+	return false;
+}
+
+void party_join( map_session_data* sd, int party_id ){
+	nullpo_retv( sd );
+
+	// Player is in a party already now
+	if( sd->status.party_id != 0 ){
+		return;
+	}
+
+	// Player is already associated with a party
+	if( sd->party_creating || sd->party_joining ){
+		return;
+	}
+
+	struct party_data* party = party_search( party_id );
+
+	if( party == nullptr ){
+		return;
+	}
+
+	int i;
+
+	if( battle_config.block_account_in_same_party ){
+		ARR_FIND( 0, MAX_PARTY, i, party->party.member[i].account_id == sd->status.account_id );
+
+		if( i < MAX_PARTY ){
+			// Player is in the party with a different character already
+			return;
+		}
+	}
+
+	// Confirm if there is an open slot in the party
+	ARR_FIND( 0, MAX_PARTY, i, party->party.member[i].account_id == 0 );
+
+	if( i == MAX_PARTY ){
+		// Party is already full
+		return;
+	}
+
+	struct party_member member = {};
+
+	sd->party_joining = true;
+	party_fill_member( &member, sd, 0 );
+	intif_party_addmember( party_id, &member );
+}
+
+bool party_booking_load( uint32 account_id, uint32 char_id, struct s_party_booking_requirement* booking ){
+	char world_name[NAME_LENGTH * 2 + 1];
+
+	Sql_EscapeString( mmysql_handle, world_name, charserver_name );
+
+	if( Sql_Query( mmysql_handle, "SELECT `minimum_level`, `maximum_level` FROM `%s` WHERE `world_name` = '%s' AND `account_id` = '%u' AND `char_id` = '%u'", partybookings_table, world_name, account_id, char_id ) != SQL_SUCCESS) {
+		Sql_ShowDebug( mmysql_handle );
+
+		return false;
+	}
+
+	if( Sql_NextRow( mmysql_handle ) != SQL_SUCCESS ){
+		Sql_FreeResult( mmysql_handle );
+
+		return false;
+	}
+
+	char* data;
+
+	Sql_GetData( mmysql_handle, 0, &data, nullptr );
+	booking->minimum_level = (uint16)strtoul( data, nullptr, 10 );
+
+	Sql_GetData( mmysql_handle, 1, &data, nullptr );
+	booking->maximum_level = (uint16)strtoul( data, nullptr, 10 );
+
+	Sql_FreeResult( mmysql_handle );
+
+	return true;
+}
+
 int party_reply_invite(map_session_data *sd,int party_id,int flag)
 {
 	map_session_data* tsd;

+ 8 - 0
src/map/party.hpp

@@ -48,6 +48,11 @@ struct party_booking_ad_info {
 	struct party_booking_detail p_detail;
 };
 
+struct s_party_booking_requirement{
+	uint16 minimum_level;
+	uint16 maximum_level;
+};
+
 extern int party_create_byscript;
 
 void do_init_party(void);
@@ -67,6 +72,9 @@ int party_leave(map_session_data *sd);
 int party_removemember(map_session_data *sd,uint32 account_id,char *name);
 int party_removemember2(map_session_data *sd,uint32 char_id,int party_id);
 int party_member_withdraw(int party_id, uint32 account_id, uint32 char_id, char *name, enum e_party_member_withdraw type);
+bool party_isleader( map_session_data* sd );
+void party_join( map_session_data* sd, int party_id );
+bool party_booking_load( uint32 account_id, uint32 char_id, struct s_party_booking_requirement* booking );
 int party_reply_invite(map_session_data *sd,int party_id,int flag);
 #define party_add_member(party_id,sd) party_reply_invite(sd,party_id,1)
 int party_recv_noinfo(int party_id, uint32 char_id);

+ 2 - 0
src/map/pc.hpp

@@ -933,6 +933,8 @@ public:
 	} captcha_upload;
 
 	s_macro_detect macro_detect;
+
+	std::vector<uint32> party_booking_requests;
 };
 
 extern struct eri *pc_sc_display_ers; /// Player's SC display table

+ 569 - 0
src/web/partybooking_controller.cpp

@@ -0,0 +1,569 @@
+// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "partybooking_controller.hpp"
+
+#include <string>
+
+#include "../common/showmsg.hpp"
+#include "../common/sql.hpp"
+#include "../common/strlib.hpp"
+
+#include "http.hpp"
+#include "auth.hpp"
+#include "sqllock.hpp"
+#include "web.hpp"
+
+const size_t WORLD_NAME_LENGTH = 32;
+const size_t COMMENT_LENGTH = 255;
+
+enum e_booking_purpose : uint16{
+	BOOKING_PURPOSE_ALL = 0,
+	BOOKING_PURPOSE_QUEST,
+	BOOKING_PURPOSE_FIELD,
+	BOOKING_PURPOSE_DUNGEON,
+	BOOKING_PURPOSE_MD,
+	BOOKING_PURPOSE_PARADISE,
+	BOOKING_PURPOSE_OTHER,
+	BOOKING_PURPOSE_MAX
+};
+
+struct s_party_booking_entry{
+	uint32 account_id;
+	uint32 char_id;
+	std::string char_name;
+	uint16 purpose;
+	bool assist;
+	bool damagedealer;
+	bool healer;
+	bool tanker;
+	uint16 minimum_level;
+	uint16 maximum_level;
+	std::string comment;
+
+public:
+	std::string to_json( std::string& world_name );
+};
+
+std::string s_party_booking_entry::to_json( std::string& world_name ){
+	return
+		"{ \"AID\": " + std::to_string( this->account_id ) +
+		", \"GID\": " + std::to_string( this->char_id ) +
+		", \"CharName\": \"" + this->char_name + "\""
+		", \"WorldName\": \"" + world_name + "\""
+		", \"Tanker\": " + ( this->tanker ? "1": "0" ) +
+		", \"Healer\": " + ( this->healer ? "1": "0" ) +
+		", \"Dealer\": " + ( this->damagedealer ? "1" : "0" ) +
+		", \"Assist\": " + ( this->assist ? "1" : "0" ) +
+		", \"MinLV\": " + std::to_string( this->minimum_level ) +
+		", \"MaxLV\": " + std::to_string( this->maximum_level ) +
+		", \"Memo\": \"" + this->comment + "\""
+		", \"Type\": " + std::to_string( this->purpose ) +
+		"}";
+}
+
+bool party_booking_read( std::string& world_name, std::vector<s_party_booking_entry>& output, const std::string& condition, const std::string& order ){
+	SQLLock sl(MAP_SQL_LOCK);
+	sl.lock();
+	auto handle = sl.getHandle();
+	SqlStmt* stmt = SqlStmt_Malloc( handle );
+	s_party_booking_entry entry;
+	char world_name_escaped[WORLD_NAME_LENGTH * 2 + 1];
+	char char_name[NAME_LENGTH ];
+	char comment[COMMENT_LENGTH + 1];
+
+	Sql_EscapeString( nullptr, world_name_escaped, world_name.c_str() );
+
+	std::string query = "SELECT `account_id`, `char_id`, `char_name`, `purpose`, `assist`, `damagedealer`, `healer`, `tanker`, `minimum_level`, `maximum_level`, `comment` FROM `" + std::string( partybookings_table ) + "` WHERE `world_name` = ? AND " + condition + order;
+
+	if( SQL_SUCCESS != SqlStmt_Prepare( stmt, query.c_str() )
+		|| SQL_SUCCESS != SqlStmt_BindParam( stmt, 0, SQLDT_STRING, (void*)world_name_escaped, strlen( world_name_escaped ) )
+		|| SQL_SUCCESS != SqlStmt_Execute( stmt )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 0, SQLDT_UINT32, &entry.account_id, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 1, SQLDT_UINT32, &entry.char_id, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 2, SQLDT_STRING, (void*)char_name, sizeof( char_name ), NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 3, SQLDT_UINT16, &entry.purpose, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 4, SQLDT_UINT8, &entry.assist, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 5, SQLDT_UINT8, &entry.damagedealer, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 6, SQLDT_UINT8, &entry.healer, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 7, SQLDT_UINT8, &entry.tanker, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 8, SQLDT_UINT16, &entry.minimum_level, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 9, SQLDT_UINT16, &entry.maximum_level, 0, NULL, NULL )
+		|| SQL_SUCCESS != SqlStmt_BindColumn( stmt, 10, SQLDT_STRING, (void*)comment, sizeof( comment ), NULL, NULL )
+	){
+		SqlStmt_ShowDebug( stmt );
+		SqlStmt_Free( stmt );
+		sl.unlock();
+		return false;
+	}
+
+	while( SQL_SUCCESS == SqlStmt_NextRow( stmt ) ){
+		entry.char_name = char_name;
+		entry.comment = comment;
+
+		output.push_back( entry );
+	}
+
+	SqlStmt_Free( stmt );
+	sl.unlock();
+
+	return true;
+}
+
+HANDLER_FUNC(partybooking_add){
+	if( !isAuthorized( req, false ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	uint32 aid = std::stoi( req.get_file_value( "AID" ).content );
+	uint32 cid = std::stoi( req.get_file_value( "GID" ).content );
+
+	SQLLock csl( CHAR_SQL_LOCK );
+	csl.lock();
+	auto chandle = csl.getHandle();
+	SqlStmt* stmt = SqlStmt_Malloc( chandle );
+	if( SQL_SUCCESS != SqlStmt_Prepare( stmt, "SELECT 1 FROM `%s` WHERE `leader_id` = ? AND `leader_char` = ?", party_table, aid, cid )
+		|| SQL_SUCCESS != SqlStmt_BindParam( stmt, 0, SQLDT_UINT32, &aid, sizeof( aid ) )
+		|| SQL_SUCCESS != SqlStmt_BindParam( stmt, 1, SQLDT_UINT32, &cid, sizeof( cid ) )
+		|| SQL_SUCCESS != SqlStmt_Execute( stmt )
+	){
+		SqlStmt_ShowDebug( stmt );
+		SqlStmt_Free( stmt );
+		csl.unlock();
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	if( SqlStmt_NumRows( stmt ) <= 0 ){
+		// No party or not party leader
+		SqlStmt_Free( stmt );
+		csl.unlock();
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	SqlStmt_Free( stmt );
+	csl.unlock();
+
+	auto world_name = req.get_file_value( "WorldName" ).content;
+
+	if( world_name.length() > WORLD_NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	s_party_booking_entry entry = {};
+
+	entry.account_id = aid;
+	entry.char_id = cid;
+	entry.char_name = req.get_file_value( "CharName" ).content;
+	entry.purpose = std::stoi( req.get_file_value( "Type" ).content );
+	entry.assist = std::stoi( req.get_file_value( "Assist" ).content ) != 0;
+	entry.damagedealer = std::stoi( req.get_file_value( "Dealer" ).content ) != 0;
+	entry.healer = std::stoi( req.get_file_value( "Healer" ).content ) != 0;
+	entry.tanker = std::stoi( req.get_file_value( "Tanker" ).content ) != 0;
+	entry.minimum_level = std::stoi( req.get_file_value( "MinLV" ).content );
+	entry.maximum_level = std::stoi( req.get_file_value( "MaxLV" ).content );
+	entry.comment = req.get_file_value( "Memo" ).content;
+
+	if( entry.char_name.length() > NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	if( entry.purpose >= BOOKING_PURPOSE_MAX ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	if( entry.comment.length() > COMMENT_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	char world_name_escaped[WORLD_NAME_LENGTH * 2 + 1];
+	char char_name_escaped[NAME_LENGTH * 2 + 1];
+	char comment_escaped[COMMENT_LENGTH * 2 + 1];
+
+	Sql_EscapeString( nullptr, world_name_escaped, world_name.c_str() );
+	Sql_EscapeString( nullptr, char_name_escaped, entry.char_name.c_str() );
+	Sql_EscapeString( nullptr, comment_escaped, entry.comment.c_str() );
+
+	StringBuf buf;
+
+	StringBuf_Init( &buf );
+
+	StringBuf_Printf( &buf, "REPLACE INTO `%s` ( `world_name`, `account_id`, `char_id`, `char_name`, `purpose`, `assist`, `damagedealer`, `healer`, `tanker`, `minimum_level`, `maximum_level`, `comment` ) VALUES ( ", partybookings_table );
+
+	StringBuf_Printf( &buf, "'%s',", world_name_escaped );
+
+	StringBuf_Printf( &buf, "'%u',", entry.account_id );
+	StringBuf_Printf( &buf, "'%u',", entry.char_id );
+	StringBuf_Printf( &buf, "'%s',", char_name_escaped );
+	StringBuf_Printf( &buf, "'%hu',", entry.purpose );
+	StringBuf_Printf( &buf, "'%d',", entry.assist );
+	StringBuf_Printf( &buf, "'%d',", entry.damagedealer );
+	StringBuf_Printf( &buf, "'%d',", entry.healer );
+	StringBuf_Printf( &buf, "'%d',", entry.tanker );
+	StringBuf_Printf( &buf, "'%hu',", entry.minimum_level );
+	StringBuf_Printf( &buf, "'%hu',", entry.maximum_level );
+	StringBuf_Printf( &buf, "'%s' );", comment_escaped );
+
+	SQLLock msl( MAP_SQL_LOCK );
+	msl.lock();
+	auto mhandle = msl.getHandle();
+
+	if( SQL_ERROR == Sql_QueryStr( mhandle, StringBuf_Value( &buf ) ) ){
+		Sql_ShowDebug( mhandle );
+
+		StringBuf_Destroy( &buf );
+		msl.unlock();
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	StringBuf_Destroy( &buf );
+	msl.unlock();
+
+	res.set_content( "{ \"Type\": 1 }", "application/json" );
+}
+
+HANDLER_FUNC(partybooking_delete){
+	if( !isAuthorized( req, false ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	auto world_name = req.get_file_value( "WorldName" ).content;
+	auto account_id = std::stoi( req.get_file_value( "AID" ).content );
+
+	if( world_name.length() > WORLD_NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	SQLLock sl( MAP_SQL_LOCK );
+	sl.lock();
+	auto handle = sl.getHandle();
+
+	if( SQL_ERROR == Sql_Query( handle, "DELETE FROM `%s` WHERE `world_name` = '%s' AND `account_id` = '%d'", partybookings_table, world_name.c_str(), account_id ) ){
+		Sql_ShowDebug( handle );
+
+		sl.unlock();
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	sl.unlock();
+
+	res.set_content( "{ \"Type\": 1 }", "application/json" );
+}
+
+HANDLER_FUNC(partybooking_get){
+	if( !isAuthorized( req, false ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	auto world_name = req.get_file_value( "WorldName" ).content;
+	auto account_id = std::stoi( req.get_file_value( "AID" ).content );
+
+	if( world_name.length() > WORLD_NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::vector<s_party_booking_entry> bookings;
+
+	if( !party_booking_read( world_name, bookings, "`account_id` = '" + std::to_string( account_id ) + "'", "" ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::string response;
+
+	if( bookings.empty() ){
+		response = "{ \"Type\": 1 }";
+	}else{
+		response = "{ \"Type\": 1, data: " + bookings.at( 0 ).to_json( world_name ) + " }";
+	}
+
+	res.set_content( response, "application/json" );
+}
+
+HANDLER_FUNC(partybooking_info){
+	if( !isAuthorized( req, false ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	auto world_name = req.get_file_value( "WorldName" ).content;
+	auto account_id = std::stoi( req.get_file_value( "QueryAID" ).content );
+
+	if( world_name.length() > WORLD_NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::vector<s_party_booking_entry> bookings;
+
+	if( !party_booking_read( world_name, bookings, "`account_id` = '" + std::to_string( account_id ) + "'", "" ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::string response;
+
+	if( bookings.empty() ){
+		response = "{ \"Type\": 1 }";
+	}else{
+		response = "{ \"Type\": 1, \"data\": [" + bookings.at( 0 ).to_json( world_name ) + "] }";
+	}
+
+	res.set_content( response, "application/json" );
+}
+
+HANDLER_FUNC(partybooking_list){
+	if( !isAuthorized( req, false ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	static const std::string condition( "1=1" );
+
+	auto world_name = req.get_file_value( "WorldName" ).content;
+
+	if( world_name.length() > WORLD_NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::vector<s_party_booking_entry> bookings;
+
+	if( !party_booking_read( world_name, bookings, condition, " ORDER BY `created` DESC" ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::string response;
+
+	response = "{ \"Type\": 1, \"totalPage\": ";
+	response += std::to_string( bookings.size() );
+	response += ", \"data\": [";
+
+	for( size_t i = 0, max = bookings.size(); i < max; i++ ){
+		s_party_booking_entry& booking = bookings.at( i );
+
+		response += booking.to_json( world_name );
+
+		if( i < ( max - 1 ) ){
+			response += ", ";
+		}
+	}
+
+	response += "] }";
+
+	res.set_content( response, "application/json" );
+}
+
+HANDLER_FUNC(partybooking_search){
+	if( !isAuthorized( req, false ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+		return;
+	}
+
+	auto world_name = req.get_file_value( "WorldName" ).content;
+
+	if( world_name.length() > WORLD_NAME_LENGTH ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	s_party_booking_entry entry;
+
+	// Unconditional
+	entry.minimum_level = std::stoi( req.get_file_value( "MinLV" ).content );
+	entry.maximum_level = std::stoi( req.get_file_value( "MaxLV" ).content );
+
+	// Conditional
+	if( req.files.find( "Type" ) != req.files.end() ){
+		entry.purpose = std::stoi( req.get_file_value( "Type" ).content );
+
+		if( entry.purpose >= BOOKING_PURPOSE_MAX ){
+			res.status = HTTP_BAD_REQUEST;
+			res.set_content( "Error", "text/plain" );
+
+			return;
+		}
+	}else{
+		entry.purpose = BOOKING_PURPOSE_ALL;
+	}
+
+	if( req.files.find( "Assist" ) != req.files.end() ){
+		entry.assist = std::stoi( req.get_file_value( "Assist" ).content ) != 0;
+	}else{
+		entry.assist = false;
+	}
+
+	if( req.files.find( "Dealer" ) != req.files.end() ){
+		entry.damagedealer = std::stoi( req.get_file_value( "Dealer" ).content ) != 0;
+	}else{
+		entry.damagedealer = false;
+	}
+
+	if( req.files.find( "Healer" ) != req.files.end() ){
+		entry.healer = std::stoi( req.get_file_value( "Healer" ).content ) != 0;
+	}else{
+		entry.healer = false;
+	}
+
+	if( req.files.find( "Tanker" ) != req.files.end() ){
+		entry.tanker = std::stoi( req.get_file_value( "Tanker" ).content ) != 0;
+	}else{
+		entry.tanker = false;
+	}
+
+	if( req.files.find( "Memo" ) != req.files.end() ){
+		entry.comment = req.get_file_value( "Memo" ).content;
+	}else{
+		entry.comment = "";
+	}
+
+	std::string condition;
+
+	condition = "`minimum_level` = '" + std::to_string( entry.minimum_level ) + "'";
+	condition += " AND `maximum_level` = '" + std::to_string( entry.maximum_level ) + "'";
+
+	if( entry.purpose != BOOKING_PURPOSE_ALL ){
+		condition += " AND `purpose` = '" + std::to_string( entry.purpose ) + "'";
+	}
+
+	if( entry.assist || entry.damagedealer || entry.healer || entry.tanker ){
+		bool or_required = false;
+
+		condition += "AND ( ";
+
+		if( entry.assist ){
+			if( or_required ){
+				condition += " OR ";
+			}else{
+				or_required = true;
+			}
+
+			condition += "`assist` = '1'";
+		}
+
+		if( entry.damagedealer ){
+			if( or_required ){
+				condition += " OR ";
+			}else{
+				or_required = true;
+			}
+
+			condition += "`damagedealer` = '1'";
+		}
+
+		if( entry.healer ){
+			if( or_required ){
+				condition += " OR ";
+			}else{
+				or_required = true;
+			}
+
+			condition += "`healer` = '1'";
+		}
+
+		if( entry.tanker ){
+			if( or_required ){
+				condition += " OR ";
+			}else{
+				or_required = true;
+			}
+
+			condition += "`tanker` = '1'";
+		}
+
+		condition += " )";
+	}
+
+	if( !entry.comment.empty() ){
+		if( entry.comment.length() > COMMENT_LENGTH ){
+			res.status = HTTP_BAD_REQUEST;
+			res.set_content( "Error", "text/plain" );
+
+			return;
+		}
+
+		char escaped_comment[COMMENT_LENGTH * 2 + 1];
+
+		Sql_EscapeString( nullptr, escaped_comment, entry.comment.c_str() );
+
+		condition += " AND `comment` like '%" + std::string( escaped_comment ) + "%'";
+	}
+
+	std::vector<s_party_booking_entry> bookings;
+
+	if( !party_booking_read( world_name, bookings, condition, " ORDER BY `created` DESC" ) ){
+		res.status = HTTP_BAD_REQUEST;
+		res.set_content( "Error", "text/plain" );
+
+		return;
+	}
+
+	std::string response;
+
+	response = "{ \"Type\": 1, \"totalPage\": ";
+	response += std::to_string( bookings.size() );
+	response += ", \"data\": [";
+
+	for( size_t i = 0, max = bookings.size(); i < max; i++ ){
+		s_party_booking_entry& booking = bookings.at( i );
+
+		response += booking.to_json( world_name );
+
+		if( i < ( max - 1 ) ){
+			response += ", ";
+		}
+	}
+
+	response += "] }";
+
+	res.set_content( response, "application/json" );
+}

+ 16 - 0
src/web/partybooking_controller.hpp

@@ -0,0 +1,16 @@
+// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef PARTYBOOKING_CONTROLLER_HPP
+#define PARTYBOOKING_CONTROLLER_HPP
+
+#include "http.hpp"
+
+HANDLER_FUNC(partybooking_add);
+HANDLER_FUNC(partybooking_delete);
+HANDLER_FUNC(partybooking_get);
+HANDLER_FUNC(partybooking_info);
+HANDLER_FUNC(partybooking_list);
+HANDLER_FUNC(partybooking_search);
+
+#endif

+ 10 - 0
src/web/sqllock.cpp

@@ -11,6 +11,7 @@ std::mutex dbmutex;
 
 extern Sql * login_handle;
 extern Sql * char_handle;
+extern Sql * map_handle;
 extern Sql * web_handle;
 
 
@@ -22,6 +23,9 @@ SQLLock::SQLLock(locktype lt) : ulock(dbmutex, std::defer_lock), lt(lt) {
 		case CHAR_SQL_LOCK:
 			handle = char_handle;
 			break;
+		case MAP_SQL_LOCK:
+			handle = map_handle;
+			break;
 		case WEB_SQL_LOCK:
 			handle = web_handle;
 			break;
@@ -36,6 +40,9 @@ void SQLLock::lock() {
 	//     case CHAR_SQL_LOCK:
 	//         ShowDebug("Locking char sql\n");
 	//         break;
+	//     case MAP_SQL_LOCK:
+	//         ShowDebug("Locking map sql\n");
+	//         break;
 	//     case WEB_SQL_LOCK:
 	//         ShowDebug("Locking web sql\n");
 	//         break;
@@ -52,6 +59,9 @@ void SQLLock::unlock() {
 	//     case CHAR_SQL_LOCK:
 	//         ShowDebug("Unlocked char sql\n");
 	//         break;
+	//     case MAP_SQL_LOCK:
+	//         ShowDebug("Unlocked map sql\n");
+	//         break;
 	//     case WEB_SQL_LOCK:
 	//         ShowDebug("Unlocked web sql\n");
 	//         break;

+ 1 - 0
src/web/sqllock.hpp

@@ -11,6 +11,7 @@
 enum locktype {
 	LOGIN_SQL_LOCK,
 	CHAR_SQL_LOCK,
+	MAP_SQL_LOCK,
 	WEB_SQL_LOCK
 };
 

+ 2 - 0
src/web/web-server.vcxproj

@@ -168,6 +168,7 @@
     <ClCompile Include="charconfig_controller.cpp" />
     <ClCompile Include="emblem_controller.cpp" />
     <ClCompile Include="merchantstore_controller.cpp" />
+    <ClCompile Include="partybooking_controller.cpp" />
     <ClCompile Include="sqllock.cpp" />
     <ClCompile Include="userconfig_controller.cpp" />
     <ClCompile Include="webutils.cpp" />
@@ -179,6 +180,7 @@
     <ClInclude Include="emblem_controller.hpp" />
     <ClInclude Include="http.hpp" />
     <ClInclude Include="merchantstore_controller.hpp" />
+    <ClInclude Include="partybooking_controller.hpp" />
     <ClInclude Include="sqllock.hpp" />
     <ClInclude Include="userconfig_controller.hpp" />
     <ClInclude Include="webutils.hpp" />

+ 12 - 6
src/web/web-server.vcxproj.filters

@@ -24,6 +24,12 @@
     <ClCompile Include="emblem_controller.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="merchantstore_controller.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="partybooking_controller.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="sqllock.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -33,9 +39,6 @@
     <ClCompile Include="web.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="merchantstore_controller.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="webutils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -53,6 +56,12 @@
     <ClInclude Include="http.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="merchantstore_controller.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="partybooking_controller.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="sqllock.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -65,9 +74,6 @@
     <ClInclude Include="webcnslif.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="merchantstore_controller.hpp">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="webutils.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>

+ 63 - 13
src/web/web.cpp

@@ -28,8 +28,9 @@
 #include "charconfig_controller.hpp"
 #include "emblem_controller.hpp"
 #include "http.hpp"
-#include "userconfig_controller.hpp"
 #include "merchantstore_controller.hpp"
+#include "partybooking_controller.hpp"
+#include "userconfig_controller.hpp"
 
 
 using namespace rathena;
@@ -55,6 +56,12 @@ std::string char_server_id = "ragnarok";
 std::string char_server_pw = "";
 std::string char_server_db = "ragnarok";
 
+int map_server_port = 3306;
+std::string map_server_ip = "127.0.0.1";
+std::string map_server_id = "ragnarok";
+std::string map_server_pw = "";
+std::string map_server_db = "ragnarok";
+
 int web_server_port = 3306;
 std::string web_server_ip = "127.0.0.1";
 std::string web_server_id = "ragnarok";
@@ -65,6 +72,7 @@ std::string default_codepage = "";
 
 Sql * login_handle = NULL;
 Sql * char_handle = NULL;
+Sql * map_handle = NULL;
 Sql * web_handle = NULL;
 
 char login_table[32] = "login";
@@ -72,6 +80,8 @@ char guild_emblems_table[32] = "guild_emblems";
 char user_configs_table[32] = "user_configs";
 char char_configs_table[32] = "char_configs";
 char merchant_configs_table[32] = "merchant_configs";
+char party_table[32] = "party";
+char partybookings_table[32] = "party_bookings";
 char guild_db_table[32] = "guild";
 char char_db_table[32] = "char";
 
@@ -195,6 +205,16 @@ int inter_config_read(const char* cfgName)
 			char_server_pw = w2;
 		else if(!strcmpi(w1,"char_server_db"))
 			char_server_db = w2;
+		else if(!strcmpi(w1,"map_server_ip"))
+			map_server_ip = w2;
+		else if(!strcmpi(w1,"map_server_port"))
+			map_server_port = atoi(w2);
+		else if(!strcmpi(w1,"map_server_id"))
+			map_server_id = w2;
+		else if(!strcmpi(w1,"map_server_pw"))
+			map_server_pw = w2;
+		else if(!strcmpi(w1,"map_server_db"))
+			map_server_db = w2;
 		else if(!strcmpi(w1,"web_server_ip"))
 			web_server_ip = w2;
 		else if(!strcmpi(w1,"web_server_port"))
@@ -213,6 +233,10 @@ int inter_config_read(const char* cfgName)
 			safestrncpy(char_configs_table, w2, sizeof(char_configs_table));
 		else if (!strcmpi(w1, "merchant_configs"))
 			safestrncpy(merchant_configs_table, w2, sizeof(merchant_configs_table));
+		else if (!strcmpi(w1, "party_db"))
+			safestrncpy(party_table, w2, sizeof(party_table));
+		else if (!strcmpi(w1, "partybookings_table"))
+			safestrncpy(partybookings_table, w2, sizeof(partybookings_table));
 		else if (!strcmpi(w1, "guild_emblems"))
 			safestrncpy(guild_emblems_table, w2, sizeof(guild_emblems_table));
 		else if (!strcmpi(w1, "login_server_account_db"))
@@ -234,7 +258,7 @@ int inter_config_read(const char* cfgName)
 
 void web_set_defaults() {
 	web_config.web_ip = "0.0.0.0";
-	web_config.web_port = 3000;
+	web_config.web_port = 8888;
 	safestrncpy(web_config.webconf_name, "conf/web_athena.conf", sizeof(web_config.webconf_name));
 	safestrncpy(web_config.msgconf_name, "conf/msg_conf/web_msg.conf", sizeof(web_config.msgconf_name));
 	web_config.print_req_res = false;
@@ -252,8 +276,8 @@ int web_sql_init(void) {
 	ShowInfo("Connecting to the Login DB server.....\n");
 
 	if (SQL_ERROR == Sql_Connect(login_handle, login_server_id.c_str(), login_server_pw.c_str(), login_server_ip.c_str(), login_server_port, login_server_db.c_str())) {
-		ShowError("Couldn't connect with uname='%s',passwd='%s',host='%s',port='%d',database='%s'\n",
-			login_server_id.c_str(), login_server_pw.c_str(), login_server_ip.c_str(), login_server_port, login_server_db.c_str());
+		ShowError("Couldn't connect with uname='%s',host='%s',port='%d',database='%s'\n",
+			login_server_id.c_str(), login_server_ip.c_str(), login_server_port, login_server_db.c_str());
 		Sql_ShowDebug(login_handle);
 		Sql_Free(login_handle);
 		exit(EXIT_FAILURE);
@@ -269,8 +293,8 @@ int web_sql_init(void) {
 	ShowInfo("Connecting to the Char DB server.....\n");
 
 	if (SQL_ERROR == Sql_Connect(char_handle, char_server_id.c_str(), char_server_pw.c_str(), char_server_ip.c_str(), char_server_port, char_server_db.c_str())) {
-		ShowError("Couldn't connect with uname='%s',passwd='%s',host='%s',port='%d',database='%s'\n",
-			char_server_id.c_str(), char_server_pw.c_str(), char_server_ip.c_str(), char_server_port, char_server_db.c_str());
+		ShowError("Couldn't connect with uname='%s',host='%s',port='%d',database='%s'\n",
+			char_server_id.c_str(), char_server_ip.c_str(), char_server_port, char_server_db.c_str());
 		Sql_ShowDebug(char_handle);
 		Sql_Free(char_handle);
 		exit(EXIT_FAILURE);
@@ -282,12 +306,29 @@ int web_sql_init(void) {
 			Sql_ShowDebug(char_handle);
 	}
 
+	map_handle = Sql_Malloc();
+	ShowInfo("Connecting to the Map DB server.....\n");
+
+	if (SQL_ERROR == Sql_Connect(map_handle, map_server_id.c_str(), map_server_pw.c_str(), map_server_ip.c_str(), map_server_port, map_server_db.c_str())) {
+		ShowError("Couldn't connect with uname='%s',host='%s',port='%d',database='%s'\n",
+			map_server_id.c_str(), map_server_ip.c_str(), map_server_port, map_server_db.c_str());
+		Sql_ShowDebug(map_handle);
+		Sql_Free(map_handle);
+		exit(EXIT_FAILURE);
+	}
+	ShowStatus("Connect success! (Map Server Connection)\n");
+
+	if (!default_codepage.empty()) {
+		if (SQL_ERROR == Sql_SetEncoding(map_handle, default_codepage.c_str()))
+			Sql_ShowDebug(map_handle);
+	}
+
 	web_handle = Sql_Malloc();
 	ShowInfo("Connecting to the Web DB server.....\n");
 
 	if (SQL_ERROR == Sql_Connect(web_handle, web_server_id.c_str(), web_server_pw.c_str(), web_server_ip.c_str(), web_server_port, web_server_db.c_str())) {
-		ShowError("Couldn't connect with uname='%s',passwd='%s',host='%s',port='%d',database='%s'\n",
-			web_server_id.c_str(), web_server_pw.c_str(), web_server_ip.c_str(), web_server_port, web_server_db.c_str());
+		ShowError("Couldn't connect with uname='%s',host='%s',port='%d',database='%s'\n",
+			web_server_id.c_str(), web_server_ip.c_str(), web_server_port, web_server_db.c_str());
 		Sql_ShowDebug(web_handle);
 		Sql_Free(web_handle);
 		exit(EXIT_FAILURE);
@@ -311,6 +352,9 @@ int web_sql_close(void)
 	ShowStatus("Close Char DB Connection....\n");
 	Sql_Free(char_handle);
 	char_handle = NULL;
+	ShowStatus("Close Map DB Connection....\n");
+	Sql_Free(map_handle);
+	map_handle = NULL;
 	ShowStatus("Close Web DB Connection....\n");
 	Sql_Free(web_handle);
 	web_handle = NULL;
@@ -319,7 +363,7 @@ int web_sql_close(void)
 }
 
 /**
- * Login-serv destructor
+ * web-server destructor
  *  dealloc..., function called at exit of the web-server
  */
 void WebServer::finalize(){
@@ -403,14 +447,20 @@ bool WebServer::initialize( int argc, char* argv[] ){
 
 	http_server = std::make_shared<httplib::Server>();
 	// set up routes
-	http_server->Post("/emblem/download", emblem_download);
-	http_server->Post("/emblem/upload", emblem_upload);
-	http_server->Post("/userconfig/load", userconfig_load);
-	http_server->Post("/userconfig/save", userconfig_save);
 	http_server->Post("/charconfig/load", charconfig_load);
 	http_server->Post("/charconfig/save", charconfig_save);
+	http_server->Post("/emblem/download", emblem_download);
+	http_server->Post("/emblem/upload", emblem_upload);
 	http_server->Post("/MerchantStore/load", merchantstore_load);
 	http_server->Post("/MerchantStore/save", merchantstore_save);
+	http_server->Post("/party/add", partybooking_add);
+	http_server->Post("/party/del", partybooking_delete);
+	http_server->Post("/party/get", partybooking_get);
+	http_server->Post("/party/info", partybooking_info);
+	http_server->Post("/party/list", partybooking_list);
+	http_server->Post("/party/search", partybooking_search);
+	http_server->Post("/userconfig/load", userconfig_load);
+	http_server->Post("/userconfig/save", userconfig_save);
 
 	// set up logger
 	http_server->set_logger(logger);

+ 2 - 0
src/web/web.hpp

@@ -66,6 +66,8 @@ extern char char_configs_table[32];
 extern char guild_db_table[32];
 extern char char_db_table[32];
 extern char merchant_configs_table[32];
+extern char party_table[32];
+extern char partybookings_table[32];
 
 #define msg_config_read(cfgName) web_msg_config_read(cfgName)
 #define msg_txt(msg_number) web_msg_txt(msg_number)