From 620e60eebce2c1f35c5c9a82f6ca365b316587f5 Mon Sep 17 00:00:00 2001 From: Valaris Date: Sun, 29 Jan 2006 16:10:48 +0000 Subject: AS OF SVN REV. 5901, WE ARE NOW USING TRUNK. ALL UNTESTED BUGFIXES/FEATURES GO INTO TRUNK. IF YOU HAVE A WORKING AND TESTED BUGFIX PUT IT INTO STABLE AS WELL AS TRUNK. EVERYTHING ELSE GOES INTO TRUNK AND WILL BE MERGED INTO STABLE BY VALARIS AND WIZPUTER. -- VALARIS git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@5094 54d463be-8e91-2dee-dedb-b68131a5f0ec --- src/char_sql/Makefile | 27 + src/char_sql/char.c | 4307 ++++++++++++++++++++++++++++++++++++++++++++ src/char_sql/char.h | 103 ++ src/char_sql/int_guild.c | 1914 ++++++++++++++++++++ src/char_sql/int_guild.h | 18 + src/char_sql/int_party.c | 865 +++++++++ src/char_sql/int_party.h | 14 + src/char_sql/int_pet.c | 351 ++++ src/char_sql/int_pet.h | 16 + src/char_sql/int_storage.c | 364 ++++ src/char_sql/int_storage.h | 17 + src/char_sql/inter.c | 789 ++++++++ src/char_sql/inter.h | 58 + src/char_sql/itemdb.c | 229 +++ src/char_sql/itemdb.h | 38 + src/char_sql/make.sh | 10 + 16 files changed, 9120 insertions(+) create mode 100644 src/char_sql/Makefile create mode 100644 src/char_sql/char.c create mode 100644 src/char_sql/char.h create mode 100644 src/char_sql/int_guild.c create mode 100644 src/char_sql/int_guild.h create mode 100644 src/char_sql/int_party.c create mode 100644 src/char_sql/int_party.h create mode 100644 src/char_sql/int_pet.c create mode 100644 src/char_sql/int_pet.h create mode 100644 src/char_sql/int_storage.c create mode 100644 src/char_sql/int_storage.h create mode 100644 src/char_sql/inter.c create mode 100644 src/char_sql/inter.h create mode 100644 src/char_sql/itemdb.c create mode 100644 src/char_sql/itemdb.h create mode 100644 src/char_sql/make.sh (limited to 'src/char_sql') diff --git a/src/char_sql/Makefile b/src/char_sql/Makefile new file mode 100644 index 000000000..f67a95315 --- /dev/null +++ b/src/char_sql/Makefile @@ -0,0 +1,27 @@ +all sql: char-server_sql + +COMMON_OBJ = ../common/obj/core.o ../common/obj/socket.o ../common/obj/timer.o \ + ../common/obj/db.o ../common/obj/plugins.o ../common/obj/lock.o \ + ../common/obj/malloc.o ../common/obj/showmsg.o ../common/obj/utils.o \ + ../common/obj/strlib.o ../common/obj/graph.o ../common/obj/grfio.o \ + ../common/obj/mapindex.o ../common/obj/ers.o ../zlib/unz.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h \ + ../common/version.h ../common/db.h ../common/plugins.h ../common/lock.h \ + ../common/malloc.h ../common/showmsg.h ../common/utils.h ../common/strlib.h \ + ../common/graph.h ../common/grfio.h ../common/mapindex.h + +char-server_sql: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o itemdb.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +clean: + rm -f *.o ../../char-server_sql + +# DO NOT DELETE + +char.o: char.c char.h ../common/strlib.h itemdb.h ../common/showmsg.h +inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h ../common/mmo.h char.h ../common/socket.h ../common/showmsg.h +int_party.o: int_party.c int_party.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/timer.h ../common/db.h ../common/showmsg.h +int_guild.o: int_guild.c int_guild.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/db.h ../common/showmsg.h +int_storage.o: int_storage.c int_storage.h char.h itemdb.h ../common/showmsg.h +int_pet.o: int_pet.c int_pet.h inter.h char.h ../common/mmo.h ../common/socket.h ../common/db.h ../common/showmsg.h +itemdb.o: itemdb.c itemdb.h ../common/db.h ../common/mmo.h ../common/showmsg.h diff --git a/src/char_sql/char.c b/src/char_sql/char.c new file mode 100644 index 000000000..7faed1e87 --- /dev/null +++ b/src/char_sql/char.c @@ -0,0 +1,4307 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung +// TXT 1.105 +#include + +#ifdef _WIN32 +#include +typedef long in_addr_t; +//#pragma lib // Not required [Lance] +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "char.h" +#include "../common/utils.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +#include "itemdb.h" +#include "inter.h" +#include "db.h" +#include "malloc.h" +#include "int_guild.h" + +static struct dbt *char_db_; + +char char_db[256] = "char"; +char scdata_db[256] = "sc_data"; +char cart_db[256] = "cart_inventory"; +char inventory_db[256] = "inventory"; +char charlog_db[256] = "charlog"; +char storage_db[256] = "storage"; +char interlog_db[256] = "interlog"; +char reg_db[256] = "global_reg_value"; +char skill_db[256] = "skill"; +char memo_db[256] = "memo"; +char guild_db[256] = "guild"; +char guild_alliance_db[256] = "guild_alliance"; +char guild_castle_db[256] = "guild_castle"; +char guild_expulsion_db[256] = "guild_expulsion"; +char guild_member_db[256] = "guild_member"; +char guild_position_db[256] = "guild_position"; +char guild_skill_db[256] = "guild_skill"; +char guild_storage_db[256] = "guild_storage"; +char party_db[256] = "party"; +char pet_db[256] = "pet"; +char friend_db[256] = "friends"; +int db_use_sqldbs; + +char login_db_account_id[32] = "account_id"; +char login_db_level[32] = "level"; + +int lowest_gm_level = 1; + +char *SQL_CONF_NAME = "conf/inter_athena.conf"; + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[NAME_LENGTH] = "Server"; +int login_ip_set_ = 0; +char login_ip_str[128]; +in_addr_t login_ip; +int login_port = 6900; +int char_ip_set_ = 0; +char char_ip_str[128]; +int bind_ip_set_ = 0; +char bind_ip_str[128]; +in_addr_t char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int char_new_display; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +char char_name_letters[1024] = ""; // list of letters/symbols used to authorise or not a name of a character. by [Yor] +//The following are characters that are trimmed regardless because they cause confusion and problems on the servers. [Skotlex] +#define TRIM_CHARS "\032\t\n " +int char_per_account = 0; //Maximum charas per account (default unlimited) [Sirius] + +int log_char = 1; // loggin char or not [devil] +int log_inter = 1; // loggin inter or not [devil] + +char lan_map_ip[128]; // Lan map ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy +char unknown_char_name[NAME_LENGTH] = "Unknown"; +char db_path[1024]="db"; + +//These are used to aid the map server in identifying valid clients. [Skotlex] +static int max_account_id = DEFAULT_MAX_ACCOUNT_ID, max_char_id = DEFAULT_MAX_CHAR_ID; +static int online_check = 1; //If one, it won't let players connect when their account is already registered online and will send the relevant map server a kick user request. [Skotlex] + +struct char_session_data{ + int account_id, login_id1, login_id2,sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, login_id2, ip, char_pos, delflag,sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) + +int char_id_count = START_CHAR_NUM; +struct mmo_charstatus *char_dat; +int char_num,char_max; +int max_connect_user = 0; +int gm_allow_level = 99; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int save_log = 1; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 2301; + +// check for exit signal +// 0 is saving complete +// other is char_id +unsigned int save_flag = 0; + +// start point (you can reset point on conf file) +struct point start_point = { 0, 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +int console = 0; + +//Structure for holding in memory which characters are online on the map servers connected. +struct online_char_data { + int account_id; + int char_id; + short server; + unsigned waiting_disconnect :1; +}; + +struct dbt *online_char_db; //Holds all online characters. + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data); + +static void * create_online_char_data(DBKey key, va_list args) { + struct online_char_data* character; + character = aCalloc(1, sizeof(struct online_char_data)); + character->account_id = key.i; + character->char_id = -1; + character->server = -1; + return character; +} + +//------------------------------------------------- +// Set Character online/offline [Wizputer] +//------------------------------------------------- + +void set_char_online(int map_id, int char_id, int account_id) { + struct online_char_data* character; + if ( char_id != 99 ) { + sprintf(tmp_sql, "UPDATE `%s` SET `online`='1' WHERE `char_id`='%d'",char_db,char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (max_account_id < account_id || max_char_id < char_id) + { //Notify map-server of the new max IDs [Skotlex] + if (account_id > max_account_id) + max_account_id = account_id; + if (char_id > max_char_id) + max_char_id = char_id; + mapif_send_maxid(max_account_id, max_char_id); + } + } + + character = idb_ensure(online_char_db, account_id, create_online_char_data); + if (online_check && character->char_id != -1 && character->server > -1 && character->server != map_id) + { + ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, map_id, account_id, char_id); + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + } + character->char_id = (char_id==99)?-1:char_id; + character->server = (char_id==99)?-1:map_id; + character->waiting_disconnect = 0; + if (char_id != 99) + { //Set char online in guild cache. If char is in memory, use the guild id on it, otherwise seek it. + struct mmo_charstatus *cp; + cp = idb_get(char_db_,char_id); + inter_guild_CharOnline(char_id, cp?cp->guild_id:-1); + } + if (login_fd <= 0 || session[login_fd]->eof) + return; + + WFIFOW(login_fd,0) = 0x272b; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); +} + +void set_char_offline(int char_id, int account_id) { + struct mmo_charstatus *cp; + struct online_char_data* character; + + if ( char_id == 99 ) + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `account_id`='%d'", char_db, account_id); + else { + cp = idb_get(char_db_,char_id); + inter_guild_CharOffline(char_id, cp?cp->guild_id:-1); + if (cp) + idb_remove(char_db_,char_id); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `char_id`='%d'", char_db, char_id); + + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if ((character = idb_get(online_char_db, account_id)) != NULL) + { //We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex] + character->char_id = -1; + character->server = -1; + character->waiting_disconnect = 0; + } + + if (login_fd <= 0 || session[login_fd]->eof) + return; + + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); +} + +static int char_db_setoffline(DBKey key, void* data, va_list ap) { + struct online_char_data* character = (struct online_char_data*)data; + int server = va_arg(ap, int); + if (server == -1) { + character->char_id = -1; + character->server = -1; + character->waiting_disconnect = 0; + } else if (character->server == server) + character->server = -2; //In some map server that we aren't connected to. + return 0; +} + +void set_all_offline(void) { + MYSQL_RES* sql_res2; //Needed because it is used inside inter_guild_CharOffline; [Skotlex] + int char_id; + sprintf(tmp_sql, "SELECT `account_id`, `char_id`, `guild_id` FROM `%s` WHERE `online`='1'",char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + ShowNotice("Sending all users offline.\n"); + sql_res2 = mysql_store_result(&mysql_handle); + if (sql_res2) { + while((sql_row = mysql_fetch_row(sql_res2))) { + char_id = atoi(sql_row[1]); + inter_guild_CharOffline(char_id, atoi(sql_row[2])); + + if ( login_fd > 0 ) { + ShowInfo("send user offline: %d\n",char_id); + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = char_id; + WFIFOSET(login_fd,6); + } + } + mysql_free_result(sql_res2); + } + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + online_char_db->foreach(online_char_db,char_db_setoffline,-1); +} +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +// Removed since nothing GM related goes on in the char server [CLOWNISIUS] +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + + +int compare_item(struct item *a, struct item *b) { + + if(a->id == b->id && + a->nameid == b->nameid && + a->amount == b->amount && + a->equip == b->equip && + a->identify == b->identify && + a->refine == b->refine && + a->attribute == b->attribute) + { + int i; + for (i=0; icard[i]==b->card[i]; i++); + return (i == MAX_SLOTS); + } + return 0; +} + +static void* create_charstatus(DBKey key, va_list args) { + struct mmo_charstatus *cp; + cp = (struct mmo_charstatus *) aCalloc(1,sizeof(struct mmo_charstatus)); + cp->char_id = key.i; + return cp; +} + +int mmo_char_tosql(int char_id, struct mmo_charstatus *p){ + int i=0,j,party_exist,guild_exist; + int count = 0; + int diff = 0; + char *tmp_ptr; //Building a single query should be more efficient than running + //multiple queries for each thing about to be saved, right? [Skotlex] + char save_status[128]; //For displaying save information. [Skotlex] + struct mmo_charstatus *cp; + struct itemtmp mapitem[MAX_GUILD_STORAGE]; + + if (char_id!=p->char_id) return 0; + + cp = idb_ensure(char_db_, char_id, create_charstatus); + +// ShowInfo("Saving char "CL_WHITE"%d"CL_RESET" (%s)...\n",char_id,char_dat[0].name); + memset(save_status, 0, sizeof(save_status)); + diff = 0; + //map inventory data + for(i=0;iinventory[i], &cp->inventory[i])) + diff = 1; + if(p->inventory[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->inventory[i].id; + mapitem[count].nameid=p->inventory[i].nameid; + mapitem[count].amount = p->inventory[i].amount; + mapitem[count].equip = p->inventory[i].equip; + mapitem[count].identify = p->inventory[i].identify; + mapitem[count].refine = p->inventory[i].refine; + mapitem[count].attribute = p->inventory[i].attribute; + for (j=0; jinventory[i].card[j]; + count++; + } + } + //printf("- Save item data to MySQL!\n"); + if (diff) + if (!memitemdata_to_sql(mapitem, count, p->char_id,TABLE_INVENTORY)) + strcat(save_status, " inventory"); + + count = 0; + diff = 0; + + //map cart data + for(i=0;icart[i], &cp->cart[i])) + diff = 1; + if(p->cart[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->cart[i].id; + mapitem[count].nameid=p->cart[i].nameid; + mapitem[count].amount = p->cart[i].amount; + mapitem[count].equip = p->cart[i].equip; + mapitem[count].identify = p->cart[i].identify; + mapitem[count].refine = p->cart[i].refine; + mapitem[count].attribute = p->cart[i].attribute; + for (j=0; jcart[i].card[j]; + count++; + } + } + + if (diff) + if (!memitemdata_to_sql(mapitem, count, p->char_id,TABLE_CART)) + strcat(save_status, " cart"); + + if ( + (p->base_exp != cp->base_exp) || (p->base_level != cp->base_level) || + (p->job_level != cp->job_level) || (p->job_exp != cp->job_exp) || + (p->zeny != cp->zeny) || + (p->last_point.x != cp->last_point.x) || (p->last_point.y != cp->last_point.y) || + (p->max_hp != cp->max_hp) || (p->hp != cp->hp) || + (p->max_sp != cp->max_sp) || (p->sp != cp->sp) || + (p->status_point != cp->status_point) || (p->skill_point != cp->skill_point) || + (p->str != cp->str) || (p->agi != cp->agi) || (p->vit != cp->vit) || + (p->int_ != cp->int_) || (p->dex != cp->dex) || (p->luk != cp->luk) || + (p->option != cp->option) || + (p->party_id != cp->party_id) || (p->guild_id != cp->guild_id) || + (p->pet_id != cp->pet_id) || (p->weapon != cp->weapon) || + (p->shield != cp->shield) || (p->head_top != cp->head_top) || + (p->head_mid != cp->head_mid) || (p->head_bottom != cp->head_bottom) + ) + { //Save status + //Check for party + party_exist=1; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `party_id` = '%d'",party_db, p->party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { //In case of failure, don't touch the data. [Skotlex + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) + party_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + } + + //check guild_exist + guild_exist=1; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id` = '%d'",guild_db, p->guild_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { //If we fail to confirm, don't touch the data. + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) + guild_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + } + + if (guild_exist==0) p->guild_id=0; + if (party_exist==0) p->party_id=0; + + //query + sprintf(tmp_sql ,"UPDATE `%s` SET `base_level`='%d', `job_level`='%d'," + "`base_exp`='%d', `job_exp`='%d', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d'," + "`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d'" + " WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->party_id, p->guild_id, p->pet_id, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, + mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, + p->account_id, p->char_id + ); + + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " status"); + } + + //Values that will seldom change (to speed up saving) + if ( + (p->hair != cp->hair) || (p->hair_color != cp->hair_color) || (p->clothes_color != cp->clothes_color) || + (p->class_ != cp->class_) || + (p->partner_id != cp->partner_id) || (p->father != cp->father) || + (p->mother != cp->mother) || (p->child != cp->child) || + (p->karma != cp->karma) || (p->manner != cp->manner) || + (p->fame != cp->fame) + ) + { + sprintf(tmp_sql ,"UPDATE `%s` SET `class`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d'," + "`partner_id`='%d', `father`='%d', `mother`='%d', `child`='%d'," + "`karma`='%d',`manner`='%d', `fame`='%d'" + " WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->class_, + p->hair, p->hair_color, p->clothes_color, + p->partner_id, p->father, p->mother, p->child, + p->karma, p->manner, p->fame, + p->account_id, p->char_id + ); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " status2"); + } + + + diff = 0; + + for(i=0;imemo_point[i].map == cp->memo_point[i].map && p->memo_point[i].x == cp->memo_point[i].x && p->memo_point[i].y == cp->memo_point[i].y) + continue; + diff = 1; + break; + } + + if (diff) + { //Save memo + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //insert here. + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr, "INSERT INTO `%s`(`char_id`,`map`,`x`,`y`) VALUES ", memo_db); + count = 0; + for(i=0;imemo_point[i].map){ + tmp_ptr += sprintf(tmp_ptr,"('%d', '%s', '%d', '%d'),", + char_id, mapindex_id2name(p->memo_point[i].map), p->memo_point[i].x, p->memo_point[i].y); + count++; + } + } + if (count) + { //Dangerous? Only if none of the above sprintf worked. [Skotlex] + tmp_ptr[-1] = '\0'; //Remove the trailing comma. + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " memo"); + } else //Memo Points cleared (how is this possible?). + strcat(save_status, " memo"); + } + + diff = 0; + for(i=0;iskill[i].lv != 0) && (p->skill[i].id == 0)) + p->skill[i].id = i; // Fix skill tree + + if((p->skill[i].id != cp->skill[i].id) || (p->skill[i].lv != cp->skill[i].lv) || + (p->skill[i].flag != cp->skill[i].flag)) + { + diff = 1; + break; + } + } + + if (diff) + { //Save skills + + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr,"INSERT INTO `%s`(`char_id`,`id`,`lv`) VALUES ", skill_db); + count = 0; + //insert here. + for(i=0;iskill[i].id && p->skill[i].flag!=1) + { + tmp_ptr += sprintf(tmp_ptr,"('%d','%d','%d'),", + char_id, p->skill[i].id, (p->skill[i].flag==0)?p->skill[i].lv:p->skill[i].flag-2); + count++; + } + } + + if (count) + { + tmp_ptr[-1] = '\0'; //Remove trailing comma. + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " skills"); + } else //Skills removed (reset?) + strcat(save_status, " skills"); + } +/* Saving of global registry values is now handled by the inter-server. [Skotlex] + diff = 0; + for(i=0;iglobal_reg_num;i++) { + if ((p->global_reg[i].str == NULL) && (cp->global_reg[i].str == NULL)) + continue; + if (((p->global_reg[i].str == NULL) != (cp->global_reg[i].str == NULL)) || + strcmp(p->global_reg[i].value, cp->global_reg[i].value) != 0 || + strcmp(p->global_reg[i].str, cp->global_reg[i].str) != 0 + ) { + diff = 1; + break; + } + } + + if (diff) + { //Save global registry. + //`global_reg_value` (`char_id`, `str`, `value`) + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, p->char_id); + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //insert here. + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr,"INSERT INTO `%s` (`type`, `char_id`, `str`, `value`) VALUES", reg_db); + count = 0; + for(i=0;iglobal_reg_num;i++) + { + if (p->global_reg[i].str && p->global_reg[i].value) + { + tmp_ptr += sprintf(tmp_ptr,"('3','%d','%s','%s'),", + char_id, jstrescapecpy(temp_str,p->global_reg[i].str), jstrescapecpy(temp_str2,p->global_reg[i].value)); + if (++count%100 == 0) + { //Save every X registers to avoid overflowing tmp_sql [Skotlex] + tmp_ptr[-1] = '\0'; + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " global_reg"); + + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr,"INSERT INTO `%s` (`type`, `char_id`, `str`, `value`) VALUES", reg_db); + count = 0; + } + } + } + + if (count) + { + tmp_ptr[-1] = '\0'; + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " global_reg"); + } else //Values cleared. + strcat(save_status, " global_reg"); + } +*/ + diff = 0; + for(i = 0; i < MAX_FRIENDS; i++){ + if(p->friends[i].char_id != cp->friends[i].char_id || + p->friends[i].account_id != cp->friends[i].account_id){ + diff = 1; + break; + } + } + + if(diff == 1) + { //Save friends + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id`='%d'", friend_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr, "INSERT INTO `%s` (`char_id`, `friend_account`, `friend_id`) VALUES ", friend_db); + count = 0; + for(i = 0; i < MAX_FRIENDS; i++){ + if(p->friends[i].char_id > 0) + { + tmp_ptr += sprintf(tmp_ptr, "('%d','%d','%d'),", char_id, p->friends[i].account_id, p->friends[i].char_id); + count++; + } + } + if (count) + { + tmp_ptr[-1] = '\0'; //Remove the last comma. [Skotlex] + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " friends"); + } else //Friend list cleared. + strcat(save_status, " friends"); + + } + + if (save_status[0]!='\0' && save_log) + ShowInfo("Saved char %d - %s:%s.\n", char_id, char_dat[0].name, save_status); + memcpy(cp, p, sizeof(struct mmo_charstatus)); + + return 0; +} + +// [Ilpalazzo-sama] +int memitemdata_to_sql(struct itemtmp mapitem[], int count, int char_id, int tableswitch) +{ + int i,j, flag, id; + char *tablename; + char selectoption[16]; + char * str_p = tmp_sql; + + switch (tableswitch) { + case TABLE_INVENTORY: + tablename = inventory_db; // no need for sprintf here as *_db are char*. + sprintf(selectoption,"char_id"); + break; + case TABLE_CART: + tablename = cart_db; + sprintf(selectoption,"char_id"); + break; + case TABLE_STORAGE: + tablename = storage_db; + sprintf(selectoption,"account_id"); + break; + case TABLE_GUILD_STORAGE: + tablename = guild_storage_db; + sprintf(selectoption,"guild_id"); + break; + default: + ShowError("Invalid table name!\n"); + return 1; + } + + //=======================================mysql database data > memory=============================================== + + str_p += sprintf(str_p, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`"); + + for (j=0; j SQL =============================== + if(!itemdb_isequip(mapitem[i].nameid)) + { //Quick update of stackable items. Update Qty and Equip should be enough, but in case we are also updating identify + sprintf(tmp_sql,"UPDATE `%s` SET `equip`='%d', `identify`='%d', `amount`='%d' WHERE `id`='%d' LIMIT 1", + tablename, mapitem[i].equip, mapitem[i].identify,mapitem[i].amount, id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } else + { //Equipment or Misc item, just update all fields. + str_p = tmp_sql; + str_p += sprintf(str_p,"UPDATE `%s` SET `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d'", + tablename, mapitem[i].equip, mapitem[i].identify, mapitem[i].refine, mapitem[i].attribute); + + for(j=0; jchar_id = char_id; + if (save_log) + ShowInfo("Char load request (%d)\n", char_id); + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //splite 2 parts. cause veeeery long SQL syntax + + sprintf(tmp_sql, "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`, `max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (!sql_row) + { //Just how does this happens? [Skotlex] + ShowError("Requested non-existant character id: %d!\n", char_id); + return 0; + } + + p->char_id = char_id; + p->account_id = atoi(sql_row[1]); + p->char_num = atoi(sql_row[2]); + strcpy(p->name, sql_row[3]); + p->class_ = atoi(sql_row[4]); + p->base_level = atoi(sql_row[5]); + p->job_level = atoi(sql_row[6]); + p->base_exp = atoi(sql_row[7]); + p->job_exp = atoi(sql_row[8]); + p->zeny = atoi(sql_row[9]); + p->str = atoi(sql_row[10]); + p->agi = atoi(sql_row[11]); + p->vit = atoi(sql_row[12]); + p->int_ = atoi(sql_row[13]); + p->dex = atoi(sql_row[14]); + p->luk = atoi(sql_row[15]); + p->max_hp = atoi(sql_row[16]); + p->hp = atoi(sql_row[17]); + p->max_sp = atoi(sql_row[18]); + p->sp = atoi(sql_row[19]); + p->status_point = atoi(sql_row[20]); + p->skill_point = atoi(sql_row[21]); + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status"); + } else + ShowError("Load char failed (%d - table %s).\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + sprintf(tmp_sql, "SELECT `option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`," + "`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`, `partner_id`, `father`, `mother`, `child`, `fame`" + "FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) { + + p->option = atoi(sql_row[0]); p->karma = atoi(sql_row[1]); p->manner = atoi(sql_row[2]); + p->party_id = atoi(sql_row[3]); p->guild_id = atoi(sql_row[4]); p->pet_id = atoi(sql_row[5]); + + p->hair = atoi(sql_row[6]); p->hair_color = atoi(sql_row[7]); p->clothes_color = atoi(sql_row[8]); + p->weapon = atoi(sql_row[9]); p->shield = atoi(sql_row[10]); + p->head_top = atoi(sql_row[11]); p->head_mid = atoi(sql_row[12]); p->head_bottom = atoi(sql_row[13]); + p->last_point.map = mapindex_name2id(sql_row[14]); p->last_point.x = atoi(sql_row[15]); p->last_point.y = atoi(sql_row[16]); + p->save_point.map = mapindex_name2id(sql_row[17]); p->save_point.x = atoi(sql_row[18]); p->save_point.y = atoi(sql_row[19]); + p->partner_id = atoi(sql_row[20]); p->father = atoi(sql_row[21]); p->mother = atoi(sql_row[22]); p->child = atoi(sql_row[23]); + p->fame = atoi(sql_row[24]); + + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status2"); + } else + ShowError("Char load failed (%d - table %s)\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + if (p->last_point.x == 0 || p->last_point.y == 0 || p->last_point.map == 0) + memcpy(&p->last_point, &start_point, sizeof(start_point)); + + if (p->save_point.x == 0 || p->save_point.y == 0 || p->save_point.map == 0) + memcpy(&p->save_point, &start_point, sizeof(start_point)); + + //read memo data + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql, "SELECT `map`,`x`,`y` FROM `%s` WHERE `char_id`='%d' ORDER by `memo_id`",memo_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + p->memo_point[i].map = mapindex_name2id(sql_row[0]); + p->memo_point[i].x=atoi(sql_row[1]); + p->memo_point[i].y=atoi(sql_row[2]); + //i ++; + } + mysql_free_result(sql_res); + strcat (t_msg, " memo"); + } + + //read inventory + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + str_p += sprintf(str_p, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`"); + + for (j=0; jinventory[i].id = atoi(sql_row[0]); + p->inventory[i].nameid = atoi(sql_row[1]); + p->inventory[i].amount = atoi(sql_row[2]); + p->inventory[i].equip = atoi(sql_row[3]); + p->inventory[i].identify = atoi(sql_row[4]); + p->inventory[i].refine = atoi(sql_row[5]); + p->inventory[i].attribute = atoi(sql_row[6]); + for (j=0; jinventory[i].card[j] = atoi(sql_row[7+j]); + } + mysql_free_result(sql_res); + strcat (t_msg, " inventory"); + } + + //read cart. + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + str_p = tmp_sql; + str_p += sprintf(str_p, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`"); + + for (j=0; jcart[i].id = atoi(sql_row[0]); + p->cart[i].nameid = atoi(sql_row[1]); + p->cart[i].amount = atoi(sql_row[2]); + p->cart[i].equip = atoi(sql_row[3]); + p->cart[i].identify = atoi(sql_row[4]); + p->cart[i].refine = atoi(sql_row[5]); + p->cart[i].attribute = atoi(sql_row[6]); + for(j=0; jcart[i].card[j] = atoi(sql_row[7+j]); + } + mysql_free_result(sql_res); + strcat (t_msg, " cart"); + } + + //read skill + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql, "SELECT `id`, `lv` FROM `%s` WHERE `char_id`='%d'",skill_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + n = atoi(sql_row[0]); + p->skill[n].id = n; //memory!? shit!. + p->skill[n].lv = atoi(sql_row[1]); + } + mysql_free_result(sql_res); + strcat (t_msg, " skills"); + } +/* Global-reg loading is now handled by the inter-server. + //global_reg + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + i = 0; + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + strcpy (p->global_reg[i].str, sql_row[0]); + strcpy (p->global_reg[i].value, sql_row[1]); + } + mysql_free_result(sql_res); + strcat (t_msg, " reg_values"); + } + p->global_reg_num=i; +*/ + //Shamelessly stolen from its_sparky (ie: thanks) and then assimilated by [Skotlex] + //Friend list + sprintf(tmp_sql, "SELECT f.friend_account, f.friend_id, c.name FROM `%s` f LEFT JOIN `%s` c ON f.friend_account=c.account_id AND f.friend_id=c.char_id WHERE f.char_id='%d'", friend_db, char_db, char_id); + + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + if(sql_res) + { + for(i = 0; (sql_row = mysql_fetch_row(sql_res)) && ifriends[i].account_id = atoi(sql_row[0]); + p->friends[i].char_id = atoi(sql_row[1]); + strncpy(p->friends[i].name, sql_row[2], NAME_LENGTH-1); //The -1 is to avoid losing the ending \0 [Skotlex] + } + } + } + mysql_free_result(sql_res); + strcat (t_msg, " friends"); + } + + if (save_log) + ShowInfo("Loaded char (%d - %s): %s\n", char_id, p->name, t_msg); //ok. all data load successfuly! + + cp = idb_ensure(char_db_, char_id, create_charstatus); + memcpy(cp, p, sizeof(struct mmo_charstatus)); + return 1; +} + +// For quick selection of data when displaying the char menu. [Skotlex] +// +int mmo_char_fromsql_short(int char_id, struct mmo_charstatus *p){ + char t_msg[128]; + + memset(p, 0, sizeof(struct mmo_charstatus)); + t_msg[0]= '\0'; + + p->char_id = char_id; +// ShowInfo("Quick Char load request (%d)\n", char_id); + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //splite 2 parts. cause veeeery long SQL syntax + + sprintf(tmp_sql, "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`, `max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (!sql_row) + { //Just how does this happens? [Skotlex] + ShowError("Requested non-existant character id: %d!\n", char_id); + return 0; + } + p->char_id = char_id; + p->account_id = atoi(sql_row[1]); + p->char_num = atoi(sql_row[2]); + strcpy(p->name, sql_row[3]); + p->class_ = atoi(sql_row[4]); + p->base_level = atoi(sql_row[5]); + p->job_level = atoi(sql_row[6]); + p->base_exp = atoi(sql_row[7]); + p->job_exp = atoi(sql_row[8]); + p->zeny = atoi(sql_row[9]); + p->str = atoi(sql_row[10]); + p->agi = atoi(sql_row[11]); + p->vit = atoi(sql_row[12]); + p->int_ = atoi(sql_row[13]); + p->dex = atoi(sql_row[14]); + p->luk = atoi(sql_row[15]); + p->max_hp = atoi(sql_row[16]); + p->hp = atoi(sql_row[17]); + p->max_sp = atoi(sql_row[18]); + p->sp = atoi(sql_row[19]); + p->status_point = atoi(sql_row[20]); + p->skill_point = atoi(sql_row[21]); + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status"); + } else + ShowError("Load char failed (%d - table %s).\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + sprintf(tmp_sql, "SELECT `option`,`karma`,`manner`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`" + "FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) { + + p->option = atoi(sql_row[0]); p->karma = atoi(sql_row[1]); p->manner = atoi(sql_row[2]); + p->hair = atoi(sql_row[3]); p->hair_color = atoi(sql_row[4]); p->clothes_color = atoi(sql_row[5]); + p->weapon = atoi(sql_row[6]); p->shield = atoi(sql_row[7]); + p->head_top = atoi(sql_row[8]); p->head_mid = atoi(sql_row[9]); p->head_bottom = atoi(sql_row[10]); + + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status2"); + } else + ShowError("Char load failed (%d - table %s)\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + if (save_log) + ShowInfo("Quick Loaded char (%d - %s): %s\n", char_id, p->name, t_msg); //ok. all data load successfuly! + + return 1; +} +//========================================================================================================== +int mmo_char_sql_init(void) { + int charcount; + + + ShowInfo("Begin Initializing.......\n"); + char_db_= db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA, sizeof(int)); + // memory initialize + // no need to set twice size in this routine. but some cause segmentation error. :P + // The hell? Why segmentation faults? Sounds like a bug that needs addressing... [Skotlex] + ShowDebug("initializing char memory...(%d byte)\n",sizeof(struct mmo_charstatus)*2); + CREATE(char_dat, struct mmo_charstatus, 2); + + memset(char_dat, 0, sizeof(struct mmo_charstatus)*2); + + //Check for max id (in case new chars would get their IDs set below 150K) [Skotlex] + sprintf(tmp_sql , "SELECT max(`char_id`) FROM `%s`", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) + { + if (mysql_num_rows(sql_res) > 0 && + (sql_row = mysql_fetch_row(sql_res)) != NULL && + sql_row[0] != NULL && atoi(sql_row[0]) >= char_id_count) + char_id_count = 0; //No need for setting the char id. + mysql_free_result(sql_res); + } + } + + sprintf(tmp_sql, "SELECT `char_id` FROM `%s`", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + }else{ + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + charcount = (int)mysql_num_rows(sql_res); + ShowStatus("total char data -> '%d'.......\n", charcount); + mysql_free_result(sql_res); + }else{ + ShowStatus("total char data -> '0'.......\n"); + } + } + + if(char_per_account == 0){ + ShowStatus("Chars per Account: 'Unlimited'.......\n"); + }else{ + ShowStatus("Chars per Account: '%d'.......\n", char_per_account); + } + + //the 'set offline' part is now in check_login_conn ... + //if the server connects to loginserver + //it will dc all off players + //and send the loginserver the new state.... + + ShowInfo("Finished initilizing.......\n"); + + return 0; +} + +//========================================================================================================== + +int make_new_char_sql(int fd, unsigned char *dat) { + struct char_session_data *sd; + char name[NAME_LENGTH]; + char t_name[NAME_LENGTH*2]; + unsigned int i; // Used in for loop and comparing with strlen, safe to be unsigned. [Lance] + int char_id, temp; + + strncpy(name, dat, NAME_LENGTH); + name[NAME_LENGTH-1] = '\0'; //Always terminate string. + trim(name,TRIM_CHARS); //Trim character name. [Skotlex] + jstrescapecpy(t_name, name); + + // disabled until fixed >.> + // Note: escape characters should be added to jstrescape()! + //mysql_real_escape_string(&mysql_handle, t_name, t_name_temp, sizeof(t_name_temp)); + + if (!session_isValid(fd) || !(sd = (struct char_session_data*)session[fd]->session_data)) + return -2; + + ShowInfo("New character request (%d)\n", sd->account_id); + + //check name != main chat nick [LuzZza] + if(strcmpi(name, main_chat_nick) == 0) { + ShowInfo("Create char failed (%d): this nick (%s) reserved for mainchat messages.\n", sd->account_id, name); + return -2; + } + + //check for charcount (maxchars) :) + if(char_per_account != 0){ + sprintf(tmp_sql, "SELECT `account_id` FROM `%s` WHERE `account_id` = '%d'", char_db, sd->account_id); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + //ok + temp = (int)mysql_num_rows(sql_res); + if(temp >= char_per_account){ + //hehe .. limit exceeded :P + ShowInfo("Create char failed (%d): charlimit exceeded.\n", sd->account_id); + mysql_free_result(sql_res); + return -2; + } + mysql_free_result(sql_res); + } + } + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; i < NAME_LENGTH && name[i]; i++) + if (strchr(char_name_letters, name[i]) == NULL) + return -2; + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden + for (i = 0; i < NAME_LENGTH && name[i]; i++) + if (strchr(char_name_letters, name[i]) != NULL) + return -2; + } // else, all letters/symbols are authorised (except control char removed before) + + //check stat error + if ((dat[24]+dat[25]+dat[26]+dat[27]+dat[28]+dat[29]!=6*5 ) || // stats + (dat[30] >= 9) || // slots (dat[30] can not be negativ) + (dat[33] <= 0) || (dat[33] >= 24) || // hair style + (dat[31] >= 9)) { // hair color (dat[31] can not be negativ) + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowWarning("Create char failed (%d): stats error (bot cheat?!)\n", sd->account_id); + return -2; + } // for now we have checked: stat points used <31, char slot is less then 9, hair style/color values are acceptable + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowWarning("Create char failed (%d): stats error (bot cheat?!)\n", sd->account_id); + return -2; + } + } // now we know that every stat has proper value but we have to check if str/int agi/luk vit/dex pairs are correct + + if( ((dat[24]+dat[27]) > 10) || ((dat[25]+dat[29]) > 10) || ((dat[26]+dat[28]) > 10) ) { + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowWarning("Create char failed (%d): stats error (bot cheat?!)\n", sd->account_id); + return -2; + } // now when we have passed all stat checks + + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + //printf("make new char %d-%d %s %d, %d, %d, %d, %d, %d - %d, %d" RETCODE, + // fd, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + + //Check Name (already in use?) + sprintf(tmp_sql, "SELECT `name` FROM `%s` WHERE `name` = '%s'",char_db, t_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -2; + } + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + temp = (int)mysql_num_rows(sql_res); + mysql_free_result(sql_res); + if (temp > 0) { + ShowInfo("Create char failed: charname already in use\n"); + return -1; + } + } + + // check char slot. + sprintf(tmp_sql, "SELECT `account_id`, `char_num` FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d'",char_db, sd->account_id, dat[30]); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + if(sql_res){ + temp = (int)mysql_num_rows(sql_res); + mysql_free_result(sql_res); + if (temp > 0) { + ShowWarning("Create char failed (%d, slot: %d), slot already in use\n", sd->account_id, dat[30]); + return -2; + } + } + + //New Querys [Sirius] + //Insert the char to the 'chardb' ^^ + if (char_id_count) //Force initial char id. [Skotlex] + sprintf(tmp_sql, "INSERT INTO `%s` (`char_id`,`account_id`, `char_num`, `name`, `zeny`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `max_hp`, `hp`," + "`max_sp`, `sp`, `hair`, `hair_color`, `last_map`, `last_x`, `last_y`, `save_map`, `save_x`, `save_y`) VALUES (" + "'%d', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d')", + char_db, char_id_count, sd->account_id , dat[30] , t_name, start_zeny, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], + (40 * (100 + dat[26])/100) , (40 * (100 + dat[26])/100 ), (11 * (100 + dat[27])/100), (11 * (100 + dat[27])/100), dat[33], dat[31], + mapindex_id2name(start_point.map), start_point.x, start_point.y, mapindex_id2name(start_point.map), start_point.x, start_point.y); + else + sprintf(tmp_sql, "INSERT INTO `%s` (`account_id`, `char_num`, `name`, `zeny`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `max_hp`, `hp`," + "`max_sp`, `sp`, `hair`, `hair_color`, `last_map`, `last_x`, `last_y`, `save_map`, `save_x`, `save_y`) VALUES (" + "'%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d')", + char_db, sd->account_id , dat[30] , t_name, start_zeny, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], + (40 * (100 + dat[26])/100) , (40 * (100 + dat[26])/100 ), (11 * (100 + dat[27])/100), (11 * (100 + dat[27])/100), dat[33], dat[31], + mapindex_id2name(start_point.map), start_point.x, start_point.y, mapindex_id2name(start_point.map), start_point.x, start_point.y); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -2; //No, stop the procedure! + } + + if (char_id_count) //Clear this out for future inserts. + char_id_count = 0; + + //Now we need the charid from sql! + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id , dat[30] , t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + //delete the char ..(no trash in DB!) + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //XD end of the (World? :P) .. charcreate (denied) + } else { + //query ok -> get the data! + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + sql_row = mysql_fetch_row(sql_res); + char_id = sql_row?atoi(sql_row[0]):0; //char id :) + mysql_free_result(sql_res); + if(char_id <= 0){ + ShowError("failed (get char id..) CHARID (%d) wrong!\n", char_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //charcreate denied .. + } + }else{ + //prevent to crash (if its false, and we want to free -> segfault :) + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //end ...... -> charcreate failed :) + } + } + + //Give the char the default items + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + if (start_weapon > 0) { //add Start Weapon (Knife?) + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", inventory_db, char_id, start_weapon,1,0x02,1); + if (mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2;//end XD + } + } + if (start_armor > 0) { //Add default armor (cotton shirt?) + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", inventory_db, char_id, start_armor,1,0x10,1); + if (mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d'", inventory_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //end.... + } + } + + ShowInfo("Created char: account: %d, char: %d, slot: %d, name: %s\n", sd->account_id, char_id, dat[30], name); + return char_id; +} + +/*----------------------------------------------------------------------------------------------------------*/ +/* Delete char - davidsiaw */ +/*----------------------------------------------------------------------------------------------------------*/ +/* Returns 0 if successful + * Returns < 0 for error + */ +int delete_char_sql(int char_id, int partner_id) +{ + char char_name[NAME_LENGTH], t_name[NAME_LENGTH*2]; //Name needs be escaped. + int account_id=0, party_id=0, guild_id=0; + + sprintf(tmp_sql, "SELECT `name`,`account_id`,`party_id`,`guild_id` FROM `%s` WHERE `char_id`='%d'",char_db, char_id); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if(sql_res) + sql_row = mysql_fetch_row(sql_res); + + if (sql_res == NULL || sql_row == NULL) + { + ShowError("delete_char_sql: Unable to fetch character data, deletion aborted.\n"); + return -1; + } + strncpy(char_name, sql_row[0], NAME_LENGTH); + char_name[NAME_LENGTH-1] = '\0'; + jstrescapecpy(t_name, char_name); //Escape string for sql use... [Skotlex] + account_id = atoi(sql_row[1]); + party_id = atoi(sql_row[2]); + guild_id = atoi(sql_row[3]); + mysql_free_result(sql_res); //Let's free this as soon as possible to avoid problems later on. + + /* Divorce [Wizputer] */ + if (partner_id) { + sprintf(tmp_sql,"UPDATE `%s` SET `partner_id`='0' WHERE `char_id`='%d'",char_db,partner_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"DELETE FROM `%s` WHERE (`nameid`='%d' OR `nameid`='%d') AND `char_id`='%d'",inventory_db,WEDDING_RING_M,WEDDING_RING_F,partner_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + //Make the character leave the party [Skotlex] + if (party_id) + inter_party_leave(party_id, account_id, char_id); + + /* delete char's pet */ + //Delete the hatched pet if you have one... + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d' AND `incuvate` = '0'",pet_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + // Komurka's suggested way to clear pets, modified by [Skotlex] (because I always personalize what I do :X) + //Removing pets that are in the char's inventory.... + { //NOTE: The syntax for multi-table deletes is a bit changed between 4.0 and 4.1 regarding aliases, so we have to consider the version... [Skotlex] + //Since we only care about the major and minor version, a double conversion is good enough. (4.1.20 -> 4.10000) + double mysql_version = atof(mysql_get_server_info(&mysql_handle)); + + sprintf(tmp_sql, + "delete FROM `%s` USING `%s` as c LEFT JOIN `%s` as i ON c.char_id = i.char_id, `%s` as p WHERE c.char_id = '%d' AND i.card0 = -256 AND p.pet_id = (i.card1|(i.card2<<2))", + (mysql_version<4.1?pet_db:"p"), char_db, inventory_db, pet_db, char_id); + + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //Removing pets that are in the char's cart.... + sprintf(tmp_sql, + "delete FROM `%s` USING `%s` as c LEFT JOIN `%s` as i ON c.char_id = i.char_id, `%s` as p WHERE c.char_id = '%d' AND i.card0 = -256 AND p.pet_id = (i.card1|(i.card2<<2))", + (mysql_version<4.1?pet_db:"p"), char_db, cart_db, pet_db, char_id); + + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + /* delete char's friends list */ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d'",friend_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete char from other's friend list */ + //NOTE: Won't this cause problems for people who are already online? [Skotlex] + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `friend_id` = '%d'",friend_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete inventory */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",inventory_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete cart inventory */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",cart_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete memo areas */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete skills */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete character */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",char_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* No need as we used inter_guild_leave [Skotlex] + // Also delete info from guildtables. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",guild_member_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + */ + + sprintf(tmp_sql, "SELECT `guild_id` FROM `%s` WHERE `master` = '%s'", guild_db, t_name); + + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res == NULL) { + if (mysql_errno(&mysql_handle) != 0) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -1; + } else { + int rows = (int)mysql_num_rows(sql_res); + mysql_free_result(sql_res); + if (rows > 0) { + mapif_parse_BreakGuild(0,guild_id); + } + else if (guild_id) //Leave your guild. + inter_guild_leave(guild_id, account_id, char_id); + } + } + return 0; +} + +//========================================================================================================== + +void mmo_char_sync(void) +{ + ShowWarning("mmo_char_sync() - nothing to do\n"); +} + +// to do +/////////////////////////// + +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + ShowWarning("mmo_char_sync_timer() tic - no works to do\n"); + return 0; +} + +int count_users(void) { + int i, users; + + if (login_fd > 0 && session[login_fd]){ + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] > 0) { + users += server[i].users; + } + } + return users; + } + return 0; +} + +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num = 0; + struct mmo_charstatus *p = NULL; +// hehe. commented other. anyway there's no need to use older version. +// if use older packet version just uncomment that! +//#ifdef NEW_006b + const int offset = 24; +//#else +// int offset = 4; +//#endif + +// ShowDebug("mmo_char_send006b start.. (account:%d)\n",sd->account_id); +// printf("offset -> %d...\n",offset); + + set_char_online(-1, 99,sd->account_id); + + //search char. + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id` = '%d'",char_db, sd->account_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + found_num = (int)mysql_num_rows(sql_res); +// ShowInfo("number of chars: %d\n", found_num); + i = 0; + while((sql_row = mysql_fetch_row(sql_res))) { + sd->found_char[i] = atoi(sql_row[0]); + i++; + } + mysql_free_result(sql_res); + } + +// printf("char fetching end (total: %d)....\n", found_num); + + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + memset(WFIFOP(fd, 0), 0, offset + found_num * 106); + WFIFOW(fd, 0) = 0x6b; + WFIFOW(fd, 2) = offset + found_num * 106; + + if (save_log) + ShowInfo("Request Char Data ("CL_BOLD"%d"CL_RESET"):\n",sd->account_id); + + for(i = 0; i < found_num; i++) { + mmo_char_fromsql_short(sd->found_char[i], char_dat); + + p = &char_dat[0]; + + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class_; + WFIFOW(fd,j+54) = p->hair; + + // pecopeco knights/crusaders crash fix + if (p->class_ == 13 || p->class_ == 21 || + p->class_ == 4014 || p->class_ == 4022 || + p->class_ == 4036 || p->class_ == 4044) + WFIFOW(fd,j+56) = 0; + else WFIFOW(fd,j+56) = p->weapon; + + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, NAME_LENGTH); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +// printf("mmo_char_send006b end..\n"); + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session. + //session eof check! + if(fd != login_fd) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (fd == login_fd) { + ShowWarning("Connection to login-server lost (connection #%d).\n", fd); + login_fd = -1; + } + do_close(fd); + return 0; + } + + sd = (struct char_session_data*)session[fd]->session_data; + + // hehe. no need to set user limit on SQL version. :P + // but char limitation is good way to maintain server. :D + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { +// printf("parse_tologin : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch(RFIFOW(fd, 0)){ + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd, 2)) { + //printf("connect login server error : %d\n", RFIFOB(fd, 2)); + ShowError("Can not connect to login-server.\n"); + ShowError("The server communication passwords (default s1/p1) are probably invalid.\n"); + ShowError("Also, please make sure your login db has the correct coounication username/passwords and the gender of the account is S.\n"); + ShowError("The communication passwords are set in map_athena.conf and char_athena.conf\n"); + return 0; + //exit(1); //fixed for server shutdown. + }else { + ShowStatus("Connected to login-server (connection #%d).\n", fd); + if (kick_on_disconnect) + set_all_offline(); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] > 0 && server[i].map[0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + ShowStatus("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd, 3); + break; + + case 0x2713: + if(RFIFOREST(fd)<51) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + sd->connect_until_time = (time_t)RFIFOL(fd,47); + memcpy(sd->email, RFIFOP(fd, 7), 40); + // send characters to player + mmo_char_send006b(i, sd); + } else if(isGM(sd->account_id) >= gm_allow_level) { + sd->connect_until_time = (time_t)RFIFOL(fd,47); + memcpy(sd->email, RFIFOP(fd, 7), 40); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + } + } + RFIFOSKIP(fd,51); + break; + + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + + // login-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + break; + + // Receiving authentification from Freya-type login server (to avoid char->login->char) + case 0x2719: + if (RFIFOREST(fd) < 18) + return 0; + // to conserv a maximum of authentification, search if account is already authentified and replace it + // that will reduce multiple connection too + for(i = 0; i < AUTH_FIFO_SIZE; i++) + if (auth_fifo[i].account_id == RFIFOL(fd,2)) + break; + // if not found, use next value + if (i == AUTH_FIFO_SIZE) { + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + i = auth_fifo_pos; + auth_fifo_pos++; + } + //printf("auth_fifo set (auth #%d) - account: %d, secure: %08x-%08x\n", i, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[i].account_id = RFIFOL(fd,2); + auth_fifo[i].char_id = 0; + auth_fifo[i].login_id1 = RFIFOL(fd,6); + auth_fifo[i].login_id2 = RFIFOL(fd,10); + auth_fifo[i].delflag = 2; // 0: auth_fifo canceled/void, 2: auth_fifo received from login/map server in memory, 1: connection authentified + auth_fifo[i].char_pos = 0; + auth_fifo[i].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[i].ip = RFIFOL(fd,14); + //auth_fifo[i].map_auth = 0; + RFIFOSKIP(fd,18); + break; + +/* case 0x2721: // gm reply. I don't want to support this function. + printf("0x2721:GM reply\n"); + { + int oldacc, newacc; + unsigned char buf[64]; + if (RFIFOREST(fd) < 10) + return 0; + oldacc = RFIFOL(fd, 2); + newacc = RFIFOL(fd, 6); + RFIFOSKIP(fd, 10); + if (newacc > 0) { + for(i=0;i map\n"); + } + break; +*/ + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex; + unsigned char buf[16]; + + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + sprintf(tmp_sql, "SELECT `char_id`,`class`,`skill_point`,`guild_id` FROM `%s` WHERE `account_id` = '%d'",char_db, acc); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + while(sql_res && (sql_row = mysql_fetch_row(sql_res))) { + int char_id, guild_id, jobclass, skill_point, class_; + char_id = atoi(sql_row[0]); + jobclass = atoi(sql_row[1]); + skill_point = atoi(sql_row[2]); + guild_id = atoi(sql_row[3]); + class_ = jobclass; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + class_ = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + class_ = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + class_ = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19,20 4020,4021 and 4042,4043 + sprintf(tmp_sql, "SELECT `lv` FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while(( sql_row = mysql_fetch_row(sql_res))) { + skill_point += atoi(sql_row[0]); + } + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + sprintf(tmp_sql, "UPDATE `%s` SET `equip` = '0' WHERE `char_id` = '%d'",inventory_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d' , `skill_point`='%d' , `weapon`='0' , `shield='0' , `head_top`='0' , `head_mid`='0' , `head_bottom`='0' WHERE `char_id` = '%d'",char_db, class_, skill_point, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (guild_id) //If there is a guild, update the guild_member data [Skotlex] + inter_guild_sex_changed(guild_id, acc, char_id, sex); + } + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == acc) { + session[i]->eof = 1; + break; + } + } + } + + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + + mapif_sendall(buf, 7); + } + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { //Receive account_reg2 registry, forward to map servers. + unsigned char buf[ACCOUNT_REG2_NUM*(256+32+2)+16]; + memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2)); +// WBUFW(buf,0) = 0x2b11; + WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex] + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); + } + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + session[i]->eof = 1; + break; + } + } + } + RFIFOSKIP(fd,11); + break; + + case 0x2732: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + unsigned char buf[32000]; + if (gm_account != NULL) + aFree(gm_account); + gm_account = (struct gm_account*)aCalloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + ShowStatus("From login-server: receiving information of %d GM accounts.\n", GM_num); + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b15; + mapif_sendall(buf, RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // Receive GM accounts [Freya login server packet by Yor] + case 0x2733: + // add test here to remember that the login-server is Freya-type + // sprintf (login_server_type, "Freya"); + if (RFIFOREST(fd) < 7) + return 0; + { + int new_level = 0; + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == RFIFOL(fd,2)) { + if (gm_account[i].level != (int)RFIFOB(fd,6)) { + gm_account[i].level = (int)RFIFOB(fd,6); + new_level = 1; + } + break; + } + // if not found, add it + if (i == GM_num) { + // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???) + // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows) + if (((int)RFIFOB(fd,6)) > 0 && GM_num < 4000) { + if (GM_num == 0) { + gm_account = (struct gm_account*)aMalloc(sizeof(struct gm_account)); + } else { + gm_account = (struct gm_account*)aRealloc(gm_account, sizeof(struct gm_account) * (GM_num + 1)); + } + gm_account[GM_num].account_id = RFIFOL(fd,2); + gm_account[GM_num].level = (int)RFIFOB(fd,6); + new_level = 1; + GM_num++; + if (GM_num >= 4000) + ShowWarning("4000 GM accounts found. Next GM accounts are not readed.\n"); + } + } + if (new_level == 1) { + ShowStatus("From login-server: receiving GM account information (%d: level %d).\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + mapif_send_gmaccounts(); + + //create_online_files(); // not change online file for only 1 player (in next timer, that will be done + // send gm acccounts level to map-servers + } + } + RFIFOSKIP(fd,7); + break; + + //Login server request to kick a character out. [Skotlex] + case 0x2734: + if (RFIFOREST(fd) < 6) + return 0; + { + struct online_char_data* character; + int aid = RFIFOL(fd,2); + if ((character = idb_get(online_char_db, aid)) != NULL) + { //Kick out this player. + if (character->server > -1) + { //Kick it from the map server it is on. + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + if (!character->waiting_disconnect) + add_timer(gettick()+15000, chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = 1; + } else { //Manual kick from char server. + struct char_session_data *tsd; + int i; + for(i = 0; i < fd_max; i++) { + if (session[i] && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == aid) + { + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; + WFIFOSET(i,3); + break; + } + } + if (i == fd_max) //Shouldn't happen, but just in case. + set_char_offline(99, aid); + } + } + RFIFOSKIP(fd,6); + } + break; + + default: + ShowError("Unknown packet 0x%04x from login server, disconnecting.\n", RFIFOW(fd, 0)); + session[fd]->eof = 1; + return 0; + } + } + + RFIFOFLUSH(fd); + + return 0; +} + +int request_accreg2(int account_id, int char_id) { + if (login_fd > 0) { + WFIFOW(login_fd, 0) = 0x272e; + WFIFOL(login_fd, 2) = account_id; + WFIFOL(login_fd, 6) = char_id; + WFIFOSET(login_fd, 10); + return 1; + } + return 0; +} +//Send packet forward to login-server for account saving +int save_accreg2(unsigned char* buf, int len) { + if (login_fd > 0) { + WFIFOHEAD(login_fd, len+4); + memcpy(WFIFOP(login_fd,4), buf, len); + WFIFOW(login_fd,0) = 0x2728; + WFIFOW(login_fd,2) = len+4; + WFIFOSET(login_fd,len+4); + return 1; + } + return 0; +} +int search_mapserver(unsigned short map, long ip, short port); + +int parse_frommap(int fd) { + int i = 0, j = 0; + int id; +// int auth_fifo_flag=0; + + // Sometimes fd=0, and it will cause server crash. Don't know why. :( + if (fd <= 0) { + ShowError("parse_frommap error fd=%d\n", fd); + return 0; + } + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if(id == MAX_MAP_SERVERS) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (id < MAX_MAP_SERVERS) { + unsigned char buf[16384]; + ShowStatus("Map-server %d (session #%d) has disconnected.\n", id, fd); + //Notify other map servers that this one is gone. [Skotlex] + WBUFW(buf,0) = 0x2b20; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[id].map[i]) + WBUFW(buf,10+(j++)*4) = server[id].map[i]; + if (j > 0) { + WBUFW(buf,2) = j * 4 + 10; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + memset(&server[id], 0, sizeof(struct mmo_map_server)); + sprintf(tmp_sql, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server_fd[id]); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + server_fd[id] = -1; + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server. + } + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { +// printf("parse_frommap : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd, 0)) { + + // map-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + break; + + case 0x2af7: + RFIFOSKIP(fd,2); + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); + } + break; + + // mapserver -> map names recv. + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 4) { + server[id].map[j] = RFIFOW(fd,i); +// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); + j++; + } + i = server[id].ip; + { + unsigned char *p = (unsigned char *)&server[id].ip; + ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + ShowStatus("Map-server %d loading complete.\n", id); + if (kick_on_disconnect) + set_all_offline(); + if (max_account_id != DEFAULT_MAX_ACCOUNT_ID || max_char_id != DEFAULT_MAX_CHAR_ID) + mapif_send_maxid(max_account_id, max_char_id); //Send the current max ids to the server to keep in sync [Skotlex] + } + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, NAME_LENGTH); // name for wisp to player + WFIFOSET(fd,3+NAME_LENGTH); + //WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + ShowWarning("Map-Server %d have NO maps.\n", id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 4 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 4); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] > 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i]) + WFIFOW(fd,10+(j++)*4) = server[x].map[i]; + if (j > 0) { + WFIFOW(fd,2) = j * 4 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + //Packet command is now used for sc_data request. [Skotlex] + case 0x2afc: + if (RFIFOREST(fd) < 10) + return 0; + { + int aid, cid; + aid = RFIFOL(fd,2); + cid = RFIFOL(fd,6); + RFIFOSKIP(fd, 10); +#ifdef ENABLE_SC_SAVING + sprintf(tmp_sql, "SELECT type, tick, val1, val2, val3, val4 from `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", + scdata_db, aid, cid); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + break; + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + struct status_change_data data; + int count = 0; + WFIFOW(fd, 0) = 0x2b1d; + WFIFOL(fd, 4) = aid; + WFIFOL(fd, 8) = cid; + while((sql_row = mysql_fetch_row(sql_res))) + { + data.type = atoi(sql_row[0]); + data.tick = atoi(sql_row[1]); + data.val1 = atoi(sql_row[2]); + data.val2 = atoi(sql_row[3]); + data.val3 = atoi(sql_row[4]); + data.val4 = atoi(sql_row[5]); + memcpy(WFIFOP(fd, 14+count*sizeof(struct status_change_data)), &data, sizeof(struct status_change_data)); + count++; + } + if (count > 0) + { + WFIFOW(fd, 2) = 14 + count*sizeof(struct status_change_data); + WFIFOW(fd, 12) = count; + WFIFOSET(fd, WFIFOW(fd,2)); + + //Clear the data once loaded. + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", scdata_db, aid, cid); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } +#endif + break; + } + //set MAP user count + case 0x2afe: + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOW(fd,2) != server[id].users) { + server[id].users = RFIFOW(fd,2); + ShowInfo("User Count: %d (Server: %d)\n", server[id].users, id); + } + RFIFOSKIP(fd, 4); + break; + // set MAP user + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int i, aid, cid; + struct online_char_data* character; + + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown' + server[id].users = RFIFOW(fd,4); + for(i = 0; i < server[id].users; i++) { + aid = RFIFOL(fd,6+i*8); + cid = RFIFOL(fd,6+i*8+4); + character = idb_ensure(online_char_db, aid, create_online_char_data); + if (character->server > -1 && character->server != id) + { + ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, id, aid, cid); + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + } + character->server = id; + character->char_id = cid; + } + //If any chars remain in -2, they will be cleaned in the cleanup timer. + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + } + // char saving + case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int aid = RFIFOL(fd,4), cid = RFIFOL(fd,8); + i = 0; + //check account + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'",char_db, aid, cid); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) i = atoi(sql_row[0]); + if (sql_res) mysql_free_result(sql_res); + + if (i == 1) { + memcpy(&char_dat[0], RFIFOP(fd,13), sizeof(struct mmo_charstatus)); + mmo_char_tosql(cid, char_dat); + //save to DB + } else + ShowError("parse_from_map (save-char): Received data for non-existant character (%d:%d)!\n", aid, cid); + if (RFIFOB(fd,12)) { //Flag? Set character offline after saving [Skotlex] + set_char_offline(cid, aid); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + } + // req char selection + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd, 2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd, 6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + + WFIFOW(fd, 0) = 0x2b03; + WFIFOL(fd, 2) = RFIFOL(fd, 2); + WFIFOB(fd, 6) = 0; + WFIFOSET(fd, 7); + + RFIFOSKIP(fd, 18); + break; + + // request "change map server" + case 0x2b05: + if (RFIFOREST(fd) < 35) + return 0; + { + unsigned short name; + int map_id, map_fd = -1; + struct online_char_data* data; + struct mmo_charstatus* char_data; + + + name = RFIFOW(fd,18); + map_id = search_mapserver(name, RFIFOL(fd,24), RFIFOW(fd,28)); //Locate mapserver by ip and port. + if (map_id >= 0) + map_fd = server_fd[map_id]; + //Char should just had been saved before this packet, so this should be safe. [Skotlex] + char_data = uidb_get(char_db_,RFIFOL(fd,14)); + if (char_data == NULL) + { //Really shouldn't happen. + mmo_char_fromsql(RFIFOL(fd,14), char_dat); + char_data = char_dat; + } + //Tell the new map server about this player using Kevin's new auth packet. [Skotlex] + if (map_fd>=0 && session[map_fd] && char_data) + { //Send the map server the auth of this player. + //Update the "last map" as this is where the player must be spawned on the new map server. + char_data->last_point.map = RFIFOW(fd,18); + char_data->last_point.x = RFIFOW(fd,20); + char_data->last_point.y = RFIFOW(fd,22); + + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = RFIFOL(fd, 2); //Account ID + WFIFOL(map_fd,8) = RFIFOL(fd, 6); //Login1 + WFIFOL(map_fd,16) = RFIFOL(fd,10); //Login2 + WFIFOL(map_fd,12) = (unsigned long)0; //TODO: connect_until_time, how do I figure it out right now? + memcpy(WFIFOP(map_fd,20), char_data, sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + data = idb_ensure(online_char_db, RFIFOL(fd, 2), create_online_char_data); + data->char_id = char_data->char_id; + data->server = map_id; //Update server where char is. + + //Reply with an ack. + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOSET(fd, 30); + } else { //Reply with nak + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOL(fd, 6) = 0; //Set login1 to 0. + WFIFOSET(fd, 30); + } + RFIFOSKIP(fd, 35); + } + break; + + // char name check + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + + sprintf(tmp_sql, "SELECT `name` FROM `%s` WHERE `char_id`='%d'", char_db, (int)RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + + if (sql_row) + memcpy(WFIFOP(fd,6), sql_row[0], NAME_LENGTH); + else + memcpy(WFIFOP(fd,6), unknown_char_name, NAME_LENGTH); + if (sql_res) mysql_free_result(sql_res); + + WFIFOSET(fd,30); + + RFIFOSKIP(fd,6); + break; + + // I want become GM - fuck! + case 0x2b0a: + if(RFIFOREST(fd)<4) + return 0; + if(RFIFOREST(fd) login %d %s %d\n", RFIFOL(fd, 4), RFIFOP(fd, 8), RFIFOW(fd, 2)); + */ + ShowWarning("packet 0x2ba (become GM) is not supported by the Char-Server.\n"); + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + + //Packet 0x2b10 deprecated in favor of packet 0x3004 for registry saving. [Skotlex] + //case 0x2b10: + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 .L .40B .40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Receiving from map-server a status change resquest. Transmission to login-server (by Yor) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[NAME_LENGTH], t_name[NAME_LENGTH*2]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), NAME_LENGTH); + character_name[NAME_LENGTH-1] = '\0'; + jstrescapecpy(t_name, character_name); //Escape string for sql use... [Skotlex] + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban + sprintf(tmp_sql, "SELECT `account_id`,`name` FROM `%s` WHERE `name` = '%s'",char_db, t_name); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + if (mysql_num_rows(sql_res)) { + sql_row = mysql_fetch_row(sql_res); + memcpy(WFIFOP(fd,6), sql_row[1], NAME_LENGTH); // put correct name if found + WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, NAME_LENGTH); + WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + WFIFOSET(fd, 34); + } + } + } + RFIFOSKIP(fd, 44); + break; + + // Recieve rates [Wizputer] + case 0x2b16: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8)) + return 0; + { + char motd[256], t_name[512]; //Required for jstrescapecpy [Skotlex] + strncpy(motd, RFIFOP(fd,10), 255); //First copy it to make sure the motd fits. + motd[255]='\0'; + jstrescapecpy(t_name,motd); + + sprintf(tmp_sql, "INSERT INTO `ragsrvinfo` SET `index`='%d',`name`='%s',`exp`='%d',`jexp`='%d',`drop`='%d',`motd`='%s'", + fd, server_name, RFIFOW(fd,2), RFIFOW(fd,4), RFIFOW(fd,6), t_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + RFIFOSKIP(fd,RFIFOW(fd,8)); + break; + } + + // Character disconnected set online 0 [Wizputer] + case 0x2b17: + if (RFIFOREST(fd) < 6 ) + return 0; + //printf("Setting %d char offline\n",RFIFOL(fd,2)); + set_char_offline(RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + // Reset all chars to offline [Wizputer] + case 0x2b18: + ShowNotice("Map server [%d] requested to set all characters offline.\n", id); + set_all_offline(); + RFIFOSKIP(fd,2); + break; + // Character set online [Wizputer] + case 0x2b19: + if (RFIFOREST(fd) < 6 ) + return 0; + set_char_online(id, RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + // Request sending of fame list + case 0x2b1a: + if (RFIFOREST(fd) < 2) + return 0; + { + int len = 8, num = 0; + unsigned char buf[32000]; + struct fame_list fame_item; + + WBUFW(buf,0) = 0x2b1b; + sprintf(tmp_sql, "SELECT `char_id`,`fame`, `name` FROM `%s` WHERE `fame`>0 AND (`class`='10' OR `class`='4011'OR `class`='4033') ORDER BY `fame` DESC LIMIT 0,10", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + fame_item.id = atoi(sql_row[0]); + fame_item.fame = atoi(sql_row[1]); + strncpy(fame_item.name, sql_row[2], NAME_LENGTH); + + memcpy(WBUFP(buf,len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + if (++num == 10) + break; + } + } + mysql_free_result(sql_res); + WBUFW(buf, 6) = len; //Blacksmith block size + + num = 0; + sprintf(tmp_sql, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND (`class`='18' OR `class`='4019' OR `class`='4041') ORDER BY `fame` DESC LIMIT 0,10", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + fame_item.id = atoi(sql_row[0]); + fame_item.fame = atoi(sql_row[1]); + strncpy(fame_item.name, sql_row[2], NAME_LENGTH); + memcpy(WBUFP(buf,len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + if (++num == 10) + break; + } + } + mysql_free_result(sql_res); + WBUFW(buf, 4) = len; //Alchemist block size + + num = 0; + sprintf(tmp_sql, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND `class`='4046' ORDER BY `fame` DESC LIMIT 0,10", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + fame_item.id = atoi(sql_row[0]); + fame_item.fame = atoi(sql_row[1]); + strncpy(fame_item.name, sql_row[2], NAME_LENGTH); + memcpy(WBUFP(buf,len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + if (++num == 10) + break; + } + } + mysql_free_result(sql_res); + WBUFW(buf, 2) = len; //Total packet length + + mapif_sendall(buf, len); + RFIFOSKIP(fd,2); + break; + } + //Request saving sc_data of a player. [Skotlex] + case 0x2b1c: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { +#ifdef ENABLE_SC_SAVING + int count, aid, cid, i; + struct status_change_data data; + + aid = RFIFOL(fd, 4); + cid = RFIFOL(fd, 8); + count = RFIFOW(fd, 12); + + sprintf(tmp_sql, "INSERT INTO `%s` (`account_id`, `char_id`, `type`, `tick`, `val1`, `val2`, `val3`, `val4`) VALUES ", scdata_db); + + for (i = 0; i < count; i++) + { + memcpy (&data, RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data)); + sprintf (tmp_sql, "%s ('%d','%d','%hu','%d','%d','%d','%d','%d'),", tmp_sql, aid, cid, + data.type, data.tick, data.val1, data.val2, data.val3, data.val4); + } + if (count > 0) + { + tmp_sql[strlen(tmp_sql)-1] = '\0'; //Remove final comma. + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } +#endif + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + } + + default: + // inter server - packet + { + int r = inter_parse_frommap(fd); + if (r == 1) break; // processed + if (r == 2) return 0; // need more packet + } + + // no inter server packet. no char server packet -> disconnect + ShowError("Unknown packet 0x%04x from map server, disconnecting.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(unsigned short map, long ip, short port) { + int i, j; + + if (!map) + return -1; + + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] > 0) + for (j = 0; server[i].map[j]; j++) + if (server[i].map[j] == map) { + if (ip > 0 && server[i].ip != ip) + continue; + if (port > 0 && server[i].port != port) + continue; + return i; + } + + return -1; +} + +int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + int subneti[4]; + unsigned int k0, k1, k2, k3; + + sscanf(lan_map_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subneti[0] = k0; subneti[1] = k1; subneti[2] = k2; subneti[3] = k3; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + return lancheck; +} + +int parse_char(int fd) { + int i, ch = 0; + char email[40]; + unsigned char buf[64]; + unsigned short cmd; + int map_fd; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + + sd = (struct char_session_data*)session[fd]->session_data; + + if(login_fd < 0) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (fd == login_fd) + login_fd = -1; + if (sd != NULL) + { + struct online_char_data* data = idb_get(online_char_db, sd->account_id); + if (!data || data->server== -1) //If it is not in any server, send it offline. [Skotlex] + set_char_offline(99,sd->account_id); + } + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { + cmd = RFIFOW(fd,0); + // crc32のスキップ用 + if( sd==NULL && // 未ログインor管理パケット + RFIFOREST(fd)>=4 && // 最低バイト数制限 & 0x7530,0x7532管理パケ除去 + RFIFOREST(fd)<=21 && // 最大バイト数制限 & サーバーログイン除去 + cmd!=0x20b && // md5通知パケット除去 + (RFIFOREST(fd)<6 || RFIFOW(fd,4)==0x65) ){ // 次に何かパケットが来てるなら、接続でないとだめ + RFIFOSKIP(fd,4); + cmd = RFIFOW(fd,0); + ShowDebug("parse_char : %d crc32 skipped\n",fd); + if(RFIFOREST(fd)==0) + return 0; + } + +//For use in packets that depend on an sd being present [Skotlex] +#define FIFOSD_CHECK(rest) { if(RFIFOREST(fd) < rest) return 0; if (sd==NULL) { RFIFOSKIP(fd,rest); return 0; } } + + switch(cmd){ + case 0x20b: //20040622 encryption ragexe correspondence + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // request to connect + ShowInfo("request connect - account_id:%d/login_id1:%d/login_id2:%d\n", RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + if (RFIFOREST(fd) < 17) + return 0; + { + if (sd == NULL) { + CREATE(session[fd]->session_data, struct char_session_data, 1); + sd = (struct char_session_data*)session[fd]->session_data; + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd, 2); + sd->login_id1 = RFIFOL(fd, 6); + sd->login_id2 = RFIFOL(fd, 10); + sd->sex = RFIFOB(fd, 16); + + WFIFOL(fd, 0) = RFIFOL(fd, 2); + WFIFOSET(fd, 4); + + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + + if (online_check) + { // check if character is not online already. [Skotlex] + struct online_char_data* character; + character = idb_get(online_char_db, sd->account_id); + + if (character) + { + if (character->server > -1) + { //Character already online. KICK KICK KICK + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + if (!character->waiting_disconnect) + add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = 1; + /* Not a good idea because this would trigger when you do a char-change from the map server! [Skotlex] + } else { //Kick from char server. + struct char_session_data *tsd; + int i; + for(i = 0; i < fd_max; i++) { + if (session[i] && i != fd && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == sd->account_id) + { + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; + WFIFOSET(i,3); + break; + } + if (i == fd_max) //Shouldn't happen, but just in case. + set_char_offline(99, sd->account_id); + } + */ + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; + WFIFOSET(fd,3); + break; + } + } + } + + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } +// printf("connection request> set delflag 1(o:2)- account_id:%d/login_id1:%d(fifo_id:%d)\n", sd->account_id, sd->login_id1, i); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2712; // ask login-server to authentify an account + WFIFOL(login_fd,2) = sd->account_id; + WFIFOL(login_fd,6) = sd->login_id1; + WFIFOL(login_fd,10) = sd->login_id2; + WFIFOB(login_fd,14) = sd->sex; + WFIFOL(login_fd,15) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd, 17); + break; + + case 0x66: // char select + FIFOSD_CHECK(3); + + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id`='%d' AND `char_num`='%d'",char_db, sd->account_id, RFIFOB(fd, 2)); + RFIFOSKIP(fd, 3); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + + if (sql_row) + { + mmo_char_fromsql(atoi(sql_row[0]), char_dat); + char_dat[0].sex = sd->sex; + } else { + mysql_free_result(sql_res); + break; + } + + if (log_char) { + char escaped_name[NAME_LENGTH*2]; + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `account_id`,`char_num`,`name`) VALUES (NOW(), '%d', '%d', '%s')", + charlog_db, sd->account_id, RFIFOB(fd, 2), jstrescapecpy(escaped_name, char_dat[0].name)); + //query + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowInfo("Selected char: (Account %d: %d - %s)" RETCODE, sd->account_id, RFIFOB(fd, 2), char_dat[0].name); + + i = search_mapserver(char_dat[0].last_point.map, -1, -1); + + // if map is not found, we check major cities + if (i < 0) { + unsigned short j; + ShowWarning("Unable to find map-server for '%s', resorting to sending to a major city.\n", mapindex_id2name(char_dat[0].last_point.map)); + if ((i = search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 273; // savepoint coordinates + char_dat[0].last_point.y = 354; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 120; // savepoint coordinates + char_dat[0].last_point.y = 100; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 160; // savepoint coordinates + char_dat[0].last_point.y = 94; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 116; // savepoint coordinates + char_dat[0].last_point.y = 57; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 87; // savepoint coordinates + char_dat[0].last_point.y = 117; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 94; // savepoint coordinates + char_dat[0].last_point.y = 103; + } else { + // get first online server + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] > 0 && server[j].map[0]) { + i = j; + ShowDebug("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); + break; + } + // if no map-servers are connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + } + } + WFIFOW(fd, 0) =0x71; + WFIFOL(fd, 2) =char_dat[0].char_id; + memcpy(WFIFOP(fd,6), mapindex_id2name(char_dat[0].last_point.map), MAP_NAME_LENGTH); + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) { + auth_fifo_pos = 0; + } + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = char_dat[0].char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + + //Send NEW auth packet [Kevin] + if ((map_fd = server_fd[i]) < 1 || session[map_fd] == NULL) + { + ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i); + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + break; + } + + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = auth_fifo[auth_fifo_pos].account_id; + WFIFOL(map_fd,8) = auth_fifo[auth_fifo_pos].login_id1; + WFIFOL(map_fd,16) = auth_fifo[auth_fifo_pos].login_id2; + WFIFOL(map_fd,12) = (unsigned long)auth_fifo[auth_fifo_pos].connect_until_time; + memcpy(WFIFOP(map_fd,20), &char_dat[0], sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + + set_char_online(i, auth_fifo[auth_fifo_pos].char_id, auth_fifo[auth_fifo_pos].account_id); + //Checks to see if the even share setting of the party must be broken. + inter_party_logged(char_dat[0].party_id, char_dat[0].account_id, char_dat[0].char_id); + auth_fifo_pos++; + break; + + case 0x67: // make new + FIFOSD_CHECK(37); + + if(char_new == 0) //turn character creation on/off [Kevin] + i = -2; + else + i = make_new_char_sql(fd, RFIFOP(fd, 2)); + + //if (i < 0) { + // WFIFOW(fd, 0) = 0x6e; + // WFIFOB(fd, 2) = 0x00; + // WFIFOSET(fd, 3); + // RFIFOSKIP(fd, 37); + // break; + //} + //Changed that we can support 'Charname already exists' (-1) amd 'Char creation denied' (-2) + //And 'You are underaged' (-3) (XD) [Sirius] + if(i == -1){ + //already exists + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x00; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + }else if(i == -2){ + //denied + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x02; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + }else if(i == -3){ + //underaged XD + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x01; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + } + + WFIFOW(fd, 0) = 0x6d; + memset(WFIFOP(fd, 2), 0x00, 106); + + mmo_char_fromsql_short(i, char_dat); //Only the short data is needed. + //mmo_char_fromsql(i, char_dat); + i = 0; + WFIFOL(fd, 2) = char_dat[i].char_id; + WFIFOL(fd,2+4) = char_dat[i].base_exp; + WFIFOL(fd,2+8) = char_dat[i].zeny; + WFIFOL(fd,2+12) = char_dat[i].job_exp; + WFIFOL(fd,2+16) = char_dat[i].job_level; + + WFIFOL(fd,2+28) = char_dat[i].karma; + WFIFOL(fd,2+32) = char_dat[i].manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; + WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; + WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; + WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; + WFIFOW(fd,2+52) = char_dat[i].class_; + WFIFOW(fd,2+54) = char_dat[i].hair; + + WFIFOW(fd,2+58) = char_dat[i].base_level; + WFIFOW(fd,2+60) = char_dat[i].skill_point; + + WFIFOW(fd,2+64) = char_dat[i].shield; + WFIFOW(fd,2+66) = char_dat[i].head_top; + WFIFOW(fd,2+68) = char_dat[i].head_mid; + WFIFOW(fd,2+70) = char_dat[i].hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].name, NAME_LENGTH); + + WFIFOB(fd,2+98) = char_dat[i].str>255?255:char_dat[i].str; + WFIFOB(fd,2+99) = char_dat[i].agi>255?255:char_dat[i].agi; + WFIFOB(fd,2+100) = char_dat[i].vit>255?255:char_dat[i].vit; + WFIFOB(fd,2+101) = char_dat[i].int_>255?255:char_dat[i].int_; + WFIFOB(fd,2+102) = char_dat[i].dex>255?255:char_dat[i].dex; + WFIFOB(fd,2+103) = char_dat[i].luk>255?255:char_dat[i].luk; + WFIFOB(fd,2+104) = char_dat[i].char_num; + + WFIFOSET(fd, 108); + RFIFOSKIP(fd, 37); + + //to do + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = char_dat[i].char_id; + break; + } + } + break; + case 0x68: /* delete char */ + FIFOSD_CHECK(46); + { + int cid = RFIFOL(fd,2); + ShowInfo(CL_RED" Request Char Deletion:"CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, cid); + memcpy(email, RFIFOP(fd,6), 40); + + /* Check if e-mail is correct */ + if(strcmpi(email, sd->email)){ + if(strcmp("a@a.com", sd->email) == 0){ + if(strcmp("a@a.com", email) == 0 || strcmp("", email) == 0){ + //ignore + }else{ + //del fail + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + break; + } + }else{ + //del fail + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + break; + } + } + + for(i = 0; i < 9; i++) { + /* Debug: + printf("Checking if char to be deleted: %d - %d (%d)\n", sd->found_char[i], RFIFOL(fd, 2), sd->account_id); + */ + if (sd->found_char[i] == cid) { + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + break; + } + } + /* Such a character does not exist in the account */ + /* If so, you are so screwed. */ + if (i == 9) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + break; + } + + /* Grab the partner id */ + sprintf(tmp_sql, "SELECT `partner_id` FROM `%s` WHERE `char_id`='%d'",char_db, cid); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if(sql_res) + { + int char_pid=0; + sql_row = mysql_fetch_row(sql_res); + if (sql_row) + char_pid = atoi(sql_row[0]); + mysql_free_result(sql_res); + + /* Delete character and partner (if any) */ + delete_char_sql(cid, char_pid); + if (char_pid != 0) + { /* If there is partner, tell map server to do divorce */ + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFL(buf,6) = char_pid; + mapif_sendall(buf,10); + } + } + /* Char successfully deleted. <- For sure? There could had been an sql db error, what is done then?. [Skotlex] */ + WFIFOW(fd, 0) = 0x6f; + WFIFOSET(fd, 2); + + RFIFOSKIP(fd, 46); + break; + } + case 0x2af8: // login as map-server + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd, 0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] <= 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp((const char*)RFIFOP(fd,2), userid) || strcmp((const char*)RFIFOP(fd,26), passwd)) { + WFIFOB(fd,2) = 3; + WFIFOSET(fd, 3); + } else { +// int len; + WFIFOB(fd,2) = 0; + WFIFOSET(fd, 3); + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + server[i].ip = RFIFOL(fd, 54); + server[i].port = RFIFOW(fd, 58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers +/* removed by CLOWNISIUS due to isGM + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len);*/ + } + RFIFOSKIP(fd,60); + break; + + case 0x187: // Alive? + if (RFIFOREST(fd) < 6) { + return 0; + } + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena info get + WFIFOW(fd, 0) = 0x7531; + WFIFOB(fd, 2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd, 3) = ATHENA_MINOR_VERSION; + WFIFOB(fd, 4) = ATHENA_REVISION; + WFIFOB(fd, 5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd, 6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd, 7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd, 8) = ATHENA_MOD_VERSION; + WFIFOSET(fd, 10); + RFIFOSKIP(fd, 2); + return 0; + + case 0x7532: // disconnect(default also disconnect) + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +// Console Command Parser [Wizputer] +int parse_console(char *buf) { + char *type,*command; + + type = (char *)aMalloc(64); + command = (char *)aMalloc(64); + + memset(type,0,64); + memset(command,0,64); + + ShowNotice("Console: %s\n",buf); + + if ( sscanf(buf, "%[^:]:%[^\n]", type , command ) < 2 ) + sscanf(buf,"%[^\n]",type); + + ShowNotice("Type of command: %s || Command: %s \n",type,command); + + if(buf) aFree(buf); + if(type) aFree(type); + if(command) aFree(command); + + return 0; +} + +// MAP send all +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if ((fd = server_fd[i]) > 0) { //0 Should not be a valid server_fd [Skotlex] + if (session[fd] == NULL) + { //Could this be the crash's source? [Skotlex] + ShowError("mapif_sendall: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", fd, i); + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + continue; + } + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i=0, c=0;i 0 && fd != sfd) { + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + + return c; +} + +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + unsigned char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +static int send_accounts_tologin_sub(DBKey key, void* data, va_list ap) { + struct online_char_data* character = (struct online_char_data*)data; + int *i = va_arg(ap, int*); + int count = va_arg(ap, int); + if ((*i) >= count) + return 0; //This is an error that shouldn't happen.... + if(character->server > -1) { + WFIFOHEAD(login_fd, 8+count*4); + WFIFOL(login_fd, 8+(*i)*4) =character->account_id; + (*i)++; + return 1; + } + return 0; +} + +int send_accounts_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(), i=0; + + if (login_fd > 0 && session[login_fd]) { + // send account list to login server + WFIFOHEAD(login_fd, 8+users*4); + WFIFOW(login_fd,0) = 0x272d; + WFIFOL(login_fd,4) = users; + online_char_db->foreach(online_char_db, send_accounts_tologin_sub, &i, users); + WFIFOW(login_fd,2) = 8+ i*4; + if (i > 0) + WFIFOSET(login_fd,WFIFOW(login_fd,2)); + } + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + struct char_session_data *sd; + int i, cc; + unsigned char buf[16]; + + ShowInfo("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + if (login_fd == -1) + { //Try again later. [Skotlex] + login_fd = 0; + return 0; + } + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + + WFIFOW(login_fd,84) = char_new_display; //only display (New) if they want to [Kevin] + + WFIFOSET(login_fd,86); + + //(re)connected to login-server, + //now wi'll look in sql which player's are ON and set them OFF + //AND send to all mapservers (if we have one / ..) to kick the players + //so the bug is fixed, if'ure using more than one charservers (worlds) + //that the player'S got reejected from server after a 'world' crash^^ + //2b1f AID.L B1 + + sprintf(tmp_sql, "SELECT `account_id`, `online` FROM `%s` WHERE `online` = '1'", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -1; + } + + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + cc = (int)mysql_num_rows(sql_res); + ShowStatus("Setting %d Players offline\n", cc); + while((sql_row = mysql_fetch_row(sql_res))){ + //sql_row[0] == AID + //tell the loginserver + WFIFOW(login_fd, 0) = 0x272c; //set off + WFIFOL(login_fd, 2) = atoi(sql_row[0]); //AID + WFIFOSET(login_fd, 6); + + //tell map to 'kick' the player (incase of 'on' ..) + WBUFW(buf, 0) = 0x2b1f; + WBUFL(buf, 2) = atoi(sql_row[0]); + WBUFB(buf, 6) = 1; + mapif_sendall(buf, 7); + + //kick the player if he's on charselect + for(i = 0; i < fd_max; i++){ + if(session[i] && (sd = (struct char_session_data*)session[i]->session_data)){ + if(sd->account_id == atoi(sql_row[0])){ + session[i]->eof = 1; + break; + } + } + } + + } + mysql_free_result(sql_res); + }else{ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -1; + } + + //Now Update all players to 'OFFLINE' + sprintf(tmp_sql, "UPDATE `%s` SET `online` = '0'", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "UPDATE `%s` SET `online` = '0'", guild_member_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "UPDATE `%s` SET `connect_member` = '0'", guild_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + } + return 0; +} + +//------------------------------------------------ +//Invoked 15 seconds after mapif_disconnectplayer in case the map server doesn't +//replies/disconnect the player we tried to kick. [Skotlex] +//------------------------------------------------ +static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data) +{ + struct online_char_data* character; + if ((character = idb_get(online_char_db, id)) != NULL && character->waiting_disconnect) + { //Mark it offline due to timeout. + set_char_offline(character->char_id, character->account_id); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +// Lan Support conf reading added by Kashy +int char_lan_config_read(const char *lancfgName){ + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + struct hostent * h = NULL; + + if ((fp = fopen(lancfgName, "r")) == NULL) { + ShowError("file not found: %s\n", lancfgName); + return 1; + } + + ShowInfo("Reading file %s...\n", lancfgName); + + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + else if (strcmpi(w1, "lan_map_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + ShowStatus("set Lan_map_IP : %s\n", lan_map_ip); + } + + else if (strcmpi(w1, "subnetmask") == 0) { + unsigned int k0, k1, k2, k3; + strcpy(subnetmask, w2); + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + ShowStatus("set subnetmask : %s\n", w2); + } + } + fclose(fp); + + ShowInfo("Done reading %s.\n", lancfgName); + return 0; +} + +void do_final(void) { + ShowInfo("Doing final stage...\n"); + //mmo_char_sync(); + //inter_save(); + do_final_itemdb(); + //check SQL save progress. + //wait until save char complete + + set_all_offline(); + + inter_final(); + + flush_fifos(); + + mapindex_final(); + + sprintf(tmp_sql,"DELETE FROM `ragsrvinfo"); + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if(gm_account) { + aFree(gm_account); + gm_account = 0; + } + + if(char_dat) { + aFree(char_dat); + char_dat = 0; + } + + delete_session(login_fd); + delete_session(char_fd); + char_db_->destroy(char_db_, NULL); + online_char_db->destroy(online_char_db, NULL); + + mysql_close(&mysql_handle); + mysql_close(&lmysql_handle); + + ShowInfo("ok! all done...\n"); +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + ShowInfo("Reading file %s...\n", cfgName); + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowFatalError("file not found: %s\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if(strcmpi(w1,"char_db")==0){ + strcpy(char_db,w2); + }else if(strcmpi(w1,"scdata_db")==0){ + strcpy(scdata_db,w2); + }else if(strcmpi(w1,"cart_db")==0){ + strcpy(cart_db,w2); + }else if(strcmpi(w1,"inventory_db")==0){ + strcpy(inventory_db,w2); + }else if(strcmpi(w1,"charlog_db")==0){ + strcpy(charlog_db,w2); + }else if(strcmpi(w1,"storage_db")==0){ + strcpy(storage_db,w2); + }else if(strcmpi(w1,"reg_db")==0){ + strcpy(reg_db,w2); + }else if(strcmpi(w1,"skill_db")==0){ + strcpy(skill_db,w2); + }else if(strcmpi(w1,"interlog_db")==0){ + strcpy(interlog_db,w2); + }else if(strcmpi(w1,"memo_db")==0){ + strcpy(memo_db,w2); + }else if(strcmpi(w1,"guild_db")==0){ + strcpy(guild_db,w2); + }else if(strcmpi(w1,"guild_alliance_db")==0){ + strcpy(guild_alliance_db,w2); + }else if(strcmpi(w1,"guild_castle_db")==0){ + strcpy(guild_castle_db,w2); + }else if(strcmpi(w1,"guild_expulsion_db")==0){ + strcpy(guild_expulsion_db,w2); + }else if(strcmpi(w1,"guild_member_db")==0){ + strcpy(guild_member_db,w2); + }else if(strcmpi(w1,"guild_skill_db")==0){ + strcpy(guild_skill_db,w2); + }else if(strcmpi(w1,"guild_position_db")==0){ + strcpy(guild_position_db,w2); + }else if(strcmpi(w1,"guild_storage_db")==0){ + strcpy(guild_storage_db,w2); + }else if(strcmpi(w1,"party_db")==0){ + strcpy(party_db,w2); + }else if(strcmpi(w1,"pet_db")==0){ + strcpy(pet_db,w2); + }else if(strcmpi(w1,"friend_db")==0){ + strcpy(friend_db,w2); + }else if(strcmpi(w1,"db_path")==0){ + strcpy(db_path,w2); + //Map server option to use SQL db or not + }else if(strcmpi(w1,"use_sql_db")==0){ // added for sql item_db read for char server [Valaris] + db_use_sqldbs = config_switch(w2); + ShowStatus("Using SQL dbs: %s\n",w2); + //custom columns for login database + }else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level,w2); + }else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id,w2); + }else if(strcmpi(w1,"lowest_gm_level")==0){ + lowest_gm_level = atoi(w2); + ShowStatus("set lowest_gm_level : %s\n",w2); + //support the import command, just like any other config + }else if(strcmpi(w1,"import")==0){ + sql_config_read(w2); + } + + } + fclose(fp); + ShowInfo("done reading %s.\n", cfgName); +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowFatalError("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + ShowInfo("Reading file %s...\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars((unsigned char *) w1); + remove_control_chars((unsigned char *) w2); + if(strcmpi(w1,"timestamp_format")==0) { + strncpy(timestamp_format, w2, 20); + } else if(strcmpi(w1,"console_silent")==0){ + msg_silent = 0; //To always allow the next line to show up. + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + msg_silent = atoi(w2); + } else if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; + ShowStatus("%s server has been initialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + login_ip_set_ = 1; + h = gethostbyname (w2); + if (h != NULL) { + ShowStatus("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str,w2,16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port=atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + char_ip_set_ = 1; + h = gethostbyname (w2); + if(h != NULL) { + ShowStatus("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "bind_ip") == 0) { + bind_ip_set_ = 1; + h = gethostbyname (w2); + if(h != NULL) { + ShowStatus("Character server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(bind_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new")==0){ + char_new = atoi(w2); + } else if (strcmpi(w1, "char_new_display")==0){ + char_new_display = atoi(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if(strcmpi(w1, "gm_allow_level") == 0) { + gm_allow_level = atoi(w2); + if(gm_allow_level < 0) + gm_allow_level = 99; + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "online_check") == 0) { + online_check = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "save_log") == 0) { + save_log = config_switch(w2); + } else if (strcmpi(w1, "start_point") == 0) { + char map[MAP_NAME_LENGTH]; + int x, y; + if (sscanf(w2,"%16[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + start_point.map = mapindex_name2id(map); + if (!start_point.map) + ShowError("Specified start_point %s not found in map-index cache.\n", map); + start_point.x = x; + start_point.y = y; + } + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_weapon = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_armor = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if(strcmpi(w1,"log_char")==0){ //log char or not [devil] + log_char = atoi(w2); + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[NAME_LENGTH-1] = 0; + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "chars_per_account") == 0) { //maxchars per account [Sirius] + char_per_account = atoi(w2); + } else if (strcmpi(w1, "console") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + console = 1; + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + ShowInfo("Done reading %s.\n", cfgName); + + return 0; +} + +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_CHAR; +} + +static int online_data_cleanup_sub(DBKey key, void *data, va_list ap) +{ + struct online_char_data *character= (struct online_char_data*)data; + if (character->server == -2) //Unknown server.. set them offline + set_char_offline(character->char_id, character->account_id); + if (character->server < 0) + //Free data from players that have not been online for a while. + db_remove(online_char_db, key); + return 0; +} + +static int online_data_cleanup(int tid, unsigned int tick, int id, int data) +{ + online_char_db->foreach(online_char_db, online_data_cleanup_sub); + return 0; +} + +int do_init(int argc, char **argv){ + int i; + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + //Read map indexes + mapindex_init(); + start_point.map = mapindex_name2id("new_1-1.gat"); + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + char_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + sql_config_read(SQL_CONF_NAME); + + ShowInfo("Finished reading the char-server configuration.\n"); + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server テハア篳ュ + ShowInfo("Finished reading the inter-server configuration.\n"); + + //Read ItemDB + do_init_itemdb(); + + ShowInfo("Initializing char server.\n"); + online_char_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + mmo_char_sql_init(); + ShowInfo("char server initialized.\n"); + +// ShowDebug("set parser -> parse_char()...\n"); + set_defaultparse(parse_char); + +// ShowDebug("set terminate function -> do_final().....\n"); + + if ((naddr_ != 0) && (login_ip_set_ == 0 || char_ip_set_ == 0)) { + // The char server should know what IP address it is running on + // - MouseJstr + int localaddr = ntohl(addr_[0]); + unsigned char *ptr = (unsigned char *) &localaddr; + char buf[16]; + sprintf(buf, "%d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]); + if (naddr_ != 1) + ShowStatus("Multiple interfaces detected.. using %s as our IP address\n", buf); + else + ShowStatus("Defaulting to %s as our IP address\n", buf); + if (login_ip_set_ == 0) + strcpy(login_ip_str, buf); + if (char_ip_set_ == 0) + strcpy(char_ip_str, buf); + + if (ptr[0] == 192 && ptr[1] == 168) + ShowWarning("Firewall detected.. edit lan_support.conf and char_athena.conf\n"); + } + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + ShowInfo("open port %d.....\n",char_port); + //char_fd = make_listen_port(char_port); + if (bind_ip_set_) + char_fd = make_listen_bind(inet_addr(bind_ip_str),char_port); + else + char_fd = make_listen_bind(INADDR_ANY,char_port); + + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); + add_timer_func_list(send_accounts_tologin, "send_accounts_tologin"); + add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect"); + + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600 * 1000); + + // send ALIVE PING to login server. + add_timer_interval(gettick() + 10, check_connect_login_server, 0, 0, 10 * 1000); + // send USER COUNT PING to login server. + add_timer_interval(gettick() + 10, send_users_tologin, 0, 0, 5 * 1000); + add_timer_interval(gettick() + 3600*1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour. + + if ( console ) { + set_defaultconsoleparse(parse_console); + start_console(); + } + + //Cleaning the tables for NULL entrys @ startup [Sirius] + //Chardb clean + ShowInfo("Cleaning the '%s' table...\n", char_db); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `account_id` = '0'", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //guilddb clean + ShowInfo("Cleaning the '%s' table...\n", guild_db); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_lv` = '0' AND `max_member` = '0' AND `exp` = '0' AND `next_exp` = '0' AND `average_lv` = '0'", guild_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //guildmemberdb clean + ShowInfo("Cleaning the '%s' table...\n", guild_member_db); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '0' AND `account_id` = '0' AND `char_id` = '0'", guild_member_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + ShowInfo("End of char server initilization function.\n"); + ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port); + return 0; +} + +#undef mysql_query + +int debug_mysql_query(char *file, int line, void *mysql, const char *q) { +#ifdef TWILIGHT + ShowDebug("sql: %s:%d# %s\n", file, line, q); +#endif + return mysql_query((MYSQL *) mysql, q); +} + +int char_child(int parent_id, int child_id) { + int tmp_id = 0; + sprintf (tmp_sql, "SELECT `child` FROM `%s` WHERE `char_id` = '%d'", char_db, parent_id); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result (&mysql_handle); + sql_row = sql_res?mysql_fetch_row (sql_res):NULL; + if (sql_row) + tmp_id = atoi (sql_row[0]); + else + ShowError("CHAR: child Failed!\n"); + if (sql_res) mysql_free_result (sql_res); + if ( tmp_id == child_id ) + return 1; + else + return 0; +} + +int char_married(int pl1,int pl2) { + int tmp_id = 0; + sprintf (tmp_sql, "SELECT `partner_id` FROM `%s` WHERE `char_id` = '%d'", char_db, pl1); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result (&mysql_handle); + sql_row = sql_res?mysql_fetch_row (sql_res):NULL; + if (sql_row) + tmp_id = atoi (sql_row[0]); + else + ShowError("CHAR: married Failed!\n"); + if (sql_res) mysql_free_result (sql_res); + if ( tmp_id == pl2 ) + return 1; + else + return 0; +} + +int char_nick2id (char *name) { + int char_id = 0; + sprintf (tmp_sql, "SELECT `char_id` FROM `%s` WHERE `name` = '%s'", char_db, name); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result (&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) + char_id = atoi (sql_row[0]); + else + ShowError ("CHAR: nick2id Failed!\n"); + if (sql_res) mysql_free_result (sql_res); + return char_id; +} diff --git a/src/char_sql/char.h b/src/char_sql/char.h new file mode 100644 index 000000000..db1a0ca95 --- /dev/null +++ b/src/char_sql/char.h @@ -0,0 +1,103 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/mmo.h" +#include "../common/version.h" +#include "../common/db.h" +#include "../common/mapindex.h" + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define START_CHAR_NUM 150000 +#define MAX_MAP_SERVERS 30 + +#define LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + unsigned short map[MAX_MAP_PER_SERVER]; +}; +struct itemtmp { + int flag;//checked = 1 else 0 + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; +}; +enum { + TABLE_INVENTORY, + TABLE_CART, + TABLE_STORAGE, + TABLE_GUILD_STORAGE, +}; +struct itemtemp{ + struct itemtmp equip[MAX_GUILD_STORAGE],notequip[MAX_GUILD_STORAGE]; +}; +int memitemdata_to_sql(struct itemtmp mapitem[], int count, int char_id,int tableswitch); + +//int memitemdataNEW_to_sql(struct itemtmp mapitem[], int count, int char_id,int tableswitch); +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +int char_nick2id (char *name); +int char_married(int pl1,int pl2); +int char_child(int parent_id, int child_id); + +int request_accreg2(int account_id, int char_id); +int save_accreg2(unsigned char* buf, int len); + +extern int autosave_interval; +extern int save_log; +extern int charsave_method; +extern char db_path[]; +extern char char_db[256]; +extern char scdata_db[256]; +extern char cart_db[256]; +extern char inventory_db[256]; +extern char charlog_db[256]; +extern char storage_db[256]; +extern char interlog_db[256]; +extern char reg_db[256]; +extern char skill_db[256]; +extern char memo_db[256]; +extern char guild_db[256]; +extern char guild_alliance_db[256]; +extern char guild_castle_db[256]; +extern char guild_expulsion_db[256]; +extern char guild_member_db[256]; +extern char guild_position_db[256]; +extern char guild_skill_db[256]; +extern char guild_storage_db[256]; +extern char party_db[256]; +extern char pet_db[256]; + +extern int db_use_sqldbs; // added for sql item_db read for char server [Valaris] +extern char login_db_level[32]; +extern char login_db_account_id[32]; + +extern int lowest_gm_level; +extern int GM_num; +extern struct gm_account *gm_account; + +extern int debug_mysql_query(char *file, int line, void *mysql, const char *q); + +#endif + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" diff --git a/src/char_sql/int_guild.c b/src/char_sql/int_guild.c new file mode 100644 index 000000000..6e1f24ed6 --- /dev/null +++ b/src/char_sql/int_guild.c @@ -0,0 +1,1914 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by hack + +#include +#include +#include + +#include "char.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +// #include "int_storage.h" +#include "inter.h" +#include "int_guild.h" +#include "mmo.h" +#include "socket.h" +#include "db.h" +#include "malloc.h" + +//Guild cache +static struct dbt *guild_db_; + +struct guild_castle castles[MAX_GUILDCASTLE]; + +static int guild_newid=30000; + +static int guild_exp[100]; + +#define GS_BASIC 0x01 +#define GS_MEMBER 0x02 +#define GS_POSITION 0x04 +#define GS_ALLIANCE 0x08 +#define GS_EXPULSION 0x10 +#define GS_SKILL 0x20 +#define GS_MASK 0x7F +#define GS_REMOVE 0x80 + +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes); +int mapif_guild_broken(int guild_id,int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len); +int mapif_guild_info(int fd,struct guild *g); +int guild_break_sub(int key,void *data,va_list ap); +int inter_guild_tosql(struct guild *g,int flag); + + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +static int guild_save(DBKey key, void *data, va_list ap) { + struct guild *g = (struct guild*) data; + int *last_id = va_arg(ap, int *); + int *state = va_arg(ap, int *); + + if ((*state) == 0 && g->guild_id == (*last_id)) + (*state)++; //Save next guild in the list. + else if (g->save_flag&GS_MASK && (*state) == 1) { + inter_guild_tosql(g, g->save_flag&GS_MASK); + g->save_flag &= ~GS_MASK; + + //Some guild saved. + (*last_id) = g->guild_id; + (*state)++; + } + + if(g->save_flag == GS_REMOVE) { //Nothing to save, guild is ready for removal. + if (save_log) + ShowInfo("Guild Unloaded (%d - %s)\n", g->guild_id, g->name); + db_remove(guild_db_, key); + } + return 0; +} + +static int guild_save_timer(int tid, unsigned int tick, int id, int data) { + static int last_id = 0; //To know in which guild we were. + int state = 0; //0: Have not reached last guild. 1: Reached last guild, ready for save. 2: Some guild saved, don't do further saving. + if (!last_id) //Save the first guild in the list. + state = 1; + guild_db_->foreach(guild_db_, guild_save, &last_id, &state); + if (state != 2) //Reached the end of the guild db without saving. + last_id = 0; //Reset guild saved, return to beginning. + + state = guild_db_->size(guild_db_); + if (state < 1) state = 1; //Calculate the time slot for the next save. + add_timer(tick + autosave_interval/state, guild_save_timer, 0, 0); + return 0; +} + +// Save guild into sql +int inter_guild_tosql(struct guild *g,int flag) +{ + // GS_BASIC `guild` (`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`) + // GS_MEMBER `guild_member` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name`) + // GS_POSITION `guild_position` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) + // GS_ALLIANCE `guild_alliance` (`guild_id`,`opposition`,`alliance_id`,`name`) + // GS_EXPULSION `guild_expulsion` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) + // GS_SKILL `guild_skill` (`guild_id`,`id`,`lv`) + + // temporary storage for str convertion. They must be twice the size of the + // original string to ensure no overflows will occur. [Skotlex] + char t_name[NAME_LENGTH*2], + t_master[NAME_LENGTH*2], + t_mes1[120], + t_mes2[240], + t_member[NAME_LENGTH*2], + t_position[NAME_LENGTH*2], + t_alliance[NAME_LENGTH*2], + t_ename[NAME_LENGTH*2], + t_emes[80], + t_info[240]; + char emblem_data[4096]; + int i=0; + + if (g->guild_id<=0) return -1; + +#ifdef NOISY + ShowInfo("Save guild request ("CL_BOLD"%d"CL_RESET" - flag 0x%x).",g->guild_id, flag); +#endif + + jstrescapecpy(t_name, g->name); + + t_info[0]='\0'; + // Insert new guild to sqlserver + if (flag&GS_BASIC){ + int len=0; + char updateflag=1; + strcat(t_info, " guild"); + + // Check if the guild exists. + sprintf(tmp_sql,"SELECT guild_id FROM `%s` WHERE guild_id='%d'",guild_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + updateflag = 0; //Assume insert? + } else { + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<=0) { //Guild does not exists + updateflag = 0; + } + mysql_free_result(sql_res); //Don't need it anymore... + } + + //printf("- Insert guild %d to guild\n",g->guild_id); + for(i=0;iemblem_len;i++){ + len+=sprintf(emblem_data+len,"%02x",(unsigned char)(g->emblem_data[i])); + //printf("%02x",(unsigned char)(g->emblem_data[i])); + } + emblem_data[len] = '\0'; + //printf("- emblem_len = %d \n",g->emblem_len); + if (updateflag) { + sprintf(tmp_sql,"UPDATE `%s` SET" + " `guild_id`=%d, `name`='%s', `master`='%s',`guild_lv`=%d, `connect_member`=%d,`max_member`=%d, " + "`average_lv`=%d,`exp`=%d,`next_exp`=%d,`skill_point`=%d,`mes1`='%s',`mes2`='%s'," + "`emblem_len`=%d,`emblem_id`=%d,`emblem_data`='%s',`char_id`=%d WHERE `guild_id`=%d", + guild_db, g->guild_id,t_name,jstrescapecpy(t_master,g->master), + g->guild_lv,g->connect_member,g->max_member,g->average_lv,g->exp,g->next_exp,g->skill_point, + jstrescapecpy(t_mes1,g->mes1),jstrescapecpy(t_mes2,g->mes2),g->emblem_len,g->emblem_id,emblem_data, + g->member[0].char_id, g->guild_id); + //printf(" %s\n",tmp_sql); + + } else { + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`,`char_id`) " + "VALUES ('%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%s', '%d')", + guild_db, g->guild_id,t_name,jstrescapecpy(t_master,g->master), + g->guild_lv,g->connect_member,g->max_member,g->average_lv,g->exp,g->next_exp,g->skill_point, + jstrescapecpy(t_mes1,g->mes1),jstrescapecpy(t_mes2,g->mes2),g->emblem_len,g->emblem_id,emblem_data, + g->member[0].char_id); + //printf(" %s\n",tmp_sql); + + } + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&GS_MEMBER){ + struct guild_member *m; + strcat(t_info, " members"); + // Re-writing from scratch (Aru) + sprintf(tmp_sql,"DELETE from `%s` where `guild_id` = '%d'", + guild_member_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"UPDATE `%s` SET `guild_id` = '0' WHERE `guild_id` = '%d'", + char_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + for(i=0;imax_member;i++){ + m = &g->member[i]; + if(m->account_id) { + //Since nothing references guild member table as foreign keys, it's safe to use REPLACE INTO + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`name`) " + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%s')", + guild_member_db, g->guild_id, m->account_id,m->char_id, + m->hair,m->hair_color,m->gender, + m->class_,m->lv,m->exp,m->exp_payper,m->online,m->position, + jstrescapecpy(t_member,m->name)); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"UPDATE `%s` SET `guild_id` = '%d' WHERE `char_id` = '%d'", + char_db, g->guild_id, m->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + + if (flag&GS_POSITION){ + strcat(t_info, " positions"); + //printf("- Insert guild %d to guild_position\n",g->guild_id); + for(i=0;iposition[i]; + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) VALUES ('%d','%d', '%s','%d','%d')", + guild_position_db, g->guild_id, i, jstrescapecpy(t_position,p->name),p->mode,p->exp_mode); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + + if (flag&GS_ALLIANCE){ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'",guild_alliance_db, g->guild_id,g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + else + { + //printf("- Insert guild %d to guild_alliance\n",g->guild_id); + for(i=0;ialliance[i]; + if(a->guild_id>0){ + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, g->guild_id,a->opposition,a->guild_id,jstrescapecpy(t_alliance,a->name)); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, a->guild_id,a->opposition,g->guild_id,t_name); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + } + + if (flag&GS_EXPULSION){ + strcat(t_info, " expulsions"); + //printf("- Insert guild %d to guild_expulsion\n",g->guild_id); + for(i=0;iexplusion[i]; + if(e->account_id>0){ + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) " + "VALUES ('%d','%s','%s','%s','%d','%d','%d','%d')", + guild_expulsion_db, g->guild_id, + jstrescapecpy(t_ename,e->name),jstrescapecpy(t_emes,e->mes),e->acc,e->account_id,e->rsv1,e->rsv2,e->rsv3 ); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + + if (flag&GS_SKILL){ + strcat(t_info, " skills"); + //printf("- Insert guild %d to guild_skill\n",g->guild_id); + for(i=0;iskill[i].id>0 && g->skill[i].lv>0){ + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`id`,`lv`) VALUES ('%d','%d','%d')", + guild_skill_db, g->guild_id,g->skill[i].id,g->skill[i].lv); + //printf("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + + if (save_log) + ShowInfo("Saved guild (%d - %s):%s\n",g->guild_id,g->name,t_info); + return 0; +} + +// Read guild from sql +struct guild * inter_guild_fromsql(int guild_id) +{ + int i; + char emblem_data[4096]; + char *pstr; + struct guild *g; + + if (guild_id<=0) return NULL; + + g = idb_get(guild_db_,guild_id); + if (g) return g; + + g = (struct guild*)aCalloc(sizeof(struct guild), 1); + +#ifdef NOISY + ShowInfo("Guild load request (%d)...\n", guild_id); +#endif + + sprintf(tmp_sql,"SELECT `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data` " + "FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<1) { + //Guild does not exists. + if (sql_res) mysql_free_result(sql_res); + aFree(g); + return NULL; + } + + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL) { + mysql_free_result(sql_res); + aFree(g); + return NULL; + } + + g->guild_id=guild_id; + strncpy(g->name,sql_row[0],NAME_LENGTH-1); + strncpy(g->master,sql_row[1],NAME_LENGTH-1); + g->guild_lv=atoi(sql_row[2]); + g->connect_member=atoi(sql_row[3]); + if (atoi(sql_row[4]) > MAX_GUILD) // Fix reduction of MAX_GUILD [PoW] + g->max_member = MAX_GUILD; + else + g->max_member = atoi(sql_row[4]); + g->average_lv=atoi(sql_row[5]); + g->exp=atoi(sql_row[6]); + g->next_exp=atoi(sql_row[7]); + g->skill_point=atoi(sql_row[8]); + //There shouldn't be a need to copy the very last char, as it's the \0 [Skotlex] + strncpy(g->mes1,sql_row[9],59); + strncpy(g->mes2,sql_row[10],119); + g->emblem_len=atoi(sql_row[11]); + g->emblem_id=atoi(sql_row[12]); + strncpy(emblem_data,sql_row[13],4096); + for(i=0,pstr=emblem_data;iemblem_len;i++,pstr+=2){ + int c1=pstr[0],c2=pstr[1],x1=0,x2=0; + if(c1>='0' && c1<='9')x1=c1-'0'; + if(c1>='a' && c1<='f')x1=c1-'a'+10; + if(c1>='A' && c1<='F')x1=c1-'A'+10; + if(c2>='0' && c2<='9')x2=c2-'0'; + if(c2>='a' && c2<='f')x2=c2-'a'+10; + if(c2>='A' && c2<='F')x2=c2-'A'+10; + g->emblem_data[i]=(x1<<4)|x2; + } + mysql_free_result(sql_res); + + //printf("- Read guild_member %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name` " + "FROM `%s` WHERE `guild_id`='%d' ORDER BY `position`", guild_member_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&imax_member);i++){ + struct guild_member *m = &g->member[i]; + m->account_id=atoi(sql_row[1]); + m->char_id=atoi(sql_row[2]); + m->hair=atoi(sql_row[3]); + m->hair_color=atoi(sql_row[4]); + m->gender=atoi(sql_row[5]); + m->class_=atoi(sql_row[6]); + m->lv=atoi(sql_row[7]); + m->exp=atoi(sql_row[8]); + m->exp_payper=atoi(sql_row[9]); + m->online=atoi(sql_row[10]); + if (atoi(sql_row[11]) >= MAX_GUILDPOSITION) // Fix reduction of MAX_GUILDPOSITION [PoW] + m->position = MAX_GUILDPOSITION - 1; + else + m->position = atoi(sql_row[11]); + + strncpy(m->name,sql_row[14],NAME_LENGTH-1); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_position %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`position`,`name`,`mode`,`exp_mode` FROM `%s` WHERE `guild_id`='%d'",guild_position_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&iposition[position]; + strncpy(p->name,sql_row[2],NAME_LENGTH-1); + p->mode=atoi(sql_row[3]); + p->exp_mode=atoi(sql_row[4]); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_alliance %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`opposition`,`alliance_id`,`name` FROM `%s` WHERE `guild_id`='%d'",guild_alliance_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&ialliance[i]; + a->opposition=atoi(sql_row[1]); + a->guild_id=atoi(sql_row[2]); + strncpy(a->name,sql_row[3],NAME_LENGTH-1); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_expulsion %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3` FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&iexplusion[i]; + + strncpy(e->name,sql_row[1],NAME_LENGTH-1); + //No need to copy char 40, the null terminator. [Skotlex] + strncpy(e->mes,sql_row[2],39); + strncpy(e->acc,sql_row[3],39); + e->account_id=atoi(sql_row[4]); + e->rsv1=atoi(sql_row[5]); + e->rsv2=atoi(sql_row[6]); + e->rsv3=atoi(sql_row[7]); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_skill %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`id`,`lv` FROM `%s` WHERE `guild_id`='%d' ORDER BY `id`",guild_skill_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + + for(i = 0; i < MAX_GUILDSKILL; i++) + { //Skill IDs must always be initialized. [Skotlex] + g->skill[i].id = i + GD_SKILLBASE; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + while ((sql_row = mysql_fetch_row(sql_res))){ + int id = atoi(sql_row[1])-GD_SKILLBASE; + if (id >= 0 && id < MAX_GUILDSKILL) + //I know this seems ridiculous, but the skills HAVE to be placed on their 'correct' array slot or things break x.x [Skotlex] + g->skill[id].lv=atoi(sql_row[2]); + } + } + mysql_free_result(sql_res); + + idb_put(guild_db_, guild_id, g); //Add to cache + g->save_flag |= GS_REMOVE; //But set it to be removed, in case it is not needed for long. + + if (save_log) + ShowInfo("Guild loaded (%d - %s)\n", guild_id, g->name); + + return g; +} + +int inter_guildcastle_tosql(struct guild_castle *gc){ + // `guild_castle` (`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`) + + if (gc==NULL) return 0; + #ifdef GUILD_DEBUG +ShowDebug("Save guild_castle (%d)\n", gc->castle_id); + #endif + +// sql_query("DELETE FROM `%s` WHERE `castle_id`='%d'",guild_castle_db, gc->castle_id); + + sprintf(tmp_sql,"REPLACE INTO `%s` " + "(`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`," + "`visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`," + "`Ghp0`, `Ghp1`, `Ghp2`, `Ghp3`, `Ghp4`, `Ghp5`, `Ghp6`, `Ghp7`)" + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d')", + guild_castle_db, gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, gc->triggerD, gc->nextTime, gc->payTime, + gc->createTime, gc->visibleC, + gc->guardian[0].visible, gc->guardian[1].visible, gc->guardian[2].visible, gc->guardian[3].visible, gc->guardian[4].visible, gc->guardian[5].visible, gc->guardian[6].visible, gc->guardian[7].visible, + gc->guardian[0].hp, gc->guardian[1].hp, gc->guardian[2].hp, gc->guardian[3].hp, gc->guardian[4].hp, gc->guardian[5].hp, gc->guardian[6].hp, gc->guardian[7].hp); + + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + + memcpy(&castles[gc->castle_id],gc,sizeof(struct guild_castle)); + + return 0; +} + + +// Read guild_castle from sql +int inter_guildcastle_fromsql(int castle_id,struct guild_castle *gc) +{ + static int castles_init=0; + if (gc==NULL) return 0; + if (castle_id==-1) return 0; + + if(!castles_init) + { + int i; + for(i=0;icastle_id=castle_id; + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL){ + mysql_free_result(sql_res); + return 1; //Assume empty castle. + } + gc->guild_id = atoi (sql_row[1]); + gc->economy = atoi (sql_row[2]); + gc->defense = atoi (sql_row[3]); + gc->triggerE = atoi (sql_row[4]); + gc->triggerD = atoi (sql_row[5]); + gc->nextTime = atoi (sql_row[6]); + gc->payTime = atoi (sql_row[7]); + gc->createTime = atoi (sql_row[8]); + gc->visibleC = atoi (sql_row[9]); + gc->guardian[0].visible = atoi (sql_row[10]); + gc->guardian[1].visible = atoi (sql_row[11]); + gc->guardian[2].visible = atoi (sql_row[12]); + gc->guardian[3].visible = atoi (sql_row[13]); + gc->guardian[4].visible = atoi (sql_row[14]); + gc->guardian[5].visible = atoi (sql_row[15]); + gc->guardian[6].visible = atoi (sql_row[16]); + gc->guardian[7].visible = atoi (sql_row[17]); + gc->guardian[0].hp = atoi (sql_row[18]); + gc->guardian[1].hp = atoi (sql_row[19]); + gc->guardian[2].hp = atoi (sql_row[20]); + gc->guardian[3].hp = atoi (sql_row[21]); + gc->guardian[4].hp = atoi (sql_row[22]); + gc->guardian[5].hp = atoi (sql_row[23]); + gc->guardian[6].hp = atoi (sql_row[24]); + gc->guardian[7].hp = atoi (sql_row[25]); + + if (save_log) + ShowInfo("Loaded Castle %d (guild %d)\n",castle_id,gc->guild_id); + + } + mysql_free_result(sql_res) ; //resource free + + memcpy(&castles[castle_id],gc,sizeof(struct guild_castle)); + + return 1; +} + + +// Read exp_guild.txt +int inter_guild_ReadEXP() +{ + int i; + FILE *fp; + char line[1024]; + for (i=0;i<100;i++) guild_exp[i]=0; + + sprintf(line, "%s/exp_guild.txt", db_path); + fp=fopen(line,"r"); + if(fp==NULL){ + ShowError("can't read %s\n", line); + return 1; + } + i=0; + while(fgets(line,256,fp) && i<100){ + if(line[0]=='/' && line[1]=='/') + continue; + guild_exp[i]=atoi(line); + i++; + } + fclose(fp); + + return 0; +} + + +int inter_guild_CharOnline(int char_id, int guild_id) { + + struct guild *g; + int i; + + if (guild_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT guild_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No guild? + + sql_row = mysql_fetch_row(sql_res); + guild_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (guild_id == 0) + return 0; //No guild... + + g = inter_guild_fromsql(guild_id); + if(!g) { + ShowError("Character %d's guild %d not found!\n", char_id, guild_id); + return 0; + } + + //Member has logged in before saving, tell saver not to delete + if(g->save_flag & GS_REMOVE) + g->save_flag &= ~GS_REMOVE; + + //Set member online + for(i=0; imax_member; i++) { + if (g->member[i].char_id == char_id) { + g->member[i].online = 1; + break; + } + } + return 1; +} + +int inter_guild_CharOffline(int char_id, int guild_id) { + struct guild *g=NULL; + int online_count=0, i; + + if (guild_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT guild_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No guild? + + sql_row = mysql_fetch_row(sql_res); + guild_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (guild_id == 0) + return 0; //No guild... + + //Character has a guild, set character offline and check if they were the only member online + g = inter_guild_fromsql(guild_id); + if (g == NULL) //Guild not found? + return 0; + + //Set member offline + for(i=0; imax_member; i++) { + if(g->member[i].char_id == char_id) + g->member[i].online = 0; + if(g->member[i].online) + online_count++; + } + + if(online_count == 0) + g->save_flag |= GS_REMOVE; + + return 1; +} + +// Initialize guild sql +int inter_guild_sql_init() +{ + int i; + + //Initialize the guild cache + guild_db_= db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + //Read exp file + inter_guild_ReadEXP(); + + //Set the new guild ID + sprintf (tmp_sql , "SELECT max(`guild_id`) FROM `%s`",guild_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + exit(0); + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res) { + sql_row = mysql_fetch_row(sql_res); + if(sql_row[0]) { + if((i = atoi(sql_row[0])) != 0) { + guild_newid = i+1; + } + } + mysql_free_result(sql_res); + } + + add_timer_func_list(guild_save_timer, "guild_save_timer"); + add_timer(gettick() + 10000, guild_save_timer, 0, 0); + return 0; +} + +static int guild_db_final(DBKey key, void *data, va_list ap) +{ + struct guild *g = (struct guild*)data; + if (g->save_flag&GS_MASK) { + inter_guild_tosql(g, g->save_flag&GS_MASK); + return 1; + } + return 0; +} + +void inter_guild_sql_final() +{ + guild_db_->destroy(guild_db_, guild_db_final); + return; +} + +// Get guild_id by its name. Returns 0 if not found, -1 on error. +int search_guildname(char *str) +{ + int guild_id; + + //Lookup guilds with the same name + sprintf (tmp_sql , "SELECT guild_id FROM `%s` WHERE name='%s'",guild_db,jstrescape(str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -1; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res) sql_row = mysql_fetch_row(sql_res); + + guild_id = (sql_row&&sql_res&&sql_row[0])?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + return guild_id; +} + +// Check if guild is empty +int guild_check_empty(struct guild *g) +{ + int i; + for(i=0;imax_member;i++){ + if(g->member[i].account_id>0){ + return 0; + } + } + //Let the calling function handle the guild removal in case they need + //to do something else with it before freeing the data. [Skotlex] + return 1; +} + +int guild_nextexp(int level) +{ + if (level == 0) + return 1; + if (level < 100 && level > 0) // Change by hack + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルが・るか確認 +int guild_checkskill(struct guild *g,int id) { + + int idx = id - GD_SKILLBASE; + + + if(idx < 0 || idx >= MAX_GUILDSKILL) + + return 0; + + return g->skill[idx].lv; + +} + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) +{ + int i,c,nextexp; + struct guild before=*g; + + // スキルIDの設定 + for(i=0;iskill[i].id=i+GD_SKILLBASE; + + // ギルドレベル + if(g->guild_lv<=0) g->guild_lv=1; + nextexp = guild_nextexp(g->guild_lv); + if(nextexp > 0) { + while(g->exp >= nextexp && nextexp > 0){ //fixed guild exp overflow [Kevin] + g->exp-=nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member = 16 + guild_checkskill(g, GD_EXTENSION) * 6; //Guild Extention skill - currently adds 6 to max per skill lv. + if(g->max_member > MAX_GUILD) + { + ShowError("Guild %d:%s has capacity for too many guild members (%d), max supported is %d\n", g->guild_id, g->name, g->max_member, MAX_GUILD); + g->max_member = MAX_GUILD; + } + + // 平均レベルとオンライン人数 + g->average_lv=0; + g->connect_member=0; + for(i=c=0;imax_member;i++){ + if(g->member[i].account_id>0){ + g->average_lv+=g->member[i].lv; + c++; + + if(g->member[i].online>0) + g->connect_member++; + } + } + if(c) g->average_lv/=c; + + // 全データを送る必要が・りそう + if(g->max_member!=before.max_member || + g->guild_lv!=before.guild_lv || + g->skill_point!=before.skill_point ){ + mapif_guild_info(-1,g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd,int account_id,struct guild *g) +{ + WFIFOW(fd,0)=0x3830; + WFIFOL(fd,2)=account_id; + if(g!=NULL){ + WFIFOL(fd,6)=g->guild_id; + ShowInfo("int_guild: Guild created (%d - %s)\n",g->guild_id,g->name); + }else{ + WFIFOL(fd,6)=0; + } + WFIFOSET(fd,10); + return 0; +} +// ギルド情報見つからず +int mapif_guild_noinfo(int fd,int guild_id) +{ + unsigned char buf[12]; + WBUFW(buf,0)=0x3831; + WBUFW(buf,2)=8; + WBUFL(buf,4)=guild_id; + ShowWarning("int_guild: info not found %d\n",guild_id); + if(fd<0) + mapif_sendall(buf,8); + else + mapif_send(fd,buf,8); + return 0; +} +// ギルド情報まとめ送り +int mapif_guild_info(int fd,struct guild *g) +{ + unsigned char buf[8+sizeof(struct guild)]; + WBUFW(buf,0)=0x3831; + memcpy(buf+4,g,sizeof(struct guild)); + WBUFW(buf,2)=4+sizeof(struct guild); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd,int guild_id,int account_id,int char_id,int flag) +{ + WFIFOW(fd,0)=0x3832; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=account_id; + WFIFOL(fd,10)=char_id; + WFIFOB(fd,14)=flag; + WFIFOSET(fd,15); + return 0; +} +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x3834; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=account_id; + WBUFL(buf,10)=char_id; + WBUFB(buf,14)=flag; + memcpy(WBUFP(buf,15),mes,40); + memcpy(WBUFP(buf,55),name,NAME_LENGTH); + mapif_sendall(buf,55+NAME_LENGTH); +// mapif_sendall(buf,79); + ShowInfo("int_guild: guild leaved (%d - %d: %s - %s)\n",guild_id,account_id,name,mes); + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g,int idx) +{ + unsigned char buf[32]; + WBUFW(buf, 0)=0x3835; + WBUFL(buf, 2)=g->guild_id; + WBUFL(buf, 6)=g->member[idx].account_id; + WBUFL(buf,10)=g->member[idx].char_id; + WBUFB(buf,14)=(unsigned char)g->member[idx].online; + WBUFW(buf,15)=g->member[idx].lv; + WBUFW(buf,17)=g->member[idx].class_; + mapif_sendall(buf,19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3836; + WBUFL(buf,2)=guild_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + ShowInfo("int_guild: Guild broken (%d)\n",guild_id); + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id,int account_id,char *mes,int len, int sfd) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3837; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=guild_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendallwos(sfd, buf,len+12); + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x3839; + WBUFW(buf, 2)=len+10; + WBUFL(buf, 4)=guild_id; + WBUFW(buf, 8)=type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x383a; + WBUFW(buf, 2)=len+18; + WBUFL(buf, 4)=guild_id; + WBUFL(buf, 8)=account_id; + WBUFL(buf,12)=char_id; + WBUFW(buf,16)=type; + memcpy(WBUFP(buf,18),data,len); + mapif_sendall(buf,len+18); + return 0; +} +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id,int skill_num,int account_id) +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x383c; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=skill_num; + WBUFL(buf,10)=account_id; + mapif_sendall(buf,14); + return 0; +} +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x383d; + WBUFL(buf, 2)=guild_id1; + WBUFL(buf, 6)=guild_id2; + WBUFL(buf,10)=account_id1; + WBUFL(buf,14)=account_id2; + WBUFB(buf,18)=flag; + memcpy(WBUFP(buf,19),name1,NAME_LENGTH); + memcpy(WBUFP(buf,19+NAME_LENGTH),name2,NAME_LENGTH); + mapif_sendall(buf,19+2*NAME_LENGTH); +// memcpy(WBUFP(buf,43),name2,NAME_LENGTH); +// mapif_sendall(buf,67); + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g,int idx) +{ + unsigned char buf[128]; + WBUFW(buf,0)=0x383b; + WBUFW(buf,2)=sizeof(struct guild_position)+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=idx; + memcpy(WBUFP(buf,12),&g->position[idx],sizeof(struct guild_position)); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) +{ + unsigned char buf[256]; + WBUFW(buf,0)=0x383e; + WBUFL(buf,2)=g->guild_id; + memcpy(WBUFP(buf,6),g->mes1,60); + memcpy(WBUFP(buf,66),g->mes2,120); + mapif_sendall(buf,186); + return 0; +} +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) +{ + unsigned char buf[2048]; + WBUFW(buf,0)=0x383f; + WBUFW(buf,2)=g->emblem_len+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=g->emblem_id; + memcpy(WBUFP(buf,12),g->emblem_data,g->emblem_len); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} +int mapif_guild_master_changed(struct guild *g, int position) +{ + unsigned char buf[12]; + WBUFW(buf,0)=0x3843; + WBUFL(buf,2)=g->guild_id; + WBUFL(buf,6)=position; + mapif_sendall(buf,10); + return 0; +} + +int mapif_guild_castle_dataload(int castle_id,int index,int value) // +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3840; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_datasave(int castle_id,int index,int value) // +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3841; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + struct guild_castle* gc = (struct guild_castle *)aMalloc(sizeof(struct guild_castle)); + int i, len = 4; + + WFIFOW(fd,0) = 0x3842; + sprintf(tmp_sql,"SELECT * FROM `%s` ORDER BY `castle_id`", guild_castle_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + for(i = 0; ((sql_row = mysql_fetch_row(sql_res)) && i < MAX_GUILDCASTLE); i++) { + memset(gc, 0, sizeof(struct guild_castle)); + gc->castle_id = atoi(sql_row[0]); + gc->guild_id = atoi(sql_row[1]); + gc->economy = atoi(sql_row[2]); + gc->defense = atoi(sql_row[3]); + gc->triggerE = atoi(sql_row[4]); + gc->triggerD = atoi(sql_row[5]); + gc->nextTime = atoi(sql_row[6]); + gc->payTime = atoi(sql_row[7]); + gc->createTime = atoi(sql_row[8]); + gc->visibleC = atoi(sql_row[9]); + gc->guardian[0].visible = atoi(sql_row[10]); + gc->guardian[1].visible = atoi(sql_row[11]); + gc->guardian[2].visible = atoi(sql_row[12]); + gc->guardian[3].visible = atoi(sql_row[13]); + gc->guardian[4].visible = atoi(sql_row[14]); + gc->guardian[5].visible = atoi(sql_row[15]); + gc->guardian[6].visible = atoi(sql_row[16]); + gc->guardian[7].visible = atoi(sql_row[17]); + gc->guardian[0].visible = atoi(sql_row[18]); + gc->guardian[1].visible = atoi(sql_row[19]); + gc->guardian[2].visible = atoi(sql_row[20]); + gc->guardian[3].visible = atoi(sql_row[21]); + gc->guardian[4].visible = atoi(sql_row[22]); + gc->guardian[5].visible = atoi(sql_row[23]); + gc->guardian[6].visible = atoi(sql_row[24]); + gc->guardian[7].visible = atoi(sql_row[25]); + memcpy(WFIFOP(fd,len), gc, sizeof(struct guild_castle)); + len += sizeof(struct guild_castle); + } + } + mysql_free_result(sql_res); + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + + aFree(gc); + + return 0; +} + + +//------------------------------------------------------------------- +// map serverからの通信 + + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd,int account_id,char *name,struct guild_member *master) +{ + struct guild *g; + int i=0; +#ifdef NOISY + ShowInfo("Creating Guild (%s)\n", name); +#endif + if(search_guildname(name) != 0){ + ShowInfo("int_guild: guild with same name exists [%s]\n",name); + mapif_guild_created(fd,account_id,NULL); + return 0; + } + g = (struct guild *)aMalloc(sizeof(struct guild)); + memset(g,0,sizeof(struct guild)); + g->guild_id=guild_newid++; + if (inter_guild_fromsql(g->guild_id) != NULL) { + ShowWarning("mapif_parse_CreateGuild: New Guild ID [%d] already exists!\n", g->guild_id); + mapif_guild_created(fd,account_id,NULL); + aFree(g); + return 0; + } + memcpy(g->name,name,NAME_LENGTH); + memcpy(g->master,master->name,NAME_LENGTH); + memcpy(&g->member[0],master,sizeof(struct guild_member)); + + g->position[0].mode=0x11; + strcpy(g->position[0].name,"GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name,"Newbie"); + for(i=1;iposition[i].name,"Position %d",i+1); + + // Initialize guild property + g->max_member=16; + g->average_lv=master->lv; + for(i=0;iskill[i].id=i + GD_SKILLBASE; + //Add to cache + ShowInfo("Created Guild %d - %s (Guild Master: %s)\n", g->guild_id, g->name, g->master); + idb_put(guild_db_, g->guild_id, g); + inter_guild_tosql(g,GS_BASIC|GS_POSITION|GS_SKILL); //Better save the whole guild right now. + + // Report to client + mapif_guild_created(fd,account_id,g); + mapif_guild_info(fd,g); + + if(log_inter) + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id ); + + return 0; +} +// Return guild info to client +int mapif_parse_GuildInfo(int fd,int guild_id) +{ + struct guild * g = inter_guild_fromsql(guild_id); //We use this because on start-up the info of castle-owned guilds is requied. [Skotlex] + if(g){ + if (!guild_calcinfo(g)) + mapif_guild_info(fd,g); + //inter_guild_tosql(g,GS_BASIC); // Change guild + }else + mapif_guild_noinfo(fd,guild_id); + return 0; +} +// Add member to guild +int mapif_parse_GuildAddMember(int fd,int guild_id,struct guild_member *m) +{ + struct guild * g; + int i; + + g = inter_guild_fromsql(guild_id); + if(g==NULL){ + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; + } + + for(i=0;imax_member;i++){ + if(g->member[i].account_id==0){ + + memcpy(&g->member[i],m,sizeof(struct guild_member)); + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,0); + if (!guild_calcinfo(g)) //Send members if it was not invoked. + mapif_guild_info(fd,g); + g->save_flag |= (GS_BASIC|GS_MEMBER); + if (g->save_flag&GS_REMOVE) + g->save_flag&=~GS_REMOVE; + return 0; + } + } + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; +} +// Delete member from guild +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + struct guild * g = inter_guild_fromsql(guild_id); + + if(g){ + int i; + for(i=0;imax_member;i++){ + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + if(flag){ // 追放の場合追放リストに入れる + int j; + for(j=0;jexplusion[j].account_id==0) + break; + } + if(j==MAX_GUILDEXPLUSION){ // 一杯なので古いのを消す + for(j=0;jexplusion[j]=g->explusion[j+1]; + j=MAX_GUILDEXPLUSION-1; + } + g->explusion[j].account_id=account_id; + memcpy(g->explusion[j].acc,"dummy",NAME_LENGTH-1); + memcpy(g->explusion[j].name,g->member[i].name,NAME_LENGTH-1); + memcpy(g->explusion[j].mes,mes,40); + } + + mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); + memset(&g->member[i],0,sizeof(struct guild_member)); + + if(!guild_check_empty(g)) { + break; + } + //Guild empty? break it. + mapif_parse_BreakGuild(-1,guild_id); //Break the guild. + return 0; + } + } + //Update member info. + if (!guild_calcinfo(g)) + mapif_guild_info(fd,g); + g->save_flag |= (GS_BASIC|GS_MEMBER|GS_EXPULSION); + }else{ + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `account_id`='%d' AND `char_id`='%d'",char_db, account_id,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + /* mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); */ + } + + return 0; +} +// Change member info +int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id, + int account_id,int char_id,int online,int lv,int class_) +{ + // Could speed up by manipulating only guild_member + struct guild * g; + int i,alv,c; + int prev_count; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + prev_count = g->connect_member; + g->connect_member=0; + + for(i=0,alv=0,c=0;imax_member;i++){ + if(g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + + g->member[i].online=online; + g->member[i].lv=lv; + g->member[i].class_=class_; + mapif_guild_memberinfoshort(g,i); + } + if( g->member[i].account_id>0 ){ + alv+=g->member[i].lv; + c++; + } + if( g->member[i].online ) + g->connect_member++; + } + + if (c) + { + alv= alv/c; + if (g->connect_member != prev_count || g->average_lv != alv) + { + g->average_lv=alv; + g->save_flag |= GS_BASIC; //FIXME: Save the base guild just because the avl/connect count changed? + } + if (g->save_flag & GS_REMOVE) + g->save_flag &= ~GS_REMOVE; + } + g->save_flag |= GS_MEMBER; //Update guild member data + return 0; +} + +// BreakGuild +int mapif_parse_BreakGuild(int fd,int guild_id) +{ + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + // Delete guild from sql + //printf("- Delete guild %d from guild\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_member_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_castle_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_storage_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d' OR `alliance_id` = '%d'", guild_alliance_db, guild_id, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_position_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_skill_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_expulsion_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //printf("- Update guild %d of char\n",guild_id); + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + mapif_guild_broken(guild_id,0); + + if(log_inter) + inter_log("guild %s (id=%d) broken" RETCODE,g->name,guild_id); + + //Remove the guild from memory. [Skotlex] + g = idb_remove(guild_db_, guild_id); + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd,int guild_id,int account_id,char *mes,int len) +{ + return mapif_guild_message(guild_id,account_id,mes,len, fd); +} +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd,int guild_id, + int type,const char *data,int len) +{ + struct guild * g; +// int dd=*((int *)data); + short dw=*((short *)data); + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + switch(type){ + case GBI_GUILDLV: { + ShowDebug("GBI_GUILDLV\n"); + if(dw>0 && g->guild_lv+dw<=50){ + g->guild_lv+=dw; + g->skill_point+=dw; + }else if(dw<0 && g->guild_lv+dw>=1) + g->guild_lv+=dw; + mapif_guild_info(-1,g); + g->save_flag |= GS_BASIC; + } return 0; + default: + ShowError("int_guild: GuildBasicInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_basicinfochanged(guild_id,type,data,len); + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd,int guild_id,int account_id,int char_id, + int type,const char *data,int len) +{ + // Could make some improvement in speed, because only change guild_member + int i; + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + for(i=0;imax_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + break; + if(i==g->max_member){ + ShowWarning("int_guild: GuildMemberChange: Not found %d,%d in guild (%d - %s)\n", + account_id,char_id,guild_id,g->name); + return 0; + } + switch(type){ + case GMI_POSITION: // 役職 + { + g->member[i].position=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= (GS_BASIC|GS_MEMBER); + break; + } + case GMI_EXP: + { // EXP + int exp,oldexp=g->member[i].exp; + exp=g->member[i].exp=*((unsigned int *)data); + g->exp+=(exp-oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id,GBI_EXP,&g->exp,4); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= (GS_BASIC|GS_MEMBER); + break; + } + case GMI_HAIR: + { + g->member[i].hair=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_HAIR_COLOR: + { + g->member[i].hair_color=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_GENDER: + { + g->member[i].gender=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_CLASS: + { + g->member[i].class_=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_LEVEL: + { + g->member[i].lv=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + default: + ShowError("int_guild: GuildMemberInfoChange: Unknown type %d\n",type); + break; + } + return 0; +} + +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender) +{ + return mapif_parse_GuildMemberInfoChange(0, guild_id, account_id, char_id, GMI_GENDER, (const char*)&gender, sizeof(gender)); +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position *p) +{ + // Could make some improvement in speed, because only change guild_position + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL || idx<0 || idx>=MAX_GUILDPOSITION) + return 0; + + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + mapif_guild_position(g,idx); + ShowInfo("int_guild: position data changed (Guild %d, position %d)\n",guild_id, idx); + g->save_flag |= GS_POSITION; // Change guild_position + return 0; +} +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd,int guild_id,int skill_num,int account_id) +{ + // Could make some improvement in speed, because only change guild_position + struct guild * g; + int idx = skill_num - GD_SKILLBASE; + + g = inter_guild_fromsql(guild_id); + if(g == NULL || idx < 0 || idx >= MAX_GUILDSKILL) + return 0; + + if(g->skill_point>0 && g->skill[idx].id>0 && + g->skill[idx].lv<10 ){ + g->skill[idx].lv++; + g->skill_point--; + if (!guild_calcinfo(g)) + mapif_guild_info(-1,g); + mapif_guild_skillupack(guild_id,skill_num,account_id); + ShowDebug("int_guild: skill %d up\n",skill_num); + g->save_flag |= (GS_BASIC|GS_SKILL); // Change guild & guild_skill + } + + return 0; +} + +//Manual deletion of an alliance when partnering guild does not exists. [Skotlex] +static int mapif_parse_GuildDeleteAlliance(struct guild *g, int guild_id, int account_id1, int account_id2, int flag) +{ + int i; + char name[NAME_LENGTH]; + for(i=0;ialliance[i].guild_id == guild_id) + { + strcpy(name, g->alliance[i].name); + g->alliance[i].guild_id=0; + break; + } + if (i == MAX_GUILDALLIANCE) + return -1; + + mapif_guild_alliance(g->guild_id,guild_id,account_id1,account_id2,flag,g->name,name); + g->save_flag |= GS_ALLIANCE; + return 0; +} +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2, + int account_id1,int account_id2,int flag) +{ + // Could speed up + struct guild *g[2]; + int j,i; + g[0] = inter_guild_fromsql(guild_id1); + g[1] = inter_guild_fromsql(guild_id2); + + if(g[0] && g[1]==NULL && (flag&0x8)) //Requested to remove an alliance with a not found guild. + return mapif_parse_GuildDeleteAlliance(g[0], guild_id2, + account_id1, account_id2, flag); //Try to do a manual removal of said guild. + + if(g[0]==NULL || g[1]==NULL) + return 0; + + if(!(flag&0x8)){ + for(i=0;i<2-(flag&1);i++){ + for(j=0;jalliance[j].guild_id==0){ + g[i]->alliance[j].guild_id=g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name,g[1-i]->name,NAME_LENGTH-1); + g[i]->alliance[j].opposition=flag&1; + break; + } + } + }else{ // 関係解消 + for(i=0;i<2-(flag&1);i++){ + for(j=0;jalliance[j].guild_id==g[1-i]->guild_id && + g[i]->alliance[j].opposition==(flag&1)){ + g[i]->alliance[j].guild_id=0; + break; + } + } + } + mapif_guild_alliance(guild_id1,guild_id2,account_id1,account_id2,flag, + g[0]->name,g[1]->name); + g[0]->save_flag |= GS_ALLIANCE; + g[1]->save_flag |= GS_ALLIANCE; + return 0; +} +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes2) +{ + struct guild *g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + memcpy(g->mes1,mes1,60); + memcpy(g->mes2,mes2,120); + g->save_flag |= GS_BASIC; //Change mes of guild + return mapif_guild_notice(g); +} +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd,int len,int guild_id,int dummy,const char *data) +{ + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id++; + g->save_flag |= GS_BASIC; //Change guild + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd,int castle_id,int index) // +{ + struct guild_castle gc; + if (!inter_guildcastle_fromsql(castle_id, &gc)) { + return mapif_guild_castle_dataload(castle_id,0,0); + } + switch(index){ + case 1: return mapif_guild_castle_dataload(gc.castle_id,index,gc.guild_id); break; + case 2: return mapif_guild_castle_dataload(gc.castle_id,index,gc.economy); break; + case 3: return mapif_guild_castle_dataload(gc.castle_id,index,gc.defense); break; + case 4: return mapif_guild_castle_dataload(gc.castle_id,index,gc.triggerE); break; + case 5: return mapif_guild_castle_dataload(gc.castle_id,index,gc.triggerD); break; + case 6: return mapif_guild_castle_dataload(gc.castle_id,index,gc.nextTime); break; + case 7: return mapif_guild_castle_dataload(gc.castle_id,index,gc.payTime); break; + case 8: return mapif_guild_castle_dataload(gc.castle_id,index,gc.createTime); break; + case 9: return mapif_guild_castle_dataload(gc.castle_id,index,gc.visibleC); break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + return mapif_guild_castle_dataload(gc.castle_id,index,gc.guardian[index-10].visible); break; + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + return mapif_guild_castle_dataload(gc.castle_id,index,gc.guardian[index-18].hp); break; + default: + ShowError("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } +} + +int mapif_parse_GuildCastleDataSave(int fd,int castle_id,int index,int value) // +{ + struct guild_castle gc; + if(!inter_guildcastle_fromsql(castle_id, &gc)) + return mapif_guild_castle_datasave(castle_id,index,value); + + switch(index){ + case 1: + if( gc.guild_id!=value ){ + int gid=(value)?value:gc.guild_id; + struct guild *g=idb_get(guild_db_, gid); + if(log_inter) + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g)?g->name:"??" ,gid, (value)?"occupy":"abandon", castle_id); + } + gc.guild_id = value; + break; + case 2: gc.economy = value; break; + case 3: gc.defense = value; break; + case 4: gc.triggerE = value; break; + case 5: gc.triggerD = value; break; + case 6: gc.nextTime = value; break; + case 7: gc.payTime = value; break; + case 8: gc.createTime = value; break; + case 9: gc.visibleC = value; break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + gc.guardian[index-10].visible = value; break; + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + gc.guardian[index-18].hp = value; break; // end additions [Valaris] + default: + ShowError("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + inter_guildcastle_tosql(&gc); + mapif_guild_castle_datasave(gc.castle_id,index,value); + return 0; +} + +int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int len) +{ + struct guild * g; + struct guild_member gm; + int pos; + + g = inter_guild_fromsql(guild_id); + + if(g==NULL || len > NAME_LENGTH) + return 0; + + for (pos = 0; pos < g->max_member && strncmp(g->member[pos].name, name, len); pos++); + + if (pos == g->max_member) + return 0; //Character not found?? + + memcpy(&gm, &g->member[pos], sizeof (struct guild_member)); + memcpy(&g->member[pos], &g->member[0], sizeof(struct guild_member)); + memcpy(&g->member[0], &gm, sizeof(struct guild_member)); + + g->member[pos].position = g->member[0].position; + g->member[0].position = 0; //Position 0: guild Master. + strncpy(g->master, name, len); + if (len < NAME_LENGTH) + g->master[len] = '\0'; + + ShowInfo("int_guild: Guildmaster Changed to %s (Guild %d - %s)\n",g->master, guild_id, g->name); + g->save_flag |= (GS_BASIC|GS_POSITION); //Save main data and member data. + return mapif_guild_master_changed(g, pos); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd,int guild_id,int account_id,int char_id) +{ + // What does this mean? Check if belong to another guild? + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3030: mapif_parse_CreateGuild(fd,RFIFOL(fd,4),(char*)RFIFOP(fd,8),(struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd,RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd,RFIFOL(fd,4),(struct guild_member *)RFIFOP(fd,8)); break; + case 0x3033: mapif_parse_GuildMasterChange(fd,RFIFOL(fd,4),(const char*)RFIFOP(fd,8),RFIFOW(fd,2)-8); break; + case 0x3034: mapif_parse_GuildLeave(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),(const char*)RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd,RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd,RFIFOL(fd,4),RFIFOL(fd,8),(char*)RFIFOP(fd,12),RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd,RFIFOL(fd,4),RFIFOW(fd,8),(const char*)RFIFOP(fd,10),RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOL(fd,12),RFIFOW(fd,16),(const char*)RFIFOP(fd,18),RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd,RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14),RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd,RFIFOL(fd,2),(const char*)RFIFOP(fd,6),(const char*)RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd,RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8),(const char*)RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd,RFIFOW(fd,2),RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd,RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); break; + + default: + return 0; + } + return 1; +} + +int inter_guild_mapif_init(int fd) +{ + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id,int account_id,int char_id) +{ + return mapif_parse_GuildLeave(-1,guild_id,account_id,char_id,0,"** Character Deleted **"); +} + +int inter_guild_broken(int guild_id) +{ + return mapif_guild_broken(guild_id, 0); +} diff --git a/src/char_sql/int_guild.h b/src/char_sql/int_guild.h new file mode 100644 index 000000000..452d55612 --- /dev/null +++ b/src/char_sql/int_guild.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_parse_frommap(int fd); +int inter_guild_sql_init(); +void inter_guild_sql_final(); +int inter_guild_mapif_init(int fd); +int inter_guild_leave(int guild_id,int account_id,int char_id); +int mapif_parse_BreakGuild(int fd,int guild_id); +int inter_guild_broken(int guild_id); +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender); +int inter_guild_CharOnline(int char_id, int guild_id); +int inter_guild_CharOffline(int char_id, int guild_id); + +#endif diff --git a/src/char_sql/int_party.c b/src/char_sql/int_party.c new file mode 100644 index 000000000..0479df848 --- /dev/null +++ b/src/char_sql/int_party.c @@ -0,0 +1,865 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by hack + +#include +#include +#include +#include "char.h" +#include "../common/db.h" +#include "../common/strlib.h" +#include "../common/socket.h" +#include "../common/showmsg.h" + +static struct party *party_pt; +static int party_newid = 100; +static struct dbt *party_db_; + +int mapif_party_broken(int party_id,int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id); + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +//Party Flags on what to save/delete. +//Create a new party entry (index holds leader's info) +#define PS_CREATE 0x01 +//Update basic party info. +#define PS_BASIC 0x02 +//Update party's leader +#define PS_LEADER 0x04 +//Specify new party member (index specifies which party member) +#define PS_ADDMEMBER 0x08 +//Specify member that left (index specifies which party member) +#define PS_DELMEMBER 0x10 +//Specify that this party must be deleted. +#define PS_BREAK 0x20 + +// Save party to mysql +int inter_party_tosql(int party_id,struct party *p, int flag, int index) +{ + // 'party' ('party_id','name','exp','item','leader_id','leader_char') + char t_name[NAME_LENGTH*2]; //Required for jstrescapecpy [Skotlex] + int party_exist = 0; + if (p == NULL || party_id == 0 || p->party_id == 0 || party_id != p->party_id) { + ShowError("Party pointer or party_id error (id: %d)\n", party_id); + return 0; + } +#ifdef NOISY + ShowInfo("Save party request ("CL_BOLD"%d"CL_RESET" - %s).\n", party_id, p->name); +#endif + jstrescapecpy(t_name, p->name); + + if (flag&PS_BREAK) { //Break the party + // we'll skip name-checking and just reset everyone with the same party id [celest] + sprintf (tmp_sql, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", char_db, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `party_id`='%d'", party_db, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + //Remove from memory + idb_remove(party_db_, party_id); + return 1; + } + + if(flag&PS_CREATE) { + //Create party, first check if ID exists. + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `party_id`='%d'", party_db, party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + sql_row = mysql_fetch_row(sql_res); + party_exist = atoi (sql_row[0]); + } + mysql_free_result(sql_res); + if (party_exist) { //TODO: Can't we just use an index, and then retrieve the new party's index from SQL? [Skotlex] + ShowError("inter_party_tosql: Creating party with already existing ID %d!\n", party_id); + aFree(p); //Free party, couldn't create it. + return 0; + } + sprintf(tmp_sql, "INSERT INTO `%s` (`party_id`, `name`, `exp`, `item`, `leader_id`, `leader_char`) VALUES ('%d', '%s', '%d', '%d', '%d', '%d')", + party_db, party_id, t_name, p->exp, p->item, p->member[index].account_id, p->member[index].char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(p); //Free party, couldn't create it. + return 0; + } + //Add party to db + idb_put(party_db_, party_id, p); + } + + if (flag&PS_BASIC) { + //Update party info. + sprintf(tmp_sql, "UPDATE `%s` SET `name`='%s', `exp`='%d', `item`='%d' WHERE `party_id`='%d'", + party_db, t_name, p->exp, p->item, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&PS_LEADER) { + //Update leader + sprintf(tmp_sql, "UPDATE `%s` SET `leader_id`='%d', `leader_char`='%d' WHERE `party_id`='%d'", + party_db, p->member[index].account_id, p->member[index].char_id, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&PS_ADDMEMBER) { + //Add one party member. + sprintf (tmp_sql, "UPDATE `%s` SET `party_id`='%d' WHERE `account_id`='%d' AND `char_id`='%d'", + char_db, party_id, p->member[index].account_id, p->member[index].char_id); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&PS_DELMEMBER) { + //Remove one party member. + sprintf (tmp_sql, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d' AND `account_id`='%d' AND `char_id`='%d'", + char_db, party_id, p->member[index].account_id, p->member[index].char_id); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (save_log) + ShowInfo("Party Saved (%d - %s)\n", party_id, p->name); + return 1; +} + +// Read party from mysql +struct party *inter_party_fromsql(int party_id) +{ + int leader_id = 0, leader_char = 0; + struct party *p; +#ifdef NOISY + ShowInfo("Load party request ("CL_BOLD"%d"CL_RESET")\n", party_id); +#endif + if (party_id <=0) + return NULL; + + //Load from memory + if ((p = idb_get(party_db_, party_id)) != NULL) + return p; + + p = party_pt; + memset(p, 0, sizeof(struct party)); + + sprintf(tmp_sql, "SELECT `party_id`, `name`,`exp`,`item`, `leader_id`, `leader_char` FROM `%s` WHERE `party_id`='%d'", + party_db, party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return NULL; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + sql_row = mysql_fetch_row(sql_res); + p->party_id = party_id; + memcpy(p->name, sql_row[1], NAME_LENGTH-1); + p->exp = atoi(sql_row[2])?1:0; + p->item = atoi(sql_row[3]); + leader_id = atoi(sql_row[4]); + leader_char = atoi(sql_row[5]); + } else { + mysql_free_result(sql_res); + return NULL; + } + mysql_free_result(sql_res); + + // Load members + sprintf(tmp_sql,"SELECT `account_id`,`char_id`,`name`,`base_level`,`last_map`,`online` FROM `%s` WHERE `party_id`='%d'", + char_db, party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + int i; + for (i = 0; (sql_row = mysql_fetch_row(sql_res)); i++) { + struct party_member *m = &p->member[i]; + m->account_id = atoi(sql_row[0]); + m->char_id = atoi(sql_row[1]); + m->leader = (m->account_id == leader_id && m->char_id == leader_char)?1:0; + memcpy(m->name, sql_row[2], NAME_LENGTH); + m->lv = atoi(sql_row[3]); + m->map = mapindex_name2id(sql_row[4]); + m->online = atoi(sql_row[5])?1:0; + } + } + mysql_free_result(sql_res); + + if (save_log) + ShowInfo("Party loaded (%d - %s).\n",party_id, p->name); + //Add party to memory. + p = aCalloc(1, sizeof(struct party)); + memcpy(p, party_pt, sizeof(struct party)); + idb_put(party_db_, party_id, p); + return p; +} + +int inter_party_sql_init(){ + int i; + + //memory alloc + party_db_ = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + party_pt = (struct party*)aCalloc(sizeof(struct party), 1); + if (!party_pt) { + ShowFatalError("inter_party_sql_init: Out of Memory!\n"); + exit(1); + } + sprintf (tmp_sql , "SELECT count(*) FROM `%s`", party_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + ShowStatus("total party data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set party_newid + sprintf (tmp_sql , "SELECT max(`party_id`) FROM `%s`", party_db); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + party_newid = atoi (sql_row[0])+1; + mysql_free_result(sql_res); + } + + ShowDebug("set party_newid: %d.......\n", party_newid); + + /* Uncomment the following if you want to do a party_db cleanup (remove parties with no members) on startup.[Skotlex] + ShowStatus("cleaning party table...\n"); + sprintf (tmp_sql, + "DELETE FROM `%s` USING `%s` LEFT JOIN `%s` ON `%s`.leader_id =`%s`.account_id AND `%s`.leader_char = `%s`.char_id WHERE `%s`.account_id IS NULL", + party_db, party_db, char_db, party_db, char_db, party_db, char_db, char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + */ + return 0; +} + +void inter_party_sql_final() +{ + party_db_->destroy(party_db_, NULL); + aFree(party_pt); + return; +} + +// Search for the party according to its name +struct party* search_partyname(char *str) +{ + char t_name[NAME_LENGTH*2]; + int party_id; + + sprintf(tmp_sql,"SELECT `party_id` FROM `%s` WHERE `name`='%s'",party_db, jstrescapecpy(t_name,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<=0) + { + if (sql_res) mysql_free_result(sql_res); + return NULL; + } + sql_row = mysql_fetch_row(sql_res); + party_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + + return inter_party_fromsql(party_id); +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) +{ + int i, oi[MAX_PARTY], dudes=0; + int maxlv=0,minlv=0x7fffffff; + + for(i=0;imember[i].lv; + if (!lv) continue; + if( p->member[i].online ){ + if( lv < minlv ) minlv=lv; + if( maxlv < lv ) maxlv=lv; + if( lv >= 70 ) dudes+=1000; + oi[dudes%1000] = i; + dudes++; + } + } + if((dudes/1000 >= 2) && (dudes%1000 == 3) && maxlv-minlv>party_share_level) + { + int pl1=0,pl2=0,pl3=0; + pl1=char_nick2id(p->member[oi[0]].name); + pl2=char_nick2id(p->member[oi[1]].name); + pl3=char_nick2id(p->member[oi[2]].name); + ShowDebug("PARTY: group of 3 Id1 %d lv %d name %s Id2 %d lv %d name %s Id3 %d lv %d name %s\n",pl1,p->member[oi[0]].lv,p->member[oi[0]].name,pl2,p->member[oi[1]].lv,p->member[oi[1]].name,pl3,p->member[oi[2]].lv,p->member[oi[2]].name); + if (char_married(pl1,pl2) && char_child(pl1,pl3)) + return 1; + if (char_married(pl1,pl3) && char_child(pl1,pl2)) + return 1; + if (char_married(pl2,pl3) && char_child(pl2,pl1)) + return 1; + } + return (maxlv==0 || maxlv-minlv<=party_share_level); +} + +// Is there any member in the party? +int party_check_empty(struct party *p) +{ + int i; + if (p==NULL||p->party_id==0) return 1; + for(i=0;imember[i].account_id>0){ + return 0; + } + } + // If there is no member, then break the party + mapif_party_broken(p->party_id,0); + inter_party_tosql(p->party_id,p, PS_BREAK, 0); + return 1; +} + + +// Check if a member is in two party, not necessary :) +int party_check_conflict(int party_id,int account_id,int char_id) +{ + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パーティ作成可否 +int mapif_party_created(int fd,int account_id,int char_id,struct party *p) +{ + WFIFOW(fd,0)=0x3820; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=char_id; + if(p!=NULL){ + WFIFOB(fd,10)=0; + WFIFOL(fd,11)=p->party_id; + memcpy(WFIFOP(fd,15),p->name,NAME_LENGTH); + ShowInfo("int_party: Party created (%d - %s)\n",p->party_id,p->name); + }else{ + WFIFOB(fd,10)=1; + WFIFOL(fd,11)=0; + memset(WFIFOP(fd,15),0,NAME_LENGTH); + } + WFIFOSET(fd,39); + + return 0; +} + +// パーティ情報見つからず +int mapif_party_noinfo(int fd,int party_id) +{ + WFIFOW(fd,0)=0x3821; + WFIFOW(fd,2)=8; + WFIFOL(fd,4)=party_id; + WFIFOSET(fd,8); + ShowWarning("int_party: info not found %d\n",party_id); + return 0; +} +// パーティ情報まとめ送り +int mapif_party_info(int fd,struct party *p) +{ + unsigned char buf[10+sizeof(struct party)]; + WBUFW(buf,0)=0x3821; + memcpy(buf+4,p,sizeof(struct party)); + WBUFW(buf,2)=4+sizeof(struct party); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); + return 0; +} +// パーティメンバ追加可否 +int mapif_party_memberadded(int fd, int party_id, int account_id, int char_id, int flag) { + WFIFOHEAD(fd, 15); + WFIFOW(fd,0) = 0x3822; + WFIFOL(fd,2) = party_id; + WFIFOL(fd,6) = account_id; + WFIFOL(fd,10) = char_id; + WFIFOB(fd,14) = flag; + WFIFOSET(fd,15); + + return 0; +} + +// パーティ設定変更通知 +int mapif_party_optionchanged(int fd,struct party *p,int account_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3823; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=account_id; + WBUFW(buf,10)=p->exp; + WBUFW(buf,12)=p->item; + WBUFB(buf,14)=flag; + if(flag==0) + mapif_sendall(buf,15); + else + mapif_send(fd,buf,15); + return 0; +} + +//Checks whether the even-share setting of a party is broken when a character logs in. [Skotlex] +int inter_party_logged(int party_id, int account_id, int char_id) +{ + struct party *p; + int i; + + if (party_id <= 0) + return 0; + + if (!party_id) + return 0; + p = inter_party_fromsql(party_id); + if(!p) //Non existant party? + return 0; + + for(i = 0; i < MAX_PARTY; i++) + if (p->member[i].account_id==account_id && p->member[i].char_id==char_id) { + p->member[i].online = 1; + break; + } + + if(p->exp && !party_check_exp_share(p)) + { + p->exp=0; + mapif_party_optionchanged(0,p,0,0); + return 1; + } + return 0; +} + +// パーティ脱退通知 +int mapif_party_leaved(int party_id,int account_id, int char_id) { + unsigned char buf[16]; + + WBUFW(buf,0) = 0x3824; + WBUFL(buf,2) = party_id; + WBUFL(buf,6) = account_id; + WBUFL(buf,10) = char_id; + mapif_sendall(buf, 14); + return 0; +} + +// パーティマップ更新通知 +int mapif_party_membermoved(struct party *p,int idx) +{ + unsigned char buf[20]; + + WBUFW(buf,0) = 0x3825; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = p->member[idx].account_id; + WBUFL(buf,10) = p->member[idx].char_id; + WBUFW(buf,14) = p->member[idx].map; + WBUFB(buf,16) = p->member[idx].online; + WBUFW(buf,17) = p->member[idx].lv; + mapif_sendall(buf, 19); + return 0; +} + +// パーティ解散通知 +int mapif_party_broken(int party_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3826; + WBUFL(buf,2)=party_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + //printf("int_party: broken %d\n",party_id); + return 0; +} +// パーティ内発言 +int mapif_party_message(int party_id,int account_id,char *mes,int len, int sfd) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3827; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=party_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendallwos(sfd, buf,len+12); + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// Create Party +int mapif_parse_CreateParty(int fd, int account_id, int char_id, char *name, char *nick, unsigned short map, int lv, int item, int item2) +{ + struct party *p; + if( (p=search_partyname(name))!=NULL){ + mapif_party_created(fd,account_id,char_id,NULL); + return 0; + } + p= aCalloc(1, sizeof(struct party)); + + p->party_id=party_newid++; + memcpy(p->name,name,NAME_LENGTH); + p->exp=0; + p->item=(item?1:0)|(item2?2:0); + p->itemc = 0; + + p->member[0].account_id=account_id; + p->member[0].char_id =char_id; + memcpy(p->member[0].name,nick,NAME_LENGTH-1); + p->member[0].map = map; + p->member[0].leader=1; + p->member[0].online=1; + p->member[0].lv=lv; + + if (inter_party_tosql(p->party_id,p,PS_CREATE|PS_ADDMEMBER,0)) { + mapif_party_created(fd,account_id,char_id,p); + mapif_party_info(fd,p); + } else //Failed to create party. + mapif_party_created(fd,account_id,char_id,NULL); + + return 0; +} +// パーティ情報要求 +int mapif_parse_PartyInfo(int fd,int party_id) +{ + struct party *p; + p = inter_party_fromsql(party_id); + + if (p) + mapif_party_info(fd,p); + else + mapif_party_noinfo(fd,party_id); + return 0; +} +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd, int party_id, int account_id, int char_id, char *nick, unsigned short map, int lv) { + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + + if(!p){ + mapif_party_memberadded(fd,party_id,account_id,char_id,1); + return 0; + } + + for(i=0;imember[i].account_id==0){ + int flag=0; + + p->member[i].account_id=account_id; + p->member[i].char_id=char_id; + memcpy(p->member[i].name,nick,NAME_LENGTH); + p->member[i].map = map; + p->member[i].leader=0; + p->member[i].online=1; + p->member[i].lv=lv; + mapif_party_memberadded(fd,party_id,account_id,char_id,0); + mapif_party_info(-1,p); + + if( p->exp && !party_check_exp_share(p) ){ + p->exp=0; + flag=0x01; + } + if(flag) + mapif_party_optionchanged(fd,p,0,0); + + inter_party_tosql(party_id, p, PS_ADDMEMBER, i); + return 0; + } + } + mapif_party_memberadded(fd,party_id,account_id,char_id,1); + return 0; +} +// パーティー設定変更要求 +int mapif_parse_PartyChangeOption(int fd,int party_id,int account_id,int exp,int flag) +{ + struct party *p; + //NOTE: No clue what that flag is about, in all observations so far it always comes as 0. [Skotlex] + flag = 0; + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + + p->exp=exp; + if( exp && !party_check_exp_share(p) ){ + flag|=0x01; + p->exp=0; + } + mapif_party_optionchanged(fd,p,account_id,flag); + inter_party_tosql(party_id, p, PS_BASIC, 0); + return 0; +} +// パーティ脱退要求 +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id) +{ + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + if (!p) { //Party does not exists? + sprintf(tmp_sql, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", char_db, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; + } + + for (i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + break; + } + if (i>= MAX_PARTY) + return 0; //Member not found? + + mapif_party_leaved(party_id, account_id, char_id); + + if (p->member[i].leader){ + int j; + for (j = 0; j < MAX_PARTY; j++) { + if (p->member[j].account_id > 0 && j != i) { + mapif_party_leaved(party_id, p->member[j].account_id, p->member[j].char_id); + p->member[j].account_id = 0; + } + p->member[i].account_id = 0; + } + //Party gets deleted on the check_empty call below. + } else { + inter_party_tosql(party_id,p,PS_DELMEMBER,i); + memset(&p->member[i], 0, sizeof(struct party_member)); + } + + if (party_check_empty(p) == 0) + mapif_party_info(-1,p); + return 0; +} +// When member goes to other map +int mapif_parse_PartyChangeMap(int fd, int party_id, int account_id, int char_id, unsigned short map, int online, int lv) +{ + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + if (p == NULL) + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + { + p->member[i].map = map; + p->member[i].online = online; + if (p->member[i].lv != lv) { + p->member[i].lv = lv; + if (p->exp && !party_check_exp_share(p)) { + p->exp = 0; + mapif_party_optionchanged(fd, p, 0, 0); + } + } + mapif_party_membermoved(p, i); + break; + } + } + return 0; +} +// パーティ解散要求 +int mapif_parse_BreakParty(int fd,int party_id) +{ + struct party *p; + + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + inter_party_tosql(party_id,p,PS_BREAK,0); + mapif_party_broken(fd,party_id); + return 0; +} +// パーティメッセージ送信 +int mapif_parse_PartyMessage(int fd,int party_id,int account_id,char *mes,int len) +{ + return mapif_party_message(party_id,account_id,mes,len, fd); +} +// パーティチェック要求 +int mapif_parse_PartyCheck(int fd,int party_id,int account_id,int char_id) +{ + return party_check_conflict(party_id,account_id,char_id); +} + +int mapif_parse_PartyLeaderChange(int fd,int party_id,int account_id,int char_id) +{ + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + + for (i = 0; i < MAX_PARTY; i++) + { + if(p->member[i].leader) + p->member[i].leader = 0; + if(p->member[i].account_id == account_id && p->member[i].char_id == char_id) { + p->member[i].leader = 1; + inter_party_tosql(party_id,p,PS_LEADER, i); + } + } + return 1; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)) { + case 0x3020: mapif_parse_CreateParty(fd, RFIFOL(fd,2), RFIFOL(fd,6),(char*)RFIFOP(fd,10), (char*)RFIFOP(fd,34), RFIFOW(fd,58), RFIFOW(fd,60), RFIFOB(fd,62), RFIFOB(fd,63)); break; + case 0x3021: mapif_parse_PartyInfo(fd, RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), (char*)RFIFOP(fd,14), RFIFOW(fd,38), RFIFOW(fd,40)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOW(fd,10), RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOW(fd,14), RFIFOB(fd,16), RFIFOW(fd,17)); break; + case 0x3026: mapif_parse_BreakParty(fd, RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), (char*)RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3029: mapif_parse_PartyLeaderChange(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + default: + return 0; + } + return 1; +} + +// サーバーから脱退要求(キャラ削除用) +int inter_party_leave(int party_id,int account_id, int char_id) +{ + return mapif_parse_PartyLeave(-1,party_id,account_id, char_id); +} + +int inter_party_CharOnline(int char_id, int party_id) { + struct party *p; + int i; + + if (party_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT party_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No party? + + sql_row = mysql_fetch_row(sql_res); + party_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (party_id == 0) + return 0; //No party... + + p = inter_party_fromsql(party_id); + if(!p) { + ShowError("Character %d's party %d not found!\n", char_id, party_id); + return 0; + } + + //Set member online + for(i=0; imember[i].char_id == char_id) { + p->member[i].online = 1; + break; + } + } + return 1; +} + +int inter_party_CharOffline(int char_id, int party_id) { + struct party *p=NULL; + int online_count=0, i; + + if (party_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT party_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No party? + + sql_row = mysql_fetch_row(sql_res); + party_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (party_id == 0) + return 0; //No party... + + //Character has a party, set character offline and check if they were the only member online + if ((p = inter_party_fromsql(party_id)) == NULL) + return 0; + + //Set member offline + for(i=0; i< MAX_PARTY; i++) { + if(p->member[i].char_id == char_id) + p->member[i].online = 0; + if(p->member[i].online) + online_count++; + } + + if(online_count == 0) + //Parties don't have any data that needs be saved at this point... so just remove it from memory. + idb_remove(party_db_, party_id); + return 1; +} diff --git a/src/char_sql/int_party.h b/src/char_sql/int_party.h new file mode 100644 index 000000000..353787a2d --- /dev/null +++ b/src/char_sql/int_party.h @@ -0,0 +1,14 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_parse_frommap(int fd); +int inter_party_sql_init(); +void inter_party_sql_final(); +int inter_party_leave(int party_id,int account_id, int char_id); +int inter_party_logged(int party_id, int account_id, int char_id); +int inter_party_CharOnline(int char_id, int party_id); +int inter_party_CharOffline(int char_id, int party_id); +#endif diff --git a/src/char_sql/int_pet.c b/src/char_sql/int_pet.c new file mode 100644 index 000000000..cc0e9330e --- /dev/null +++ b/src/char_sql/int_pet.c @@ -0,0 +1,351 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung + +#include +#include +#include + +#include "char.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" + +struct s_pet *pet_pt; +static int pet_newid = 100; + + + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet *p) { + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + char t_name[NAME_LENGTH*2]; + +// ShowInfo("Saving pet (%d)...\n",pet_id); + + jstrescapecpy(t_name, p->name); + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + sprintf(tmp_sql,"SELECT * FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) + //row reside -> updating + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + pet_db, p->class_, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id); + else //no row -> insert + sprintf(tmp_sql,"INSERT INTO `%s` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) VALUES ('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + pet_db, pet_id, p->class_, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (save_log) + ShowInfo("Pet saved %d - %s.\n", pet_id, p->name); + return 0; +} + +int inter_pet_fromsql(int pet_id, struct s_pet *p){ + +#ifdef NOISY + ShowInfo("Loading pet (%d)...\n",pet_id); +#endif + memset(p, 0, sizeof(struct s_pet)); + + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + sprintf(tmp_sql,"SELECT `pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate` FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + p->pet_id = pet_id; + p->class_ = atoi(sql_row[1]); + memcpy(p->name, sql_row[2],NAME_LENGTH-1); + p->account_id = atoi(sql_row[3]); + p->char_id = atoi(sql_row[4]); + p->level = atoi(sql_row[5]); + p->egg_id = atoi(sql_row[6]); + p->equip = atoi(sql_row[7]); + p->intimate = atoi(sql_row[8]); + p->hungry = atoi(sql_row[9]); + p->rename_flag = atoi(sql_row[10]); + p->incuvate = atoi(sql_row[11]); + } + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + mysql_free_result(sql_res); + + if (save_log) + ShowInfo("Pet loaded (%d - %s).\n", pet_id, p->name); + return 0; +} +//---------------------------------------------- + +int inter_pet_sql_init(){ + int i; + + //memory alloc + ShowDebug("interserver pet memory initialize.... (%d byte)\n",sizeof(struct s_pet)); + pet_pt = (struct s_pet*)aCalloc(sizeof(struct s_pet), 1); + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`", pet_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + exit(0); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + ShowStatus("total pet data: '%s'\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set pet_newid + sprintf (tmp_sql , "SELECT max(`pet_id`) FROM `%s`",pet_db ); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + pet_newid = atoi (sql_row[0])+1; //should SET MAX existing PET ID + 1 [Lupus] + mysql_free_result(sql_res); + } + + ShowDebug("set pet_newid: %d.\n",pet_newid); + + return 0; +} +void inter_pet_sql_final(){ + if (pet_pt) aFree(pet_pt); + return; +} +//---------------------------------- +int inter_pet_delete(int pet_id){ + ShowInfo("delete pet request: %d...\n",pet_id); + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; +} +//------------------------------------------------------ +int mapif_pet_created(int fd, int account_id, struct s_pet *p) +{ + WFIFOW(fd, 0) =0x3880; + WFIFOL(fd, 2) =account_id; + if(p!=NULL){ + WFIFOB(fd, 6)=0; + WFIFOL(fd, 7) =p->pet_id; + ShowInfo("int_pet: created pet %d - %s\n", p->pet_id, p->name); + }else{ + WFIFOB(fd, 6)=1; + WFIFOL(fd, 7)=0; + } + WFIFOSET(fd, 11); + + return 0; +} + +int mapif_pet_info(int fd, int account_id, struct s_pet *p){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=0; + memcpy(WFIFOP(fd, 9), p, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_pet_noinfo(int fd, int account_id){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=1; + memset(WFIFOP(fd, 9), 0, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_save_pet_ack(int fd, int account_id, int flag){ + WFIFOW(fd, 0) =0x3882; + WFIFOL(fd, 2) =account_id; + WFIFOB(fd, 6) =flag; + WFIFOSET(fd, 7); + + return 0; +} + +int mapif_delete_pet_ack(int fd, int flag){ + WFIFOW(fd, 0) =0x3883; + WFIFOB(fd, 2) =flag; + WFIFOSET(fd, 3); + + return 0; +} + +int mapif_create_pet(int fd, int account_id, int char_id, short pet_class, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name){ + + memset(pet_pt, 0, sizeof(struct s_pet)); + pet_pt->pet_id = pet_newid++; + memcpy(pet_pt->name, pet_name, NAME_LENGTH-1); + if(incuvate == 1) + pet_pt->account_id = pet_pt->char_id = 0; + else { + pet_pt->account_id = account_id; + pet_pt->char_id = char_id; + } + pet_pt->class_ = pet_class; + pet_pt->level = pet_lv; + pet_pt->egg_id = pet_egg_id; + pet_pt->equip = pet_equip; + pet_pt->intimate = intimate; + pet_pt->hungry = hungry; + pet_pt->rename_flag = rename_flag; + pet_pt->incuvate = incuvate; + + if(pet_pt->hungry < 0) + pet_pt->hungry = 0; + else if(pet_pt->hungry > 100) + pet_pt->hungry = 100; + if(pet_pt->intimate < 0) + pet_pt->intimate = 0; + else if(pet_pt->intimate > 1000) + pet_pt->intimate = 1000; + + inter_pet_tosql(pet_pt->pet_id,pet_pt); + + mapif_pet_created(fd, account_id, pet_pt); + + return 0; +} + +int mapif_load_pet(int fd, int account_id, int char_id, int pet_id){ + memset(pet_pt, 0, sizeof(struct s_pet)); + + inter_pet_fromsql(pet_id, pet_pt); + + if(pet_pt!=NULL) { + if(pet_pt->incuvate == 1) { + pet_pt->account_id = pet_pt->char_id = 0; + mapif_pet_info(fd, account_id, pet_pt); + } + else if(account_id == pet_pt->account_id && char_id == pet_pt->char_id) + mapif_pet_info(fd, account_id, pet_pt); + else + mapif_pet_noinfo(fd, account_id); + } + else + mapif_pet_noinfo(fd, account_id); + + return 0; +} + +int mapif_save_pet(int fd, int account_id, struct s_pet *data) { + //here process pet save request. + int len=RFIFOW(fd, 2); + if(sizeof(struct s_pet)!=len-8) { + ShowError("inter pet: data size error %d %d\n", sizeof(struct s_pet), len-8); + } + + else{ + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + inter_pet_tosql(data->pet_id,data); + mapif_save_pet_ack(fd, account_id, 0); + } + + return 0; +} + +int mapif_delete_pet(int fd, int pet_id){ + mapif_delete_pet_ack(fd, inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd){ + mapif_create_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOW(fd, 10), RFIFOW(fd, 12), RFIFOW(fd, 14), RFIFOW(fd, 16), RFIFOL(fd, 18), + RFIFOL(fd, 20), RFIFOB(fd, 22), RFIFOB(fd, 23), (char*)RFIFOP(fd, 24)); + return 0; +} + +int mapif_parse_LoadPet(int fd){ + mapif_load_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + return 0; +} + +int mapif_parse_SavePet(int fd){ + mapif_save_pet(fd, RFIFOL(fd, 4), (struct s_pet *) RFIFOP(fd, 8)); + return 0; +} + +int mapif_parse_DeletePet(int fd){ + mapif_delete_pet(fd, RFIFOL(fd, 2)); + return 0; +} + +int inter_pet_parse_frommap(int fd){ + switch(RFIFOW(fd, 0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char_sql/int_pet.h b/src/char_sql/int_pet.h new file mode 100644 index 000000000..d39574344 --- /dev/null +++ b/src/char_sql/int_pet.h @@ -0,0 +1,16 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +void inter_pet_sql_final(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); +int inter_pet_sql_init(); +//extern char pet_txt[256]; + +#endif diff --git a/src/char_sql/int_storage.c b/src/char_sql/int_storage.c new file mode 100644 index 000000000..1dac9dc51 --- /dev/null +++ b/src/char_sql/int_storage.c @@ -0,0 +1,364 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung + +#include +#include + +#include "char.h" +#include "itemdb.h" +#include "../common/showmsg.h" + +#define STORAGE_MEMINC 16 + +// reset by inter_config_read() +struct storage *storage_pt=NULL; +struct guild_storage *guild_storage_pt=NULL; + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +// storage data -> DB conversion +int storage_tosql(int account_id,struct storage *p){ + int i,j; +// int eqcount=1; +// int noteqcount=1; + int count=0; + struct itemtmp mapitem[MAX_GUILD_STORAGE]; + for(i=0;istorage_[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->storage_[i].id; + mapitem[count].nameid=p->storage_[i].nameid; + mapitem[count].amount = p->storage_[i].amount; + mapitem[count].equip = p->storage_[i].equip; + mapitem[count].identify = p->storage_[i].identify; + mapitem[count].refine = p->storage_[i].refine; + mapitem[count].attribute = p->storage_[i].attribute; + for(j=0; jstorage_[i].card[j]; + count++; + } + } + + memitemdata_to_sql(mapitem, count, account_id,TABLE_STORAGE); + + //printf ("storage dump to DB - id: %d (total: %d)\n", account_id, j); + return 0; +} + +// DB -> storage data conversion +int storage_fromsql(int account_id, struct storage *p){ + int i=0,j; + char * str_p = tmp_sql; + + memset(p,0,sizeof(struct storage)); //clean up memory + p->storage_amount = 0; + p->account_id = account_id; + + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + str_p += sprintf(str_p,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`"); + + for (j=0; jstorage_[i].id= atoi(sql_row[0]); + p->storage_[i].nameid= atoi(sql_row[1]); + p->storage_[i].amount= atoi(sql_row[2]); + p->storage_[i].equip= atoi(sql_row[3]); + p->storage_[i].identify= atoi(sql_row[4]); + p->storage_[i].refine= atoi(sql_row[5]); + p->storage_[i].attribute= atoi(sql_row[6]); + for (j=0; jstorage_[i].card[j]= atoi(sql_row[7+j]); + p->storage_amount = ++i; + } + mysql_free_result(sql_res); + } + + ShowInfo ("storage load complete from DB - id: %d (total: %d)\n", account_id, p->storage_amount); + return 1; +} + +// Save guild_storage data to sql +int guild_storage_tosql(int guild_id, struct guild_storage *p){ + int i,j; +// int eqcount=1; +// int noteqcount=1; + int count=0; + struct itemtmp mapitem[MAX_GUILD_STORAGE]; + for(i=0;istorage_[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->storage_[i].id; + mapitem[count].nameid=p->storage_[i].nameid; + mapitem[count].amount = p->storage_[i].amount; + mapitem[count].equip = p->storage_[i].equip; + mapitem[count].identify = p->storage_[i].identify; + mapitem[count].refine = p->storage_[i].refine; + mapitem[count].attribute = p->storage_[i].attribute; + for (j=0; jstorage_[i].card[j]; + count++; + } + } + + memitemdata_to_sql(mapitem, count, guild_id,TABLE_GUILD_STORAGE); + + ShowInfo ("guild storage save to DB - id: %d (total: %d)\n", guild_id,i); + return 0; +} + +// Load guild_storage data to mem +int guild_storage_fromsql(int guild_id, struct guild_storage *p){ + int i=0,j; + struct guild_storage *gs=guild_storage_pt; + char * str_p = tmp_sql; + p=gs; + + memset(p,0,sizeof(struct guild_storage)); //clean up memory + p->storage_amount = 0; + p->guild_id = guild_id; + + // storage {`guild_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + str_p += sprintf(str_p,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`"); + + for (j=0; jstorage_[i].id= atoi(sql_row[0]); + p->storage_[i].nameid= atoi(sql_row[1]); + p->storage_[i].amount= atoi(sql_row[2]); + p->storage_[i].equip= atoi(sql_row[3]); + p->storage_[i].identify= atoi(sql_row[4]); + p->storage_[i].refine= atoi(sql_row[5]); + p->storage_[i].attribute= atoi(sql_row[6]); + for (j=0; jstorage_[i].card[j] = atoi(sql_row[7+j]); + p->storage_amount = ++i; + if (i >= MAX_GUILD_STORAGE) + break; + } + mysql_free_result(sql_res); + } + ShowInfo ("guild storage load complete from DB - id: %d (total: %d)\n", guild_id, p->storage_amount); + return 0; +} + +//--------------------------------------------------------- +// storage data initialize +int inter_storage_sql_init(){ + + //memory alloc + ShowDebug("interserver storage memory initialize....(%d byte)\n",sizeof(struct storage)); + storage_pt = (struct storage*)aCalloc(sizeof(struct storage), 1); + guild_storage_pt = (struct guild_storage*)aCalloc(sizeof(struct guild_storage), 1); +// memset(storage_pt,0,sizeof(struct storage)); //Calloc sets stuff to 0 already. [Skotlex] +// memset(guild_storage_pt,0,sizeof(struct guild_storage)); + + return 1; +} +// storage data finalize +void inter_storage_sql_final() +{ + if (storage_pt) aFree(storage_pt); + if (guild_storage_pt) aFree(guild_storage_pt); + return; +} +// q?f[^? +int inter_storage_delete(int account_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id`='%d'",storage_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; +} +int inter_guild_storage_delete(int guild_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_storage_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive packet about storage data +int mapif_load_storage(int fd,int account_id){ + //load from DB + storage_fromsql(account_id, storage_pt); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),storage_pt,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// send ack to map server which is "storage data save ok." +int mapif_save_storage_ack(int fd,int account_id){ + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + int guild_exist=1; + WFIFOW(fd,0)=0x3818; + +#if 0 // innodb guilds should render this check unnecessary [Aru] + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free +#endif + if(guild_exist==1) { + guild_storage_fromsql(guild_id,guild_storage_pt); + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),guild_storage_pt,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive request about storage data +int mapif_parse_LoadStorage(int fd){ + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// storage data recive and save +int mapif_parse_SaveStorage(int fd){ + int account_id=RFIFOL(fd,4); + int len=RFIFOW(fd,2); + + if(sizeof(struct storage)!=len-8){ + ShowError("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + }else{ + memcpy(&storage_pt[0],RFIFOP(fd,8),sizeof(struct storage)); + storage_tosql(account_id, storage_pt); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} + +int mapif_parse_SaveGuildStorage(int fd) +{ + int guild_exist=1; + int guild_id=RFIFOL(fd,8); + int len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + ShowError("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { +#if 0 // Again, innodb key checks make the check pointless + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free +#endif + if(guild_exist==1) { + memcpy(guild_storage_pt,RFIFOP(fd,12),sizeof(struct guild_storage)); + guild_storage_tosql(guild_id,guild_storage_pt); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + + +int inter_storage_parse_frommap(int fd){ + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char_sql/int_storage.h b/src/char_sql/int_storage.h new file mode 100644 index 000000000..f3b56a6d1 --- /dev/null +++ b/src/char_sql/int_storage.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_sql_init(); +void inter_storage_sql_final(); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + + +//extern char storage_txt[256]; + +#endif diff --git a/src/char_sql/inter.c b/src/char_sql/inter.c new file mode 100644 index 000000000..0c6145bf9 --- /dev/null +++ b/src/char_sql/inter.c @@ -0,0 +1,789 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung + +#include +#include + +#include "char.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" + +#define WISDATA_TTL (60*1000) // Wisデータの生存時間(60秒) +#define WISDELLIST_MAX 256 // Wisデータ削除リストの要素数 + + +struct accreg { + int account_id, char_id; + int reg_num; + struct global_reg reg[MAX_REG_NUM]; +}; + +static struct accreg *accreg_pt; + + +int party_share_level = 10; +int kick_on_disconnect = 1; +MYSQL mysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +int sql_fields, sql_cnt; +char tmp_sql[65535]; + +MYSQL lmysql_handle; +MYSQL_RES* lsql_res ; +MYSQL_ROW lsql_row ; + +int char_server_port = 3306; +char char_server_ip[32] = "127.0.0.1"; +char char_server_id[32] = "ragnarok"; +char char_server_pw[32] = "ragnarok"; +char char_server_db[32] = "ragnarok"; +char default_codepage[32] = ""; //Feature by irmin. + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; + +char main_chat_nick[16] = "Main"; + +// sending packet list +// NOTE: This variable ain't used at all! And it's confusing.. where do I add that the length of packet 0x2b07 is 10? x.x [Skotlex] +int inter_send_packet_length[]={ + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// recv. packet list +int inter_recv_packet_length[]={ + -1,-1, 7,-1, -1,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3000-0x300f + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, //0x3010-0x301f + 64, 6,42,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, //0x3020-0x302f + -1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, //0x3030-0x303f + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3080-0x308f +}; + +struct WisData { + int id, fd, count,len; + unsigned long tick; + unsigned char src[24], dst[24], msg[512]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + +int inter_sql_test (void); + +//-------------------------------------------------------- +// Save registry to sql +int inter_accreg_tosql(int account_id, int char_id, struct accreg *reg, int type){ + + int j; + char temp_str[64]; //Needs be twice the source to ensure it fits [Skotlex] + char temp_str2[512]; + if (account_id<=0) return 0; + reg->account_id=account_id; + reg->char_id = char_id; + + switch (type) { + case 3: //Char Reg + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, char_id); + break; + case 2: //Account Reg + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, account_id); + break; + case 1: //Account2 Reg + ShowError("inter_accreg_tosql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n"); + return 0; + default: + ShowError("inter_accreg_tosql: Invalid type %d\n", type); + return 0; + + } + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (reg->reg_num<=0) return 0; + + for(j=0;jreg_num;j++){ + if(reg->reg[j].str != NULL){ + sprintf(tmp_sql,"INSERT INTO `%s` (`type`, `account_id`, `char_id`, `str`, `value`) VALUES ('%d','%d','%d','%s','%s')", + reg_db, type, type!=3?reg->account_id:0, type==3?reg->char_id:0, + jstrescapecpy(temp_str,reg->reg[j].str), jstrescapecpy(temp_str2,reg->reg[j].value)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + return 1; +} + +// Load account_reg from sql (type=2) +int inter_accreg_fromsql(int account_id,int char_id, struct accreg *reg, int type) +{ + int j=0; + if (reg==NULL) return 0; + memset(reg, 0, sizeof(struct accreg)); + reg->account_id=account_id; + reg->char_id=char_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + switch (type) { + case 3: //char reg + sprintf (tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, reg->char_id); + break; + case 2: //account reg + sprintf (tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, reg->account_id); + break; + case 1: //account2 reg + ShowError("inter_accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n"); + return 0; + } + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(j=0;(sql_row = mysql_fetch_row(sql_res));j++){ + strcpy(reg->reg[j].str, sql_row[0]); + strcpy(reg->reg[j].value, sql_row[1]); + } + mysql_free_result(sql_res); + } + reg->reg_num=j; + return 1; +} + +// Initialize +int inter_accreg_sql_init() +{ + CREATE(accreg_pt, struct accreg, 1); + return 0; + +} + +/*========================================== + * read config file + *------------------------------------------ + */ +int inter_config_read(const char *cfgName) { + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + ShowError("file not found: %s\n", cfgName); + return 1; + } + + ShowInfo("reading file %s...\n",cfgName); + + while(fgets(line, 1020, fp)){ + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + if(strcmpi(w1,"char_server_ip")==0){ + strcpy(char_server_ip, w2); + ShowStatus ("set char_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"char_server_port")==0){ + char_server_port=atoi(w2); + ShowStatus ("set char_server_port : %s\n",w2); + } + else if(strcmpi(w1,"char_server_id")==0){ + strcpy(char_server_id, w2); + ShowStatus ("set char_server_id : %s\n",w2); + } + else if(strcmpi(w1,"char_server_pw")==0){ + strcpy(char_server_pw, w2); + ShowStatus ("set char_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"char_server_db")==0){ + strcpy(char_server_db, w2); + ShowStatus ("set char_server_db : %s\n",w2); + } + else if(strcmpi(w1,"default_codepage")==0){ + strcpy(default_codepage, w2); + ShowStatus ("set default_codepage : %s\n",w2); + } + //Logins information to be read from the inter_athena.conf + //for character deletion (checks email in the loginDB) + + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + ShowStatus ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + ShowStatus ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + ShowStatus ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + ShowStatus ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + ShowStatus ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1,"kick_on_disconnect")==0){ + kick_on_disconnect=atoi(w2); + } + else if(strcmpi(w1,"party_share_level")==0){ + party_share_level=atoi(w2); + if(party_share_level < 0) party_share_level = 0; + } + else if(strcmpi(w1,"log_inter")==0){ + log_inter = atoi(w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + ShowStatus ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1, "main_chat_nick")==0){ // Main chat nick [LuzZza] + strcpy(main_chat_nick, w2); // + } + else if(strcmpi(w1,"import")==0){ + inter_config_read(w2); + } + } + fclose(fp); + + ShowInfo ("done reading %s.\n", cfgName); + + return 0; +} + +// Save interlog into sql +int inter_log(char *fmt,...) +{ + char str[255]; + char temp_str[510]; //Needs be twice as long as str[] //Skotlex + va_list ap; + va_start(ap,fmt); + + vsprintf(str,fmt,ap); + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `log`) VALUES (NOW(), '%s')",interlog_db, jstrescapecpy(temp_str,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + va_end(ap); + return 0; +} + + +// initialize +int inter_init(const char *file) +{ + //int i; + + ShowInfo ("interserver initialize...\n"); + inter_config_read(file); + + //DB connection initialized + mysql_init(&mysql_handle); + ShowInfo("Connect Character DB server.... (Character Server)\n"); + if(!mysql_real_connect(&mysql_handle, char_server_ip, char_server_id, char_server_pw, + char_server_db ,char_server_port, (char *)NULL, 0)) { + //pointer check + ShowFatalError("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else if (inter_sql_test()) { + ShowStatus("Connect Success! (Character Server)\n"); + } + + mysql_init(&lmysql_handle); + ShowInfo("Connect Character DB server.... (login server)\n"); + if(!mysql_real_connect(&lmysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db ,login_server_port, (char *)NULL, 0)) { + //pointer check + ShowFatalError("%s\n",mysql_error(&lmysql_handle)); + exit(1); + }else { + ShowStatus ("Connect Success! (Login Server)\n"); + } + if(strlen(default_codepage) > 0 ) { + sprintf( tmp_sql, "SET NAMES %s", default_codepage ); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + if (mysql_query(&lmysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&lmysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + wis_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + inter_guild_sql_init(); + inter_storage_sql_init(); + inter_party_sql_init(); + + inter_pet_sql_init(); + inter_accreg_sql_init(); + + //printf ("interserver timer initializing : %d sec...\n",autosave_interval); + //i=add_timer_interval(gettick()+autosave_interval,inter_save_timer,0,0,autosave_interval); + + return 0; +} + +int inter_sql_test (void) +{ + const char fields[][24] = { + "father", // version 1363 + "fame", // version 1491 + }; + char buf[1024] = ""; + int i; + + sprintf(tmp_sql, "EXPLAIN `%s`",char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + // store DB fields + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + strcat (buf, sql_row[0]); + strcat (buf, " "); + } + } + + // check DB strings + for (i = 0; i < (int)(sizeof(fields) / sizeof(fields[0])); i++) { + if(!strstr(buf, fields[i])) { + ShowSQL ("Field `%s` not be found in `%s`. Consider updating your database!\n", fields[i], char_db); + exit(1); + } + } + + mysql_free_result(sql_res); + + return 1; +} + +// finalize +void inter_final() { + wis_db->destroy(wis_db, NULL); + + inter_guild_sql_final(); + inter_storage_sql_final(); + inter_party_sql_final(); + inter_pet_sql_final(); + + if (accreg_pt) aFree(accreg_pt); + return; +} + +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + + +//-------------------------------------------------------- + +// GM message sending +int mapif_GMmessage(unsigned char *mes, int len, unsigned long color, int sfd) { + unsigned char buf[2048]; + + if (len > 2048) len = 2047; //Make it fit to avoid crashes. [Skotlex] + WBUFW(buf, 0) = 0x3800; + WBUFW(buf, 2) = len; + WBUFL(buf, 4) = color; + memcpy(WBUFP(buf, 8), mes, len-8); + mapif_sendallwos(sfd, buf, len); + return 0; +} + +// Wis sending +int mapif_wis_message(struct WisData *wd) { + unsigned char buf[2048]; + if (wd->len > 2047-56) wd->len = 2047-56; //Force it to fit to avoid crashes. [Skotlex] + + WBUFW(buf, 0) = 0x3801; + WBUFW(buf, 2) = 56 +wd->len; + WBUFL(buf, 4) = wd->id; + memcpy(WBUFP(buf, 8), wd->src, NAME_LENGTH); + memcpy(WBUFP(buf,32), wd->dst, NAME_LENGTH); + memcpy(WBUFP(buf,56), wd->msg, wd->len); + wd->count = mapif_sendall(buf,WBUFW(buf,2)); + + return 0; +} +// Wis sending result +int mapif_wis_end(struct WisData *wd,int flag) +{ + unsigned char buf[27]; + + WBUFW(buf, 0)=0x3802; + memcpy(WBUFP(buf, 2),wd->src,24); + WBUFB(buf,26)=flag; + mapif_send(wd->fd,buf,27); +// printf("inter server wis_end %d\n",flag); + return 0; +} + +int mapif_account_reg(int fd,unsigned char *src) +{ +// unsigned char buf[WBUFW(src,2)]; <- Hey, can this really be done? [Skotlex] + unsigned char *buf = aCalloc(1,WBUFW(src,2)); // [Lance] - Skot... Dynamic allocation is better :D + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0)=0x3804; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + aFree(buf); + return 0; +} + +// Send the requested account_reg +int mapif_account_reg_reply(int fd,int account_id,int char_id, int type) +{ + struct accreg *reg=accreg_pt; + inter_accreg_fromsql(account_id,char_id,reg,type); + + WFIFOW(fd,0)=0x3804; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=char_id; + WFIFOB(fd,12)=type; + if(reg->reg_num==0){ + WFIFOW(fd,2)=13; + }else{ + int i,p; + for (p=13,i = 0; i < reg->reg_num; i++) { + p+= sprintf(WFIFOP(fd,p), "%s", reg->reg[i].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf(WFIFOP(fd,p), "%s", reg->reg[i].value)+1; + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +int mapif_send_gmaccounts() +{ + int i, len = 4; + unsigned char buf[32000]; + + // forward the gm accounts to the map server + len = 4; + WBUFW(buf,0) = 0x2b15; + + for(i = 0; i < GM_num; i++) { + WBUFL(buf, len) = gm_account[i].account_id; + WBUFB(buf, len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WBUFW(buf, 2) = len; + mapif_sendall(buf, len); + + return 0; +} + +//Sends to map server the current max Account/Char id [Skotlex] +void mapif_send_maxid(int account_id, int char_id) +{ + unsigned char buf[12]; + + WBUFW(buf,0) = 0x2b07; + WBUFL(buf,2) = account_id; + WBUFL(buf,6) = char_id; + mapif_sendall(buf, 10); +} + +//Request to kick char from a certain map server. [Skotlex] +int mapif_disconnectplayer(int fd, int account_id, int char_id, int reason) +{ + if (fd < 0) + return -1; + + WFIFOW(fd,0) = 0x2b1f; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = reason; + WFIFOSET(fd,7); + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(DBKey key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata() { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + wis_db->foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = idb_get(wis_db, wis_dellist[i]); + ShowWarning("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst); + // removed. not send information after a timeout. Just no answer for the player + //mapif_wis_end(wd, 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + idb_remove(wis_db, wd->id); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- + +// GM message sending +int mapif_parse_GMmessage(int fd) +{ + mapif_GMmessage(RFIFOP(fd, 8), RFIFOW(fd, 2), RFIFOL(fd, 4), fd); + return 0; +} + + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) { + struct WisData* wd; + static int wisid = 0; + char name[NAME_LENGTH], t_name[NAME_LENGTH*2]; //Needs space to allocate names with escaped chars [Skotlex] + + if ( fd <= 0 ) {return 0;} // check if we have a valid fd + + if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) { + ShowWarning("inter: Wis message size too long.\n"); + return 0; + } else if (RFIFOW(fd,2)-52 <= 0) { // normaly, impossible, but who knows... + ShowError("inter: Wis message doesn't exist.\n"); + return 0; + } + memcpy(name, RFIFOP(fd,28), NAME_LENGTH); //Received name may be too large and not contain \0! [Skotlex] + name[NAME_LENGTH-1]= '\0'; + + sprintf (tmp_sql, "SELECT `name` FROM `%s` WHERE `name`='%s'", + char_db, jstrescapecpy(t_name, name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + // search if character exists before to ask all map-servers + if (!(sql_row = mysql_fetch_row(sql_res))) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(name, 0, NAME_LENGTH); + strncpy(name, sql_row[0], NAME_LENGTH); + // if source is destination, don't ask other servers. + if (strcmp((char*)RFIFOP(fd,4),name) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + CREATE(wd, struct WisData, 1); + + // Whether the failure of previous wisp/page transmission (timeout) + check_ttl_wisdata(); + + wd->id = ++wisid; + wd->fd = fd; + wd->len= RFIFOW(fd,2)-52; + memcpy(wd->src, RFIFOP(fd, 4), NAME_LENGTH); + memcpy(wd->dst, RFIFOP(fd,28), NAME_LENGTH); + memcpy(wd->msg, RFIFOP(fd,52), wd->len); + wd->tick = gettick(); + idb_put(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + //Freeing ... O.o + if(sql_res){ + mysql_free_result(sql_res); + } + + return 0; +} + + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id = RFIFOL(fd,2), flag = RFIFOB(fd,6); + struct WisData *wd = idb_get(wis_db, id); + + if (wd == NULL) + return 0; // This wisp was probably suppress before, because it was timeout of because of target was found on another map-server + + if ((--wd->count) <= 0 || flag != 1) { + mapif_wis_end(wd, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + idb_remove(wis_db, id); + } + + return 0; +} + +// Received wisp message from map-server for ALL gm (just copy the message and resends it to ALL map-servers) +int mapif_parse_WisToGM(int fd) { + unsigned char buf[2048]; // 0x3003/0x3803 .w .24B .w .?B + + ShowDebug("Sent packet back!\n"); + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf, 0) = 0x3803; + mapif_sendall(buf, RFIFOW(fd,2)); + + return 0; +} + +// Save account_reg into sql (type=2) +int mapif_parse_Registry(int fd) +{ + int j,p,len, max; + struct accreg *reg=accreg_pt; + + memset(accreg_pt,0,sizeof(struct accreg)); + switch (RFIFOB(fd, 12)) { + case 3: //Character registry + max = GLOBAL_REG_NUM; + break; + case 2: //Account Registry + max = ACCOUNT_REG_NUM; + break; + case 1: //Account2 registry, must be sent over to login server. + return save_accreg2(RFIFOP(fd,4), RFIFOW(fd,2)-4); + default: + return 1; + } + for(j=0,p=13;jreg[j].str,&len); + reg->reg[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf(RFIFOP(fd,p), "%255c%n",reg->reg[j].value,&len); + reg->reg[j].value[len]='\0'; + p +=len+1; + } + reg->reg_num=j; + + inter_accreg_tosql(RFIFOL(fd,4),RFIFOL(fd,8),reg, RFIFOB(fd,12)); + mapif_account_reg(fd,RFIFOP(fd,0)); // Send updated accounts to other map servers. + return 0; +} + +// Request the value of all registries. +int mapif_parse_RegistryRequest(int fd) +{ + RFIFOHEAD(fd); + //Load Char Registry + if (RFIFOB(fd,12)) + mapif_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6),3); + //Load Account Registry + if (RFIFOB(fd,11)) + mapif_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6),2); + //Ask Login Server for Account2 values. + if (RFIFOB(fd,10)) + request_accreg2(RFIFOL(fd,2),RFIFOL(fd,6)-2); + return 1; +} + +//-------------------------------------------------------- +int inter_parse_frommap(int fd) +{ + int cmd=RFIFOW(fd,0); + int len=0; + + // inter鯖管轄かを調べる + if(cmd < 0x3000 || cmd >= 0x3000 + (sizeof(inter_recv_packet_length)/ + sizeof(inter_recv_packet_length[0]) ) ) + return 0; + + if (inter_recv_packet_length[cmd-0x3000] == 0) //This is necessary, because otherwise we return 2 and the char server will just hang waiting for packets! [Skotlex] + return 0; + + // パケット長を調べる + if((len = inter_check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0) + return 2; + + switch(cmd){ + case 0x3000: mapif_parse_GMmessage(fd); break; + case 0x3001: mapif_parse_WisRequest(fd); break; + case 0x3002: mapif_parse_WisReply(fd); break; + case 0x3003: mapif_parse_WisToGM(fd); break; + case 0x3004: mapif_parse_Registry(fd); break; + case 0x3005: mapif_parse_RegistryRequest(fd); break; + default: + if(inter_party_parse_frommap(fd)) + break; + if(inter_guild_parse_frommap(fd)) + break; + if(inter_storage_parse_frommap(fd)) + break; + if(inter_pet_parse_frommap(fd)) + break; + return 0; + } + + RFIFOSKIP(fd, len); + return 1; +} + +// RFIFO check +int inter_check_length(int fd, int length) +{ + if(length==-1){ // v-len packet + if(RFIFOREST(fd)<4) // packet not yet + return 0; + length = RFIFOW(fd, 2); + } + + if((int)RFIFOREST(fd) < length) // packet not yet + return 0; + + return length; +} diff --git a/src/char_sql/inter.h b/src/char_sql/inter.h new file mode 100644 index 000000000..c1b40db01 --- /dev/null +++ b/src/char_sql/inter.h @@ -0,0 +1,58 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +void inter_final(); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); +int mapif_send_gmaccounts(); +void mapif_send_maxid(int, int); +int mapif_disconnectplayer(int fd, int account_id, int char_id, int reason); + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern int kick_on_disconnect; //For deciding whether characters are kicked or not on reconnections. [Skotlex] +extern char inter_log_filename[1024]; + +#ifdef __WIN32 +//Windows.h need to be included before mysql.h +#include +#endif +//add include for DBMS(mysql) +#include + +extern MYSQL mysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; +extern int sql_cnt; + +extern MYSQL lmysql_handle; +extern MYSQL_RES* lsql_res ; +extern MYSQL_ROW lsql_row ; + +extern int char_server_port; +extern char char_server_ip[32]; +extern char char_server_id[32]; +extern char char_server_pw[32]; +extern char char_server_db[32]; + +extern int login_db_server_port; +extern char login_db_server_ip[32]; +extern char login_db_server_id[32]; +extern char login_db_server_pw[32]; +extern char login_db_server_db[32]; + +extern int log_inter; + +extern char main_chat_nick[16]; + +#endif diff --git a/src/char_sql/itemdb.c b/src/char_sql/itemdb.c new file mode 100644 index 000000000..25c55e73d --- /dev/null +++ b/src/char_sql/itemdb.c @@ -0,0 +1,229 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "itemdb.h" +#include "db.h" +#include "inter.h" +#include "char.h" +#include "utils.h" +#include "../common/showmsg.h" + +#define MAX_RANDITEM 2000 + +// ** ITEMDB_OVERRIDE_NAME_VERBOSE ** +// 定義すると、itemdb.txtとgrfで名前が異なる場合、表示します. +//#define ITEMDB_OVERRIDE_NAME_VERBOSE 1 + +char item_db_db[256]="item_db"; // added to specify item_db sql table [Valaris] + +static struct dbt* item_db; + +static void* create_item(DBKey key, va_list args) { + struct item_data *id; + int nameid = key.i; + + CREATE(id, struct item_data, 1); + id->nameid = nameid; + if(nameid>500 && nameid<600) + id->type=0; //heal item + else if(nameid>600 && nameid<700) + id->type=2; //use item + else if((nameid>700 && nameid<1100) || + (nameid>7000 && nameid<8000)) + id->type=3; //correction + else if(nameid>=1750 && nameid<1771) + id->type=10; //arrow + else if(nameid>1100 && nameid<2000) + id->type=4; //weapon + else if((nameid>2100 && nameid<3000) || + (nameid>5000 && nameid<6000)) + id->type=5; //armor + else if(nameid>4000 && nameid<5000) + id->type=6; //card + else if(nameid>9000 && nameid<10000) + id->type=7; //egg + else if(nameid>10000) + id->type=8; //petequip + return id; +} +/*========================================== + * DBの検索 + *------------------------------------------ + */ +struct item_data* itemdb_search(int nameid) +{ + return idb_ensure(item_db,nameid,create_item); +} + +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + return 1; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip2(struct item_data *data) +{ + if(data) { + int type=data->type; + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + else + return 1; + } + return 0; +} + + + +/*========================================== + * アイテムデータベースの読み込み + *------------------------------------------ + */ +static int itemdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j; + char *str[128],*p,*np; + struct item_data *id; + + sprintf(line, "%s/item_db.txt", db_path); + fp=fopen(line,"r"); + if(fp==NULL){ + ShowError("can't read %s\n", str); + exit(1); + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,np=p=line;j<17 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p){ *p++=0; np=p; } + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000) + continue; + ln++; + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View + id=itemdb_search(nameid); + memcpy(id->name,str[1],ITEM_NAME_LENGTH-1); + memcpy(id->jname,str[2],ITEM_NAME_LENGTH-1); + id->type=atoi(str[3]); + + } + fclose(fp); + ShowStatus("done reading item_db.txt (count=%d)\n",ln); + return 0; +} + +static int itemdb_read_sqldb(void) // sql item_db read, shortened version of map-server item_db read [Valaris] +{ + unsigned int nameid; // Type should be "unsigned short int", but currently isn't for compatibility with numdb_insert() + struct item_data *id; + + // ---------- + + // Output query to retrieve all rows from the item database table + sprintf(tmp_sql, "SELECT * FROM `%s`", item_db_db); + + // Execute the query; if the query execution fails, output an error + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + // Store the query result + sql_res = mysql_store_result(&mysql_handle); + + // If the storage of the query result succeeded + if (sql_res) { + // Parse each row in the query result into sql_row + while ((sql_row = mysql_fetch_row(sql_res))) { + nameid = atoi(sql_row[0]); + + // If the identifier is not within the valid range, process the next row + if (nameid == 0 || nameid >= 20000) { // Should ">= 20000" be "> 20000"? + continue; + } + + // ---------- + + // Update/Insert row into the item database + id=itemdb_search(nameid); + + memcpy(id->name, sql_row[1], ITEM_NAME_LENGTH-1); + memcpy(id->jname, sql_row[2], ITEM_NAME_LENGTH-1); + + id->type = atoi(sql_row[3]); + } + + // If the retrieval failed, output an error + if (mysql_errno(&mysql_handle)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + ShowInfo("read %s done (count = %lu)\n", item_db_db, (unsigned long) mysql_num_rows(sql_res)); + + // Free the query result + mysql_free_result(sql_res); + } else { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + return 0; +} + +static int itemdb_final(DBKey key,void *data,va_list ap) +{ + struct item_data *id = (struct item_data*)data; + if(id->use_script) + aFree(id->use_script); + if(id->equip_script) + aFree(id->equip_script); + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +void do_final_itemdb(void) +{ + if(item_db){ + item_db->destroy(item_db,itemdb_final); + item_db=NULL; + } +} +int do_init_itemdb(void) +{ + item_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + if (db_use_sqldbs) // it db_use_sqldbs in inter config are yes, will read from item_db for char server display [Valaris] + itemdb_read_sqldb(); + else + itemdb_readdb(); + return 0; +} diff --git a/src/char_sql/itemdb.h b/src/char_sql/itemdb.h new file mode 100644 index 000000000..350e42b2c --- /dev/null +++ b/src/char_sql/itemdb.h @@ -0,0 +1,38 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ +#include "mmo.h" + +struct item_data { + int nameid; + char name[ITEM_NAME_LENGTH],jname[ITEM_NAME_LENGTH]; + int value_buy,value_sell,value_notdc,value_notoc; + int type; + int class_; + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + char *use_script; // 回復とかも全部この中でやろうかなと + char *equip_script; // 攻撃,防御の属性設定もこの中で可能かな? + char available; +}; + +struct item_data* itemdb_search(int nameid); +#define itemdb_type(n) itemdb_search(n)->type + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif diff --git a/src/char_sql/make.sh b/src/char_sql/make.sh new file mode 100644 index 000000000..d5f109f3e --- /dev/null +++ b/src/char_sql/make.sh @@ -0,0 +1,10 @@ +#!/bin/sh + rsqlt=`rm -rf *.o` + gcc -c char.c -I/usr/local/include/mysql/ + gcc -c int_guild.c -I/usr/local/include/mysql/ + gcc -c int_party.c -I/usr/local/include/mysql/ + gcc -c int_pet.c -I/usr/local/include/mysql/ + gcc -c int_storage.c -I/usr/local/include/mysql/ + gcc -c inter.c -I/usr/local/include/mysql/ + gcc -c itemdb.c -I../common/ + gcc -o ../char-server inter.o char.o int_pet.o int_storage.o int_guild.o int_party.o ../common/strlib.o itemdb.o ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o -L/usr/local/lib/mysql -lmysqlclient -lz -- cgit v1.2.3-70-g09d2